Merge pull request #15120 from Budibase/chore/api-typing-3

Final app service API typing
This commit is contained in:
Michael Drury 2024-12-05 14:23:39 +00:00 committed by GitHub
commit 63a5821877
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
47 changed files with 401 additions and 241 deletions

View File

@ -83,6 +83,7 @@
"@types/semver": "7.3.7",
"@types/tar-fs": "2.0.1",
"@types/uuid": "8.3.4",
"@types/koa": "2.13.4",
"chance": "1.1.8",
"ioredis-mock": "8.9.0",
"jest": "29.7.0",

View File

@ -1,6 +1,10 @@
import { BBContext } from "@budibase/types"
import { Ctx } from "@budibase/types"
import type { Middleware, Next } from "koa"
export default async (ctx: BBContext | any, next: any) => {
// this middleware exists purely to be overridden by middlewares supplied by the @budibase/pro library
const middleware = (async (ctx: Ctx, next: Next) => {
// Placeholder for audit log middleware
return next()
}
}) as Middleware
export default middleware

View File

@ -22,6 +22,7 @@ import {
} from "@budibase/types"
import { ErrorCode, InvalidAPIKeyError } from "../errors"
import tracer from "dd-trace"
import type { Middleware, Next } from "koa"
const ONE_MINUTE = env.SESSION_UPDATE_PERIOD
? parseInt(env.SESSION_UPDATE_PERIOD)
@ -94,6 +95,14 @@ async function checkApiKey(
})
}
function getHeader(ctx: Ctx, header: Header): string | undefined {
const contents = ctx.request.headers[header]
if (Array.isArray(contents)) {
throw new Error("Unexpected header format")
}
return contents
}
/**
* This middleware is tenancy aware, so that it does not depend on other middlewares being used.
* The tenancy modules should not be used here and it should be assumed that the tenancy context
@ -106,9 +115,9 @@ export default function (
}
) {
const noAuthOptions = noAuthPatterns ? buildMatcherRegex(noAuthPatterns) : []
return async (ctx: Ctx | any, next: any) => {
return (async (ctx: Ctx, next: Next) => {
let publicEndpoint = false
const version = ctx.request.headers[Header.API_VER]
const version = getHeader(ctx, Header.API_VER)
// the path is not authenticated
const found = matches(ctx, noAuthOptions)
if (found) {
@ -116,18 +125,18 @@ export default function (
}
try {
// check the actual user is authenticated first, try header or cookie
let headerToken = ctx.request.headers[Header.TOKEN]
let headerToken = getHeader(ctx, Header.TOKEN)
const authCookie =
getCookie<SessionCookie>(ctx, Cookie.Auth) ||
openJwt<SessionCookie>(headerToken)
let apiKey = ctx.request.headers[Header.API_KEY]
let apiKey = getHeader(ctx, Header.API_KEY)
if (!apiKey && ctx.request.headers[Header.AUTHORIZATION]) {
apiKey = ctx.request.headers[Header.AUTHORIZATION].split(" ")[1]
}
const tenantId = ctx.request.headers[Header.TENANT_ID]
const tenantId = getHeader(ctx, Header.TENANT_ID)
let authenticated: boolean = false,
user: User | { tenantId: string } | undefined = undefined,
internal: boolean = false,
@ -243,5 +252,5 @@ export default function (
ctx.throw(err.status || 403, err)
}
}
}
}) as Middleware
}

View File

@ -1,6 +1,7 @@
import { Header } from "../constants"
import { buildMatcherRegex, matches } from "./matchers"
import { BBContext, EndpointMatcher } from "@budibase/types"
import { Ctx, EndpointMatcher } from "@budibase/types"
import type { Middleware, Next } from "koa"
/**
* GET, HEAD and OPTIONS methods are considered safe operations
@ -36,7 +37,7 @@ export default function (
opts: { noCsrfPatterns: EndpointMatcher[] } = { noCsrfPatterns: [] }
) {
const noCsrfOptions = buildMatcherRegex(opts.noCsrfPatterns)
return async (ctx: BBContext | any, next: any) => {
return (async (ctx: Ctx, next: Next) => {
// don't apply for excluded paths
const found = matches(ctx, noCsrfOptions)
if (found) {
@ -77,5 +78,5 @@ export default function (
}
return next()
}
}) as Middleware
}

View File

@ -1,11 +1,11 @@
import { Header } from "../constants"
import { BBContext } from "@budibase/types"
import { Ctx } from "@budibase/types"
import { isValidInternalAPIKey } from "../utils"
/**
* API Key only endpoint.
*/
export default async (ctx: BBContext, next: any) => {
export default async (ctx: Ctx, next: any) => {
const apiKey = ctx.request.headers[Header.API_KEY]
if (!apiKey) {
ctx.throw(403, "Unauthorized")

View File

@ -1,4 +1,4 @@
import { BBContext, EndpointMatcher, RegexMatcher } from "@budibase/types"
import { Ctx, EndpointMatcher, RegexMatcher } from "@budibase/types"
const PARAM_REGEX = /\/:(.*?)(\/.*)?$/g
@ -27,7 +27,7 @@ export const buildMatcherRegex = (
})
}
export const matches = (ctx: BBContext, options: RegexMatcher[]) => {
export const matches = (ctx: Ctx, options: RegexMatcher[]) => {
return options.find(({ regex, method }) => {
const urlMatch = regex.test(ctx.request.url)
const methodMatch =

View File

@ -2,7 +2,7 @@ import { UserStatus } from "../../constants"
import { compare } from "../../utils"
import * as users from "../../users"
import { authError } from "./utils"
import { BBContext } from "@budibase/types"
import { Ctx } from "@budibase/types"
const INVALID_ERR = "Invalid credentials"
const EXPIRED = "This account has expired. Please reset your password"
@ -20,7 +20,7 @@ export const options = {
* @returns The authenticated user, or errors if they occur
*/
export async function authenticate(
ctx: BBContext,
ctx: Ctx,
email: string,
password: string,
done: Function

View File

@ -3,11 +3,12 @@ import { getTenantIDFromCtx } from "../tenancy"
import { buildMatcherRegex, matches } from "./matchers"
import { Header } from "../constants"
import {
BBContext,
Ctx,
EndpointMatcher,
GetTenantIdOptions,
TenantResolutionStrategy,
} from "@budibase/types"
import type { Next, Middleware } from "koa"
export default function (
allowQueryStringPatterns: EndpointMatcher[],
@ -17,7 +18,7 @@ export default function (
const allowQsOptions = buildMatcherRegex(allowQueryStringPatterns)
const noTenancyOptions = buildMatcherRegex(noTenancyPatterns)
return async function (ctx: BBContext | any, next: any) {
return async function (ctx: Ctx, next: Next) {
const allowNoTenant =
opts.noTenancyRequired || !!matches(ctx, noTenancyOptions)
const tenantOpts: GetTenantIdOptions = {
@ -32,5 +33,5 @@ export default function (
const tenantId = getTenantIDFromCtx(ctx, tenantOpts)
ctx.set(Header.TENANT_ID, tenantId as string)
return doInTenant(tenantId, next)
}
} as Middleware
}

View File

@ -592,7 +592,10 @@ export class AccessController {
)
}
async checkScreensAccess(screens: Screen[], userRoleId: string) {
async checkScreensAccess(
screens: Screen[],
userRoleId: string
): Promise<Screen[]> {
let accessibleScreens = []
// don't want to handle this with Promise.all as this would mean all custom roles would be
// retrieved at same time, it is likely a custom role will be re-used and therefore want

View File

@ -6,7 +6,7 @@ import {
getPlatformURL,
} from "../context"
import {
BBContext,
Ctx,
TenantResolutionStrategy,
GetTenantIdOptions,
} from "@budibase/types"
@ -37,7 +37,7 @@ export const isUserInAppTenant = (appId: string, user?: any) => {
const ALL_STRATEGIES = Object.values(TenantResolutionStrategy)
export const getTenantIDFromCtx = (
ctx: BBContext,
ctx: Ctx,
opts: GetTenantIdOptions
): string | undefined => {
// exit early if not multi-tenant

View File

@ -5,7 +5,7 @@ import * as db from "../../db"
import { Header } from "../../constants"
import { newid } from "../../utils"
import env from "../../environment"
import { BBContext } from "@budibase/types"
import { Ctx } from "@budibase/types"
describe("utils", () => {
const config = new DBTestConfiguration()
@ -109,7 +109,7 @@ describe("utils", () => {
})
describe("isServingBuilder", () => {
let ctx: BBContext
let ctx: Ctx
const expectResult = (result: boolean) =>
expect(utils.isServingBuilder(ctx)).toBe(result)
@ -133,7 +133,7 @@ describe("utils", () => {
})
describe("isServingBuilderPreview", () => {
let ctx: BBContext
let ctx: Ctx
const expectResult = (result: boolean) =>
expect(utils.isServingBuilderPreview(ctx)).toBe(result)
@ -157,7 +157,7 @@ describe("utils", () => {
})
describe("isPublicAPIRequest", () => {
let ctx: BBContext
let ctx: Ctx
const expectResult = (result: boolean) =>
expect(utils.isPublicApiRequest(ctx)).toBe(result)

View File

@ -1,8 +1,8 @@
import { createMockContext, createMockCookies } from "@shopify/jest-koa-mocks"
import { BBContext } from "@budibase/types"
import { Ctx } from "@budibase/types"
export const newContext = (): BBContext => {
const ctx = createMockContext() as any
export const newContext = (): Ctx => {
const ctx = createMockContext() as Ctx
return {
...ctx,
path: "/",

View File

@ -10,13 +10,16 @@ import { updateAppPackage } from "./application"
import {
Plugin,
ScreenProps,
BBContext,
Screen,
UserCtx,
FetchScreenResponse,
SaveScreenRequest,
SaveScreenResponse,
DeleteScreenResponse,
} from "@budibase/types"
import { builderSocket } from "../../websockets"
export async function fetch(ctx: BBContext) {
export async function fetch(ctx: UserCtx<void, FetchScreenResponse>) {
const db = context.getAppDB()
const screens = (
@ -37,7 +40,9 @@ export async function fetch(ctx: BBContext) {
)
}
export async function save(ctx: UserCtx<Screen, Screen>) {
export async function save(
ctx: UserCtx<SaveScreenRequest, SaveScreenResponse>
) {
const db = context.getAppDB()
let screen = ctx.request.body
@ -107,7 +112,7 @@ export async function save(ctx: UserCtx<Screen, Screen>) {
builderSocket?.emitScreenUpdate(ctx, savedScreen)
}
export async function destroy(ctx: BBContext) {
export async function destroy(ctx: UserCtx<void, DeleteScreenResponse>) {
const db = context.getAppDB()
const id = ctx.params.screenId
const screen = await db.get<Screen>(id)

View File

@ -14,7 +14,3 @@ export async function execute(ctx: Ctx) {
throw err
}
}
export async function save(ctx: Ctx) {
ctx.throw(501, "Not currently implemented")
}

View File

@ -27,7 +27,13 @@ import {
Ctx,
DocumentType,
Feature,
GetSignedUploadUrlRequest,
GetSignedUploadUrlResponse,
ProcessAttachmentResponse,
ServeAppResponse,
ServeBuilderPreviewResponse,
ServeClientLibraryResponse,
ToggleBetaFeatureResponse,
UserCtx,
} from "@budibase/types"
import {
@ -38,7 +44,9 @@ import {
import send from "koa-send"
import { getThemeVariables } from "../../../constants/themes"
export const toggleBetaUiFeature = async function (ctx: Ctx) {
export const toggleBetaUiFeature = async function (
ctx: Ctx<void, ToggleBetaFeatureResponse>
) {
const cookieName = `beta:${ctx.params.feature}`
if (ctx.cookies.get(cookieName)) {
@ -66,13 +74,13 @@ export const toggleBetaUiFeature = async function (ctx: Ctx) {
}
}
export const serveBuilder = async function (ctx: Ctx) {
export const serveBuilder = async function (ctx: Ctx<void, void>) {
const builderPath = join(TOP_LEVEL_PATH, "builder")
await send(ctx, ctx.file, { root: builderPath })
}
export const uploadFile = async function (
ctx: Ctx<{}, ProcessAttachmentResponse>
ctx: Ctx<void, ProcessAttachmentResponse>
) {
const file = ctx.request?.files?.file
if (!file) {
@ -144,7 +152,7 @@ const requiresMigration = async (ctx: Ctx) => {
return latestMigrationApplied !== latestMigration
}
export const serveApp = async function (ctx: UserCtx) {
export const serveApp = async function (ctx: UserCtx<void, ServeAppResponse>) {
if (ctx.url.includes("apple-touch-icon.png")) {
ctx.redirect("/builder/bblogo.png")
return
@ -249,7 +257,9 @@ export const serveApp = async function (ctx: UserCtx) {
}
}
export const serveBuilderPreview = async function (ctx: Ctx) {
export const serveBuilderPreview = async function (
ctx: Ctx<void, ServeBuilderPreviewResponse>
) {
const db = context.getAppDB({ skip_setup: true })
const appInfo = await db.get<App>(DocumentType.APP_METADATA)
@ -268,7 +278,9 @@ export const serveBuilderPreview = async function (ctx: Ctx) {
}
}
export const serveClientLibrary = async function (ctx: Ctx) {
export const serveClientLibrary = async function (
ctx: Ctx<void, ServeClientLibraryResponse>
) {
const version = ctx.request.query.version
if (Array.isArray(version)) {
@ -297,7 +309,9 @@ export const serveClientLibrary = async function (ctx: Ctx) {
}
}
export const getSignedUploadURL = async function (ctx: Ctx) {
export const getSignedUploadURL = async function (
ctx: Ctx<GetSignedUploadUrlRequest, GetSignedUploadUrlResponse>
) {
// Ensure datasource is valid
let datasource
try {

View File

@ -19,17 +19,18 @@ import {
EventType,
FetchTablesResponse,
FieldType,
MigrateRequest,
MigrateResponse,
MigrateTableRequest,
MigrateTableResponse,
SaveTableRequest,
SaveTableResponse,
Table,
TableResponse,
FindTableResponse,
TableSourceType,
UserCtx,
ValidateNewTableImportRequest,
ValidateTableImportRequest,
ValidateTableImportResponse,
DeleteTableResponse,
} from "@budibase/types"
import sdk from "../../../sdk"
import { jsonFromCsvString } from "../../../utilities/csv"
@ -94,7 +95,7 @@ export async function fetch(ctx: UserCtx<void, FetchTablesResponse>) {
ctx.body = result
}
export async function find(ctx: UserCtx<void, TableResponse>) {
export async function find(ctx: UserCtx<void, FindTableResponse>) {
const tableId = ctx.params.tableId
const table = await sdk.tables.getTable(tableId)
@ -137,7 +138,7 @@ export async function save(ctx: UserCtx<SaveTableRequest, SaveTableResponse>) {
builderSocket?.emitTableUpdate(ctx, cloneDeep(savedTable))
}
export async function destroy(ctx: UserCtx) {
export async function destroy(ctx: UserCtx<void, DeleteTableResponse>) {
const appId = ctx.appId
const tableId = ctx.params.tableId
await sdk.rowActions.deleteAll(tableId)
@ -223,7 +224,9 @@ export async function validateExistingTableImport(
}
}
export async function migrate(ctx: UserCtx<MigrateRequest, MigrateResponse>) {
export async function migrate(
ctx: UserCtx<MigrateTableRequest, MigrateTableResponse>
) {
const { oldColumn, newColumn } = ctx.request.body
let tableId = ctx.params.tableId as string
const table = await sdk.tables.getTable(tableId)

View File

@ -1,13 +1,17 @@
import nodeFetch from "node-fetch"
import { downloadTemplate as dlTemplate } from "../../utilities/fileSystem"
import env from "../../environment"
import { BBContext } from "@budibase/types"
import {
DownloadTemplateResponse,
FetchTemplateResponse,
UserCtx,
} from "@budibase/types"
// development flag, can be used to test against templates exported locally
const DEFAULT_TEMPLATES_BUCKET =
"prod-budi-templates.s3-eu-west-1.amazonaws.com"
export async function fetch(ctx: BBContext) {
export async function fetch(ctx: UserCtx<void, FetchTemplateResponse>) {
let type = env.TEMPLATE_REPOSITORY
let response,
error = false
@ -32,7 +36,9 @@ export async function fetch(ctx: BBContext) {
// can't currently test this, have to ignore from coverage
/* istanbul ignore next */
export async function downloadTemplate(ctx: BBContext) {
export async function downloadTemplate(
ctx: UserCtx<void, DownloadTemplateResponse>
) {
const { type, name } = ctx.params
await dlTemplate(type, name)

View File

@ -7,19 +7,24 @@ import {
FetchUserMetadataResponse,
FindUserMetadataResponse,
Flags,
SetFlagRequest,
SetUserFlagRequest,
UpdateSelfMetadataRequest,
UpdateSelfMetadataResponse,
UpdateUserMetadataResponse,
UpdateUserMetadataRequest,
UserCtx,
UserMetadata,
DeleteUserMetadataResponse,
SetUserFlagResponse,
GetUserFlagsResponse,
} from "@budibase/types"
import sdk from "../../sdk"
import { DocumentInsertResponse } from "@budibase/nano"
export async function fetchMetadata(ctx: Ctx<void, FetchUserMetadataResponse>) {
ctx.body = await sdk.users.fetchMetadata()
}
export async function updateSelfMetadata(
ctx: UserCtx<UserMetadata, DocumentInsertResponse>
ctx: UserCtx<UpdateSelfMetadataRequest, UpdateSelfMetadataResponse>
) {
// overwrite the ID with current users
ctx.request.body._id = ctx.user?._id
@ -31,7 +36,7 @@ export async function updateSelfMetadata(
}
export async function updateMetadata(
ctx: UserCtx<UserMetadata, DocumentInsertResponse>
ctx: UserCtx<UpdateUserMetadataRequest, UpdateUserMetadataResponse>
) {
const db = context.getAppDB()
const user = ctx.request.body
@ -44,7 +49,9 @@ export async function updateMetadata(
ctx.body = await db.put(metadata)
}
export async function destroyMetadata(ctx: UserCtx<void, { message: string }>) {
export async function destroyMetadata(
ctx: UserCtx<void, DeleteUserMetadataResponse>
) {
const db = context.getAppDB()
try {
const dbUser = await sdk.users.get(ctx.params.id)
@ -64,7 +71,7 @@ export async function findMetadata(
}
export async function setFlag(
ctx: UserCtx<SetFlagRequest, { message: string }>
ctx: UserCtx<SetUserFlagRequest, SetUserFlagResponse>
) {
const userId = ctx.user?._id
const { flag, value } = ctx.request.body
@ -84,7 +91,7 @@ export async function setFlag(
ctx.body = { message: "Flag set successfully" }
}
export async function getFlags(ctx: UserCtx<void, Flags>) {
export async function getFlags(ctx: UserCtx<void, GetUserFlagsResponse>) {
const userId = ctx.user?._id
const docId = generateUserFlagID(userId!)
const db = context.getAppDB()

View File

@ -4,7 +4,6 @@ import {
Ctx,
RequiredKeys,
UpdateViewRequest,
ViewResponse,
ViewResponseEnriched,
ViewV2,
BasicViewFieldMetadata,
@ -15,6 +14,8 @@ import {
ViewFetchResponseEnriched,
CountDistinctCalculationFieldMetadata,
CountCalculationFieldMetadata,
CreateViewResponse,
UpdateViewResponse,
} from "@budibase/types"
import { builderSocket, gridSocket } from "../../../websockets"
import { helpers } from "@budibase/shared-core"
@ -132,7 +133,7 @@ export async function fetch(ctx: Ctx<void, ViewFetchResponseEnriched>) {
}
}
export async function create(ctx: Ctx<CreateViewRequest, ViewResponse>) {
export async function create(ctx: Ctx<CreateViewRequest, CreateViewResponse>) {
const view = ctx.request.body
const { tableId } = view
@ -159,7 +160,7 @@ export async function create(ctx: Ctx<CreateViewRequest, ViewResponse>) {
gridSocket?.emitViewUpdate(ctx, result)
}
export async function update(ctx: Ctx<UpdateViewRequest, ViewResponse>) {
export async function update(ctx: Ctx<UpdateViewRequest, UpdateViewResponse>) {
const view = ctx.request.body
if (view.version !== 2) {
@ -196,7 +197,7 @@ export async function update(ctx: Ctx<UpdateViewRequest, ViewResponse>) {
gridSocket?.emitViewUpdate(ctx, result)
}
export async function remove(ctx: Ctx) {
export async function remove(ctx: Ctx<void, void>) {
const { viewId } = ctx.params
const view = await sdk.views.remove(viewId)

View File

@ -4,9 +4,17 @@ import { db as dbCore, context } from "@budibase/backend-core"
import {
Webhook,
WebhookActionType,
BBContext,
Ctx,
Automation,
AutomationActionStepId,
FetchWebhooksResponse,
SaveWebhookResponse,
SaveWebhookRequest,
DeleteWebhookResponse,
BuildWebhookSchemaRequest,
BuildWebhookSchemaResponse,
TriggerWebhookRequest,
TriggerWebhookResponse,
} from "@budibase/types"
import sdk from "../../sdk"
import * as pro from "@budibase/pro"
@ -16,17 +24,17 @@ const validate = require("jsonschema").validate
const AUTOMATION_DESCRIPTION = "Generated from Webhook Schema"
export async function fetch(ctx: BBContext) {
export async function fetch(ctx: Ctx<void, FetchWebhooksResponse>) {
const db = context.getAppDB()
const response = await db.allDocs(
const response = await db.allDocs<Webhook>(
getWebhookParams(null, {
include_docs: true,
})
)
ctx.body = response.rows.map((row: any) => row.doc)
ctx.body = response.rows.filter(row => row.doc).map(row => row.doc!)
}
export async function save(ctx: BBContext) {
export async function save(ctx: Ctx<SaveWebhookRequest, SaveWebhookResponse>) {
const webhook = await sdk.automations.webhook.save(ctx.request.body)
ctx.body = {
message: "Webhook created successfully",
@ -34,21 +42,23 @@ export async function save(ctx: BBContext) {
}
}
export async function destroy(ctx: BBContext) {
export async function destroy(ctx: Ctx<void, DeleteWebhookResponse>) {
ctx.body = await sdk.automations.webhook.destroy(
ctx.params.id,
ctx.params.rev
)
}
export async function buildSchema(ctx: BBContext) {
export async function buildSchema(
ctx: Ctx<BuildWebhookSchemaRequest, BuildWebhookSchemaResponse>
) {
await context.doInAppContext(ctx.params.instance, async () => {
const db = context.getAppDB()
const webhook = (await db.get(ctx.params.id)) as Webhook
const webhook = await db.get<Webhook>(ctx.params.id)
webhook.bodySchema = toJsonSchema(ctx.request.body)
// update the automation outputs
if (webhook.action.type === WebhookActionType.AUTOMATION) {
let automation = (await db.get(webhook.action.target)) as Automation
let automation = await db.get<Automation>(webhook.action.target)
const autoOutputs = automation.definition.trigger.schema.outputs
let properties = webhook.bodySchema.properties
// reset webhook outputs
@ -67,17 +77,29 @@ export async function buildSchema(ctx: BBContext) {
})
}
export async function trigger(ctx: BBContext) {
export async function trigger(
ctx: Ctx<TriggerWebhookRequest, TriggerWebhookResponse>
) {
const prodAppId = dbCore.getProdAppID(ctx.params.instance)
const appNotDeployed = () => {
ctx.body = {
message: "Application not deployed yet.",
}
}
await context.doInAppContext(prodAppId, async () => {
try {
const db = context.getAppDB()
const webhook = (await db.get(ctx.params.id)) as Webhook
const webhook = await db.tryGet<Webhook>(ctx.params.id)
if (!webhook) {
return appNotDeployed()
}
// validate against the schema
if (webhook.bodySchema) {
validate(ctx.request.body, webhook.bodySchema)
}
const target = await db.get<Automation>(webhook.action.target)
const target = await db.tryGet<Automation>(webhook.action.target)
if (!target) {
return appNotDeployed()
}
if (webhook.action.type === WebhookActionType.AUTOMATION) {
// trigger with both the pure request and then expand it
// incase the user has produced a schema to bind to
@ -87,8 +109,10 @@ export async function trigger(ctx: BBContext) {
const response = await triggers.externalTrigger(
target,
{
body: ctx.request.body,
fields: {
...ctx.request.body,
body: ctx.request.body,
},
appId: prodAppId,
},
{ getResponses: true }
@ -99,30 +123,22 @@ export async function trigger(ctx: BBContext) {
(step: any) => step.stepId === AutomationActionStepId.COLLECT
)
ctx.status = 200
ctx.body = collectedValue?.outputs
} else {
ctx.throw(400, "Automation did not have a collect block.")
}
} else {
await triggers.externalTrigger(target, {
body: ctx.request.body,
fields: {
...ctx.request.body,
body: ctx.request.body,
},
appId: prodAppId,
})
ctx.status = 200
ctx.body = {
message: "Webhook trigger fired successfully",
}
}
}
} catch (err: any) {
if (err.status === 404) {
ctx.status = 200
ctx.body = {
message: "Application not deployed yet.",
}
}
}
})
}

View File

@ -58,12 +58,9 @@ if (apiEnabled()) {
})
)
.use(pro.licensing())
// @ts-ignore
.use(currentApp)
.use(auth.auditLog)
// @ts-ignore
.use(migrations)
// @ts-ignore
.use(cleanup)
// authenticated routes

View File

@ -1,10 +0,0 @@
import Router from "@koa/router"
import * as controller from "../controllers/script"
import authorized from "../../middleware/authorized"
import { permissions } from "@budibase/backend-core"
const router: Router = new Router()
router.post("/api/script", authorized(permissions.BUILDER), controller.save)
export default router

View File

@ -1,5 +1,5 @@
import { isDevAppID, isProdAppID } from "../db/utils"
import { BBContext } from "@budibase/types"
import { Ctx } from "@budibase/types"
export enum AppType {
DEV = "dev",
@ -7,7 +7,7 @@ export enum AppType {
}
export function middleware({ appType }: { appType?: AppType } = {}) {
return (ctx: BBContext, next: any) => {
return (ctx: Ctx, next: any) => {
const appId = ctx.appId
if (appType === AppType.DEV && appId && !isDevAppID(appId)) {
ctx.throw(400, "Only apps in development support this endpoint")

View File

@ -1,8 +1,9 @@
import { UserCtx } from "@budibase/types"
import { checkMissingMigrations } from "../appMigrations"
import env from "../environment"
import type { Middleware, Next } from "koa"
export default async (ctx: UserCtx, next: any) => {
const middleware = (async (ctx: UserCtx, next: Next) => {
const { appId } = ctx
// migrations can be disabled via environment variable if you
@ -16,4 +17,6 @@ export default async (ctx: UserCtx, next: any) => {
}
return checkMissingMigrations(ctx, next, appId)
}
}) as Middleware
export default middleware

View File

@ -1,8 +1,9 @@
import { Ctx } from "@budibase/types"
import { context } from "@budibase/backend-core"
import { tracer } from "dd-trace"
import type { Middleware, Next } from "koa"
export default async (ctx: Ctx, next: any) => {
const middleware = (async (ctx: Ctx, next: Next) => {
const resp = await next()
const current = context.getCurrentContext()
@ -30,4 +31,6 @@ export default async (ctx: Ctx, next: any) => {
}
return resp
}
}) as Middleware
export default middleware

View File

@ -13,8 +13,9 @@ import env from "../environment"
import { isWebhookEndpoint, isBrowser, isApiKey } from "./utils"
import { UserCtx, ContextUser } from "@budibase/types"
import tracer from "dd-trace"
import type { Middleware, Next } from "koa"
export default async (ctx: UserCtx, next: any) => {
const middleware = (async (ctx: UserCtx, next: Next) => {
// try to get the appID from the request
let requestAppId = await utils.getAppIdFromCtx(ctx)
if (!requestAppId) {
@ -116,4 +117,6 @@ export default async (ctx: UserCtx, next: any) => {
return next()
})
}
}) as Middleware
export default middleware

View File

@ -1,9 +1,9 @@
import Joi from "joi"
import { BBContext } from "@budibase/types"
import { Ctx } from "@budibase/types"
function validate(schema: Joi.Schema, property: string) {
// Return a Koa middleware function
return (ctx: BBContext, next: any) => {
return (ctx: Ctx, next: any) => {
if (!schema) {
return next()
}

View File

@ -1,4 +1,4 @@
import { BBContext } from "@budibase/types"
import { Ctx } from "@budibase/types"
export class ResourceIdGetter {
parameter: string
@ -26,7 +26,7 @@ export class ResourceIdGetter {
const parameter = this.parameter,
main = this.main,
sub = this.sub
return (ctx: BBContext, next: any) => {
return (ctx: Ctx, next: any) => {
// @ts-ignore
const request = ctx.request[parameter] || ctx[parameter]
if (request == null) {

View File

@ -1,9 +1,9 @@
import env from "../environment"
import { BBContext } from "@budibase/types"
import { Ctx } from "@budibase/types"
// if added as a middleware will stop requests unless builder is in self host mode
// or cloud is in self host
export default async (ctx: BBContext, next: any) => {
export default async (ctx: Ctx, next: any) => {
if (env.SELF_HOSTED) {
await next()
return

View File

@ -9,7 +9,7 @@ import {
Database,
INTERNAL_TABLE_SOURCE_ID,
Table,
TableResponse,
FindTableResponse,
TableSourceType,
TableViewsResponse,
} from "@budibase/types"
@ -173,7 +173,9 @@ export async function getTables(tableIds: string[]): Promise<Table[]> {
return await processTables(tables)
}
export async function enrichViewSchemas(table: Table): Promise<TableResponse> {
export async function enrichViewSchemas(
table: Table
): Promise<FindTableResponse> {
const views = []
for (const view of Object.values(table.views ?? [])) {
if (sdk.views.isV2(view)) {

View File

@ -3,8 +3,8 @@ import {
BulkImportResponse,
CsvToJsonRequest,
CsvToJsonResponse,
MigrateRequest,
MigrateResponse,
MigrateTableRequest,
MigrateTableResponse,
SaveTableRequest,
SaveTableResponse,
Table,
@ -38,13 +38,16 @@ export class TableAPI extends TestAPI {
migrate = async (
tableId: string,
data: MigrateRequest,
data: MigrateTableRequest,
expectations?: Expectations
): Promise<MigrateResponse> => {
return await this._post<MigrateResponse>(`/api/tables/${tableId}/migrate`, {
): Promise<MigrateTableResponse> => {
return await this._post<MigrateTableResponse>(
`/api/tables/${tableId}/migrate`,
{
body: data,
expectations,
})
}
)
}
import = async (

View File

@ -1,5 +1,5 @@
import type { PlanType } from "../../sdk"
import type { Layout, App, Screen } from "../../documents"
import type { PlanType } from "../../../sdk"
import type { Layout, App, Screen } from "../../../documents"
import { ReadStream } from "fs"
export interface SyncAppResponse {

View File

@ -1,9 +1,14 @@
import {
Automation,
AutomationActionStepId,
AutomationLogPage,
AutomationStatus,
AutomationStepDefinition,
AutomationTriggerDefinition,
AutomationTriggerStepId,
Row,
} from "../../../documents"
import { DocumentDestroyResponse } from "@budibase/nano"
export type GetAutomationTriggerDefinitionsResponse = Record<
keyof typeof AutomationTriggerStepId,
@ -19,3 +24,54 @@ export interface GetAutomationStepDefinitionsResponse {
trigger: GetAutomationTriggerDefinitionsResponse
action: GetAutomationActionDefinitionsResponse
}
export interface DeleteAutomationResponse extends DocumentDestroyResponse {}
export interface FetchAutomationResponse {
automations: Automation[]
}
export interface FindAutomationResponse extends Automation {}
export interface UpdateAutomationRequest extends Automation {}
export interface UpdateAutomationResponse {
message: string
automation: Automation
}
export interface CreateAutomationRequest extends Automation {}
export interface CreateAutomationResponse {
message: string
automation: Automation
}
export interface SearchAutomationLogsRequest {
startDate?: string
status?: AutomationStatus
automationId?: string
page?: string
}
export interface SearchAutomationLogsResponse extends AutomationLogPage {}
export interface ClearAutomationLogRequest {
automationId: string
appId: string
}
export interface ClearAutomationLogResponse {
message: string
}
export interface TriggerAutomationRequest {
fields: Record<string, any>
// time in seconds
timeout: number
}
export type TriggerAutomationResponse = Record<string, any> | undefined
export interface TestAutomationRequest {
id?: string
revision?: string
fields: Record<string, any>
row?: Row
}
export interface TestAutomationResponse {}

View File

@ -1,4 +1,4 @@
import { DeploymentDoc, DeploymentStatus } from "../../documents"
import { DeploymentDoc, DeploymentStatus } from "../../../documents"
export interface PublishAppResponse extends DeploymentDoc {}

View File

@ -13,3 +13,9 @@ export * from "./integration"
export * from "./metadata"
export * from "./query"
export * from "./screen"
export * from "./application"
export * from "./layout"
export * from "./deployment"
export * from "./role"
export * from "./webhook"
export * from "./static"

View File

@ -1,4 +1,4 @@
import { Layout } from "../../documents"
import { Layout } from "../../../documents"
export interface SaveLayoutRequest extends Layout {}

View File

@ -1,5 +1,5 @@
import { Role, RoleUIMetadata } from "../../documents"
import { PermissionLevel, BuiltinPermissionID } from "../../sdk"
import { Role, RoleUIMetadata } from "../../../documents"
import { PermissionLevel, BuiltinPermissionID } from "../../../sdk"
export interface SaveRoleRequest {
_id?: string

View File

@ -1,4 +1,4 @@
import { ScreenRoutingJson } from "../../../documents"
import { ScreenRoutingJson, Screen } from "../../../documents"
export interface FetchScreenRoutingResponse {
routes: ScreenRoutingJson
@ -6,3 +6,12 @@ export interface FetchScreenRoutingResponse {
export interface FetchClientScreenRoutingResponse
extends FetchScreenRoutingResponse {}
export type FetchScreenResponse = Screen[]
export interface SaveScreenRequest extends Screen {}
export interface SaveScreenResponse extends Screen {}
export interface DeleteScreenResponse {
message: string
}

View File

@ -0,0 +1,25 @@
import { App } from "../../../documents"
import stream from "node:stream"
export interface ToggleBetaFeatureResponse {
message: string
}
export type ServeAppResponse = string
interface BuilderPreview extends App {
builderPreview: boolean
}
export type ServeBuilderPreviewResponse = BuilderPreview | string
export type ServeClientLibraryResponse = stream.Readable
export interface GetSignedUploadUrlRequest {
bucket: string
key: string
}
export interface GetSignedUploadUrlResponse {
signedUrl?: string
publicUrl?: string
}

View File

@ -3,33 +3,30 @@ import { ViewV2Enriched } from "../../../sdk"
export type TableViewsResponse = { [key: string]: View | ViewV2Enriched }
export interface TableResponse extends Table {
export interface FindTableResponse extends Table {
views?: TableViewsResponse
}
export type FetchTablesResponse = TableResponse[]
export type FetchTablesResponse = FindTableResponse[]
export interface SaveTableRequest extends TableRequest {
rows?: Row[]
}
export type SaveTableResponse = Table
export interface BulkImportRequest {
rows: Row[]
identifierFields?: Array<string>
}
export interface BulkImportResponse {
message: string
}
export interface MigrateRequest {
export interface MigrateTableRequest {
oldColumn: string
newColumn: string
}
export interface MigrateResponse {
export interface MigrateTableResponse {
message: string
}
@ -37,12 +34,10 @@ export interface ValidateNewTableImportRequest {
rows: Row[]
schema: TableSchema
}
export interface ValidateTableImportRequest {
tableId?: string
rows: Row[]
}
export interface ValidateTableImportResponse {
schemaValidation: {
[field: string]: boolean
@ -55,5 +50,8 @@ export interface ValidateTableImportResponse {
export interface CsvToJsonRequest {
csvString: string
}
export type CsvToJsonResponse = any[]
export interface DeleteTableResponse {
message: string
}

View File

@ -1,11 +1,27 @@
import { ContextUserMetadata } from "../../../"
import { ContextUserMetadata, Flags, UserMetadata } from "../../../"
import { DocumentInsertResponse } from "@budibase/nano"
export type FetchUserMetadataResponse = ContextUserMetadata[]
export type FindUserMetadataResponse = ContextUserMetadata
export interface SetFlagRequest {
export interface SetUserFlagRequest {
flag: string
value: any
}
export interface SetUserFlagResponse {
message: string
}
export interface GetUserFlagsResponse extends Flags {}
export type AppSelfResponse = ContextUserMetadata | {}
export interface UpdateSelfMetadataRequest extends UserMetadata {}
export interface UpdateSelfMetadataResponse extends DocumentInsertResponse {}
export interface UpdateUserMetadataRequest extends UserMetadata {}
export interface UpdateUserMetadataResponse extends DocumentInsertResponse {}
export interface DeleteUserMetadataResponse {
message: string
}

View File

@ -14,5 +14,7 @@ export interface ViewFetchResponseEnriched {
}
export interface CreateViewRequest extends Omit<ViewV2, "version" | "id"> {}
export interface CreateViewResponse extends ViewResponse {}
export interface UpdateViewRequest extends ViewV2 {}
export interface UpdateViewResponse extends ViewResponse {}

View File

@ -0,0 +1,21 @@
import { Webhook } from "../../../documents"
import { DocumentDestroyResponse, DocumentInsertResponse } from "@budibase/nano"
export type FetchWebhooksResponse = Webhook[]
export interface SaveWebhookRequest extends Webhook {}
export interface SaveWebhookResponse {
message: string
webhook: Webhook
}
export interface DeleteWebhookResponse extends DocumentDestroyResponse {}
export interface BuildWebhookSchemaRequest extends Record<string, any> {}
export interface BuildWebhookSchemaResponse extends DocumentInsertResponse {}
export interface TriggerWebhookRequest {}
export type TriggerWebhookResponse =
| Record<string, any>
| { message: string }
| undefined

View File

@ -1,58 +0,0 @@
import { DocumentDestroyResponse } from "@budibase/nano"
import {
Automation,
AutomationLogPage,
AutomationStatus,
Row,
} from "../../documents"
export interface DeleteAutomationResponse extends DocumentDestroyResponse {}
export interface FetchAutomationResponse {
automations: Automation[]
}
export interface FindAutomationResponse extends Automation {}
export interface UpdateAutomationRequest extends Automation {}
export interface UpdateAutomationResponse {
message: string
automation: Automation
}
export interface CreateAutomationRequest extends Automation {}
export interface CreateAutomationResponse {
message: string
automation: Automation
}
export interface SearchAutomationLogsRequest {
startDate?: string
status?: AutomationStatus
automationId?: string
page?: string
}
export interface SearchAutomationLogsResponse extends AutomationLogPage {}
export interface ClearAutomationLogRequest {
automationId: string
appId: string
}
export interface ClearAutomationLogResponse {
message: string
}
export interface TriggerAutomationRequest {
fields: Record<string, any>
// time in seconds
timeout: number
}
export type TriggerAutomationResponse = Record<string, any> | undefined
export interface TestAutomationRequest {
id?: string
revision?: string
fields: Record<string, any>
row?: Row
}
export interface TestAutomationResponse {}

View File

@ -1,4 +1,3 @@
export * from "./application"
export * from "./analytics"
export * from "./auth"
export * from "./user"
@ -11,10 +10,7 @@ export * from "./global"
export * from "./pagination"
export * from "./searchFilter"
export * from "./cookies"
export * from "./automation"
export * from "./layout"
export * from "./role"
export * from "./plugins"
export * from "./apikeys"
export * from "./deployment"
export * from "./dev"
export * from "./template"

View File

@ -0,0 +1,21 @@
export enum TemplateType {
APP = "app",
}
export interface TemplateMetadata {
background: string
icon: string
category: string
description: string
name: string
url: string
type: TemplateType
key: string
image: string
}
export type FetchTemplateResponse = TemplateMetadata[]
export interface DownloadTemplateResponse {
message: string
}

View File

@ -1,4 +1,4 @@
import { BBContext } from "./koa"
import { Ctx } from "./koa"
import { Hosting } from "./hosting"
export interface AuthToken {
@ -32,7 +32,7 @@ export interface ScannedSession {
}
export interface PlatformLogoutOpts {
ctx: BBContext
ctx: Ctx
userId: string
keepActiveSession?: boolean
}