Merge branch 'master' of github.com:Budibase/budibase into frontend-core-ts-2

This commit is contained in:
Andrew Kingston 2024-12-16 14:40:06 +00:00
commit cb88f1faa9
No known key found for this signature in database
42 changed files with 211 additions and 93 deletions

View File

@ -1,6 +1,6 @@
{ {
"$schema": "node_modules/lerna/schemas/lerna-schema.json", "$schema": "node_modules/lerna/schemas/lerna-schema.json",
"version": "3.2.28", "version": "3.2.29",
"npmClient": "yarn", "npmClient": "yarn",
"concurrency": 20, "concurrency": 20,
"command": { "command": {

View File

@ -43,12 +43,11 @@
flex-direction: row; flex-direction: row;
justify-content: flex-start; justify-content: flex-start;
align-items: stretch; align-items: stretch;
overflow-y: scroll !important;
flex: 1 1 auto; flex: 1 1 auto;
overflow-x: hidden; overflow-x: hidden;
} }
.main { .main {
overflow: auto; overflow-y: scroll;
} }
.content { .content {
display: flex; display: flex;

View File

@ -61,7 +61,7 @@ a {
height: 8px; height: 8px;
} }
::-webkit-scrollbar-track { ::-webkit-scrollbar-track {
background: var(--spectrum-alias-background-color-default); background: transparent;
} }
::-webkit-scrollbar-thumb { ::-webkit-scrollbar-thumb {
background-color: var(--spectrum-global-color-gray-400); background-color: var(--spectrum-global-color-gray-400);
@ -71,6 +71,5 @@ a {
background: var(--spectrum-alias-background-color-default); background: var(--spectrum-alias-background-color-default);
} }
html * { html * {
scrollbar-color: var(--spectrum-global-color-gray-400) scrollbar-color: var(--spectrum-global-color-gray-400) transparent;
var(--spectrum-alias-background-color-default);
} }

@ -1 +1 @@
Subproject commit e7c9f08aeb0498a20594f3c912afedcfdc220a6a Subproject commit 7fc699463b3957eb050351b983edef0d25a531ae

View File

@ -1,10 +1,11 @@
import { events, context } from "@budibase/backend-core" import { context, events } from "@budibase/backend-core"
import { import {
AnalyticsPingRequest,
App,
PingSource,
Ctx,
AnalyticsEnabledResponse, AnalyticsEnabledResponse,
AnalyticsPingRequest,
AnalyticsPingResponse,
App,
Ctx,
PingSource,
} from "@budibase/types" } from "@budibase/types"
import { DocumentType, isDevAppID } from "../../db/utils" import { DocumentType, isDevAppID } from "../../db/utils"
@ -15,9 +16,12 @@ export const isEnabled = async (ctx: Ctx<void, AnalyticsEnabledResponse>) => {
} }
} }
export const ping = async (ctx: Ctx<AnalyticsPingRequest, void>) => { export const ping = async (
ctx: Ctx<AnalyticsPingRequest, AnalyticsPingResponse>
) => {
const body = ctx.request.body const body = ctx.request.body
let pingType: PingSource | undefined
switch (body.source) { switch (body.source) {
case PingSource.APP: { case PingSource.APP: {
const db = context.getAppDB({ skip_setup: true }) const db = context.getAppDB({ skip_setup: true })
@ -29,13 +33,18 @@ export const ping = async (ctx: Ctx<AnalyticsPingRequest, void>) => {
} else { } else {
await events.serve.servedApp(appInfo, body.timezone, body.embedded) await events.serve.servedApp(appInfo, body.timezone, body.embedded)
} }
pingType = PingSource.APP
break break
} }
case PingSource.BUILDER: { case PingSource.BUILDER: {
await events.serve.servedBuilder(body.timezone) await events.serve.servedBuilder(body.timezone)
pingType = PingSource.BUILDER
break break
} }
} }
ctx.status = 200 ctx.body = {
message: "pong",
source: pingType,
}
} }

View File

