Finish global migration

This commit is contained in:
Rory Powell 2022-05-20 21:16:29 +01:00
parent 8ae358d237
commit 63dd69f5b3
25 changed files with 354 additions and 34 deletions

View File

@ -71,6 +71,7 @@ jest.mock("../../../events", () => {
},
rows: {
imported: jest.fn(),
created: jest.fn(),
},
screen: {
created: jest.fn(),

View File

@ -1,3 +1,7 @@
import * as users from "./global/users"
import * as rows from "./global/rows"
import * as configs from "./global/configs"
/**
* Date:
* May 2022
@ -6,4 +10,8 @@
* Backfill global events.
*/
export const run = async (db: any) => {}
export const run = async (db: any) => {
await users.backfill(db)
await rows.backfill()
await configs.backfill(db)
}

View File

@ -1,7 +0,0 @@
// EMAIL_SMTP_CREATED = "email:smtp:created",
// AUTH_SSO_CREATED = "auth:sso:created",
// AUTH_SSO_ACTIVATED = "auth:sso:activated",
// AUTH_SSO_DEACTIVATED = "auth:sso:deactivated",
// ORG_NAME_UPDATED = "org:info:name:updated",
// ORG_LOGO_UPDATED = "org:info:logo:updated",
// ORG_PLATFORM_URL_UPDATED = "org:platformurl:updated",

View File

@ -0,0 +1,63 @@
import { events, db } from "@budibase/backend-core"
import {
Config,
isSMTPConfig,
isGoogleConfig,
isOIDCConfig,
isSettingsConfig,
} from "@budibase/types"
import env from "./../../../../environment"
const getConfigs = async (globalDb: any): Promise<Config[]> => {
const response = await globalDb.allDocs(
db.getConfigParams(
{},
{
include_docs: true,
}
)
)
return response.rows.map((row: any) => row.doc)
}
export const backfill = async (globalDb: any) => {
const configs = await getConfigs(globalDb)
for (const config of configs) {
if (isSMTPConfig(config)) {
events.email.SMTPCreated(config)
}
if (isGoogleConfig(config)) {
events.auth.SSOCreated("google")
if (config.config.activated) {
events.auth.SSOActivated("google")
}
}
if (isOIDCConfig(config)) {
events.auth.SSOCreated("oidc")
if (config.config.configs[0].activated) {
events.auth.SSOActivated("oidc")
}
}
if (isSettingsConfig(config)) {
const company = config.config.company
if (company && company !== "Budibase") {
events.org.nameUpdated()
}
const logoUrl = config.config.logoUrl
if (logoUrl) {
events.org.logoUpdated()
}
const platformUrl = config.config.platformUrl
if (
platformUrl &&
platformUrl !== "http://localhost:10000" &&
env.SELF_HOSTED
) {
events.org.platformURLUpdated()
}
}
}
}

View File

@ -10,5 +10,7 @@ export const backfill = async () => {
const appIds = allApps ? allApps.map((app: { appId: any }) => app.appId) : []
const rows: Row[] = await getUniqueRows(appIds)
const rowCount = rows ? rows.length : 0
if (rowCount) {
events.rows.created(rowCount)
}
}

View File

@ -1,3 +0,0 @@
// USER_CREATED = "user:created",
// USER_PERMISSION_ADMIN_ASSIGNED = "user:admin:assigned",
// USER_PERMISSION_BUILDER_ASSIGNED = "user:builder:assigned",

View File

@ -0,0 +1,38 @@
import { events, db } from "@budibase/backend-core"
import { User } from "@budibase/types"
// manually define user doc params - normally server doesn't read users from the db
const getUserParams = (props: any) => {
return db.getDocParams(db.DocumentTypes.USER, null, props)
}
const getUsers = async (globalDb: any): Promise<User[]> => {
const response = await globalDb.allDocs(
getUserParams({
include_docs: true,
})
)
return response.rows.map((row: any) => row.doc)
}
export const backfill = async (globalDb: any) => {
const users = await getUsers(globalDb)
for (const user of users) {
events.user.created(user)
if (user.admin?.global) {
events.user.permissionAdminAssigned(user)
}
if (user.builder?.global) {
events.user.permissionBuilderAssigned(user)
}
if (user.roles) {
for (const [appId, role] of Object.entries(user.roles)) {
events.role.assigned(user, role)
}
}
}
}

View File

@ -0,0 +1,30 @@
// Mimic configs test configuration from worker, creation configs directly in database
import * as structures from "./structures"
import { db } from "@budibase/backend-core"
import { Config } from "@budibase/types"
export const saveSettingsConfig = async (globalDb: any) => {
const config = structures.settings()
await saveConfig(config, globalDb)
}
export const saveGoogleConfig = async (globalDb: any) => {
const config = structures.google()
await saveConfig(config, globalDb)
}
export const saveOIDCConfig = async (globalDb: any) => {
const config = structures.oidc()
await saveConfig(config, globalDb)
}
export const saveSmtpConfig = async (globalDb: any) => {
const config = structures.smtp()
await saveConfig(config, globalDb)
}
const saveConfig = async (config: Config, globalDb: any) => {
config._id = db.generateConfigID({ type: config.type })
await globalDb.put(config)
}

View File

@ -1,7 +1,8 @@
import { events, migrations } from "@budibase/backend-core"
import { events, migrations, tenancy } from "@budibase/backend-core"
import TestConfig from "../../tests/utilities/TestConfiguration"
import structures from "../../tests/utilities/structures"
import { MIGRATIONS } from "../"
import * as helpers from "./helpers"
jest.setTimeout(100000)
@ -57,4 +58,39 @@ describe("migrations", () => {
})
})
})
it("runs global db migration", async () => {
await config.doInContext(null, async () => {
await config.createUser(undefined, undefined, false, true) // admin only
await config.createUser(undefined, undefined, false, false) // non admin non builder
await config.createTable()
await config.createRow()
await config.createRow()
const db = tenancy.getGlobalDB()
await helpers.saveGoogleConfig(db)
await helpers.saveOIDCConfig(db)
await helpers.saveSettingsConfig(db)
await helpers.saveSmtpConfig(db)
jest.clearAllMocks()
const migration = MIGRATIONS.filter(
m => m.name === "event_global_backfill"
)[0]
await migrations.runMigration(migration)
expect(events.user.created).toBeCalledTimes(3)
expect(events.role.assigned).toBeCalledTimes(2)
expect(events.user.permissionBuilderAssigned).toBeCalledTimes(1) // default test user
expect(events.user.permissionAdminAssigned).toBeCalledTimes(1) // admin from above
expect(events.rows.created).toBeCalledTimes(1)
expect(events.rows.created).toBeCalledWith(2)
expect(events.email.SMTPCreated).toBeCalledTimes(1)
expect(events.auth.SSOCreated).toBeCalledTimes(2)
expect(events.auth.SSOActivated).toBeCalledTimes(2)
expect(events.org.logoUpdated).toBeCalledTimes(1)
expect(events.org.nameUpdated).toBeCalledTimes(1)
expect(events.org.platformURLUpdated).toBeCalledTimes(1)
})
})
})

