Merge branch 'master' of github.com:budibase/budibase into budi-8882-ms-sql-export-schema-feature-creates-and-downloads

This commit is contained in:
Sam Rose 2024-12-05 10:47:17 +00:00
commit 0e6b3c258a
No known key found for this signature in database
49 changed files with 514 additions and 231 deletions

View File

@ -2,6 +2,8 @@ import {
PermissionLevel, PermissionLevel,
PermissionType, PermissionType,
BuiltinPermissionID, BuiltinPermissionID,
Permission,
BuiltinPermissions,
} from "@budibase/types" } from "@budibase/types"
import flatten from "lodash/flatten" import flatten from "lodash/flatten"
import cloneDeep from "lodash/fp/cloneDeep" import cloneDeep from "lodash/fp/cloneDeep"
@ -12,7 +14,7 @@ export type RoleHierarchy = {
permissionId: string permissionId: string
}[] }[]
export class Permission { export class PermissionImpl implements Permission {
type: PermissionType type: PermissionType
level: PermissionLevel level: PermissionLevel
@ -61,68 +63,62 @@ export function getAllowedLevels(userPermLevel: PermissionLevel): string[] {
} }
} }
export const BUILTIN_PERMISSIONS: { export const BUILTIN_PERMISSIONS: BuiltinPermissions = {
[key in keyof typeof BuiltinPermissionID]: {
_id: (typeof BuiltinPermissionID)[key]
name: string
permissions: Permission[]
}
} = {
PUBLIC: { PUBLIC: {
_id: BuiltinPermissionID.PUBLIC, _id: BuiltinPermissionID.PUBLIC,
name: "Public", name: "Public",
permissions: [ permissions: [
new Permission(PermissionType.WEBHOOK, PermissionLevel.EXECUTE), new PermissionImpl(PermissionType.WEBHOOK, PermissionLevel.EXECUTE),
], ],
}, },
READ_ONLY: { READ_ONLY: {
_id: BuiltinPermissionID.READ_ONLY, _id: BuiltinPermissionID.READ_ONLY,
name: "Read only", name: "Read only",
permissions: [ permissions: [
new Permission(PermissionType.QUERY, PermissionLevel.READ), new PermissionImpl(PermissionType.QUERY, PermissionLevel.READ),
new Permission(PermissionType.TABLE, PermissionLevel.READ), new PermissionImpl(PermissionType.TABLE, PermissionLevel.READ),
new Permission(PermissionType.APP, PermissionLevel.READ), new PermissionImpl(PermissionType.APP, PermissionLevel.READ),
], ],
}, },
WRITE: { WRITE: {
_id: BuiltinPermissionID.WRITE, _id: BuiltinPermissionID.WRITE,
name: "Read/Write", name: "Read/Write",
permissions: [ permissions: [
new Permission(PermissionType.QUERY, PermissionLevel.WRITE), new PermissionImpl(PermissionType.QUERY, PermissionLevel.WRITE),
new Permission(PermissionType.TABLE, PermissionLevel.WRITE), new PermissionImpl(PermissionType.TABLE, PermissionLevel.WRITE),
new Permission(PermissionType.AUTOMATION, PermissionLevel.EXECUTE), new PermissionImpl(PermissionType.AUTOMATION, PermissionLevel.EXECUTE),
new Permission(PermissionType.LEGACY_VIEW, PermissionLevel.READ), new PermissionImpl(PermissionType.LEGACY_VIEW, PermissionLevel.READ),
new Permission(PermissionType.APP, PermissionLevel.READ), new PermissionImpl(PermissionType.APP, PermissionLevel.READ),
], ],
}, },
POWER: { POWER: {
_id: BuiltinPermissionID.POWER, _id: BuiltinPermissionID.POWER,
name: "Power", name: "Power",
permissions: [ permissions: [
new Permission(PermissionType.TABLE, PermissionLevel.WRITE), new PermissionImpl(PermissionType.TABLE, PermissionLevel.WRITE),
new Permission(PermissionType.USER, PermissionLevel.READ), new PermissionImpl(PermissionType.USER, PermissionLevel.READ),
new Permission(PermissionType.AUTOMATION, PermissionLevel.EXECUTE), new PermissionImpl(PermissionType.AUTOMATION, PermissionLevel.EXECUTE),
new Permission(PermissionType.WEBHOOK, PermissionLevel.READ), new PermissionImpl(PermissionType.WEBHOOK, PermissionLevel.READ),
new Permission(PermissionType.LEGACY_VIEW, PermissionLevel.READ), new PermissionImpl(PermissionType.LEGACY_VIEW, PermissionLevel.READ),
new Permission(PermissionType.APP, PermissionLevel.READ), new PermissionImpl(PermissionType.APP, PermissionLevel.READ),
], ],
}, },
ADMIN: { ADMIN: {
_id: BuiltinPermissionID.ADMIN, _id: BuiltinPermissionID.ADMIN,
name: "Admin", name: "Admin",
permissions: [ permissions: [
new Permission(PermissionType.TABLE, PermissionLevel.ADMIN), new PermissionImpl(PermissionType.TABLE, PermissionLevel.ADMIN),
new Permission(PermissionType.USER, PermissionLevel.ADMIN), new PermissionImpl(PermissionType.USER, PermissionLevel.ADMIN),
new Permission(PermissionType.AUTOMATION, PermissionLevel.ADMIN), new PermissionImpl(PermissionType.AUTOMATION, PermissionLevel.ADMIN),
new Permission(PermissionType.WEBHOOK, PermissionLevel.READ), new PermissionImpl(PermissionType.WEBHOOK, PermissionLevel.READ),
new Permission(PermissionType.QUERY, PermissionLevel.ADMIN), new PermissionImpl(PermissionType.QUERY, PermissionLevel.ADMIN),
new Permission(PermissionType.LEGACY_VIEW, PermissionLevel.READ), new PermissionImpl(PermissionType.LEGACY_VIEW, PermissionLevel.READ),
new Permission(PermissionType.APP, PermissionLevel.READ), new PermissionImpl(PermissionType.APP, PermissionLevel.READ),
], ],
}, },
} }
export function getBuiltinPermissions() { export function getBuiltinPermissions(): BuiltinPermissions {
return cloneDeep(BUILTIN_PERMISSIONS) return cloneDeep(BUILTIN_PERMISSIONS)
} }

View File

