From 398a4e7034abfa52e269bbea43457852a11c01cb Mon Sep 17 00:00:00 2001 From: Rory Powell Date: Thu, 26 May 2022 23:57:14 +0100 Subject: [PATCH] Installation identities, upgrade / downgrade events, filling in more event properties --- packages/backend-core/src/db/constants.js | 1 + .../backend-core/src/events/identification.ts | 76 ++++++++++----- .../src/events/processors/LoggingProcessor.ts | 15 +++ .../src/events/processors/Processors.ts | 9 ++ .../src/events/processors/types.ts | 1 + .../src/events/publishers/auth.ts | 8 +- .../src/events/publishers/automation.ts | 58 +++++++++--- .../src/events/publishers/datasource.ts | 15 ++- .../src/events/publishers/index.ts | 1 + .../src/events/publishers/layout.ts | 8 +- .../backend-core/src/events/publishers/org.ts | 10 +- .../src/events/publishers/version.ts | 26 ++++++ packages/backend-core/src/migrations/index.js | 20 ++-- packages/server/package.json | 2 + packages/server/src/api/controllers/dev.js | 2 +- packages/server/src/app.ts | 4 + packages/server/src/installation.ts | 92 +++++++++++++++++++ .../migrations/functions/backfill/global.ts | 11 ++- .../migrations/functions/backfill/index.ts | 1 + .../functions/backfill/installation.ts | 22 +++++ packages/server/src/migrations/index.ts | 6 ++ packages/server/yarn.lock | 12 +++ packages/types/src/core/sessions.ts | 15 ++- .../types/src/documents/app/automation.ts | 11 ++- .../types/src/documents/app/datasource.ts | 4 +- packages/types/src/documents/platform/info.ts | 8 ++ packages/types/src/events/auth.ts | 21 ++++- packages/types/src/events/automation.ts | 46 ++++++++-- packages/types/src/events/datasource.ts | 15 ++- packages/types/src/events/event.ts | 4 +- packages/types/src/events/identification.ts | 10 +- packages/types/src/events/index.ts | 2 +- packages/types/src/events/layout.ts | 8 +- packages/types/src/events/org.ts | 1 - packages/types/src/events/version.ts | 8 ++ 35 files changed, 463 insertions(+), 90 deletions(-) create mode 100644 packages/backend-core/src/events/publishers/version.ts create mode 100644 packages/server/src/installation.ts create mode 100644 packages/server/src/migrations/functions/backfill/installation.ts delete mode 100644 packages/types/src/events/org.ts create mode 100644 packages/types/src/events/version.ts diff --git a/packages/backend-core/src/db/constants.js b/packages/backend-core/src/db/constants.js index 271d4f412d..10c6e174d7 100644 --- a/packages/backend-core/src/db/constants.js +++ b/packages/backend-core/src/db/constants.js @@ -31,6 +31,7 @@ exports.StaticDatabases = { name: "global-info", docs: { tenants: "tenants", + install: "install", }, }, } diff --git a/packages/backend-core/src/events/identification.ts b/packages/backend-core/src/events/identification.ts index 6961815067..8eabfa2618 100644 --- a/packages/backend-core/src/events/identification.ts +++ b/packages/backend-core/src/events/identification.ts @@ -14,15 +14,21 @@ import { SettingsConfig, CloudAccount, UserIdentity, + InstallationIdentity, + Installation, + isInstallation, } from "@budibase/types" -import { analyticsProcessor } from "./processors" +import { processors } from "./processors" import * as dbUtils from "../db/utils" import { Configs } from "../constants" import * as hashing from "../hashing" +const pkg = require("../../package.json") + export const getCurrentIdentity = async (): Promise => { const user: SessionUser | undefined = context.getUser() - let tenantId = context.getTenantId() + + const tenantId = await getGlobalTenantId(context.getTenantId()) let id: string let type: IdentityType @@ -30,12 +36,14 @@ export const getCurrentIdentity = async (): Promise => { id = user._id type = IdentityType.USER } else { - const global = await getGlobalIdentifiers(tenantId) - id = global.id - tenantId = global.tenantId + id = tenantId type = IdentityType.TENANT } + if (user && isInstallation(user)) { + type = IdentityType.INSTALLATION + } + return { id, tenantId, @@ -43,36 +51,29 @@ export const getCurrentIdentity = async (): Promise => { } } -const getGlobalId = async (): Promise => { +const getGlobalId = async (tenantId: string): Promise => { const db = context.getGlobalDB() const config: SettingsConfig = await dbUtils.getScopedFullConfig(db, { type: Configs.SETTINGS, }) + + let globalId: string if (config.config.globalId) { return config.config.globalId } else { - const globalId = `global_${hashing.newid()}` + globalId = `${hashing.newid()}_${tenantId}` config.config.globalId = globalId await db.put(config) return globalId } } -const getGlobalIdentifiers = async ( - tenantId: string -): Promise<{ id: string; tenantId: string }> => { +const getGlobalTenantId = async (tenantId: string): Promise => { if (env.SELF_HOSTED) { - const globalId = await getGlobalId() - return { - id: globalId, - tenantId: `${globalId}-${tenantId}`, - } + return getGlobalId(tenantId) } else { // tenant id's in the cloud are already unique - return { - id: tenantId, - tenantId: tenantId, - } + return tenantId } } @@ -80,13 +81,34 @@ const getHostingFromEnv = () => { return env.SELF_HOSTED ? Hosting.SELF : Hosting.CLOUD } +export const identifyInstallation = async ( + install: Installation, + timestamp: string | number +) => { + const id = install.installId + // the default tenant id, so we can match installations to other events + const tenantId = await getGlobalTenantId(context.getTenantId()) + const version: string = pkg.version as string + const type = IdentityType.INSTALLATION + const hosting = getHostingFromEnv() + + const identity: InstallationIdentity = { + id, + tenantId, + type, + version, + hosting, + } + await identify(identity, timestamp) +} + export const identifyTenant = async ( tenantId: string, account: CloudAccount | undefined, timestamp?: string | number ) => { - const global = await getGlobalIdentifiers(tenantId) - const id = global.id + const globalTenantId = await getGlobalTenantId(tenantId) + const id = globalTenantId const hosting = getHostingFromEnv() const type = IdentityType.TENANT const profession = account?.profession @@ -94,7 +116,7 @@ export const identifyTenant = async ( const identity: TenantIdentity = { id, - tenantId: global.tenantId, + tenantId: globalTenantId, hosting, type, profession, @@ -116,7 +138,8 @@ export const identifyUser = async ( let admin = user.admin?.global let providerType = user.providerType const accountHolder = account?.budibaseUserId === user._id - const verified = account ? account.verified : false + const verified = + account && account?.budibaseUserId === user._id ? account.verified : false const profession = account?.profession const companySize = account?.size @@ -170,6 +193,9 @@ export const identifyAccount = async (account: Account) => { await identify(identity) } -const identify = async (identity: Identity, timestamp?: string | number) => { - await analyticsProcessor.identify(identity, timestamp) +export const identify = async ( + identity: Identity, + timestamp?: string | number +) => { + await processors.identify(identity, timestamp) } diff --git a/packages/backend-core/src/events/processors/LoggingProcessor.ts b/packages/backend-core/src/events/processors/LoggingProcessor.ts index e834243003..47b91b7dea 100644 --- a/packages/backend-core/src/events/processors/LoggingProcessor.ts +++ b/packages/backend-core/src/events/processors/LoggingProcessor.ts @@ -22,6 +22,21 @@ export default class LoggingProcessor implements EventProcessor { ) } + async identify(identity: Identity, timestamp?: string | number) { + if (env.SELF_HOSTED && !env.isDev()) { + return + } + + let timestampString = "" + if (timestamp) { + timestampString = `[timestamp=${new Date(timestamp).toISOString()}]` + } + + console.log( + `[audit] [${JSON.stringify(identity)}] ${timestampString} identified` + ) + } + shutdown(): void { // no-op } diff --git a/packages/backend-core/src/events/processors/Processors.ts b/packages/backend-core/src/events/processors/Processors.ts index d2ee44a4a6..4263b7e06b 100644 --- a/packages/backend-core/src/events/processors/Processors.ts +++ b/packages/backend-core/src/events/processors/Processors.ts @@ -20,6 +20,15 @@ export default class Processor implements EventProcessor { } } + async identify( + identity: Identity, + timestamp?: string | number + ): Promise { + for (const eventProcessor of this.processors) { + await eventProcessor.identify(identity, timestamp) + } + } + shutdown() { for (const eventProcessor of this.processors) { eventProcessor.shutdown() diff --git a/packages/backend-core/src/events/processors/types.ts b/packages/backend-core/src/events/processors/types.ts index 9b48b2beee..b8574cbd3d 100644 --- a/packages/backend-core/src/events/processors/types.ts +++ b/packages/backend-core/src/events/processors/types.ts @@ -12,5 +12,6 @@ export interface EventProcessor { properties: any, timestamp?: string | number ): Promise + identify(identity: Identity, timestamp?: string | number): Promise shutdown(): void } diff --git a/packages/backend-core/src/events/publishers/auth.ts b/packages/backend-core/src/events/publishers/auth.ts index a1f15470f9..93378501f3 100644 --- a/packages/backend-core/src/events/publishers/auth.ts +++ b/packages/backend-core/src/events/publishers/auth.ts @@ -10,16 +10,22 @@ import { SSOType, SSOUpdatedEvent, } from "@budibase/types" +import { identification } from ".." export async function login(source: LoginSource) { + const identity = await identification.getCurrentIdentity() const properties: LoginEvent = { + userId: identity.id, source, } await publishEvent(Event.AUTH_LOGIN, properties) } export async function logout() { - const properties: LogoutEvent = {} + const identity = await identification.getCurrentIdentity() + const properties: LogoutEvent = { + userId: identity.id, + } await publishEvent(Event.AUTH_LOGOUT, properties) } diff --git a/packages/backend-core/src/events/publishers/automation.ts b/packages/backend-core/src/events/publishers/automation.ts index ba4477618b..daaa06f571 100644 --- a/packages/backend-core/src/events/publishers/automation.ts +++ b/packages/backend-core/src/events/publishers/automation.ts @@ -3,7 +3,6 @@ import { Automation, Event, AutomationStep, - AutomationTrigger, AutomationCreatedEvent, AutomationDeletedEvent, AutomationTestedEvent, @@ -13,17 +12,42 @@ import { } from "@budibase/types" export async function created(automation: Automation, timestamp?: string) { - const properties: AutomationCreatedEvent = {} + const properties: AutomationCreatedEvent = { + appId: automation.appId, + automationId: automation._id as string, + triggerId: automation.definition?.trigger?.id, + triggerType: automation.definition?.trigger?.stepId, + } await publishEvent(Event.AUTOMATION_CREATED, properties, timestamp) } +export async function triggerUpdated(automation: Automation) { + const properties: AutomationTriggerUpdatedEvent = { + appId: automation.appId, + automationId: automation._id as string, + triggerId: automation.definition?.trigger?.id, + triggerType: automation.definition?.trigger?.stepId, + } + await publishEvent(Event.AUTOMATION_TRIGGER_UPDATED, properties) +} + export async function deleted(automation: Automation) { - const properties: AutomationDeletedEvent = {} + const properties: AutomationDeletedEvent = { + appId: automation.appId, + automationId: automation._id as string, + triggerId: automation.definition?.trigger?.id, + triggerType: automation.definition?.trigger?.stepId, + } await publishEvent(Event.AUTOMATION_DELETED, properties) } export async function tested(automation: Automation) { - const properties: AutomationTestedEvent = {} + const properties: AutomationTestedEvent = { + appId: automation.appId, + automationId: automation._id as string, + triggerId: automation.definition?.trigger?.id, + triggerType: automation.definition?.trigger?.stepId, + } await publishEvent(Event.AUTOMATION_TESTED, properties) } @@ -32,7 +56,14 @@ export async function stepCreated( step: AutomationStep, timestamp?: string ) { - const properties: AutomationStepCreatedEvent = {} + const properties: AutomationStepCreatedEvent = { + appId: automation.appId, + automationId: automation._id as string, + triggerId: automation.definition?.trigger?.id, + triggerType: automation.definition?.trigger?.stepId, + stepId: step.id, + stepType: step.stepId, + } await publishEvent(Event.AUTOMATION_STEP_CREATED, properties, timestamp) } @@ -40,14 +71,13 @@ export async function stepDeleted( automation: Automation, step: AutomationStep ) { - const properties: AutomationStepDeletedEvent = {} + const properties: AutomationStepDeletedEvent = { + appId: automation.appId, + automationId: automation._id as string, + triggerId: automation.definition?.trigger?.id, + triggerType: automation.definition?.trigger?.stepId, + stepId: step.id, + stepType: step.stepId, + } await publishEvent(Event.AUTOMATION_STEP_DELETED, properties) } - -export async function triggerUpdated( - automation: Automation, - trigger: AutomationTrigger -) { - const properties: AutomationTriggerUpdatedEvent = {} - await publishEvent(Event.AUTOMATION_TRIGGER_UPDATED, properties) -} diff --git a/packages/backend-core/src/events/publishers/datasource.ts b/packages/backend-core/src/events/publishers/datasource.ts index 95f47f7947..4cff0c9a18 100644 --- a/packages/backend-core/src/events/publishers/datasource.ts +++ b/packages/backend-core/src/events/publishers/datasource.ts @@ -8,16 +8,25 @@ import { } from "@budibase/types" export async function created(datasource: Datasource, timestamp?: string) { - const properties: DatasourceCreatedEvent = {} + const properties: DatasourceCreatedEvent = { + datasourceId: datasource._id as string, + source: datasource.source, + } await publishEvent(Event.DATASOURCE_CREATED, properties, timestamp) } export async function updated(datasource: Datasource) { - const properties: DatasourceUpdatedEvent = {} + const properties: DatasourceUpdatedEvent = { + datasourceId: datasource._id as string, + source: datasource.source, + } await publishEvent(Event.DATASOURCE_UPDATED, properties) } export async function deleted(datasource: Datasource) { - const properties: DatasourceDeletedEvent = {} + const properties: DatasourceDeletedEvent = { + datasourceId: datasource._id as string, + source: datasource.source, + } await publishEvent(Event.DATASOURCE_DELETED, properties) } diff --git a/packages/backend-core/src/events/publishers/index.ts b/packages/backend-core/src/events/publishers/index.ts index fd60b9dc4f..99069aede8 100644 --- a/packages/backend-core/src/events/publishers/index.ts +++ b/packages/backend-core/src/events/publishers/index.ts @@ -15,3 +15,4 @@ export * as table from "./table" export * as serve from "./serve" export * as user from "./user" export * as view from "./view" +export * as version from "./version" diff --git a/packages/backend-core/src/events/publishers/layout.ts b/packages/backend-core/src/events/publishers/layout.ts index c7d4f20225..7a334b059f 100644 --- a/packages/backend-core/src/events/publishers/layout.ts +++ b/packages/backend-core/src/events/publishers/layout.ts @@ -7,11 +7,15 @@ import { } from "@budibase/types" export async function created(layout: Layout, timestamp?: string) { - const properties: LayoutCreatedEvent = {} + const properties: LayoutCreatedEvent = { + layoutId: layout._id as string, + } await publishEvent(Event.LAYOUT_CREATED, properties, timestamp) } export async function deleted(layout: Layout) { - const properties: LayoutDeletedEvent = {} + const properties: LayoutDeletedEvent = { + layoutId: layout._id as string, + } await publishEvent(Event.LAYOUT_DELETED, properties) } diff --git a/packages/backend-core/src/events/publishers/org.ts b/packages/backend-core/src/events/publishers/org.ts index 075ab1592b..4567357db8 100644 --- a/packages/backend-core/src/events/publishers/org.ts +++ b/packages/backend-core/src/events/publishers/org.ts @@ -1,5 +1,5 @@ import { publishEvent } from "../events" -import { Event, VersionCheckedEvent } from "@budibase/types" +import { Event } from "@budibase/types" export async function nameUpdated(timestamp?: string | number) { const properties = {} @@ -16,14 +16,8 @@ export async function platformURLUpdated(timestamp?: string | number) { await publishEvent(Event.ORG_PLATFORM_URL_UPDATED, properties, timestamp) } -export async function versionChecked(version: number) { - const properties: VersionCheckedEvent = { - version, - } - await publishEvent(Event.UPDATE_VERSION_CHECKED, properties) -} - // TODO + export async function analyticsOptOut() { const properties = {} await publishEvent(Event.ANALYTICS_OPT_OUT, properties) diff --git a/packages/backend-core/src/events/publishers/version.ts b/packages/backend-core/src/events/publishers/version.ts new file mode 100644 index 0000000000..1c96ed629f --- /dev/null +++ b/packages/backend-core/src/events/publishers/version.ts @@ -0,0 +1,26 @@ +import { publishEvent } from "../events" +import { Event, VersionCheckedEvent, VersionChangeEvent } from "@budibase/types" + +export async function checked(version: string) { + const properties: VersionCheckedEvent = { + currentVersion: version, + } + await publishEvent(Event.VERSION_CHECKED, properties) +} + +export async function upgraded(from: string, to: string) { + const properties: VersionChangeEvent = { + from, + to, + } + + await publishEvent(Event.VERSION_UPGRADED, properties) +} + +export async function downgraded(from: string, to: string) { + const properties: VersionChangeEvent = { + from, + to, + } + await publishEvent(Event.VERSION_DOWNGRADED, properties) +} diff --git a/packages/backend-core/src/migrations/index.js b/packages/backend-core/src/migrations/index.js index 26be4c90bc..6bfedf03c5 100644 --- a/packages/backend-core/src/migrations/index.js +++ b/packages/backend-core/src/migrations/index.js @@ -1,6 +1,6 @@ const { DEFAULT_TENANT_ID } = require("../constants") const { doWithDB } = require("../db") -const { DocumentTypes } = require("../db/constants") +const { DocumentTypes, StaticDatabases } = require("../db/constants") const { getAllApps } = require("../db/utils") const environment = require("../environment") const { @@ -11,8 +11,9 @@ const { } = require("../tenancy") exports.MIGRATION_TYPES = { - GLOBAL: "global", // run once, recorded in global db, global db is provided as an argument + 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 => { @@ -22,14 +23,19 @@ exports.getMigrationsDoc = async db => { } catch (err) { if (err.status && err.status === 404) { return { _id: DocumentTypes.MIGRATIONS } + } else { + console.error(err) + throw err } - console.error(err) } } exports.runMigration = async (migration, options = {}) => { - const tenantId = getTenantId() const migrationType = migration.type + let tenantId + if (migrationType !== exports.MIGRATION_TYPES.INSTALLATION) { + tenantId = getTenantId() + } const migrationName = migration.name const silent = migration.silent @@ -46,10 +52,10 @@ exports.runMigration = async (migration, options = {}) => { } 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( - `[Tenant: ${tenantId}] Unrecognised migration type [${migrationType}]` - ) + throw new Error(`Unrecognised migration type [${migrationType}]`) } const length = dbNames.length diff --git a/packages/server/package.json b/packages/server/package.json index 36ea8391ee..9e336d0280 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -129,6 +129,7 @@ "pouchdb-find": "^7.2.2", "pouchdb-replication-stream": "1.2.9", "redis": "4", + "semver": "^7.0.0", "server-destroy": "1.0.1", "svelte": "^3.38.2", "swagger-parser": "^10.0.3", @@ -159,6 +160,7 @@ "@types/node": "^15.12.4", "@types/oracledb": "^5.2.1", "@types/redis": "^4.0.11", + "@types/semver": "^7.0.0", "@typescript-eslint/parser": "5.12.0", "apidoc": "^0.50.2", "babel-jest": "^27.0.2", diff --git a/packages/server/src/api/controllers/dev.js b/packages/server/src/api/controllers/dev.js index e78fe7ddce..03ac140eb2 100644 --- a/packages/server/src/api/controllers/dev.js +++ b/packages/server/src/api/controllers/dev.js @@ -131,5 +131,5 @@ exports.getBudibaseVersion = async ctx => { ctx.body = { version, } - await events.org.versionChecked(version) + await events.version.versionChecked(version) } diff --git a/packages/server/src/app.ts b/packages/server/src/app.ts index bc9910b370..127d0f8853 100644 --- a/packages/server/src/app.ts +++ b/packages/server/src/app.ts @@ -17,6 +17,7 @@ const bullboard = require("./automations/bullboard") import redis from "./utilities/redis" import * as migrations from "./migrations" import { events } from "@budibase/backend-core" +import * as installation from "./installation" const app = new Koa() @@ -108,3 +109,6 @@ if (!env.HTTP_MIGRATIONS && !env.isTest()) { shutdown() }) } + +// check for version updates +installation.checkInstallVersion() diff --git a/packages/server/src/installation.ts b/packages/server/src/installation.ts new file mode 100644 index 0000000000..3832d6f474 --- /dev/null +++ b/packages/server/src/installation.ts @@ -0,0 +1,92 @@ +import { + db as dbUtils, + events, + utils, + context, + tenancy, +} from "@budibase/backend-core" +import { Installation } from "@budibase/types" +import semver from "semver" + +const pkg = require("../package.json") + +export const getInstall = async (): Promise => { + return dbUtils.doWithDB( + dbUtils.StaticDatabases.PLATFORM_INFO.name, + async (platformDb: any) => { + let install: Installation + try { + install = await platformDb.get( + dbUtils.StaticDatabases.PLATFORM_INFO.docs.install + ) + } catch (e: any) { + if (e.status === 404) { + install = { + _id: dbUtils.StaticDatabases.PLATFORM_INFO.docs.install, + installId: utils.newid(), + version: pkg.version, + } + const resp = await platformDb.put(install) + install._rev = resp.rev + } else { + throw e + } + } + return install + }, + {} + ) +} + +const updateVersion = async (version: string): Promise => { + try { + await dbUtils.doWithDB( + dbUtils.StaticDatabases.PLATFORM_INFO.name, + async (platformDb: any) => { + const install = await getInstall() + install.version = version + await platformDb.put(install) + }, + {} + ) + } catch (e: any) { + if (e.status === 409) { + // do nothing - version has already been updated + // likely in clustered environment + return false + } + throw e + } + return true +} + +export const checkInstallVersion = async (): Promise => { + const install = await getInstall() + + const currentVersion = install.version + const newVersion = pkg.version + + if (currentVersion !== newVersion) { + const isUpgrade = semver.gt(newVersion, currentVersion) + const isDowngrade = semver.lt(newVersion, currentVersion) + + const success = await updateVersion(newVersion) + + if (success) { + await context.doInUserContext( + { + _id: install.installId, + isInstall: true, + tenantId: tenancy.DEFAULT_TENANT_ID, + }, + async () => { + if (isUpgrade) { + await events.version.upgraded(currentVersion, newVersion) + } else if (isDowngrade) { + await events.version.downgraded(currentVersion, newVersion) + } + } + ) + } + } +} diff --git a/packages/server/src/migrations/functions/backfill/global.ts b/packages/server/src/migrations/functions/backfill/global.ts index a9d74e1e64..99d08c46ed 100644 --- a/packages/server/src/migrations/functions/backfill/global.ts +++ b/packages/server/src/migrations/functions/backfill/global.ts @@ -2,8 +2,9 @@ import * as users from "./global/users" import * as rows from "./global/rows" import * as configs from "./global/configs" import { tenancy, events, migrations, accounts } from "@budibase/backend-core" -import { CloudAccount } from "@budibase/types" +import { CloudAccount, Installation } from "@budibase/types" import env from "../../../environment" +import * as installation from "../../../installation" /** * Date: @@ -39,10 +40,12 @@ export const isComplete = async (): Promise => { return !!migrationsDoc.event_global_backfill } -const getInstallTimestamp = async (db: any): Promise => { - const allUsers = await users.getUsers(db) +export const getInstallTimestamp = async ( + globalDb: any +): Promise => { + const allUsers = await users.getUsers(globalDb) - // get the olders user timestamp + // get the oldest user timestamp const timestamps = allUsers .map(user => user.createdAt) .filter(timestamp => !!timestamp) diff --git a/packages/server/src/migrations/functions/backfill/index.ts b/packages/server/src/migrations/functions/backfill/index.ts index fb9859a581..dee9b82e1a 100644 --- a/packages/server/src/migrations/functions/backfill/index.ts +++ b/packages/server/src/migrations/functions/backfill/index.ts @@ -1,2 +1,3 @@ export * as app from "./app" export * as global from "./global" +export * as installation from "./installation" diff --git a/packages/server/src/migrations/functions/backfill/installation.ts b/packages/server/src/migrations/functions/backfill/installation.ts new file mode 100644 index 0000000000..bf3b413fa7 --- /dev/null +++ b/packages/server/src/migrations/functions/backfill/installation.ts @@ -0,0 +1,22 @@ +import { events, tenancy } from "@budibase/backend-core" +import { Installation } from "@budibase/types" +import * as installation from "../../../installation" +import * as global from "./global" + +/** + * Date: + * May 2022 + * + * Description: + * Backfill installation events. + */ + +export const run = async () => { + // need to use the default tenant to try to get the installation time + await tenancy.doInTenant(tenancy.DEFAULT_TENANT_ID, async () => { + const db = tenancy.getGlobalDB() + const installTimestamp = (await global.getInstallTimestamp(db)) as number + const install: Installation = await installation.getInstall() + await events.identification.identifyInstallation(install, installTimestamp) + }) +} diff --git a/packages/server/src/migrations/index.ts b/packages/server/src/migrations/index.ts index 92e17ed24e..1418a05f18 100644 --- a/packages/server/src/migrations/index.ts +++ b/packages/server/src/migrations/index.ts @@ -73,6 +73,12 @@ export const MIGRATIONS: Migration[] = [ fn: backfill.global.run, silent: !!env.SELF_HOSTED, // reduce noisy logging }, + { + type: migrations.MIGRATION_TYPES.INSTALLATION, + name: "event_installation_backfill", + fn: backfill.installation.run, + silent: !!env.SELF_HOSTED, // reduce noisy logging + }, ] export const migrate = async (options?: MigrationOptions) => { diff --git a/packages/server/yarn.lock b/packages/server/yarn.lock index 4548fd2bd0..7236ec8d84 100644 --- a/packages/server/yarn.lock +++ b/packages/server/yarn.lock @@ -2754,6 +2754,11 @@ "@types/tough-cookie" "*" form-data "^2.5.0" +"@types/semver@^7.0.0": + version "7.3.9" + resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.3.9.tgz#152c6c20a7688c30b967ec1841d31ace569863fc" + integrity sha512-L/TMpyURfBkf+o/526Zb6kd/tchUP3iBDEPjqjb+U2MAJhVRxxrmr2fwpe08E7QsV7YLcpq0tUaQ9O9x97ZIxQ== + "@types/serve-static@*": version "1.13.10" resolved "https://registry.yarnpkg.com/@types/serve-static/-/serve-static-1.13.10.tgz#f5e0ce8797d2d7cc5ebeda48a52c96c4fa47a8d9" @@ -11645,6 +11650,13 @@ semver@^6.0.0, semver@^6.1.1, semver@^6.1.2, semver@^6.2.0, semver@^6.3.0: resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== +semver@^7.0.0: + version "7.3.7" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.7.tgz#12c5b649afdbf9049707796e22a4028814ce523f" + integrity sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g== + dependencies: + lru-cache "^6.0.0" + seq-queue@^0.0.5: version "0.0.5" resolved "https://registry.yarnpkg.com/seq-queue/-/seq-queue-0.0.5.tgz#d56812e1c017a6e4e7c3e3a37a1da6d78dd3c93e" diff --git a/packages/types/src/core/sessions.ts b/packages/types/src/core/sessions.ts index a47fd90223..56db6e4f54 100644 --- a/packages/types/src/core/sessions.ts +++ b/packages/types/src/core/sessions.ts @@ -33,4 +33,17 @@ export const isUserSession = ( return !user.account || user.account?.hosting === Hosting.CLOUD } -export type SessionUser = AccountUserSession | BudibaseUserSession +// not technically a session, but used to identify the installation +export interface InstallationSession { + _id: string + isInstallation: boolean +} + +export const isInstallation = (user: any): user is InstallationSession => { + return !!user.isInstallation +} + +export type SessionUser = + | AccountUserSession + | BudibaseUserSession + | InstallationSession diff --git a/packages/types/src/documents/app/automation.ts b/packages/types/src/documents/app/automation.ts index 478ef0b7de..ae6c3918a6 100644 --- a/packages/types/src/documents/app/automation.ts +++ b/packages/types/src/documents/app/automation.ts @@ -5,8 +5,15 @@ export interface Automation extends Document { steps: AutomationStep[] trigger: AutomationTrigger } + appId: string } -export interface AutomationStep {} +export interface AutomationStep { + id: string + stepId: string +} -export interface AutomationTrigger {} +export interface AutomationTrigger { + id: string + stepId: string +} diff --git a/packages/types/src/documents/app/datasource.ts b/packages/types/src/documents/app/datasource.ts index 63499cd02b..3a8704a0a9 100644 --- a/packages/types/src/documents/app/datasource.ts +++ b/packages/types/src/documents/app/datasource.ts @@ -1,3 +1,5 @@ import { Document } from "../document" -export interface Datasource extends Document {} +export interface Datasource extends Document { + source: string +} diff --git a/packages/types/src/documents/platform/info.ts b/packages/types/src/documents/platform/info.ts index 782ce5dd09..ce493bc26f 100644 --- a/packages/types/src/documents/platform/info.ts +++ b/packages/types/src/documents/platform/info.ts @@ -1 +1,9 @@ +import { Document } from "../document" + export interface GlobalInfo {} + +export interface Installation extends Document { + _id: string + installId: string + version: string +} diff --git a/packages/types/src/events/auth.ts b/packages/types/src/events/auth.ts index e5dfb224bc..c0e046a2ca 100644 --- a/packages/types/src/events/auth.ts +++ b/packages/types/src/events/auth.ts @@ -2,15 +2,26 @@ export type LoginSource = "local" | "google" | "oidc" export type SSOType = "oidc" | "google" export interface LoginEvent { + userId: string source: LoginSource } -export interface LogoutEvent {} +export interface LogoutEvent { + userId: string +} -export interface SSOCreatedEvent {} +export interface SSOCreatedEvent { + type: SSOType +} -export interface SSOUpdatedEvent {} +export interface SSOUpdatedEvent { + type: SSOType +} -export interface SSOActivatedEvent {} +export interface SSOActivatedEvent { + type: SSOType +} -export interface SSODeactivatedEvent {} +export interface SSODeactivatedEvent { + type: SSOType +} diff --git a/packages/types/src/events/automation.ts b/packages/types/src/events/automation.ts index 699d130618..4bd40bee92 100644 --- a/packages/types/src/events/automation.ts +++ b/packages/types/src/events/automation.ts @@ -1,11 +1,45 @@ -export interface AutomationCreatedEvent {} +export interface AutomationCreatedEvent { + appId: string + automationId: string + triggerId: string + triggerType: string +} -export interface AutomationDeletedEvent {} +export interface AutomationTriggerUpdatedEvent { + appId: string + automationId: string + triggerId: string + triggerType: string +} -export interface AutomationTestedEvent {} +export interface AutomationDeletedEvent { + appId: string + automationId: string + triggerId: string + triggerType: string +} -export interface AutomationStepCreatedEvent {} +export interface AutomationTestedEvent { + appId: string + automationId: string + triggerId: string + triggerType: string +} -export interface AutomationStepDeletedEvent {} +export interface AutomationStepCreatedEvent { + appId: string + automationId: string + triggerId: string + triggerType: string + stepId: string + stepType: string +} -export interface AutomationTriggerUpdatedEvent {} +export interface AutomationStepDeletedEvent { + appId: string + automationId: string + triggerId: string + triggerType: string + stepId: string + stepType: string +} diff --git a/packages/types/src/events/datasource.ts b/packages/types/src/events/datasource.ts index 2eca9e30f5..cead646477 100644 --- a/packages/types/src/events/datasource.ts +++ b/packages/types/src/events/datasource.ts @@ -1,5 +1,14 @@ -export interface DatasourceCreatedEvent {} +export interface DatasourceCreatedEvent { + datasourceId: string + source: string +} -export interface DatasourceUpdatedEvent {} +export interface DatasourceUpdatedEvent { + datasourceId: string + source: string +} -export interface DatasourceDeletedEvent {} +export interface DatasourceDeletedEvent { + datasourceId: string + source: string +} diff --git a/packages/types/src/events/event.ts b/packages/types/src/events/event.ts index e4d6b40d6e..910a30f925 100644 --- a/packages/types/src/events/event.ts +++ b/packages/types/src/events/event.ts @@ -38,7 +38,9 @@ export enum Event { ORG_PLATFORM_URL_UPDATED = "org:platformurl:updated", // ORG / UPDATE - UPDATE_VERSION_CHECKED = "version:checked", + VERSION_CHECKED = "version:checked", + VERSION_UPGRADED = "version:upgraded", + VERSION_DOWNGRADED = "version:downgraded", // ORG / ANALYTICS ANALYTICS_OPT_OUT = "analytics:opt:out", diff --git a/packages/types/src/events/identification.ts b/packages/types/src/events/identification.ts index 73072134d9..29d0aa333e 100644 --- a/packages/types/src/events/identification.ts +++ b/packages/types/src/events/identification.ts @@ -1,8 +1,9 @@ import { Hosting } from "../core" export enum IdentityType { - USER = "user", // cloud and self hosted users - TENANT = "tenant", // cloud and self hosted tenants + USER = "user", + TENANT = "tenant", + INSTALLATION = "installation", } export interface Identity { @@ -11,6 +12,11 @@ export interface Identity { type: IdentityType } +export interface InstallationIdentity extends Identity { + version: string + hosting: Hosting +} + export interface TenantIdentity extends Identity { hosting: Hosting profession?: string diff --git a/packages/types/src/events/index.ts b/packages/types/src/events/index.ts index 678bb18485..c55711205c 100644 --- a/packages/types/src/events/index.ts +++ b/packages/types/src/events/index.ts @@ -6,7 +6,7 @@ export * from "./datasource" export * from "./event" export * from "./layout" export * from "./license" -export * from "./org" +export * from "./version" export * from "./query" export * from "./role" export * from "./rows" diff --git a/packages/types/src/events/layout.ts b/packages/types/src/events/layout.ts index fbfac2c411..b0b5be4f6d 100644 --- a/packages/types/src/events/layout.ts +++ b/packages/types/src/events/layout.ts @@ -1,3 +1,7 @@ -export interface LayoutCreatedEvent {} +export interface LayoutCreatedEvent { + layoutId: string +} -export interface LayoutDeletedEvent {} +export interface LayoutDeletedEvent { + layoutId: string +} diff --git a/packages/types/src/events/org.ts b/packages/types/src/events/org.ts deleted file mode 100644 index 8d0243366e..0000000000 --- a/packages/types/src/events/org.ts +++ /dev/null @@ -1 +0,0 @@ -export interface VersionCheckedEvent {} diff --git a/packages/types/src/events/version.ts b/packages/types/src/events/version.ts new file mode 100644 index 0000000000..07873212c1 --- /dev/null +++ b/packages/types/src/events/version.ts @@ -0,0 +1,8 @@ +export interface VersionCheckedEvent { + currentVersion: string +} + +export interface VersionChangeEvent { + from: string + to: string +}