diff --git a/packages/builder/src/builderStore/store/automation/index.js b/packages/builder/src/builderStore/store/automation/index.js index 7bd0ccca22..73aa2757ec 100644 --- a/packages/builder/src/builderStore/store/automation/index.js +++ b/packages/builder/src/builderStore/store/automation/index.js @@ -1,95 +1,130 @@ import { writable } from "svelte/store" -import api from "../../api" +import { API } from "api" import Automation from "./Automation" import { cloneDeep } from "lodash/fp" import analytics, { Events } from "analytics" +import { notifications } from "@budibase/bbui" + +const initialAutomationState = { + automations: [], + blockDefinitions: { + TRIGGER: [], + ACTION: [], + }, + selectedAutomation: null, +} + +export const getAutomationStore = () => { + const store = writable(initialAutomationState) + store.actions = automationActions(store) + return store +} const automationActions = store => ({ fetch: async () => { - const responses = await Promise.all([ - api.get(`/api/automations`), - api.get(`/api/automations/definitions/list`), - ]) - const jsonResponses = await Promise.all(responses.map(x => x.json())) - store.update(state => { - let selected = state.selectedAutomation?.automation - state.automations = jsonResponses[0] - state.blockDefinitions = { - TRIGGER: jsonResponses[1].trigger, - ACTION: jsonResponses[1].action, - } - // if previously selected find the new obj and select it - if (selected) { - selected = jsonResponses[0].filter( - automation => automation._id === selected._id - ) - state.selectedAutomation = new Automation(selected[0]) - } - return state - }) + try { + const responses = await Promise.all([ + API.getAutomations(), + API.getAutomationDefinitions(), + ]) + store.update(state => { + let selected = state.selectedAutomation?.automation + state.automations = responses[0] + state.blockDefinitions = { + TRIGGER: responses[1].trigger, + ACTION: responses[1].action, + } + // If previously selected find the new obj and select it + if (selected) { + selected = responses[0].filter( + automation => automation._id === selected._id + ) + state.selectedAutomation = new Automation(selected[0]) + } + return state + }) + } catch (error) { + notifications.error("Error fetching automations") + store.set(initialAutomationState) + } }, create: async ({ name }) => { - const automation = { - name, - type: "automation", - definition: { - steps: [], - }, + try { + const automation = { + name, + type: "automation", + definition: { + steps: [], + }, + } + const response = await API.createAutomation(automation) + store.update(state => { + state.automations = [...state.automations, response.automation] + store.actions.select(response.automation) + return state + }) + } catch (error) { + notifications.error("Error creating automation") } - const CREATE_AUTOMATION_URL = `/api/automations` - const response = await api.post(CREATE_AUTOMATION_URL, automation) - const json = await response.json() - store.update(state => { - state.automations = [...state.automations, json.automation] - store.actions.select(json.automation) - return state - }) }, save: async automation => { - const UPDATE_AUTOMATION_URL = `/api/automations` - const response = await api.put(UPDATE_AUTOMATION_URL, automation) - const json = await response.json() - store.update(state => { - const newAutomation = json.automation - const existingIdx = state.automations.findIndex( - existing => existing._id === automation._id - ) - if (existingIdx !== -1) { - state.automations.splice(existingIdx, 1, newAutomation) - state.automations = [...state.automations] - store.actions.select(newAutomation) - return state - } - }) + try { + const response = await API.updateAutomation(automation) + store.update(state => { + const updatedAutomation = response.automation + const existingIdx = state.automations.findIndex( + existing => existing._id === automation._id + ) + if (existingIdx !== -1) { + state.automations.splice(existingIdx, 1, updatedAutomation) + state.automations = [...state.automations] + store.actions.select(updatedAutomation) + return state + } + }) + notifications.success("Automation saved successfully") + } catch (error) { + notifications.error("Error saving automation") + } }, delete: async automation => { - const { _id, _rev } = automation - const DELETE_AUTOMATION_URL = `/api/automations/${_id}/${_rev}` - await api.delete(DELETE_AUTOMATION_URL) - - store.update(state => { - const existingIdx = state.automations.findIndex( - existing => existing._id === _id - ) - state.automations.splice(existingIdx, 1) - state.automations = [...state.automations] - state.selectedAutomation = null - state.selectedBlock = null - return state - }) - }, - trigger: async automation => { - const { _id } = automation - return await api.post(`/api/automations/${_id}/trigger`) + try { + await API.deleteAutomation({ + automationId: automation?._id, + automationRev: automation?._rev, + }) + store.update(state => { + const existingIdx = state.automations.findIndex( + existing => existing._id === automation?._id + ) + state.automations.splice(existingIdx, 1) + state.automations = [...state.automations] + state.selectedAutomation = null + state.selectedBlock = null + return state + }) + notifications.success("Automation deleted successfully") + } catch (error) { + notifications.error("Error deleting automation") + } }, test: async (automation, testData) => { - const { _id } = automation - const response = await api.post(`/api/automations/${_id}/test`, testData) - const json = await response.json() - store.update(state => { - state.selectedAutomation.testResults = json - return state - }) + try { + const result = await API.testAutomation({ + automationId: automation?._id, + testData, + }) + store.update(state => { + state.selectedAutomation.testResults = result + return state + }) + } catch (error) { + notifications.error("Error testing automation") + store.update(state => { + state.selectedAutomation.testResults = null + return state + }) + } }, select: automation => { store.update(state => { @@ -143,17 +178,3 @@ const automationActions = store => ({ }) }, }) - -export const getAutomationStore = () => { - const INITIAL_AUTOMATION_STATE = { - automations: [], - blockDefinitions: { - TRIGGER: [], - ACTION: [], - }, - selectedAutomation: null, - } - const store = writable(INITIAL_AUTOMATION_STATE) - store.actions = automationActions(store) - return store -} diff --git a/packages/builder/src/builderStore/store/hosting.js b/packages/builder/src/builderStore/store/hosting.js index fb174c2663..980130b147 100644 --- a/packages/builder/src/builderStore/store/hosting.js +++ b/packages/builder/src/builderStore/store/hosting.js @@ -1,5 +1,6 @@ import { writable } from "svelte/store" -import api, { get } from "../api" +import { API } from "api" +import { notifications } from "@budibase/bbui" const INITIAL_HOSTING_UI_STATE = { appUrl: "", @@ -12,22 +13,40 @@ export const getHostingStore = () => { const store = writable({ ...INITIAL_HOSTING_UI_STATE }) store.actions = { fetch: async () => { - const response = await api.get("/api/hosting/urls") - const urls = await response.json() - store.update(state => { - state.appUrl = urls.app - return state - }) + try { + const urls = await API.getHostingURLs() + store.update(state => { + state.appUrl = urls.app + return state + }) + } catch (error) { + store.update(state => { + state.appUrl = "" + return state + }) + notifications.error("Error fetching hosting URLs") + } }, fetchDeployedApps: async () => { - let deployments = await (await get("/api/hosting/apps")).json() - store.update(state => { - state.deployedApps = deployments - state.deployedAppNames = Object.values(deployments).map(app => app.name) - state.deployedAppUrls = Object.values(deployments).map(app => app.url) - return state - }) - return deployments + try { + const deployments = await API.getDeployedApps() + store.update(state => { + state.deployedApps = deployments + state.deployedAppNames = Object.values(deployments).map( + app => app.name + ) + state.deployedAppUrls = Object.values(deployments).map(app => app.url) + return state + }) + } catch (error) { + store.update(state => { + state.deployedApps = {} + state.deployedAppNames = [] + state.deployedAppUrls = [] + return state + }) + notifications.error("Failed detching deployed apps") + } }, } return store diff --git a/packages/builder/src/components/automation/AutomationBuilder/FlowChart/FlowChart.svelte b/packages/builder/src/components/automation/AutomationBuilder/FlowChart/FlowChart.svelte index 2d6881d652..5bf625940a 100644 --- a/packages/builder/src/components/automation/AutomationBuilder/FlowChart/FlowChart.svelte +++ b/packages/builder/src/components/automation/AutomationBuilder/FlowChart/FlowChart.svelte @@ -33,23 +33,6 @@ await automationStore.actions.delete( $automationStore.selectedAutomation?.automation ) - notifications.success("Automation deleted.") - } - - async function testAutomation() { - const result = await automationStore.actions.trigger( - $automationStore.selectedAutomation.automation - ) - if (result.status === 200) { - notifications.success( - `Automation ${$automationStore.selectedAutomation.automation.name} triggered successfully.` - ) - } else { - notifications.error( - `Failed to trigger automation ${$automationStore.selectedAutomation.automation.name}.` - ) - } - return result } @@ -85,7 +68,7 @@ animate:flip={{ duration: 500 }} in:fly|local={{ x: 500, duration: 1500 }} > - + {/each} @@ -101,7 +84,7 @@ - + diff --git a/packages/builder/src/components/backend/DataTable/api.js b/packages/builder/src/components/backend/DataTable/api.js deleted file mode 100644 index b461c70c4b..0000000000 --- a/packages/builder/src/components/backend/DataTable/api.js +++ /dev/null @@ -1,34 +0,0 @@ -import api from "builderStore/api" - -export async function createUser(user) { - const CREATE_USER_URL = `/api/users/metadata` - const response = await api.post(CREATE_USER_URL, user) - return await response.json() -} - -export async function saveRow(row, tableId) { - const SAVE_ROW_URL = `/api/${tableId}/rows` - const response = await api.post(SAVE_ROW_URL, row) - - return await response.json() -} - -export async function deleteRow(row) { - const DELETE_ROWS_URL = `/api/${row.tableId}/rows` - return api.delete(DELETE_ROWS_URL, { - _id: row._id, - _rev: row._rev, - }) -} - -export async function fetchDataForTable(tableId) { - const FETCH_ROWS_URL = `/api/${tableId}/rows` - - const response = await api.get(FETCH_ROWS_URL) - const json = await response.json() - - if (response.status !== 200) { - throw new Error(json.message) - } - return json -} diff --git a/packages/builder/src/components/backend/DataTable/modals/CreateEditRow.svelte b/packages/builder/src/components/backend/DataTable/modals/CreateEditRow.svelte index 559e8275db..965458e297 100644 --- a/packages/builder/src/components/backend/DataTable/modals/CreateEditRow.svelte +++ b/packages/builder/src/components/backend/DataTable/modals/CreateEditRow.svelte @@ -3,7 +3,7 @@ import { tables, rows } from "stores/backend" import { notifications } from "@budibase/bbui" import RowFieldControl from "../RowFieldControl.svelte" - import * as api from "../api" + import { API } from "api" import { ModalContent } from "@budibase/bbui" import ErrorsBox from "components/common/ErrorsBox.svelte" import { FIELDS } from "constants/backend" @@ -22,30 +22,30 @@ $: tableSchema = Object.entries(table?.schema ?? {}) async function saveRow() { - const rowResponse = await api.saveRow( - { ...row, tableId: table._id }, - table._id - ) - - if (rowResponse.errors) { - errors = Object.entries(rowResponse.errors) - .map(([key, error]) => ({ dataPath: key, message: error })) - .flat() + errors = [] + try { + await API.saveRow({ ...row, tableId: table._id }) + notifications.success("Row saved successfully") + rows.save() + dispatch("updaterows") + } catch (error) { + if (error.handled) { + const response = error.json + if (response?.errors) { + errors = Object.entries(response.errors) + .map(([key, error]) => ({ dataPath: key, message: error })) + .flat() + } else if (error.status === 400 && response?.validationErrors) { + errors = Object.keys(response.validationErrors).map(field => ({ + message: `${field} ${response.validationErrors[field][0]}`, + })) + } + } else { + notifications.error("Failed to save row") + } // Prevent modal closing if there were errors return false - } else if (rowResponse.status === 400 && rowResponse.validationErrors) { - errors = Object.keys(rowResponse.validationErrors).map(field => ({ - message: `${field} ${rowResponse.validationErrors[field][0]}`, - })) - return false - } else if (rowResponse.status >= 400) { - errors = [{ message: rowResponse.message }] - return false } - - notifications.success("Row saved successfully.") - rows.save(rowResponse) - dispatch("updaterows") } diff --git a/packages/builder/src/components/backend/DataTable/modals/CreateEditUser.svelte b/packages/builder/src/components/backend/DataTable/modals/CreateEditUser.svelte index f1de23fb97..088280e266 100644 --- a/packages/builder/src/components/backend/DataTable/modals/CreateEditUser.svelte +++ b/packages/builder/src/components/backend/DataTable/modals/CreateEditUser.svelte @@ -4,7 +4,7 @@ import { roles } from "stores/backend" import { notifications } from "@budibase/bbui" import RowFieldControl from "../RowFieldControl.svelte" - import * as backendApi from "../api" + import { API } from "api" import { ModalContent, Select } from "@budibase/bbui" import ErrorsBox from "components/common/ErrorsBox.svelte" @@ -53,27 +53,31 @@ return false } - const rowResponse = await backendApi.saveRow( - { ...row, tableId: table._id }, - table._id - ) - if (rowResponse.errors) { - if (Array.isArray(rowResponse.errors)) { - errors = rowResponse.errors.map(error => ({ message: error })) + try { + await API.saveRow({ ...row, tableId: table._id }) + notifications.success("User saved successfully") + rows.save() + dispatch("updaterows") + } catch (error) { + if (error.handled) { + const response = error.json + if (response?.errors) { + if (Array.isArray(response.errors)) { + errors = response.errors.map(error => ({ message: error })) + } else { + errors = Object.entries(response.errors) + .map(([key, error]) => ({ dataPath: key, message: error })) + .flat() + } + } else if (error.status === 400) { + errors = [{ message: response?.message || "Unknown error" }] + } } else { - errors = Object.entries(rowResponse.errors) - .map(([key, error]) => ({ dataPath: key, message: error })) - .flat() + notifications.error("Error saving user") } - return false - } else if (rowResponse.status === 400 || rowResponse.status === 500) { - errors = [{ message: rowResponse.message }] + // Prevent closing the modal on errors return false } - - notifications.success("User saved successfully") - rows.save(rowResponse) - dispatch("updaterows") } diff --git a/packages/builder/src/pages/builder/app/[application]/_layout.svelte b/packages/builder/src/pages/builder/app/[application]/_layout.svelte index 0478b46f73..62a7c91d3f 100644 --- a/packages/builder/src/pages/builder/app/[application]/_layout.svelte +++ b/packages/builder/src/pages/builder/app/[application]/_layout.svelte @@ -6,25 +6,26 @@ import RevertModal from "components/deploy/RevertModal.svelte" import VersionModal from "components/deploy/VersionModal.svelte" import NPSFeedbackForm from "components/feedback/NPSFeedbackForm.svelte" - import { get, post } from "builderStore/api" + import { API } from "api" import { auth, admin } from "stores/portal" import { isActive, goto, layout, redirect } from "@roxi/routify" import Logo from "assets/bb-emblem.svg" import { capitalise } from "helpers" - import UpgradeModal from "../../../../components/upgrade/UpgradeModal.svelte" + import UpgradeModal from "components/upgrade/UpgradeModal.svelte" import { onMount } from "svelte" - // Get Package and set store export let application + + // Get Package and set store let promise = getPackage() - // sync once when you load the app + + // Sync once when you load the app let hasSynced = false + let userShouldPostFeedback = false $: selected = capitalise( $layout.children.find(layout => $isActive(layout.path))?.title ?? "data" ) - let userShouldPostFeedback = false - function previewApp() { if (!$auth?.user?.flags?.feedbackSubmitted) { userShouldPostFeedback = true @@ -33,34 +34,25 @@ } async function getPackage() { - const res = await get(`/api/applications/${application}/appPackage`) - const pkg = await res.json() - - if (res.ok) { - try { - await store.actions.initialise(pkg) - // edge case, lock wasn't known to client when it re-directed, or user went directly - } catch (err) { - if (!err.ok && err.reason === "locked") { - $redirect("../../") - } else { - throw err - } - } + try { + const pkg = await API.fetchAppPackage(application) + await store.actions.initialise(pkg) await automationStore.actions.fetch() await roles.fetch() await flags.fetch() return pkg - } else { - throw new Error(pkg) + } catch (error) { + // TODO: some stuff about redirecting if locked + // $redirect("../../") + notifications.error("Error initialising app") } } - // handles navigation between frontend, backend, automation. - // this remembers your last place on each of the sections + // Handles navigation between frontend, backend, automation. + // This remembers your last place on each of the sections // e.g. if one of your screens is selected on front end, then // you browse to backend, when you click frontend, you will be - // brought back to the same screen + // brought back to the same screen. const topItemNavigate = path => () => { const activeTopNav = $layout.children.find(c => $isActive(c.path)) if (!activeTopNav) return @@ -74,8 +66,9 @@ onMount(async () => { if (!hasSynced && application) { - const res = await post(`/api/applications/${application}/sync`) - if (res.status !== 200) { + try { + await API.syncApp(application) + } catch (error) { notifications.error("Failed to sync with production database") } hasSynced = true diff --git a/packages/builder/src/pages/builder/portal/manage/auth/index.svelte b/packages/builder/src/pages/builder/portal/manage/auth/index.svelte index 896e1d53e1..bdbf884c99 100644 --- a/packages/builder/src/pages/builder/portal/manage/auth/index.svelte +++ b/packages/builder/src/pages/builder/portal/manage/auth/index.svelte @@ -20,7 +20,7 @@ Toggle, } from "@budibase/bbui" import { onMount } from "svelte" - import api from "builderStore/api" + import { API } from "api" import { organisation, admin } from "stores/portal" import { Helpers } from "@budibase/bbui" import analytics, { Events } from "analytics" @@ -137,17 +137,6 @@ providers.oidc?.config?.configs[0].clientID && providers.oidc?.config?.configs[0].clientSecret - async function uploadLogo(file) { - let data = new FormData() - data.append("file", file) - const res = await api.post( - `/api/global/configs/upload/logos_oidc/${file.name}`, - data, - {} - ) - return await res.json() - } - const onFileSelected = e => { let fileName = e.target.files[0].name image = e.target.files[0] @@ -156,17 +145,28 @@ } async function save(docs) { - // only if the user has provided an image, upload it. - image && uploadLogo(image) let calls = [] + + // Only if the user has provided an image, upload it + if (image) { + let data = new FormData() + data.append("file", image) + calls.push( + API.uploadOIDCLogo({ + name: image.name, + data, + }) + ) + } + docs.forEach(element => { if (element.type === ConfigTypes.OIDC) { - //Add a UUID here so each config is distinguishable when it arrives at the login page + // Add a UUID here so each config is distinguishable when it arrives at the login page for (let config of element.config.configs) { if (!config.uuid) { config.uuid = Helpers.uuid() } - // callback urls shouldn't be included + // Callback urls shouldn't be included delete config.callbackURL } if (partialOidc) { @@ -175,8 +175,8 @@ `Please fill in all required ${ConfigTypes.OIDC} fields` ) } else { - calls.push(api.post(`/api/global/configs`, element)) - // turn the save button grey when clicked + calls.push(API.saveConfig(element)) + // Turn the save button grey when clicked oidcSaveButtonDisabled = true originalOidcDoc = cloneDeep(providers.oidc) } @@ -189,71 +189,70 @@ `Please fill in all required ${ConfigTypes.Google} fields` ) } else { - calls.push(api.post(`/api/global/configs`, element)) + calls.push(API.saveConfig(element)) googleSaveButtonDisabled = true originalGoogleDoc = cloneDeep(providers.google) } } } }) - calls.length && + + if (calls.length) { Promise.all(calls) - .then(responses => { - return Promise.all( - responses.map(response => { - return response.json() - }) - ) - }) .then(data => { data.forEach(res => { providers[res.type]._rev = res._rev providers[res.type]._id = res._id }) - notifications.success(`Settings saved.`) + notifications.success(`Settings saved`) analytics.captureEvent(Events.SSO.SAVED) }) - .catch(err => { - notifications.error(`Failed to update auth settings. ${err}`) - throw new Error(err.message) + .catch(error => { + notifications.error(`Failed to update auth settings`) + console.error(error.message) }) + } } onMount(async () => { await organisation.init() - // fetch the configs for oauth - const googleResponse = await api.get( - `/api/global/configs/${ConfigTypes.Google}` - ) - const googleDoc = await googleResponse.json() - if (!googleDoc._id) { + // Fetch Google config + let googleDoc + try { + googleDoc = await API.getConfig(ConfigTypes.Google) + } catch (error) { + notifications.error("Error fetching Google OAuth config") + } + if (!googleDoc?._id) { providers.google = { type: ConfigTypes.Google, config: { activated: true }, } originalGoogleDoc = cloneDeep(googleDoc) } else { - // default activated to true for older configs + // Default activated to true for older configs if (googleDoc.config.activated === undefined) { googleDoc.config.activated = true } originalGoogleDoc = cloneDeep(googleDoc) providers.google = googleDoc } - googleCallbackUrl = providers?.google?.config?.callbackURL - //Get the list of user uploaded logos and push it to the dropdown options. - //This needs to be done before the config call so they're available when the dropdown renders - const res = await api.get(`/api/global/configs/logos_oidc`) - const configSettings = await res.json() - - if (configSettings.config) { - const logoKeys = Object.keys(configSettings.config) - + // Get the list of user uploaded logos and push it to the dropdown options. + // This needs to be done before the config call so they're available when + // the dropdown renders. + let oidcLogos + try { + oidcLogos = await API.getOIDCLogos() + } catch (error) { + notifications.error("Error fetching OIDC logos") + } + if (oidcLogos?.config) { + const logoKeys = Object.keys(oidcLogos.config) logoKeys.map(logoKey => { - const logoUrl = configSettings.config[logoKey] + const logoUrl = oidcLogos.config[logoKey] iconDropdownOptions.unshift({ label: logoKey, value: logoKey, @@ -261,11 +260,15 @@ }) }) } - const oidcResponse = await api.get( - `/api/global/configs/${ConfigTypes.OIDC}` - ) - const oidcDoc = await oidcResponse.json() - if (!oidcDoc._id) { + + // Fetch OIDC config + let oidcDoc + try { + oidcDoc = await API.getConfig(ConfigTypes.OIDC) + } catch (error) { + notifications.error("Error fetching OIDC config") + } + if (!oidcDoc?._id) { providers.oidc = { type: ConfigTypes.OIDC, config: { configs: [{ activated: true }] }, diff --git a/packages/builder/src/pages/builder/portal/settings/organisation.svelte b/packages/builder/src/pages/builder/portal/settings/organisation.svelte index 6903854922..c9086b2b45 100644 --- a/packages/builder/src/pages/builder/portal/settings/organisation.svelte +++ b/packages/builder/src/pages/builder/portal/settings/organisation.svelte @@ -11,7 +11,7 @@ notifications, } from "@budibase/bbui" import { auth, organisation, admin } from "stores/portal" - import { post } from "builderStore/api" + import { API } from "api" import { writable } from "svelte/store" import { redirect } from "@roxi/routify" @@ -32,14 +32,13 @@ let loading = false async function uploadLogo(file) { - let data = new FormData() - data.append("file", file) - const res = await post( - "/api/global/configs/upload/settings/logoUrl", - data, - {} - ) - return await res.json() + try { + let data = new FormData() + data.append("file", file) + await API.uploadLogo(data) + } catch (error) { + notifications.error("Error uploading logo") + } } async function saveConfig() { @@ -55,19 +54,14 @@ company: $values.company ?? "", platformUrl: $values.platformUrl ?? "", } - // remove logo if required + + // Remove logo if required if (!$values.logo) { config.logoUrl = "" } // Update settings - const res = await organisation.save(config) - if (res.status === 200) { - notifications.success("Settings saved successfully") - } else { - notifications.error(res.message) - } - + await organisation.save(config) loading = false } diff --git a/packages/builder/src/pages/builder/portal/settings/update.svelte b/packages/builder/src/pages/builder/portal/settings/update.svelte index 5deb724a7c..d87736144d 100644 --- a/packages/builder/src/pages/builder/portal/settings/update.svelte +++ b/packages/builder/src/pages/builder/portal/settings/update.svelte @@ -9,7 +9,7 @@ notifications, Label, } from "@budibase/bbui" - import api from "builderStore/api" + import { API } from "api" import { auth, admin } from "stores/portal" import { redirect } from "@roxi/routify" @@ -38,8 +38,12 @@ } async function getVersion() { - const response = await api.get("/api/dev/version") - version = await response.text() + try { + version = await API.getBudibaseVersion() + } catch (error) { + notifications.error("Error getting Budibase version") + version = null + } } onMount(() => { diff --git a/packages/frontend-core/src/api/app.js b/packages/frontend-core/src/api/app.js index c1c4204c9d..543dcd58ee 100644 --- a/packages/frontend-core/src/api/app.js +++ b/packages/frontend-core/src/api/app.js @@ -122,4 +122,14 @@ export const buildAppEndpoints = API => ({ url: `/api/dev/${appId}/lock`, }) }, + + /** + * Syncs an app with the production database. + * @param appId the ID of the app to sync + */ + syncApp: async appId => { + return await API.post({ + url: `/api/applications/${appId}/sync`, + }) + }, }) diff --git a/packages/frontend-core/src/api/auth.js b/packages/frontend-core/src/api/auth.js index 4f61c155f0..5f280b50da 100644 --- a/packages/frontend-core/src/api/auth.js +++ b/packages/frontend-core/src/api/auth.js @@ -36,6 +36,17 @@ export const buildAuthEndpoints = API => ({ }) }, + /** + * 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 @@ -116,4 +127,38 @@ export const buildAuthEndpoints = API => ({ 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", + }) + }, }) diff --git a/packages/frontend-core/src/api/automations.js b/packages/frontend-core/src/api/automations.js index 95605a91a5..2843599e78 100644 --- a/packages/frontend-core/src/api/automations.js +++ b/packages/frontend-core/src/api/automations.js @@ -8,4 +8,67 @@ export const buildAutomationEndpoints = API => ({ body: { fields }, }) }, + + /** + * Tests an automation with data. + * @param automationId the ID of the automation to test + * @param testData the test data to run against the automation + */ + testAutomation: async ({ automationId, testData }) => { + return await API.post({ + url: `/api/automations/${automationId}/test`, + body: testData, + }) + }, + + /** + * Gets a list of all automations. + */ + getAutomations: async () => { + return await API.get({ + url: "/api/automations", + }) + }, + + /** + * Gets a list of all the definitions for blocks in automations. + */ + getAutomationDefinitions: async () => { + return await API.get({ + url: "/api/automations/definitions/list", + }) + }, + + /** + * Creates an automation. + * @param automation the automation to create + */ + createAutomation: async automation => { + return await API.post({ + url: "/api/automations", + body: automation, + }) + }, + + /** + * Updates an automation. + * @param automation the automation to update + */ + updateAutomation: async automation => { + return await API.put({ + url: "/api/automations", + body: automation, + }) + }, + + /** + * Deletes an automation + * @param automationId the ID of the automation to delete + * @param automationRev the rev of the automation to delete + */ + deleteAutomation: async ({ automationId, automationRev }) => { + return await API.delete({ + url: `/api/automations/${automationId}/${automationRev}`, + }) + }, }) diff --git a/packages/frontend-core/src/api/builder.js b/packages/frontend-core/src/api/builder.js index dbe64e611e..dbb34a6a7b 100644 --- a/packages/frontend-core/src/api/builder.js +++ b/packages/frontend-core/src/api/builder.js @@ -5,13 +5,26 @@ export const buildBuilderEndpoints = API => ({ * @param {string} appId - ID of the currently running app */ fetchComponentLibDefinitions: async appId => { - return await API.get({ url: `/api/${appId}/components/definitions` }) + return await API.get({ + url: `/api/${appId}/components/definitions`, + }) }, /** * Gets the list of available integrations. */ getIntegrations: async () => { - return await API.get({ url: "/api/integrations" }) + return await API.get({ + url: "/api/integrations", + }) + }, + + /** + * Gets the version of the installed Budibase environment. + */ + getBudibaseVersion: async () => { + return await API.get({ + url: "/api/dev/version", + }) }, }) diff --git a/packages/frontend-core/src/api/hosting.js b/packages/frontend-core/src/api/hosting.js new file mode 100644 index 0000000000..8c398f9ae7 --- /dev/null +++ b/packages/frontend-core/src/api/hosting.js @@ -0,0 +1,19 @@ +export const buildHostingEndpoints = API => ({ + /** + * Gets the hosting URLs of the environment. + */ + getHostingURLs: async () => { + return await API.get({ + url: "/api/hosting/urls", + }) + }, + + /** + * Gets the list of deployed apps. + */ + getDeployedApps: async () => { + return await API.get({ + url: "/api/hosting/apps", + }) + }, +}) diff --git a/packages/frontend-core/src/api/index.js b/packages/frontend-core/src/api/index.js index e1cf2f3619..1f7a703dcf 100644 --- a/packages/frontend-core/src/api/index.js +++ b/packages/frontend-core/src/api/index.js @@ -4,6 +4,7 @@ import { buildAppEndpoints } from "./app" import { buildAttachmentEndpoints } from "./attachments" import { buildAuthEndpoints } from "./auth" import { buildAutomationEndpoints } from "./automations" +import { buildHostingEndpoints } from "./hosting" import { buildQueryEndpoints } from "./queries" import { buildRelationshipEndpoints } from "./relationships" import { buildRouteEndpoints } from "./routes" @@ -35,6 +36,7 @@ export const createAPIClient = config => { // Try to read a message from the error let message = response.statusText + let json = null try { const json = await response.json() if (json?.message) { @@ -47,6 +49,7 @@ export const createAPIClient = config => { } return { message, + json, status: response.status, url: response.url, method, @@ -58,6 +61,7 @@ export const createAPIClient = config => { const makeError = message => { return { message, + json: null, status: 400, url: "", method: "", @@ -173,6 +177,7 @@ export const createAPIClient = config => { ...buildAttachmentEndpoints(API), ...buildAuthEndpoints(API), ...buildAutomationEndpoints(API), + ...buildHostingEndpoints(API), ...buildQueryEndpoints(API), ...buildRelationshipEndpoints(API), ...buildRouteEndpoints(API),