Update remaining builder stores to use new core API and handle errors properly

This commit is contained in:
Andrew Kingston 2022-01-24 14:32:27 +00:00
parent 453386696f
commit db3b4c0b8c
42 changed files with 740 additions and 573 deletions

View File

@ -109,9 +109,11 @@ export const getFrontendStore = () => {
theme: { theme: {
save: async theme => { save: async theme => {
const appId = get(store).appId const appId = get(store).appId
const metadata = { appId, theme }
try { try {
await API.saveAppMetadata(metadata) await API.saveAppMetadata({
appId,
metadata: { theme },
})
store.update(state => { store.update(state => {
state.theme = theme state.theme = theme
return state return state
@ -124,9 +126,11 @@ export const getFrontendStore = () => {
customTheme: { customTheme: {
save: async customTheme => { save: async customTheme => {
const appId = get(store).appId const appId = get(store).appId
const metadata = { appId, customTheme }
try { try {
await API.saveAppMetadata(metadata) await API.saveAppMetadata({
appId,
metadata: { customTheme },
})
store.update(state => { store.update(state => {
state.customTheme = customTheme state.customTheme = customTheme
return state return state

View File

@ -13,6 +13,7 @@
Detail, Detail,
Divider, Divider,
Layout, Layout,
notifications,
} from "@budibase/bbui" } from "@budibase/bbui"
import { auth } from "stores/portal" import { auth } from "stores/portal"
@ -45,20 +46,28 @@
improvements, improvements,
comment, comment,
}) })
try {
auth.updateSelf({ auth.updateSelf({
flags: { flags: {
feedbackSubmitted: true, feedbackSubmitted: true,
}, },
}) })
} catch (error) {
notifications.error("Error updating user")
}
dispatch("complete") dispatch("complete")
} }
function cancelFeedback() { function cancelFeedback() {
try {
auth.updateSelf({ auth.updateSelf({
flags: { flags: {
feedbackSubmitted: true, feedbackSubmitted: true,
}, },
}) })
} catch (error) {
notifications.error("Error updating user")
}
dispatch("complete") dispatch("complete")
} }
</script> </script>

View File

@ -1,5 +1,12 @@
<script> <script>
import { ModalContent, Modal, Icon, ColorPicker, Label } from "@budibase/bbui" import {
ModalContent,
Modal,
Icon,
ColorPicker,
Label,
notifications,
} from "@budibase/bbui"
import { apps } from "stores/portal" import { apps } from "stores/portal"
export let app export let app
@ -51,12 +58,16 @@
} }
const save = async () => { const save = async () => {
try {
await apps.update(app.instance._id, { await apps.update(app.instance._id, {
icon: { icon: {
name: selectedIcon, name: selectedIcon,
color: selectedColor, color: selectedColor,
}, },
}) })
} catch (error) {
notifications.error("Error updating app")
}
} }
</script> </script>

View File

@ -118,15 +118,18 @@
await auth.setInitInfo({}) await auth.setInitInfo({})
$goto(`/builder/app/${createdApp.instance._id}`) $goto(`/builder/app/${createdApp.instance._id}`)
} catch (error) { } catch (error) {
console.error(error) notifications.error("Error creating app")
notifications.error(error)
submitting = false submitting = false
} }
} }
async function onCancel() { async function onCancel() {
template = null template = null
try {
await auth.setInitInfo({}) await auth.setInitInfo({})
} catch (error) {
notifications.error("Error setting init info")
}
} }
</script> </script>

View File

@ -80,8 +80,7 @@
await apps.update(app.instance._id, { name: $values.name.trim() }) await apps.update(app.instance._id, { name: $values.name.trim() })
hide() hide()
} catch (error) { } catch (error) {
console.error(error) notifications.error("Error updating app")
notifications.error(error)
} }
} }

View File

@ -3,6 +3,7 @@
import { admin, auth } from "stores/portal" import { admin, auth } from "stores/portal"
import { onMount } from "svelte" import { onMount } from "svelte"
import { CookieUtils, Constants } from "@budibase/frontend-core" import { CookieUtils, Constants } from "@budibase/frontend-core"
import { notifications } from "@budibase/bbui"
let loaded = false let loaded = false
@ -41,9 +42,12 @@
if (user.tenantId !== urlTenantId) { if (user.tenantId !== urlTenantId) {
// user should not be here - play it safe and log them out // user should not be here - play it safe and log them out
try {
await auth.logout() await auth.logout()
await auth.setOrganisation(null) await auth.setOrganisation(null)
return } catch (error) {
// Swallow error and do nothing
}
} }
} else { } else {
// no user - set the org according to the url // no user - set the org according to the url
@ -52,17 +56,18 @@
} }
onMount(async () => { onMount(async () => {
try {
if ($params["?template"]) { if ($params["?template"]) {
await auth.setInitInfo({ init_template: $params["?template"] }) await auth.setInitInfo({ init_template: $params["?template"] })
} }
await auth.checkAuth() await auth.checkAuth()
await admin.init() await admin.init()
if (useAccountPortal && multiTenancyEnabled) { if (useAccountPortal && multiTenancyEnabled) {
await validateTenantId() await validateTenantId()
} }
} catch (error) {
notifications.error("Error initialising builder")
}
loaded = true loaded = true
}) })

View File

@ -31,17 +31,21 @@
adminUser.tenantId = tenantId adminUser.tenantId = tenantId
// Save the admin user // Save the admin user
await API.createAdminUser(adminUser) await API.createAdminUser(adminUser)
notifications.success(`Admin user created`) notifications.success("Admin user created")
await admin.init() await admin.init()
$goto("../portal") $goto("../portal")
} catch (err) { } catch (error) {
notifications.error(`Failed to create admin user: ${err}`) notifications.error("Failed to create admin user")
} }
} }
onMount(async () => { onMount(async () => {
if (!cloud) { if (!cloud) {
try {
await admin.checkImportComplete() await admin.checkImportComplete()
} catch (error) {
notifications.error("Error checking import status")
}
} }
}) })
</script> </script>

View File