@ -133,7 +133,7 @@ describe("getBuiltinPermissionByID", () => {
_id: BuiltinPermissionID.PUBLIC, _id: BuiltinPermissionID.PUBLIC,
name: "Public", name: "Public",
permissions: [ permissions: [
new permissions.Permission( new permissions.PermissionImpl(
permissions.PermissionType.WEBHOOK, permissions.PermissionType.WEBHOOK,
permissions.PermissionLevel.EXECUTE permissions.PermissionLevel.EXECUTE
), ),

View File

@ -52,9 +52,16 @@
let modal let modal
$: text = value?.label ?? "Choose an option" $: text = value?.label ?? "Choose an option"
$: tables = $tablesStore.list.map(table => $: tables = $tablesStore.list
format.table(table, $datasources.list) .map(table => format.table(table, $datasources.list))
) .sort((a, b) => {
// sort tables alphabetically, grouped by datasource
const dsComparison = a.datasourceName.localeCompare(b.datasourceName)
if (dsComparison !== 0) {
return dsComparison
}
return a.label.localeCompare(b.label)
})
$: viewsV1 = $viewsStore.list.map(view => ({ $: viewsV1 = $viewsStore.list.map(view => ({
...view, ...view,
label: view.name, label: view.name,

View File

@ -1,5 +1,5 @@
<script> <script>
import { Heading, Body, Layout, Button, Modal } from "@budibase/bbui" import { Heading, Body, Layout, Button, Modal, Icon } from "@budibase/bbui"
import AutomationPanel from "components/automation/AutomationPanel/AutomationPanel.svelte" import AutomationPanel from "components/automation/AutomationPanel/AutomationPanel.svelte"
import CreateAutomationModal from "components/automation/AutomationPanel/CreateAutomationModal.svelte" import CreateAutomationModal from "components/automation/AutomationPanel/CreateAutomationModal.svelte"
import CreateWebhookModal from "components/automation/Shared/CreateWebhookModal.svelte" import CreateWebhookModal from "components/automation/Shared/CreateWebhookModal.svelte"
@ -12,11 +12,13 @@
automationStore, automationStore,
selectedAutomation, selectedAutomation,
} from "stores/builder" } from "stores/builder"
import { createLocalStorageStore } from "@budibase/frontend-core"
import { fly } from "svelte/transition"
$: automationId = $selectedAutomation?.data?._id $: automationId = $selectedAutomation?.data?._id
$: builderStore.selectResource(automationId) $: builderStore.selectResource(automationId)
// Keep URL and state in sync for selected screen ID const surveyDismissed = createLocalStorageStore("automation-survey", false)
const stopSyncing = syncURLToState({ const stopSyncing = syncURLToState({
urlParam: "automationId", urlParam: "automationId",
stateKey: "selectedAutomationId", stateKey: "selectedAutomationId",
@ -29,9 +31,11 @@
let modal let modal
let webhookModal let webhookModal
let mounted = false
onMount(() => { onMount(() => {
$automationStore.showTestPanel = false $automationStore.showTestPanel = false
mounted = true
}) })
onDestroy(stopSyncing) onDestroy(stopSyncing)
@ -79,6 +83,43 @@
</Modal> </Modal>
</div> </div>
{#if !$surveyDismissed && mounted}
<div
class="survey"
in:fly={{ x: 600, duration: 260, delay: 1000 }}
out:fly={{ x: 600, duration: 260 }}
>
<div class="survey__body">
<div class="survey__title">We value your feedback!</div>
<div class="survey__text">
<a
href="https://t.maze.co/310149185"
target="_blank"
rel="noopener noreferrer"
on:click={() => surveyDismissed.set(true)}
>
Complete our survey on Automations</a
>
and receive a $20 thank-you gift.
<a
href="https://drive.google.com/file/d/12-qk_2F9g5PdbM6wuKoz2KkIyLI-feMX/view?usp=sharing"
target="_blank"
rel="noopener noreferrer"
>
Terms apply.
</a>
</div>
</div>
<Icon
name="Close"
hoverable
color="var(--spectrum-global-color-static-gray-300)"
hoverColor="var(--spectrum-global-color-static-gray-100)"
on:click={() => surveyDismissed.set(true)}
/>
</div>
{/if}
<style> <style>
.root { .root {
flex: 1 1 auto; flex: 1 1 auto;
@ -108,11 +149,9 @@
justify-content: center; justify-content: center;
align-items: center; align-items: center;
} }
.main { .main {
width: 300px; width: 300px;
} }
.setup { .setup {
padding-top: 9px; padding-top: 9px;
border-left: var(--border-light); border-left: var(--border-light);
@ -125,4 +164,39 @@
grid-column: 3; grid-column: 3;
overflow: auto; overflow: auto;
} }
/* Survey */
.survey {
position: absolute;
bottom: 32px;
right: 32px;
background: var(--spectrum-semantic-positive-color-background);
display: flex;
flex-direction: row;
padding: var(--spacing-l) var(--spacing-xl);
border-radius: 4px;
gap: var(--spacing-xl);
}
.survey * {
color: var(--spectrum-global-color-static-gray-300);
white-space: nowrap;
}
.survey a {
text-decoration: underline;
transition: color 130ms ease-out;
}
.survey a:hover {
color: var(--spectrum-global-color-static-gray-100);
cursor: pointer;
}
.survey__body {
flex: 1 1 auto;
display: flex;
flex-direction: column;
gap: 2px;
}
.survey__title {
font-weight: 600;
font-size: 15px;
}
</style> </style>

View File

@ -1,12 +1,17 @@
import { getDefinition, getDefinitions } from "../../integrations" import { getDefinition, getDefinitions } from "../../integrations"
import { SourceName, UserCtx } from "@budibase/types" import {
SourceName,
UserCtx,
FetchIntegrationsResponse,
FindIntegrationResponse,
} from "@budibase/types"
const DISABLED_EXTERNAL_INTEGRATIONS = [ const DISABLED_EXTERNAL_INTEGRATIONS = [
SourceName.AIRTABLE, SourceName.AIRTABLE,
SourceName.BUDIBASE, SourceName.BUDIBASE,
] ]
export async function fetch(ctx: UserCtx) { export async function fetch(ctx: UserCtx<void, FetchIntegrationsResponse>) {
const definitions = await getDefinitions() const definitions = await getDefinitions()
for (let disabledIntegration of DISABLED_EXTERNAL_INTEGRATIONS) { for (let disabledIntegration of DISABLED_EXTERNAL_INTEGRATIONS) {
delete definitions[disabledIntegration] delete definitions[disabledIntegration]
@ -14,10 +19,14 @@ export async function fetch(ctx: UserCtx) {
ctx.body = definitions ctx.body = definitions
} }
export async function find(ctx: UserCtx) { export async function find(ctx: UserCtx<void, FindIntegrationResponse>) {
const sourceType = ctx.params?.type const sourceType = ctx.params?.type
if (DISABLED_EXTERNAL_INTEGRATIONS.indexOf(sourceType) !== -1) { if (DISABLED_EXTERNAL_INTEGRATIONS.indexOf(sourceType) !== -1) {
ctx.throw(400, `Invalid source type - ${sourceType} is not supported.`) ctx.throw(400, `Invalid source type - ${sourceType} is not supported.`)
} }
ctx.body = await getDefinition(ctx.params.type) const integration = await getDefinition(ctx.params.type)
if (!integration) {
ctx.throw(404, "Integration not found")
}
ctx.body = integration
} }

View File

@ -2,7 +2,7 @@ import { EMPTY_LAYOUT } from "../../constants/layouts"
import { generateLayoutID, getScreenParams } from "../../db/utils" import { generateLayoutID, getScreenParams } from "../../db/utils"
import { events, context } from "@budibase/backend-core" import { events, context } from "@budibase/backend-core"
import { import {
BBContext, DeleteLayoutResponse,
Layout, Layout,
SaveLayoutRequest, SaveLayoutRequest,
SaveLayoutResponse, SaveLayoutResponse,
@ -32,7 +32,7 @@ export async function save(
ctx.status = 200 ctx.status = 200
} }
export async function destroy(ctx: BBContext) { export async function destroy(ctx: UserCtx<void, DeleteLayoutResponse>) {
const db = context.getAppDB() const db = context.getAppDB()
const layoutId = ctx.params.layoutId, const layoutId = ctx.params.layoutId,
layoutRev = ctx.params.layoutRev layoutRev = ctx.params.layoutRev

View File

@ -1,24 +1,35 @@
import { MetadataTypes } from "../../constants"
import { generateMetadataID } from "../../db/utils" import { generateMetadataID } from "../../db/utils"
import { saveEntityMetadata, deleteEntityMetadata } from "../../utilities" import { saveEntityMetadata, deleteEntityMetadata } from "../../utilities"
import { context } from "@budibase/backend-core" import { context } from "@budibase/backend-core"
import { BBContext } from "@budibase/types" import {
UserCtx,
MetadataType,
GetMetadataTypesResponse,
SaveMetadataRequest,
SaveMetadataResponse,
DeleteMetadataResponse,
FindMetadataResponse,
} from "@budibase/types"
export async function getTypes(ctx: BBContext) { export async function getTypes(ctx: UserCtx<void, GetMetadataTypesResponse>) {
ctx.body = { ctx.body = {
types: MetadataTypes, types: MetadataType,
} }
} }
export async function saveMetadata(ctx: BBContext) { export async function saveMetadata(
ctx: UserCtx<SaveMetadataRequest, SaveMetadataResponse>
) {
const { type, entityId } = ctx.params const { type, entityId } = ctx.params
if (type === MetadataTypes.AUTOMATION_TEST_HISTORY) { if (type === MetadataType.AUTOMATION_TEST_HISTORY) {
ctx.throw(400, "Cannot save automation history type") ctx.throw(400, "Cannot save automation history type")
} }
ctx.body = await saveEntityMetadata(type, entityId, ctx.request.body) ctx.body = await saveEntityMetadata(type, entityId, ctx.request.body)
} }
export async function deleteMetadata(ctx: BBContext) { export async function deleteMetadata(
ctx: UserCtx<void, DeleteMetadataResponse>
) {
const { type, entityId } = ctx.params const { type, entityId } = ctx.params
await deleteEntityMetadata(type, entityId) await deleteEntityMetadata(type, entityId)
ctx.body = { ctx.body = {
@ -26,17 +37,9 @@ export async function deleteMetadata(ctx: BBContext) {
} }
} }
export async function getMetadata(ctx: BBContext) { export async function getMetadata(ctx: UserCtx<void, FindMetadataResponse>) {
const { type, entityId } = ctx.params const { type, entityId } = ctx.params
const db = context.getAppDB() const db = context.getAppDB()
const id = generateMetadataID(type, entityId) const id = generateMetadataID(type, entityId)
try { ctx.body = (await db.tryGet(id)) || {}
ctx.body = await db.get(id)
} catch (err: any) {
if (err.status === 404) {
ctx.body = {}
} else {
ctx.throw(err.status, err)
}
}
} }

View File

@ -1,24 +1,33 @@
import { context } from "@budibase/backend-core" import { context } from "@budibase/backend-core"
import { migrate as migrationImpl, MIGRATIONS } from "../../migrations" import { migrate as migrationImpl, MIGRATIONS } from "../../migrations"
import { Ctx } from "@budibase/types" import {
Ctx,
FetchOldMigrationResponse,
GetOldMigrationStatus,
RunOldMigrationRequest,
} from "@budibase/types"
import { import {
getAppMigrationVersion, getAppMigrationVersion,
getLatestEnabledMigrationId, getLatestEnabledMigrationId,
} from "../../appMigrations" } from "../../appMigrations"
export async function migrate(ctx: Ctx) { export async function migrate(ctx: Ctx<RunOldMigrationRequest, void>) {
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.status = 200
} }
export async function fetchDefinitions(ctx: Ctx) { export async function fetchDefinitions(
ctx: Ctx<void, FetchOldMigrationResponse>
) {
ctx.body = MIGRATIONS ctx.body = MIGRATIONS
ctx.status = 200 ctx.status = 200
} }
export async function getMigrationStatus(ctx: Ctx) { export async function getMigrationStatus(
ctx: Ctx<void, GetOldMigrationStatus>
) {
const appId = context.getAppId() const appId = context.getAppId()
if (!appId) { if (!appId) {

View File

@ -1,16 +1,7 @@
import { Ctx } from "@budibase/types" import { Ctx, LogOpsRequest, ErrorOpsRequest } from "@budibase/types"
import { logging } from "@budibase/backend-core" import { logging } from "@budibase/backend-core"
interface LogRequest { export async function log(ctx: Ctx<LogOpsRequest, void>) {
message: string
data?: any
}
interface ErrorRequest {
message: string
}
export async function log(ctx: Ctx<LogRequest>) {
const body = ctx.request.body const body = ctx.request.body
console.trace(body.message, body.data) console.trace(body.message, body.data)
console.debug(body.message, body.data) console.debug(body.message, body.data)
@ -20,13 +11,13 @@ export async function log(ctx: Ctx<LogRequest>) {
ctx.status = 204 ctx.status = 204
} }
export async function alert(ctx: Ctx<ErrorRequest>) { export async function alert(ctx: Ctx<ErrorOpsRequest, void>) {
const body = ctx.request.body const body = ctx.request.body
logging.logAlert(body.message, new Error(body.message)) logging.logAlert(body.message, new Error(body.message))
ctx.status = 204 ctx.status = 204
} }
export async function error(ctx: Ctx<ErrorRequest>) { export async function error(ctx: Ctx<ErrorOpsRequest, void>) {
const body = ctx.request.body const body = ctx.request.body
throw new Error(body.message) throw new Error(body.message)
} }

View File

@ -9,6 +9,8 @@ import {
RemovePermissionRequest, RemovePermissionRequest,
RemovePermissionResponse, RemovePermissionResponse,
FetchResourcePermissionInfoResponse, FetchResourcePermissionInfoResponse,
FetchBuiltinPermissionsRequest,
FetchPermissionLevelsRequest,
} from "@budibase/types" } from "@budibase/types"
import { import {
CURRENTLY_SUPPORTED_LEVELS, CURRENTLY_SUPPORTED_LEVELS,
@ -19,11 +21,13 @@ import { PermissionUpdateType } from "../../sdk/app/permissions"
const SUPPORTED_LEVELS = CURRENTLY_SUPPORTED_LEVELS const SUPPORTED_LEVELS = CURRENTLY_SUPPORTED_LEVELS
export function fetchBuiltin(ctx: UserCtx) { export function fetchBuiltin(
ctx: UserCtx<void, FetchBuiltinPermissionsRequest>
) {
ctx.body = Object.values(permissions.getBuiltinPermissions()) ctx.body = Object.values(permissions.getBuiltinPermissions())
} }
export function fetchLevels(ctx: UserCtx) { export function fetchLevels(ctx: UserCtx<void, FetchPermissionLevelsRequest>) {
// for now only provide the read/write perms externally // for now only provide the read/write perms externally
ctx.body = SUPPORTED_LEVELS ctx.body = SUPPORTED_LEVELS
} }

View File

@ -3,8 +3,12 @@ import {
getPluginMetadata, getPluginMetadata,
extractTarball, extractTarball,
} from "../../../utilities/fileSystem" } from "../../../utilities/fileSystem"
import { KoaFile } from "@budibase/types"
export async function fileUpload(file: { name: string; path: string }) { export async function fileUpload(file: KoaFile) {
if (!file.name || !file.path) {
throw new Error("File is not valid - cannot upload.")
}
if (!file.name.endsWith(".tar.gz")) { if (!file.name.endsWith(".tar.gz")) {
throw new Error("Plugin must be compressed into a gzipped tarball.") throw new Error("Plugin must be compressed into a gzipped tarball.")
} }

View File

@ -2,26 +2,37 @@ import { npmUpload, urlUpload, githubUpload } from "./uploaders"
import { plugins as pluginCore } from "@budibase/backend-core" import { plugins as pluginCore } from "@budibase/backend-core"
import { import {
PluginType, PluginType,
FileType,
PluginSource, PluginSource,
Ctx,
CreatePluginRequest, CreatePluginRequest,
CreatePluginResponse, CreatePluginResponse,
UserCtx,
UploadPluginRequest,
Plugin,
UploadPluginResponse,
FetchPluginResponse,
DeletePluginResponse,
} from "@budibase/types" } from "@budibase/types"
import env from "../../../environment" import env from "../../../environment"
import { clientAppSocket } from "../../../websockets" import { clientAppSocket } from "../../../websockets"
import sdk from "../../../sdk" import sdk from "../../../sdk"
import { sdk as pro } from "@budibase/pro" import { sdk as pro } from "@budibase/pro"
export async function upload(ctx: any) { export async function upload(
const plugins: FileType[] = ctx: UserCtx<UploadPluginRequest, UploadPluginResponse>
ctx.request.files.file.length > 1 ) {
? Array.from(ctx.request.files.file) const files = ctx.request.files
: [ctx.request.files.file] const plugins =
files && Array.isArray(files.file) && files.file.length > 1
? Array.from(files.file)
: [files?.file]
try { try {
let docs = [] let docs: Plugin[] = []
// can do single or multiple plugins // can do single or multiple plugins
for (let plugin of plugins) { for (let plugin of plugins) {
if (!plugin || Array.isArray(plugin)) {
continue
}
const doc = await sdk.plugins.processUploaded(plugin, PluginSource.FILE) const doc = await sdk.plugins.processUploaded(plugin, PluginSource.FILE)
docs.push(doc) docs.push(doc)
} }
@ -37,7 +48,7 @@ export async function upload(ctx: any) {
} }
export async function create( export async function create(
ctx: Ctx<CreatePluginRequest, CreatePluginResponse> ctx: UserCtx<CreatePluginRequest, CreatePluginResponse>
) { ) {
const { source, url, headers, githubToken } = ctx.request.body const { source, url, headers, githubToken } = ctx.request.body
@ -91,11 +102,11 @@ export async function create(
} }
} }
export async function fetch(ctx: any) { export async function fetch(ctx: UserCtx<void, FetchPluginResponse>) {
ctx.body = await sdk.plugins.fetch() ctx.body = await sdk.plugins.fetch()
} }
export async function destroy(ctx: any) { export async function destroy(ctx: UserCtx<void, DeletePluginResponse>) {
const { pluginId } = ctx.params const { pluginId } = ctx.params
try { try {

View File

@ -4,26 +4,38 @@ import { save as saveDatasource } from "../datasource"
import { RestImporter } from "./import" import { RestImporter } from "./import"
import { invalidateCachedVariable } from "../../../threads/utils" import { invalidateCachedVariable } from "../../../threads/utils"
import env from "../../../environment" import env from "../../../environment"
import { events, context, utils, constants } from "@budibase/backend-core" import { constants, context, events, utils } from "@budibase/backend-core"
import sdk from "../../../sdk" import sdk from "../../../sdk"
import { QueryEvent, QueryEventParameters } from "../../../threads/definitions" import { QueryEvent, QueryEventParameters } from "../../../threads/definitions"
import { import {
ConfigType, ConfigType,
Query, CreateDatasourceRequest,
UserCtx, Datasource,
SessionCookie,
JsonFieldSubType,
QueryResponse,
QuerySchema,
FieldType,
ExecuteQueryRequest, ExecuteQueryRequest,
ExecuteQueryResponse, ExecuteV2QueryResponse,
ExecuteV1QueryResponse,
FetchQueriesResponse,
FieldType,
FindQueryResponse,
ImportRestQueryRequest,
ImportRestQueryResponse,
JsonFieldSubType,
PreviewQueryRequest, PreviewQueryRequest,
PreviewQueryResponse, PreviewQueryResponse,
Query,
QueryResponse,
QuerySchema,
SaveQueryRequest,
SaveQueryResponse,
SessionCookie,
SourceName,
UserCtx,
DeleteQueryResponse,
} from "@budibase/types" } from "@budibase/types"
import { ValidQueryNameRegex, utils as JsonUtils } from "@budibase/shared-core" import { utils as JsonUtils, ValidQueryNameRegex } from "@budibase/shared-core"
import { findHBSBlocks } from "@budibase/string-templates" import { findHBSBlocks } from "@budibase/string-templates"
import { ObjectId } from "mongodb" import { ObjectId } from "mongodb"
import { merge } from "lodash"
const Runner = new Thread(ThreadType.QUERY, { const Runner = new Thread(ThreadType.QUERY, {
timeoutMs: env.QUERY_THREAD_TIMEOUT, timeoutMs: env.QUERY_THREAD_TIMEOUT,
@ -43,11 +55,13 @@ function validateQueryInputs(parameters: QueryEventParameters) {
} }
} }
export async function fetch(ctx: UserCtx) { export async function fetch(ctx: UserCtx<void, FetchQueriesResponse>) {
ctx.body = await sdk.queries.fetch() ctx.body = await sdk.queries.fetch()
} }
const _import = async (ctx: UserCtx) => { const _import = async (
ctx: UserCtx<ImportRestQueryRequest, ImportRestQueryResponse>
) => {
const body = ctx.request.body const body = ctx.request.body
const data = body.data const data = body.data
@ -58,9 +72,9 @@ const _import = async (ctx: UserCtx) => {
if (!body.datasourceId) { if (!body.datasourceId) {
// construct new datasource // construct new datasource
const info: any = await importer.getInfo() const info: any = await importer.getInfo()
let datasource = { let datasource: Datasource = {
type: "datasource", type: "datasource",
source: "REST", source: SourceName.REST,
config: { config: {
url: info.url, url: info.url,
defaultHeaders: [], defaultHeaders: [],
@ -69,8 +83,14 @@ const _import = async (ctx: UserCtx) => {
name: info.name, name: info.name,
} }
// save the datasource // save the datasource
const datasourceCtx = { ...ctx } const datasourceCtx: UserCtx<CreateDatasourceRequest> = merge(ctx, {
datasourceCtx.request.body.datasource = datasource request: {
body: {
datasource,
tablesFilter: [],
},
},
})
await saveDatasource(datasourceCtx) await saveDatasource(datasourceCtx)
datasourceId = datasourceCtx.body.datasource._id datasourceId = datasourceCtx.body.datasource._id
} else { } else {
@ -88,7 +108,7 @@ const _import = async (ctx: UserCtx) => {
} }
export { _import as import } export { _import as import }
export async function save(ctx: UserCtx<Query, Query>) { export async function save(ctx: UserCtx<SaveQueryRequest, SaveQueryResponse>) {
const db = context.getAppDB() const db = context.getAppDB()
const query: Query = ctx.request.body const query: Query = ctx.request.body
@ -119,10 +139,9 @@ export async function save(ctx: UserCtx<Query, Query>) {
query._rev = response.rev query._rev = response.rev
ctx.body = query ctx.body = query
ctx.message = `Query ${query.name} saved successfully.`
} }
export async function find(ctx: UserCtx) { export async function find(ctx: UserCtx<void, FindQueryResponse>) {
const queryId = ctx.params.queryId const queryId = ctx.params.queryId
ctx.body = await sdk.queries.find(queryId) ctx.body = await sdk.queries.find(queryId)
} }
@ -335,7 +354,7 @@ export async function preview(
async function execute( async function execute(
ctx: UserCtx< ctx: UserCtx<
ExecuteQueryRequest, ExecuteQueryRequest,
ExecuteQueryResponse | Record<string, any>[] ExecuteV2QueryResponse | ExecuteV1QueryResponse
>, >,
opts: any = { rowsOnly: false, isAutomation: false } opts: any = { rowsOnly: false, isAutomation: false }
) { ) {
@ -390,19 +409,21 @@ async function execute(
} }
export async function executeV1( export async function executeV1(
ctx: UserCtx<ExecuteQueryRequest, Record<string, any>[]> ctx: UserCtx<ExecuteQueryRequest, ExecuteV1QueryResponse>
) { ) {
return execute(ctx, { rowsOnly: true, isAutomation: false }) return execute(ctx, { rowsOnly: true, isAutomation: false })
} }
export async function executeV2( export async function executeV2(
ctx: UserCtx< ctx: UserCtx<ExecuteQueryRequest, ExecuteV2QueryResponse>
ExecuteQueryRequest,
ExecuteQueryResponse | Record<string, any>[]
>,
{ isAutomation }: { isAutomation?: boolean } = {}
) { ) {
return execute(ctx, { rowsOnly: false, isAutomation }) return execute(ctx, { rowsOnly: false })
}
export async function executeV2AsAutomation(
ctx: UserCtx<ExecuteQueryRequest, ExecuteV2QueryResponse>
) {
return execute(ctx, { rowsOnly: false, isAutomation: true })
} }
const removeDynamicVariables = async (queryId: string) => { const removeDynamicVariables = async (queryId: string) => {
@ -426,14 +447,14 @@ const removeDynamicVariables = async (queryId: string) => {
} }
} }
export async function destroy(ctx: UserCtx) { export async function destroy(ctx: UserCtx<void, DeleteQueryResponse>) {
const db = context.getAppDB() const db = context.getAppDB()
const queryId = ctx.params.queryId as string const queryId = ctx.params.queryId as string
await removeDynamicVariables(queryId) await removeDynamicVariables(queryId)
const query = await db.get<Query>(queryId) const query = await db.get<Query>(queryId)
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.message = `Query deleted.` ctx.body = { message: `Query deleted.` }
ctx.status = 200 ctx.status = 200
await events.query.deleted(datasource, query) await events.query.deleted(datasource, query)
} }

View File

@ -9,7 +9,7 @@ import { getUserMetadataParams, InternalTables } from "../../db/utils"
import { import {
AccessibleRolesResponse, AccessibleRolesResponse,
Database, Database,
DestroyRoleResponse, DeleteRoleResponse,
FetchRolesResponse, FetchRolesResponse,
FindRoleResponse, FindRoleResponse,
Role, Role,
@ -199,7 +199,7 @@ export async function save(ctx: UserCtx<SaveRoleRequest, SaveRoleResponse>) {
builderSocket?.emitRoleUpdate(ctx, role) builderSocket?.emitRoleUpdate(ctx, role)
} }
export async function destroy(ctx: UserCtx<void, DestroyRoleResponse>) { export async function destroy(ctx: UserCtx<void, DeleteRoleResponse>) {
const db = context.getAppDB() const db = context.getAppDB()
let roleId = ctx.params.roleId as string let roleId = ctx.params.roleId as string
if (roles.isBuiltin(roleId)) { if (roles.isBuiltin(roleId)) {

View File

@ -1,11 +1,17 @@
import { getRoutingInfo } from "../../utilities/routing" import { getRoutingInfo } from "../../utilities/routing"
import { roles } from "@budibase/backend-core" import { roles } from "@budibase/backend-core"
import { UserCtx } from "@budibase/types" import {
FetchClientScreenRoutingResponse,
FetchScreenRoutingResponse,
ScreenRoutingJson,
UserCtx,
} from "@budibase/types"
const URL_SEPARATOR = "/" const URL_SEPARATOR = "/"
class Routing { class Routing {
json: any json: ScreenRoutingJson
constructor() { constructor() {
this.json = {} this.json = {}
} }
@ -43,7 +49,7 @@ class Routing {
* @returns The routing structure, this is the full structure designed for use in the builder, * @returns The routing structure, this is the full structure designed for use in the builder,
* if the client routing is required then the updateRoutingStructureForUserRole should be used. * if the client routing is required then the updateRoutingStructureForUserRole should be used.
*/ */
async function getRoutingStructure() { async function getRoutingStructure(): Promise<{ routes: ScreenRoutingJson }> {
const screenRoutes = await getRoutingInfo() const screenRoutes = await getRoutingInfo()
const routing = new Routing() const routing = new Routing()
@ -56,11 +62,13 @@ async function getRoutingStructure() {
return { routes: routing.json } return { routes: routing.json }
} }
export async function fetch(ctx: UserCtx) { export async function fetch(ctx: UserCtx<void, FetchScreenRoutingResponse>) {
ctx.body = await getRoutingStructure() ctx.body = await getRoutingStructure()
} }
export async function clientFetch(ctx: UserCtx) { export async function clientFetch(
ctx: UserCtx<void, FetchClientScreenRoutingResponse>
) {
const routing = await getRoutingStructure() const routing = await getRoutingStructure()
let roleId = ctx.user?.role?._id let roleId = ctx.user?.role?._id
const roleIds = roleId ? await roles.getUserRoleIdHierarchy(roleId) : [] const roleIds = roleId ? await roles.getUserRoleIdHierarchy(roleId) : []

View File

@ -11,11 +11,14 @@ import {
DeleteRow, DeleteRow,
DeleteRowRequest, DeleteRowRequest,
DeleteRows, DeleteRows,
DownloadAttachmentResponse,
EventType, EventType,
ExportRowsRequest, ExportRowsRequest,
ExportRowsResponse, ExportRowsResponse,
FetchEnrichedRowResponse,
FetchRowsResponse,
FieldType, FieldType,
GetRowResponse, FindRowResponse,
isRelationshipField, isRelationshipField,
PatchRowRequest, PatchRowRequest,
PatchRowResponse, PatchRowResponse,
@ -23,12 +26,15 @@ import {
Row, Row,
RowAttachment, RowAttachment,
RowSearchParams, RowSearchParams,
SaveRowRequest,
SaveRowResponse,
SearchFilters, SearchFilters,
SearchRowRequest, SearchRowRequest,
SearchRowResponse, SearchRowResponse,
Table, Table,
UserCtx, UserCtx,
ValidateResponse, ValidateRowRequest,
ValidateRowResponse,
} from "@budibase/types" } from "@budibase/types"
import * as utils from "./utils" import * as utils from "./utils"
import { gridSocket } from "../../../websockets" import { gridSocket } from "../../../websockets"
@ -83,7 +89,7 @@ export async function patch(
} }
} }
export const save = async (ctx: UserCtx<Row, Row>) => { export const save = async (ctx: UserCtx<SaveRowRequest, SaveRowResponse>) => {
const { tableId, viewId } = utils.getSourceId(ctx) const { tableId, viewId } = utils.getSourceId(ctx)
const sourceId = viewId || tableId const sourceId = viewId || tableId
@ -131,12 +137,12 @@ export async function fetchLegacyView(ctx: any) {
}) })
} }
export async function fetch(ctx: any) { export async function fetch(ctx: UserCtx<void, FetchRowsResponse>) {
const { tableId } = utils.getSourceId(ctx) const { tableId } = utils.getSourceId(ctx)
ctx.body = await sdk.rows.fetch(tableId) ctx.body = await sdk.rows.fetch(tableId)
} }
export async function find(ctx: UserCtx<void, GetRowResponse>) { export async function find(ctx: UserCtx<void, FindRowResponse>) {
const { tableId, viewId } = utils.getSourceId(ctx) const { tableId, viewId } = utils.getSourceId(ctx)
const sourceId = viewId || tableId const sourceId = viewId || tableId
const rowId = ctx.params.rowId const rowId = ctx.params.rowId
@ -314,7 +320,9 @@ function replaceTableNamesInFilters(
}) })
} }
export async function validate(ctx: Ctx<Row, ValidateResponse>) { export async function validate(
ctx: Ctx<ValidateRowRequest, ValidateRowResponse>
) {
const source = await utils.getSource(ctx) const source = await utils.getSource(ctx)
const table = await utils.getTableFromSource(source) const table = await utils.getTableFromSource(source)
// external tables are hard to validate currently // external tables are hard to validate currently
@ -328,7 +336,9 @@ export async function validate(ctx: Ctx<Row, ValidateResponse>) {
} }
} }
export async function fetchEnrichedRow(ctx: UserCtx<void, Row>) { export async function fetchEnrichedRow(
ctx: UserCtx<void, FetchEnrichedRowResponse>
) {
const { tableId } = utils.getSourceId(ctx) const { tableId } = utils.getSourceId(ctx)
ctx.body = await pickApi(tableId).fetchEnrichedRow(ctx) ctx.body = await pickApi(tableId).fetchEnrichedRow(ctx)
} }
@ -366,7 +376,9 @@ export const exportRows = async (
ctx.body = apiFileReturn(content) ctx.body = apiFileReturn(content)
} }
export async function downloadAttachment(ctx: UserCtx) { export async function downloadAttachment(
ctx: UserCtx<void, DownloadAttachmentResponse>
) {
const { columnName } = ctx.params const { columnName } = ctx.params
const { tableId } = utils.getSourceId(ctx) const { tableId } = utils.getSourceId(ctx)

View File

@ -56,7 +56,7 @@ router
"/api/v2/queries/:queryId", "/api/v2/queries/:queryId",
paramResource("queryId"), paramResource("queryId"),
authorized(PermissionType.QUERY, PermissionLevel.WRITE), authorized(PermissionType.QUERY, PermissionLevel.WRITE),
queryController.executeV2 as any queryController.executeV2
) )
export default router export default router

View File

@ -1,11 +1,11 @@
const { testAutomation } = require("./utilities/TestFunctions") import { testAutomation } from "./utilities/TestFunctions"
const setup = require("./utilities") import * as setup from "./utilities"
const { MetadataTypes } = require("../../../constants") import { MetadataType, Automation } from "@budibase/types"
describe("/metadata", () => { describe("/metadata", () => {
let request = setup.getRequest() let request = setup.getRequest()
let config = setup.getConfig() let config = setup.getConfig()
let automation let automation: Automation
afterAll(setup.afterAll) afterAll(setup.afterAll)
@ -15,8 +15,8 @@ describe("/metadata", () => {
}) })
async function createMetadata( async function createMetadata(
data, data: Record<string, string>,
type = MetadataTypes.AUTOMATION_TEST_INPUT type = MetadataType.AUTOMATION_TEST_INPUT
) { ) {
const res = await request const res = await request
.post(`/api/metadata/${type}/${automation._id}`) .post(`/api/metadata/${type}/${automation._id}`)
@ -27,7 +27,7 @@ describe("/metadata", () => {
expect(res.body._rev).toBeDefined() expect(res.body._rev).toBeDefined()
} }
async function getMetadata(type) { async function getMetadata(type: MetadataType) {
const res = await request const res = await request
.get(`/api/metadata/${type}/${automation._id}`) .get(`/api/metadata/${type}/${automation._id}`)
.set(config.defaultHeaders()) .set(config.defaultHeaders())
@ -39,14 +39,14 @@ describe("/metadata", () => {
describe("save", () => { describe("save", () => {
it("should be able to save some metadata", async () => { it("should be able to save some metadata", async () => {
await createMetadata({ test: "a" }) await createMetadata({ test: "a" })
const testInput = await getMetadata(MetadataTypes.AUTOMATION_TEST_INPUT) const testInput = await getMetadata(MetadataType.AUTOMATION_TEST_INPUT)
expect(testInput.test).toBe("a") expect(testInput.test).toBe("a")
}) })
it("should save history metadata on automation run", async () => { it("should save history metadata on automation run", async () => {
// this should have created some history // this should have created some history
await testAutomation(config, automation) await testAutomation(config, automation, {})
const metadata = await getMetadata(MetadataTypes.AUTOMATION_TEST_HISTORY) const metadata = await getMetadata(MetadataType.AUTOMATION_TEST_HISTORY)
expect(metadata).toBeDefined() expect(metadata).toBeDefined()
expect(metadata.history.length).toBe(1) expect(metadata.history.length).toBe(1)
expect(typeof metadata.history[0].occurredAt).toBe("number") expect(typeof metadata.history[0].occurredAt).toBe("number")
@ -57,13 +57,13 @@ describe("/metadata", () => {
it("should be able to delete some test inputs", async () => { it("should be able to delete some test inputs", async () => {
const res = await request const res = await request
.delete( .delete(
`/api/metadata/${MetadataTypes.AUTOMATION_TEST_INPUT}/${automation._id}` `/api/metadata/${MetadataType.AUTOMATION_TEST_INPUT}/${automation._id}`
) )
.set(config.defaultHeaders()) .set(config.defaultHeaders())
.expect("Content-Type", /json/) .expect("Content-Type", /json/)
.expect(200) .expect(200)
expect(res.body.message).toBeDefined() expect(res.body.message).toBeDefined()
const metadata = await getMetadata(MetadataTypes.AUTOMATION_TEST_INPUT) const metadata = await getMetadata(MetadataType.AUTOMATION_TEST_INPUT)
expect(metadata.test).toBeUndefined() expect(metadata.test).toBeUndefined()
}) })
}) })

View File

@ -94,7 +94,7 @@ export async function run({
}) })
try { try {
await queryController.executeV2(ctx, { isAutomation: true }) await queryController.executeV2AsAutomation(ctx)
const { data, ...rest } = ctx.body const { data, ...rest } = ctx.body
return { return {

View File

@ -2,7 +2,6 @@ import { Thread, ThreadType } from "../threads"
import { definitions } from "./triggerInfo" import { definitions } from "./triggerInfo"
import { automationQueue } from "./bullboard" import { automationQueue } from "./bullboard"
import { updateEntityMetadata } from "../utilities" import { updateEntityMetadata } from "../utilities"
import { MetadataTypes } from "../constants"
import { context, db as dbCore, utils } from "@budibase/backend-core" import { context, db as dbCore, utils } from "@budibase/backend-core"
import { getAutomationMetadataParams } from "../db/utils" import { getAutomationMetadataParams } from "../db/utils"
import { cloneDeep } from "lodash/fp" import { cloneDeep } from "lodash/fp"
@ -14,6 +13,7 @@ import {
AutomationStepDefinition, AutomationStepDefinition,
AutomationTriggerDefinition, AutomationTriggerDefinition,
AutomationTriggerStepId, AutomationTriggerStepId,
MetadataType,
} from "@budibase/types" } from "@budibase/types"
import { automationsEnabled } from "../features" import { automationsEnabled } from "../features"
import { helpers, REBOOT_CRON } from "@budibase/shared-core" import { helpers, REBOOT_CRON } from "@budibase/shared-core"
@ -107,7 +107,7 @@ export async function updateTestHistory(
history: any history: any
) { ) {
return updateEntityMetadata( return updateEntityMetadata(
MetadataTypes.AUTOMATION_TEST_HISTORY, MetadataType.AUTOMATION_TEST_HISTORY,
automation._id, automation._id,
(metadata: any) => { (metadata: any) => {
if (metadata && Array.isArray(metadata.history)) { if (metadata && Array.isArray(metadata.history)) {

View File

@ -124,11 +124,6 @@ export enum BaseQueryVerbs {
DELETE = "delete", DELETE = "delete",
} }
export enum MetadataTypes {
AUTOMATION_TEST_INPUT = "automationTestInput",
AUTOMATION_TEST_HISTORY = "automationTestHistory",
}
export enum InvalidColumns { export enum InvalidColumns {
ID = "_id", ID = "_id",
REV = "_rev", REV = "_rev",

View File

@ -3,10 +3,10 @@ import {
RequiredKeys, RequiredKeys,
Webhook, Webhook,
WebhookActionType, WebhookActionType,
MetadataType,
} from "@budibase/types" } from "@budibase/types"
import { generateAutomationID, getAutomationParams } from "../../../db/utils" import { generateAutomationID, getAutomationParams } from "../../../db/utils"
import { deleteEntityMetadata } from "../../../utilities" import { deleteEntityMetadata } from "../../../utilities"
import { MetadataTypes } from "../../../constants"
import { import {
context, context,
events, events,
@ -161,7 +161,7 @@ export async function update(automation: Automation) {
if (oldAutoTrigger && oldAutoTrigger.id !== newAutoTrigger?.id) { if (oldAutoTrigger && oldAutoTrigger.id !== newAutoTrigger?.id) {
await events.automation.triggerUpdated(automation) await events.automation.triggerUpdated(automation)
await deleteEntityMetadata( await deleteEntityMetadata(
MetadataTypes.AUTOMATION_TEST_INPUT, MetadataType.AUTOMATION_TEST_INPUT,
automation._id! automation._id!
) )
} }
@ -183,11 +183,8 @@ export async function remove(automationId: string, rev: string) {
}) })
// delete metadata first // delete metadata first
await deleteEntityMetadata(MetadataTypes.AUTOMATION_TEST_INPUT, automationId) await deleteEntityMetadata(MetadataType.AUTOMATION_TEST_INPUT, automationId)
await deleteEntityMetadata( await deleteEntityMetadata(MetadataType.AUTOMATION_TEST_HISTORY, automationId)
MetadataTypes.AUTOMATION_TEST_HISTORY,
automationId
)
const result = await db.remove(automationId, rev) const result = await db.remove(automationId, rev)

View File

@ -1,4 +1,4 @@
import { FileType, Plugin, PluginSource, PluginType } from "@budibase/types" import { KoaFile, Plugin, PluginSource, PluginType } from "@budibase/types"
import { import {
db as dbCore, db as dbCore,
objectStore, objectStore,
@ -10,7 +10,7 @@ import env from "../../environment"
import { clientAppSocket } from "../../websockets" import { clientAppSocket } from "../../websockets"
import { sdk as pro } from "@budibase/pro" import { sdk as pro } from "@budibase/pro"
export async function fetch(type?: PluginType) { export async function fetch(type?: PluginType): Promise<Plugin[]> {
const db = tenancy.getGlobalDB() const db = tenancy.getGlobalDB()
const response = await db.allDocs( const response = await db.allDocs(
dbCore.getPluginParams(null, { dbCore.getPluginParams(null, {
@ -26,7 +26,7 @@ export async function fetch(type?: PluginType) {
} }
} }
export async function processUploaded(plugin: FileType, source?: PluginSource) { export async function processUploaded(plugin: KoaFile, source?: PluginSource) {
const { metadata, directory } = await fileUpload(plugin) const { metadata, directory } = await fileUpload(plugin)
pluginCore.validate(metadata?.schema) pluginCore.validate(metadata?.schema)

View File

@ -1,7 +1,7 @@
import { import {
Query, Query,
ExecuteQueryRequest, ExecuteQueryRequest,
ExecuteQueryResponse, ExecuteV2QueryResponse,
PreviewQueryRequest, PreviewQueryRequest,
PreviewQueryResponse, PreviewQueryResponse,
} from "@budibase/types" } from "@budibase/types"
@ -17,8 +17,8 @@ export class QueryAPI extends TestAPI {
queryId: string, queryId: string,
body?: ExecuteQueryRequest, body?: ExecuteQueryRequest,
expectations?: Expectations expectations?: Expectations
): Promise<ExecuteQueryResponse> => { ): Promise<ExecuteV2QueryResponse> => {
return await this._post<ExecuteQueryResponse>( return await this._post<ExecuteV2QueryResponse>(
`/api/v2/queries/${queryId}`, `/api/v2/queries/${queryId}`,
{ {
body, body,

View File

@ -2,7 +2,7 @@ import {
PatchRowRequest, PatchRowRequest,
SaveRowRequest, SaveRowRequest,
Row, Row,
ValidateResponse, ValidateRowResponse,
ExportRowsRequest, ExportRowsRequest,
BulkImportRequest, BulkImportRequest,
BulkImportResponse, BulkImportResponse,
@ -51,8 +51,8 @@ export class RowAPI extends TestAPI {
sourceId: string, sourceId: string,
row: SaveRowRequest, row: SaveRowRequest,
expectations?: Expectations expectations?: Expectations
): Promise<ValidateResponse> => { ): Promise<ValidateRowResponse> => {
return await this._post<ValidateResponse>( return await this._post<ValidateRowResponse>(
`/api/${sourceId}/rows/validate`, `/api/${sourceId}/rows/validate`,
{ {
body: row, body: row,

View File

@ -88,7 +88,7 @@ export async function saveEntityMetadata(
type: string, type: string,
entityId: string, entityId: string,
metadata: Document metadata: Document
) { ): Promise<Document> {
return updateEntityMetadata(type, entityId, () => { return updateEntityMetadata(type, entityId, () => {
return metadata return metadata
}) })

View File

@ -1,24 +1,19 @@
import { createRoutingView } from "../../db/views/staticViews" import { createRoutingView } from "../../db/views/staticViews"
import { ViewName, getQueryIndex, UNICODE_MAX } from "../../db/utils" import { ViewName, getQueryIndex, UNICODE_MAX } from "../../db/utils"
import { context } from "@budibase/backend-core" import { context } from "@budibase/backend-core"
import { ScreenRouting, Document } from "@budibase/types" import { ScreenRoutesViewOutput } from "@budibase/types"
interface ScreenRoutesView extends Document { export async function getRoutingInfo(): Promise<ScreenRoutesViewOutput[]> {
id: string
routing: ScreenRouting
}
export async function getRoutingInfo(): Promise<ScreenRoutesView[]> {
const db = context.getAppDB() const db = context.getAppDB()
try { try {
const allRouting = await db.query<ScreenRoutesView>( const allRouting = await db.query<ScreenRoutesViewOutput>(
getQueryIndex(ViewName.ROUTING), getQueryIndex(ViewName.ROUTING),
{ {
startkey: "", startkey: "",
endkey: UNICODE_MAX, endkey: UNICODE_MAX,
} }
) )
return allRouting.rows.map(row => row.value as ScreenRoutesView) return allRouting.rows.map(row => row.value)
} catch (err: any) { } catch (err: any) {
// check if the view doesn't exist, it should for all new instances // check if the view doesn't exist, it should for all new instances
/* istanbul ignore next */ /* istanbul ignore next */

View File

@ -1,6 +1,5 @@
export * from "./backup" export * from "./backup"
export * from "./datasource" export * from "./datasource"
export * from "./row"
export * from "./view" export * from "./view"
export * from "./rows" export * from "./rows"
export * from "./table" export * from "./table"
@ -10,3 +9,7 @@ export * from "./user"
export * from "./rowAction" export * from "./rowAction"
export * from "./automation" export * from "./automation"
export * from "./component" export * from "./component"
export * from "./integration"
export * from "./metadata"
export * from "./query"
export * from "./screen"

View File

@ -0,0 +1,8 @@
import { Integration, SourceName } from "../../../sdk"
export type FetchIntegrationsResponse = Record<
SourceName,
Integration | undefined
>
export type FindIntegrationResponse = Integration

View File

@ -0,0 +1,14 @@
import { MetadataType, Document } from "../../../documents"
export interface GetMetadataTypesResponse {
types: typeof MetadataType
}
export interface SaveMetadataRequest extends Document {}
export interface SaveMetadataResponse extends Document {}
export interface DeleteMetadataResponse {
message: string
}
export interface FindMetadataResponse extends Document {}

View File

@ -1,4 +1,8 @@
import { PermissionLevel } from "../../../sdk" import { BuiltinPermission, PermissionLevel } from "../../../sdk"
export type FetchBuiltinPermissionsRequest = BuiltinPermission[]
export type FetchPermissionLevelsRequest = string[]
export interface FetchResourcePermissionInfoResponse { export interface FetchResourcePermissionInfoResponse {
[key: string]: Record<string, string> [key: string]: Record<string, string>

View File

@ -0,0 +1,47 @@
import {
Datasource,
Query,
QueryPreview,
QuerySchema,
} from "../../../documents"
export type FetchQueriesResponse = Query[]
export interface SaveQueryRequest extends Query {}
export interface SaveQueryResponse extends Query {}
export interface ImportRestQueryRequest {
datasourceId: string
data: string
datasource: Datasource
}
export interface ImportRestQueryResponse {
errorQueries: Query[]
queries: Query[]
datasourceId: string
}
export interface FindQueryResponse extends Query {}
export interface PreviewQueryRequest extends QueryPreview {}
export interface PreviewQueryResponse {
rows: any[]
nestedSchemaFields: { [key: string]: { [key: string]: string | QuerySchema } }
schema: { [key: string]: string | QuerySchema }
info: any
extra: any
}
export interface ExecuteQueryRequest {
parameters?: Record<string, string>
pagination?: any
}
export type ExecuteV1QueryResponse = Record<string, any>[]
export interface ExecuteV2QueryResponse {
data: Record<string, any>[]
}
export interface DeleteQueryResponse {
message: string
}

View File

@ -1,18 +0,0 @@
import { Row } from "../../../documents/app/row"
export interface GetRowResponse extends Row {}
export interface DeleteRows {
rows: (Row | string)[]
}
export interface DeleteRow {
_id: string
}
export type DeleteRowRequest = DeleteRows | DeleteRow
export interface ValidateResponse {
valid: boolean
errors: Record<string, any>
}

View File

@ -1,11 +1,17 @@
import { SearchFilters } from "../../../../sdk" import { SearchFilters } from "../../../../sdk"
import { Row } from "../../../../documents" import { Row } from "../../../../documents"
import { SortOrder } from "../../../../api/web/pagination" import { SortOrder } from "../../pagination"
import { ReadStream } from "fs" import { ReadStream } from "fs"
import stream from "node:stream"
export * from "./search" export * from "./search"
export interface FetchEnrichedRowResponse extends Row {}
export type FetchRowsResponse = Row[]
export interface SaveRowRequest extends Row {} export interface SaveRowRequest extends Row {}
export interface SaveRowResponse extends Row {}
export interface PatchRowRequest extends Row { export interface PatchRowRequest extends Row {
_id: string _id: string
@ -26,3 +32,23 @@ export interface ExportRowsRequest {
} }
export type ExportRowsResponse = ReadStream export type ExportRowsResponse = ReadStream
export type DownloadAttachmentResponse = stream.PassThrough | stream.Readable
export interface FindRowResponse extends Row {}
export interface DeleteRows {
rows: (Row | string)[]
}
export interface DeleteRow {
_id: string
}
export type DeleteRowRequest = DeleteRows | DeleteRow
export interface ValidateRowRequest extends Row {}
export interface ValidateRowResponse {
valid: boolean
errors: Record<string, any>
}

View File

@ -0,0 +1,8 @@
import { ScreenRoutingJson } from "../../../documents"
export interface FetchScreenRoutingResponse {
routes: ScreenRoutingJson
}
export interface FetchClientScreenRoutingResponse
extends FetchScreenRoutingResponse {}

View File

@ -4,3 +4,4 @@ export * from "./events"
export * from "./configs" export * from "./configs"
export * from "./scim" export * from "./scim"
export * from "./license" export * from "./license"
export * from "./oldMigration"

View File

@ -0,0 +1,9 @@
import { Migration, MigrationOptions } from "../../../sdk"
export interface RunOldMigrationRequest extends MigrationOptions {}
export type FetchOldMigrationResponse = Migration[]
export interface GetOldMigrationStatus {
migrated: boolean
}

View File

@ -13,7 +13,6 @@ export * from "./searchFilter"
export * from "./cookies" export * from "./cookies"
export * from "./automation" export * from "./automation"
export * from "./layout" export * from "./layout"
export * from "./query"
export * from "./role" export * from "./role"
export * from "./plugins" export * from "./plugins"
export * from "./apikeys" export * from "./apikeys"

View File

@ -3,3 +3,7 @@ import { Layout } from "../../documents"
export interface SaveLayoutRequest extends Layout {} export interface SaveLayoutRequest extends Layout {}
export interface SaveLayoutResponse extends Layout {} export interface SaveLayoutResponse extends Layout {}
export interface DeleteLayoutResponse {
message: string
}

View File

@ -1,4 +1,10 @@
import { PluginSource } from "../../documents" import { PluginSource, Plugin } from "../../documents"
export interface UploadPluginRequest {}
export interface UploadPluginResponse {
message: string
plugins: Plugin[]
}
export interface CreatePluginRequest { export interface CreatePluginRequest {
source: PluginSource source: PluginSource
@ -10,3 +16,9 @@ export interface CreatePluginRequest {
export interface CreatePluginResponse { export interface CreatePluginResponse {
plugin: any plugin: any
} }
export type FetchPluginResponse = Plugin[]
export interface DeletePluginResponse {
message: string
}

View File

@ -1,20 +0,0 @@
import { QueryPreview, QuerySchema } from "../../documents"
export interface PreviewQueryRequest extends QueryPreview {}
export interface PreviewQueryResponse {
rows: any[]
nestedSchemaFields: { [key: string]: { [key: string]: string | QuerySchema } }
schema: { [key: string]: string | QuerySchema }
info: any
extra: any
}
export interface ExecuteQueryRequest {
parameters?: Record<string, string>
pagination?: any
}
export interface ExecuteQueryResponse {
data: Record<string, any>[]
}

View File

@ -18,7 +18,7 @@ export interface FindRoleResponse extends Role {}
export type FetchRolesResponse = Role[] export type FetchRolesResponse = Role[]
export interface DestroyRoleResponse { export interface DeleteRoleResponse {
message: string message: string
} }

View File

@ -1,2 +1,3 @@
export * from "./environment" export * from "./environment"
export * from "./status" export * from "./status"
export * from "./ops"

View File

@ -0,0 +1,8 @@
export interface LogOpsRequest {
message: string
data?: any
}
export interface ErrorOpsRequest {
message: string
}

View File

@ -19,3 +19,4 @@ export * from "./snippet"
export * from "./rowAction" export * from "./rowAction"
export * from "./theme" export * from "./theme"
export * from "./deployment" export * from "./deployment"
export * from "./metadata"

View File

@ -0,0 +1,4 @@
export enum MetadataType {
AUTOMATION_TEST_INPUT = "automationTestInput",
AUTOMATION_TEST_HISTORY = "automationTestHistory",
}

View File

@ -24,3 +24,20 @@ export interface Screen extends Document {
name?: string name?: string
pluginAdded?: boolean pluginAdded?: boolean
} }
export interface ScreenRoutesViewOutput extends Document {
id: string
routing: ScreenRouting
}
export type ScreenRoutingJson = Record<
string,
{
subpaths: Record<
string,
{
screens: Record<string, string>
}
>
}
>

View File

@ -12,9 +12,9 @@ export enum PluginSource {
URL = "URL", URL = "URL",
FILE = "File Upload", FILE = "File Upload",
} }
export interface FileType { export interface KoaFile {
path: string path: string | null
name: string name: string | null
} }
export interface Plugin extends Document { export interface Plugin extends Document {

View File

@ -36,3 +36,22 @@ export enum PermissionSource {
INHERITED = "INHERITED", INHERITED = "INHERITED",
BASE = "BASE", BASE = "BASE",
} }
export interface Permission {
type: PermissionType
level: PermissionLevel
}
export interface BuiltinPermission {
_id: BuiltinPermissionID
name: string
permissions: Permission[]
}
export type BuiltinPermissions = {
[key in keyof typeof BuiltinPermissionID]: {
_id: (typeof BuiltinPermissionID)[key]
name: string
permissions: Permission[]
}
}