diff --git a/packages/builder/src/builderStore/api.js b/packages/builder/src/builderStore/api.js
index 085c176f88..bd7cccbc5a 100644
--- a/packages/builder/src/builderStore/api.js
+++ b/packages/builder/src/builderStore/api.js
@@ -14,16 +14,25 @@ export const API = createAPIClient({
},
onError: error => {
- const { url, message, status } = error
+ const { url, message, status, method, handled } = error || {}
// Log all API errors to Sentry
// analytics.captureException(error)
+ // Log any errors that we haven't manually handled
+ if (!handled) {
+ console.error("Unhandled error from API client", error)
+ return
+ }
+
// Show a notification for any errors
if (message) {
notifications.error(`Error fetching ${url}: ${message}`)
}
+ // Log all errors to console
+ console.error(`HTTP ${status} on ${method}:${url}:\n\t${message}`)
+
// Logout on 403's
if (status === 403) {
// Don't do anything if fetching templates.
diff --git a/packages/builder/src/components/common/Dropzone.svelte b/packages/builder/src/components/common/Dropzone.svelte
index 328cd31f23..9a86554b49 100644
--- a/packages/builder/src/components/common/Dropzone.svelte
+++ b/packages/builder/src/components/common/Dropzone.svelte
@@ -1,6 +1,6 @@
diff --git a/packages/builder/src/components/deploy/DeployModal.svelte b/packages/builder/src/components/deploy/DeployModal.svelte
index 4a86394c72..9f2aae56c5 100644
--- a/packages/builder/src/components/deploy/DeployModal.svelte
+++ b/packages/builder/src/components/deploy/DeployModal.svelte
@@ -9,7 +9,7 @@
async function deployApp() {
try {
- await API.deployApp()
+ await API.deployAppChanges()
analytics.captureEvent(Events.APP.PUBLISHED, {
appId: $store.appId,
})
diff --git a/packages/builder/src/components/deploy/RevertModal.svelte b/packages/builder/src/components/deploy/RevertModal.svelte
index 4207fcad00..717c55f05e 100644
--- a/packages/builder/src/components/deploy/RevertModal.svelte
+++ b/packages/builder/src/components/deploy/RevertModal.svelte
@@ -16,7 +16,7 @@
const revert = async () => {
try {
- await API.revertApp(appId)
+ await API.revertAppChanges(appId)
// Reset frontend state after revert
const applicationPkg = await API.fetchAppPackage(appId)
diff --git a/packages/builder/src/components/deploy/VersionModal.svelte b/packages/builder/src/components/deploy/VersionModal.svelte
index 0fb061face..9707517c54 100644
--- a/packages/builder/src/components/deploy/VersionModal.svelte
+++ b/packages/builder/src/components/deploy/VersionModal.svelte
@@ -8,7 +8,7 @@
Button,
} from "@budibase/bbui"
import { store } from "builderStore"
- import api from "builderStore/api"
+ import { API } from "api"
import clientPackage from "@budibase/client/package.json"
let updateModal
@@ -18,26 +18,17 @@
$: revertAvailable = $store.revertableVersion != null
const refreshAppPackage = async () => {
- const applicationPkg = await api.get(
- `/api/applications/${appId}/appPackage`
- )
- const pkg = await applicationPkg.json()
- if (applicationPkg.ok) {
+ try {
+ const pkg = await API.fetchAppPackage(appId)
await store.actions.initialise(pkg)
- } else {
- throw new Error(pkg)
+ } catch (error) {
+ notifications.error("Error fetching app package")
}
}
const update = async () => {
try {
- const response = await api.post(
- `/api/applications/${appId}/client/update`
- )
- const json = await response.json()
- if (response.status !== 200) {
- throw json.message
- }
+ await API.updateAppClientVersion(appId)
// Don't wait for the async refresh, since this causes modal flashing
refreshAppPackage()
@@ -47,23 +38,17 @@
} catch (err) {
notifications.error(`Error updating app: ${err}`)
}
+ updateModal.hide()
}
const revert = async () => {
try {
- const revertableVersion = $store.revertableVersion
- const response = await api.post(
- `/api/applications/${appId}/client/revert`
- )
- const json = await response.json()
- if (response.status !== 200) {
- throw json.message
- }
+ await API.revertAppClientVersion(appId)
// Don't wait for the async refresh, since this causes modal flashing
refreshAppPackage()
notifications.success(
- `App reverted successfully to version ${revertableVersion}`
+ `App reverted successfully to version ${$store.revertableVersion}`
)
} catch (err) {
notifications.error(`Error reverting app: ${err}`)
diff --git a/packages/builder/src/components/start/CreateAppModal.svelte b/packages/builder/src/components/start/CreateAppModal.svelte
index 60065b6eef..ebb24e9e2f 100644
--- a/packages/builder/src/components/start/CreateAppModal.svelte
+++ b/packages/builder/src/components/start/CreateAppModal.svelte
@@ -5,7 +5,7 @@
import { store, automationStore, hostingStore } from "builderStore"
import { admin, auth } from "stores/portal"
import { string, mixed, object } from "yup"
- import api, { get, post } from "builderStore/api"
+ import { API } from "api"
import analytics, { Events } from "analytics"
import { onMount } from "svelte"
import { capitalise } from "helpers"
@@ -99,40 +99,24 @@
}
// Create App
- const appResp = await post("/api/applications", data, {})
- const appJson = await appResp.json()
- if (!appResp.ok) {
- throw new Error(appJson.message)
- }
-
+ const createdApp = await API.createApp(data)
analytics.captureEvent(Events.APP.CREATED, {
name: $values.name,
- appId: appJson.instance._id,
+ appId: createdApp.instance._id,
templateToUse,
})
// Select Correct Application/DB in prep for creating user
- const applicationPkg = await get(
- `/api/applications/${appJson.instance._id}/appPackage`
- )
- const pkg = await applicationPkg.json()
- if (applicationPkg.ok) {
- await store.actions.initialise(pkg)
- await automationStore.actions.fetch()
- // update checklist - incase first app
- await admin.init()
- } else {
- throw new Error(pkg)
- }
+ const pkg = await API.fetchAppPackage(createdApp.instance._id)
+ await store.actions.initialise(pkg)
+ await automationStore.actions.fetch()
+ // Update checklist - in case first app
+ await admin.init()
// Create user
- const user = {
- roleId: $values.roleId,
- }
- const userResp = await api.post(`/api/users/metadata/self`, user)
- await userResp.json()
+ await API.updateOwnMetadata({ roleId: $values.roleId })
await auth.setInitInfo({})
- $goto(`/builder/app/${appJson.instance._id}`)
+ $goto(`/builder/app/${createdApp.instance._id}`)
} catch (error) {
console.error(error)
notifications.error(error)
diff --git a/packages/builder/src/pages/builder/admin/_components/ImportAppsModal.svelte b/packages/builder/src/pages/builder/admin/_components/ImportAppsModal.svelte
index de29e11301..182df63967 100644
--- a/packages/builder/src/pages/builder/admin/_components/ImportAppsModal.svelte
+++ b/packages/builder/src/pages/builder/admin/_components/ImportAppsModal.svelte
@@ -1,6 +1,6 @@
@@ -36,10 +31,10 @@
onConfirm={importApps}
disabled={!value.file}
>
-
Please upload the file that was exported from your Cloud environment to get
- started
+
+ Please upload the file that was exported from your Cloud environment to get
+ started
+
{
try {
- const response = await del(`/api/dev/${app.devId}/lock`)
- if (response.status !== 200) {
- const json = await response.json()
- throw json.message
- }
+ await API.releaseAppLock(app.devId)
await apps.load()
notifications.success("Lock released successfully")
} catch (err) {
- notifications.error(`Error releasing lock: ${err}`)
+ notifications.error("Error releasing lock")
}
}
diff --git a/packages/builder/src/pages/builder/portal/manage/email/index.svelte b/packages/builder/src/pages/builder/portal/manage/email/index.svelte
index 5a78623b81..4ef59d2daa 100644
--- a/packages/builder/src/pages/builder/portal/manage/email/index.svelte
+++ b/packages/builder/src/pages/builder/portal/manage/email/index.svelte
@@ -14,7 +14,7 @@
Checkbox,
} from "@budibase/bbui"
import { email } from "stores/portal"
- import api from "builderStore/api"
+ import { API } from "api"
import { cloneDeep } from "lodash/fp"
import analytics, { Events } from "analytics"
@@ -54,55 +54,48 @@
delete smtp.config.auth
}
// Save your SMTP config
- const response = await api.post(`/api/global/configs`, smtp)
-
- if (response.status !== 200) {
- const error = await response.text()
- let message
- try {
- message = JSON.parse(error).message
- } catch (err) {
- message = error
- }
- notifications.error(`Failed to save email settings, reason: ${message}`)
- } else {
- const json = await response.json()
- smtpConfig._rev = json._rev
- smtpConfig._id = json._id
- notifications.success(`Settings saved.`)
+ try {
+ const savedConfig = await API.saveConfig(smtp)
+ smtpConfig._rev = savedConfig._rev
+ smtpConfig._id = savedConfig._id
+ notifications.success(`Settings saved`)
analytics.captureEvent(Events.SMTP.SAVED)
+ } catch (error) {
+ notifications.error(
+ `Failed to save email settings, reason: ${error?.message || "Unknown"}`
+ )
}
}
async function fetchSmtp() {
loading = true
- // fetch the configs for smtp
- const smtpResponse = await api.get(
- `/api/global/configs/${ConfigTypes.SMTP}`
- )
- const smtpDoc = await smtpResponse.json()
-
- if (!smtpDoc._id) {
- smtpConfig = {
- type: ConfigTypes.SMTP,
- config: {
- secure: true,
- },
+ try {
+ // Fetch the configs for smtp
+ const smtpDoc = await API.getConfig(ConfigTypes.SMTP)
+ if (!smtpDoc._id) {
+ smtpConfig = {
+ type: ConfigTypes.SMTP,
+ config: {
+ secure: true,
+ },
+ }
+ } else {
+ smtpConfig = smtpDoc
}
- } else {
- smtpConfig = smtpDoc
- }
- loading = false
- requireAuth = smtpConfig.config.auth != null
- // always attach the auth for the forms purpose -
- // this will be removed later if required
- if (!smtpDoc.config) {
- smtpDoc.config = {}
- }
- if (!smtpDoc.config.auth) {
- smtpConfig.config.auth = {
- type: "login",
+ loading = false
+ requireAuth = smtpConfig.config.auth != null
+ // Always attach the auth for the forms purpose -
+ // this will be removed later if required
+ if (!smtpDoc.config) {
+ smtpDoc.config = {}
}
+ if (!smtpDoc.config.auth) {
+ smtpConfig.config.auth = {
+ type: "login",
+ }
+ }
+ } catch (error) {
+ notifications.error("Error fetching SMTP config")
}
}
diff --git a/packages/builder/src/stores/portal/admin.js b/packages/builder/src/stores/portal/admin.js
index d98eae8363..d97105fd46 100644
--- a/packages/builder/src/stores/portal/admin.js
+++ b/packages/builder/src/stores/portal/admin.js
@@ -1,5 +1,5 @@
import { writable, get } from "svelte/store"
-import api from "builderStore/api"
+import { API } from "api"
import { auth } from "stores/portal"
export function createAdminStore() {
@@ -25,21 +25,19 @@ export function createAdminStore() {
async function init() {
try {
const tenantId = get(auth).tenantId
- const response = await api.get(
- `/api/global/configs/checklist?tenantId=${tenantId}`
- )
- const json = await response.json()
- const totalSteps = Object.keys(json).length
- const completedSteps = Object.values(json).filter(x => x?.checked).length
-
+ const checklist = await API.getChecklist(tenantId)
+ const totalSteps = Object.keys(checklist).length
+ const completedSteps = Object.values(checklist).filter(
+ x => x?.checked
+ ).length
await getEnvironment()
admin.update(store => {
store.loaded = true
- store.checklist = json
+ store.checklist = checklist
store.onboardingProgress = (completedSteps / totalSteps) * 100
return store
})
- } catch (err) {
+ } catch (error) {
admin.update(store => {
store.checklist = null
return store
@@ -48,11 +46,15 @@ export function createAdminStore() {
}
async function checkImportComplete() {
- const response = await api.get(`/api/cloud/import/complete`)
- if (response.status === 200) {
- const json = await response.json()
+ try {
+ const result = await API.checkImportComplete()
admin.update(store => {
- store.importComplete = json ? json.imported : false
+ store.importComplete = result ? result.imported : false
+ return store
+ })
+ } catch (error) {
+ admin.update(store => {
+ store.importComplete = false
return store
})
}
@@ -65,15 +67,14 @@ export function createAdminStore() {
let accountPortalUrl = ""
let isDev = false
try {
- const response = await api.get(`/api/system/environment`)
- const json = await response.json()
- multiTenancyEnabled = json.multiTenancy
- cloud = json.cloud
- disableAccountPortal = json.disableAccountPortal
- accountPortalUrl = json.accountPortalUrl
- isDev = json.isDev
+ 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
+ // Just let it stay disabled
}
admin.update(store => {
store.multiTenancy = multiTenancyEnabled
diff --git a/packages/builder/src/stores/portal/oidc.js b/packages/builder/src/stores/portal/oidc.js
index 3e3a7048ca..751988bff6 100644
--- a/packages/builder/src/stores/portal/oidc.js
+++ b/packages/builder/src/stores/portal/oidc.js
@@ -1,5 +1,5 @@
import { writable, get } from "svelte/store"
-import api from "builderStore/api"
+import { API } from "api"
import { auth } from "stores/portal"
const OIDC_CONFIG = {
@@ -14,16 +14,17 @@ export function createOidcStore() {
async function init() {
const tenantId = get(auth).tenantId
- const res = await api.get(
- `/api/global/configs/public/oidc?tenantId=${tenantId}`
- )
- const json = await res.json()
-
- if (json.status === 400 || Object.keys(json).length === 0) {
+ try {
+ const config = await API.getOIDCConfig(tenantId)
+ if (Object.keys(config || {}).length) {
+ // Just use the first config for now.
+ // We will be support multiple logins buttons later on.
+ set(...config)
+ } else {
+ set(OIDC_CONFIG)
+ }
+ } catch (error) {
set(OIDC_CONFIG)
- } else {
- // Just use the first config for now. We will be support multiple logins buttons later on.
- set(...json)
}
}
diff --git a/packages/client/src/api.js b/packages/client/src/api.js
index e617dae00f..3ded21f473 100644
--- a/packages/client/src/api.js
+++ b/packages/client/src/api.js
@@ -18,9 +18,21 @@ export const API = createAPIClient({
// We could also log these to sentry.
// Or we could check error.status and redirect to login on a 403 etc.
onError: error => {
- if (error.message) {
- notificationStore.actions.error(error.message)
+ const { status, method, url, message, handled } = error || {}
+
+ // Log any errors that we haven't manually handled
+ if (!handled) {
+ console.error("Unhandled error from API client", error)
+ return
}
+
+ // Notify all errors
+ if (message) {
+ notificationStore.actions.error(message)
+ }
+
+ // Log all errors to console
+ console.error(`HTTP ${status} on ${method}:${url}:\n\t${message}`)
},
// Patch certain endpoints with functionality specific to client apps
diff --git a/packages/frontend-core/src/api/app.js b/packages/frontend-core/src/api/app.js
index 4edd76d9b1..c1c4204c9d 100644
--- a/packages/frontend-core/src/api/app.js
+++ b/packages/frontend-core/src/api/app.js
@@ -25,7 +25,7 @@ export const buildAppEndpoints = API => ({
/**
* Deploys the current app.
*/
- deployApp: async () => {
+ deployAppChanges: async () => {
return await API.post({
url: "/api/deploy",
})
@@ -35,12 +35,32 @@ export const buildAppEndpoints = API => ({
* Reverts an app to a previous version.
* @param appId the app ID to revert
*/
- revertApp: async appId => {
+ revertAppChanges: async appId => {
return await API.post({
url: `/api/dev/${appId}/revert`,
})
},
+ /**
+ * Updates an app's version of the client library.
+ * @param appId the app ID to update
+ */
+ updateAppClientVersion: async appId => {
+ return await API.post({
+ url: `/api/applications/${appId}/client/update`,
+ })
+ },
+
+ /**
+ * Reverts an app's version of the client library to the previous version.
+ * @param appId the app ID to revert
+ */
+ revertAppClientVersion: async appId => {
+ return await API.post({
+ url: `/api/applications/${appId}/client/revert`,
+ })
+ },
+
/**
* Gets a list of app deployments.
*/
@@ -49,4 +69,57 @@ export const buildAppEndpoints = API => ({
url: "/api/deployments",
})
},
+
+ /**
+ * Creates an app.
+ * @param app the app to create
+ */
+ createApp: async app => {
+ return await API.post({
+ url: "/api/applications",
+ body: app,
+ })
+ },
+
+ /**
+ * Imports an export of all apps.
+ * @param apps the FormData containing the apps to import
+ */
+ importApps: async apps => {
+ return await API.post({
+ url: "/api/cloud/import",
+ body: apps,
+ json: false,
+ })
+ },
+
+ /**
+ * Unpublishes a published app.
+ * @param appId the production ID of the app to unpublish
+ */
+ unpublishApp: async appId => {
+ return await API.delete({
+ url: `/api/applications/${appId}?unpublish=1`,
+ })
+ },
+
+ /**
+ * Deletes a dev app.
+ * @param appId the dev app ID to delete
+ */
+ deleteApp: async appId => {
+ return await API.delete({
+ url: `/api/applications/${appId}`,
+ })
+ },
+
+ /**
+ * Releases the lock on a dev app.
+ * @param appId the dev app ID to unlock
+ */
+ releaseAppLock: async appId => {
+ return await API.delete({
+ url: `/api/dev/${appId}/lock`,
+ })
+ },
})
diff --git a/packages/frontend-core/src/api/attachments.js b/packages/frontend-core/src/api/attachments.js
index 5dd2582ea5..9a8325a433 100644
--- a/packages/frontend-core/src/api/attachments.js
+++ b/packages/frontend-core/src/api/attachments.js
@@ -1,6 +1,8 @@
export const buildAttachmentEndpoints = API => ({
/**
* Uploads an attachment to the server.
+ * @param data the attachment to upload
+ * @param tableId the table ID to upload to
*/
uploadAttachment: async ({ data, tableId }) => {
return await API.post({
@@ -9,4 +11,16 @@ export const buildAttachmentEndpoints = API => ({
json: false,
})
},
+
+ /**
+ * Uploads an attachment to the server as a builder user from the builder.
+ * @param data the data to upload
+ */
+ uploadBuilderAttachment: async data => {
+ return await API.post({
+ url: "/api/attachments/process",
+ body: data,
+ json: false,
+ })
+ },
})
diff --git a/packages/frontend-core/src/api/auth.js b/packages/frontend-core/src/api/auth.js
index 55554df94a..4f61c155f0 100644
--- a/packages/frontend-core/src/api/auth.js
+++ b/packages/frontend-core/src/api/auth.js
@@ -31,6 +31,89 @@ export const buildAuthEndpoints = API => ({
* Fetches the currently logged in user object
*/
fetchSelf: async () => {
- return await API.get({ url: "/api/self" })
+ return await API.get({
+ url: "/api/self",
+ })
+ },
+
+ /**
+ * 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,
+ })
+ },
+
+ /**
+ * 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",
+ })
},
})
diff --git a/packages/frontend-core/src/api/index.js b/packages/frontend-core/src/api/index.js
index f1c12dc941..e1cf2f3619 100644
--- a/packages/frontend-core/src/api/index.js
+++ b/packages/frontend-core/src/api/index.js
@@ -30,7 +30,9 @@ export const createAPIClient = config => {
}
// Generates an error object from an API response
- const makeErrorFromResponse = async response => {
+ const makeErrorFromResponse = async (response, method) => {
+ console.log("making error from", response)
+
// Try to read a message from the error
let message = response.statusText
try {
@@ -47,6 +49,8 @@ export const createAPIClient = config => {
message,
status: response.status,
url: response.url,
+ method,
+ handled: true,
}
}
@@ -56,6 +60,8 @@ export const createAPIClient = config => {
message,
status: 400,
url: "",
+ method: "",
+ handled: true,
}
}
@@ -110,11 +116,7 @@ export const createAPIClient = config => {
return null
}
} else {
- const error = await makeErrorFromResponse(response)
- if (config?.onError) {
- config.onError(error)
- }
- throw error
+ throw await makeErrorFromResponse(response, method)
}
}
@@ -134,14 +136,21 @@ export const createAPIClient = config => {
return await cache[identifier]
}
- // Constructs an API call function for a particular HTTP method.
+ // Constructs an API call function for a particular HTTP method
const requestApiCall = method => async params => {
- let { url, cache = false, external = false } = params
- if (!external) {
- url = `/${url}`.replace("//", "/")
+ try {
+ let { url, cache = false, external = false } = params
+ if (!external) {
+ url = `/${url}`.replace("//", "/")
+ }
+ const enrichedParams = { ...params, method, url }
+ return await (cache ? makeCachedApiCall : makeApiCall)(enrichedParams)
+ } catch (error) {
+ if (config?.onError) {
+ config.onError(error)
+ }
+ throw error
}
- const enrichedParams = { ...params, method, url }
- return await (cache ? makeCachedApiCall : makeApiCall)(enrichedParams)
}
// Build the underlying core API methods