@ -10,6 +10,7 @@
Icon, Icon,
Body, Body,
Modal, Modal,
notifications,
} from "@budibase/bbui" } from "@budibase/bbui"
import { onMount } from "svelte" import { onMount } from "svelte"
import { apps, organisation, auth, admin } from "stores/portal" import { apps, organisation, auth, admin } from "stores/portal"
@ -26,8 +27,12 @@
let changePasswordModal let changePasswordModal
onMount(async () => { onMount(async () => {
try {
await organisation.init() await organisation.init()
await apps.load() await apps.load()
} catch (error) {
notifications.error("Error loading apps")
}
loaded = true loaded = true
}) })
@ -44,6 +49,14 @@
function getUrl(app) { function getUrl(app) {
return !isCloud ? `/app/${encodeURIComponent(app.name)}` : `/${app.prodId}` return !isCloud ? `/app/${encodeURIComponent(app.name)}` : `/${app.prodId}`
} }
const logout = async () => {
try {
await auth.logout()
} catch (error) {
// Swallow error and do nothing
}
}
</script> </script>
{#if $auth.user && loaded} {#if $auth.user && loaded}
@ -79,7 +92,7 @@
Open developer mode Open developer mode
</MenuItem> </MenuItem>
{/if} {/if}
<MenuItem icon="LogOut" on:click={auth.logout}>Log out</MenuItem> <MenuItem icon="LogOut" on:click={logout}>Log out</MenuItem>
</ActionMenu> </ActionMenu>
</div> </div>
<Layout noPadding gap="XS"> <Layout noPadding gap="XS">

View File

@ -1,5 +1,5 @@
<script> <script>
import { ActionButton } from "@budibase/bbui" import { ActionButton, notifications } from "@budibase/bbui"
import OidcLogo from "assets/oidc-logo.png" import OidcLogo from "assets/oidc-logo.png"
import Auth0Logo from "assets/auth0-logo.png" import Auth0Logo from "assets/auth0-logo.png"
import MicrosoftLogo from "assets/microsoft-logo.png" import MicrosoftLogo from "assets/microsoft-logo.png"
@ -20,7 +20,11 @@
} }
onMount(async () => { onMount(async () => {
try {
await oidc.init() await oidc.init()
} catch (error) {
notifications.error("Error getting OIDC config")
}
}) })
$: src = !$oidc.logo $: src = !$oidc.logo

View File

@ -25,7 +25,11 @@
} }
onMount(async () => { onMount(async () => {
try {
await organisation.init() await organisation.init()
} catch (error) {
notifications.error("Error getting org config")
}
}) })
</script> </script>

View File

@ -2,6 +2,7 @@
import { redirect } from "@roxi/routify" import { redirect } from "@roxi/routify"
import { auth, admin } from "stores/portal" import { auth, admin } from "stores/portal"
import { onMount } from "svelte" import { onMount } from "svelte"
import { notifications } from "@budibase/bbui"
$: tenantSet = $auth.tenantSet $: tenantSet = $auth.tenantSet
$: multiTenancyEnabled = $admin.multiTenancy $: multiTenancyEnabled = $admin.multiTenancy
@ -17,8 +18,12 @@
} }
onMount(async () => { onMount(async () => {
try {
await admin.init() await admin.init()
await auth.checkQueryString() await auth.checkQueryString()
} catch (error) {
notifications.error("Error getting checklist")
}
loaded = true loaded = true
}) })
</script> </script>

View File

@ -31,7 +31,6 @@
username, username,
password, password,
}) })
if ($auth?.user?.forceResetPassword) { if ($auth?.user?.forceResetPassword) {
$goto("./reset") $goto("./reset")
} else { } else {
@ -39,8 +38,7 @@
$goto("../portal") $goto("../portal")
} }
} catch (err) { } catch (err) {
console.error(err) notifications.error(err.message ? err.message : "Invalid credentials")
notifications.error(err.message ? err.message : "Invalid Credentials")
} }
} }
@ -49,7 +47,11 @@
} }
onMount(async () => { onMount(async () => {
try {
await organisation.init() await organisation.init()
} catch (error) {
notifications.error("Error getting org config")
}
loaded = true loaded = true
}) })
</script> </script>

View File

@ -1,5 +1,13 @@
<script> <script>
import { Body, Button, Divider, Heading, Input, Layout } from "@budibase/bbui" import {
Body,
Button,
Divider,
Heading,
Input,
Layout,
notifications,
} from "@budibase/bbui"
import { goto } from "@roxi/routify" import { goto } from "@roxi/routify"
import { auth, admin } from "stores/portal" import { auth, admin } from "stores/portal"
import Logo from "assets/bb-emblem.svg" import Logo from "assets/bb-emblem.svg"
@ -13,6 +21,7 @@
$: useAccountPortal = cloud && !$admin.disableAccountPortal $: useAccountPortal = cloud && !$admin.disableAccountPortal
async function setOrg() { async function setOrg() {
try {
if (tenantId == null || tenantId === "") { if (tenantId == null || tenantId === "") {
tenantId = "default" tenantId = "default"
} }
@ -20,6 +29,9 @@
// re-init now org selected // re-init now org selected
await admin.init() await admin.init()
$goto("../") $goto("../")
} catch (error) {
notifications.error("Error setting organisation")
}
} }
function handleKeydown(evt) { function handleKeydown(evt) {

View File

@ -31,7 +31,11 @@
} }
onMount(async () => { onMount(async () => {
try {
await organisation.init() await organisation.init()
} catch (error) {
notifications.error("Error getting org config")
}
}) })
</script> </script>

View File

@ -10,14 +10,11 @@
async function acceptInvite() { async function acceptInvite() {
try { try {
const res = await users.acceptInvite(inviteCode, password) await users.acceptInvite(inviteCode, password)
if (!res) { notifications.success("Invitation accepted successfully")
throw new Error(res.message)
}
notifications.success(`User created.`)
$goto("../auth/login") $goto("../auth/login")
} catch (err) { } catch (error) {
notifications.error(err) notifications.error("Error accepting invitation")
} }
} }
</script> </script>

View File

@ -10,6 +10,7 @@
MenuItem, MenuItem,
Modal, Modal,
clickOutside, clickOutside,
notifications,
} from "@budibase/bbui" } from "@budibase/bbui"
import ConfigChecklist from "components/common/ConfigChecklist.svelte" import ConfigChecklist from "components/common/ConfigChecklist.svelte"
import { organisation, auth } from "stores/portal" import { organisation, auth } from "stores/portal"
@ -78,6 +79,14 @@
return menu return menu
} }
const logout = async () => {
try {
await auth.logout()
} catch (error) {
// Swallow error and do nothing
}
}
const showMobileMenu = () => (mobileMenuVisible = true) const showMobileMenu = () => (mobileMenuVisible = true)
const hideMobileMenu = () => (mobileMenuVisible = false) const hideMobileMenu = () => (mobileMenuVisible = false)
@ -87,7 +96,11 @@
if (!$auth.user?.builder?.global) { if (!$auth.user?.builder?.global) {
$redirect("../") $redirect("../")
} else { } else {
try {
await organisation.init() await organisation.init()
} catch (error) {
notifications.error("Error getting org config")
}
loaded = true loaded = true
} }
} }
@ -158,7 +171,7 @@
<MenuItem icon="UserDeveloper" on:click={() => $goto("../apps")}> <MenuItem icon="UserDeveloper" on:click={() => $goto("../apps")}>
Close developer mode Close developer mode
</MenuItem> </MenuItem>
<MenuItem icon="LogOut" on:click={auth.logout}>Log out</MenuItem> <MenuItem icon="LogOut" on:click={logout}>Log out</MenuItem>
</ActionMenu> </ActionMenu>
</div> </div>
</div> </div>

View File

@ -139,7 +139,6 @@
$goto(`/builder/app/${createdApp.instance._id}`) $goto(`/builder/app/${createdApp.instance._id}`)
} catch (error) { } catch (error) {
notifications.error("Error creating app") notifications.error("Error creating app")
console.error(error)
} }
} }
@ -248,18 +247,24 @@
} }
onMount(async () => { onMount(async () => {
try {
await apps.load() await apps.load()
await templates.load() await templates.load()
if ($templates?.length === 0) { if ($templates?.length === 0) {
notifications.error("There was a problem loading quick start templates.") notifications.error(
"There was a problem loading quick start templates."
)
} }
// if the portal is loaded from an external URL with a template param // If the portal is loaded from an external URL with a template param
const initInfo = await auth.getInitInfo() const initInfo = await auth.getInitInfo()
if (initInfo?.init_template) { if (initInfo?.init_template) {
creatingFromTemplate = true creatingFromTemplate = true
createAppFromTemplateUrl(initInfo.init_template) createAppFromTemplateUrl(initInfo.init_template)
return return
} }
} catch (error) {
notifications.error("Error loading apps and templates")
}
loaded = true loaded = true
}) })
</script> </script>

View File

@ -207,15 +207,18 @@
notifications.success(`Settings saved`) notifications.success(`Settings saved`)
analytics.captureEvent(Events.SSO.SAVED) analytics.captureEvent(Events.SSO.SAVED)
}) })
.catch(error => { .catch(() => {
notifications.error(`Failed to update auth settings`) notifications.error("Failed to update auth settings")
console.error(error.message)
}) })
} }
} }
onMount(async () => { onMount(async () => {
try {
await organisation.init() await organisation.init()
} catch (error) {
notifications.error("Error getting org config")
}
// Fetch Google config // Fetch Google config
let googleDoc let googleDoc

View File

@ -36,9 +36,9 @@
try { try {
// Save your template config // Save your template config
await email.templates.save(selectedTemplate) await email.templates.save(selectedTemplate)
notifications.success(`Template saved.`) notifications.success("Template saved")
} catch (err) { } catch (error) {
notifications.error(`Failed to update template settings. ${err}`) notifications.error("Failed to update template settings")
} }
} }

View File

@ -1,7 +1,15 @@
<script> <script>
import { onMount } from "svelte"
import { email } from "stores/portal" import { email } from "stores/portal"
import { notifications } from "@budibase/bbui"
email.templates.fetch() onMount(async () => {
try {
await email.templates.fetch()
} catch (error) {
notifications.error("Error fetching email templates")
}
})
</script> </script>
<slot /> <slot />

View File

@ -64,31 +64,43 @@
const apps = fetchData(`/api/global/roles`) const apps = fetchData(`/api/global/roles`)
async function deleteUser() { async function deleteUser() {
const res = await users.delete(userId) try {
if (res.status === 200) { await users.delete(userId)
notifications.success(`User ${$userFetch?.data?.email} deleted.`) notifications.success(`User ${$userFetch?.data?.email} deleted.`)
$goto("./") $goto("./")
} else { } catch (error) {
notifications.error(res?.message ? res.message : "Failed to delete user.") notifications.error("Error deleting user")
} }
} }
let toggleDisabled = false let toggleDisabled = false
async function updateUserFirstName(evt) { async function updateUserFirstName(evt) {
try {
await users.save({ ...$userFetch?.data, firstName: evt.target.value }) await users.save({ ...$userFetch?.data, firstName: evt.target.value })
await userFetch.refresh() await userFetch.refresh()
} catch (error) {
notifications.error("Error updating user")
}
} }
async function updateUserLastName(evt) { async function updateUserLastName(evt) {
try {
await users.save({ ...$userFetch?.data, lastName: evt.target.value }) await users.save({ ...$userFetch?.data, lastName: evt.target.value })
await userFetch.refresh() await userFetch.refresh()
} catch (error) {
notifications.error("Error updating user")
}
} }
async function toggleFlag(flagName, detail) { async function toggleFlag(flagName, detail) {
toggleDisabled = true toggleDisabled = true
try {
await users.save({ ...$userFetch?.data, [flagName]: { global: detail } }) await users.save({ ...$userFetch?.data, [flagName]: { global: detail } })
await userFetch.refresh() await userFetch.refresh()
} catch (error) {
notifications.error("Error updating user")
}
toggleDisabled = false toggleDisabled = false
} }

View File

@ -21,12 +21,12 @@
const [email, error, touched] = createValidationStore("", emailValidator) const [email, error, touched] = createValidationStore("", emailValidator)
async function createUserFlow() { async function createUserFlow() {
try {
const res = await users.invite({ email: $email, builder, admin }) const res = await users.invite({ email: $email, builder, admin })
if (res.status) {
notifications.error(res.message)
} else {
notifications.success(res.message) notifications.success(res.message)
analytics.captureEvent(Events.USER.INVITE, { type: selected }) analytics.captureEvent(Events.USER.INVITE, { type: selected })
} catch (error) {
notifications.error("Error inviting user")
} }
} }
</script> </script>

View File

@ -16,17 +16,17 @@
admin = false admin = false
async function createUser() { async function createUser() {
const res = await users.create({ try {
await users.create({
email: $email, email: $email,
password, password,
builder, builder,
admin, admin,
forceResetPassword: true, forceResetPassword: true,
}) })
if (res.status) {
notifications.error(res.message)
} else {
notifications.success("Successfully created user") notifications.success("Successfully created user")
} catch (error) {
notifications.error("Error creating user")
} }
} }
</script> </script>

View File

@ -10,16 +10,16 @@
const password = Math.random().toString(36).substr(2, 20) const password = Math.random().toString(36).substr(2, 20)
async function resetPassword() { async function resetPassword() {
const res = await users.save({ try {
await users.save({
...user, ...user,
password, password,
forceResetPassword: true, forceResetPassword: true,
}) })
if (res.status) { notifications.success("Password reset successfully")
notifications.error(res.message)
} else {
notifications.success("Password reset.")
dispatch("update") dispatch("update")
} catch (error) {
notifications.error("Error resetting password")
} }
} }
</script> </script>

View File

@ -18,20 +18,20 @@
let selectedRole = user?.roles?.[app?._id] let selectedRole = user?.roles?.[app?._id]
async function updateUserRoles() { async function updateUserRoles() {
let res try {
if (selectedRole === NO_ACCESS) { if (selectedRole === NO_ACCESS) {
// remove the user role // Remove the user role
const filteredRoles = { ...user.roles } const filteredRoles = { ...user.roles }
delete filteredRoles[app?._id] delete filteredRoles[app?._id]
res = await users.save({ await users.save({
...user, ...user,
roles: { roles: {
...filteredRoles, ...filteredRoles,
}, },
}) })
} else { } else {
// add the user role // Add the user role
res = await users.save({ await users.save({
...user, ...user,
roles: { roles: {
...user.roles, ...user.roles,
@ -39,12 +39,10 @@
}, },
}) })
} }
if (res.status === 400) {
notifications.error("Failed to update role")
} else {
notifications.success("Role updated") notifications.success("Role updated")
dispatch("update") dispatch("update")
} catch (error) {
notifications.error("Failed to update role")
} }
} }
</script> </script>

View File

@ -11,13 +11,13 @@
Label, Label,
Layout, Layout,
Modal, Modal,
notifications,
} from "@budibase/bbui" } from "@budibase/bbui"
import TagsRenderer from "./_components/TagsTableRenderer.svelte" import TagsRenderer from "./_components/TagsTableRenderer.svelte"
import AddUserModal from "./_components/AddUserModal.svelte" import AddUserModal from "./_components/AddUserModal.svelte"
import BasicOnboardingModal from "./_components/BasicOnboardingModal.svelte" import BasicOnboardingModal from "./_components/BasicOnboardingModal.svelte"
import { users } from "stores/portal" import { users } from "stores/portal"
import { onMount } from "svelte"
users.init()
const schema = { const schema = {
email: {}, email: {},
@ -47,6 +47,14 @@
createUserModal.hide() createUserModal.hide()
basicOnboardingModal.show() basicOnboardingModal.show()
} }
onMount(async () => {
try {
await users.init()
} catch (error) {
notifications.error("Error getting user list")
}
})
</script> </script>
<Layout noPadding> <Layout noPadding>

