Merge branch 'master' of github.com:budibase/budibase into fix/aws-session-token-s3-ver2

This commit is contained in:
Sam Rose 2024-06-11 18:04:40 +01:00
commit a140a002e2
No known key found for this signature in database
28 changed files with 290 additions and 78 deletions

2
.gitignore vendored
View File

@ -8,6 +8,8 @@ bb-airgapped.tar.gz
packages/server/build/oldClientVersions/**/* packages/server/build/oldClientVersions/**/*
packages/builder/src/components/deploy/clientVersions.json packages/builder/src/components/deploy/clientVersions.json
packages/server/src/integrations/tests/utils/*.lock
# Logs # Logs
logs logs
*.log *.log

View File

@ -93,15 +93,21 @@ function isApps() {
return environment.SERVICE_TYPE === ServiceType.APPS return environment.SERVICE_TYPE === ServiceType.APPS
} }
function isQA() {
return environment.BUDIBASE_ENVIRONMENT === "QA"
}
const environment = { const environment = {
isTest, isTest,
isJest, isJest,
isDev, isDev,
isWorker, isWorker,
isApps, isApps,
isQA,
isProd: () => { isProd: () => {
return !isDev() return !isDev()
}, },
BUDIBASE_ENVIRONMENT: process.env.BUDIBASE_ENVIRONMENT,
JS_BCRYPT: process.env.JS_BCRYPT, JS_BCRYPT: process.env.JS_BCRYPT,
JWT_SECRET: process.env.JWT_SECRET, JWT_SECRET: process.env.JWT_SECRET,
JWT_SECRET_FALLBACK: process.env.JWT_SECRET_FALLBACK, JWT_SECRET_FALLBACK: process.env.JWT_SECRET_FALLBACK,

View File

@ -63,12 +63,12 @@ class InMemoryQueue implements Partial<Queue> {
* Same callback API as Bull, each callback passed to this will consume messages as they are * Same callback API as Bull, each callback passed to this will consume messages as they are
* available. Please note this is a queue service, not a notification service, so each * available. Please note this is a queue service, not a notification service, so each
* consumer will receive different messages. * consumer will receive different messages.
* @param func The callback function which will return a "Job", the same
* as the Bull API, within this job the property "data" contains the JSON message. Please * as the Bull API, within this job the property "data" contains the JSON message. Please
* note this is incredibly limited compared to Bull as in reality the Job would contain * note this is incredibly limited compared to Bull as in reality the Job would contain
* a lot more information about the queue and current status of Bull cluster. * a lot more information about the queue and current status of Bull cluster.
*/ */
async process(func: any) { async process(concurrencyOrFunc: number | any, func?: any) {
func = typeof concurrencyOrFunc === "number" ? func : concurrencyOrFunc
this._emitter.on("message", async () => { this._emitter.on("message", async () => {
if (this._messages.length <= 0) { if (this._messages.length <= 0) {
return return

View File

@ -1,6 +1,16 @@
import { getDB } from "../db/db" import { getDB } from "../db/db"
import { getGlobalDBName } from "../context" import { getGlobalDBName } from "../context"
import { TenantInfo } from "@budibase/types"
export function getTenantDB(tenantId: string) { export function getTenantDB(tenantId: string) {
return getDB(getGlobalDBName(tenantId)) return getDB(getGlobalDBName(tenantId))
} }
export async function saveTenantInfo(tenantInfo: TenantInfo) {
const db = getTenantDB(tenantInfo.tenantId)
// save the tenant info to db
return await db.put({
_id: "tenant_info",
...tenantInfo,
})
}

View File

@ -29,7 +29,7 @@
on:click={() => onSelect(data)} on:click={() => onSelect(data)}
> >
<span class="spectrum-Menu-itemLabel"> <span class="spectrum-Menu-itemLabel">
{data.label} {data.datasource?.name ? `${data.datasource.name} - ` : ""}{data.label}
</span> </span>
<svg <svg
class="spectrum-Icon spectrum-UIIcon-Checkmark100 spectrum-Menu-checkmark spectrum-Menu-itemIcon" class="spectrum-Icon spectrum-UIIcon-Checkmark100 spectrum-Menu-checkmark spectrum-Menu-itemIcon"

View File

@ -55,6 +55,9 @@
label: m.name, label: m.name,
tableId: m._id, tableId: m._id,
type: "table", type: "table",
datasource: $datasources.list.find(
ds => ds._id === m.sourceId || m.datasourceId
),
})) }))
$: viewsV1 = $viewsStore.list.map(view => ({ $: viewsV1 = $viewsStore.list.map(view => ({
...view, ...view,

View File

@ -860,8 +860,10 @@
"json", "json",
"internal", "internal",
"barcodeqr", "barcodeqr",
"signature_single",
"bigint", "bigint",
"bb_reference" "bb_reference",
"bb_reference_single"
], ],
"description": "Defines the type of the column, most explain themselves, a link column is a relationship." "description": "Defines the type of the column, most explain themselves, a link column is a relationship."
}, },
@ -1067,8 +1069,10 @@
"json", "json",
"internal", "internal",
"barcodeqr", "barcodeqr",
"signature_single",
"bigint", "bigint",
"bb_reference" "bb_reference",
"bb_reference_single"
], ],
"description": "Defines the type of the column, most explain themselves, a link column is a relationship." "description": "Defines the type of the column, most explain themselves, a link column is a relationship."
}, },
@ -1285,8 +1289,10 @@
"json", "json",
"internal", "internal",
"barcodeqr", "barcodeqr",
"signature_single",
"bigint", "bigint",
"bb_reference" "bb_reference",
"bb_reference_single"
], ],
"description": "Defines the type of the column, most explain themselves, a link column is a relationship." "description": "Defines the type of the column, most explain themselves, a link column is a relationship."
}, },

