Back populate no-op migrations on new app and tenant create
This commit is contained in:
parent
9c671fd706
commit
eb0214a231
|
@ -1,5 +1,5 @@
|
||||||
import env from "../environment"
|
import env from "../environment"
|
||||||
import * as tenancy from "../tenancy"
|
import tenancy from "../tenancy"
|
||||||
import * as dbUtils from "../db/utils"
|
import * as dbUtils from "../db/utils"
|
||||||
import { Configs } from "../constants"
|
import { Configs } from "../constants"
|
||||||
import { withCache, TTL, CacheKeys } from "../cache/generic"
|
import { withCache, TTL, CacheKeys } from "../cache/generic"
|
||||||
|
|
|
@ -12,6 +12,7 @@ import sessions from "./security/sessions"
|
||||||
import deprovisioning from "./context/deprovision"
|
import deprovisioning from "./context/deprovision"
|
||||||
import auth from "./auth"
|
import auth from "./auth"
|
||||||
import constants from "./constants"
|
import constants from "./constants"
|
||||||
|
import * as dbConstants from "./db/constants"
|
||||||
|
|
||||||
// mimic the outer package exports
|
// mimic the outer package exports
|
||||||
import * as db from "./pkg/db"
|
import * as db from "./pkg/db"
|
||||||
|
@ -20,7 +21,6 @@ import * as utils from "./pkg/utils"
|
||||||
import redis from "./pkg/redis"
|
import redis from "./pkg/redis"
|
||||||
import cache from "./pkg/cache"
|
import cache from "./pkg/cache"
|
||||||
import context from "./pkg/context"
|
import context from "./pkg/context"
|
||||||
const StaticDatabases = db.StaticDatabases
|
|
||||||
|
|
||||||
const init = (opts: any = {}) => {
|
const init = (opts: any = {}) => {
|
||||||
db.init(opts.db)
|
db.init(opts.db)
|
||||||
|
@ -28,8 +28,8 @@ const init = (opts: any = {}) => {
|
||||||
|
|
||||||
const core = {
|
const core = {
|
||||||
init,
|
init,
|
||||||
StaticDatabases,
|
|
||||||
db,
|
db,
|
||||||
|
...dbConstants,
|
||||||
redis,
|
redis,
|
||||||
objectStore,
|
objectStore,
|
||||||
utils,
|
utils,
|
||||||
|
@ -37,6 +37,7 @@ const core = {
|
||||||
cache,
|
cache,
|
||||||
auth,
|
auth,
|
||||||
constants,
|
constants,
|
||||||
|
...constants,
|
||||||
migrations,
|
migrations,
|
||||||
env,
|
env,
|
||||||
accounts,
|
accounts,
|
||||||
|
|
|
@ -0,0 +1,40 @@
|
||||||
|
import {
|
||||||
|
MigrationType,
|
||||||
|
MigrationName,
|
||||||
|
MigrationDefinition,
|
||||||
|
} from "@budibase/types"
|
||||||
|
|
||||||
|
export const DEFINITIONS: MigrationDefinition[] = [
|
||||||
|
{
|
||||||
|
type: MigrationType.GLOBAL,
|
||||||
|
name: MigrationName.USER_EMAIL_VIEW_CASING,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: MigrationType.GLOBAL,
|
||||||
|
name: MigrationName.QUOTAS_1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: MigrationType.APP,
|
||||||
|
name: MigrationName.APP_URLS,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: MigrationType.GLOBAL,
|
||||||
|
name: MigrationName.DEVELOPER_QUOTA,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: MigrationType.GLOBAL,
|
||||||
|
name: MigrationName.PUBLISHED_APP_QUOTA,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: MigrationType.APP,
|
||||||
|
name: MigrationName.EVENT_APP_BACKFILL,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: MigrationType.GLOBAL,
|
||||||
|
name: MigrationName.EVENT_GLOBAL_BACKFILL,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: MigrationType.INSTALLATION,
|
||||||
|
name: MigrationName.EVENT_INSTALLATION_BACKFILL,
|
||||||
|
},
|
||||||
|
]
|
|
@ -1,163 +0,0 @@
|
||||||
const { DEFAULT_TENANT_ID } = require("../constants")
|
|
||||||
const { doWithDB } = require("../db")
|
|
||||||
const { DocumentTypes, StaticDatabases } = require("../db/constants")
|
|
||||||
const { getAllApps } = require("../db/utils")
|
|
||||||
const environment = require("../environment")
|
|
||||||
const {
|
|
||||||
doInTenant,
|
|
||||||
getTenantIds,
|
|
||||||
getGlobalDBName,
|
|
||||||
getTenantId,
|
|
||||||
} = require("../tenancy")
|
|
||||||
const context = require("../context")
|
|
||||||
|
|
||||||
exports.MIGRATION_TYPES = {
|
|
||||||
GLOBAL: "global", // run once per tenant, recorded in global db, global db is provided as an argument
|
|
||||||
APP: "app", // run per app, recorded in each app db, app db is provided as an argument
|
|
||||||
INSTALLATION: "installation", // run once, recorded in global info db, global info db is provided as an argument
|
|
||||||
}
|
|
||||||
|
|
||||||
exports.getMigrationsDoc = async db => {
|
|
||||||
// get the migrations doc
|
|
||||||
try {
|
|
||||||
return await db.get(DocumentTypes.MIGRATIONS)
|
|
||||||
} catch (err) {
|
|
||||||
if (err.status && err.status === 404) {
|
|
||||||
return { _id: DocumentTypes.MIGRATIONS }
|
|
||||||
} else {
|
|
||||||
console.error(err)
|
|
||||||
throw err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
exports.runMigration = async (migration, options = {}) => {
|
|
||||||
const migrationType = migration.type
|
|
||||||
let tenantId
|
|
||||||
if (migrationType !== exports.MIGRATION_TYPES.INSTALLATION) {
|
|
||||||
tenantId = getTenantId()
|
|
||||||
}
|
|
||||||
const migrationName = migration.name
|
|
||||||
const silent = migration.silent
|
|
||||||
|
|
||||||
const log = message => {
|
|
||||||
if (!silent) {
|
|
||||||
console.log(message)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// get the db to store the migration in
|
|
||||||
let dbNames
|
|
||||||
if (migrationType === exports.MIGRATION_TYPES.GLOBAL) {
|
|
||||||
dbNames = [getGlobalDBName()]
|
|
||||||
} else if (migrationType === exports.MIGRATION_TYPES.APP) {
|
|
||||||
const apps = await getAllApps(migration.opts)
|
|
||||||
dbNames = apps.map(app => app.appId)
|
|
||||||
} else if (migrationType === exports.MIGRATION_TYPES.INSTALLATION) {
|
|
||||||
dbNames = [StaticDatabases.PLATFORM_INFO.name]
|
|
||||||
} else {
|
|
||||||
throw new Error(`Unrecognised migration type [${migrationType}]`)
|
|
||||||
}
|
|
||||||
|
|
||||||
const length = dbNames.length
|
|
||||||
let count = 0
|
|
||||||
|
|
||||||
// run the migration against each db
|
|
||||||
for (const dbName of dbNames) {
|
|
||||||
count++
|
|
||||||
const lengthStatement = length > 1 ? `[${count}/${length}]` : ""
|
|
||||||
|
|
||||||
await doWithDB(dbName, async db => {
|
|
||||||
try {
|
|
||||||
const doc = await exports.getMigrationsDoc(db)
|
|
||||||
|
|
||||||
// exit if the migration has been performed already
|
|
||||||
if (doc[migrationName]) {
|
|
||||||
if (
|
|
||||||
options.force &&
|
|
||||||
options.force[migrationType] &&
|
|
||||||
options.force[migrationType].includes(migrationName)
|
|
||||||
) {
|
|
||||||
log(
|
|
||||||
`[Tenant: ${tenantId}] [Migration: ${migrationName}] [DB: ${dbName}] Forcing`
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
// the migration has already been performed
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
log(
|
|
||||||
`[Tenant: ${tenantId}] [Migration: ${migrationName}] [DB: ${dbName}] Running ${lengthStatement}`
|
|
||||||
)
|
|
||||||
|
|
||||||
if (migration.preventRetry) {
|
|
||||||
// eagerly set the completion date
|
|
||||||
// so that we never run this migration twice even upon failure
|
|
||||||
doc[migrationName] = Date.now()
|
|
||||||
const response = await db.put(doc)
|
|
||||||
doc._rev = response.rev
|
|
||||||
}
|
|
||||||
|
|
||||||
// run the migration with tenant context
|
|
||||||
if (migrationType === exports.MIGRATION_TYPES.APP) {
|
|
||||||
await context.doInAppContext(db.name, async () => {
|
|
||||||
await migration.fn(db)
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
await migration.fn(db)
|
|
||||||
}
|
|
||||||
|
|
||||||
log(
|
|
||||||
`[Tenant: ${tenantId}] [Migration: ${migrationName}] [DB: ${dbName}] Complete`
|
|
||||||
)
|
|
||||||
|
|
||||||
// mark as complete
|
|
||||||
doc[migrationName] = Date.now()
|
|
||||||
await db.put(doc)
|
|
||||||
} catch (err) {
|
|
||||||
console.error(
|
|
||||||
`[Tenant: ${tenantId}] [Migration: ${migrationName}] [DB: ${dbName}] Error: `,
|
|
||||||
err
|
|
||||||
)
|
|
||||||
throw err
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
exports.runMigrations = async (migrations, options = {}) => {
|
|
||||||
let tenantIds
|
|
||||||
if (environment.MULTI_TENANCY) {
|
|
||||||
if (!options.tenantIds || !options.tenantIds.length) {
|
|
||||||
// run for all tenants
|
|
||||||
tenantIds = await getTenantIds()
|
|
||||||
} else {
|
|
||||||
tenantIds = options.tenantIds
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// single tenancy
|
|
||||||
tenantIds = [DEFAULT_TENANT_ID]
|
|
||||||
}
|
|
||||||
|
|
||||||
if (tenantIds.length > 1) {
|
|
||||||
console.log(`Checking migrations for ${tenantIds.length} tenants`)
|
|
||||||
} else {
|
|
||||||
console.log("Checking migrations")
|
|
||||||
}
|
|
||||||
|
|
||||||
let count = 0
|
|
||||||
// for all tenants
|
|
||||||
for (const tenantId of tenantIds) {
|
|
||||||
count++
|
|
||||||
if (tenantIds.length > 1) {
|
|
||||||
console.log(`Progress [${count}/${tenantIds.length}]`)
|
|
||||||
}
|
|
||||||
// for all migrations
|
|
||||||
for (const migration of migrations) {
|
|
||||||
// run the migration
|
|
||||||
await doInTenant(tenantId, () => exports.runMigration(migration, options))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
console.log("Migrations complete")
|
|
||||||
}
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
export * from "./migrations"
|
||||||
|
export * from "./definitions"
|
|
@ -0,0 +1,189 @@
|
||||||
|
import { DEFAULT_TENANT_ID } from "../constants"
|
||||||
|
import { doWithDB } from "../db"
|
||||||
|
import { DocumentTypes, StaticDatabases } from "../db/constants"
|
||||||
|
import { getAllApps } from "../db/utils"
|
||||||
|
import environment from "../environment"
|
||||||
|
import {
|
||||||
|
doInTenant,
|
||||||
|
getTenantIds,
|
||||||
|
getGlobalDBName,
|
||||||
|
getTenantId,
|
||||||
|
} from "../tenancy"
|
||||||
|
import context from "../context"
|
||||||
|
import { DEFINITIONS } from "."
|
||||||
|
import {
|
||||||
|
Migration,
|
||||||
|
MigrationOptions,
|
||||||
|
MigrationType,
|
||||||
|
MigrationNoOpOptions,
|
||||||
|
} from "@budibase/types"
|
||||||
|
|
||||||
|
export const getMigrationsDoc = async (db: any) => {
|
||||||
|
// get the migrations doc
|
||||||
|
try {
|
||||||
|
return await db.get(DocumentTypes.MIGRATIONS)
|
||||||
|
} catch (err: any) {
|
||||||
|
if (err.status && err.status === 404) {
|
||||||
|
return { _id: DocumentTypes.MIGRATIONS }
|
||||||
|
} else {
|
||||||
|
console.error(err)
|
||||||
|
throw err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const backPopulateMigrations = async (opts: MigrationNoOpOptions) => {
|
||||||
|
// filter migrations to the type and populate a no-op migration
|
||||||
|
const migrations: Migration[] = DEFINITIONS.filter(
|
||||||
|
def => def.type === opts.type
|
||||||
|
).map(d => ({ ...d, fn: () => {} }))
|
||||||
|
await runMigrations(migrations, { noOp: opts })
|
||||||
|
}
|
||||||
|
|
||||||
|
export const runMigration = async (
|
||||||
|
migration: Migration,
|
||||||
|
options: MigrationOptions = {}
|
||||||
|
) => {
|
||||||
|
const migrationType = migration.type
|
||||||
|
let tenantId: string
|
||||||
|
if (migrationType !== MigrationType.INSTALLATION) {
|
||||||
|
tenantId = getTenantId()
|
||||||
|
}
|
||||||
|
const migrationName = migration.name
|
||||||
|
const silent = migration.silent
|
||||||
|
|
||||||
|
const log = (message: string) => {
|
||||||
|
if (!silent) {
|
||||||
|
console.log(message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// get the db to store the migration in
|
||||||
|
let dbNames
|
||||||
|
if (migrationType === MigrationType.GLOBAL) {
|
||||||
|
dbNames = [getGlobalDBName()]
|
||||||
|
} else if (migrationType === MigrationType.APP) {
|
||||||
|
if (options.noOp) {
|
||||||
|
dbNames = [options.noOp.appId]
|
||||||
|
} else {
|
||||||
|
const apps = await getAllApps(migration.appOpts)
|
||||||
|
dbNames = apps.map(app => app.appId)
|
||||||
|
}
|
||||||
|
} else if (migrationType === MigrationType.INSTALLATION) {
|
||||||
|
dbNames = [StaticDatabases.PLATFORM_INFO.name]
|
||||||
|
} else {
|
||||||
|
throw new Error(`Unrecognised migration type [${migrationType}]`)
|
||||||
|
}
|
||||||
|
|
||||||
|
const length = dbNames.length
|
||||||
|
let count = 0
|
||||||
|
|
||||||
|
// run the migration against each db
|
||||||
|
for (const dbName of dbNames) {
|
||||||
|
count++
|
||||||
|
const lengthStatement = length > 1 ? `[${count}/${length}]` : ""
|
||||||
|
|
||||||
|
await doWithDB(dbName, async (db: any) => {
|
||||||
|
try {
|
||||||
|
const doc = await exports.getMigrationsDoc(db)
|
||||||
|
|
||||||
|
// the migration has already been run
|
||||||
|
if (doc[migrationName]) {
|
||||||
|
// check for force
|
||||||
|
if (
|
||||||
|
options.force &&
|
||||||
|
options.force[migrationType] &&
|
||||||
|
options.force[migrationType].includes(migrationName)
|
||||||
|
) {
|
||||||
|
log(
|
||||||
|
`[Tenant: ${tenantId}] [Migration: ${migrationName}] [DB: ${dbName}] Forcing`
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
// no force, exit
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if the migration is not a no-op
|
||||||
|
if (!options.noOp) {
|
||||||
|
log(
|
||||||
|
`[Tenant: ${tenantId}] [Migration: ${migrationName}] [DB: ${dbName}] Running ${lengthStatement}`
|
||||||
|
)
|
||||||
|
|
||||||
|
if (migration.preventRetry) {
|
||||||
|
// eagerly set the completion date
|
||||||
|
// so that we never run this migration twice even upon failure
|
||||||
|
doc[migrationName] = Date.now()
|
||||||
|
const response = await db.put(doc)
|
||||||
|
doc._rev = response.rev
|
||||||
|
}
|
||||||
|
|
||||||
|
// run the migration
|
||||||
|
if (migrationType === MigrationType.APP) {
|
||||||
|
await context.doInAppContext(db.name, async () => {
|
||||||
|
await migration.fn(db)
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
await migration.fn(db)
|
||||||
|
}
|
||||||
|
|
||||||
|
log(
|
||||||
|
`[Tenant: ${tenantId}] [Migration: ${migrationName}] [DB: ${dbName}] Complete`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// mark as complete
|
||||||
|
doc[migrationName] = Date.now()
|
||||||
|
await db.put(doc)
|
||||||
|
} catch (err) {
|
||||||
|
console.error(
|
||||||
|
`[Tenant: ${tenantId}] [Migration: ${migrationName}] [DB: ${dbName}] Error: `,
|
||||||
|
err
|
||||||
|
)
|
||||||
|
throw err
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const runMigrations = async (
|
||||||
|
migrations: Migration[],
|
||||||
|
options: MigrationOptions = {}
|
||||||
|
) => {
|
||||||
|
let tenantIds
|
||||||
|
|
||||||
|
if (environment.MULTI_TENANCY) {
|
||||||
|
if (options.noOp) {
|
||||||
|
tenantIds = [options.noOp.tenantId]
|
||||||
|
} else if (!options.tenantIds || !options.tenantIds.length) {
|
||||||
|
// run for all tenants
|
||||||
|
tenantIds = await getTenantIds()
|
||||||
|
} else {
|
||||||
|
tenantIds = options.tenantIds
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// single tenancy
|
||||||
|
tenantIds = [DEFAULT_TENANT_ID]
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tenantIds.length > 1) {
|
||||||
|
console.log(`Checking migrations for ${tenantIds.length} tenants`)
|
||||||
|
} else {
|
||||||
|
console.log("Checking migrations")
|
||||||
|
}
|
||||||
|
|
||||||
|
let count = 0
|
||||||
|
// for all tenants
|
||||||
|
for (const tenantId of tenantIds) {
|
||||||
|
count++
|
||||||
|
if (tenantIds.length > 1) {
|
||||||
|
console.log(`Progress [${count}/${tenantIds.length}]`)
|
||||||
|
}
|
||||||
|
// for all migrations
|
||||||
|
for (const migration of migrations) {
|
||||||
|
// run the migration
|
||||||
|
await doInTenant(tenantId, () => runMigration(migration, options))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
console.log("Migrations complete")
|
||||||
|
}
|
|
@ -1,4 +0,0 @@
|
||||||
module.exports = {
|
|
||||||
...require("../context"),
|
|
||||||
...require("./tenancy"),
|
|
||||||
}
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
import * as context from "../context"
|
||||||
|
import * as tenancy from "./tenancy"
|
||||||
|
|
||||||
|
const pkg = {
|
||||||
|
...context,
|
||||||
|
...tenancy,
|
||||||
|
}
|
||||||
|
|
||||||
|
export = pkg
|
|
@ -1,18 +1,18 @@
|
||||||
const { doWithDB } = require("../db")
|
import { doWithDB } from "../db"
|
||||||
const { StaticDatabases } = require("../db/constants")
|
import { StaticDatabases } from "../db/constants"
|
||||||
const { baseGlobalDBName } = require("./utils")
|
import { baseGlobalDBName } from "./utils"
|
||||||
const {
|
import {
|
||||||
getTenantId,
|
getTenantId,
|
||||||
DEFAULT_TENANT_ID,
|
DEFAULT_TENANT_ID,
|
||||||
isMultiTenant,
|
isMultiTenant,
|
||||||
getTenantIDFromAppID,
|
getTenantIDFromAppID,
|
||||||
} = require("../context")
|
} from "../context"
|
||||||
const env = require("../environment")
|
import env from "../environment"
|
||||||
|
|
||||||
const TENANT_DOC = StaticDatabases.PLATFORM_INFO.docs.tenants
|
const TENANT_DOC = StaticDatabases.PLATFORM_INFO.docs.tenants
|
||||||
const PLATFORM_INFO_DB = StaticDatabases.PLATFORM_INFO.name
|
const PLATFORM_INFO_DB = StaticDatabases.PLATFORM_INFO.name
|
||||||
|
|
||||||
exports.addTenantToUrl = url => {
|
export const addTenantToUrl = (url: string) => {
|
||||||
const tenantId = getTenantId()
|
const tenantId = getTenantId()
|
||||||
|
|
||||||
if (isMultiTenant()) {
|
if (isMultiTenant()) {
|
||||||
|
@ -23,8 +23,8 @@ exports.addTenantToUrl = url => {
|
||||||
return url
|
return url
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.doesTenantExist = async tenantId => {
|
export const doesTenantExist = async (tenantId: string) => {
|
||||||
return doWithDB(PLATFORM_INFO_DB, async db => {
|
return doWithDB(PLATFORM_INFO_DB, async (db: any) => {
|
||||||
let tenants
|
let tenants
|
||||||
try {
|
try {
|
||||||
tenants = await db.get(TENANT_DOC)
|
tenants = await db.get(TENANT_DOC)
|
||||||
|
@ -40,9 +40,14 @@ exports.doesTenantExist = async tenantId => {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.tryAddTenant = async (tenantId, userId, email) => {
|
export const tryAddTenant = async (
|
||||||
return doWithDB(PLATFORM_INFO_DB, async db => {
|
tenantId: string,
|
||||||
const getDoc = async id => {
|
userId: string,
|
||||||
|
email: string,
|
||||||
|
afterCreateTenant: () => Promise<void>
|
||||||
|
) => {
|
||||||
|
return doWithDB(PLATFORM_INFO_DB, async (db: any) => {
|
||||||
|
const getDoc = async (id: string) => {
|
||||||
if (!id) {
|
if (!id) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
@ -76,12 +81,13 @@ exports.tryAddTenant = async (tenantId, userId, email) => {
|
||||||
if (tenants.tenantIds.indexOf(tenantId) === -1) {
|
if (tenants.tenantIds.indexOf(tenantId) === -1) {
|
||||||
tenants.tenantIds.push(tenantId)
|
tenants.tenantIds.push(tenantId)
|
||||||
promises.push(db.put(tenants))
|
promises.push(db.put(tenants))
|
||||||
|
await afterCreateTenant()
|
||||||
}
|
}
|
||||||
await Promise.all(promises)
|
await Promise.all(promises)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.getGlobalDBName = (tenantId = null) => {
|
export const getGlobalDBName = (tenantId?: string) => {
|
||||||
// tenant ID can be set externally, for example user API where
|
// tenant ID can be set externally, for example user API where
|
||||||
// new tenants are being created, this may be the case
|
// new tenants are being created, this may be the case
|
||||||
if (!tenantId) {
|
if (!tenantId) {
|
||||||
|
@ -90,12 +96,12 @@ exports.getGlobalDBName = (tenantId = null) => {
|
||||||
return baseGlobalDBName(tenantId)
|
return baseGlobalDBName(tenantId)
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.doWithGlobalDB = (tenantId, cb) => {
|
export const doWithGlobalDB = (tenantId: string, cb: any) => {
|
||||||
return doWithDB(exports.getGlobalDBName(tenantId), cb)
|
return doWithDB(getGlobalDBName(tenantId), cb)
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.lookupTenantId = async userId => {
|
export const lookupTenantId = async (userId: string) => {
|
||||||
return doWithDB(StaticDatabases.PLATFORM_INFO.name, async db => {
|
return doWithDB(StaticDatabases.PLATFORM_INFO.name, async (db: any) => {
|
||||||
let tenantId = env.MULTI_TENANCY ? DEFAULT_TENANT_ID : null
|
let tenantId = env.MULTI_TENANCY ? DEFAULT_TENANT_ID : null
|
||||||
try {
|
try {
|
||||||
const doc = await db.get(userId)
|
const doc = await db.get(userId)
|
||||||
|
@ -110,8 +116,8 @@ exports.lookupTenantId = async userId => {
|
||||||
}
|
}
|
||||||
|
|
||||||
// lookup, could be email or userId, either will return a doc
|
// lookup, could be email or userId, either will return a doc
|
||||||
exports.getTenantUser = async identifier => {
|
export const getTenantUser = async (identifier: string) => {
|
||||||
return doWithDB(PLATFORM_INFO_DB, async db => {
|
return doWithDB(PLATFORM_INFO_DB, async (db: any) => {
|
||||||
try {
|
try {
|
||||||
return await db.get(identifier)
|
return await db.get(identifier)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
@ -120,7 +126,7 @@ exports.getTenantUser = async identifier => {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.isUserInAppTenant = (appId, user = null) => {
|
export const isUserInAppTenant = (appId: string, user: any) => {
|
||||||
let userTenantId
|
let userTenantId
|
||||||
if (user) {
|
if (user) {
|
||||||
userTenantId = user.tenantId || DEFAULT_TENANT_ID
|
userTenantId = user.tenantId || DEFAULT_TENANT_ID
|
||||||
|
@ -131,8 +137,8 @@ exports.isUserInAppTenant = (appId, user = null) => {
|
||||||
return tenantId === userTenantId
|
return tenantId === userTenantId
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.getTenantIds = async () => {
|
export const getTenantIds = async () => {
|
||||||
return doWithDB(PLATFORM_INFO_DB, async db => {
|
return doWithDB(PLATFORM_INFO_DB, async (db: any) => {
|
||||||
let tenants
|
let tenants
|
||||||
try {
|
try {
|
||||||
tenants = await db.get(TENANT_DOC)
|
tenants = await db.get(TENANT_DOC)
|
|
@ -52,8 +52,8 @@ const {
|
||||||
} = require("@budibase/backend-core/context")
|
} = require("@budibase/backend-core/context")
|
||||||
import { getUniqueRows } from "../../utilities/usageQuota/rows"
|
import { getUniqueRows } from "../../utilities/usageQuota/rows"
|
||||||
import { quotas } from "@budibase/pro"
|
import { quotas } from "@budibase/pro"
|
||||||
import { errors, events } from "@budibase/backend-core"
|
import { errors, events, migrations } from "@budibase/backend-core"
|
||||||
import { App } from "@budibase/types"
|
import { App, MigrationType } from "@budibase/types"
|
||||||
|
|
||||||
const URL_REGEX_SLASH = /\/|\\/g
|
const URL_REGEX_SLASH = /\/|\\/g
|
||||||
|
|
||||||
|
@ -319,6 +319,12 @@ const creationEvents = async (request: any, app: App) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
const appPostCreate = async (ctx: any, app: App) => {
|
const appPostCreate = async (ctx: any, app: App) => {
|
||||||
|
const tenantId = getTenantId()
|
||||||
|
await migrations.backPopulateMigrations({
|
||||||
|
type: MigrationType.APP,
|
||||||
|
tenantId,
|
||||||
|
appId: app.appId,
|
||||||
|
})
|
||||||
await creationEvents(ctx.request, app)
|
await creationEvents(ctx.request, app)
|
||||||
// app import & template creation
|
// app import & template creation
|
||||||
if (ctx.request.body.useTemplate === "true") {
|
if (ctx.request.body.useTemplate === "true") {
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
import { migrations, redis } from "@budibase/backend-core"
|
import { migrations, redis } from "@budibase/backend-core"
|
||||||
|
import { Migration, MigrationOptions, MigrationName } from "@budibase/types"
|
||||||
|
import env from "../environment"
|
||||||
|
|
||||||
// migration functions
|
// migration functions
|
||||||
import * as userEmailViewCasing from "./functions/userEmailViewCasing"
|
import * as userEmailViewCasing from "./functions/userEmailViewCasing"
|
||||||
|
@ -7,83 +9,88 @@ import * as appUrls from "./functions/appUrls"
|
||||||
import * as developerQuota from "./functions/developerQuota"
|
import * as developerQuota from "./functions/developerQuota"
|
||||||
import * as publishedAppsQuota from "./functions/publishedAppsQuota"
|
import * as publishedAppsQuota from "./functions/publishedAppsQuota"
|
||||||
import * as backfill from "./functions/backfill"
|
import * as backfill from "./functions/backfill"
|
||||||
import env from "../environment"
|
|
||||||
|
|
||||||
export interface Migration {
|
|
||||||
type: string
|
|
||||||
name: string
|
|
||||||
opts?: object
|
|
||||||
fn: Function
|
|
||||||
silent?: boolean
|
|
||||||
preventRetry?: boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* e.g.
|
* Populate the migration function and additional configuration from
|
||||||
* {
|
* the static migration definitions.
|
||||||
* tenantIds: ['bb'],
|
|
||||||
* force: {
|
|
||||||
* global: ['quota_1']
|
|
||||||
* }
|
|
||||||
* }
|
|
||||||
*/
|
*/
|
||||||
export interface MigrationOptions {
|
export const buildMigrations = () => {
|
||||||
tenantIds?: string[]
|
const definitions = migrations.DEFINITIONS
|
||||||
force?: {
|
const serverMigrations: Migration[] = []
|
||||||
[type: string]: string[]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const MIGRATIONS: Migration[] = [
|
for (const definition of definitions) {
|
||||||
{
|
switch (definition.name) {
|
||||||
type: migrations.MIGRATION_TYPES.GLOBAL,
|
case MigrationName.USER_EMAIL_VIEW_CASING: {
|
||||||
name: "user_email_view_casing",
|
serverMigrations.push({
|
||||||
|
...definition,
|
||||||
fn: userEmailViewCasing.run,
|
fn: userEmailViewCasing.run,
|
||||||
},
|
})
|
||||||
{
|
break
|
||||||
type: migrations.MIGRATION_TYPES.GLOBAL,
|
}
|
||||||
name: "quotas_1",
|
case MigrationName.QUOTAS_1: {
|
||||||
|
serverMigrations.push({
|
||||||
|
...definition,
|
||||||
fn: quota1.run,
|
fn: quota1.run,
|
||||||
},
|
})
|
||||||
{
|
break
|
||||||
type: migrations.MIGRATION_TYPES.APP,
|
}
|
||||||
name: "app_urls",
|
case MigrationName.APP_URLS: {
|
||||||
opts: { all: true },
|
serverMigrations.push({
|
||||||
|
...definition,
|
||||||
|
appOpts: { all: true },
|
||||||
fn: appUrls.run,
|
fn: appUrls.run,
|
||||||
},
|
})
|
||||||
{
|
break
|
||||||
type: migrations.MIGRATION_TYPES.GLOBAL,
|
}
|
||||||
name: "developer_quota",
|
case MigrationName.DEVELOPER_QUOTA: {
|
||||||
|
serverMigrations.push({
|
||||||
|
...definition,
|
||||||
fn: developerQuota.run,
|
fn: developerQuota.run,
|
||||||
},
|
})
|
||||||
{
|
break
|
||||||
type: migrations.MIGRATION_TYPES.GLOBAL,
|
}
|
||||||
name: "published_apps_quota",
|
case MigrationName.PUBLISHED_APP_QUOTA: {
|
||||||
|
serverMigrations.push({
|
||||||
|
...definition,
|
||||||
fn: publishedAppsQuota.run,
|
fn: publishedAppsQuota.run,
|
||||||
},
|
})
|
||||||
{
|
break
|
||||||
type: migrations.MIGRATION_TYPES.APP,
|
}
|
||||||
name: "event_app_backfill",
|
case MigrationName.EVENT_APP_BACKFILL: {
|
||||||
opts: { all: true },
|
serverMigrations.push({
|
||||||
|
...definition,
|
||||||
|
appOpts: { all: true },
|
||||||
fn: backfill.app.run,
|
fn: backfill.app.run,
|
||||||
silent: !!env.SELF_HOSTED, // reduce noisy logging
|
silent: !!env.SELF_HOSTED, // reduce noisy logging
|
||||||
preventRetry: !!env.SELF_HOSTED, // only ever run once
|
preventRetry: !!env.SELF_HOSTED, // only ever run once
|
||||||
},
|
})
|
||||||
{
|
break
|
||||||
type: migrations.MIGRATION_TYPES.GLOBAL,
|
}
|
||||||
name: "event_global_backfill",
|
case MigrationName.EVENT_GLOBAL_BACKFILL: {
|
||||||
|
serverMigrations.push({
|
||||||
|
...definition,
|
||||||
fn: backfill.global.run,
|
fn: backfill.global.run,
|
||||||
silent: !!env.SELF_HOSTED, // reduce noisy logging
|
silent: !!env.SELF_HOSTED, // reduce noisy logging
|
||||||
preventRetry: !!env.SELF_HOSTED, // only ever run once
|
preventRetry: !!env.SELF_HOSTED, // only ever run once
|
||||||
},
|
})
|
||||||
{
|
break
|
||||||
type: migrations.MIGRATION_TYPES.INSTALLATION,
|
}
|
||||||
name: "event_installation_backfill",
|
case MigrationName.EVENT_INSTALLATION_BACKFILL: {
|
||||||
|
serverMigrations.push({
|
||||||
|
...definition,
|
||||||
fn: backfill.installation.run,
|
fn: backfill.installation.run,
|
||||||
silent: !!env.SELF_HOSTED, // reduce noisy logging
|
silent: !!env.SELF_HOSTED, // reduce noisy logging
|
||||||
preventRetry: !!env.SELF_HOSTED, // only ever run once
|
preventRetry: !!env.SELF_HOSTED, // only ever run once
|
||||||
},
|
})
|
||||||
]
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return serverMigrations
|
||||||
|
}
|
||||||
|
|
||||||
|
export const MIGRATIONS = buildMigrations()
|
||||||
|
|
||||||
export const migrate = async (options?: MigrationOptions) => {
|
export const migrate = async (options?: MigrationOptions) => {
|
||||||
if (env.SELF_HOSTED) {
|
if (env.SELF_HOSTED) {
|
||||||
|
|
|
@ -1,4 +1,11 @@
|
||||||
import { events, migrations, tenancy } from "@budibase/backend-core"
|
import {
|
||||||
|
events,
|
||||||
|
migrations,
|
||||||
|
tenancy,
|
||||||
|
DocumentTypes,
|
||||||
|
context,
|
||||||
|
db,
|
||||||
|
} from "@budibase/backend-core"
|
||||||
import TestConfig from "../../tests/utilities/TestConfiguration"
|
import TestConfig from "../../tests/utilities/TestConfiguration"
|
||||||
import structures from "../../tests/utilities/structures"
|
import structures from "../../tests/utilities/structures"
|
||||||
import { MIGRATIONS } from "../"
|
import { MIGRATIONS } from "../"
|
||||||
|
@ -7,6 +14,15 @@ import * as helpers from "./helpers"
|
||||||
const { mocks } = require("@budibase/backend-core/tests")
|
const { mocks } = require("@budibase/backend-core/tests")
|
||||||
const timestamp = mocks.date.MOCK_DATE.toISOString()
|
const timestamp = mocks.date.MOCK_DATE.toISOString()
|
||||||
|
|
||||||
|
const clearMigrations = async () => {
|
||||||
|
const dbs = [context.getDevAppDB(), context.getProdAppDB()]
|
||||||
|
for (const db of dbs) {
|
||||||
|
const doc = await db.get(DocumentTypes.MIGRATIONS)
|
||||||
|
const newDoc = { _id: doc._id, _rev: doc._rev }
|
||||||
|
await db.put(newDoc)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
describe("migrations", () => {
|
describe("migrations", () => {
|
||||||
const config = new TestConfig()
|
const config = new TestConfig()
|
||||||
|
|
||||||
|
@ -21,6 +37,7 @@ describe("migrations", () => {
|
||||||
describe("backfill", () => {
|
describe("backfill", () => {
|
||||||
it("runs app db migration", async () => {
|
it("runs app db migration", async () => {
|
||||||
await config.doInContext(null, async () => {
|
await config.doInContext(null, async () => {
|
||||||
|
await clearMigrations()
|
||||||
await config.createAutomation()
|
await config.createAutomation()
|
||||||
await config.createAutomation(structures.newAutomation())
|
await config.createAutomation(structures.newAutomation())
|
||||||
await config.createDatasource()
|
await config.createDatasource()
|
||||||
|
@ -71,6 +88,7 @@ describe("migrations", () => {
|
||||||
|
|
||||||
it("runs global db migration", async () => {
|
it("runs global db migration", async () => {
|
||||||
await config.doInContext(null, async () => {
|
await config.doInContext(null, async () => {
|
||||||
|
await clearMigrations()
|
||||||
const appId = config.prodAppId
|
const appId = config.prodAppId
|
||||||
const roles = { [appId]: "role_12345" }
|
const roles = { [appId]: "role_12345" }
|
||||||
await config.createUser(undefined, undefined, false, true, roles) // admin only
|
await config.createUser(undefined, undefined, false, true, roles) // admin only
|
||||||
|
|
|
@ -1,3 +0,0 @@
|
||||||
export * from "./hosting"
|
|
||||||
export * from "./context"
|
|
||||||
export * from "./identification"
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { Hosting } from "../../core"
|
import { Hosting } from "../../sdk"
|
||||||
|
|
||||||
export interface CreateAccount {
|
export interface CreateAccount {
|
||||||
email: string
|
email: string
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
export * from "./documents"
|
export * from "./documents"
|
||||||
export * from "./events"
|
export * from "./sdk/events"
|
||||||
export * from "./licensing"
|
export * from "./sdk/licensing"
|
||||||
export * from "./core"
|
export * from "./sdk"
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { User, Account } from "../documents"
|
import { User, Account } from "../documents"
|
||||||
import { IdentityType } from "./identification"
|
import { IdentityType } from "./events/identification"
|
||||||
|
|
||||||
export interface BaseContext {
|
export interface BaseContext {
|
||||||
_id: string
|
_id: string
|
|
@ -1,4 +1,4 @@
|
||||||
import { Hosting } from "."
|
import { Hosting } from ".."
|
||||||
|
|
||||||
// GROUPS
|
// GROUPS
|
||||||
|
|
|
@ -17,3 +17,4 @@ export * from "./user"
|
||||||
export * from "./view"
|
export * from "./view"
|
||||||
export * from "./account"
|
export * from "./account"
|
||||||
export * from "./backfill"
|
export * from "./backfill"
|
||||||
|
export * from "./identification"
|
|
@ -1,4 +1,4 @@
|
||||||
import { ViewCalculation } from "../documents"
|
import { ViewCalculation } from "../../documents"
|
||||||
import { BaseEvent, TableExportFormat } from "./event"
|
import { BaseEvent, TableExportFormat } from "./event"
|
||||||
|
|
||||||
export interface ViewCreatedEvent extends BaseEvent {
|
export interface ViewCreatedEvent extends BaseEvent {
|
|
@ -0,0 +1,5 @@
|
||||||
|
export * from "./hosting"
|
||||||
|
export * from "./context"
|
||||||
|
export * from "./events"
|
||||||
|
export * from "./licensing"
|
||||||
|
export * from "./migrations"
|
|
@ -0,0 +1,54 @@
|
||||||
|
export interface Migration extends MigrationDefinition {
|
||||||
|
appOpts?: object
|
||||||
|
fn: Function
|
||||||
|
silent?: boolean
|
||||||
|
preventRetry?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum MigrationType {
|
||||||
|
// run once per tenant, recorded in global db, global db is provided as an argument
|
||||||
|
GLOBAL = "global",
|
||||||
|
// run per app, recorded in each app db, app db is provided as an argument
|
||||||
|
APP = "app",
|
||||||
|
// run once, recorded in global info db, global info db is provided as an argument
|
||||||
|
INSTALLATION = "installation",
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface MigrationNoOpOptions {
|
||||||
|
type: MigrationType
|
||||||
|
tenantId: string
|
||||||
|
appId?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* e.g.
|
||||||
|
* {
|
||||||
|
* tenantIds: ['bb'],
|
||||||
|
* force: {
|
||||||
|
* global: ['quota_1']
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
export interface MigrationOptions {
|
||||||
|
tenantIds?: string[]
|
||||||
|
force?: {
|
||||||
|
[type: string]: string[]
|
||||||
|
}
|
||||||
|
noOp?: MigrationNoOpOptions
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum MigrationName {
|
||||||
|
USER_EMAIL_VIEW_CASING = "user_email_view_casing",
|
||||||
|
QUOTAS_1 = "quotas_1",
|
||||||
|
APP_URLS = "app_urls",
|
||||||
|
DEVELOPER_QUOTA = "developer_quota",
|
||||||
|
PUBLISHED_APP_QUOTA = "published_apps_quota",
|
||||||
|
EVENT_APP_BACKFILL = "event_app_backfill",
|
||||||
|
EVENT_GLOBAL_BACKFILL = "event_global_backfill",
|
||||||
|
EVENT_INSTALLATION_BACKFILL = "event_installation_backfill",
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface MigrationDefinition {
|
||||||
|
type: MigrationType
|
||||||
|
name: MigrationName
|
||||||
|
}
|
|
@ -13,7 +13,9 @@ import {
|
||||||
sessions,
|
sessions,
|
||||||
HTTPError,
|
HTTPError,
|
||||||
accounts,
|
accounts,
|
||||||
|
migrations,
|
||||||
} from "@budibase/backend-core"
|
} from "@budibase/backend-core"
|
||||||
|
import { MigrationType } from "@budibase/types"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves all users from the current tenancy.
|
* Retrieves all users from the current tenancy.
|
||||||
|
@ -146,7 +148,12 @@ export const save = async (
|
||||||
await eventHelpers.handleSaveEvents(user, dbUser)
|
await eventHelpers.handleSaveEvents(user, dbUser)
|
||||||
|
|
||||||
if (env.MULTI_TENANCY) {
|
if (env.MULTI_TENANCY) {
|
||||||
await tenancy.tryAddTenant(tenantId, _id, email)
|
const afterCreateTenant = () =>
|
||||||
|
migrations.backPopulateMigrations({
|
||||||
|
type: MigrationType.GLOBAL,
|
||||||
|
tenantId,
|
||||||
|
})
|
||||||
|
await tenancy.tryAddTenant(tenantId, _id, email, afterCreateTenant)
|
||||||
}
|
}
|
||||||
await cache.user.invalidateUser(response.id)
|
await cache.user.invalidateUser(response.id)
|
||||||
// let server know to sync user
|
// let server know to sync user
|
||||||
|
|
|
@ -13,6 +13,10 @@ cd packages/types
|
||||||
yarn link
|
yarn link
|
||||||
cd -
|
cd -
|
||||||
|
|
||||||
|
echo "Linking bbui"
|
||||||
|
cd packages/bbui
|
||||||
|
yarn link
|
||||||
|
cd -
|
||||||
|
|
||||||
if [ -d "../budibase-pro" ]; then
|
if [ -d "../budibase-pro" ]; then
|
||||||
cd ../budibase-pro
|
cd ../budibase-pro
|
||||||
|
@ -52,5 +56,7 @@ if [ -d "../account-portal" ]; then
|
||||||
yarn link "@budibase/pro"
|
yarn link "@budibase/pro"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
cd -
|
cd ../ui
|
||||||
|
echo "Linking bbui to account-portal"
|
||||||
|
yarn link "@budibase/bbui"
|
||||||
fi
|
fi
|
||||||
|
|
Loading…
Reference in New Issue