View File

@ -44,6 +44,7 @@
async function saveConfig() { async function saveConfig() {
loading = true loading = true
try {
// Upload logo if required // Upload logo if required
if ($values.logo && !$values.logo.url) { if ($values.logo && !$values.logo.url) {
await uploadLogo($values.logo) await uploadLogo($values.logo)
@ -62,6 +63,9 @@
// Update settings // Update settings
await organisation.save(config) await organisation.save(config)
} catch (error) {
notifications.error("Error saving org config")
}
loading = false loading = false
} }
</script> </script>

View File

@ -23,7 +23,6 @@ export function createAdminStore() {
const admin = writable(DEFAULT_CONFIG) const admin = writable(DEFAULT_CONFIG)
async function init() { async function init() {
try {
const tenantId = get(auth).tenantId const tenantId = get(auth).tenantId
const checklist = await API.getChecklist(tenantId) const checklist = await API.getChecklist(tenantId)
const totalSteps = Object.keys(checklist).length const totalSteps = Object.keys(checklist).length
@ -37,51 +36,24 @@ export function createAdminStore() {
store.onboardingProgress = (completedSteps / totalSteps) * 100 store.onboardingProgress = (completedSteps / totalSteps) * 100
return store return store
}) })
} catch (error) {
admin.update(store => {
store.checklist = null
return store
})
}
} }
async function checkImportComplete() { async function checkImportComplete() {
try {
const result = await API.checkImportComplete() const result = await API.checkImportComplete()
admin.update(store => { admin.update(store => {
store.importComplete = result ? result.imported : false store.importComplete = result ? result.imported : false
return store return store
}) })
} catch (error) {
admin.update(store => {
store.importComplete = false
return store
})
}
} }
async function getEnvironment() { async function getEnvironment() {
let multiTenancyEnabled = false
let cloud = false
let disableAccountPortal = false
let accountPortalUrl = ""
let isDev = false
try {
const environment = await API.getEnvironment() const environment = await API.getEnvironment()
multiTenancyEnabled = environment.multiTenancy
cloud = environment.cloud
disableAccountPortal = environment.disableAccountPortal
accountPortalUrl = environment.accountPortalUrl
isDev = environment.isDev
} catch (err) {
// Just let it stay disabled
}
admin.update(store => { admin.update(store => {
store.multiTenancy = multiTenancyEnabled store.multiTenancy = environment.multiTenancy
store.cloud = cloud store.cloud = environment.cloud
store.disableAccountPortal = disableAccountPortal store.disableAccountPortal = environment.disableAccountPortal
store.accountPortalUrl = accountPortalUrl store.accountPortalUrl = environment.accountPortalUrl
store.isDev = isDev store.isDev = environment.isDev
return store return store
}) })
} }

View File

