mirror of https://github.com/Budibase/budibase.git
409 changed files with 13176 additions and 6345 deletions
@ -1,48 +1,79 @@ |
|||||
{ |
{ |
||||
"name": "@budibase/backend-core", |
"name": "@budibase/backend-core", |
||||
"version": "1.0.192-alpha.2", |
"version": "1.0.200-alpha.3", |
||||
"description": "Budibase backend core libraries used in server and worker", |
"description": "Budibase backend core libraries used in server and worker", |
||||
"main": "src/index.js", |
"main": "dist/src/index.js", |
||||
|
"types": "dist/src/index.d.ts", |
||||
|
"exports": { |
||||
|
".": "./dist/src/index.js", |
||||
|
"./tests": "./dist/tests/index.js", |
||||
|
"./*": "./dist/*.js" |
||||
|
}, |
||||
"author": "Budibase", |
"author": "Budibase", |
||||
"license": "GPL-3.0", |
"license": "GPL-3.0", |
||||
"scripts": { |
"scripts": { |
||||
|
"prebuild": "rimraf dist/", |
||||
|
"build": "tsc -p tsconfig.build.json", |
||||
|
"build:dev": "yarn prebuild && tsc --build --watch --preserveWatchOutput", |
||||
"test": "jest", |
"test": "jest", |
||||
"test:watch": "jest --watchAll" |
"test:watch": "jest --watchAll" |
||||
}, |
}, |
||||
"dependencies": { |
"dependencies": { |
||||
"@techpass/passport-openidconnect": "^0.3.0", |
"@techpass/passport-openidconnect": "0.3.2", |
||||
"aws-sdk": "^2.901.0", |
"aws-sdk": "2.1030.0", |
||||
"bcrypt": "^5.0.1", |
"bcrypt": "5.0.1", |
||||
"dotenv": "^16.0.1", |
"dotenv": "16.0.1", |
||||
"emitter-listener": "^1.1.2", |
"emitter-listener": "1.1.2", |
||||
"ioredis": "^4.27.1", |
"ioredis": "4.28.0", |
||||
"jsonwebtoken": "^8.5.1", |
"jsonwebtoken": "8.5.1", |
||||
"koa-passport": "^4.1.4", |
"koa-passport": "4.1.4", |
||||
"lodash": "^4.17.21", |
"lodash": "4.17.21", |
||||
"lodash.isarguments": "^3.1.0", |
"lodash.isarguments": "3.1.0", |
||||
"node-fetch": "^2.6.1", |
"node-fetch": "2.6.7", |
||||
"passport-google-auth": "^1.0.2", |
"passport-google-auth": "1.0.2", |
||||
"passport-google-oauth": "^2.0.0", |
"passport-google-oauth": "2.0.0", |
||||
"passport-jwt": "^4.0.0", |
"passport-jwt": "4.0.0", |
||||
"passport-local": "^1.0.0", |
"passport-local": "1.0.0", |
||||
"posthog-node": "^1.3.0", |
"posthog-node": "1.3.0", |
||||
"pouchdb": "7.3.0", |
"pouchdb": "7.3.0", |
||||
"pouchdb-find": "^7.2.2", |
"pouchdb-find": "7.2.2", |
||||
"pouchdb-replication-stream": "^1.2.9", |
"pouchdb-replication-stream": "1.2.9", |
||||
"sanitize-s3-objectkey": "^0.0.1", |
"redlock": "4.2.0", |
||||
"tar-fs": "^2.1.1", |
"sanitize-s3-objectkey": "0.0.1", |
||||
"uuid": "^8.3.2", |
"semver": "7.3.7", |
||||
"zlib": "^1.0.5" |
"tar-fs": "2.1.1", |
||||
|
"uuid": "8.3.2", |
||||
|
"zlib": "1.0.5" |
||||
}, |
}, |
||||
"jest": { |
"jest": { |
||||
|
"preset": "ts-jest", |
||||
|
"testEnvironment": "node", |
||||
|
"moduleNameMapper": { |
||||
|
"@budibase/types": "<rootDir>/../types/src" |
||||
|
}, |
||||
"setupFiles": [ |
"setupFiles": [ |
||||
"./scripts/jestSetup.js" |
"./scripts/jestSetup.ts" |
||||
] |
] |
||||
}, |
}, |
||||
"devDependencies": { |
"devDependencies": { |
||||
"ioredis-mock": "^5.5.5", |
"@budibase/types": "^1.0.200-alpha.3", |
||||
"jest": "^26.6.3", |
"@shopify/jest-koa-mocks": "3.1.5", |
||||
"pouchdb-adapter-memory": "^7.2.2" |
"@types/jest": "27.5.1", |
||||
|
"@types/koa": "2.0.52", |
||||
|
"@types/node": "14.18.20", |
||||
|
"@types/node-fetch": "2.6.1", |
||||
|
"@types/redlock": "4.0.3", |
||||
|
"@types/semver": "7.3.7", |
||||
|
"@types/tar-fs": "2.0.1", |
||||
|
"@types/uuid": "8.3.4", |
||||
|
"ioredis-mock": "5.8.0", |
||||
|
"jest": "27.5.1", |
||||
|
"koa": "2.7.0", |
||||
|
"nodemon": "2.0.16", |
||||
|
"pouchdb-adapter-memory": "7.2.2", |
||||
|
"timekeeper": "2.2.0", |
||||
|
"ts-jest": "27.1.5", |
||||
|
"typescript": "4.7.3" |
||||
}, |
}, |
||||
"gitHead": "d1836a898cab3f8ab80ee6d8f42be1a9eed7dcdc" |
"gitHead": "d1836a898cab3f8ab80ee6d8f42be1a9eed7dcdc" |
||||
} |
} |
||||
|
|||||
@ -1,4 +1,5 @@ |
|||||
module.exports = { |
module.exports = { |
||||
Client: require("./src/redis"), |
Client: require("./src/redis"), |
||||
utils: require("./src/redis/utils"), |
utils: require("./src/redis/utils"), |
||||
|
clients: require("./src/redis/authRedis"), |
||||
} |
} |
||||
|
|||||
@ -1,6 +0,0 @@ |
|||||
const env = require("../src/environment") |
|
||||
|
|
||||
env._set("SELF_HOSTED", "1") |
|
||||
env._set("NODE_ENV", "jest") |
|
||||
env._set("JWT_SECRET", "test-jwtsecret") |
|
||||
env._set("LOG_LEVEL", "silent") |
|
||||
@ -0,0 +1,12 @@ |
|||||
|
import env from "../src/environment" |
||||
|
import { mocks } from "../tests/utilities" |
||||
|
|
||||
|
// mock all dates to 2020-01-01T00:00:00.000Z
|
||||
|
// use tk.reset() to use real dates in individual tests
|
||||
|
import tk from "timekeeper" |
||||
|
tk.freeze(mocks.date.MOCK_DATE) |
||||
|
|
||||
|
env._set("SELF_HOSTED", "1") |
||||
|
env._set("NODE_ENV", "jest") |
||||
|
env._set("JWT_SECRET", "test-jwtsecret") |
||||
|
env._set("LOG_LEVEL", "silent") |
||||
@ -1,39 +0,0 @@ |
|||||
const API = require("./api") |
|
||||
const env = require("../environment") |
|
||||
const { Headers } = require("../constants") |
|
||||
|
|
||||
const api = new API(env.ACCOUNT_PORTAL_URL) |
|
||||
|
|
||||
exports.getAccount = async email => { |
|
||||
const payload = { |
|
||||
email, |
|
||||
} |
|
||||
const response = await api.post(`/api/accounts/search`, { |
|
||||
body: payload, |
|
||||
headers: { |
|
||||
[Headers.API_KEY]: env.ACCOUNT_PORTAL_API_KEY, |
|
||||
}, |
|
||||
}) |
|
||||
const json = await response.json() |
|
||||
|
|
||||
if (response.status !== 200) { |
|
||||
throw new Error(`Error getting account by email ${email}`, json) |
|
||||
} |
|
||||
|
|
||||
return json[0] |
|
||||
} |
|
||||
|
|
||||
exports.getStatus = async () => { |
|
||||
const response = await api.get(`/api/status`, { |
|
||||
headers: { |
|
||||
[Headers.API_KEY]: env.ACCOUNT_PORTAL_API_KEY, |
|
||||
}, |
|
||||
}) |
|
||||
const json = await response.json() |
|
||||
|
|
||||
if (response.status !== 200) { |
|
||||
throw new Error(`Error getting status`) |
|
||||
} |
|
||||
|
|
||||
return json |
|
||||
} |
|
||||
@ -0,0 +1,63 @@ |
|||||
|
import API from "./api" |
||||
|
import env from "../environment" |
||||
|
import { Headers } from "../constants" |
||||
|
import { CloudAccount } from "@budibase/types" |
||||
|
|
||||
|
const api = new API(env.ACCOUNT_PORTAL_URL) |
||||
|
|
||||
|
export const getAccount = async ( |
||||
|
email: string |
||||
|
): Promise<CloudAccount | undefined> => { |
||||
|
const payload = { |
||||
|
email, |
||||
|
} |
||||
|
const response = await api.post(`/api/accounts/search`, { |
||||
|
body: payload, |
||||
|
headers: { |
||||
|
[Headers.API_KEY]: env.ACCOUNT_PORTAL_API_KEY, |
||||
|
}, |
||||
|
}) |
||||
|
|
||||
|
if (response.status !== 200) { |
||||
|
throw new Error(`Error getting account by email ${email}`) |
||||
|
} |
||||
|
|
||||
|
const json: CloudAccount[] = await response.json() |
||||
|
return json[0] |
||||
|
} |
||||
|
|
||||
|
export const getAccountByTenantId = async ( |
||||
|
tenantId: string |
||||
|
): Promise<CloudAccount | undefined> => { |
||||
|
const payload = { |
||||
|
tenantId, |
||||
|
} |
||||
|
const response = await api.post(`/api/accounts/search`, { |
||||
|
body: payload, |
||||
|
headers: { |
||||
|
[Headers.API_KEY]: env.ACCOUNT_PORTAL_API_KEY, |
||||
|
}, |
||||
|
}) |
||||
|
|
||||
|
if (response.status !== 200) { |
||||
|
throw new Error(`Error getting account by tenantId ${tenantId}`) |
||||
|
} |
||||
|
|
||||
|
const json: CloudAccount[] = await response.json() |
||||
|
return json[0] |
||||
|
} |
||||
|
|
||||
|
export const getStatus = async () => { |
||||
|
const response = await api.get(`/api/status`, { |
||||
|
headers: { |
||||
|
[Headers.API_KEY]: env.ACCOUNT_PORTAL_API_KEY, |
||||
|
}, |
||||
|
}) |
||||
|
const json = await response.json() |
||||
|
|
||||
|
if (response.status !== 200) { |
||||
|
throw new Error(`Error getting status`) |
||||
|
} |
||||
|
|
||||
|
return json |
||||
|
} |
||||
@ -0,0 +1,50 @@ |
|||||
|
import { |
||||
|
IdentityContext, |
||||
|
IdentityType, |
||||
|
User, |
||||
|
UserContext, |
||||
|
isCloudAccount, |
||||
|
Account, |
||||
|
AccountUserContext, |
||||
|
} from "@budibase/types" |
||||
|
import * as context from "." |
||||
|
|
||||
|
export const getIdentity = (): IdentityContext | undefined => { |
||||
|
return context.getIdentity() |
||||
|
} |
||||
|
|
||||
|
export const doInIdentityContext = (identity: IdentityContext, task: any) => { |
||||
|
return context.doInIdentityContext(identity, task) |
||||
|
} |
||||
|
|
||||
|
export const doInUserContext = (user: User, task: any) => { |
||||
|
const userContext: UserContext = { |
||||
|
...user, |
||||
|
_id: user._id as string, |
||||
|
type: IdentityType.USER, |
||||
|
} |
||||
|
return doInIdentityContext(userContext, task) |
||||
|
} |
||||
|
|
||||
|
export const doInAccountContext = (account: Account, task: any) => { |
||||
|
const _id = getAccountUserId(account) |
||||
|
const tenantId = account.tenantId |
||||
|
const accountContext: AccountUserContext = { |
||||
|
_id, |
||||
|
type: IdentityType.USER, |
||||
|
tenantId, |
||||
|
account, |
||||
|
} |
||||
|
return doInIdentityContext(accountContext, task) |
||||
|
} |
||||
|
|
||||
|
export const getAccountUserId = (account: Account) => { |
||||
|
let userId: string |
||||
|
if (isCloudAccount(account)) { |
||||
|
userId = account.budibaseUserId |
||||
|
} else { |
||||
|
// use account id as user id for self hosting
|
||||
|
userId = account.accountId |
||||
|
} |
||||
|
return userId |
||||
|
} |
||||
@ -0,0 +1,26 @@ |
|||||
|
require("../../../tests/utilities/TestConfiguration") |
||||
|
const { dangerousGetDB } = require("../") |
||||
|
|
||||
|
describe("db", () => { |
||||
|
|
||||
|
describe("getDB", () => { |
||||
|
it("returns a db", async () => { |
||||
|
const db = dangerousGetDB("test") |
||||
|
expect(db).toBeDefined() |
||||
|
expect(db._adapter).toBe("memory") |
||||
|
expect(db.prefix).toBe("_pouch_") |
||||
|
expect(db.name).toBe("test") |
||||
|
}) |
||||
|
|
||||
|
it("uses the custom put function", async () => { |
||||
|
const db = dangerousGetDB("test") |
||||
|
let doc = { _id: "test" } |
||||
|
await db.put(doc) |
||||
|
doc = await db.get(doc._id) |
||||
|
expect(doc.createdAt).toBe(new Date().toISOString()) |
||||
|
expect(doc.updatedAt).toBe(new Date().toISOString()) |
||||
|
await db.destroy() |
||||
|
}) |
||||
|
}) |
||||
|
}) |
||||
|
|
||||
@ -0,0 +1,11 @@ |
|||||
|
const { BudibaseError } = require("./base") |
||||
|
|
||||
|
class GenericError extends BudibaseError { |
||||
|
constructor(message, code, type) { |
||||
|
super(message, code, type ? type : "generic") |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
module.exports = { |
||||
|
GenericError, |
||||
|
} |
||||
@ -0,0 +1,12 @@ |
|||||
|
const { GenericError } = require("./generic") |
||||
|
|
||||
|
class HTTPError extends GenericError { |
||||
|
constructor(message, httpStatus, code = "http", type = "generic") { |
||||
|
super(message, code, type) |
||||
|
this.status = httpStatus |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
module.exports = { |
||||
|
HTTPError, |
||||
|
} |
||||
@ -0,0 +1,57 @@ |
|||||
|
import env from "../environment" |
||||
|
import tenancy from "../tenancy" |
||||
|
import * as dbUtils from "../db/utils" |
||||
|
import { Configs } from "../constants" |
||||
|
import { withCache, TTL, CacheKeys } from "../cache/generic" |
||||
|
|
||||
|
export const enabled = async () => { |
||||
|
// cloud - always use the environment variable
|
||||
|
if (!env.SELF_HOSTED) { |
||||
|
return !!env.ENABLE_ANALYTICS |
||||
|
} |
||||
|
|
||||
|
// self host - prefer the settings doc
|
||||
|
// use cache as events have high throughput
|
||||
|
const enabledInDB = await withCache( |
||||
|
CacheKeys.ANALYTICS_ENABLED, |
||||
|
TTL.ONE_DAY, |
||||
|
async () => { |
||||
|
const settings = await getSettingsDoc() |
||||
|
|
||||
|
// need to do explicit checks in case the field is not set
|
||||
|
if (settings?.config?.analyticsEnabled === false) { |
||||
|
return false |
||||
|
} else if (settings?.config?.analyticsEnabled === true) { |
||||
|
return true |
||||
|
} |
||||
|
} |
||||
|
) |
||||
|
|
||||
|
if (enabledInDB !== undefined) { |
||||
|
return enabledInDB |
||||
|
} |
||||
|
|
||||
|
// fallback to the environment variable
|
||||
|
// explicitly check for 0 or false here, undefined or otherwise is treated as true
|
||||
|
const envEnabled: any = env.ENABLE_ANALYTICS |
||||
|
if (envEnabled === 0 || envEnabled === false) { |
||||
|
return false |
||||
|
} else { |
||||
|
return true |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
const getSettingsDoc = async () => { |
||||
|
const db = tenancy.getGlobalDB() |
||||
|
let settings |
||||
|
try { |
||||
|
settings = await db.get( |
||||
|
dbUtils.generateConfigID({ type: Configs.SETTINGS }) |
||||
|
) |
||||
|
} catch (e: any) { |
||||
|
if (e.status !== 404) { |
||||
|
throw e |
||||
|
} |
||||
|
} |
||||
|
return settings |
||||
|
} |
||||
@ -0,0 +1,183 @@ |
|||||
|
import { |
||||
|
Event, |
||||
|
BackfillMetadata, |
||||
|
CachedEvent, |
||||
|
SSOCreatedEvent, |
||||
|
AutomationCreatedEvent, |
||||
|
AutomationStepCreatedEvent, |
||||
|
DatasourceCreatedEvent, |
||||
|
LayoutCreatedEvent, |
||||
|
QueryCreatedEvent, |
||||
|
RoleCreatedEvent, |
||||
|
ScreenCreatedEvent, |
||||
|
TableCreatedEvent, |
||||
|
ViewCreatedEvent, |
||||
|
ViewCalculationCreatedEvent, |
||||
|
ViewFilterCreatedEvent, |
||||
|
AppPublishedEvent, |
||||
|
UserCreatedEvent, |
||||
|
RoleAssignedEvent, |
||||
|
UserPermissionAssignedEvent, |
||||
|
AppCreatedEvent, |
||||
|
} from "@budibase/types" |
||||
|
import * as context from "../context" |
||||
|
import { CacheKeys } from "../cache/generic" |
||||
|
import * as cache from "../cache/generic" |
||||
|
|
||||
|
// LIFECYCLE
|
||||
|
|
||||
|
export const start = async (events: Event[]) => { |
||||
|
const metadata: BackfillMetadata = { |
||||
|
eventWhitelist: events, |
||||
|
} |
||||
|
return saveBackfillMetadata(metadata) |
||||
|
} |
||||
|
|
||||
|
export const recordEvent = async (event: Event, properties: any) => { |
||||
|
const eventKey = getEventKey(event, properties) |
||||
|
// don't use a ttl - cleaned up by migration
|
||||
|
// don't use tenancy - already in the key
|
||||
|
await cache.store(eventKey, properties, undefined, { useTenancy: false }) |
||||
|
} |
||||
|
|
||||
|
export const end = async () => { |
||||
|
await deleteBackfillMetadata() |
||||
|
await clearEvents() |
||||
|
} |
||||
|
|
||||
|
// CRUD
|
||||
|
|
||||
|
const getBackfillMetadata = async (): Promise<BackfillMetadata | null> => { |
||||
|
return cache.get(CacheKeys.BACKFILL_METADATA) |
||||
|
} |
||||
|
|
||||
|
const saveBackfillMetadata = async ( |
||||
|
backfill: BackfillMetadata |
||||
|
): Promise<void> => { |
||||
|
// no TTL - deleted by backfill
|
||||
|
return cache.store(CacheKeys.BACKFILL_METADATA, backfill) |
||||
|
} |
||||
|
|
||||
|
const deleteBackfillMetadata = async (): Promise<void> => { |
||||
|
await cache.delete(CacheKeys.BACKFILL_METADATA) |
||||
|
} |
||||
|
|
||||
|
const clearEvents = async () => { |
||||
|
// wildcard
|
||||
|
const pattern = getEventKey() |
||||
|
const keys = await cache.keys(pattern) |
||||
|
|
||||
|
for (const key of keys) { |
||||
|
// delete each key
|
||||
|
// don't use tenancy, already in the key
|
||||
|
await cache.delete(key, { useTenancy: false }) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// HELPERS
|
||||
|
|
||||
|
export const isBackfillingEvent = async (event: Event) => { |
||||
|
const backfill = await getBackfillMetadata() |
||||
|
const events = backfill?.eventWhitelist |
||||
|
if (events && events.includes(event)) { |
||||
|
return true |
||||
|
} else { |
||||
|
return false |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
export const isAlreadySent = async (event: Event, properties: any) => { |
||||
|
const eventKey = getEventKey(event, properties) |
||||
|
const cachedEvent: CachedEvent = await cache.get(eventKey, { |
||||
|
useTenancy: false, |
||||
|
}) |
||||
|
return !!cachedEvent |
||||
|
} |
||||
|
|
||||
|
const CUSTOM_PROPERTY_SUFFIX: any = { |
||||
|
// APP EVENTS
|
||||
|
[Event.AUTOMATION_CREATED]: (properties: AutomationCreatedEvent) => { |
||||
|
return properties.automationId |
||||
|
}, |
||||
|
[Event.AUTOMATION_STEP_CREATED]: (properties: AutomationStepCreatedEvent) => { |
||||
|
return properties.stepId |
||||
|
}, |
||||
|
[Event.DATASOURCE_CREATED]: (properties: DatasourceCreatedEvent) => { |
||||
|
return properties.datasourceId |
||||
|
}, |
||||
|
[Event.LAYOUT_CREATED]: (properties: LayoutCreatedEvent) => { |
||||
|
return properties.layoutId |
||||
|
}, |
||||
|
[Event.QUERY_CREATED]: (properties: QueryCreatedEvent) => { |
||||
|
return properties.queryId |
||||
|
}, |
||||
|
[Event.ROLE_CREATED]: (properties: RoleCreatedEvent) => { |
||||
|
return properties.roleId |
||||
|
}, |
||||
|
[Event.SCREEN_CREATED]: (properties: ScreenCreatedEvent) => { |
||||
|
return properties.screenId |
||||
|
}, |
||||
|
[Event.TABLE_CREATED]: (properties: TableCreatedEvent) => { |
||||
|
return properties.tableId |
||||
|
}, |
||||
|
[Event.VIEW_CREATED]: (properties: ViewCreatedEvent) => { |
||||
|
return properties.tableId // best uniqueness
|
||||
|
}, |
||||
|
[Event.VIEW_CALCULATION_CREATED]: ( |
||||
|
properties: ViewCalculationCreatedEvent |
||||
|
) => { |
||||
|
return properties.tableId // best uniqueness
|
||||
|
}, |
||||
|
[Event.VIEW_FILTER_CREATED]: (properties: ViewFilterCreatedEvent) => { |
||||
|
return properties.tableId // best uniqueness
|
||||
|
}, |
||||
|
[Event.APP_CREATED]: (properties: AppCreatedEvent) => { |
||||
|
return properties.appId // best uniqueness
|
||||
|
}, |
||||
|
[Event.APP_PUBLISHED]: (properties: AppPublishedEvent) => { |
||||
|
return properties.appId // best uniqueness
|
||||
|
}, |
||||
|
// GLOBAL EVENTS
|
||||
|
[Event.AUTH_SSO_CREATED]: (properties: SSOCreatedEvent) => { |
||||
|
return properties.type |
||||
|
}, |
||||
|
[Event.AUTH_SSO_ACTIVATED]: (properties: SSOCreatedEvent) => { |
||||
|
return properties.type |
||||
|
}, |
||||
|
[Event.USER_CREATED]: (properties: UserCreatedEvent) => { |
||||
|
return properties.userId |
||||
|
}, |
||||
|
[Event.USER_PERMISSION_ADMIN_ASSIGNED]: ( |
||||
|
properties: UserPermissionAssignedEvent |
||||
|
) => { |
||||
|
return properties.userId |
||||
|
}, |
||||
|
[Event.USER_PERMISSION_BUILDER_ASSIGNED]: ( |
||||
|
properties: UserPermissionAssignedEvent |
||||
|
) => { |
||||
|
return properties.userId |
||||
|
}, |
||||
|
[Event.ROLE_ASSIGNED]: (properties: RoleAssignedEvent) => { |
||||
|
return `${properties.roleId}-${properties.userId}` |
||||
|
}, |
||||
|
} |
||||
|
|
||||
|
const getEventKey = (event?: Event, properties?: any) => { |
||||
|
let eventKey: string |
||||
|
|
||||
|
const tenantId = context.getTenantId() |
||||
|
if (event) { |
||||
|
eventKey = `${CacheKeys.EVENTS}:${tenantId}:${event}` |
||||
|
|
||||
|
// use some properties to make the key more unique
|
||||
|
const custom = CUSTOM_PROPERTY_SUFFIX[event] |
||||
|
const suffix = custom ? custom(properties) : undefined |
||||
|
if (suffix) { |
||||
|
eventKey = `${eventKey}:${suffix}` |
||||
|
} |
||||
|
} else { |
||||
|
eventKey = `${CacheKeys.EVENTS}:${tenantId}:*` |
||||
|
} |
||||
|
|
||||
|
return eventKey |
||||
|
} |
||||
@ -0,0 +1,31 @@ |
|||||
|
import { Event } from "@budibase/types" |
||||
|
import { processors } from "./processors" |
||||
|
import * as identification from "./identification" |
||||
|
import * as backfill from "./backfill" |
||||
|
|
||||
|
export const publishEvent = async ( |
||||
|
event: Event, |
||||
|
properties: any, |
||||
|
timestamp?: string | number |
||||
|
) => { |
||||
|
// in future this should use async events via a distributed queue.
|
||||
|
const identity = await identification.getCurrentIdentity() |
||||
|
|
||||
|
const backfilling = await backfill.isBackfillingEvent(event) |
||||
|
// no backfill - send the event and exit
|
||||
|
if (!backfilling) { |
||||
|
await processors.processEvent(event, identity, properties, timestamp) |
||||
|
return |
||||
|
} |
||||
|
|
||||
|
// backfill active - check if the event has been sent already
|
||||
|
const alreadySent = await backfill.isAlreadySent(event, properties) |
||||
|
if (alreadySent) { |
||||
|
// do nothing
|
||||
|
return |
||||
|
} else { |
||||
|
// send and record the event
|
||||
|
await processors.processEvent(event, identity, properties, timestamp) |
||||
|
await backfill.recordEvent(event, properties) |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,267 @@ |
|||||
|
import * as context from "../context" |
||||
|
import * as identityCtx from "../context/identity" |
||||
|
import env from "../environment" |
||||
|
import { |
||||
|
Hosting, |
||||
|
User, |
||||
|
Identity, |
||||
|
IdentityType, |
||||
|
Account, |
||||
|
isCloudAccount, |
||||
|
isSSOAccount, |
||||
|
TenantGroup, |
||||
|
SettingsConfig, |
||||
|
CloudAccount, |
||||
|
UserIdentity, |
||||
|
InstallationGroup, |
||||
|
isSelfHostAccount, |
||||
|
UserContext, |
||||
|
Group, |
||||
|
} from "@budibase/types" |
||||
|
import { processors } from "./processors" |
||||
|
import * as dbUtils from "../db/utils" |
||||
|
import { Configs } from "../constants" |
||||
|
import * as hashing from "../hashing" |
||||
|
import * as installation from "../installation" |
||||
|
import { withCache, TTL, CacheKeys } from "../cache/generic" |
||||
|
|
||||
|
const pkg = require("../../package.json") |
||||
|
|
||||
|
/** |
||||
|
* An identity can be: |
||||
|
* - account user (Self host) |
||||
|
* - budibase user |
||||
|
* - tenant |
||||
|
* - installation |
||||
|
*/ |
||||
|
export const getCurrentIdentity = async (): Promise<Identity> => { |
||||
|
let identityContext = identityCtx.getIdentity() |
||||
|
|
||||
|
let identityType |
||||
|
|
||||
|
if (!identityContext) { |
||||
|
identityType = IdentityType.TENANT |
||||
|
} else { |
||||
|
identityType = identityContext.type |
||||
|
} |
||||
|
|
||||
|
if (identityType === IdentityType.INSTALLATION) { |
||||
|
const installationId = await getInstallationId() |
||||
|
return { |
||||
|
id: formatDistinctId(installationId, identityType), |
||||
|
type: identityType, |
||||
|
installationId, |
||||
|
} |
||||
|
} else if (identityType === IdentityType.TENANT) { |
||||
|
const installationId = await getInstallationId() |
||||
|
const tenantId = await getEventTenantId(context.getTenantId()) |
||||
|
|
||||
|
return { |
||||
|
id: formatDistinctId(tenantId, identityType), |
||||
|
type: identityType, |
||||
|
installationId, |
||||
|
tenantId, |
||||
|
} |
||||
|
} else if (identityType === IdentityType.USER) { |
||||
|
const userContext = identityContext as UserContext |
||||
|
const tenantId = await getEventTenantId(context.getTenantId()) |
||||
|
let installationId: string | undefined |
||||
|
|
||||
|
// self host account users won't have installation
|
||||
|
if (!userContext.account || !isSelfHostAccount(userContext.account)) { |
||||
|
installationId = await getInstallationId() |
||||
|
} |
||||
|
|
||||
|
return { |
||||
|
id: userContext._id, |
||||
|
type: identityType, |
||||
|
installationId, |
||||
|
tenantId, |
||||
|
} |
||||
|
} else { |
||||
|
throw new Error("Unknown identity type") |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
export const identifyInstallationGroup = async ( |
||||
|
installId: string, |
||||
|
timestamp?: string | number |
||||
|
): Promise<void> => { |
||||
|
const id = installId |
||||
|
const type = IdentityType.INSTALLATION |
||||
|
const hosting = getHostingFromEnv() |
||||
|
const version = pkg.version |
||||
|
|
||||
|
const group: InstallationGroup = { |
||||
|
id, |
||||
|
type, |
||||
|
hosting, |
||||
|
version, |
||||
|
} |
||||
|
|
||||
|
await identifyGroup(group, timestamp) |
||||
|
// need to create a normal identity for the group to be able to query it globally
|
||||
|
// match the posthog syntax to link this identity to the empty auto generated one
|
||||
|
await identify({ ...group, id: `$${type}_${id}` }, timestamp) |
||||
|
} |
||||
|
|
||||
|
export const identifyTenantGroup = async ( |
||||
|
tenantId: string, |
||||
|
account: Account | undefined, |
||||
|
timestamp?: string | number |
||||
|
): Promise<void> => { |
||||
|
const id = await getEventTenantId(tenantId) |
||||
|
const type = IdentityType.TENANT |
||||
|
|
||||
|
let hosting: Hosting |
||||
|
let profession: string | undefined |
||||
|
let companySize: string | undefined |
||||
|
|
||||
|
if (account) { |
||||
|
profession = account.profession |
||||
|
companySize = account.size |
||||
|
hosting = account.hosting |
||||
|
} else { |
||||
|
hosting = getHostingFromEnv() |
||||
|
} |
||||
|
|
||||
|
const group: TenantGroup = { |
||||
|
id, |
||||
|
type, |
||||
|
hosting, |
||||
|
profession, |
||||
|
companySize, |
||||
|
} |
||||
|
|
||||
|
await identifyGroup(group, timestamp) |
||||
|
// need to create a normal identity for the group to be able to query it globally
|
||||
|
// match the posthog syntax to link this identity to the auto generated one
|
||||
|
await identify({ ...group, id: `$${type}_${id}` }, timestamp) |
||||
|
} |
||||
|
|
||||
|
export const identifyUser = async ( |
||||
|
user: User, |
||||
|
account: CloudAccount | undefined, |
||||
|
timestamp?: string | number |
||||
|
) => { |
||||
|
const id = user._id as string |
||||
|
const tenantId = await getEventTenantId(user.tenantId) |
||||
|
const type = IdentityType.USER |
||||
|
let builder = user.builder?.global || false |
||||
|
let admin = user.admin?.global || false |
||||
|
let providerType = user.providerType |
||||
|
const accountHolder = account?.budibaseUserId === user._id || false |
||||
|
const verified = |
||||
|
account && account?.budibaseUserId === user._id ? account.verified : false |
||||
|
const installationId = await getInstallationId() |
||||
|
|
||||
|
const identity: UserIdentity = { |
||||
|
id, |
||||
|
type, |
||||
|
installationId, |
||||
|
tenantId, |
||||
|
verified, |
||||
|
accountHolder, |
||||
|
providerType, |
||||
|
builder, |
||||
|
admin, |
||||
|
} |
||||
|
|
||||
|
await identify(identity, timestamp) |
||||
|
} |
||||
|
|
||||
|
export const identifyAccount = async (account: Account) => { |
||||
|
let id = account.accountId |
||||
|
const tenantId = account.tenantId |
||||
|
let type = IdentityType.USER |
||||
|
let providerType = isSSOAccount(account) ? account.providerType : undefined |
||||
|
const verified = account.verified |
||||
|
const accountHolder = true |
||||
|
|
||||
|
if (isCloudAccount(account)) { |
||||
|
if (account.budibaseUserId) { |
||||
|
// use the budibase user as the id if set
|
||||
|
id = account.budibaseUserId |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
const identity: UserIdentity = { |
||||
|
id, |
||||
|
type, |
||||
|
tenantId, |
||||
|
providerType, |
||||
|
verified, |
||||
|
accountHolder, |
||||
|
} |
||||
|
|
||||
|
await identify(identity) |
||||
|
} |
||||
|
|
||||
|
export const identify = async ( |
||||
|
identity: Identity, |
||||
|
timestamp?: string | number |
||||
|
) => { |
||||
|
await processors.identify(identity, timestamp) |
||||
|
} |
||||
|
|
||||
|
export const identifyGroup = async ( |
||||
|
group: Group, |
||||
|
timestamp?: string | number |
||||
|
) => { |
||||
|
await processors.identifyGroup(group, timestamp) |
||||
|
} |
||||
|
|
||||
|
const getHostingFromEnv = () => { |
||||
|
return env.SELF_HOSTED ? Hosting.SELF : Hosting.CLOUD |
||||
|
} |
||||
|
|
||||
|
export const getInstallationId = async () => { |
||||
|
if (isAccountPortal()) { |
||||
|
return "account-portal" |
||||
|
} |
||||
|
const install = await installation.getInstall() |
||||
|
return install.installId |
||||
|
} |
||||
|
|
||||
|
const getEventTenantId = async (tenantId: string): Promise<string> => { |
||||
|
if (env.SELF_HOSTED) { |
||||
|
return getUniqueTenantId(tenantId) |
||||
|
} else { |
||||
|
// tenant id's in the cloud are already unique
|
||||
|
return tenantId |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
const getUniqueTenantId = async (tenantId: string): Promise<string> => { |
||||
|
// make sure this tenantId always matches the tenantId in context
|
||||
|
return context.doInTenant(tenantId, () => { |
||||
|
return withCache(CacheKeys.UNIQUE_TENANT_ID, TTL.ONE_DAY, async () => { |
||||
|
const db = context.getGlobalDB() |
||||
|
const config: SettingsConfig = await dbUtils.getScopedFullConfig(db, { |
||||
|
type: Configs.SETTINGS, |
||||
|
}) |
||||
|
|
||||
|
let uniqueTenantId: string |
||||
|
if (config.config.uniqueTenantId) { |
||||
|
return config.config.uniqueTenantId |
||||
|
} else { |
||||
|
uniqueTenantId = `${hashing.newid()}_${tenantId}` |
||||
|
config.config.uniqueTenantId = uniqueTenantId |
||||
|
await db.put(config) |
||||
|
return uniqueTenantId |
||||
|
} |
||||
|
}) |
||||
|
}) |
||||
|
} |
||||
|
|
||||
|
const isAccountPortal = () => { |
||||
|
return env.SERVICE === "account-portal" |
||||
|
} |
||||
|
|
||||
|
const formatDistinctId = (id: string, type: IdentityType) => { |
||||
|
if (type === IdentityType.INSTALLATION || type === IdentityType.TENANT) { |
||||
|
return `$${type}_${id}` |
||||
|
} else { |
||||
|
return id |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,11 @@ |
|||||
|
export * from "./publishers" |
||||
|
export * as processors from "./processors" |
||||
|
export * as analytics from "./analytics" |
||||
|
export * as identification from "./identification" |
||||
|
export * as backfillCache from "./backfill" |
||||
|
|
||||
|
import { processors } from "./processors" |
||||
|
|
||||
|
export const shutdown = () => { |
||||
|
processors.shutdown() |
||||
|
} |
||||
@ -0,0 +1,61 @@ |
|||||
|
import { Event, Identity, Group, IdentityType } from "@budibase/types" |
||||
|
import { EventProcessor } from "./types" |
||||
|
import env from "../../environment" |
||||
|
import * as analytics from "../analytics" |
||||
|
import PosthogProcessor from "./PosthogProcessor" |
||||
|
|
||||
|
/** |
||||
|
* Events that are always captured. |
||||
|
*/ |
||||
|
const EVENT_WHITELIST = [Event.VERSION_UPGRADED, Event.VERSION_DOWNGRADED] |
||||
|
const IDENTITY_WHITELIST = [IdentityType.INSTALLATION, IdentityType.TENANT] |
||||
|
|
||||
|
export default class AnalyticsProcessor implements EventProcessor { |
||||
|
posthog: PosthogProcessor | undefined |
||||
|
|
||||
|
constructor() { |
||||
|
if (env.POSTHOG_TOKEN) { |
||||
|
this.posthog = new PosthogProcessor(env.POSTHOG_TOKEN) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
async processEvent( |
||||
|
event: Event, |
||||
|
identity: Identity, |
||||
|
properties: any, |
||||
|
timestamp?: string | number |
||||
|
): Promise<void> { |
||||
|
if (!EVENT_WHITELIST.includes(event) && !(await analytics.enabled())) { |
||||
|
return |
||||
|
} |
||||
|
if (this.posthog) { |
||||
|
this.posthog.processEvent(event, identity, properties, timestamp) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
async identify(identity: Identity, timestamp?: string | number) { |
||||
|
// Group indentifications (tenant and installation) always on
|
||||
|
if ( |
||||
|
!IDENTITY_WHITELIST.includes(identity.type) && |
||||
|
!(await analytics.enabled()) |
||||
|
) { |
||||
|
return |
||||
|
} |
||||
|
if (this.posthog) { |
||||
|
this.posthog.identify(identity, timestamp) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
async identifyGroup(group: Group, timestamp?: string | number) { |
||||
|
// Group indentifications (tenant and installation) always on
|
||||
|
if (this.posthog) { |
||||
|
this.posthog.identifyGroup(group, timestamp) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
shutdown() { |
||||
|
if (this.posthog) { |
||||
|
this.posthog.shutdown() |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,54 @@ |
|||||
|
import { Event, Identity, Group } from "@budibase/types" |
||||
|
import { EventProcessor } from "./types" |
||||
|
import env from "../../environment" |
||||
|
|
||||
|
const getTimestampString = (timestamp?: string | number) => { |
||||
|
let timestampString = "" |
||||
|
if (timestamp) { |
||||
|
timestampString = `[timestamp=${new Date(timestamp).toISOString()}]` |
||||
|
} |
||||
|
return timestampString |
||||
|
} |
||||
|
|
||||
|
const skipLogging = env.SELF_HOSTED && !env.isDev() |
||||
|
|
||||
|
export default class LoggingProcessor implements EventProcessor { |
||||
|
async processEvent( |
||||
|
event: Event, |
||||
|
identity: Identity, |
||||
|
properties: any, |
||||
|
timestamp?: string |
||||
|
): Promise<void> { |
||||
|
if (skipLogging) { |
||||
|
return |
||||
|
} |
||||
|
let timestampString = getTimestampString(timestamp) |
||||
|
console.log( |
||||
|
`[audit] [tenant=${identity.tenantId}] [identityType=${identity.type}] [identity=${identity.id}] ${timestampString} ${event} ` |
||||
|
) |
||||
|
} |
||||
|
|
||||
|
async identify(identity: Identity, timestamp?: string | number) { |
||||
|
if (skipLogging) { |
||||
|
return |
||||
|
} |
||||
|
let timestampString = getTimestampString(timestamp) |
||||
|
console.log( |
||||
|
`[audit] [${JSON.stringify(identity)}] ${timestampString} identified` |
||||
|
) |
||||
|
} |
||||
|
|
||||
|
async identifyGroup(group: Group, timestamp?: string | number) { |
||||
|
if (skipLogging) { |
||||
|
return |
||||
|
} |
||||
|
let timestampString = getTimestampString(timestamp) |
||||
|
console.log( |
||||
|
`[audit] [${JSON.stringify(group)}] ${timestampString} group identified` |
||||
|
) |
||||
|
} |
||||
|
|
||||
|
shutdown(): void { |
||||
|
// no-op
|
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,80 @@ |
|||||
|
import PostHog from "posthog-node" |
||||
|
import { Event, Identity, Group, BaseEvent } from "@budibase/types" |
||||
|
import { EventProcessor } from "./types" |
||||
|
import env from "../../environment" |
||||
|
import context from "../../context" |
||||
|
const pkg = require("../../../package.json") |
||||
|
|
||||
|
export default class PosthogProcessor implements EventProcessor { |
||||
|
posthog: PostHog |
||||
|
|
||||
|
constructor(token: string | undefined) { |
||||
|
if (!token) { |
||||
|
throw new Error("Posthog token is not defined") |
||||
|
} |
||||
|
this.posthog = new PostHog(token) |
||||
|
} |
||||
|
|
||||
|
async processEvent( |
||||
|
event: Event, |
||||
|
identity: Identity, |
||||
|
properties: BaseEvent, |
||||
|
timestamp?: string | number |
||||
|
): Promise<void> { |
||||
|
properties.version = pkg.version |
||||
|
properties.service = env.SERVICE |
||||
|
properties.environment = env.DEPLOYMENT_ENVIRONMENT |
||||
|
|
||||
|
const appId = context.getAppId() |
||||
|
if (appId) { |
||||
|
properties.appId = appId |
||||
|
} |
||||
|
|
||||
|
const payload: any = { distinctId: identity.id, event, properties } |
||||
|
|
||||
|
if (timestamp) { |
||||
|
payload.timestamp = new Date(timestamp) |
||||
|
} |
||||
|
|
||||
|
// add groups to the event
|
||||
|
if (identity.installationId || identity.tenantId) { |
||||
|
payload.groups = {} |
||||
|
if (identity.installationId) { |
||||
|
payload.groups.installation = identity.installationId |
||||
|
payload.properties.installationId = identity.installationId |
||||
|
} |
||||
|
if (identity.tenantId) { |
||||
|
payload.groups.tenant = identity.tenantId |
||||
|
payload.properties.tenantId = identity.tenantId |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
this.posthog.capture(payload) |
||||
|
} |
||||
|
|
||||
|
async identify(identity: Identity, timestamp?: string | number) { |
||||
|
const payload: any = { distinctId: identity.id, properties: identity } |
||||
|
if (timestamp) { |
||||
|
payload.timestamp = new Date(timestamp) |
||||
|
} |
||||
|
this.posthog.identify(payload) |
||||
|
} |
||||
|
|
||||
|
async identifyGroup(group: Group, timestamp?: string | number) { |
||||
|
const payload: any = { |
||||
|
distinctId: group.id, |
||||
|
groupType: group.type, |
||||
|
groupKey: group.id, |
||||
|
properties: group, |
||||
|
} |
||||
|
|
||||
|
if (timestamp) { |
||||
|
payload.timestamp = new Date(timestamp) |
||||
|
} |
||||
|
this.posthog.groupIdentify(payload) |
||||
|
} |
||||
|
|
||||
|
shutdown() { |
||||
|
this.posthog.shutdown() |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,46 @@ |
|||||
|
import { Event, Identity, Group } from "@budibase/types" |
||||
|
import { EventProcessor } from "./types" |
||||
|
|
||||
|
export default class Processor implements EventProcessor { |
||||
|
initialised: boolean = false |
||||
|
processors: EventProcessor[] = [] |
||||
|
|
||||
|
constructor(processors: EventProcessor[]) { |
||||
|
this.processors = processors |
||||
|
} |
||||
|
|
||||
|
async processEvent( |
||||
|
event: Event, |
||||
|
identity: Identity, |
||||
|
properties: any, |
||||
|
timestamp?: string | number |
||||
|
): Promise<void> { |
||||
|
for (const eventProcessor of this.processors) { |
||||
|
await eventProcessor.processEvent(event, identity, properties, timestamp) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
async identify( |
||||
|
identity: Identity, |
||||
|
timestamp?: string | number |
||||
|
): Promise<void> { |
||||
|
for (const eventProcessor of this.processors) { |
||||
|
await eventProcessor.identify(identity, timestamp) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
async identifyGroup( |
||||
|
identity: Group, |
||||
|
timestamp?: string | number |
||||
|
): Promise<void> { |
||||
|
for (const eventProcessor of this.processors) { |
||||
|
await eventProcessor.identifyGroup(identity, timestamp) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
shutdown() { |
||||
|
for (const eventProcessor of this.processors) { |
||||
|
eventProcessor.shutdown() |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,8 @@ |
|||||
|
import AnalyticsProcessor from "./AnalyticsProcessor" |
||||
|
import LoggingProcessor from "./LoggingProcessor" |
||||
|
import Processors from "./Processors" |
||||
|
|
||||
|
export const analyticsProcessor = new AnalyticsProcessor() |
||||
|
const loggingProcessor = new LoggingProcessor() |
||||
|
|
||||
|
export const processors = new Processors([analyticsProcessor, loggingProcessor]) |
||||
@ -0,0 +1,18 @@ |
|||||
|
import { Event, Identity, Group } from "@budibase/types" |
||||
|
|
||||
|
export enum EventProcessorType { |
||||
|
POSTHOG = "posthog", |
||||
|
LOGGING = "logging", |
||||
|
} |
||||
|
|
||||
|
export interface EventProcessor { |
||||
|
processEvent( |
||||
|
event: Event, |
||||
|
identity: Identity, |
||||
|
properties: any, |
||||
|
timestamp?: string | number |
||||
|
): Promise<void> |
||||
|
identify(identity: Identity, timestamp?: string | number): Promise<void> |
||||
|
identifyGroup(group: Group, timestamp?: string | number): Promise<void> |
||||
|
shutdown(): void |
||||
|
} |
||||
@ -0,0 +1,29 @@ |
|||||
|
import { publishEvent } from "../events" |
||||
|
import { |
||||
|
Event, |
||||
|
Account, |
||||
|
AccountCreatedEvent, |
||||
|
AccountDeletedEvent, |
||||
|
AccountVerifiedEvent, |
||||
|
} from "@budibase/types" |
||||
|
|
||||
|
export async function created(account: Account) { |
||||
|
const properties: AccountCreatedEvent = { |
||||
|
tenantId: account.tenantId, |
||||
|
} |
||||
|
await publishEvent(Event.ACCOUNT_CREATED, properties) |
||||
|
} |
||||
|
|
||||
|
export async function deleted(account: Account) { |
||||
|
const properties: AccountDeletedEvent = { |
||||
|
tenantId: account.tenantId, |
||||
|
} |
||||
|
await publishEvent(Event.ACCOUNT_DELETED, properties) |
||||
|
} |
||||
|
|
||||
|
export async function verified(account: Account) { |
||||
|
const properties: AccountVerifiedEvent = { |
||||
|
tenantId: account.tenantId, |
||||
|
} |
||||
|
await publishEvent(Event.ACCOUNT_VERIFIED, properties) |
||||
|
} |
||||
@ -0,0 +1,108 @@ |
|||||
|
import { publishEvent } from "../events" |
||||
|
import { |
||||
|
Event, |
||||
|
App, |
||||
|
AppCreatedEvent, |
||||
|
AppUpdatedEvent, |
||||
|
AppDeletedEvent, |
||||
|
AppPublishedEvent, |
||||
|
AppUnpublishedEvent, |
||||
|
AppFileImportedEvent, |
||||
|
AppTemplateImportedEvent, |
||||
|
AppVersionUpdatedEvent, |
||||
|
AppVersionRevertedEvent, |
||||
|
AppRevertedEvent, |
||||
|
AppExportedEvent, |
||||
|
} from "@budibase/types" |
||||
|
|
||||
|
export const created = async (app: App, timestamp?: string | number) => { |
||||
|
const properties: AppCreatedEvent = { |
||||
|
appId: app.appId, |
||||
|
version: app.version, |
||||
|
} |
||||
|
await publishEvent(Event.APP_CREATED, properties, timestamp) |
||||
|
} |
||||
|
|
||||
|
export async function updated(app: App) { |
||||
|
const properties: AppUpdatedEvent = { |
||||
|
appId: app.appId, |
||||
|
version: app.version, |
||||
|
} |
||||
|
await publishEvent(Event.APP_UPDATED, properties) |
||||
|
} |
||||
|
|
||||
|
export async function deleted(app: App) { |
||||
|
const properties: AppDeletedEvent = { |
||||
|
appId: app.appId, |
||||
|
} |
||||
|
await publishEvent(Event.APP_DELETED, properties) |
||||
|
} |
||||
|
|
||||
|
export async function published(app: App, timestamp?: string | number) { |
||||
|
const properties: AppPublishedEvent = { |
||||
|
appId: app.appId, |
||||
|
} |
||||
|
await publishEvent(Event.APP_PUBLISHED, properties, timestamp) |
||||
|
} |
||||
|
|
||||
|
export async function unpublished(app: App) { |
||||
|
const properties: AppUnpublishedEvent = { |
||||
|
appId: app.appId, |
||||
|
} |
||||
|
await publishEvent(Event.APP_UNPUBLISHED, properties) |
||||
|
} |
||||
|
|
||||
|
export async function fileImported(app: App) { |
||||
|
const properties: AppFileImportedEvent = { |
||||
|
appId: app.appId, |
||||
|
} |
||||
|
await publishEvent(Event.APP_FILE_IMPORTED, properties) |
||||
|
} |
||||
|
|
||||
|
export async function templateImported(app: App, templateKey: string) { |
||||
|
const properties: AppTemplateImportedEvent = { |
||||
|
appId: app.appId, |
||||
|
templateKey, |
||||
|
} |
||||
|
await publishEvent(Event.APP_TEMPLATE_IMPORTED, properties) |
||||
|
} |
||||
|
|
||||
|
export async function versionUpdated( |
||||
|
app: App, |
||||
|
currentVersion: string, |
||||
|
updatedToVersion: string |
||||
|
) { |
||||
|
const properties: AppVersionUpdatedEvent = { |
||||
|
appId: app.appId, |
||||
|
currentVersion, |
||||
|
updatedToVersion, |
||||
|
} |
||||
|
await publishEvent(Event.APP_VERSION_UPDATED, properties) |
||||
|
} |
||||
|
|
||||
|
export async function versionReverted( |
||||
|
app: App, |
||||
|
currentVersion: string, |
||||
|
revertedToVersion: string |
||||
|
) { |
||||
|
const properties: AppVersionRevertedEvent = { |
||||
|
appId: app.appId, |
||||
|
currentVersion, |
||||
|
revertedToVersion, |
||||
|
} |
||||
|
await publishEvent(Event.APP_VERSION_REVERTED, properties) |
||||
|
} |
||||
|
|
||||
|
export async function reverted(app: App) { |
||||
|
const properties: AppRevertedEvent = { |
||||
|
appId: app.appId, |
||||
|
} |
||||
|
await publishEvent(Event.APP_REVERTED, properties) |
||||
|
} |
||||
|
|
||||
|
export async function exported(app: App) { |
||||
|
const properties: AppExportedEvent = { |
||||
|
appId: app.appId, |
||||
|
} |
||||
|
await publishEvent(Event.APP_EXPORTED, properties) |
||||
|
} |
||||
@ -0,0 +1,58 @@ |
|||||
|
import { publishEvent } from "../events" |
||||
|
import { |
||||
|
Event, |
||||
|
LoginEvent, |
||||
|
LoginSource, |
||||
|
LogoutEvent, |
||||
|
SSOActivatedEvent, |
||||
|
SSOCreatedEvent, |
||||
|
SSODeactivatedEvent, |
||||
|
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 identity = await identification.getCurrentIdentity() |
||||
|
const properties: LogoutEvent = { |
||||
|
userId: identity.id, |
||||
|
} |
||||
|
await publishEvent(Event.AUTH_LOGOUT, properties) |
||||
|
} |
||||
|
|
||||
|
export async function SSOCreated(type: SSOType, timestamp?: string | number) { |
||||
|
const properties: SSOCreatedEvent = { |
||||
|
type, |
||||
|
} |
||||
|
await publishEvent(Event.AUTH_SSO_CREATED, properties, timestamp) |
||||
|
} |
||||
|
|
||||
|
export async function SSOUpdated(type: SSOType) { |
||||
|
const properties: SSOUpdatedEvent = { |
||||
|
type, |
||||
|
} |
||||
|
await publishEvent(Event.AUTH_SSO_UPDATED, properties) |
||||
|
} |
||||
|
|
||||
|
export async function SSOActivated(type: SSOType, timestamp?: string | number) { |
||||
|
const properties: SSOActivatedEvent = { |
||||
|
type, |
||||
|
} |
||||
|
await publishEvent(Event.AUTH_SSO_ACTIVATED, properties, timestamp) |
||||
|
} |
||||
|
|
||||
|
export async function SSODeactivated(type: SSOType) { |
||||
|
const properties: SSODeactivatedEvent = { |
||||
|
type, |
||||
|
} |
||||
|
await publishEvent(Event.AUTH_SSO_DEACTIVATED, properties) |
||||
|
} |
||||
@ -0,0 +1,94 @@ |
|||||
|
import { publishEvent } from "../events" |
||||
|
import { |
||||
|
Automation, |
||||
|
Event, |
||||
|
AutomationStep, |
||||
|
AutomationCreatedEvent, |
||||
|
AutomationDeletedEvent, |
||||
|
AutomationTestedEvent, |
||||
|
AutomationStepCreatedEvent, |
||||
|
AutomationStepDeletedEvent, |
||||
|
AutomationTriggerUpdatedEvent, |
||||
|
AutomationsRunEvent, |
||||
|
} from "@budibase/types" |
||||
|
|
||||
|
export async function created( |
||||
|
automation: Automation, |
||||
|
timestamp?: string | number |
||||
|
) { |
||||
|
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 = { |
||||
|
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 = { |
||||
|
appId: automation.appId, |
||||
|
automationId: automation._id as string, |
||||
|
triggerId: automation.definition?.trigger?.id, |
||||
|
triggerType: automation.definition?.trigger?.stepId, |
||||
|
} |
||||
|
await publishEvent(Event.AUTOMATION_TESTED, properties) |
||||
|
} |
||||
|
|
||||
|
export const run = async (count: number, timestamp?: string | number) => { |
||||
|
const properties: AutomationsRunEvent = { |
||||
|
count, |
||||
|
} |
||||
|
await publishEvent(Event.AUTOMATIONS_RUN, properties, timestamp) |
||||
|
} |
||||
|
|
||||
|
export async function stepCreated( |
||||
|
automation: Automation, |
||||
|
step: AutomationStep, |
||||
|
timestamp?: string | number |
||||
|
) { |
||||
|
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) |
||||
|
} |
||||
|
|
||||
|
export async function stepDeleted( |
||||
|
automation: Automation, |
||||
|
step: AutomationStep |
||||
|
) { |
||||
|
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) |
||||
|
} |
||||
@ -0,0 +1,67 @@ |
|||||
|
import { publishEvent } from "../events" |
||||
|
import { |
||||
|
Event, |
||||
|
AppBackfillSucceededEvent, |
||||
|
AppBackfillFailedEvent, |
||||
|
TenantBackfillSucceededEvent, |
||||
|
TenantBackfillFailedEvent, |
||||
|
InstallationBackfillSucceededEvent, |
||||
|
InstallationBackfillFailedEvent, |
||||
|
} from "@budibase/types" |
||||
|
const env = require("../../environment") |
||||
|
|
||||
|
const shouldSkip = !env.SELF_HOSTED && !env.isDev() |
||||
|
|
||||
|
export async function appSucceeded(properties: AppBackfillSucceededEvent) { |
||||
|
if (shouldSkip) { |
||||
|
return |
||||
|
} |
||||
|
await publishEvent(Event.APP_BACKFILL_SUCCEEDED, properties) |
||||
|
} |
||||
|
|
||||
|
export async function appFailed(error: any) { |
||||
|
if (shouldSkip) { |
||||
|
return |
||||
|
} |
||||
|
const properties: AppBackfillFailedEvent = { |
||||
|
error: JSON.stringify(error, Object.getOwnPropertyNames(error)), |
||||
|
} |
||||
|
await publishEvent(Event.APP_BACKFILL_FAILED, properties) |
||||
|
} |
||||
|
|
||||
|
export async function tenantSucceeded( |
||||
|
properties: TenantBackfillSucceededEvent |
||||
|
) { |
||||
|
if (shouldSkip) { |
||||
|
return |
||||
|
} |
||||
|
await publishEvent(Event.TENANT_BACKFILL_SUCCEEDED, properties) |
||||
|
} |
||||
|
|
||||
|
export async function tenantFailed(error: any) { |
||||
|
if (shouldSkip) { |
||||
|
return |
||||
|
} |
||||
|
const properties: TenantBackfillFailedEvent = { |
||||
|
error: JSON.stringify(error, Object.getOwnPropertyNames(error)), |
||||
|
} |
||||
|
await publishEvent(Event.TENANT_BACKFILL_FAILED, properties) |
||||
|
} |
||||
|
|
||||
|
export async function installationSucceeded() { |
||||
|
if (shouldSkip) { |
||||
|
return |
||||
|
} |
||||
|
const properties: InstallationBackfillSucceededEvent = {} |
||||
|
await publishEvent(Event.INSTALLATION_BACKFILL_SUCCEEDED, properties) |
||||
|
} |
||||
|
|
||||
|
export async function installationFailed(error: any) { |
||||
|
if (shouldSkip) { |
||||
|
return |
||||
|
} |
||||
|
const properties: InstallationBackfillFailedEvent = { |
||||
|
error: JSON.stringify(error, Object.getOwnPropertyNames(error)), |
||||
|
} |
||||
|
await publishEvent(Event.INSTALLATION_BACKFILL_FAILED, properties) |
||||
|
} |
||||
@ -0,0 +1,35 @@ |
|||||
|
import { publishEvent } from "../events" |
||||
|
import { |
||||
|
Event, |
||||
|
Datasource, |
||||
|
DatasourceCreatedEvent, |
||||
|
DatasourceUpdatedEvent, |
||||
|
DatasourceDeletedEvent, |
||||
|
} from "@budibase/types" |
||||
|
|
||||
|
export async function created( |
||||
|
datasource: Datasource, |
||||
|
timestamp?: string | number |
||||
|
) { |
||||
|
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 = { |
||||
|
datasourceId: datasource._id as string, |
||||
|
source: datasource.source, |
||||
|
} |
||||
|
await publishEvent(Event.DATASOURCE_UPDATED, properties) |
||||
|
} |
||||
|
|
||||
|
export async function deleted(datasource: Datasource) { |
||||
|
const properties: DatasourceDeletedEvent = { |
||||
|
datasourceId: datasource._id as string, |
||||
|
source: datasource.source, |
||||
|
} |
||||
|
await publishEvent(Event.DATASOURCE_DELETED, properties) |
||||
|
} |
||||
@ -0,0 +1,12 @@ |
|||||
|
import { publishEvent } from "../events" |
||||
|
import { Event, SMTPCreatedEvent, SMTPUpdatedEvent } from "@budibase/types" |
||||
|
|
||||
|
export async function SMTPCreated(timestamp?: string | number) { |
||||
|
const properties: SMTPCreatedEvent = {} |
||||
|
await publishEvent(Event.EMAIL_SMTP_CREATED, properties, timestamp) |
||||
|
} |
||||
|
|
||||
|
export async function SMTPUpdated() { |
||||
|
const properties: SMTPUpdatedEvent = {} |
||||
|
await publishEvent(Event.EMAIL_SMTP_UPDATED, properties) |
||||
|
} |
||||
@ -0,0 +1,19 @@ |
|||||
|
export * as account from "./account" |
||||
|
export * as app from "./app" |
||||
|
export * as auth from "./auth" |
||||
|
export * as automation from "./automation" |
||||
|
export * as datasource from "./datasource" |
||||
|
export * as email from "./email" |
||||
|
export * as license from "./license" |
||||
|
export * as layout from "./layout" |
||||
|
export * as org from "./org" |
||||
|
export * as query from "./query" |
||||
|
export * as role from "./role" |
||||
|
export * as screen from "./screen" |
||||
|
export * as rows from "./rows" |
||||
|
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" |
||||
|
export * as backfill from "./backfill" |
||||
@ -0,0 +1,21 @@ |
|||||
|
import { publishEvent } from "../events" |
||||
|
import { |
||||
|
Event, |
||||
|
Layout, |
||||
|
LayoutCreatedEvent, |
||||
|
LayoutDeletedEvent, |
||||
|
} from "@budibase/types" |
||||
|
|
||||
|
export async function created(layout: Layout, timestamp?: string | number) { |
||||
|
const properties: LayoutCreatedEvent = { |
||||
|
layoutId: layout._id as string, |
||||
|
} |
||||
|
await publishEvent(Event.LAYOUT_CREATED, properties, timestamp) |
||||
|
} |
||||
|
|
||||
|
export async function deleted(layoutId: string) { |
||||
|
const properties: LayoutDeletedEvent = { |
||||
|
layoutId, |
||||
|
} |
||||
|
await publishEvent(Event.LAYOUT_DELETED, properties) |
||||
|
} |
||||
@ -0,0 +1,33 @@ |
|||||
|
import { publishEvent } from "../events" |
||||
|
import { |
||||
|
Event, |
||||
|
License, |
||||
|
LicenseActivatedEvent, |
||||
|
LicenseDowngradedEvent, |
||||
|
LicenseUpdatedEvent, |
||||
|
LicenseUpgradedEvent, |
||||
|
} from "@budibase/types" |
||||
|
|
||||
|
// TODO
|
||||
|
export async function updgraded(license: License) { |
||||
|
const properties: LicenseUpgradedEvent = {} |
||||
|
await publishEvent(Event.LICENSE_UPGRADED, properties) |
||||
|
} |
||||
|
|
||||
|
// TODO
|
||||
|
export async function downgraded(license: License) { |
||||
|
const properties: LicenseDowngradedEvent = {} |
||||
|
await publishEvent(Event.LICENSE_DOWNGRADED, properties) |
||||
|
} |
||||
|
|
||||
|
// TODO
|
||||
|
export async function updated(license: License) { |
||||
|
const properties: LicenseUpdatedEvent = {} |
||||
|
await publishEvent(Event.LICENSE_UPDATED, properties) |
||||
|
} |
||||
|
|
||||
|
// TODO
|
||||
|
export async function activated(license: License) { |
||||
|
const properties: LicenseActivatedEvent = {} |
||||
|
await publishEvent(Event.LICENSE_ACTIVATED, properties) |
||||
|
} |
||||
@ -0,0 +1,29 @@ |
|||||
|
import { publishEvent } from "../events" |
||||
|
import { Event } from "@budibase/types" |
||||
|
|
||||
|
export async function nameUpdated(timestamp?: string | number) { |
||||
|
const properties = {} |
||||
|
await publishEvent(Event.ORG_NAME_UPDATED, properties, timestamp) |
||||
|
} |
||||
|
|
||||
|
export async function logoUpdated(timestamp?: string | number) { |
||||
|
const properties = {} |
||||
|
await publishEvent(Event.ORG_LOGO_UPDATED, properties, timestamp) |
||||
|
} |
||||
|
|
||||
|
export async function platformURLUpdated(timestamp?: string | number) { |
||||
|
const properties = {} |
||||
|
await publishEvent(Event.ORG_PLATFORM_URL_UPDATED, properties, timestamp) |
||||
|
} |
||||
|
|
||||
|
// TODO
|
||||
|
|
||||
|
export async function analyticsOptOut() { |
||||
|
const properties = {} |
||||
|
await publishEvent(Event.ANALYTICS_OPT_OUT, properties) |
||||
|
} |
||||
|
|
||||
|
export async function analyticsOptIn() { |
||||
|
const properties = {} |
||||
|
await publishEvent(Event.ANALYTICS_OPT_OUT, properties) |
||||
|
} |
||||
@ -0,0 +1,79 @@ |
|||||
|
import { publishEvent } from "../events" |
||||
|
import { |
||||
|
Event, |
||||
|
Datasource, |
||||
|
Query, |
||||
|
QueryCreatedEvent, |
||||
|
QueryUpdatedEvent, |
||||
|
QueryDeletedEvent, |
||||
|
QueryImportedEvent, |
||||
|
QueryPreviewedEvent, |
||||
|
QueriesRunEvent, |
||||
|
} from "@budibase/types" |
||||
|
|
||||
|
/* eslint-disable */ |
||||
|
|
||||
|
export const created = async ( |
||||
|
datasource: Datasource, |
||||
|
query: Query, |
||||
|
timestamp?: string | number |
||||
|
) => { |
||||
|
const properties: QueryCreatedEvent = { |
||||
|
queryId: query._id as string, |
||||
|
datasourceId: datasource._id as string, |
||||
|
source: datasource.source, |
||||
|
queryVerb: query.queryVerb, |
||||
|
} |
||||
|
await publishEvent(Event.QUERY_CREATED, properties, timestamp) |
||||
|
} |
||||
|
|
||||
|
export const updated = async (datasource: Datasource, query: Query) => { |
||||
|
const properties: QueryUpdatedEvent = { |
||||
|
queryId: query._id as string, |
||||
|
datasourceId: datasource._id as string, |
||||
|
source: datasource.source, |
||||
|
queryVerb: query.queryVerb, |
||||
|
} |
||||
|
await publishEvent(Event.QUERY_UPDATED, properties) |
||||
|
} |
||||
|
|
||||
|
export const deleted = async (datasource: Datasource, query: Query) => { |
||||
|
const properties: QueryDeletedEvent = { |
||||
|
queryId: query._id as string, |
||||
|
datasourceId: datasource._id as string, |
||||
|
source: datasource.source, |
||||
|
queryVerb: query.queryVerb, |
||||
|
} |
||||
|
await publishEvent(Event.QUERY_DELETED, properties) |
||||
|
} |
||||
|
|
||||
|
export const imported = async ( |
||||
|
datasource: Datasource, |
||||
|
importSource: any, |
||||
|
count: any |
||||
|
) => { |
||||
|
const properties: QueryImportedEvent = { |
||||
|
datasourceId: datasource._id as string, |
||||
|
source: datasource.source, |
||||
|
count, |
||||
|
importSource, |
||||
|
} |
||||
|
await publishEvent(Event.QUERY_IMPORT, properties) |
||||
|
} |
||||
|
|
||||
|
export const run = async (count: number, timestamp?: string | number) => { |
||||
|
const properties: QueriesRunEvent = { |
||||
|
count, |
||||
|
} |
||||
|
await publishEvent(Event.QUERIES_RUN, properties, timestamp) |
||||
|
} |
||||
|
|
||||
|
export const previewed = async (datasource: Datasource, query: Query) => { |
||||
|
const properties: QueryPreviewedEvent = { |
||||
|
queryId: query._id, |
||||
|
datasourceId: datasource._id as string, |
||||
|
source: datasource.source, |
||||
|
queryVerb: query.queryVerb, |
||||
|
} |
||||
|
await publishEvent(Event.QUERY_PREVIEWED, properties) |
||||
|
} |
||||
@ -0,0 +1,54 @@ |
|||||
|
import { publishEvent } from "../events" |
||||
|
import { |
||||
|
Event, |
||||
|
Role, |
||||
|
RoleAssignedEvent, |
||||
|
RoleCreatedEvent, |
||||
|
RoleDeletedEvent, |
||||
|
RoleUnassignedEvent, |
||||
|
RoleUpdatedEvent, |
||||
|
User, |
||||
|
} from "@budibase/types" |
||||
|
|
||||
|
export async function created(role: Role, timestamp?: string | number) { |
||||
|
const properties: RoleCreatedEvent = { |
||||
|
roleId: role._id as string, |
||||
|
permissionId: role.permissionId, |
||||
|
inherits: role.inherits, |
||||
|
} |
||||
|
await publishEvent(Event.ROLE_CREATED, properties, timestamp) |
||||
|
} |
||||
|
|
||||
|
export async function updated(role: Role) { |
||||
|
const properties: RoleUpdatedEvent = { |
||||
|
roleId: role._id as string, |
||||
|
permissionId: role.permissionId, |
||||
|
inherits: role.inherits, |
||||
|
} |
||||
|
await publishEvent(Event.ROLE_UPDATED, properties) |
||||
|
} |
||||
|
|
||||
|
export async function deleted(role: Role) { |
||||
|
const properties: RoleDeletedEvent = { |
||||
|
roleId: role._id as string, |
||||
|
permissionId: role.permissionId, |
||||
|
inherits: role.inherits, |
||||
|
} |
||||
|
await publishEvent(Event.ROLE_DELETED, properties) |
||||
|
} |
||||
|
|
||||
|
export async function assigned(user: User, roleId: string, timestamp?: number) { |
||||
|
const properties: RoleAssignedEvent = { |
||||
|
userId: user._id as string, |
||||
|
roleId, |
||||
|
} |
||||
|
await publishEvent(Event.ROLE_ASSIGNED, properties, timestamp) |
||||
|
} |
||||
|
|
||||
|
export async function unassigned(user: User, roleId: string) { |
||||
|
const properties: RoleUnassignedEvent = { |
||||
|
userId: user._id as string, |
||||
|
roleId, |
||||
|
} |
||||
|
await publishEvent(Event.ROLE_UNASSIGNED, properties) |
||||
|
} |
||||
@ -0,0 +1,30 @@ |
|||||
|
import { publishEvent } from "../events" |
||||
|
import { |
||||
|
Event, |
||||
|
RowsImportedEvent, |
||||
|
RowsCreatedEvent, |
||||
|
RowImportFormat, |
||||
|
Table, |
||||
|
} from "@budibase/types" |
||||
|
|
||||
|
/* eslint-disable */ |
||||
|
|
||||
|
export const created = async (count: number, timestamp?: string | number) => { |
||||
|
const properties: RowsCreatedEvent = { |
||||
|
count, |
||||
|
} |
||||
|
await publishEvent(Event.ROWS_CREATED, properties, timestamp) |
||||
|
} |
||||
|
|
||||
|
export const imported = async ( |
||||
|
table: Table, |
||||
|
format: RowImportFormat, |
||||
|
count: number |
||||
|
) => { |
||||
|
const properties: RowsImportedEvent = { |
||||
|
tableId: table._id as string, |
||||
|
format, |
||||
|
count, |
||||
|
} |
||||
|
await publishEvent(Event.ROWS_IMPORTED, properties) |
||||
|
} |
||||
@ -0,0 +1,25 @@ |
|||||
|
import { publishEvent } from "../events" |
||||
|
import { |
||||
|
Event, |
||||
|
Screen, |
||||
|
ScreenCreatedEvent, |
||||
|
ScreenDeletedEvent, |
||||
|
} from "@budibase/types" |
||||
|
|
||||
|
export async function created(screen: Screen, timestamp?: string | number) { |
||||
|
const properties: ScreenCreatedEvent = { |
||||
|
layoutId: screen.layoutId, |
||||
|
screenId: screen._id as string, |
||||
|
roleId: screen.routing.roleId, |
||||
|
} |
||||
|
await publishEvent(Event.SCREEN_CREATED, properties, timestamp) |
||||
|
} |
||||
|
|
||||
|
export async function deleted(screen: Screen) { |
||||
|
const properties: ScreenDeletedEvent = { |
||||
|
layoutId: screen.layoutId, |
||||
|
screenId: screen._id as string, |
||||
|
roleId: screen.routing.roleId, |
||||
|
} |
||||
|
await publishEvent(Event.SCREEN_DELETED, properties) |
||||
|
} |
||||
@ -0,0 +1,28 @@ |
|||||
|
import { publishEvent } from "../events" |
||||
|
import { |
||||
|
App, |
||||
|
BuilderServedEvent, |
||||
|
Event, |
||||
|
AppPreviewServedEvent, |
||||
|
AppServedEvent, |
||||
|
} from "@budibase/types" |
||||
|
|
||||
|
export async function servedBuilder() { |
||||
|
const properties: BuilderServedEvent = {} |
||||
|
await publishEvent(Event.SERVED_BUILDER, properties) |
||||
|
} |
||||
|
|
||||
|
export async function servedApp(app: App) { |
||||
|
const properties: AppServedEvent = { |
||||
|
appVersion: app.version, |
||||
|
} |
||||
|
await publishEvent(Event.SERVED_APP, properties) |
||||
|
} |
||||
|
|
||||
|
export async function servedAppPreview(app: App) { |
||||
|
const properties: AppPreviewServedEvent = { |
||||
|
appId: app.appId, |
||||
|
appVersion: app.version, |
||||
|
} |
||||
|
await publishEvent(Event.SERVED_APP_PREVIEW, properties) |
||||
|
} |
||||
@ -0,0 +1,49 @@ |
|||||
|
import { publishEvent } from "../events" |
||||
|
import { |
||||
|
Event, |
||||
|
TableExportFormat, |
||||
|
TableImportFormat, |
||||
|
Table, |
||||
|
TableCreatedEvent, |
||||
|
TableUpdatedEvent, |
||||
|
TableDeletedEvent, |
||||
|
TableExportedEvent, |
||||
|
TableImportedEvent, |
||||
|
} from "@budibase/types" |
||||
|
|
||||
|
export async function created(table: Table, timestamp?: string | number) { |
||||
|
const properties: TableCreatedEvent = { |
||||
|
tableId: table._id as string, |
||||
|
} |
||||
|
await publishEvent(Event.TABLE_CREATED, properties, timestamp) |
||||
|
} |
||||
|
|
||||
|
export async function updated(table: Table) { |
||||
|
const properties: TableUpdatedEvent = { |
||||
|
tableId: table._id as string, |
||||
|
} |
||||
|
await publishEvent(Event.TABLE_UPDATED, properties) |
||||
|
} |
||||
|
|
||||
|
export async function deleted(table: Table) { |
||||
|
const properties: TableDeletedEvent = { |
||||
|
tableId: table._id as string, |
||||
|
} |
||||
|
await publishEvent(Event.TABLE_DELETED, properties) |
||||
|
} |
||||
|
|
||||
|
export async function exported(table: Table, format: TableExportFormat) { |
||||
|
const properties: TableExportedEvent = { |
||||
|
tableId: table._id as string, |
||||
|
format, |
||||
|
} |
||||
|
await publishEvent(Event.TABLE_EXPORTED, properties) |
||||
|
} |
||||
|
|
||||
|
export async function imported(table: Table, format: TableImportFormat) { |
||||
|
const properties: TableImportedEvent = { |
||||
|
tableId: table._id as string, |
||||
|
format, |
||||
|
} |
||||
|
await publishEvent(Event.TABLE_IMPORTED, properties) |
||||
|
} |
||||
@ -0,0 +1,122 @@ |
|||||
|
import { publishEvent } from "../events" |
||||
|
import { |
||||
|
Event, |
||||
|
User, |
||||
|
UserCreatedEvent, |
||||
|
UserDeletedEvent, |
||||
|
UserInviteAcceptedEvent, |
||||
|
UserInvitedEvent, |
||||
|
UserPasswordForceResetEvent, |
||||
|
UserPasswordResetEvent, |
||||
|
UserPasswordResetRequestedEvent, |
||||
|
UserPasswordUpdatedEvent, |
||||
|
UserPermissionAssignedEvent, |
||||
|
UserPermissionRemovedEvent, |
||||
|
UserUpdatedEvent, |
||||
|
} from "@budibase/types" |
||||
|
|
||||
|
export async function created(user: User, timestamp?: number) { |
||||
|
const properties: UserCreatedEvent = { |
||||
|
userId: user._id as string, |
||||
|
} |
||||
|
await publishEvent(Event.USER_CREATED, properties, timestamp) |
||||
|
} |
||||
|
|
||||
|
export async function updated(user: User) { |
||||
|
const properties: UserUpdatedEvent = { |
||||
|
userId: user._id as string, |
||||
|
} |
||||
|
await publishEvent(Event.USER_UPDATED, properties) |
||||
|
} |
||||
|
|
||||
|
export async function deleted(user: User) { |
||||
|
const properties: UserDeletedEvent = { |
||||
|
userId: user._id as string, |
||||
|
} |
||||
|
await publishEvent(Event.USER_DELETED, properties) |
||||
|
} |
||||
|
|
||||
|
// PERMISSIONS
|
||||
|
|
||||
|
export async function permissionAdminAssigned(user: User, timestamp?: number) { |
||||
|
const properties: UserPermissionAssignedEvent = { |
||||
|
userId: user._id as string, |
||||
|
} |
||||
|
await publishEvent( |
||||
|
Event.USER_PERMISSION_ADMIN_ASSIGNED, |
||||
|
properties, |
||||
|
timestamp |
||||
|
) |
||||
|
} |
||||
|
|
||||
|
export async function permissionAdminRemoved(user: User) { |
||||
|
const properties: UserPermissionRemovedEvent = { |
||||
|
userId: user._id as string, |
||||
|
} |
||||
|
await publishEvent(Event.USER_PERMISSION_ADMIN_REMOVED, properties) |
||||
|
} |
||||
|
|
||||
|
export async function permissionBuilderAssigned( |
||||
|
user: User, |
||||
|
timestamp?: number |
||||
|
) { |
||||
|
const properties: UserPermissionAssignedEvent = { |
||||
|
userId: user._id as string, |
||||
|
} |
||||
|
await publishEvent( |
||||
|
Event.USER_PERMISSION_BUILDER_ASSIGNED, |
||||
|
properties, |
||||
|
timestamp |
||||
|
) |
||||
|
} |
||||
|
|
||||
|
export async function permissionBuilderRemoved(user: User) { |
||||
|
const properties: UserPermissionRemovedEvent = { |
||||
|
userId: user._id as string, |
||||
|
} |
||||
|
await publishEvent(Event.USER_PERMISSION_BUILDER_REMOVED, properties) |
||||
|
} |
||||
|
|
||||
|
// INVITE
|
||||
|
|
||||
|
export async function invited() { |
||||
|
const properties: UserInvitedEvent = {} |
||||
|
await publishEvent(Event.USER_INVITED, properties) |
||||
|
} |
||||
|
|
||||
|
export async function inviteAccepted(user: User) { |
||||
|
const properties: UserInviteAcceptedEvent = { |
||||
|
userId: user._id as string, |
||||
|
} |
||||
|
await publishEvent(Event.USER_INVITED_ACCEPTED, properties) |
||||
|
} |
||||
|
|
||||
|
// PASSWORD
|
||||
|
|
||||
|
export async function passwordForceReset(user: User) { |
||||
|
const properties: UserPasswordForceResetEvent = { |
||||
|
userId: user._id as string, |
||||
|
} |
||||
|
await publishEvent(Event.USER_PASSWORD_FORCE_RESET, properties) |
||||
|
} |
||||
|
|
||||
|
export async function passwordUpdated(user: User) { |
||||
|
const properties: UserPasswordUpdatedEvent = { |
||||
|
userId: user._id as string, |
||||
|
} |
||||
|
await publishEvent(Event.USER_PASSWORD_UPDATED, properties) |
||||
|
} |
||||
|
|
||||
|
export async function passwordResetRequested(user: User) { |
||||
|
const properties: UserPasswordResetRequestedEvent = { |
||||
|
userId: user._id as string, |
||||
|
} |
||||
|
await publishEvent(Event.USER_PASSWORD_RESET_REQUESTED, properties) |
||||
|
} |
||||
|
|
||||
|
export async function passwordReset(user: User) { |
||||
|
const properties: UserPasswordResetEvent = { |
||||
|
userId: user._id as string, |
||||
|
} |
||||
|
await publishEvent(Event.USER_PASSWORD_RESET, properties) |
||||
|
} |
||||
@ -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) |
||||
|
} |
||||
@ -0,0 +1,97 @@ |
|||||
|
import { publishEvent } from "../events" |
||||
|
import { |
||||
|
Event, |
||||
|
ViewCalculationCreatedEvent, |
||||
|
ViewCalculationDeletedEvent, |
||||
|
ViewCalculationUpdatedEvent, |
||||
|
ViewCreatedEvent, |
||||
|
ViewDeletedEvent, |
||||
|
ViewExportedEvent, |
||||
|
ViewFilterCreatedEvent, |
||||
|
ViewFilterDeletedEvent, |
||||
|
ViewFilterUpdatedEvent, |
||||
|
ViewUpdatedEvent, |
||||
|
View, |
||||
|
ViewCalculation, |
||||
|
Table, |
||||
|
TableExportFormat, |
||||
|
} from "@budibase/types" |
||||
|
|
||||
|
/* eslint-disable */ |
||||
|
|
||||
|
export async function created(view: View, timestamp?: string | number) { |
||||
|
const properties: ViewCreatedEvent = { |
||||
|
tableId: view.tableId, |
||||
|
} |
||||
|
await publishEvent(Event.VIEW_CREATED, properties, timestamp) |
||||
|
} |
||||
|
|
||||
|
export async function updated(view: View) { |
||||
|
const properties: ViewUpdatedEvent = { |
||||
|
tableId: view.tableId, |
||||
|
} |
||||
|
await publishEvent(Event.VIEW_UPDATED, properties) |
||||
|
} |
||||
|
|
||||
|
export async function deleted(view: View) { |
||||
|
const properties: ViewDeletedEvent = { |
||||
|
tableId: view.tableId, |
||||
|
} |
||||
|
await publishEvent(Event.VIEW_DELETED, properties) |
||||
|
} |
||||
|
|
||||
|
export async function exported(table: Table, format: TableExportFormat) { |
||||
|
const properties: ViewExportedEvent = { |
||||
|
tableId: table._id as string, |
||||
|
format, |
||||
|
} |
||||
|
await publishEvent(Event.VIEW_EXPORTED, properties) |
||||
|
} |
||||
|
|
||||
|
export async function filterCreated(view: View, timestamp?: string | number) { |
||||
|
const properties: ViewFilterCreatedEvent = { |
||||
|
tableId: view.tableId, |
||||
|
} |
||||
|
await publishEvent(Event.VIEW_FILTER_CREATED, properties, timestamp) |
||||
|
} |
||||
|
|
||||
|
export async function filterUpdated(view: View) { |
||||
|
const properties: ViewFilterUpdatedEvent = { |
||||
|
tableId: view.tableId, |
||||
|
} |
||||
|
await publishEvent(Event.VIEW_FILTER_UPDATED, properties) |
||||
|
} |
||||
|
|
||||
|
export async function filterDeleted(view: View) { |
||||
|
const properties: ViewFilterDeletedEvent = { |
||||
|
tableId: view.tableId, |
||||
|
} |
||||
|
await publishEvent(Event.VIEW_FILTER_DELETED, properties) |
||||
|
} |
||||
|
|
||||
|
export async function calculationCreated( |
||||
|
view: View, |
||||
|
timestamp?: string | number |
||||
|
) { |
||||
|
const properties: ViewCalculationCreatedEvent = { |
||||
|
tableId: view.tableId, |
||||
|
calculation: view.calculation as ViewCalculation, |
||||
|
} |
||||
|
await publishEvent(Event.VIEW_CALCULATION_CREATED, properties, timestamp) |
||||
|
} |
||||
|
|
||||
|
export async function calculationUpdated(view: View) { |
||||
|
const properties: ViewCalculationUpdatedEvent = { |
||||
|
tableId: view.tableId, |
||||
|
calculation: view.calculation as ViewCalculation, |
||||
|
} |
||||
|
await publishEvent(Event.VIEW_CALCULATION_UPDATED, properties) |
||||
|
} |
||||
|
|
||||
|
export async function calculationDeleted(existingView: View) { |
||||
|
const properties: ViewCalculationDeletedEvent = { |
||||
|
tableId: existingView.tableId, |
||||
|
calculation: existingView.calculation as ViewCalculation, |
||||
|
} |
||||
|
await publishEvent(Event.VIEW_CALCULATION_DELETED, properties) |
||||
|
} |
||||
@ -1,24 +0,0 @@ |
|||||
const db = require("./db") |
|
||||
|
|
||||
module.exports = { |
|
||||
init(opts = {}) { |
|
||||
db.init(opts.db) |
|
||||
}, |
|
||||
// some default exports from the library, however these ideally shouldn't
|
|
||||
// be used, instead the syntax require("@budibase/backend-core/db") should be used
|
|
||||
StaticDatabases: require("./db/utils").StaticDatabases, |
|
||||
db: require("../db"), |
|
||||
redis: require("../redis"), |
|
||||
objectStore: require("../objectStore"), |
|
||||
utils: require("../utils"), |
|
||||
cache: require("../cache"), |
|
||||
auth: require("../auth"), |
|
||||
constants: require("../constants"), |
|
||||
migrations: require("../migrations"), |
|
||||
errors: require("./errors"), |
|
||||
env: require("./environment"), |
|
||||
accounts: require("./cloud/accounts"), |
|
||||
tenancy: require("./tenancy"), |
|
||||
context: require("../context"), |
|
||||
featureFlags: require("./featureFlags"), |
|
||||
} |
|
||||
@ -0,0 +1,55 @@ |
|||||
|
import errors from "./errors" |
||||
|
const errorClasses = errors.errors |
||||
|
import * as events from "./events" |
||||
|
import * as migrations from "./migrations" |
||||
|
import * as users from "./users" |
||||
|
import * as accounts from "./cloud/accounts" |
||||
|
import * as installation from "./installation" |
||||
|
import env from "./environment" |
||||
|
import tenancy from "./tenancy" |
||||
|
import featureFlags from "./featureFlags" |
||||
|
import sessions from "./security/sessions" |
||||
|
import deprovisioning from "./context/deprovision" |
||||
|
import auth from "./auth" |
||||
|
import constants from "./constants" |
||||
|
import * as dbConstants from "./db/constants" |
||||
|
|
||||
|
// mimic the outer package exports
|
||||
|
import * as db from "./pkg/db" |
||||
|
import * as objectStore from "./pkg/objectStore" |
||||
|
import * as utils from "./pkg/utils" |
||||
|
import redis from "./pkg/redis" |
||||
|
import cache from "./pkg/cache" |
||||
|
import context from "./pkg/context" |
||||
|
|
||||
|
const init = (opts: any = {}) => { |
||||
|
db.init(opts.db) |
||||
|
} |
||||
|
|
||||
|
const core = { |
||||
|
init, |
||||
|
db, |
||||
|
...dbConstants, |
||||
|
redis, |
||||
|
objectStore, |
||||
|
utils, |
||||
|
users, |
||||
|
cache, |
||||
|
auth, |
||||
|
constants, |
||||
|
...constants, |
||||
|
migrations, |
||||
|
env, |
||||
|
accounts, |
||||
|
tenancy, |
||||
|
context, |
||||
|
featureFlags, |
||||
|
events, |
||||
|
sessions, |
||||
|
deprovisioning, |
||||
|
installation, |
||||
|
errors, |
||||
|
...errorClasses, |
||||
|
} |
||||
|
|
||||
|
export = core |
||||
@ -0,0 +1,96 @@ |
|||||
|
import * as hashing from "./hashing" |
||||
|
import * as events from "./events" |
||||
|
import { StaticDatabases } from "./db/constants" |
||||
|
import { doWithDB } from "./db" |
||||
|
import { Installation, IdentityType } from "@budibase/types" |
||||
|
import * as context from "./context" |
||||
|
import semver from "semver" |
||||
|
import { bustCache, withCache, TTL, CacheKeys } from "./cache/generic" |
||||
|
|
||||
|
const pkg = require("../package.json") |
||||
|
|
||||
|
export const getInstall = async (): Promise<Installation> => { |
||||
|
return withCache(CacheKeys.INSTALLATION, TTL.ONE_DAY, getInstallFromDB, { |
||||
|
useTenancy: false, |
||||
|
}) |
||||
|
} |
||||
|
|
||||
|
const getInstallFromDB = async (): Promise<Installation> => { |
||||
|
return doWithDB( |
||||
|
StaticDatabases.PLATFORM_INFO.name, |
||||
|
async (platformDb: any) => { |
||||
|
let install: Installation |
||||
|
try { |
||||
|
install = await platformDb.get( |
||||
|
StaticDatabases.PLATFORM_INFO.docs.install |
||||
|
) |
||||
|
} catch (e: any) { |
||||
|
if (e.status === 404) { |
||||
|
install = { |
||||
|
_id: StaticDatabases.PLATFORM_INFO.docs.install, |
||||
|
installId: hashing.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<boolean> => { |
||||
|
try { |
||||
|
await doWithDB( |
||||
|
StaticDatabases.PLATFORM_INFO.name, |
||||
|
async (platformDb: any) => { |
||||
|
const install = await getInstall() |
||||
|
install.version = version |
||||
|
await platformDb.put(install) |
||||
|
await bustCache(CacheKeys.INSTALLATION) |
||||
|
} |
||||
|
) |
||||
|
} 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<void> => { |
||||
|
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.doInIdentityContext( |
||||
|
{ |
||||
|
_id: install.installId, |
||||
|
type: IdentityType.INSTALLATION, |
||||
|
}, |
||||
|
async () => { |
||||
|
if (isUpgrade) { |
||||
|
await events.version.upgraded(currentVersion, newVersion) |
||||
|
} else if (isDowngrade) { |
||||
|
await events.version.downgraded(currentVersion, newVersion) |
||||
|
} |
||||
|
} |
||||
|
) |
||||
|
await events.identification.identifyInstallationGroup(install.installId) |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -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,117 +0,0 @@ |
|||||
const { DEFAULT_TENANT_ID } = require("../constants") |
|
||||
const { doWithDB } = require("../db") |
|
||||
const { DocumentTypes } = require("../db/constants") |
|
||||
const { getAllApps } = require("../db/utils") |
|
||||
const environment = require("../environment") |
|
||||
const { |
|
||||
doInTenant, |
|
||||
getTenantIds, |
|
||||
getGlobalDBName, |
|
||||
getTenantId, |
|
||||
} = require("../tenancy") |
|
||||
|
|
||||
exports.MIGRATION_TYPES = { |
|
||||
GLOBAL: "global", // run once, 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
|
|
||||
} |
|
||||
|
|
||||
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 } |
|
||||
} |
|
||||
console.error(err) |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
const runMigration = async (migration, options = {}) => { |
|
||||
const tenantId = getTenantId() |
|
||||
const migrationType = migration.type |
|
||||
const migrationName = migration.name |
|
||||
|
|
||||
// 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 { |
|
||||
throw new Error( |
|
||||
`[Tenant: ${tenantId}] Unrecognised migration type [${migrationType}]` |
|
||||
) |
|
||||
} |
|
||||
|
|
||||
// run the migration against each db
|
|
||||
for (const dbName of dbNames) { |
|
||||
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) |
|
||||
) { |
|
||||
console.log( |
|
||||
`[Tenant: ${tenantId}] [Migration: ${migrationName}] [DB: ${dbName}] Forcing` |
|
||||
) |
|
||||
} else { |
|
||||
// the migration has already been performed
|
|
||||
return |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
console.log( |
|
||||
`[Tenant: ${tenantId}] [Migration: ${migrationName}] [DB: ${dbName}] Running` |
|
||||
) |
|
||||
// run the migration with tenant context
|
|
||||
await migration.fn(db) |
|
||||
console.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 = {}) => { |
|
||||
console.log("Running migrations") |
|
||||
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] |
|
||||
} |
|
||||
|
|
||||
// for all tenants
|
|
||||
for (const tenantId of tenantIds) { |
|
||||
// for all migrations
|
|
||||
for (const migration of migrations) { |
|
||||
// run the migration
|
|
||||
await doInTenant(tenantId, () => 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") |
||||
|
} |
||||
@ -0,0 +1,11 @@ |
|||||
|
// Mimic the outer package export for usage in index.ts
|
||||
|
// The outer exports can't be used as they now reference dist directly
|
||||
|
import * as generic from "../cache/generic" |
||||
|
import * as user from "../cache/user" |
||||
|
import * as app from "../cache/appMetadata" |
||||
|
|
||||
|
export = { |
||||
|
app, |
||||
|
user, |
||||
|
...generic, |
||||
|
} |
||||
@ -0,0 +1,24 @@ |
|||||
|
// Mimic the outer package export for usage in index.ts
|
||||
|
// The outer exports can't be used as they now reference dist directly
|
||||
|
import { |
||||
|
getAppDB, |
||||
|
getDevAppDB, |
||||
|
getProdAppDB, |
||||
|
getAppId, |
||||
|
updateAppId, |
||||
|
doInAppContext, |
||||
|
doInTenant, |
||||
|
} from "../context" |
||||
|
|
||||
|
import * as identity from "../context/identity" |
||||
|
|
||||
|
export = { |
||||
|
getAppDB, |
||||
|
getDevAppDB, |
||||
|
getProdAppDB, |
||||
|
getAppId, |
||||
|
updateAppId, |
||||
|
doInAppContext, |
||||
|
doInTenant, |
||||
|
identity, |
||||
|
} |
||||
@ -0,0 +1,7 @@ |
|||||
|
// Mimic the outer package export for usage in index.ts
|
||||
|
// The outer exports can't be used as they now reference dist directly
|
||||
|
export * from "../db" |
||||
|
export * from "../db/utils" |
||||
|
export * from "../db/views" |
||||
|
export * from "../db/pouch" |
||||
|
export * from "../db/constants" |
||||
@ -0,0 +1,4 @@ |
|||||
|
// Mimic the outer package export for usage in index.ts
|
||||
|
// The outer exports can't be used as they now reference dist directly
|
||||
|
export * from "../objectStore" |
||||
|
export * from "../objectStore/utils" |
||||
@ -0,0 +1,11 @@ |
|||||
|
// Mimic the outer package export for usage in index.ts
|
||||
|
// The outer exports can't be used as they now reference dist directly
|
||||
|
import Client from "../redis" |
||||
|
import utils from "../redis/utils" |
||||
|
import clients from "../redis/authRedis" |
||||
|
|
||||
|
export = { |
||||
|
Client, |
||||
|
utils, |
||||
|
clients, |
||||
|
} |
||||
@ -0,0 +1,4 @@ |
|||||
|
// Mimic the outer package export for usage in index.ts
|
||||
|
// The outer exports can't be used as they now reference dist directly
|
||||
|
export * from "../utils" |
||||
|
export * from "../hashing" |
||||
@ -0,0 +1,21 @@ |
|||||
|
import Redlock from "redlock" |
||||
|
|
||||
|
export const getRedlock = (redisClient: any, opts = { retryCount: 10 }) => { |
||||
|
return new Redlock([redisClient], { |
||||
|
// the expected clock drift; for more details
|
||||
|
// see http://redis.io/topics/distlock
|
||||
|
driftFactor: 0.01, // multiplied by lock ttl to determine drift time
|
||||
|
|
||||
|
// the max number of times Redlock will attempt
|
||||
|
// to lock a resource before erroring
|
||||
|
retryCount: opts.retryCount, |
||||
|
|
||||
|
// the time in ms between attempts
|
||||
|
retryDelay: 200, // time in ms
|
||||
|
|
||||
|
// the max time in ms randomly added to retries
|
||||
|
// to improve performance under high contention
|
||||
|
// see https://www.awsarchitectureblog.com/2015/03/backoff.html
|
||||
|
retryJitter: 200, // time in ms
|
||||
|
}) |
||||
|
} |
||||
@ -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 |
||||
@ -0,0 +1,17 @@ |
|||||
|
require("../../tests/utilities/TestConfiguration") |
||||
|
const { structures } = require("../../tests/utilities") |
||||
|
const utils = require("../utils") |
||||
|
const events = require("../events") |
||||
|
const { doInTenant, DEFAULT_TENANT_ID }= require("../context") |
||||
|
|
||||
|
describe("utils", () => { |
||||
|
describe("platformLogout", () => { |
||||
|
it("should call platform logout", async () => { |
||||
|
await doInTenant(DEFAULT_TENANT_ID, async () => { |
||||
|
const ctx = structures.koa.newContext() |
||||
|
await utils.platformLogout({ ctx, userId: "test" }) |
||||
|
expect(events.auth.logout).toBeCalledTimes(1) |
||||
|
}) |
||||
|
}) |
||||
|
}) |
||||
|
}) |
||||
@ -0,0 +1,21 @@ |
|||||
|
const { ViewNames } = require("./db/utils") |
||||
|
const { queryGlobalView } = require("./db/views") |
||||
|
|
||||
|
/** |
||||
|
* Given an email address this will use a view to search through |
||||
|
* all the users to find one with this email address. |
||||
|
* @param {string} email the email to lookup the user by. |
||||
|
* @return {Promise<object|null>} |
||||
|
*/ |
||||
|
exports.getGlobalUserByEmail = async email => { |
||||
|
if (email == null) { |
||||
|
throw "Must supply an email address to view" |
||||
|
} |
||||
|
|
||||
|
const response = await queryGlobalView(ViewNames.USER_BY_EMAIL, { |
||||
|
key: email.toLowerCase(), |
||||
|
include_docs: true, |
||||
|
}) |
||||
|
|
||||
|
return response |
||||
|
} |
||||
@ -0,0 +1 @@ |
|||||
|
export * from "./utilities" |
||||
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue