Merge branch 'master' into 3.0-metrics

This commit is contained in:
Martin McKeaveney 2024-12-09 15:50:48 +00:00 committed by GitHub
commit 690f7f0aa5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
55 changed files with 504 additions and 260 deletions

View File

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

View File

@ -3,18 +3,10 @@ import { Duration } from "../utils"
import env from "../environment" import env from "../environment"
import { getTenantId } from "../context" import { getTenantId } from "../context"
import * as redis from "../redis/init" import * as redis from "../redis/init"
import { Invite, InviteWithCode } from "@budibase/types"
const TTL_SECONDS = Duration.fromDays(7).toSeconds() const TTL_SECONDS = Duration.fromDays(7).toSeconds()
interface Invite {
email: string
info: any
}
interface InviteWithCode extends Invite {
code: string
}
/** /**
* Given an invite code and invite body, allow the update an existing/valid invite in redis * Given an invite code and invite body, allow the update an existing/valid invite in redis
* @param code The invite code for an invite in redis * @param code The invite code for an invite in redis

View File

@ -46,7 +46,7 @@
} }
} else { } else {
// Leave the core data as it is // Leave the core data as it is
return testData return cloneDeep(testData)
} }
} }
@ -63,7 +63,10 @@
return true return true
} }
$: testData = testData || parseTestData($selectedAutomation.data.testData) $: currentTestData = $selectedAutomation.data.testData
// Can be updated locally to avoid race condition when testing
$: testData = parseTestData(currentTestData)
$: { $: {
// clone the trigger so we're not mutating the reference // clone the trigger so we're not mutating the reference
@ -85,7 +88,7 @@
required => testData?.[required] || required !== "row" required => testData?.[required] || required !== "row"
) )
function parseTestJSON(e) { async function parseTestJSON(e) {
let jsonUpdate let jsonUpdate
try { try {
@ -105,7 +108,9 @@
} }
} }
automationStore.actions.addTestDataToAutomation(jsonUpdate) const updatedAuto =
automationStore.actions.addTestDataToAutomation(jsonUpdate)
await automationStore.actions.save(updatedAuto)
} }
const testAutomation = async () => { const testAutomation = async () => {
@ -150,10 +155,14 @@
{#if selectedValues} {#if selectedValues}
<div class="tab-content-padding"> <div class="tab-content-padding">
<AutomationBlockSetup <AutomationBlockSetup
bind:testData
{schemaProperties} {schemaProperties}
isTestModal isTestModal
{testData}
block={trigger} block={trigger}
on:update={e => {
const { testData: updatedTestData } = e.detail
testData = updatedTestData
}}
/> />
</div> </div>
{/if} {/if}
@ -162,7 +171,7 @@
<TextArea <TextArea
value={JSON.stringify($selectedAutomation.data.testData, null, 2)} value={JSON.stringify($selectedAutomation.data.testData, null, 2)}
error={failedParse} error={failedParse}
on:change={e => parseTestJSON(e)} on:change={async e => await parseTestJSON(e)}
/> />
</div> </div>
{/if} {/if}

View File

@ -48,7 +48,7 @@
import { QueryUtils, Utils, search, memo } from "@budibase/frontend-core" import { QueryUtils, Utils, search, memo } from "@budibase/frontend-core"
import { getSchemaForDatasourcePlus } from "dataBinding" import { getSchemaForDatasourcePlus } from "dataBinding"
import { TriggerStepID, ActionStepID } from "constants/backend/automations" import { TriggerStepID, ActionStepID } from "constants/backend/automations"
import { onMount } from "svelte" import { onMount, createEventDispatcher } from "svelte"
import { writable } from "svelte/store" import { writable } from "svelte/store"
import { cloneDeep } from "lodash/fp" import { cloneDeep } from "lodash/fp"
import { import {
@ -67,6 +67,8 @@
export let isTestModal = false export let isTestModal = false
export let bindings = [] export let bindings = []
const dispatch = createEventDispatcher()
// Stop unnecessary rendering // Stop unnecessary rendering
const memoBlock = memo(block) const memoBlock = memo(block)
@ -503,15 +505,7 @@
row: { "Active": true, "Order Id" : 14, ... } row: { "Active": true, "Order Id" : 14, ... }
}) })
*/ */
const onChange = async update => { const onChange = Utils.sequential(async update => {
if (isTestModal) {
testData = update
}
updateAutomation(update)
}
const updateAutomation = Utils.sequential(async update => {
const request = cloneDeep(update) const request = cloneDeep(update)
// Process app trigger updates // Process app trigger updates
if (isTrigger && !isTestModal) { if (isTrigger && !isTestModal) {
@ -540,7 +534,9 @@
} }
try { try {
if (isTestModal) { if (isTestModal) {
let newTestData = { schema } // Be sure to merge in the testData prop data, as it can contain custom
// default data
let newTestData = { schema, ...testData }
// Special case for webhook, as it requires a body, but the schema already brings back the body's contents // Special case for webhook, as it requires a body, but the schema already brings back the body's contents
if (stepId === TriggerStepID.WEBHOOK) { if (stepId === TriggerStepID.WEBHOOK) {
@ -557,7 +553,13 @@
...request, ...request,
} }
await automationStore.actions.addTestDataToAutomation(newTestData) const updatedAuto =
automationStore.actions.addTestDataToAutomation(newTestData)
// Ensure the test request has the latest info.
dispatch("update", updatedAuto)
await automationStore.actions.save(updatedAuto)
} else { } else {
const data = { schema, ...request } const data = { schema, ...request }
await automationStore.actions.updateBlockInputs(block, data) await automationStore.actions.updateBlockInputs(block, data)

View File

@ -56,7 +56,10 @@
.map(table => format.table(table, $datasources.list)) .map(table => format.table(table, $datasources.list))
.sort((a, b) => { .sort((a, b) => {
// sort tables alphabetically, grouped by datasource // sort tables alphabetically, grouped by datasource
const dsComparison = a.datasourceName.localeCompare(b.datasourceName) const dsA = a.datasourceName ?? ""
const dsB = b.datasourceName ?? ""
const dsComparison = dsA.localeCompare(dsB)
if (dsComparison !== 0) { if (dsComparison !== 0) {
return dsComparison return dsComparison
} }

View File

@ -880,13 +880,13 @@ const automationActions = store => ({
appId, appId,
}) })
}, },
addTestDataToAutomation: async data => { addTestDataToAutomation: data => {
let newAutomation = cloneDeep(get(selectedAutomation).data) let newAutomation = cloneDeep(get(selectedAutomation).data)
newAutomation.testData = { newAutomation.testData = {
...newAutomation.testData, ...newAutomation.testData,
...data, ...data,
} }
await store.actions.save(newAutomation) return newAutomation
}, },
constructBlock(type, stepId, blockDefinition) { constructBlock(type, stepId, blockDefinition) {
let newName let newName

View File

@ -3,7 +3,7 @@ import { downloadTemplate as dlTemplate } from "../../utilities/fileSystem"
import env from "../../environment" import env from "../../environment"
import { import {
DownloadTemplateResponse, DownloadTemplateResponse,
FetchTemplateResponse, FetchGlobalTemplateResponse,
UserCtx, UserCtx,
} from "@budibase/types" } from "@budibase/types"
@ -11,7 +11,7 @@ import {
const DEFAULT_TEMPLATES_BUCKET = const DEFAULT_TEMPLATES_BUCKET =
"prod-budi-templates.s3-eu-west-1.amazonaws.com" "prod-budi-templates.s3-eu-west-1.amazonaws.com"
export async function fetch(ctx: UserCtx<void, FetchTemplateResponse>) { export async function fetch(ctx: UserCtx<void, FetchGlobalTemplateResponse>) {
let type = env.TEMPLATE_REPOSITORY let type = env.TEMPLATE_REPOSITORY
let response, let response,
error = false error = false

View File

@ -3,14 +3,28 @@ export interface LoginRequest {
password: string password: string
} }
export interface LogoutResponse {
message: string
}
export interface SetInitInfoRequest extends Record<string, any> {}
export interface GetInitInfoResponse extends Record<string, any> {}
export interface PasswordResetRequest { export interface PasswordResetRequest {
email: string email: string
} }
export interface PasswordResetResponse {
message: string
}
export interface PasswordResetUpdateRequest { export interface PasswordResetUpdateRequest {
resetCode: string resetCode: string
password: string password: string
} }
export interface PasswordResetUpdateResponse {
message: string
}
export interface UpdateSelfRequest { export interface UpdateSelfRequest {
firstName?: string firstName?: string

View File

@ -1,4 +1,10 @@
import { SettingsConfig, SettingsInnerConfig } from "../../../documents" import {
Config,
ConfigType,
SettingsBrandingConfig,
SettingsConfig,
SettingsInnerConfig,
} from "../../../documents"
/** /**
* Settings that aren't stored in the database - enriched at runtime. * Settings that aren't stored in the database - enriched at runtime.
@ -22,3 +28,34 @@ export interface PublicOIDCConfig {
} }
export type GetPublicOIDCConfigResponse = PublicOIDCConfig[] export type GetPublicOIDCConfigResponse = PublicOIDCConfig[]
export interface SaveConfigRequest extends Config {}
export interface SaveConfigResponse {
type: ConfigType
_id: string
_rev: string
}
export interface DeleteConfigResponse {
message: string
}
interface ChecklistItem {
checked: boolean
label: string
link: string
}
export interface ConfigChecklistResponse {
apps: ChecklistItem
smtp: ChecklistItem
adminUser: ChecklistItem
sso: ChecklistItem
branding: SettingsBrandingConfig
}
export type FindConfigResponse = Config | {}
export interface UploadConfigFileResponse {
message: string
url: string
}

View File

@ -0,0 +1,28 @@
import { EmailAttachment, EmailInvite } from "../../../documents"
export enum EmailTemplatePurpose {
CORE = "core",
BASE = "base",
PASSWORD_RECOVERY = "password_recovery",
INVITATION = "invitation",
WELCOME = "welcome",
CUSTOM = "custom",
}
export interface SendEmailRequest {
workspaceId?: string
email: string
userId: string
purpose: EmailTemplatePurpose
contents?: string
from?: string
subject: string
cc?: boolean
bcc?: boolean
automation?: boolean
invite?: EmailInvite
attachments?: EmailAttachment[]
}
export interface SendEmailResponse extends Record<string, any> {
message: string
}

View File

@ -5,3 +5,7 @@ export * from "./configs"
export * from "./scim" export * from "./scim"
export * from "./license" export * from "./license"
export * from "./oldMigration" export * from "./oldMigration"
export * from "./email"
export * from "./role"
export * from "./self"
export * from "./template"

View File

@ -1,5 +1,7 @@
// LICENSE KEY // LICENSE KEY
import { QuotaUsage } from "../../../documents"
export interface ActivateLicenseKeyRequest { export interface ActivateLicenseKeyRequest {
licenseKey: string licenseKey: string
} }
@ -23,3 +25,5 @@ export interface GetOfflineLicenseTokenResponse {
export interface GetOfflineIdentifierResponse { export interface GetOfflineIdentifierResponse {
identifierBase64: string identifierBase64: string
} }
export interface GetQuotaUsageResponse extends QuotaUsage {}

View File

@ -0,0 +1,17 @@
import { Role } from "../../../documents"
interface GlobalRoleResponse {
roles: Role[]
name: string
version: string
url?: string
}
export interface FetchGlobalRolesResponse
extends Record<string, GlobalRoleResponse> {}
export interface FindGlobalRoleResponse extends GlobalRoleResponse {}
export interface RemoveAppRoleResponse {
message: string
}

View File

@ -0,0 +1,12 @@
import { DevInfo, User } from "../../../documents"
export interface GenerateAPIKeyRequest {
userId: string
}
export interface GenerateAPIKeyResponse extends DevInfo {}
export interface FetchAPIKeyResponse extends DevInfo {}
export interface GetGlobalSelfResponse extends User {
flags?: Record<string, string>
}

View File

@ -0,0 +1,30 @@
import { Template } from "../../../documents"
export interface GlobalTemplateDefinition {
name: string
description: string
category: string
}
export interface GlobalTemplateBinding {
name: string
description: string
}
export interface FetchGlobalTemplateDefinitionResponse {
info: Record<string, GlobalTemplateDefinition>
bindings: Record<string, GlobalTemplateBinding[]>
}
export interface SaveGlobalTemplateRequest extends Template {}
export interface SaveGlobalTemplateResponse extends Template {}
export type FetchGlobalTemplateResponse = Template[]
export type FetchGlobalTemplateByTypeResponse = Template[]
export type FetchGlobalTemplateByOwnerIDResponse = Template[]
export interface FindGlobalTemplateResponse extends Template {}
export interface DeleteGlobalTemplateResponse {
message: string
}

View File

@ -0,0 +1,4 @@
import { Account, AccountMetadata } from "../../../documents"
export interface SaveAccountRequest extends Account {}
export interface SaveAccountResponse extends AccountMetadata {}

View File

@ -1,8 +1,11 @@
export interface GetEnvironmentResponse { export interface GetEnvironmentResponse {
multiTenancy: boolean multiTenancy: boolean
offlineMode: boolean
cloud: boolean cloud: boolean
accountPortalUrl: string accountPortalUrl?: string
baseUrl: string
disableAccountPortal: boolean disableAccountPortal: boolean
baseUrl?: string
isDev: boolean isDev: boolean
maintenance: { type: string }[]
passwordMinLength?: string
} }

View File

@ -1,3 +1,8 @@
export * from "./environment" export * from "./environment"
export * from "./status" export * from "./status"
export * from "./ops" export * from "./ops"
export * from "./account"
export * from "./log"
export * from "./migration"
export * from "./restore"
export * from "./tenant"

View File

@ -0,0 +1 @@
export type GetLogResponse = Buffer

View File

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

View File

@ -0,0 +1,3 @@
export interface SystemRestoreResponse {
message: string
}

View File

@ -0,0 +1,3 @@
export interface GetTenantInfoResponse {
exists: boolean
}

View File

@ -1,6 +1,15 @@
import { User } from "../../documents" import { AccountMetadata, PlatformUser, User } from "../../documents"
import { SearchFilters } from "../../sdk" import { SearchFilters } from "../../sdk"
export interface Invite {
email: string
info: any
}
export interface InviteWithCode extends Invite {
code: string
}
export interface SaveUserResponse { export interface SaveUserResponse {
_id: string _id: string
_rev: string _rev: string
@ -47,6 +56,11 @@ export interface InviteUserRequest {
email: string email: string
userInfo: any userInfo: any
} }
export interface InviteUserResponse {
message: string
successful: { email: string }[]
unsuccessful: { email: string; reason: string }[]
}
export interface DeleteInviteUserRequest { export interface DeleteInviteUserRequest {
code: string code: string
@ -54,6 +68,9 @@ export interface DeleteInviteUserRequest {
export type InviteUsersRequest = InviteUserRequest[] export type InviteUsersRequest = InviteUserRequest[]
export type DeleteInviteUsersRequest = DeleteInviteUserRequest[] export type DeleteInviteUsersRequest = DeleteInviteUserRequest[]
export interface DeleteInviteUsersResponse {
message: string
}
export interface InviteUsersResponse { export interface InviteUsersResponse {
successful: { email: string }[] successful: { email: string }[]
@ -68,6 +85,17 @@ export interface SearchUsersRequest {
limit?: number limit?: number
paginate?: boolean paginate?: boolean
} }
export interface SearchUsersResponse {
data: User[]
hasNextPage?: boolean
nextPage?: string
}
export type FetchUsersResponse = User[]
export interface FindUserResponse extends User {}
export type LookupTenantUserResponse = PlatformUser
export interface CreateAdminUserRequest { export interface CreateAdminUserRequest {
email: string email: string
@ -106,3 +134,28 @@ export interface AcceptUserInviteResponse {
export interface SyncUserRequest { export interface SyncUserRequest {
previousUser?: User previousUser?: User
} }
export interface DeleteUserResponse {
message: string
}
export interface CountUserResponse {
userCount: number
}
export interface CheckInviteResponse {
email: string
}
export type GetUserInvitesResponse = InviteWithCode[]
export interface UpdateInviteRequest extends Omit<Invite, "email"> {
email?: string
builder?: {
apps: string[]
}
apps: string[]
}
export interface UpdateInviteResponse extends Invite {}
export type LookupAccountHolderResponse = AccountMetadata | null

View File

@ -0,0 +1,6 @@
import { Document } from "../document"
export interface DevInfo extends Document {
userId: string
apiKey?: string
}

View File

@ -8,3 +8,4 @@ export * from "./templates"
export * from "./environmentVariables" export * from "./environmentVariables"
export * from "./auditLogs" export * from "./auditLogs"
export * from "./apikeys" export * from "./apikeys"
export * from "./devInfo"

View File

@ -16,8 +16,15 @@ import {
PasswordResetUpdateRequest, PasswordResetUpdateRequest,
GoogleInnerConfig, GoogleInnerConfig,
DatasourceAuthCookie, DatasourceAuthCookie,
LogoutResponse,
UserCtx,
SetInitInfoRequest,
GetInitInfoResponse,
PasswordResetResponse,
PasswordResetUpdateResponse,
} from "@budibase/types" } from "@budibase/types"
import env from "../../../environment" import env from "../../../environment"
import { Next } from "koa"
import * as authSdk from "../../../sdk/auth" import * as authSdk from "../../../sdk/auth"
import * as userSdk from "../../../sdk/users" import * as userSdk from "../../../sdk/users"
@ -52,7 +59,7 @@ async function passportCallback(
ctx.set(Header.TOKEN, token) ctx.set(Header.TOKEN, token)
} }
export const login = async (ctx: Ctx<LoginRequest>, next: any) => { export const login = async (ctx: Ctx<LoginRequest, void>, next: Next) => {
const email = ctx.request.body.username const email = ctx.request.body.username
const user = await userSdk.db.getUserByEmail(email) const user = await userSdk.db.getUserByEmail(email)
@ -72,7 +79,7 @@ export const login = async (ctx: Ctx<LoginRequest>, next: any) => {
)(ctx, next) )(ctx, next)
} }
export const logout = async (ctx: any) => { export const logout = async (ctx: UserCtx<void, LogoutResponse>) => {
if (ctx.user && ctx.user._id) { if (ctx.user && ctx.user._id) {
await authSdk.logout({ ctx, userId: ctx.user._id }) await authSdk.logout({ ctx, userId: ctx.user._id })
} }
@ -81,13 +88,13 @@ export const logout = async (ctx: any) => {
// INIT // INIT
export const setInitInfo = (ctx: any) => { export const setInitInfo = (ctx: UserCtx<SetInitInfoRequest, void>) => {
const initInfo = ctx.request.body const initInfo = ctx.request.body
setCookie(ctx, initInfo, Cookie.Init) setCookie(ctx, initInfo, Cookie.Init)
ctx.status = 200 ctx.status = 200
} }
export const getInitInfo = (ctx: any) => { export const getInitInfo = (ctx: UserCtx<void, GetInitInfoResponse>) => {
try { try {
ctx.body = getCookie(ctx, Cookie.Init) || {} ctx.body = getCookie(ctx, Cookie.Init) || {}
} catch (err) { } catch (err) {
@ -101,7 +108,9 @@ export const getInitInfo = (ctx: any) => {
/** /**
* Reset the user password, used as part of a forgotten password flow. * Reset the user password, used as part of a forgotten password flow.
*/ */
export const reset = async (ctx: Ctx<PasswordResetRequest>) => { export const reset = async (
ctx: Ctx<PasswordResetRequest, PasswordResetResponse>
) => {
const { email } = ctx.request.body const { email } = ctx.request.body
await authSdk.reset(email) await authSdk.reset(email)
@ -114,7 +123,9 @@ export const reset = async (ctx: Ctx<PasswordResetRequest>) => {
/** /**
* Perform the user password update if the provided reset code is valid. * Perform the user password update if the provided reset code is valid.
*/ */
export const resetUpdate = async (ctx: Ctx<PasswordResetUpdateRequest>) => { export const resetUpdate = async (
ctx: Ctx<PasswordResetUpdateRequest, PasswordResetUpdateResponse>
) => {
const { resetCode, password } = ctx.request.body const { resetCode, password } = ctx.request.body
try { try {
await authSdk.resetUpdate(resetCode, password) await authSdk.resetUpdate(resetCode, password)
@ -130,7 +141,10 @@ export const resetUpdate = async (ctx: Ctx<PasswordResetUpdateRequest>) => {
// DATASOURCE // DATASOURCE
export const datasourcePreAuth = async (ctx: any, next: any) => { export const datasourcePreAuth = async (
ctx: UserCtx<void, void>,
next: Next
) => {
const provider = ctx.params.provider const provider = ctx.params.provider
const { middleware } = require(`@budibase/backend-core`) const { middleware } = require(`@budibase/backend-core`)
const handler = middleware.datasource[provider] const handler = middleware.datasource[provider]
@ -147,7 +161,7 @@ export const datasourcePreAuth = async (ctx: any, next: any) => {
return handler.preAuth(passport, ctx, next) return handler.preAuth(passport, ctx, next)
} }
export const datasourceAuth = async (ctx: any, next: any) => { export const datasourceAuth = async (ctx: UserCtx<void, void>, next: Next) => {
const authStateCookie = getCookie<DatasourceAuthCookie>( const authStateCookie = getCookie<DatasourceAuthCookie>(
ctx, ctx,
Cookie.DatasourceAuth Cookie.DatasourceAuth
@ -171,7 +185,7 @@ export async function googleCallbackUrl(config?: GoogleInnerConfig) {
* The initial call that google authentication makes to take you to the google login screen. * The initial call that google authentication makes to take you to the google login screen.
* On a successful login, you will be redirected to the googleAuth callback route. * On a successful login, you will be redirected to the googleAuth callback route.
*/ */
export const googlePreAuth = async (ctx: any, next: any) => { export const googlePreAuth = async (ctx: Ctx<void, void>, next: Next) => {
const config = await configs.getGoogleConfig() const config = await configs.getGoogleConfig()
if (!config) { if (!config) {
return ctx.throw(400, "Google config not found") return ctx.throw(400, "Google config not found")
@ -190,7 +204,7 @@ export const googlePreAuth = async (ctx: any, next: any) => {
})(ctx, next) })(ctx, next)
} }
export const googleCallback = async (ctx: any, next: any) => { export const googleCallback = async (ctx: Ctx<void, void>, next: Next) => {
const config = await configs.getGoogleConfig() const config = await configs.getGoogleConfig()
if (!config) { if (!config) {
return ctx.throw(400, "Google config not found") return ctx.throw(400, "Google config not found")
@ -241,7 +255,7 @@ export const oidcStrategyFactory = async (ctx: any) => {
* The initial call that OIDC authentication makes to take you to the configured OIDC login screen. * The initial call that OIDC authentication makes to take you to the configured OIDC login screen.
* On a successful login, you will be redirected to the oidcAuth callback route. * On a successful login, you will be redirected to the oidcAuth callback route.
*/ */
export const oidcPreAuth = async (ctx: Ctx, next: any) => { export const oidcPreAuth = async (ctx: Ctx<void, void>, next: Next) => {
const { configId } = ctx.params const { configId } = ctx.params
if (!configId) { if (!configId) {
ctx.throw(400, "OIDC config id is required") ctx.throw(400, "OIDC config id is required")
@ -266,7 +280,7 @@ export const oidcPreAuth = async (ctx: Ctx, next: any) => {
})(ctx, next) })(ctx, next)
} }
export const oidcCallback = async (ctx: any, next: any) => { export const oidcCallback = async (ctx: Ctx<void, void>, next: Next) => {
const strategy = await oidcStrategyFactory(ctx) const strategy = await oidcStrategyFactory(ctx)
return passport.authenticate( return passport.authenticate(

View File

@ -15,8 +15,11 @@ import {
AIConfig, AIConfig,
AIInnerConfig, AIInnerConfig,
Config, Config,
ConfigChecklistResponse,
ConfigType, ConfigType,
Ctx, Ctx,
DeleteConfigResponse,
FindConfigResponse,
GetPublicOIDCConfigResponse, GetPublicOIDCConfigResponse,
GetPublicSettingsResponse, GetPublicSettingsResponse,
GoogleInnerConfig, GoogleInnerConfig,
@ -29,11 +32,14 @@ import {
OIDCLogosConfig, OIDCLogosConfig,
PASSWORD_REPLACEMENT, PASSWORD_REPLACEMENT,
QuotaUsageType, QuotaUsageType,
SaveConfigRequest,
SaveConfigResponse,
SettingsBrandingConfig, SettingsBrandingConfig,
SettingsInnerConfig, SettingsInnerConfig,
SSOConfig, SSOConfig,
SSOConfigType, SSOConfigType,
StaticQuotaName, StaticQuotaName,
UploadConfigFileResponse,
UserCtx, UserCtx,
} from "@budibase/types" } from "@budibase/types"
import * as pro from "@budibase/pro" import * as pro from "@budibase/pro"
@ -225,7 +231,9 @@ export async function verifyAIConfig(
} }
} }
export async function save(ctx: UserCtx<Config>) { export async function save(
ctx: UserCtx<SaveConfigRequest, SaveConfigResponse>
) {
const body = ctx.request.body const body = ctx.request.body
const type = body.type const type = body.type
const config = body.config const config = body.config
@ -337,7 +345,7 @@ function enrichOIDCLogos(oidcLogos: OIDCLogosConfig) {
) )
} }
export async function find(ctx: UserCtx) { export async function find(ctx: UserCtx<void, FindConfigResponse>) {
try { try {
// Find the config with the most granular scope based on context // Find the config with the most granular scope based on context
const type = ctx.params.type const type = ctx.params.type
@ -473,7 +481,7 @@ export async function publicSettings(
} }
} }
export async function upload(ctx: UserCtx) { export async function upload(ctx: UserCtx<void, UploadConfigFileResponse>) {
if (ctx.request.files == null || Array.isArray(ctx.request.files.file)) { if (ctx.request.files == null || Array.isArray(ctx.request.files.file)) {
ctx.throw(400, "One file must be uploaded.") ctx.throw(400, "One file must be uploaded.")
} }
@ -518,7 +526,7 @@ export async function upload(ctx: UserCtx) {
} }
} }
export async function destroy(ctx: UserCtx) { export async function destroy(ctx: UserCtx<void, DeleteConfigResponse>) {
const db = tenancy.getGlobalDB() const db = tenancy.getGlobalDB()
const { id, rev } = ctx.params const { id, rev } = ctx.params
try { try {
@ -537,14 +545,14 @@ export async function destroy(ctx: UserCtx) {
} }
} }
export async function configChecklist(ctx: Ctx) { export async function configChecklist(ctx: Ctx<void, ConfigChecklistResponse>) {
const tenantId = tenancy.getTenantId() const tenantId = tenancy.getTenantId()
try { try {
ctx.body = await cache.withCache( ctx.body = await cache.withCache(
cache.CacheKey.CHECKLIST, cache.CacheKey.CHECKLIST,
env.CHECKLIST_CACHE_TTL, env.CHECKLIST_CACHE_TTL,
async () => { async (): Promise<ConfigChecklistResponse> => {
let apps = [] let apps = []
if (!env.MULTI_TENANCY || tenantId) { if (!env.MULTI_TENANCY || tenantId) {
// Apps exist // Apps exist

View File

@ -1,8 +1,15 @@
import { sendEmail as sendEmailFn } from "../../../utilities/email" import { sendEmail as sendEmailFn } from "../../../utilities/email"
import { tenancy } from "@budibase/backend-core" import { tenancy } from "@budibase/backend-core"
import { BBContext, User } from "@budibase/types" import {
UserCtx,
User,
SendEmailRequest,
SendEmailResponse,
} from "@budibase/types"
export async function sendEmail(ctx: BBContext) { export async function sendEmail(
ctx: UserCtx<SendEmailRequest, SendEmailResponse>
) {
let { let {
workspaceId, workspaceId,
email, email,

View File

@ -5,6 +5,7 @@ import {
GetLicenseKeyResponse, GetLicenseKeyResponse,
GetOfflineIdentifierResponse, GetOfflineIdentifierResponse,
GetOfflineLicenseTokenResponse, GetOfflineLicenseTokenResponse,
GetQuotaUsageResponse,
UserCtx, UserCtx,
} from "@budibase/types" } from "@budibase/types"
@ -36,7 +37,7 @@ export async function deleteLicenseKey(ctx: UserCtx<void, void>) {
// OFFLINE LICENSE // OFFLINE LICENSE
export async function activateOfflineLicenseToken( export async function activateOfflineLicenseToken(
ctx: UserCtx<ActivateOfflineLicenseTokenRequest> ctx: UserCtx<ActivateOfflineLicenseTokenRequest, void>
) { ) {
const { offlineLicenseToken } = ctx.request.body const { offlineLicenseToken } = ctx.request.body
await licensing.offline.activateOfflineLicenseToken(offlineLicenseToken) await licensing.offline.activateOfflineLicenseToken(offlineLicenseToken)
@ -70,14 +71,16 @@ export async function getOfflineLicenseIdentifier(
// LICENSES // LICENSES
export const refresh = async (ctx: any) => { export const refresh = async (ctx: UserCtx<void, void>) => {
await licensing.cache.refresh() await licensing.cache.refresh()
ctx.status = 200 ctx.status = 200
} }
// USAGE // USAGE
export const getQuotaUsage = async (ctx: any) => { export const getQuotaUsage = async (
ctx: UserCtx<void, GetQuotaUsageResponse>
) => {
ctx.body = await quotas.getQuotaUsage() ctx.body = await quotas.getQuotaUsage()
ctx.status = 200 ctx.status = 200
} }

View File

@ -6,9 +6,15 @@ import {
tenancy, tenancy,
} from "@budibase/backend-core" } from "@budibase/backend-core"
import sdk from "../../../sdk" import sdk from "../../../sdk"
import { Ctx, App } from "@budibase/types" import {
Ctx,
App,
FetchGlobalRolesResponse,
FindGlobalRoleResponse,
RemoveAppRoleResponse,
} from "@budibase/types"
export async function fetch(ctx: Ctx) { export async function fetch(ctx: Ctx<void, FetchGlobalRolesResponse>) {
const tenantId = ctx.user!.tenantId const tenantId = ctx.user!.tenantId
// always use the dev apps as they'll be most up to date (true) // always use the dev apps as they'll be most up to date (true)
const apps = (await dbCore.getAllApps({ tenantId, all: true })) as App[] const apps = (await dbCore.getAllApps({ tenantId, all: true })) as App[]
@ -31,7 +37,7 @@ export async function fetch(ctx: Ctx) {
ctx.body = response ctx.body = response
} }
export async function find(ctx: Ctx) { export async function find(ctx: Ctx<void, FindGlobalRoleResponse>) {
const appId = ctx.params.appId const appId = ctx.params.appId
await context.doInAppContext(dbCore.getDevAppID(appId), async () => { await context.doInAppContext(dbCore.getDevAppID(appId), async () => {
const db = context.getAppDB() const db = context.getAppDB()
@ -45,7 +51,7 @@ export async function find(ctx: Ctx) {
}) })
} }
export async function removeAppRole(ctx: Ctx) { export async function removeAppRole(ctx: Ctx<void, RemoveAppRoleResponse>) {
const { appId } = ctx.params const { appId } = ctx.params
const db = tenancy.getGlobalDB() const db = tenancy.getGlobalDB()
const users = await sdk.users.db.allUsers() const users = await sdk.users.db.allUsers()

View File

@ -10,6 +10,11 @@ import {
import env from "../../../environment" import env from "../../../environment"
import { groups } from "@budibase/pro" import { groups } from "@budibase/pro"
import { import {
DevInfo,
FetchAPIKeyResponse,
GenerateAPIKeyRequest,
GenerateAPIKeyResponse,
GetGlobalSelfResponse,
UpdateSelfRequest, UpdateSelfRequest,
UpdateSelfResponse, UpdateSelfResponse,
User, User,
@ -35,22 +40,24 @@ function cleanupDevInfo(info: any) {
return info return info
} }
export async function generateAPIKey(ctx: any) { export async function generateAPIKey(
ctx: UserCtx<GenerateAPIKeyRequest, GenerateAPIKeyResponse>
) {
let userId let userId
let apiKey let apiKey
if (env.isTest() && ctx.request.body.userId) { if (env.isTest() && ctx.request.body.userId) {
userId = ctx.request.body.userId userId = ctx.request.body.userId
apiKey = newTestApiKey() apiKey = newTestApiKey()
} else { } else {
userId = ctx.user._id userId = ctx.user._id!
apiKey = newApiKey() apiKey = newApiKey()
} }
const db = tenancy.getGlobalDB() const db = tenancy.getGlobalDB()
const id = dbCore.generateDevInfoID(userId) const id = dbCore.generateDevInfoID(userId)
let devInfo let devInfo: DevInfo
try { try {
devInfo = await db.get<any>(id) devInfo = await db.get<DevInfo>(id)
} catch (err) { } catch (err) {
devInfo = { _id: id, userId } devInfo = { _id: id, userId }
} }
@ -59,9 +66,9 @@ export async function generateAPIKey(ctx: any) {
ctx.body = cleanupDevInfo(devInfo) ctx.body = cleanupDevInfo(devInfo)
} }
export async function fetchAPIKey(ctx: any) { export async function fetchAPIKey(ctx: UserCtx<void, FetchAPIKeyResponse>) {
const db = tenancy.getGlobalDB() const db = tenancy.getGlobalDB()
const id = dbCore.generateDevInfoID(ctx.user._id) const id = dbCore.generateDevInfoID(ctx.user._id!)
let devInfo let devInfo
try { try {
devInfo = await db.get(id) devInfo = await db.get(id)
@ -87,11 +94,11 @@ const addSessionAttributesToUser = (ctx: any) => {
ctx.body.csrfToken = ctx.user.csrfToken ctx.body.csrfToken = ctx.user.csrfToken
} }
export async function getSelf(ctx: any) { export async function getSelf(ctx: UserCtx<void, GetGlobalSelfResponse>) {
if (!ctx.user) { if (!ctx.user) {
ctx.throw(403, "User not logged in") ctx.throw(403, "User not logged in")
} }
const userId = ctx.user._id const userId = ctx.user._id!
ctx.params = { ctx.params = {
id: userId, id: userId,
} }

View File

@ -3,10 +3,25 @@ import {
TemplateBindings, TemplateBindings,
GLOBAL_OWNER, GLOBAL_OWNER,
} from "../../../constants" } from "../../../constants"
import { getTemplates } from "../../../constants/templates" import { getTemplateByID, getTemplates } from "../../../constants/templates"
import { tenancy, db as dbCore } from "@budibase/backend-core" import { tenancy, db as dbCore } from "@budibase/backend-core"
import {
DeleteGlobalTemplateResponse,
FetchGlobalTemplateByOwnerIDResponse,
FetchGlobalTemplateByTypeResponse,
FetchGlobalTemplateDefinitionResponse,
FetchGlobalTemplateResponse,
FindGlobalTemplateResponse,
SaveGlobalTemplateRequest,
SaveGlobalTemplateResponse,
GlobalTemplateBinding,
GlobalTemplateDefinition,
UserCtx,
} from "@budibase/types"
export async function save(ctx: any) { export async function save(
ctx: UserCtx<SaveGlobalTemplateRequest, SaveGlobalTemplateResponse>
) {
const db = tenancy.getGlobalDB() const db = tenancy.getGlobalDB()
let template = ctx.request.body let template = ctx.request.body
if (!template.ownerId) { if (!template.ownerId) {
@ -23,9 +38,11 @@ export async function save(ctx: any) {
} }
} }
export async function definitions(ctx: any) { export async function definitions(
const bindings: any = {} ctx: UserCtx<void, FetchGlobalTemplateDefinitionResponse>
const info: any = {} ) {
const bindings: Record<string, GlobalTemplateBinding[]> = {}
const info: Record<string, GlobalTemplateDefinition> = {}
for (let template of TemplateMetadata.email) { for (let template of TemplateMetadata.email) {
bindings[template.purpose] = template.bindings bindings[template.purpose] = template.bindings
info[template.purpose] = { info[template.purpose] = {
@ -44,34 +61,35 @@ export async function definitions(ctx: any) {
} }
} }
export async function fetch(ctx: any) { export async function fetch(ctx: UserCtx<void, FetchGlobalTemplateResponse>) {
ctx.body = await getTemplates() ctx.body = await getTemplates()
} }
export async function fetchByType(ctx: any) { export async function fetchByType(
// @ts-ignore ctx: UserCtx<void, FetchGlobalTemplateByTypeResponse>
) {
ctx.body = await getTemplates({ ctx.body = await getTemplates({
type: ctx.params.type, type: ctx.params.type,
}) })
} }
export async function fetchByOwner(ctx: any) { export async function fetchByOwner(
ctx: UserCtx<void, FetchGlobalTemplateByOwnerIDResponse>
) {
// @ts-ignore // @ts-ignore
ctx.body = await getTemplates({ ctx.body = await getTemplates({
ownerId: ctx.params.ownerId, ownerId: ctx.params.ownerId,
}) })
} }
export async function find(ctx: any) { export async function find(ctx: UserCtx<void, FindGlobalTemplateResponse>) {
// @ts-ignore ctx.body = await getTemplateByID(ctx.params.id)
ctx.body = await getTemplates({
id: ctx.params.id,
})
} }
export async function destroy(ctx: any) { export async function destroy(
ctx: UserCtx<void, DeleteGlobalTemplateResponse>
) {
const db = tenancy.getGlobalDB() const db = tenancy.getGlobalDB()
await db.remove(ctx.params.id, ctx.params.rev) await db.remove(ctx.params.id, ctx.params.rev)
ctx.message = `Template ${ctx.params.id} deleted.` ctx.body = { message: `Template ${ctx.params.id} deleted.` }
ctx.status = 200
} }

View File

@ -6,21 +6,34 @@ import {
AddSSoUserRequest, AddSSoUserRequest,
BulkUserRequest, BulkUserRequest,
BulkUserResponse, BulkUserResponse,
CheckInviteResponse,
CountUserResponse,
CreateAdminUserRequest, CreateAdminUserRequest,
CreateAdminUserResponse, CreateAdminUserResponse,
Ctx, Ctx,
DeleteInviteUserRequest, DeleteInviteUserRequest,
DeleteInviteUsersRequest, DeleteInviteUsersRequest,
DeleteInviteUsersResponse,
DeleteUserResponse,
FetchUsersResponse,
FindUserResponse,
GetUserInvitesResponse,
Hosting, Hosting,
InviteUserRequest, InviteUserRequest,
InviteUserResponse,
InviteUsersRequest, InviteUsersRequest,
InviteUsersResponse, InviteUsersResponse,
LockName, LockName,
LockType, LockType,
LookupAccountHolderResponse,
LookupTenantUserResponse,
MigrationType, MigrationType,
PlatformUserByEmail, PlatformUserByEmail,
SaveUserResponse, SaveUserResponse,
SearchUsersRequest, SearchUsersRequest,
SearchUsersResponse,
UpdateInviteRequest,
UpdateInviteResponse,
User, User,
UserCtx, UserCtx,
UserIdentifier, UserIdentifier,
@ -80,7 +93,7 @@ export const save = async (ctx: UserCtx<User, SaveUserResponse>) => {
} }
} }
export const addSsoSupport = async (ctx: Ctx<AddSSoUserRequest>) => { export const addSsoSupport = async (ctx: Ctx<AddSSoUserRequest, void>) => {
const { email, ssoId } = ctx.request.body const { email, ssoId } = ctx.request.body
try { try {
// Status is changed to 404 from getUserDoc if user is not found // Status is changed to 404 from getUserDoc if user is not found
@ -207,7 +220,7 @@ export const adminUser = async (
}) })
} }
export const countByApp = async (ctx: any) => { export const countByApp = async (ctx: UserCtx<void, CountUserResponse>) => {
const appId = ctx.params.appId const appId = ctx.params.appId
try { try {
ctx.body = await userSdk.db.countUsersByApp(appId) ctx.body = await userSdk.db.countUsersByApp(appId)
@ -216,7 +229,7 @@ export const countByApp = async (ctx: any) => {
} }
} }
export const destroy = async (ctx: any) => { export const destroy = async (ctx: UserCtx<void, DeleteUserResponse>) => {
const id = ctx.params.id const id = ctx.params.id
if (id === ctx.user._id) { if (id === ctx.user._id) {
ctx.throw(400, "Unable to delete self.") ctx.throw(400, "Unable to delete self.")
@ -239,7 +252,9 @@ export const getAppUsers = async (ctx: Ctx<SearchUsersRequest>) => {
ctx.body = { data: users } ctx.body = { data: users }
} }
export const search = async (ctx: Ctx<SearchUsersRequest>) => { export const search = async (
ctx: Ctx<SearchUsersRequest, SearchUsersResponse>
) => {
const body = ctx.request.body const body = ctx.request.body
// TODO: for now only two supported search keys; string.email and equal._id // TODO: for now only two supported search keys; string.email and equal._id
@ -280,7 +295,7 @@ export const search = async (ctx: Ctx<SearchUsersRequest>) => {
} }
// called internally by app server user fetch // called internally by app server user fetch
export const fetch = async (ctx: any) => { export const fetch = async (ctx: UserCtx<void, FetchUsersResponse>) => {
const all = await userSdk.db.allUsers() const all = await userSdk.db.allUsers()
// user hashed password shouldn't ever be returned // user hashed password shouldn't ever be returned
for (let user of all) { for (let user of all) {
@ -292,11 +307,13 @@ export const fetch = async (ctx: any) => {
} }
// called internally by app server user find // called internally by app server user find
export const find = async (ctx: any) => { export const find = async (ctx: UserCtx<void, FindUserResponse>) => {
ctx.body = await userSdk.db.getUser(ctx.params.id) ctx.body = await userSdk.db.getUser(ctx.params.id)
} }
export const tenantUserLookup = async (ctx: any) => { export const tenantUserLookup = async (
ctx: UserCtx<void, LookupTenantUserResponse>
) => {
const id = ctx.params.id const id = ctx.params.id
// is email, check its valid // is email, check its valid
if (id.includes("@") && !emailValidator.validate(id)) { if (id.includes("@") && !emailValidator.validate(id)) {
@ -314,7 +331,9 @@ export const tenantUserLookup = async (ctx: any) => {
* This will be paginated to a default of the first 50 users, * This will be paginated to a default of the first 50 users,
* So the account holder may not be found until further pagination has occurred * So the account holder may not be found until further pagination has occurred
*/ */
export const accountHolderLookup = async (ctx: Ctx) => { export const accountHolderLookup = async (
ctx: Ctx<void, LookupAccountHolderResponse>
) => {
try { try {
const users = await userSdk.core.getAllUsers() const users = await userSdk.core.getAllUsers()
const response = await userSdk.core.getExistingAccounts( const response = await userSdk.core.getExistingAccounts(
@ -366,7 +385,9 @@ export const onboardUsers = async (
ctx.body = { ...resp, created: true } ctx.body = { ...resp, created: true }
} }
export const invite = async (ctx: Ctx<InviteUserRequest>) => { export const invite = async (
ctx: Ctx<InviteUserRequest, InviteUserResponse>
) => {
const request = ctx.request.body const request = ctx.request.body
let multiRequest = [request] let multiRequest = [request]
@ -389,12 +410,14 @@ export const invite = async (ctx: Ctx<InviteUserRequest>) => {
} }
} }
export const inviteMultiple = async (ctx: Ctx<InviteUsersRequest>) => { export const inviteMultiple = async (
ctx: Ctx<InviteUsersRequest, InviteUsersResponse>
) => {
ctx.body = await userSdk.invite(ctx.request.body) ctx.body = await userSdk.invite(ctx.request.body)
} }
export const removeMultipleInvites = async ( export const removeMultipleInvites = async (
ctx: Ctx<DeleteInviteUsersRequest> ctx: Ctx<DeleteInviteUsersRequest, DeleteInviteUsersResponse>
) => { ) => {
const inviteCodesToRemove = ctx.request.body.map( const inviteCodesToRemove = ctx.request.body.map(
(invite: DeleteInviteUserRequest) => invite.code (invite: DeleteInviteUserRequest) => invite.code
@ -407,7 +430,7 @@ export const removeMultipleInvites = async (
} }
} }
export const checkInvite = async (ctx: any) => { export const checkInvite = async (ctx: UserCtx<void, CheckInviteResponse>) => {
const { code } = ctx.params const { code } = ctx.params
let invite let invite
try { try {
@ -422,7 +445,9 @@ export const checkInvite = async (ctx: any) => {
} }
} }
export const getUserInvites = async (ctx: any) => { export const getUserInvites = async (
ctx: UserCtx<void, GetUserInvitesResponse>
) => {
try { try {
// Restricted to the currently authenticated tenant // Restricted to the currently authenticated tenant
ctx.body = await cache.invite.getInviteCodes() ctx.body = await cache.invite.getInviteCodes()
@ -431,7 +456,9 @@ export const getUserInvites = async (ctx: any) => {
} }
} }
export const updateInvite = async (ctx: any) => { export const updateInvite = async (
ctx: UserCtx<UpdateInviteRequest, UpdateInviteResponse>
) => {
const { code } = ctx.params const { code } = ctx.params
let updateBody = { ...ctx.request.body } let updateBody = { ...ctx.request.body }

View File

@ -1,53 +0,0 @@
import { tenancy, db as dbCore } from "@budibase/backend-core"
import { BBContext } from "@budibase/types"
export async function save(ctx: BBContext) {
const db = tenancy.getGlobalDB()
const workspaceDoc = ctx.request.body
// workspace does not exist yet
if (!workspaceDoc._id) {
workspaceDoc._id = dbCore.generateWorkspaceID()
}
try {
const response = await db.put(workspaceDoc)
ctx.body = {
_id: response.id,
_rev: response.rev,
}
} catch (err: any) {
ctx.throw(err.status, err)
}
}
export async function fetch(ctx: BBContext) {
const db = tenancy.getGlobalDB()
const response = await db.allDocs(
dbCore.getWorkspaceParams(undefined, {
include_docs: true,
})
)
ctx.body = response.rows.map(row => row.doc)
}
export async function find(ctx: BBContext) {
const db = tenancy.getGlobalDB()
try {
ctx.body = await db.get(ctx.params.id)
} catch (err: any) {
ctx.throw(err.status, err)
}
}
export async function destroy(ctx: BBContext) {
const db = tenancy.getGlobalDB()
const { id, rev } = ctx.params
try {
await db.remove(id, rev)
ctx.body = { message: "Workspace deleted successfully" }
} catch (err: any) {
ctx.throw(err.status, err)
}
}

View File

@ -1,7 +1,15 @@
import { Account, AccountMetadata, Ctx } from "@budibase/types" import {
Account,
AccountMetadata,
Ctx,
SaveAccountRequest,
SaveAccountResponse,
} from "@budibase/types"
import * as accounts from "../../../sdk/accounts" import * as accounts from "../../../sdk/accounts"
export const save = async (ctx: Ctx<Account, AccountMetadata>) => { export const save = async (
ctx: Ctx<SaveAccountRequest, SaveAccountResponse>
) => {
const account = ctx.request.body as Account const account = ctx.request.body as Account
let metadata: AccountMetadata = { let metadata: AccountMetadata = {
_id: accounts.metadata.formatAccountMetadataId(account.accountId), _id: accounts.metadata.formatAccountMetadataId(account.accountId),
@ -14,7 +22,7 @@ export const save = async (ctx: Ctx<Account, AccountMetadata>) => {
ctx.status = 200 ctx.status = 200
} }
export const destroy = async (ctx: any) => { export const destroy = async (ctx: Ctx<void, void>) => {
const accountId = accounts.metadata.formatAccountMetadataId( const accountId = accounts.metadata.formatAccountMetadataId(
ctx.params.accountId ctx.params.accountId
) )

View File

@ -1,4 +1,4 @@
import { Ctx, MaintenanceType } from "@budibase/types" import { Ctx, GetEnvironmentResponse, MaintenanceType } from "@budibase/types"
import env from "../../../environment" import env from "../../../environment"
import { env as coreEnv, db as dbCore } from "@budibase/backend-core" import { env as coreEnv, db as dbCore } from "@budibase/backend-core"
import nodeFetch from "node-fetch" import nodeFetch from "node-fetch"
@ -38,13 +38,13 @@ async function isSqsMissing() {
return !(await isSqsAvailable()) return !(await isSqsAvailable())
} }
export const fetch = async (ctx: Ctx) => { export const fetch = async (ctx: Ctx<void, GetEnvironmentResponse>) => {
ctx.body = { ctx.body = {
multiTenancy: !!env.MULTI_TENANCY, multiTenancy: !!env.MULTI_TENANCY,
offlineMode: !!coreEnv.OFFLINE_MODE, offlineMode: !!coreEnv.OFFLINE_MODE,
cloud: !env.SELF_HOSTED, cloud: !env.SELF_HOSTED,
accountPortalUrl: env.ACCOUNT_PORTAL_URL, accountPortalUrl: env.ACCOUNT_PORTAL_URL,
disableAccountPortal: env.DISABLE_ACCOUNT_PORTAL, disableAccountPortal: !!env.DISABLE_ACCOUNT_PORTAL,
baseUrl: env.PLATFORM_URL, baseUrl: env.PLATFORM_URL,
isDev: env.isDev() && !env.isTest(), isDev: env.isDev() && !env.isTest(),
maintenance: [], maintenance: [],

View File

@ -1,7 +1,7 @@
import { UserCtx } from "@budibase/types" import { GetLogResponse, UserCtx } from "@budibase/types"
import { installation, logging } from "@budibase/backend-core" import { installation, logging } from "@budibase/backend-core"
export async function getLogs(ctx: UserCtx) { export async function getLogs(ctx: UserCtx<void, GetLogResponse>) {
const logReadStream = logging.system.getLogReadStream() const logReadStream = logging.system.getLogReadStream()
const { installId } = await installation.getInstall() const { installId } = await installation.getInstall()

View File

@ -1,13 +1,23 @@
import {
FetchMigrationDefinitionsResponse,
RunGlobalMigrationRequest,
UserCtx,
} from "@budibase/types"
const { migrate, MIGRATIONS } = require("../../../migrations") const { migrate, MIGRATIONS } = require("../../../migrations")
export const runMigrations = async (ctx: any) => { export const runMigrations = async (
ctx: UserCtx<RunGlobalMigrationRequest, 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
migrate(options) migrate(options)
ctx.status = 200 ctx.status = 200
} }
export const fetchDefinitions = async (ctx: any) => { export const fetchDefinitions = async (
ctx: UserCtx<void, FetchMigrationDefinitionsResponse>
) => {
ctx.body = MIGRATIONS ctx.body = MIGRATIONS
ctx.status = 200 ctx.status = 200
} }

View File

@ -1,8 +1,10 @@
import env from "../../../environment" import env from "../../../environment"
import { BBContext } from "@budibase/types" import { SystemRestoreResponse, UserCtx } from "@budibase/types"
import { cache } from "@budibase/backend-core" import { cache } from "@budibase/backend-core"
export async function systemRestored(ctx: BBContext) { export async function systemRestored(
ctx: UserCtx<void, SystemRestoreResponse>
) {
if (!env.SELF_HOSTED) { if (!env.SELF_HOSTED) {
ctx.throw(405, "This operation is not allowed in cloud.") ctx.throw(405, "This operation is not allowed in cloud.")
} }

View File

@ -1,7 +1,7 @@
import { UserCtx } from "@budibase/types" import { GetTenantInfoResponse, UserCtx } from "@budibase/types"
import * as tenantSdk from "../../../sdk/tenants" import * as tenantSdk from "../../../sdk/tenants"
export async function destroy(ctx: UserCtx) { export async function destroy(ctx: UserCtx<void, void>) {
const user = ctx.user! const user = ctx.user!
const tenantId = ctx.params.tenantId const tenantId = ctx.params.tenantId
@ -18,6 +18,6 @@ export async function destroy(ctx: UserCtx) {
} }
} }
export async function info(ctx: UserCtx) { export async function info(ctx: UserCtx<void, GetTenantInfoResponse>) {
ctx.body = await tenantSdk.tenantInfo(ctx.params.tenantId) ctx.body = await tenantSdk.tenantInfo(ctx.params.tenantId)
} }

View File

@ -1,7 +1,7 @@
import Router from "@koa/router" import Router from "@koa/router"
import * as controller from "../../controllers/global/email" import * as controller from "../../controllers/global/email"
import { EmailTemplatePurpose } from "../../../constants"
import { auth } from "@budibase/backend-core" import { auth } from "@budibase/backend-core"
import { EmailTemplatePurpose } from "@budibase/types"
import Joi from "joi" import Joi from "joi"
const router: Router = new Router() const router: Router = new Router()

View File

@ -1,8 +1,8 @@
jest.mock("nodemailer") jest.mock("nodemailer")
import { EmailTemplatePurpose } from "@budibase/types"
import { TestConfiguration, mocks } from "../../../../tests" import { TestConfiguration, mocks } from "../../../../tests"
const sendMailMock = mocks.email.mock() const sendMailMock = mocks.email.mock()
import { EmailTemplatePurpose } from "../../../../constants"
describe("/api/global/email", () => { describe("/api/global/email", () => {
const config = new TestConfiguration() const config = new TestConfiguration()

View File

@ -1,11 +1,10 @@
jest.unmock("node-fetch") jest.unmock("node-fetch")
import { TestConfiguration } from "../../../../tests" import { TestConfiguration } from "../../../../tests"
import { EmailTemplatePurpose } from "../../../../constants"
import { objectStore } from "@budibase/backend-core" import { objectStore } from "@budibase/backend-core"
import { helpers } from "@budibase/shared-core" import { helpers } from "@budibase/shared-core"
import tk from "timekeeper" import tk from "timekeeper"
import { EmailAttachment } from "@budibase/types" import { EmailAttachment, EmailTemplatePurpose } from "@budibase/types"
const fetch = require("node-fetch") const fetch = require("node-fetch")

View File

@ -1,9 +1,6 @@
import { import { TemplateMetadata, TemplateType } from "../../../../constants"
EmailTemplatePurpose,
TemplateMetadata,
TemplateType,
} from "../../../../constants"
import { TestConfiguration } from "../../../../tests" import { TestConfiguration } from "../../../../tests"
import { EmailTemplatePurpose } from "@budibase/types"
// TODO // TODO

View File

@ -1,37 +0,0 @@
import Router from "@koa/router"
import * as controller from "../../controllers/global/workspaces"
import { auth } from "@budibase/backend-core"
import Joi from "joi"
const router: Router = new Router()
function buildWorkspaceSaveValidation() {
// prettier-ignore
return auth.joiValidator.body(Joi.object({
_id: Joi.string().optional(),
_rev: Joi.string().optional(),
name: Joi.string().required(),
users: Joi.array().required(),
managers: Joi.array().required(),
roles: Joi.object({
default: Joi.string().optional(),
app: Joi.object()
.pattern(/.*/, Joi.string())
.required()
.unknown(true),
}).unknown(true).optional(),
}).required().unknown(true))
}
router
.post(
"/api/global/workspaces",
auth.adminOnly,
buildWorkspaceSaveValidation(),
controller.save
)
.delete("/api/global/workspaces/:id", auth.adminOnly, controller.destroy)
.get("/api/global/workspaces", controller.fetch)
.get("/api/global/workspaces/:id", controller.find)
export default router

View File

@ -2,7 +2,6 @@ import Router from "@koa/router"
import { api as pro } from "@budibase/pro" import { api as pro } from "@budibase/pro"
import userRoutes from "./global/users" import userRoutes from "./global/users"
import configRoutes from "./global/configs" import configRoutes from "./global/configs"
import workspaceRoutes from "./global/workspaces"
import templateRoutes from "./global/templates" import templateRoutes from "./global/templates"
import emailRoutes from "./global/email" import emailRoutes from "./global/email"
import authRoutes from "./global/auth" import authRoutes from "./global/auth"
@ -24,7 +23,6 @@ export const routes: Router[] = [
configRoutes, configRoutes,
userRoutes, userRoutes,
pro.users, pro.users,
workspaceRoutes,
authRoutes, authRoutes,
templateRoutes, templateRoutes,
tenantsRoutes, tenantsRoutes,

View File

@ -22,7 +22,7 @@ describe("/api/system/environment", () => {
const env = await config.api.environment.getEnvironment() const env = await config.api.environment.getEnvironment()
expect(env.body).toEqual({ expect(env.body).toEqual({
cloud: true, cloud: true,
disableAccountPortal: 0, disableAccountPortal: false,
isDev: false, isDev: false,
multiTenancy: true, multiTenancy: true,
baseUrl: "http://localhost:10000", baseUrl: "http://localhost:10000",
@ -36,7 +36,7 @@ describe("/api/system/environment", () => {
const env = await config.api.environment.getEnvironment() const env = await config.api.environment.getEnvironment()
expect(env.body).toEqual({ expect(env.body).toEqual({
cloud: false, cloud: false,
disableAccountPortal: 0, disableAccountPortal: false,
isDev: false, isDev: false,
multiTenancy: true, multiTenancy: true,
baseUrl: "http://localhost:10000", baseUrl: "http://localhost:10000",

View File

@ -1,4 +1,5 @@
import { constants } from "@budibase/backend-core" import { constants } from "@budibase/backend-core"
import { EmailTemplatePurpose } from "@budibase/types"
export const LOGO_URL = export const LOGO_URL =
"https://d33wubrfki0l68.cloudfront.net/aac32159d7207b5085e74a7ef67afbb7027786c5/2b1fd/img/logo/bb-emblem.svg" "https://d33wubrfki0l68.cloudfront.net/aac32159d7207b5085e74a7ef67afbb7027786c5/2b1fd/img/logo/bb-emblem.svg"
@ -19,15 +20,6 @@ export enum TemplateType {
EMAIL = "email", EMAIL = "email",
} }
export enum EmailTemplatePurpose {
CORE = "core",
BASE = "base",
PASSWORD_RECOVERY = "password_recovery",
INVITATION = "invitation",
WELCOME = "welcome",
CUSTOM = "custom",
}
export enum TemplateMetadataNames { export enum TemplateMetadataNames {
BASE = "Base format", BASE = "Base format",
PASSWORD_RECOVERY = "Password recovery", PASSWORD_RECOVERY = "Password recovery",

View File

@ -1,13 +1,8 @@
import { readStaticFile } from "../../utilities/fileSystem" import { readStaticFile } from "../../utilities/fileSystem"
import { import { TemplateType, TemplatePurpose, GLOBAL_OWNER } from "../index"
EmailTemplatePurpose,
TemplateType,
TemplatePurpose,
GLOBAL_OWNER,
} from "../index"
import { join } from "path" import { join } from "path"
import { db as dbCore, tenancy } from "@budibase/backend-core" import { db as dbCore, tenancy } from "@budibase/backend-core"
import { Template } from "@budibase/types" import { Template, EmailTemplatePurpose } from "@budibase/types"
export const EmailTemplates = { export const EmailTemplates = {
[EmailTemplatePurpose.PASSWORD_RECOVERY]: readStaticFile( [EmailTemplatePurpose.PASSWORD_RECOVERY]: readStaticFile(
@ -53,8 +48,21 @@ export function addBaseTemplates(templates: Template[], type?: string) {
export async function getTemplates({ export async function getTemplates({
ownerId, ownerId,
type, type,
id, }: { ownerId?: string; type?: string } = {}) {
}: { ownerId?: string; type?: string; id?: string } = {}) { const db = tenancy.getGlobalDB()
const response = await db.allDocs<Template>(
dbCore.getTemplateParams(ownerId || GLOBAL_OWNER, undefined, {
include_docs: true,
})
)
let templates = response.rows.map(row => row.doc!)
if (type) {
templates = templates.filter(template => template.type === type)
}
return addBaseTemplates(templates, type)
}
export async function getTemplateByID(id: string, ownerId?: string) {
const db = tenancy.getGlobalDB() const db = tenancy.getGlobalDB()
const response = await db.allDocs<Template>( const response = await db.allDocs<Template>(
dbCore.getTemplateParams(ownerId || GLOBAL_OWNER, id, { dbCore.getTemplateParams(ownerId || GLOBAL_OWNER, id, {
@ -63,16 +71,10 @@ export async function getTemplates({
) )
let templates = response.rows.map(row => row.doc!) let templates = response.rows.map(row => row.doc!)
// should only be one template with ID // should only be one template with ID
if (id) { return templates[0]
return templates[0]
}
if (type) {
templates = templates.filter(template => template.type === type)
}
return addBaseTemplates(templates, type)
} }
export async function getTemplateByPurpose(type: string, purpose: string) { export async function getTemplateByPurpose(type: string, purpose: string) {
const templates = (await getTemplates({ type })) as Template[] const templates = await getTemplates({ type })
return templates.find((template: Template) => template.purpose === purpose) return templates.find((template: Template) => template.purpose === purpose)
} }

View File

@ -1,12 +1,12 @@
import env from "../environment" import env from "../environment"
import { constants, utils } from "@budibase/backend-core" import { constants, utils } from "@budibase/backend-core"
import { BBContext } from "@budibase/types" import { UserCtx } from "@budibase/types"
/** /**
* This is a restricted endpoint in the cloud. * This is a restricted endpoint in the cloud.
* Ensure that the correct API key has been supplied. * Ensure that the correct API key has been supplied.
*/ */
export default async (ctx: BBContext, next: any) => { export default async (ctx: UserCtx, next: any) => {
if (!env.SELF_HOSTED && !env.DISABLE_ACCOUNT_PORTAL) { if (!env.SELF_HOSTED && !env.DISABLE_ACCOUNT_PORTAL) {
const apiKey = ctx.request.headers[constants.Header.API_KEY] const apiKey = ctx.request.headers[constants.Header.API_KEY]
if (!apiKey) { if (!apiKey) {

View File

@ -8,11 +8,10 @@ import {
utils as coreUtils, utils as coreUtils,
cache, cache,
} from "@budibase/backend-core" } from "@budibase/backend-core"
import { PlatformLogoutOpts, User } from "@budibase/types" import { PlatformLogoutOpts, User, EmailTemplatePurpose } from "@budibase/types"
import jwt from "jsonwebtoken" import jwt from "jsonwebtoken"
import * as userSdk from "../users" import * as userSdk from "../users"
import * as emails from "../../utilities/email" import * as emails from "../../utilities/email"
import { EmailTemplatePurpose } from "../../constants"
// LOGIN / LOGOUT // LOGIN / LOGOUT

View File

@ -3,9 +3,9 @@ import {
InviteUserRequest, InviteUserRequest,
InviteUsersRequest, InviteUsersRequest,
InviteUsersResponse, InviteUsersResponse,
EmailTemplatePurpose,
} from "@budibase/types" } from "@budibase/types"
import { sendEmail } from "../../utilities/email" import { sendEmail } from "../../utilities/email"
import { EmailTemplatePurpose } from "../../constants"
export async function invite( export async function invite(
users: InviteUsersRequest users: InviteUsersRequest

View File

@ -1,5 +1,4 @@
export * as email from "../api/controllers/global/email" export * as email from "../api/controllers/global/email"
export * as workspaces from "../api/controllers/global/workspaces"
export * as config from "../api/controllers/global/configs" export * as config from "../api/controllers/global/configs"
export * as templates from "../api/controllers/global/templates" export * as templates from "../api/controllers/global/templates"
export * as users from "../api/controllers/global/users" export * as users from "../api/controllers/global/users"

View File

@ -1,9 +1,14 @@
import env from "../environment" import env from "../environment"
import { EmailTemplatePurpose, TemplateType } from "../constants" import { TemplateType } from "../constants"
import { getTemplateByPurpose, EmailTemplates } from "../constants/templates" import { getTemplateByPurpose, EmailTemplates } from "../constants/templates"
import { getSettingsTemplateContext } from "./templates" import { getSettingsTemplateContext } from "./templates"
import { processString } from "@budibase/string-templates" import { processString } from "@budibase/string-templates"
import { User, SendEmailOpts, SMTPInnerConfig } from "@budibase/types" import {
User,
SendEmailOpts,
SMTPInnerConfig,
EmailTemplatePurpose,
} from "@budibase/types"
import { configs, cache, objectStore } from "@budibase/backend-core" import { configs, cache, objectStore } from "@budibase/backend-core"
import ical from "ical-generator" import ical from "ical-generator"
import _ from "lodash" import _ from "lodash"

View File

@ -1,9 +1,6 @@
import { tenancy, configs } from "@budibase/backend-core" import { tenancy, configs } from "@budibase/backend-core"
import { import { EmailTemplatePurpose } from "@budibase/types"
InternalTemplateBinding, import { InternalTemplateBinding, LOGO_URL } from "../constants"
LOGO_URL,
EmailTemplatePurpose,
} from "../constants"
import { checkSlashesInUrl } from "./index" import { checkSlashesInUrl } from "./index"
const BASE_COMPANY = "Budibase" const BASE_COMPANY = "Budibase"