@ -68,6 +68,9 @@ import {
ImportToUpdateAppRequest, ImportToUpdateAppRequest,
ImportToUpdateAppResponse, ImportToUpdateAppResponse,
SetRevertableAppVersionRequest, SetRevertableAppVersionRequest,
AddAppSampleDataResponse,
UnpublishAppResponse,
SetRevertableAppVersionResponse,
} from "@budibase/types" } from "@budibase/types"
import { BASE_LAYOUT_PROP_IDS } from "../../constants/layouts" import { BASE_LAYOUT_PROP_IDS } from "../../constants/layouts"
import sdk from "../../sdk" import sdk from "../../sdk"
@ -175,7 +178,9 @@ async function createInstance(appId: string, template: AppTemplate) {
return { _id: appId } return { _id: appId }
} }
export const addSampleData = async (ctx: UserCtx<void, void>) => { export const addSampleData = async (
ctx: UserCtx<void, AddAppSampleDataResponse>
) => {
const db = context.getAppDB() const db = context.getAppDB()
try { try {
@ -188,7 +193,7 @@ export const addSampleData = async (ctx: UserCtx<void, void>) => {
await db.bulkDocs([...defaultDbDocs]) await db.bulkDocs([...defaultDbDocs])
} }
ctx.status = 200 ctx.body = { message: "Sample tables added." }
} }
export async function fetch(ctx: UserCtx<void, FetchAppsResponse>) { export async function fetch(ctx: UserCtx<void, FetchAppsResponse>) {
@ -528,7 +533,6 @@ export async function create(
await appPostCreate(ctx, newApplication) await appPostCreate(ctx, newApplication)
await cache.bustCache(cache.CacheKey.CHECKLIST) await cache.bustCache(cache.CacheKey.CHECKLIST)
ctx.body = newApplication ctx.body = newApplication
ctx.status = 200
} }
// This endpoint currently operates as a PATCH rather than a PUT // This endpoint currently operates as a PATCH rather than a PUT
@ -551,7 +555,6 @@ export async function update(
const app = await updateAppPackage(ctx.request.body, ctx.params.appId) const app = await updateAppPackage(ctx.request.body, ctx.params.appId)
await events.app.updated(app) await events.app.updated(app)
ctx.status = 200
ctx.body = app ctx.body = app
builderSocket?.emitAppMetadataUpdate(ctx, { builderSocket?.emitAppMetadataUpdate(ctx, {
theme: app.theme, theme: app.theme,
@ -592,7 +595,6 @@ export async function updateClient(
} }
const app = await updateAppPackage(appPackageUpdates, ctx.params.appId) const app = await updateAppPackage(appPackageUpdates, ctx.params.appId)
await events.app.versionUpdated(app, currentVersion, updatedToVersion) await events.app.versionUpdated(app, currentVersion, updatedToVersion)
ctx.status = 200
ctx.body = app ctx.body = app
} }
@ -624,7 +626,6 @@ export async function revertClient(
} }
const app = await updateAppPackage(appPackageUpdates, ctx.params.appId) const app = await updateAppPackage(appPackageUpdates, ctx.params.appId)
await events.app.versionReverted(app, currentVersion, revertedToVersion) await events.app.versionReverted(app, currentVersion, revertedToVersion)
ctx.status = 200
ctx.body = app ctx.body = app
} }
@ -689,11 +690,10 @@ export async function destroy(ctx: UserCtx<void, DeleteAppResponse>) {
await preDestroyApp(ctx) await preDestroyApp(ctx)
const result = await destroyApp(ctx) const result = await destroyApp(ctx)
await postDestroyApp(ctx) await postDestroyApp(ctx)
ctx.status = 200
ctx.body = result ctx.body = result
} }
export async function unpublish(ctx: UserCtx<void, void>) { export async function unpublish(ctx: UserCtx<void, UnpublishAppResponse>) {
const prodAppId = dbCore.getProdAppID(ctx.params.appId) const prodAppId = dbCore.getProdAppID(ctx.params.appId)
const dbExists = await dbCore.dbExists(prodAppId) const dbExists = await dbCore.dbExists(prodAppId)
@ -705,8 +705,8 @@ export async function unpublish(ctx: UserCtx<void, void>) {
await preDestroyApp(ctx) await preDestroyApp(ctx)
await unpublishApp(ctx) await unpublishApp(ctx)
await postDestroyApp(ctx) await postDestroyApp(ctx)
ctx.status = 204
builderSocket?.emitAppUnpublish(ctx) builderSocket?.emitAppUnpublish(ctx)
ctx.body = { message: "App unpublished." }
} }
export async function sync(ctx: UserCtx<void, SyncAppResponse>) { export async function sync(ctx: UserCtx<void, SyncAppResponse>) {
@ -802,7 +802,6 @@ export async function duplicateApp(
duplicateAppId: newApplication?.appId, duplicateAppId: newApplication?.appId,
sourceAppId, sourceAppId,
} }
ctx.status = 200
} }
export async function updateAppPackage( export async function updateAppPackage(
@ -830,7 +829,7 @@ export async function updateAppPackage(
} }
export async function setRevertableVersion( export async function setRevertableVersion(
ctx: UserCtx<SetRevertableAppVersionRequest, void> ctx: UserCtx<SetRevertableAppVersionRequest, SetRevertableAppVersionResponse>
) { ) {
if (!env.isDev()) { if (!env.isDev()) {
ctx.status = 403 ctx.status = 403
@ -840,8 +839,7 @@ export async function setRevertableVersion(
const app = await sdk.applications.metadata.get() const app = await sdk.applications.metadata.get()
app.revertableVersion = ctx.request.body.revertableVersion app.revertableVersion = ctx.request.body.revertableVersion
await db.put(app) await db.put(app)
ctx.body = { message: "Revertable version updated." }
ctx.status = 200
} }
async function migrateAppNavigation() { async function migrateAppNavigation() {

View File

@ -62,7 +62,6 @@ export async function create(
const createdAutomation = await sdk.automations.create(automation) const createdAutomation = await sdk.automations.create(automation)
ctx.status = 200
ctx.body = { ctx.body = {
message: "Automation created successfully", message: "Automation created successfully",
automation: createdAutomation, automation: createdAutomation,
@ -84,7 +83,6 @@ export async function update(
const updatedAutomation = await sdk.automations.update(automation) const updatedAutomation = await sdk.automations.update(automation)
ctx.status = 200
ctx.body = { ctx.body = {
message: `Automation ${automation._id} updated successfully.`, message: `Automation ${automation._id} updated successfully.`,
automation: updatedAutomation, automation: updatedAutomation,

View File

@ -182,7 +182,6 @@ export async function update(
} }
} }
ctx.status = 200
ctx.message = "Datasource saved successfully." ctx.message = "Datasource saved successfully."
ctx.body = { ctx.body = {
datasource: await sdk.datasources.removeSecretSingle(datasource), datasource: await sdk.datasources.removeSecretSingle(datasource),
@ -290,8 +289,7 @@ export async function destroy(ctx: UserCtx<void, DeleteDatasourceResponse>) {
await db.remove(datasourceId, ctx.params.revId) await db.remove(datasourceId, ctx.params.revId)
await events.datasource.deleted(datasource) await events.datasource.deleted(datasource)
ctx.message = `Datasource deleted.` ctx.body = { message: `Datasource deleted.` }
ctx.status = 200
builderSocket?.emitDatasourceDeletion(ctx, datasourceId) builderSocket?.emitDatasourceDeletion(ctx, datasourceId)
} }

View File

@ -29,7 +29,6 @@ export async function save(
layout._rev = response.rev layout._rev = response.rev
ctx.body = layout ctx.body = layout
ctx.status = 200
} }
export async function destroy(ctx: UserCtx<void, DeleteLayoutResponse>) { export async function destroy(ctx: UserCtx<void, DeleteLayoutResponse>) {
@ -51,5 +50,4 @@ export async function destroy(ctx: UserCtx<void, DeleteLayoutResponse>) {
await db.remove(layoutId, layoutRev) await db.remove(layoutId, layoutRev)
await events.layout.deleted(layoutId) await events.layout.deleted(layoutId)
ctx.body = { message: "Layout deleted successfully" } ctx.body = { message: "Layout deleted successfully" }
ctx.status = 200
} }

View File

@ -4,6 +4,7 @@ import {
Ctx, Ctx,
FetchOldMigrationResponse, FetchOldMigrationResponse,
GetOldMigrationStatus, GetOldMigrationStatus,
RuneOldMigrationResponse,
RunOldMigrationRequest, RunOldMigrationRequest,
} from "@budibase/types" } from "@budibase/types"
import { import {
@ -11,18 +12,19 @@ import {
getLatestEnabledMigrationId, getLatestEnabledMigrationId,
} from "../../appMigrations" } from "../../appMigrations"
export async function migrate(ctx: Ctx<RunOldMigrationRequest, void>) { export async function migrate(
ctx: Ctx<RunOldMigrationRequest, RuneOldMigrationResponse>
) {
const options = ctx.request.body const options = ctx.request.body
// don't await as can take a while, just return // don't await as can take a while, just return
migrationImpl(options) migrationImpl(options)
ctx.status = 200 ctx.body = { message: "Migration started." }
} }
export async function fetchDefinitions( export async function fetchDefinitions(
ctx: Ctx<void, FetchOldMigrationResponse> ctx: Ctx<void, FetchOldMigrationResponse>
) { ) {
ctx.body = MIGRATIONS ctx.body = MIGRATIONS
ctx.status = 200
} }
export async function getMigrationStatus( export async function getMigrationStatus(
@ -41,5 +43,4 @@ export async function getMigrationStatus(
!latestMigrationId || latestAppliedMigration >= latestMigrationId !latestMigrationId || latestAppliedMigration >= latestMigrationId
ctx.body = { migrated } ctx.body = { migrated }
ctx.status = 200
} }

View File

@ -99,7 +99,7 @@ export async function getDependantResources(
export async function addPermission(ctx: UserCtx<void, AddPermissionResponse>) { export async function addPermission(ctx: UserCtx<void, AddPermissionResponse>) {
const params: AddPermissionRequest = ctx.params const params: AddPermissionRequest = ctx.params
await sdk.permissions.updatePermissionOnRole(params, PermissionUpdateType.ADD) await sdk.permissions.updatePermissionOnRole(params, PermissionUpdateType.ADD)
ctx.status = 200 ctx.body = { message: "Permission added." }
} }
export async function removePermission( export async function removePermission(
@ -110,5 +110,5 @@ export async function removePermission(
params, params,
PermissionUpdateType.REMOVE PermissionUpdateType.REMOVE
) )
ctx.status = 200 ctx.body = { message: "Permission removed." }
} }

View File

@ -104,7 +104,6 @@ const _import = async (
...importResult, ...importResult,
datasourceId, datasourceId,
} }
ctx.status = 200
} }
export { _import as import } export { _import as import }
@ -455,6 +454,5 @@ export async function destroy(ctx: UserCtx<void, DeleteQueryResponse>) {
const datasource = await sdk.datasources.get(query.datasourceId) const datasource = await sdk.datasources.get(query.datasourceId)
await db.remove(ctx.params.queryId, ctx.params.revId) await db.remove(ctx.params.queryId, ctx.params.revId)
ctx.body = { message: `Query deleted.` } ctx.body = { message: `Query deleted.` }
ctx.status = 200
await events.query.deleted(datasource, query) await events.query.deleted(datasource, query)
} }

View File

@ -234,8 +234,7 @@ export async function destroy(ctx: UserCtx<void, DeleteRoleResponse>) {
// clean up inherits // clean up inherits
await removeRoleFromOthers(roleId) await removeRoleFromOthers(roleId)
ctx.message = `Role ${ctx.params.roleId} deleted successfully` ctx.body = { message: `Role ${ctx.params.roleId} deleted successfully` }
ctx.status = 200
builderSocket?.emitRoleDeletion(ctx, role) builderSocket?.emitRoleDeletion(ctx, role)
} }

View File

@ -71,7 +71,6 @@ export async function patch(
if (!row) { if (!row) {
ctx.throw(404, "Row not found") ctx.throw(404, "Row not found")
} }
ctx.status = 200
ctx.eventEmitter?.emitRow({ ctx.eventEmitter?.emitRow({
eventName: EventType.ROW_UPDATE, eventName: EventType.ROW_UPDATE,
@ -110,7 +109,6 @@ export const save = async (ctx: UserCtx<SaveRowRequest, SaveRowResponse>) => {
: await quotas.addRow(() => : await quotas.addRow(() =>
sdk.rows.save(sourceId, ctx.request.body, ctx.user?._id) sdk.rows.save(sourceId, ctx.request.body, ctx.user?._id)
) )
ctx.status = 200
ctx.eventEmitter?.emitRow({ ctx.eventEmitter?.emitRow({
eventName: EventType.ROW_SAVE, eventName: EventType.ROW_SAVE,
@ -223,7 +221,6 @@ async function deleteRow(ctx: UserCtx<DeleteRowRequest>) {
export async function destroy(ctx: UserCtx<DeleteRowRequest>) { export async function destroy(ctx: UserCtx<DeleteRowRequest>) {
let response, row let response, row
ctx.status = 200
if (isDeleteRows(ctx.request.body)) { if (isDeleteRows(ctx.request.body)) {
response = await deleteRows(ctx) response = await deleteRows(ctx)
@ -275,7 +272,6 @@ export async function search(ctx: Ctx<SearchRowRequest, SearchRowResponse>) {
rows: undefined, rows: undefined,
} }
ctx.status = 200
ctx.body = await sdk.rows.search(searchParams) ctx.body = await sdk.rows.search(searchParams)
} }

View File

@ -1,10 +1,16 @@
import { RowActionTriggerRequest, Ctx } from "@budibase/types" import {
RowActionTriggerRequest,
Ctx,
RowActionTriggerResponse,
} from "@budibase/types"
import sdk from "../../../sdk" import sdk from "../../../sdk"
export async function run(ctx: Ctx<RowActionTriggerRequest, void>) { export async function run(
ctx: Ctx<RowActionTriggerRequest, RowActionTriggerResponse>
) {
const { tableId, actionId } = ctx.params const { tableId, actionId } = ctx.params
const { rowId } = ctx.request.body const { rowId } = ctx.request.body
await sdk.rowActions.run(tableId, actionId, rowId, ctx.user) await sdk.rowActions.run(tableId, actionId, rowId, ctx.user)
ctx.status = 200 ctx.body = { message: "Row action triggered." }
} }

View File

@ -123,7 +123,6 @@ export async function destroy(ctx: UserCtx<void, DeleteScreenResponse>) {
ctx.body = { ctx.body = {
message: "Screen deleted successfully", message: "Screen deleted successfully",
} }
ctx.status = 200
builderSocket?.emitScreenDeletion(ctx, id) builderSocket?.emitScreenDeletion(ctx, id)
} }

View File

@ -136,7 +136,6 @@ export async function save(ctx: UserCtx<SaveTableRequest, SaveTableResponse>) {
if (isImport) { if (isImport) {
await events.table.imported(savedTable) await events.table.imported(savedTable)
} }
ctx.status = 200
ctx.message = `Table ${table.name} saved successfully.` ctx.message = `Table ${table.name} saved successfully.`
ctx.eventEmitter?.emitTable(EventType.TABLE_SAVE, appId, { ...savedTable }) ctx.eventEmitter?.emitTable(EventType.TABLE_SAVE, appId, { ...savedTable })
ctx.body = savedTable ctx.body = savedTable
@ -153,7 +152,6 @@ export async function destroy(ctx: UserCtx<void, DeleteTableResponse>) {
await events.table.deleted(deletedTable) await events.table.deleted(deletedTable)
ctx.eventEmitter?.emitTable(EventType.TABLE_DELETE, appId, deletedTable) ctx.eventEmitter?.emitTable(EventType.TABLE_DELETE, appId, deletedTable)
ctx.status = 200
ctx.table = deletedTable ctx.table = deletedTable
ctx.body = { message: `Table ${tableId} deleted.` } ctx.body = { message: `Table ${tableId} deleted.` }
builderSocket?.emitTableDeletion(ctx, deletedTable) builderSocket?.emitTableDeletion(ctx, deletedTable)
@ -169,7 +167,6 @@ export async function bulkImport(
// can only be done in the builder, but in the future we may need to // can only be done in the builder, but in the future we may need to
// think about events for bulk items // think about events for bulk items
ctx.status = 200
ctx.body = { message: `Bulk rows created.` } ctx.body = { message: `Bulk rows created.` }
} }
@ -180,7 +177,6 @@ export async function csvToJson(
const result = await jsonFromCsvString(csvString) const result = await jsonFromCsvString(csvString)
ctx.status = 200
ctx.body = result ctx.body = result
} }
@ -190,7 +186,6 @@ export async function validateNewTableImport(
const { rows, schema } = ctx.request.body const { rows, schema } = ctx.request.body
if (isRows(rows) && isSchema(schema)) { if (isRows(rows) && isSchema(schema)) {
ctx.status = 200
ctx.body = validateSchema(rows, schema, PROTECTED_INTERNAL_COLUMNS) ctx.body = validateSchema(rows, schema, PROTECTED_INTERNAL_COLUMNS)
} else { } else {
ctx.status = 422 ctx.status = 422
@ -224,7 +219,6 @@ export async function validateExistingTableImport(
} }
if (tableId && isRows(rows) && isSchema(schema)) { if (tableId && isRows(rows) && isSchema(schema)) {
ctx.status = 200
ctx.body = validateSchema(rows, schema, protectedColumnNames) ctx.body = validateSchema(rows, schema, protectedColumnNames)
} else { } else {
ctx.status = 422 ctx.status = 422
@ -245,6 +239,5 @@ export async function migrate(
}) })
} }
ctx.status = 200
ctx.body = { message: `Column ${oldColumn} migrated.` } ctx.body = { message: `Column ${oldColumn} migrated.` }
} }

View File

@ -112,7 +112,7 @@ export async function run({
response: ctx.body, response: ctx.body,
id: ctx.body._id, id: ctx.body._id,
revision: ctx.body._rev, revision: ctx.body._rev,
success: ctx.status === 200, success: !!ctx.body._id,
} }
} catch (err) { } catch (err) {
return { return {

View File

@ -94,7 +94,7 @@ export async function run({
return { return {
response: ctx.body, response: ctx.body,
row: ctx.row, row: ctx.row,
success: ctx.status === 200, success: ctx.body.ok,
} }
} catch (err) { } catch (err) {
return { return {

View File

@ -152,7 +152,7 @@ export async function run({
return { return {
rows, rows,
success: ctx.status === 200, success: true,
} }
} catch (err) { } catch (err) {
return { return {

View File

@ -166,7 +166,7 @@ export async function run({
response: ctx.message, response: ctx.message,
id: ctx.body._id, id: ctx.body._id,
revision: ctx.body._rev, revision: ctx.body._rev,
success: ctx.status === 200, success: !!ctx.body._id,
} }
} catch (err) { } catch (err) {
return { return {

View File

@ -96,7 +96,7 @@ describe("Test a query step automation", () => {
) )
.run() .run()
expect(result.steps[0].outputs.success).toBe(false) expect(result.steps[0].outputs.success).toBe(true)
expect(result.steps[0].outputs.rows).toBeDefined() expect(result.steps[0].outputs.rows).toBeDefined()
expect(result.steps[0].outputs.rows.length).toBe(0) expect(result.steps[0].outputs.rows.length).toBe(0)
}) })
@ -125,7 +125,7 @@ describe("Test a query step automation", () => {
) )
.run() .run()
expect(result.steps[0].outputs.success).toBe(false) expect(result.steps[0].outputs.success).toBe(true)
expect(result.steps[0].outputs.rows).toBeDefined() expect(result.steps[0].outputs.rows).toBeDefined()
expect(result.steps[0].outputs.rows.length).toBe(0) expect(result.steps[0].outputs.rows.length).toBe(0)
}) })

View File

@ -48,7 +48,7 @@ export class ApplicationAPI extends TestAPI {
unpublish = async (appId: string): Promise<void> => { unpublish = async (appId: string): Promise<void> => {
await this._post(`/api/applications/${appId}/unpublish`, { await this._post(`/api/applications/${appId}/unpublish`, {
expectations: { status: 204 }, expectations: { status: 200 },
}) })
} }

View File

@ -1,7 +1,7 @@
import authorized from "../middleware/authorized" import authorized from "../middleware/authorized"
import currentApp from "../middleware/currentapp" import currentApp from "../middleware/currentapp"
import { BaseSocket } from "./websocket" import { BaseSocket } from "./websocket"
import { auth, permissions } from "@budibase/backend-core" import { auth, permissions, context } from "@budibase/backend-core"
import http from "http" import http from "http"
import Koa from "koa" import Koa from "koa"
import { getSourceId } from "../api/controllers/row/utils" import { getSourceId } from "../api/controllers/row/utils"
@ -10,6 +10,12 @@ import { Socket } from "socket.io"
import { GridSocketEvent } from "@budibase/shared-core" import { GridSocketEvent } from "@budibase/shared-core"
import { userAgent } from "koa-useragent" import { userAgent } from "koa-useragent"
import { createContext, runMiddlewares } from "./middleware" import { createContext, runMiddlewares } from "./middleware"
import sdk from "../sdk"
import {
findHBSBlocks,
isJSBinding,
decodeJSBinding,
} from "@budibase/string-templates"
const { PermissionType, PermissionLevel } = permissions const { PermissionType, PermissionLevel } = permissions
@ -18,15 +24,46 @@ export default class GridSocket extends BaseSocket {
super(app, server, "/socket/grid") super(app, server, "/socket/grid")
} }
// Checks if a view's query contains any current user bindings
containsCurrentUserBinding(view: ViewV2): boolean {
return findHBSBlocks(JSON.stringify(view.query))
.map(binding => {
const sanitizedBinding = binding.replace(/\\"/g, '"')
if (isJSBinding(sanitizedBinding)) {
return decodeJSBinding(sanitizedBinding)
} else {
return sanitizedBinding
}
})
.some(binding => binding?.includes("[user]"))
}
async onConnect(socket: Socket) { async onConnect(socket: Socket) {
// Initial identification of connected spreadsheet // Initial identification of connected spreadsheet
socket.on(GridSocketEvent.SelectDatasource, async (payload, callback) => { socket.on(GridSocketEvent.SelectDatasource, async (payload, callback) => {
const ds = payload.datasource const ds = payload.datasource
const appId = payload.appId const appId = payload.appId
const resourceId = ds?.type === "table" ? ds?.tableId : ds?.id const resourceId = ds?.type === "table" ? ds?.tableId : ds?.id
let valid = true
// Ignore if no table or app specified // Validate datasource
if (!resourceId || !appId) { if (!resourceId || !appId) {
// Ignore if no table or app specified
valid = false
} else if (ds.type === "viewV2") {
// If this is a view filtered by current user, don't sync changes
try {
await context.doInAppContext(appId, async () => {
const view = await sdk.views.get(ds.id)
if (this.containsCurrentUserBinding(view)) {
valid = false
}
})
} catch (err) {
valid = false
}
}
if (!valid) {
socket.disconnect(true) socket.disconnect(true)
return return
} }

View File

@ -12,3 +12,7 @@ export interface AnalyticsPingRequest {
timezone: string timezone: string
embedded?: boolean embedded?: boolean
} }
export interface AnalyticsPingResponse {
message: string
source?: PingSource
}

View File

@ -44,6 +44,10 @@ export interface FetchAppPackageResponse {
hasLock: boolean hasLock: boolean
} }
export interface AddAppSampleDataResponse {
message: string
}
export type FetchAppsResponse = App[] export type FetchAppsResponse = App[]
export interface PublishResponse { export interface PublishResponse {
@ -61,6 +65,10 @@ export interface DeleteAppResponse {
ok: boolean ok: boolean
} }
export interface UnpublishAppResponse {
message: string
}
export interface ImportToUpdateAppRequest { export interface ImportToUpdateAppRequest {
encryptionPassword?: string encryptionPassword?: string
} }
@ -71,6 +79,9 @@ export interface ImportToUpdateAppResponse {
export interface SetRevertableAppVersionRequest { export interface SetRevertableAppVersionRequest {
revertableVersion: string revertableVersion: string
} }
export interface SetRevertableAppVersionResponse {
message: string
}
export interface ExportAppDumpRequest { export interface ExportAppDumpRequest {
excludeRows: boolean excludeRows: boolean

View File

@ -29,7 +29,9 @@ export interface AddedPermission {
reason?: string reason?: string
} }
export interface AddPermissionResponse {} export interface AddPermissionResponse {
message: string
}
export interface AddPermissionRequest { export interface AddPermissionRequest {
roleId: string roleId: string
@ -38,4 +40,6 @@ export interface AddPermissionRequest {
} }
export interface RemovePermissionRequest extends AddPermissionRequest {} export interface RemovePermissionRequest extends AddPermissionRequest {}
export interface RemovePermissionResponse {} export interface RemovePermissionResponse {
message: string
}

View File

@ -21,6 +21,9 @@ export interface RowActionsResponse {
export interface RowActionTriggerRequest { export interface RowActionTriggerRequest {
rowId: string rowId: string
} }
export interface RowActionTriggerResponse {
message: string
}
export interface RowActionPermissionsResponse export interface RowActionPermissionsResponse
extends RowActionPermissionsData {} extends RowActionPermissionsData {}

View File

@ -2,12 +2,19 @@ export interface LoginRequest {
username: string username: string
password: string password: string
} }
export interface LoginResponse {
message: string
userId?: string
}
export interface LogoutResponse { export interface LogoutResponse {
message: string message: string
} }
export interface SetInitInfoRequest extends Record<string, any> {} export interface SetInitInfoRequest extends Record<string, any> {}
export interface SetInitInfoResponse {
message: string
}
export interface GetInitInfoResponse extends Record<string, any> {} export interface GetInitInfoResponse extends Record<string, any> {}

View File

@ -7,12 +7,22 @@ export interface CreateEnvironmentVariableRequest {
production: string production: string
development: string development: string
} }
export interface CreateEnvironmentVariableResponse {
message: string
}
export interface UpdateEnvironmentVariableRequest { export interface UpdateEnvironmentVariableRequest {
production: string production: string
development: string development: string
} }
export interface UpdateEnvironmentVariableResponse {
message: string
}
export interface GetEnvironmentVariablesResponse { export interface GetEnvironmentVariablesResponse {
variables: string[] variables: string[]
} }
export interface DeleteEnvironmentVariablesResponse {
message: string
}

View File

@ -5,3 +5,6 @@ export enum EventPublishType {
export interface PostEventPublishRequest { export interface PostEventPublishRequest {
type: EventPublishType type: EventPublishType
} }
export interface PostEventPublishResponse {
message: string
}

View File

@ -5,6 +5,9 @@ import { QuotaUsage } from "../../../documents"
export interface ActivateLicenseKeyRequest { export interface ActivateLicenseKeyRequest {
licenseKey: string licenseKey: string
} }
export interface ActivateLicenseKeyResponse {
message: string
}
export interface GetLicenseKeyResponse { export interface GetLicenseKeyResponse {
licenseKey: string licenseKey: string
@ -15,11 +18,20 @@ export interface GetLicenseKeyResponse {
export interface ActivateOfflineLicenseTokenRequest { export interface ActivateOfflineLicenseTokenRequest {
offlineLicenseToken: string offlineLicenseToken: string
} }
export interface ActivateOfflineLicenseTokenResponse {
message: string
}
export interface GetOfflineLicenseTokenResponse { export interface GetOfflineLicenseTokenResponse {
offlineLicenseToken: string offlineLicenseToken: string
} }
// REFRESH
export interface RefreshOfflineLicenseResponse {
message: string
}
// IDENTIFIER // IDENTIFIER
export interface GetOfflineIdentifierResponse { export interface GetOfflineIdentifierResponse {

View File

@ -1,6 +1,9 @@
import { Migration, MigrationOptions } from "../../../sdk" import { Migration, MigrationOptions } from "../../../sdk"
export interface RunOldMigrationRequest extends MigrationOptions {} export interface RunOldMigrationRequest extends MigrationOptions {}
export interface RuneOldMigrationResponse {
message: string
}
export type FetchOldMigrationResponse = Migration[] export type FetchOldMigrationResponse = Migration[]

View File

@ -1,5 +1,8 @@
import { MigrationDefinition, MigrationOptions } from "../../../sdk" import { MigrationDefinition, MigrationOptions } from "../../../sdk"
export interface RunGlobalMigrationRequest extends MigrationOptions {} export interface RunGlobalMigrationRequest extends MigrationOptions {}
export interface RunGlobalMigrationResponse {
message: string
}
export type FetchMigrationDefinitionsResponse = MigrationDefinition[] export type FetchMigrationDefinitionsResponse = MigrationDefinition[]

View File

@ -110,6 +110,9 @@ export interface AddSSoUserRequest {
ssoId: string ssoId: string
email: string email: string
} }
export interface AddSSoUserResponse {
message: string
}
export interface CreateAdminUserResponse { export interface CreateAdminUserResponse {
_id: string _id: string

View File

@ -22,6 +22,8 @@ import {
GetInitInfoResponse, GetInitInfoResponse,
PasswordResetResponse, PasswordResetResponse,
PasswordResetUpdateResponse, PasswordResetUpdateResponse,
SetInitInfoResponse,
LoginResponse,
} from "@budibase/types" } from "@budibase/types"
import env from "../../../environment" import env from "../../../environment"
import { Next } from "koa" import { Next } from "koa"
@ -59,7 +61,10 @@ async function passportCallback(
ctx.set(Header.TOKEN, token) ctx.set(Header.TOKEN, token)
} }
export const login = async (ctx: Ctx<LoginRequest, void>, next: Next) => { export const login = async (
ctx: Ctx<LoginRequest, LoginResponse>,
next: Next
) => {
const email = ctx.request.body.username const email = ctx.request.body.username
const user = await userSdk.db.getUserByEmail(email) const user = await userSdk.db.getUserByEmail(email)
@ -74,7 +79,10 @@ export const login = async (ctx: Ctx<LoginRequest, void>, next: Next) => {
await context.identity.doInUserContext(user, ctx, async () => { await context.identity.doInUserContext(user, ctx, async () => {
await events.auth.login("local", user.email) await events.auth.login("local", user.email)
}) })
ctx.status = 200 ctx.body = {
message: "Login successful",
userId: user.userId,
}
} }
)(ctx, next) )(ctx, next)
} }
@ -88,10 +96,14 @@ export const logout = async (ctx: UserCtx<void, LogoutResponse>) => {
// INIT // INIT
export const setInitInfo = (ctx: UserCtx<SetInitInfoRequest, void>) => { export const setInitInfo = (
ctx: UserCtx<SetInitInfoRequest, SetInitInfoResponse>
) => {
const initInfo = ctx.request.body const initInfo = ctx.request.body
setCookie(ctx, initInfo, Cookie.Init) setCookie(ctx, initInfo, Cookie.Init)
ctx.status = 200 ctx.body = {
message: "Init info updated.",
}
} }
export const getInitInfo = (ctx: UserCtx<void, GetInitInfoResponse>) => { export const getInitInfo = (ctx: UserCtx<void, GetInitInfoResponse>) => {

View File

@ -2,10 +2,13 @@ import {
UserCtx, UserCtx,
PostEventPublishRequest, PostEventPublishRequest,
EventPublishType, EventPublishType,
PostEventPublishResponse,
} from "@budibase/types" } from "@budibase/types"
import { events } from "@budibase/backend-core" import { events } from "@budibase/backend-core"
export async function publish(ctx: UserCtx<PostEventPublishRequest, void>) { export async function publish(
ctx: UserCtx<PostEventPublishRequest, PostEventPublishResponse>
) {
switch (ctx.request.body.type) { switch (ctx.request.body.type) {
case EventPublishType.ENVIRONMENT_VARIABLE_UPGRADE_PANEL_OPENED: case EventPublishType.ENVIRONMENT_VARIABLE_UPGRADE_PANEL_OPENED:
await events.environmentVariable.upgradePanelOpened(ctx.user._id!) await events.environmentVariable.upgradePanelOpened(ctx.user._id!)
@ -13,5 +16,5 @@ export async function publish(ctx: UserCtx<PostEventPublishRequest, void>) {
default: default:
ctx.throw(400, "Invalid publish event type.") ctx.throw(400, "Invalid publish event type.")
} }
ctx.status = 200 ctx.body = { message: "Event published." }
} }

View File

@ -1,29 +1,33 @@
import { licensing, quotas } from "@budibase/pro" import { licensing, quotas } from "@budibase/pro"
import { import {
ActivateLicenseKeyRequest, ActivateLicenseKeyRequest,
ActivateLicenseKeyResponse,
ActivateOfflineLicenseTokenRequest, ActivateOfflineLicenseTokenRequest,
ActivateOfflineLicenseTokenResponse,
GetLicenseKeyResponse, GetLicenseKeyResponse,
GetOfflineIdentifierResponse, GetOfflineIdentifierResponse,
GetOfflineLicenseTokenResponse, GetOfflineLicenseTokenResponse,
GetQuotaUsageResponse, GetQuotaUsageResponse,
RefreshOfflineLicenseResponse,
UserCtx, UserCtx,
} from "@budibase/types" } from "@budibase/types"
// LICENSE KEY // LICENSE KEY
export async function activateLicenseKey( export async function activateLicenseKey(
ctx: UserCtx<ActivateLicenseKeyRequest> ctx: UserCtx<ActivateLicenseKeyRequest, ActivateLicenseKeyResponse>
) { ) {
const { licenseKey } = ctx.request.body const { licenseKey } = ctx.request.body
await licensing.keys.activateLicenseKey(licenseKey) await licensing.keys.activateLicenseKey(licenseKey)
ctx.status = 200 ctx.body = {
message: "License activated.",
}
} }
export async function getLicenseKey(ctx: UserCtx<void, GetLicenseKeyResponse>) { export async function getLicenseKey(ctx: UserCtx<void, GetLicenseKeyResponse>) {
const licenseKey = await licensing.keys.getLicenseKey() const licenseKey = await licensing.keys.getLicenseKey()
if (licenseKey) { if (licenseKey) {
ctx.body = { licenseKey: "*" } ctx.body = { licenseKey: "*" }
ctx.status = 200
} else { } else {
ctx.status = 404 ctx.status = 404
} }
@ -37,11 +41,16 @@ export async function deleteLicenseKey(ctx: UserCtx<void, void>) {
// OFFLINE LICENSE // OFFLINE LICENSE
export async function activateOfflineLicenseToken( export async function activateOfflineLicenseToken(
ctx: UserCtx<ActivateOfflineLicenseTokenRequest, void> ctx: UserCtx<
ActivateOfflineLicenseTokenRequest,
ActivateOfflineLicenseTokenResponse
>
) { ) {
const { offlineLicenseToken } = ctx.request.body const { offlineLicenseToken } = ctx.request.body
await licensing.offline.activateOfflineLicenseToken(offlineLicenseToken) await licensing.offline.activateOfflineLicenseToken(offlineLicenseToken)
ctx.status = 200 ctx.body = {
message: "License token activated.",
}
} }
export async function getOfflineLicenseToken( export async function getOfflineLicenseToken(
@ -50,7 +59,6 @@ export async function getOfflineLicenseToken(
const offlineLicenseToken = await licensing.offline.getOfflineLicenseToken() const offlineLicenseToken = await licensing.offline.getOfflineLicenseToken()
if (offlineLicenseToken) { if (offlineLicenseToken) {
ctx.body = { offlineLicenseToken: "*" } ctx.body = { offlineLicenseToken: "*" }
ctx.status = 200
} else { } else {
ctx.status = 404 ctx.status = 404
} }
@ -66,14 +74,17 @@ export async function getOfflineLicenseIdentifier(
) { ) {
const identifierBase64 = await licensing.offline.getIdentifierBase64() const identifierBase64 = await licensing.offline.getIdentifierBase64()
ctx.body = { identifierBase64 } ctx.body = { identifierBase64 }
ctx.status = 200
} }
// LICENSES // LICENSES
export const refresh = async (ctx: UserCtx<void, void>) => { export const refresh = async (
ctx: UserCtx<void, RefreshOfflineLicenseResponse>
) => {
await licensing.cache.refresh() await licensing.cache.refresh()
ctx.status = 200 ctx.body = {
message: "License refreshed.",
}
} }
// USAGE // USAGE
@ -82,5 +93,4 @@ export const getQuotaUsage = async (
ctx: UserCtx<void, GetQuotaUsageResponse> ctx: UserCtx<void, GetQuotaUsageResponse>
) => { ) => {
ctx.body = await quotas.getQuotaUsage() ctx.body = await quotas.getQuotaUsage()
ctx.status = 200
} }

View File

@ -4,6 +4,7 @@ import {
AcceptUserInviteRequest, AcceptUserInviteRequest,
AcceptUserInviteResponse, AcceptUserInviteResponse,
AddSSoUserRequest, AddSSoUserRequest,
AddSSoUserResponse,
BulkUserRequest, BulkUserRequest,
BulkUserResponse, BulkUserResponse,
CheckInviteResponse, CheckInviteResponse,
@ -93,7 +94,9 @@ export const save = async (ctx: UserCtx<User, SaveUserResponse>) => {
} }
} }
export const addSsoSupport = async (ctx: Ctx<AddSSoUserRequest, void>) => { export const addSsoSupport = async (
ctx: Ctx<AddSSoUserRequest, AddSSoUserResponse>
) => {
const { email, ssoId } = ctx.request.body const { email, ssoId } = ctx.request.body
try { try {
// Status is changed to 404 from getUserDoc if user is not found // Status is changed to 404 from getUserDoc if user is not found
@ -113,7 +116,7 @@ export const addSsoSupport = async (ctx: Ctx<AddSSoUserRequest, void>) => {
email, email,
ssoId, ssoId,
}) })
ctx.status = 200 ctx.body = { message: "SSO support added." }
} catch (err: any) { } catch (err: any) {
ctx.throw(err.status || 400, err) ctx.throw(err.status || 400, err)
} }

View File

@ -19,7 +19,6 @@ export const save = async (
metadata = await accounts.metadata.saveMetadata(metadata) metadata = await accounts.metadata.saveMetadata(metadata)
ctx.body = metadata ctx.body = metadata
ctx.status = 200
} }
export const destroy = async (ctx: Ctx<void, void>) => { export const destroy = async (ctx: Ctx<void, void>) => {

View File

@ -1,23 +1,23 @@
import { import {
FetchMigrationDefinitionsResponse, FetchMigrationDefinitionsResponse,
RunGlobalMigrationRequest, RunGlobalMigrationRequest,
RunGlobalMigrationResponse,
UserCtx, UserCtx,
} from "@budibase/types" } from "@budibase/types"
const { migrate, MIGRATIONS } = require("../../../migrations") const { migrate, MIGRATIONS } = require("../../../migrations")
export const runMigrations = async ( export const runMigrations = async (
ctx: UserCtx<RunGlobalMigrationRequest, void> ctx: UserCtx<RunGlobalMigrationRequest, RunGlobalMigrationResponse>
) => { ) => {
const options = ctx.request.body const options = ctx.request.body
// don't await as can take a while, just return // don't await as can take a while, just return
migrate(options) migrate(options)
ctx.status = 200 ctx.body = { message: "Migration started." }
} }
export const fetchDefinitions = async ( export const fetchDefinitions = async (
ctx: UserCtx<void, FetchMigrationDefinitionsResponse> ctx: UserCtx<void, FetchMigrationDefinitionsResponse>
) => { ) => {
ctx.body = MIGRATIONS ctx.body = MIGRATIONS
ctx.status = 200
} }

View File

@ -36,7 +36,7 @@ describe("/api/system/migrations", () => {
it("runs migrations", async () => { it("runs migrations", async () => {
const res = await config.api.migrations.runMigrations() const res = await config.api.migrations.runMigrations()
expect(res.text).toBe("OK") expect(res.body.message).toBeDefined()
expect(migrateFn).toHaveBeenCalledTimes(1) expect(migrateFn).toHaveBeenCalledTimes(1)
}) })
}) })