View File

@ -782,8 +782,10 @@ components:
- json - json
- internal - internal
- barcodeqr - barcodeqr
- signature_single
- bigint - bigint
- bb_reference - bb_reference
- bb_reference_single
description: Defines the type of the column, most explain themselves, a link description: Defines the type of the column, most explain themselves, a link
column is a relationship. column is a relationship.
constraints: constraints:
@ -948,8 +950,10 @@ components:
- json - json
- internal - internal
- barcodeqr - barcodeqr
- signature_single
- bigint - bigint
- bb_reference - bb_reference
- bb_reference_single
description: Defines the type of the column, most explain themselves, a link description: Defines the type of the column, most explain themselves, a link
column is a relationship. column is a relationship.
constraints: constraints:
@ -1121,8 +1125,10 @@ components:
- json - json
- internal - internal
- barcodeqr - barcodeqr
- signature_single
- bigint - bigint
- bb_reference - bb_reference
- bb_reference_single
description: Defines the type of the column, most explain themselves, a link description: Defines the type of the column, most explain themselves, a link
column is a relationship. column is a relationship.
constraints: constraints:

View File

@ -1,4 +1,4 @@
import { Duration, cache, context, db, env } from "@budibase/backend-core" import { Duration, cache, db, env } from "@budibase/backend-core"
import { Database, DocumentType, Document } from "@budibase/types" import { Database, DocumentType, Document } from "@budibase/types"
export interface AppMigrationDoc extends Document { export interface AppMigrationDoc extends Document {
@ -25,8 +25,8 @@ export async function getAppMigrationVersion(appId: string): Promise<string> {
let metadata: AppMigrationDoc | undefined = await cache.get(cacheKey) let metadata: AppMigrationDoc | undefined = await cache.get(cacheKey)
// We don't want to cache in dev, in order to be able to tweak it // We don't want to cache in dev or QA in order to be able to tweak it
if (metadata && !env.isDev()) { if (metadata && !env.isDev() && !env.isQA()) {
return metadata.version return metadata.version
} }
@ -42,7 +42,10 @@ export async function getAppMigrationVersion(appId: string): Promise<string> {
version = "" version = ""
} }
// only cache if we have a valid version
if (version) {
await cache.store(cacheKey, version, EXPIRY_SECONDS) await cache.store(cacheKey, version, EXPIRY_SECONDS)
}
return version return version
} }
@ -54,8 +57,7 @@ export async function updateAppMigrationMetadata({
appId: string appId: string
version: string version: string
}): Promise<void> { }): Promise<void> {
const db = context.getAppDB() const appDb = db.getDB(appId)
let appMigrationDoc: AppMigrationDoc let appMigrationDoc: AppMigrationDoc
try { try {
@ -70,7 +72,7 @@ export async function updateAppMigrationMetadata({
version: "", version: "",
history: {}, history: {},
} }
await db.put(appMigrationDoc) await appDb.put(appMigrationDoc)
appMigrationDoc = await getFromDB(appId) appMigrationDoc = await getFromDB(appId)
} }
@ -82,7 +84,7 @@ export async function updateAppMigrationMetadata({
[version]: { runAt: new Date().toISOString() }, [version]: { runAt: new Date().toISOString() },
}, },
} }
await db.put(updatedMigrationDoc) await appDb.put(updatedMigrationDoc)
const cacheKey = getCacheKey(appId) const cacheKey = getCacheKey(appId)