@ -1,7 +1,6 @@
import { writable } from "svelte/store" import { writable } from "svelte/store"
import { get } from "builderStore/api"
import { AppStatus } from "../../constants" import { AppStatus } from "../../constants"
import api from "../../builderStore/api" import { API } from "api"
const extractAppId = id => { const extractAppId = id => {
const split = id?.split("_") || [] const split = id?.split("_") || []
@ -12,10 +11,8 @@ export function createAppStore() {
const store = writable([]) const store = writable([])
async function load() { async function load() {
try { const json = await API.getApps()
const res = await get(`/api/applications?status=all`) if (Array.isArray(json)) {
const json = await res.json()
if (res.ok && Array.isArray(json)) {
// Merge apps into one sensible list // Merge apps into one sensible list
let appMap = {} let appMap = {}
let devApps = json.filter(app => app.status === AppStatus.DEV) let devApps = json.filter(app => app.status === AppStatus.DEV)
@ -59,20 +56,15 @@ export function createAppStore() {
} else { } else {
store.set([]) store.set([])
} }
return json
} catch (error) {
store.set([])
}
} }
async function update(appId, value) { async function update(appId, value) {
console.log({ value }) await API.saveAppMetadata({
const response = await api.put(`/api/applications/${appId}`, { ...value }) appId,
if (response.status === 200) { metadata: value,
})
store.update(state => { store.update(state => {
const updatedAppIndex = state.findIndex( const updatedAppIndex = state.findIndex(app => app.instance._id === appId)
app => app.instance._id === appId
)
if (updatedAppIndex !== -1) { if (updatedAppIndex !== -1) {
let updatedApp = state[updatedAppIndex] let updatedApp = state[updatedAppIndex]
updatedApp = { ...updatedApp, ...value } updatedApp = { ...updatedApp, ...value }
@ -80,9 +72,6 @@ export function createAppStore() {
} }
return state return state
}) })
} else {
throw new Error("Error updating name")
}
} }
return { return {

View File

@ -1,5 +1,5 @@
import { derived, writable, get } from "svelte/store" import { derived, writable, get } from "svelte/store"
import api from "../../builderStore/api" import { API } from "api"
import { admin } from "stores/portal" import { admin } from "stores/portal"
import analytics from "analytics" import analytics from "analytics"
@ -83,7 +83,7 @@ export function createAuthStore() {
} }
async function setInitInfo(info) { async function setInitInfo(info) {
await api.post(`/api/global/auth/init`, info) await API.setInitInfo(info)
auth.update(store => { auth.update(store => {
store.initInfo = info store.initInfo = info
return store return store
@ -99,13 +99,12 @@ export function createAuthStore() {
} }
async function getInitInfo() { async function getInitInfo() {
const response = await api.get(`/api/global/auth/init`) const info = await API.getInitInfo()
const json = response.json()
auth.update(store => { auth.update(store => {
store.initInfo = json store.initInfo = info
return store return store
}) })
return json return info
} }
return { return {
@ -124,77 +123,43 @@ export function createAuthStore() {
await setOrganisation(tenantId) await setOrganisation(tenantId)
}, },
checkAuth: async () => { checkAuth: async () => {
const response = await api.get("/api/global/users/self") const user = await API.fetchBuilderSelf()
if (response.status !== 200) { setUser(user)
setUser(null)
} else {
const json = await response.json()
setUser(json)
}
}, },
login: async creds => { login: async creds => {
const tenantId = get(store).tenantId const tenantId = get(store).tenantId
const response = await api.post( const response = await API.logIn({
`/api/global/auth/${tenantId}/login`, username: creds.username,
creds password: creds.password,
) tenantId,
const json = await response.json() })
if (response.status === 200) { setUser(response.user)
setUser(json.user)
} else {
throw new Error(json.message ? json.message : "Invalid credentials")
}
return json
}, },
logout: async () => { logout: async () => {
const response = await api.post(`/api/global/auth/logout`) await API.logOut()
if (response.status !== 200) {
throw "Unable to create logout"
}
await response.json()
await setInitInfo({}) await setInitInfo({})
setUser(null) setUser(null)
setPostLogout() setPostLogout()
}, },
updateSelf: async fields => { updateSelf: async fields => {
const newUser = { ...get(auth).user, ...fields } const newUser = { ...get(auth).user, ...fields }
const response = await api.post("/api/global/users/self", newUser) await API.updateSelf(newUser)
if (response.status === 200) {
setUser(newUser) setUser(newUser)
} else {
throw "Unable to update user details"
}
}, },
forgotPassword: async email => { forgotPassword: async email => {
const tenantId = get(store).tenantId const tenantId = get(store).tenantId
const response = await api.post(`/api/global/auth/${tenantId}/reset`, { await API.requestForgotPassword({
tenantId,
email, email,
}) })
if (response.status !== 200) {
throw "Unable to send email with reset link"
}
await response.json()
}, },
resetPassword: async (password, code) => { resetPassword: async (password, resetCode) => {
const tenantId = get(store).tenantId const tenantId = get(store).tenantId
const response = await api.post( await API.resetPassword({
`/api/global/auth/${tenantId}/reset/update`, tenantId,
{
password, password,
resetCode: code, resetCode,
} })
)
if (response.status !== 200) {
throw "Unable to reset password"
}
await response.json()
},
createUser: async user => {
const response = await api.post(`/api/global/users`, user)
if (response.status !== 200) {
throw "Unable to create user"
}
await response.json()
}, },
} }
} }

View File

@ -1,6 +1,5 @@
import { writable } from "svelte/store" import { writable } from "svelte/store"
import { API } from "api" import { API } from "api"
import { notifications } from "@budibase/bbui"
export function createEmailStore() { export function createEmailStore() {
const store = writable({}) const store = writable({})
@ -9,21 +8,15 @@ export function createEmailStore() {
subscribe: store.subscribe, subscribe: store.subscribe,
templates: { templates: {
fetch: async () => { fetch: async () => {
try { // Fetch the email template definitions and templates
// fetch the email template definitions and templates
const definitions = await API.getEmailTemplateDefinitions() const definitions = await API.getEmailTemplateDefinitions()
const templates = await API.getEmailTemplates() const templates = await API.getEmailTemplates()
store.set({ store.set({
definitions, definitions,
templates, templates,
}) })
} catch (error) {
notifications.error("Error fetching email templates")
store.set({})
}
}, },
save: async template => { save: async template => {
try {
// Save your template config // Save your template config
const savedTemplate = await API.saveEmailTemplate(template) const savedTemplate = await API.saveEmailTemplate(template)
template._rev = savedTemplate._rev template._rev = savedTemplate._rev
@ -35,9 +28,6 @@ export function createEmailStore() {
state.templates.splice(currentIdx, 1, template) state.templates.splice(currentIdx, 1, template)
return state return state
}) })
} catch (error) {
notifications.error("Error saving email template")
}
}, },
}, },
} }

View File

@ -11,10 +11,11 @@ const OIDC_CONFIG = {
export function createOidcStore() { export function createOidcStore() {
const store = writable(OIDC_CONFIG) const store = writable(OIDC_CONFIG)
const { set, subscribe } = store const { set, subscribe } = store
return {
async function init() { subscribe,
set,
init: async () => {
const tenantId = get(auth).tenantId const tenantId = get(auth).tenantId
try {
const config = await API.getOIDCConfig(tenantId) const config = await API.getOIDCConfig(tenantId)
if (Object.keys(config || {}).length) { if (Object.keys(config || {}).length) {
// Just use the first config for now. // Just use the first config for now.
@ -23,15 +24,7 @@ export function createOidcStore() {
} else { } else {
set(OIDC_CONFIG) set(OIDC_CONFIG)
} }
} catch (error) { },
set(OIDC_CONFIG)
}
}
return {
subscribe,
set,
init,
} }
} }

View File

@ -1,5 +1,5 @@
import { writable, get } from "svelte/store" import { writable, get } from "svelte/store"
import api from "builderStore/api" import { API } from "api"
import { auth } from "stores/portal" import { auth } from "stores/portal"
const DEFAULT_CONFIG = { const DEFAULT_CONFIG = {
@ -19,35 +19,23 @@ export function createOrganisationStore() {
async function init() { async function init() {
const tenantId = get(auth).tenantId const tenantId = get(auth).tenantId
const res = await api.get(`/api/global/configs/public?tenantId=${tenantId}`) const tenant = await API.getTenantConfig(tenantId)
const json = await res.json() set({ ...DEFAULT_CONFIG, ...tenant.config, _rev: tenant._rev })
if (json.status === 400) {
set(DEFAULT_CONFIG)
} else {
set({ ...DEFAULT_CONFIG, ...json.config, _rev: json._rev })
}
} }
async function save(config) { async function save(config) {
// delete non-persisted fields // Delete non-persisted fields
const storeConfig = get(store) const storeConfig = get(store)
delete storeConfig.oidc delete storeConfig.oidc
delete storeConfig.google delete storeConfig.google
delete storeConfig.oidcCallbackUrl delete storeConfig.oidcCallbackUrl
delete storeConfig.googleCallbackUrl delete storeConfig.googleCallbackUrl
await API.saveConfig({
const res = await api.post("/api/global/configs", {
type: "settings", type: "settings",
config: { ...get(store), ...config }, config: { ...get(store), ...config },
_rev: get(store)._rev, _rev: get(store)._rev,
}) })
const json = await res.json()
if (json.status) {
return json
}
await init() await init()
return { status: 200 }
} }
return { return {

View File

@ -1,18 +1,15 @@
import { writable } from "svelte/store" import { writable } from "svelte/store"
import api from "builderStore/api" import { API } from "api"
export function templatesStore() { export function templatesStore() {
const { subscribe, set } = writable([]) const { subscribe, set } = writable([])
async function load() {
const response = await api.get("/api/templates?type=app")
const json = await response.json()
set(json)
}
return { return {
subscribe, subscribe,
load, load: async () => {
const templates = await API.getAppTemplates()
set(templates)
},
} }
} }

View File

@ -1,38 +1,28 @@
import { writable } from "svelte/store" import { writable } from "svelte/store"
import api, { post } from "builderStore/api" import { API } from "api"
import { update } from "lodash" import { update } from "lodash"
export function createUsersStore() { export function createUsersStore() {
const { subscribe, set } = writable([]) const { subscribe, set } = writable([])
async function init() { async function init() {
const response = await api.get(`/api/global/users`) const users = await API.getUsers()
const json = await response.json() set(users)
set(json)
} }
async function invite({ email, builder, admin }) { async function invite({ email, builder, admin }) {
const body = { email, userInfo: {} } await API.inviteUser({
if (admin) { email,
body.userInfo.admin = { builder,
global: true, admin,
} })
}
if (builder) {
body.userInfo.builder = {
global: true,
}
}
const response = await api.post(`/api/global/users/invite`, body)
return await response.json()
} }
async function acceptInvite(inviteCode, password) { async function acceptInvite(inviteCode, password) {
const response = await api.post("/api/global/users/invite/accept", { await API.acceptInvite({
inviteCode, inviteCode,
password, password,
}) })
return await response.json()
} }
async function create({ async function create({
@ -56,29 +46,17 @@ export function createUsersStore() {
if (admin) { if (admin) {
body.admin = { global: true } body.admin = { global: true }
} }
const response = await api.post("/api/global/users", body) await API.saveUser(body)
await init() await init()
return await response.json()
} }
async function del(id) { async function del(id) {
const response = await api.delete(`/api/global/users/${id}`) await API.deleteUser(id)
update(users => users.filter(user => user._id !== id)) update(users => users.filter(user => user._id !== id))
const json = await response.json()
return {
...json,
status: response.status,
}
} }
async function save(data) { async function save(data) {
try { await API.saveUser(data)
const res = await post(`/api/global/users`, data)
return await res.json()
} catch (error) {
console.log(error)
return error
}
} }
return { return {

View File

@ -10,14 +10,12 @@ export const buildAppEndpoints = API => ({
/** /**
* Saves and patches metadata about an app. * Saves and patches metadata about an app.
* @param appId the ID of the app to update
* @param metadata the app metadata to save * @param metadata the app metadata to save
*/ */
saveAppMetadata: async metadata => { saveAppMetadata: async ({ appId, metadata }) => {
if (!metadata?.appId) {
throw API.error("App metadata must have an appId set")
}
return await API.put({ return await API.put({
url: `/api/applications/${metadata.appId}`, url: `/api/applications/${appId}`,
body: metadata, body: metadata,
}) })
}, },
@ -132,4 +130,13 @@ export const buildAppEndpoints = API => ({
url: `/api/applications/${appId}/sync`, url: `/api/applications/${appId}/sync`,
}) })
}, },
/**
* Gets a list of apps.
*/
getApps: async () => {
return await API.get({
url: "/api/applications?status=all",
})
},
}) })

View File

@ -1,18 +1,15 @@
export const buildAuthEndpoints = API => ({ export const buildAuthEndpoints = API => ({
/** /**
* Performs a login request. * Performs a login request.
* @param tenantId the ID of the tenant to log in to
* @param username the username (email)
* @param password the password
*/ */
logIn: async ({ email, password }) => { logIn: async ({ tenantId, username, password }) => {
if (!email) {
return API.error("Please enter your email")
}
if (!password) {
return API.error("Please enter your password")
}
return await API.post({ return await API.post({
url: "/api/global/auth", url: `/api/global/auth/${tenantId}/login`,
body: { body: {
username: email, username,
password, password,
}, },
}) })
@ -28,137 +25,52 @@ export const buildAuthEndpoints = API => ({
}, },
/** /**
* Fetches the currently logged in user object * Sets initialisation info.
* @param info the info to set
*/ */
fetchSelf: async () => { setInitInfo: async info => {
return await API.get({
url: "/api/self",
})
},
/**
* Creates a user for an app.
* @param user the user to create
*/
createAppUser: async user => {
return await API.post({ return await API.post({
url: "/api/users/metadata", url: "/api/global/auth/init",
body: user, body: info,
}) })
}, },
/** /**
* Updates the current user metadata. * Gets the initialisation info.
* @param metadata the metadata to save
*/ */
updateOwnMetadata: async metadata => { getInitInfo: async () => {
return await API.get({
url: "/api/global/auth/init",
})
},
/**
* Sends a password reset email.
* @param tenantId the ID of the tenant the user is in
* @param email the email address of the user
*/
requestForgotPassword: async ({ tenantId, email }) => {
return await API.post({ return await API.post({
url: "/api/users/metadata/self", url: `/api/global/auth/${tenantId}/reset`,
body: metadata, body: {
email,
},
}) })
}, },
/** /**
* Creates an admin user. * Resets a user's password.
* @param adminUser the admin user to create * @param tenantId the ID of the tenant the user is in
* @param password the new password to set
* @param resetCode the reset code to authenticate the request
*/ */
createAdminUser: async adminUser => { resetPassword: async ({ tenantId, password, resetCode }) => {
return await API.post({ return await API.post({
url: "/api/global/users/init", url: `/api/global/auth/${tenantId}/reset/update`,
body: adminUser, body: {
}) password,
resetCode,
}, },
/**
* Saves a global config.
* @param config the config to save
*/
saveConfig: async config => {
return await API.post({
url: "/api/global/configs",
body: config,
})
},
/**
* Gets a global config of a certain type.
* @param type the type to fetch
*/
getConfig: async type => {
return await API.get({
url: `/api/global/configs/${type}`,
})
},
/**
* Gets the OIDC config for a certain tenant.
* @param tenantId the tenant ID to get the config for
*/
getOIDCConfig: async tenantId => {
return await API.get({
url: `/api/global/configs/public/oidc?tenantId=${tenantId}`,
})
},
/**
* Gets the checklist for a specific tenant.
* @param tenantId the tenant ID to get the checklist for
*/
getChecklist: async tenantId => {
return await API.get({
url: `/api/global/configs/checklist?tenantId=${tenantId}`,
})
},
/**
* TODO: find out what this is
*/
checkImportComplete: async () => {
return await API.get({
url: "/api/cloud/import/complete",
})
},
/**
* Gets the current environment details.
*/
getEnvironment: async () => {
return await API.get({
url: "/api/system/environment",
})
},
/**
* Updates the company logo for the environment.
* @param data the logo form data
*/
uploadLogo: async data => {
return await API.post({
url: "/api/global/configs/upload/settings/logoUrl",
body: data,
json: false,
})
},
/**
* Uploads a logo for an OIDC provider.
* @param name the name of the OIDC provider
* @param data the logo form data to upload
*/
uploadOIDCLogo: async ({ name, data }) => {
return await API.post({
url: `/api/global/configs/upload/logos_oidc/${name}`,
body: data,
json: false,
})
},
/**
* Gets the list of OIDC logos.
*/
getOIDCLogos: async () => {
return await API.get({
url: "/api/global/configs/logos_oidc",
}) })
}, },
}) })

View File

@ -0,0 +1,86 @@
export const buildConfigEndpoints = API => ({
/**
* Saves a global config.
* @param config the config to save
*/
saveConfig: async config => {
return await API.post({
url: "/api/global/configs",
body: config,
})
},
/**
* Gets a global config of a certain type.
* @param type the type to fetch
*/
getConfig: async type => {
return await API.get({
url: `/api/global/configs/${type}`,
})
},
/**
* Gets the config for a certain tenant.
* @param tenantId the tenant ID to get the config for
*/
getTenantConfig: async tenantId => {
return await API.get({
url: `/api/global/configs/public?tenantId=${tenantId}`,
})
},
/**
* Gets the OIDC config for a certain tenant.
* @param tenantId the tenant ID to get the config for
*/
getOIDCConfig: async tenantId => {
return await API.get({
url: `/api/global/configs/public/oidc?tenantId=${tenantId}`,
})
},
/**
* Gets the checklist for a specific tenant.
* @param tenantId the tenant ID to get the checklist for
*/
getChecklist: async tenantId => {
return await API.get({
url: `/api/global/configs/checklist?tenantId=${tenantId}`,
})
},
/**
* Updates the company logo for the environment.
* @param data the logo form data
*/
uploadLogo: async data => {
return await API.post({
url: "/api/global/configs/upload/settings/logoUrl",
body: data,
json: false,
})
},
/**
* Uploads a logo for an OIDC provider.
* @param name the name of the OIDC provider
* @param data the logo form data to upload
*/
uploadOIDCLogo: async ({ name, data }) => {
return await API.post({
url: `/api/global/configs/upload/logos_oidc/${name}`,
body: data,
json: false,
})
},
/**
* Gets the list of OIDC logos.
*/
getOIDCLogos: async () => {
return await API.get({
url: "/api/global/configs/logos_oidc",
})
},
})

View File

@ -4,9 +4,11 @@ import { buildAppEndpoints } from "./app"
import { buildAttachmentEndpoints } from "./attachments" import { buildAttachmentEndpoints } from "./attachments"
import { buildAuthEndpoints } from "./auth" import { buildAuthEndpoints } from "./auth"
import { buildAutomationEndpoints } from "./automations" import { buildAutomationEndpoints } from "./automations"
import { buildConfigEndpoints } from "./configs"
import { buildDatasourceEndpoints } from "./datasources" import { buildDatasourceEndpoints } from "./datasources"
import { buildFlagEndpoints } from "./flags" import { buildFlagEndpoints } from "./flags"
import { buildHostingEndpoints } from "./hosting" import { buildHostingEndpoints } from "./hosting"
import { buildOtherEndpoints } from "./other"
import { buildPermissionsEndpoints } from "./permissions" import { buildPermissionsEndpoints } from "./permissions"
import { buildQueryEndpoints } from "./queries" import { buildQueryEndpoints } from "./queries"
import { buildRelationshipEndpoints } from "./relationships" import { buildRelationshipEndpoints } from "./relationships"
@ -16,6 +18,7 @@ import { buildRowEndpoints } from "./rows"
import { buildScreenEndpoints } from "./screens" import { buildScreenEndpoints } from "./screens"
import { buildTableEndpoints } from "./tables" import { buildTableEndpoints } from "./tables"
import { buildTemplateEndpoints } from "./templates" import { buildTemplateEndpoints } from "./templates"
import { buildUserEndpoints } from "./user"
import { buildViewEndpoints } from "./views" import { buildViewEndpoints } from "./views"
const defaultAPIClientConfig = { const defaultAPIClientConfig = {
@ -189,9 +192,11 @@ export const createAPIClient = config => {
...buildAttachmentEndpoints(API), ...buildAttachmentEndpoints(API),
...buildAuthEndpoints(API), ...buildAuthEndpoints(API),
...buildAutomationEndpoints(API), ...buildAutomationEndpoints(API),
...buildConfigEndpoints(API),
...buildDatasourceEndpoints(API), ...buildDatasourceEndpoints(API),
...buildFlagEndpoints(API), ...buildFlagEndpoints(API),
...buildHostingEndpoints(API), ...buildHostingEndpoints(API),
...buildOtherEndpoints(API),
...buildPermissionsEndpoints(API), ...buildPermissionsEndpoints(API),
...buildQueryEndpoints(API), ...buildQueryEndpoints(API),
...buildRelationshipEndpoints(API), ...buildRelationshipEndpoints(API),
@ -201,6 +206,7 @@ export const createAPIClient = config => {
...buildScreenEndpoints(API), ...buildScreenEndpoints(API),
...buildTableEndpoints(API), ...buildTableEndpoints(API),
...buildTemplateEndpoints(API), ...buildTemplateEndpoints(API),
...buildUserEndpoints(API),
...buildViewEndpoints(API), ...buildViewEndpoints(API),
} }

View File

@ -0,0 +1,19 @@
export const buildOtherEndpoints = API => ({
/**
* TODO: find out what this is
*/
checkImportComplete: async () => {
return await API.get({
url: "/api/cloud/import/complete",
})
},
/**
* Gets the current environment details.
*/
getEnvironment: async () => {
return await API.get({
url: "/api/system/environment",
})
},
})

View File

@ -25,4 +25,13 @@ export const buildTemplateEndpoints = API => ({
}, },
}) })
}, },
/**
* Gets a list of app templates.
*/
getAppTemplates: async () => {
return await API.get({
url: "/api/templates?type=app",
})
},
}) })

View File

@ -0,0 +1,129 @@
export const buildUserEndpoints = API => ({
/**
* Fetches the currently logged-in user object.
* Used in client apps.
*/
fetchSelf: async () => {
return await API.get({
url: "/api/self",
})
},
/**
* Fetches the currently logged-in user object.
* Used in the builder.
*/
fetchBuilderSelf: async () => {
return await API.get({
url: "/api/global/users/self",
})
},
/**
* Gets a list of users in the current tenant.
*/
getUsers: async () => {
return await API.get({
url: "/api/global/users",
})
},
/**
* Creates a user for an app.
* @param user the user to create
*/
createAppUser: async user => {
return await API.post({
url: "/api/users/metadata",
body: user,
})
},
/**
* Updates the current user metadata.
* @param metadata the metadata to save
*/
updateOwnMetadata: async metadata => {
return await API.post({
url: "/api/users/metadata/self",
body: metadata,
})
},
/**
* Creates an admin user.
* @param adminUser the admin user to create
*/
createAdminUser: async adminUser => {
return await API.post({
url: "/api/global/users/init",
body: adminUser,
})
},
/**
* Updates the current logged-in user.
* @param user the new user object to save
*/
updateSelf: async user => {
return await API.post({
url: "/api/global/users/self",
body: user,
})
},
/**
* Creates or updates a user in the current tenant.
* @param user the new user to create
*/
saveUser: async user => {
return await API.post({
url: "/api/global/users",
body: user,
})
},
/**
* Deletes a user from the curernt tenant.
* @param userId the ID of the user to delete
*/
deleteUser: async userId => {
return await API.delete({
url: `/api/global/users/${userId}`,
})
},
/**
* Invites a user to the current tenant.
* @param email the email address to send the invitation to
* @param builder whether the user should be a global builder
* @param admin whether the user should be a global admin
*/
inviteUser: async ({ email, builder, admin }) => {
return await API.post({
url: "/api/global/users/invite",
body: {
email,
userInfo: {
admin: admin ? { global: true } : undefined,
builder: builder ? { global: true } : undefined,
},
},
})
},
/**
* Accepts an invitation to join the platform and creates a user.
* @param inviteCode the invite code sent in the email
* @param password the password for the newly created user
*/
acceptInvitation: async ({ inviteCode, password }) => {
return await API.post({
url: "/api/global/users/invite/accept",
body: {
inviteCode,
password,
},
})
},
})