Merge branch 'master' of github.com:budibase/budibase into fix/aws-session-token-s3-ver2
This commit is contained in:
commit
a140a002e2
|
@ -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
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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."
|
||||||
},
|
},
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
@ -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 (
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
|
@ -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,
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
||||||
|
|
|
@ -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
|
|
@ -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"}')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
|
@ -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,
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
24
yarn.lock
24
yarn.lock
|
@ -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"
|
||||||
|
|
Loading…
Reference in New Issue