View File

@ -16,7 +16,10 @@ export type AppMigration = {
export function getLatestEnabledMigrationId(migrations?: AppMigration[]) { export function getLatestEnabledMigrationId(migrations?: AppMigration[]) {
let latestMigrationId: string | undefined let latestMigrationId: string | undefined
for (let migration of migrations || MIGRATIONS) { if (!migrations) {
migrations = MIGRATIONS
}
for (let migration of migrations) {
// if a migration is disabled, all migrations after it are disabled // if a migration is disabled, all migrations after it are disabled
if (migration.disabled) { if (migration.disabled) {
break break
@ -35,8 +38,14 @@ export async function checkMissingMigrations(
next: Next, next: Next,
appId: string appId: string
) { ) {
const currentVersion = await getAppMigrationVersion(appId)
const latestMigration = getLatestEnabledMigrationId() const latestMigration = getLatestEnabledMigrationId()
// no migrations set - edge case, don't try to do anything
if (!latestMigration) {
return next()
}
const currentVersion = await getAppMigrationVersion(appId)
const queue = getAppMigrationQueue() const queue = getAppMigrationQueue()
if ( if (

View File

@ -13,8 +13,8 @@ export async function processMigrations(
) { ) {
console.log(`Processing app migration for "${appId}"`) console.log(`Processing app migration for "${appId}"`)
try { try {
// have to wrap in context, this gets the tenant from the app ID // first step - setup full context - tenancy, app and guards
await context.doInAppContext(appId, async () => { await context.doInAppMigrationContext(appId, async () => {
console.log(`Acquiring app migration lock for "${appId}"`) console.log(`Acquiring app migration lock for "${appId}"`)
await locks.doWithLock( await locks.doWithLock(
{ {
@ -23,7 +23,6 @@ export async function processMigrations(
resource: appId, resource: appId,
}, },
async () => { async () => {
await context.doInAppMigrationContext(appId, async () => {
console.log(`Lock acquired starting app migration for "${appId}"`) console.log(`Lock acquired starting app migration for "${appId}"`)
let currentVersion = await getAppMigrationVersion(appId) let currentVersion = await getAppMigrationVersion(appId)
@ -59,12 +58,10 @@ export async function processMigrations(
}) })
currentVersion = id currentVersion = id
} }
})
} }
) )
console.log(`App migration for "${appId}" processed`)
}) })
console.log(`App migration for "${appId}" processed`)
} catch (err) { } catch (err) {
logging.logAlert("Failed to run app migration", err) logging.logAlert("Failed to run app migration", err)
throw err throw err

View File

@ -2,9 +2,10 @@ import { queue, logging } from "@budibase/backend-core"
import { Job } from "bull" import { Job } from "bull"
import { MIGRATIONS } from "./migrations" import { MIGRATIONS } from "./migrations"
import { processMigrations } from "./migrationsProcessor" import { processMigrations } from "./migrationsProcessor"
import { apiEnabled } from "../features"
const MAX_ATTEMPTS = 1 const MAX_ATTEMPTS = 3
// max number of migrations to run at same time, per node
const MIGRATION_CONCURRENCY = 5
export type AppMigrationJob = { export type AppMigrationJob = {
appId: string appId: string
@ -13,10 +14,6 @@ export type AppMigrationJob = {
let appMigrationQueue: queue.Queue<AppMigrationJob> | undefined let appMigrationQueue: queue.Queue<AppMigrationJob> | undefined
export function init() { export function init() {
// only run app migrations in main API services
if (!apiEnabled()) {
return
}
appMigrationQueue = queue.createQueue<AppMigrationJob>( appMigrationQueue = queue.createQueue<AppMigrationJob>(
queue.JobQueue.APP_MIGRATION, queue.JobQueue.APP_MIGRATION,
{ {
@ -34,10 +31,10 @@ export function init() {
} }
) )
return appMigrationQueue.process(processMessage) return appMigrationQueue.process(MIGRATION_CONCURRENCY, processMessage)
} }
async function processMessage(job: Job) { async function processMessage(job: Job<AppMigrationJob>) {
const { appId } = job.data const { appId } = job.data
await processMigrations(appId, MIGRATIONS) await processMigrations(appId, MIGRATIONS)

View File

@ -4,8 +4,9 @@ import * as mongodb from "./mongodb"
import * as mysql from "./mysql" import * as mysql from "./mysql"
import * as mssql from "./mssql" import * as mssql from "./mssql"
import * as mariadb from "./mariadb" import * as mariadb from "./mariadb"
import { GenericContainer } from "testcontainers" import { GenericContainer, StartedTestContainer } from "testcontainers"
import { testContainerUtils } from "@budibase/backend-core/tests" import { testContainerUtils } from "@budibase/backend-core/tests"
import cloneDeep from "lodash/cloneDeep"
export type DatasourceProvider = () => Promise<Datasource> export type DatasourceProvider = () => Promise<Datasource>
@ -65,9 +66,39 @@ export async function rawQuery(ds: Datasource, sql: string): Promise<any> {
} }
export async function startContainer(container: GenericContainer) { export async function startContainer(container: GenericContainer) {
container = container.withReuse().withLabels({ "com.budibase": "true" }) const imageName = (container as any).imageName.string as string
const key = imageName.replaceAll("/", "-").replaceAll(":", "-")
const startedContainer = await container.start() container = container
.withReuse()
.withLabels({ "com.budibase": "true" })
.withName(key)
let startedContainer: StartedTestContainer | undefined = undefined
let lastError = undefined
for (let i = 0; i < 10; i++) {
try {
// container.start() is not an idempotent operation, calling `start`
// modifies the internal state of a GenericContainer instance such that
// the hash it uses to determine reuse changes. We need to clone the
// container before calling start to ensure that we're using the same
// reuse hash every time.
const containerCopy = cloneDeep(container)
startedContainer = await containerCopy.start()
lastError = undefined
break
} catch (e: any) {
lastError = e
await new Promise(resolve => setTimeout(resolve, 1000))
}
}
if (!startedContainer) {
if (lastError) {
throw lastError
}
throw new Error(`failed to start container: ${imageName}`)
}
const info = testContainerUtils.getContainerById(startedContainer.getId()) const info = testContainerUtils.getContainerById(startedContainer.getId())
if (!info) { if (!info) {

View File

@ -29,6 +29,9 @@ export async function getDatasource(): Promise<Datasource> {
} }
const port = (await ports).find(x => x.container === 1433)?.host const port = (await ports).find(x => x.container === 1433)?.host
if (!port) {
throw new Error("SQL Server port not found")
}
const datasource: Datasource = { const datasource: Datasource = {
type: "datasource_plus", type: "datasource_plus",

View File

@ -38,6 +38,9 @@ export async function getDatasource(): Promise<Datasource> {
} }
const port = (await ports).find(x => x.container === 3306)?.host const port = (await ports).find(x => x.container === 3306)?.host
if (!port) {
throw new Error("MySQL port not found")
}
const datasource: Datasource = { const datasource: Datasource = {
type: "datasource_plus", type: "datasource_plus",

View File

@ -21,6 +21,9 @@ export async function getDatasource(): Promise<Datasource> {
} }
const port = (await ports).find(x => x.container === 5432)?.host const port = (await ports).find(x => x.container === 5432)?.host
if (!port) {
throw new Error("Postgres port not found")
}
const datasource: Datasource = { const datasource: Datasource = {
type: "datasource_plus", type: "datasource_plus",

View File

@ -20,7 +20,7 @@ import * as pro from "@budibase/pro"
import * as api from "../api" import * as api from "../api"
import sdk from "../sdk" import sdk from "../sdk"
import { initialise as initialiseWebsockets } from "../websockets" import { initialise as initialiseWebsockets } from "../websockets"
import { automationsEnabled, printFeatures } from "../features" import { apiEnabled, automationsEnabled, printFeatures } from "../features"
import * as jsRunner from "../jsRunner" import * as jsRunner from "../jsRunner"
import Koa from "koa" import Koa from "koa"
import { Server } from "http" import { Server } from "http"
@ -70,6 +70,9 @@ export async function startup(
return return
} }
printFeatures() printFeatures()
if (env.BUDIBASE_ENVIRONMENT) {
console.log(`service running environment: "${env.BUDIBASE_ENVIRONMENT}"`)
}
STARTUP_RAN = true STARTUP_RAN = true
if (app && server && !env.CLUSTER_MODE) { if (app && server && !env.CLUSTER_MODE) {
console.log(`Budibase running on ${JSON.stringify(server.address())}`) console.log(`Budibase running on ${JSON.stringify(server.address())}`)
@ -115,10 +118,13 @@ export async function startup(
// configure events to use the pro audit log write // configure events to use the pro audit log write
// can't integrate directly into backend-core due to cyclic issues // can't integrate directly into backend-core due to cyclic issues
queuePromises.push(events.processors.init(pro.sdk.auditLogs.write)) queuePromises.push(events.processors.init(pro.sdk.auditLogs.write))
queuePromises.push(appMigrations.init()) // app migrations and automations on other service
if (automationsEnabled()) { if (automationsEnabled()) {
queuePromises.push(automations.init()) queuePromises.push(automations.init())
} }
if (apiEnabled()) {
queuePromises.push(appMigrations.init())
}
queuePromises.push(initPro()) queuePromises.push(initPro())
if (app) { if (app) {
// bring routes online as final step once everything ready // bring routes online as final step once everything ready

View File

@ -7,3 +7,4 @@ export * from "./schedule"
export * from "./templates" export * from "./templates"
export * from "./environmentVariables" export * from "./environmentVariables"
export * from "./auditLogs" export * from "./auditLogs"
export * from "./tenantInfo"

View File

@ -0,0 +1,13 @@
import { Document } from "../document"
export interface TenantInfo extends Document {
owner: {
email: string
password?: string
ssoId?: string
givenName?: string
familyName?: string
budibaseUserId?: string
}
tenantId: string
}

View File

@ -0,0 +1,10 @@
import { tenancy } from "@budibase/backend-core"
import { TenantInfo, Ctx } from "@budibase/types"
export const save = async (ctx: Ctx<TenantInfo>) => {
const response = await tenancy.saveTenantInfo(ctx.request.body)
ctx.body = {
_id: response.id,
_rev: response.rev,
}
}

View File

@ -76,6 +76,10 @@ const PUBLIC_ENDPOINTS = [
route: "/api/global/users/invite", route: "/api/global/users/invite",
method: "GET", method: "GET",
}, },
{
route: "/api/global/tenant",
method: "POST",
},
] ]
const NO_TENANCY_ENDPOINTS = [ const NO_TENANCY_ENDPOINTS = [
@ -121,6 +125,10 @@ const NO_TENANCY_ENDPOINTS = [
route: "/api/global/users/invite/:code", route: "/api/global/users/invite/:code",
method: "GET", method: "GET",
}, },
{
route: "/api/global/tenant",
method: "POST",
},
] ]
// most public endpoints are gets, but some are posts // most public endpoints are gets, but some are posts

View File

@ -0,0 +1,33 @@
import Router from "@koa/router"
import Joi from "joi"
import { auth } from "@budibase/backend-core"
import * as controller from "../../controllers/global/tenant"
import cloudRestricted from "../../../middleware/cloudRestricted"
const router: Router = new Router()
const OPTIONAL_STRING = Joi.string().optional().allow(null).allow("")
function buildTenantInfoValidation() {
return auth.joiValidator.body(
Joi.object({
owner: Joi.object({
email: Joi.string().required(),
password: OPTIONAL_STRING,
ssoId: OPTIONAL_STRING,
givenName: OPTIONAL_STRING,
familyName: OPTIONAL_STRING,
budibaseUserId: OPTIONAL_STRING,
}).required(),
tenantId: Joi.string().required(),
}).required()
)
}
router.post(
"/api/global/tenant",
cloudRestricted,
buildTenantInfoValidation(),
controller.save
)
export default router

View File

@ -0,0 +1,47 @@
import { TenantInfo } from "@budibase/types"
import { TestConfiguration } from "../../../../tests"
import { tenancy as _tenancy } from "@budibase/backend-core"
const tenancy = jest.mocked(_tenancy)
describe("/api/global/tenant", () => {
const config = new TestConfiguration()
beforeAll(async () => {
await config.beforeAll()
})
afterAll(async () => {
await config.afterAll()
})
beforeEach(() => {
jest.clearAllMocks()
})
describe("POST /api/global/tenant", () => {
it("should save the tenantInfo", async () => {
tenancy.saveTenantInfo = jest.fn().mockImplementation(async () => ({
id: "DOC_ID",
ok: true,
rev: "DOC_REV",
}))
const tenantInfo: TenantInfo = {
owner: {
email: "test@example.com",
password: "PASSWORD",
ssoId: "SSO_ID",
givenName: "Jane",
familyName: "Doe",
budibaseUserId: "USER_ID",
},
tenantId: "tenant123",
}
const response = await config.api.tenants.saveTenantInfo(tenantInfo)
expect(_tenancy.saveTenantInfo).toHaveBeenCalledTimes(1)
expect(_tenancy.saveTenantInfo).toHaveBeenCalledWith(tenantInfo)
expect(response.text).toEqual('{"_id":"DOC_ID","_rev":"DOC_REV"}')
})
})
})

View File

@ -1,6 +1,7 @@
import Router from "@koa/router" 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 tenantRoutes from "./global/tenant"
import configRoutes from "./global/configs" import configRoutes from "./global/configs"
import workspaceRoutes from "./global/workspaces" import workspaceRoutes from "./global/workspaces"
import templateRoutes from "./global/templates" import templateRoutes from "./global/templates"
@ -40,6 +41,7 @@ export const routes: Router[] = [
accountRoutes, accountRoutes,
restoreRoutes, restoreRoutes,
eventRoutes, eventRoutes,
tenantRoutes,
pro.scim, pro.scim,
] ]

View File

@ -47,6 +47,7 @@ const environment = {
SMTP_FALLBACK_ENABLED: process.env.SMTP_FALLBACK_ENABLED, SMTP_FALLBACK_ENABLED: process.env.SMTP_FALLBACK_ENABLED,
DISABLE_DEVELOPER_LICENSE: process.env.DISABLE_DEVELOPER_LICENSE, DISABLE_DEVELOPER_LICENSE: process.env.DISABLE_DEVELOPER_LICENSE,
SQS_SEARCH_ENABLE: process.env.SQS_SEARCH_ENABLE, SQS_SEARCH_ENABLE: process.env.SQS_SEARCH_ENABLE,
BUDIBASE_ENVIRONMENT: process.env.BUDIBASE_ENVIRONMENT,
// smtp // smtp
SMTP_USER: process.env.SMTP_USER, SMTP_USER: process.env.SMTP_USER,
SMTP_PASSWORD: process.env.SMTP_PASSWORD, SMTP_PASSWORD: process.env.SMTP_PASSWORD,

View File

@ -88,7 +88,11 @@ const shutdown = () => {
} }
export default server.listen(parseInt(env.PORT || "4002"), async () => { export default server.listen(parseInt(env.PORT || "4002"), async () => {
console.log(`Worker running on ${JSON.stringify(server.address())}`) let startupLog = `Worker running on ${JSON.stringify(server.address())}`
if (env.BUDIBASE_ENVIRONMENT) {
startupLog = `${startupLog} - environment: "${env.BUDIBASE_ENVIRONMENT}"`
}
console.log(startupLog)
await initPro() await initPro()
await redis.clients.init() await redis.clients.init()
cache.docWritethrough.init() cache.docWritethrough.init()

View File

@ -1,3 +1,4 @@
import { TenantInfo } from "@budibase/types"
import TestConfiguration from "../TestConfiguration" import TestConfiguration from "../TestConfiguration"
import { TestAPI, TestAPIOpts } from "./base" import { TestAPI, TestAPIOpts } from "./base"
@ -14,4 +15,12 @@ export class TenantAPI extends TestAPI {
.set(opts?.headers) .set(opts?.headers)
.expect(opts?.status ? opts.status : 204) .expect(opts?.status ? opts.status : 204)
} }
saveTenantInfo = (tenantInfo: TenantInfo) => {
return this.request
.post("/api/global/tenant")
.set(this.config.internalAPIHeaders())
.send(tenantInfo)
.expect(200)
}
} }

View File

@ -3483,10 +3483,10 @@
dependencies: dependencies:
lodash "^4.17.21" lodash "^4.17.21"
"@koa/cors@^3.1.0": "@koa/cors@^5.0.0":
version "3.4.3" version "5.0.0"
resolved "https://registry.yarnpkg.com/@koa/cors/-/cors-3.4.3.tgz#d669ee6e8d6e4f0ec4a7a7b0a17e7a3ed3752ebb" resolved "https://registry.yarnpkg.com/@koa/cors/-/cors-5.0.0.tgz#0029b5f057fa0d0ae0e37dd2c89ece315a0daffd"
integrity sha512-WPXQUaAeAMVaLTEFpoq3T2O1C+FstkjJnDQqy95Ck1UdILajsRhu6mhJ8H2f4NFPRBoCNN+qywTJfq/gGki5mw== integrity sha512-x/iUDjcS90W69PryLDIMgFyV21YLTnG9zOpPXS7Bkt2b8AsY3zZsIpOLBkYr9fBcF3HbkKaER5hOBZLfpLgYNw==
dependencies: dependencies:
vary "^1.1.2" vary "^1.1.2"
@ -5797,10 +5797,10 @@
"@types/koa-compose" "*" "@types/koa-compose" "*"
"@types/node" "*" "@types/node" "*"
"@types/koa__cors@^3.1.1": "@types/koa__cors@^5.0.0":
version "3.3.1" version "5.0.0"
resolved "https://registry.yarnpkg.com/@types/koa__cors/-/koa__cors-3.3.1.tgz#0ec7543c4c620fd23451bfdd3e21b9a6aadedccd" resolved "https://registry.yarnpkg.com/@types/koa__cors/-/koa__cors-5.0.0.tgz#74567a045b599266e2cd3940cef96cedecc2ef1f"
integrity sha512-aFGYhTFW7651KhmZZ05VG0QZJre7QxBxDj2LF1lf6GA/wSXEfKVAJxiQQWzRV4ZoMzQIO8vJBXKsUcRuvYK9qw== integrity sha512-LCk/n25Obq5qlernGOK/2LUwa/2YJb2lxHUkkvYFDOpLXlVI6tKcdfCHRBQnOY4LwH6el5WOLs6PD/a8Uzau6g==
dependencies: dependencies:
"@types/koa" "*" "@types/koa" "*"
@ -16299,10 +16299,10 @@ node-source-walk@^5.0.0:
dependencies: dependencies:
"@babel/parser" "^7.0.0" "@babel/parser" "^7.0.0"
nodemailer@6.7.2: nodemailer@6.9.13:
version "6.7.2" version "6.9.13"
resolved "https://registry.yarnpkg.com/nodemailer/-/nodemailer-6.7.2.tgz#44b2ad5f7ed71b7067f7a21c4fedabaec62b85e0" resolved "https://registry.yarnpkg.com/nodemailer/-/nodemailer-6.9.13.tgz#5b292bf1e92645f4852ca872c56a6ba6c4a3d3d6"
integrity sha512-Dz7zVwlef4k5R71fdmxwR8Q39fiboGbu3xgswkzGwczUfjp873rVxt1O46+Fh0j1ORnAC6L9+heI8uUpO6DT7Q== integrity sha512-7o38Yogx6krdoBf3jCAqnIN4oSQFx+fMa0I7dK1D+me9kBxx12D+/33wSb+fhOCtIxvYJ+4x4IMEhmhCKfAiOA==
nodemailer@6.9.9: nodemailer@6.9.9:
version "6.9.9" version "6.9.9"