View File

@ -0,0 +1,66 @@
import { utils } from "@budibase/backend-core"
import {
SMTPConfig,
OIDCConfig,
GoogleConfig,
SettingsConfig,
ConfigType,
} from "@budibase/types"
export const oidc = (conf?: OIDCConfig): OIDCConfig => {
return {
type: ConfigType.OIDC,
config: {
configs: [
{
configUrl: "http://someconfigurl",
clientID: "clientId",
clientSecret: "clientSecret",
logo: "Microsoft",
name: "Active Directory",
uuid: utils.newid(),
activated: true,
...conf,
},
],
},
}
}
export const google = (conf?: GoogleConfig): GoogleConfig => {
return {
type: ConfigType.GOOGLE,
config: {
clientID: "clientId",
clientSecret: "clientSecret",
activated: true,
...conf,
},
}
}
export const smtp = (conf?: SMTPConfig): SMTPConfig => {
return {
type: ConfigType.SMTP,
config: {
port: 12345,
host: "smtptesthost.com",
from: "testfrom@test.com",
subject: "Hello!",
secure: false,
...conf,
},
}
}
export const settings = (conf?: SettingsConfig): SettingsConfig => {
return {
type: ConfigType.SETTINGS,
config: {
platformUrl: "http://mycustomdomain.com",
logoUrl: "http://mylogourl,com",
company: "mycompany",
...conf,
},
}
}

View File

@ -121,6 +121,7 @@ class TestConfiguration {
async globalUser({
id = GLOBAL_USER_ID,
builder = true,
admin = false,
email = EMAIL,
roles,
} = {}) {
@ -147,6 +148,11 @@ class TestConfiguration {
} else {
user.builder = { global: false }
}
if (admin) {
user.admin = { global: true }
} else {
user.admin = { global: false }
}
const resp = await db.put(user)
return {
_rev: resp._rev,
@ -155,9 +161,17 @@ class TestConfiguration {
})
}
async createUser(id = null, email = EMAIL) {
async createUser(id = null, email = EMAIL, builder = true, admin = false) {
const globalId = !id ? `us_${Math.random()}` : `us_${id}`
const resp = await this.globalUser({ id: globalId, email })
const appId = this.prodAppId
const roles = { [appId]: "role_12345" }
const resp = await this.globalUser({
id: globalId,
email,
builder,
admin,
roles,
})
await userCache.invalidateUser(globalId)
return {
...resp,
@ -277,7 +291,7 @@ class TestConfiguration {
// APP
async createApp(appName, opts = { deploy: true }) {
async createApp(appName) {
// create dev app
// clear any old app
this.appId = null
@ -287,11 +301,9 @@ class TestConfiguration {
await context.updateAppId(this.appId)
// create production app
if (opts.deploy) {
this.prodApp = await this.deploy()
this.prodAppId = this.prodApp.appId
this.allApps.push(this.prodApp)
}
this.allApps.push(this.app)

View File

@ -1,4 +1,4 @@
import { Document } from "./document"
import { Document } from "../document"
export interface App extends Document {
appId: string

View File

@ -1,4 +1,4 @@
import { Document } from "./document"
import { Document } from "../document"
export interface Automation extends Document {
definition: {

View File

@ -1,3 +1,3 @@
import { Document } from "./document"
import { Document } from "../document"
export interface Datasource extends Document {}

View File

@ -7,6 +7,6 @@ export * from "./role"
export * from "./table"
export * from "./screen"
export * from "./view"
export * from "./document"
export * from "../document"
export * from "./row"
export * from "./user"

View File

@ -1,3 +1,3 @@
import { Document } from "./document"
import { Document } from "../document"
export interface Layout extends Document {}

View File

@ -1,4 +1,4 @@
import { Document } from "./document"
import { Document } from "../document"
export interface Query extends Document {
datasourceId: string

View File

@ -1,3 +1,3 @@
import { Document } from "./document"
import { Document } from "../document"
export interface Role extends Document {}

View File

@ -1,3 +1,3 @@
import { Document } from "./document"
import { Document } from "../document"
export interface Row extends Document {}

View File

@ -1,3 +1,3 @@
import { Document } from "./document"
import { Document } from "../document"
export interface Screen extends Document {}

View File

@ -1,4 +1,4 @@
import { Document } from "./document"
import { Document } from "../document"
import { View } from "./view"
export interface Table extends Document {

View File

@ -1,4 +1,4 @@
import { Document } from "./document"
import { Document } from "../document"
export interface UserMetadata extends Document {
roleId: string

View File

@ -1 +1,67 @@
export interface SMTPConfig {}
import { Document } from "../document"
export interface Config extends Document {
type: ConfigType
}
export interface SMTPConfig extends Config {
config: {
port: number
host: string
from: string
subject: string
secure: boolean
}
}
export interface SettingsConfig extends Config {
config: {
company: string
logoUrl: string
platformUrl: string
}
}
export interface GoogleConfig extends Config {
config: {
clientID: string
clientSecret: string
activated: boolean
}
}
export interface OIDCConfig extends Config {
config: {
configs: {
configUrl: string
clientID: string
clientSecret: string
logo: string
name: string
uuid: string
activated: boolean
}[]
}
}
export type NestedConfig =
| SMTPConfig
| SettingsConfig
| GoogleConfig
| OIDCConfig
export const isSettingsConfig = (config: Config): config is SettingsConfig =>
config.type === ConfigType.SETTINGS
export const isSMTPConfig = (config: Config): config is SMTPConfig =>
config.type === ConfigType.SMTP
export const isGoogleConfig = (config: Config): config is GoogleConfig =>
config.type === ConfigType.GOOGLE
export const isOIDCConfig = (config: Config): config is OIDCConfig =>
config.type === ConfigType.OIDC
export enum ConfigType {
SETTINGS = "settings",
SMTP = "smtp",
GOOGLE = "google",
OIDC = "oidc",
}

View File

@ -1,5 +1,13 @@
export interface User {
import { Document } from "../document"
export interface User extends Document {
roles: UserRoles
builder?: {
global: boolean
}
admin?: {
global: boolean
}
}
export interface UserRoles {