Event identification

This commit is contained in:
Rory Powell 2022-05-23 22:14:44 +01:00
parent 6ea7912a73
commit 22aa226ca9
80 changed files with 724 additions and 408 deletions

View File

@ -46,7 +46,7 @@
},
"devDependencies": {
"@shopify/jest-koa-mocks": "^3.1.5",
"@budibase/types": "^1.0.126-alpha.0",
"@budibase/types": "^1.0.167-alpha.8",
"@types/jest": "^27.4.1",
"@types/koa": "^2.13.3",
"@types/node": "^15.12.4",

View File

@ -16,6 +16,7 @@ const ContextKeys = {
TENANT_ID: "tenantId",
GLOBAL_DB: "globalDb",
APP_ID: "appId",
USER: "user",
// whatever the request app DB was
CURRENT_DB: "currentDb",
// get the prod app DB from the request
@ -173,6 +174,21 @@ exports.doInAppContext = (appId, task) => {
}
}
exports.doInUserContext = (user, task) => {
return cls.run(async () => {
cls.setOnContext(ContextKeys.USER, user)
return task()
})
}
exports.getUser = () => {
try {
return cls.getFromContext(ContextKeys.USER)
} catch (e) {
// do nothing - user is not in context
}
}
exports.updateTenantId = tenantId => {
cls.setOnContext(ContextKeys.TENANT_ID, tenantId)
exports.setGlobalDB(tenantId)

View File

@ -10,6 +10,7 @@ import { getAppMetadata } from "../cache/appMetadata"
import { checkSlashesInUrl } from "../helpers"
import { isDevApp, isDevAppID } from "./conversions"
import { APP_PREFIX } from "./constants"
import * as events from "../events"
const UNICODE_MAX = "\ufff0"
@ -380,15 +381,19 @@ export const getScopedFullConfig = async function (
)[0]
// custom logic for settings doc
// always provide the platform URL
if (type === Configs.SETTINGS) {
if (scopedConfig && scopedConfig.doc) {
// overrides affected by environment variables
scopedConfig.doc.config.platformUrl = await getPlatformUrl()
scopedConfig.doc.config.analyticsEnabled =
await events.analytics.enabled()
} else {
// defaults
scopedConfig = {
doc: {
config: {
platformUrl: await getPlatformUrl(),
analyticsEnabled: await events.analytics.enabled(),
},
},
}

View File

@ -16,7 +16,7 @@ if (!LOADED && isDev() && !isTest()) {
LOADED = true
}
export = {
const env: any = {
isTest,
isDev,
JWT_SECRET: process.env.JWT_SECRET,
@ -59,9 +59,11 @@ export = {
}
// clean up any environment variable edge cases
for (let [key, value] of Object.entries(module.exports)) {
for (let [key, value] of Object.entries(env)) {
// handle the edge case of "0" to disable an environment variable
if (value === "0") {
module.exports[key] = 0
env[key] = 0
}
}
export = env

View File

@ -0,0 +1,45 @@
import env from "../environment"
import * as tenancy from "../tenancy"
import * as dbUtils from "../db/utils"
import { Configs } from "../constants"
export const enabled = async () => {
// cloud - always use the environment variable
if (!env.SELF_HOSTED) {
return !!env.ENABLE_ANALYTICS
}
// self host - prefer the settings doc
// check for explicit true/false values to support
// backwards compatibility where setting may not exist
const settings = await getSettingsDoc()
if (settings?.config?.analyticsEnabled === false) {
return false
} else if (settings?.config?.analyticsEnabled === true) {
return true
}
// 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
}

View File

@ -1,9 +1,9 @@
import { Event } from "@budibase/types"
import { processors } from "./processors"
import * as identification from "./identification"
export const publishEvent = (event: Event, properties: any) => {
// in future this should use async events
// via a queue. For now we can use sync as
// this is non-blocking
processors.processEvent(event, properties)
export const publishEvent = async (event: Event, properties: any) => {
// in future this should use async events via a distributed queue.
const identity = identification.getCurrentIdentity()
await processors.processEvent(event, identity, properties)
}

View File

@ -0,0 +1,93 @@
import {
isCloudAccount,
isSSOAccount,
} from "./../../../types/src/documents/account/account"
import * as context from "../context"
import env from "../environment"
import {
Hosting,
User,
SessionUser,
Identity,
IdentityType,
Account,
AccountIdentity,
BudibaseIdentity,
} from "@budibase/types"
import { analyticsProcessor } from "./processors"
export const getCurrentIdentity = (): Identity => {
const user: SessionUser | undefined = context.getUser()
const tenantId = context.getTenantId()
let id: string
if (user) {
id = user._id
} else if (env.SELF_HOSTED) {
id = "installationId" // TODO
} else {
id = context.getTenantId()
}
return {
id,
tenantId,
}
}
export const identifyUser = async (user: User) => {
const id = user._id as string
const tenantId = user.tenantId
const hosting = env.SELF_HOSTED ? Hosting.SELF : Hosting.CLOUD
const type = IdentityType.USER
let builder = user.builder?.global
let admin = user.admin?.global
let authType = user.providerType ? user.providerType : "password"
const identity: BudibaseIdentity = {
id,
tenantId,
hosting,
type,
builder,
admin,
authType,
}
await identify(identity)
}
export const identifyAccount = async (account: Account) => {
let id = account.accountId
const tenantId = account.tenantId
const hosting = account.hosting
let type = IdentityType.ACCOUNT
let authType = isSSOAccount(account)
? (account.providerType as string)
: "password"
if (isCloudAccount(account)) {
if (account.budibaseUserId) {
// use the budibase user as the id if set
id = account.budibaseUserId
type = IdentityType.USER
}
}
const identity: AccountIdentity = {
id,
tenantId,
hosting,
type,
authType,
verified: account.verified,
profession: account.profession,
companySize: account.size,
}
await identify(identity)
}
const identify = async (identity: Identity) => {
await analyticsProcessor.identify(identity)
}

View File

@ -1,10 +1,7 @@
import { processors } from "./processors"
export * from "./publishers"
export * as analytics from "./analytics"
export const shutdown = () => {
processors.shutdown()
}
export const analyticsEnabled = () => {
return true
}

View File

@ -0,0 +1,43 @@
import { Event, Identity } from "@budibase/types"
import { EventProcessor } from "./types"
import env from "../../environment"
import * as analytics from "../analytics"
import PosthogProcessor from "./PosthogProcessor"
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
): Promise<void> {
if (!(await analytics.enabled())) {
return
}
if (this.posthog) {
this.posthog.processEvent(event, identity, properties)
}
}
async identify(identity: Identity) {
if (!(await analytics.enabled())) {
return
}
if (this.posthog) {
this.posthog.identify(identity)
}
}
shutdown() {
if (this.posthog) {
this.posthog.shutdown()
}
}
}

View File

@ -1,12 +1,15 @@
import { Event } from "@budibase/types"
import { getTenantId } from "../../context"
import { Event, Identity } from "@budibase/types"
import { EventProcessor } from "./types"
export default class LoggingProcessor implements EventProcessor {
processEvent(event: Event, properties: any): void {
const tenantId = getTenantId()
const userId = getTenantId() // TODO
console.log(`[audit] [tenant=${tenantId}] [user=${userId}] ${event}`)
async processEvent(
event: Event,
identity: Identity,
properties: any
): Promise<void> {
console.log(
`[audit] [tenant=${identity.tenantId}] [identity=${identity.id}] ${event}`
)
}
shutdown(): void {

View File

@ -1,7 +1,6 @@
import PostHog from "posthog-node"
import { Event } from "@budibase/types"
import { Event, Identity } from "@budibase/types"
import { EventProcessor } from "./types"
import { getTenantId } from "../../context"
export default class PosthogProcessor implements EventProcessor {
posthog: PostHog
@ -13,13 +12,16 @@ export default class PosthogProcessor implements EventProcessor {
this.posthog = new PostHog(token)
}
processEvent(event: Event, properties: any): void {
const userId = getTenantId() // TODO
this.posthog.capture({ distinctId: userId, event, properties })
async processEvent(
event: Event,
identity: Identity,
properties: any
): Promise<void> {
this.posthog.capture({ distinctId: identity.id, event, properties })
}
identify(distinctId: string, properties: any) {
this.posthog.identify({ distinctId, properties })
async identify(identity: Identity) {
this.posthog.identify({ distinctId: identity.id, properties: identity })
}
shutdown() {

View File

@ -1,22 +1,21 @@
import { Event } from "@budibase/types"
import { Event, Identity } from "@budibase/types"
import { EventProcessor } from "./types"
import env from "../../environment"
import LoggingProcessor from "./LoggingProcessor"
import PosthogProcessor from "./PosthogProcessor"
export default class Processor implements EventProcessor {
initialised: boolean = false
processors: EventProcessor[] = []
constructor() {
if (env.ENABLE_ANALYTICS && env.POSTHOG_TOKEN) {
this.processors.push(new PosthogProcessor(env.POSTHOG_TOKEN))
}
this.processors.push(new LoggingProcessor())
constructor(processors: EventProcessor[]) {
this.processors = processors
}
processEvent(event: Event, properties: any): void {
async processEvent(
event: Event,
identity: Identity,
properties: any
): Promise<void> {
for (const eventProcessor of this.processors) {
eventProcessor.processEvent(event, properties)
await eventProcessor.processEvent(event, identity, properties)
}
}
@ -25,39 +24,4 @@ export default class Processor implements EventProcessor {
eventProcessor.shutdown()
}
}
// Identity todo
// export const identify(type: IdentityType, id: string, hosting?: Hosting) {
// const tenantId = getTenantId()
// if (!hosting) {
// hosting = env.SELF_HOSTED ? Hosting.SELF : Hosting.CLOUD
// }
// const properties = {
// type,
// hosting,
// tenantId,
// }
// this.posthog!.identify(id, properties)
// }
// identifyUser(userId: string) {
// this.identify(IdentityType.USER, userId)
// }
// identifyTenant() {
// let distinctId
// if (env.SELF_HOSTED) {
// distinctId = getTenantId() // TODO: Get installation ID
// } else {
// distinctId = getTenantId()
// }
// this.identify(IdentityType.TENANT, distinctId)
// }
// identifyAccount(account: Account) {
// const distinctId = account.accountId
// const hosting = account.hosting
// this.identify(IdentityType.ACCOUNT, distinctId, hosting)
// }
}

View File

@ -1,2 +1,8 @@
import AnalyticsProcessor from "./AnalyticsProcessor"
import LoggingProcessor from "./LoggingProcessor"
import Processors from "./Processors"
export const processors = new Processors()
export const analyticsProcessor = new AnalyticsProcessor()
const loggingProcessor = new LoggingProcessor()
export const processors = new Processors([analyticsProcessor, loggingProcessor])

View File

@ -1,4 +1,4 @@
import { Event } from "@budibase/types"
import { Event, Identity } from "@budibase/types"
export enum EventProcessorType {
POSTHOG = "posthog",
@ -6,6 +6,6 @@ export enum EventProcessorType {
}
export interface EventProcessor {
processEvent(event: Event, properties: any): void
processEvent(event: Event, identity: Identity, properties: any): Promise<void>
shutdown(): void
}

View File

@ -1,17 +1,17 @@
import { publishEvent } from "../events"
import { Event, Account } from "@budibase/types"
export function created(account: Account) {
export async function created(account: Account) {
const properties = {}
publishEvent(Event.ACCOUNT_CREATED, properties)
await publishEvent(Event.ACCOUNT_CREATED, properties)
}
export function deleted(account: Account) {
export async function deleted(account: Account) {
const properties = {}
publishEvent(Event.ACCOUNT_DELETED, properties)
await publishEvent(Event.ACCOUNT_DELETED, properties)
}
export function verified(account: Account) {
export async function verified(account: Account) {
const properties = {}
publishEvent(Event.ACCOUNT_VERIFIED, properties)
await publishEvent(Event.ACCOUNT_VERIFIED, properties)
}

View File

@ -20,54 +20,54 @@ export const created = async (app: App) => {
await publishEvent(Event.APP_CREATED, properties)
}
export function updated(app: App) {
export async function updated(app: App) {
const properties: AppUpdatedEvent = {}
publishEvent(Event.APP_UPDATED, properties)
await publishEvent(Event.APP_UPDATED, properties)
}
export function deleted(app: App) {
export async function deleted(app: App) {
const properties: AppDeletedEvent = {}
publishEvent(Event.APP_DELETED, properties)
await publishEvent(Event.APP_DELETED, properties)
}
export function published(app: App) {
export async function published(app: App) {
const properties: AppPublishedEvent = {}
publishEvent(Event.APP_PUBLISHED, properties)
await publishEvent(Event.APP_PUBLISHED, properties)
}
export function unpublished(app: App) {
export async function unpublished(app: App) {
const properties: AppUnpublishedEvent = {}
publishEvent(Event.APP_UNPUBLISHED, properties)
await publishEvent(Event.APP_UNPUBLISHED, properties)
}
export function fileImported(app: App) {
export async function fileImported(app: App) {
const properties: AppFileImportedEvent = {}
publishEvent(Event.APP_FILE_IMPORTED, properties)
await publishEvent(Event.APP_FILE_IMPORTED, properties)
}
export function templateImported(templateKey: string) {
export async function templateImported(templateKey: string) {
const properties: AppTemplateImportedEvent = {
templateKey,
}
publishEvent(Event.APP_TEMPLATE_IMPORTED, properties)
await publishEvent(Event.APP_TEMPLATE_IMPORTED, properties)
}
export function versionUpdated(app: App) {
export async function versionUpdated(app: App) {
const properties: AppVersionUpdatedEvent = {}
publishEvent(Event.APP_VERSION_UPDATED, properties)
await publishEvent(Event.APP_VERSION_UPDATED, properties)
}
export function versionReverted(app: App) {
export async function versionReverted(app: App) {
const properties: AppVersionRevertedEvent = {}
publishEvent(Event.APP_VERSION_REVERTED, properties)
await publishEvent(Event.APP_VERSION_REVERTED, properties)
}
export function reverted(app: App) {
export async function reverted(app: App) {
const properties: AppRevertedEvent = {}
publishEvent(Event.APP_REVERTED, properties)
await publishEvent(Event.APP_REVERTED, properties)
}
export function exported(app: App) {
export async function exported(app: App) {
const properties: AppExportedEvent = {}
publishEvent(Event.APP_EXPORTED, properties)
await publishEvent(Event.APP_EXPORTED, properties)
}

View File

@ -11,42 +11,42 @@ import {
SSOUpdatedEvent,
} from "@budibase/types"
export function login(source: LoginSource) {
export async function login(source: LoginSource) {
const properties: LoginEvent = {
source,
}
publishEvent(Event.AUTH_LOGIN, properties)
await publishEvent(Event.AUTH_LOGIN, properties)
}
export function logout() {
export async function logout() {
const properties: LogoutEvent = {}
publishEvent(Event.AUTH_LOGOUT, properties)
await publishEvent(Event.AUTH_LOGOUT, properties)
}
export function SSOCreated(type: SSOType) {
export async function SSOCreated(type: SSOType) {
const properties: SSOCreatedEvent = {
type,
}
publishEvent(Event.AUTH_SSO_CREATED, properties)
await publishEvent(Event.AUTH_SSO_CREATED, properties)
}
export function SSOUpdated(type: SSOType) {
export async function SSOUpdated(type: SSOType) {
const properties: SSOUpdatedEvent = {
type,
}
publishEvent(Event.AUTH_SSO_UPDATED, properties)
await publishEvent(Event.AUTH_SSO_UPDATED, properties)
}
export function SSOActivated(type: SSOType) {
export async function SSOActivated(type: SSOType) {
const properties: SSOActivatedEvent = {
type,
}
publishEvent(Event.AUTH_SSO_ACTIVATED, properties)
await publishEvent(Event.AUTH_SSO_ACTIVATED, properties)
}
export function SSODeactivated(type: SSOType) {
export async function SSODeactivated(type: SSOType) {
const properties: SSODeactivatedEvent = {
type,
}
publishEvent(Event.AUTH_SSO_DEACTIVATED, properties)
await publishEvent(Event.AUTH_SSO_DEACTIVATED, properties)
}

View File

@ -12,41 +12,41 @@ import {
AutomationTriggerUpdatedEvent,
} from "@budibase/types"
export function created(automation: Automation) {
export async function created(automation: Automation) {
const properties: AutomationCreatedEvent = {}
publishEvent(Event.AUTOMATION_CREATED, properties)
await publishEvent(Event.AUTOMATION_CREATED, properties)
}
export function deleted(automation: Automation) {
export async function deleted(automation: Automation) {
const properties: AutomationDeletedEvent = {}
publishEvent(Event.AUTOMATION_DELETED, properties)
await publishEvent(Event.AUTOMATION_DELETED, properties)
}
export function tested(automation: Automation) {
export async function tested(automation: Automation) {
const properties: AutomationTestedEvent = {}
publishEvent(Event.AUTOMATION_TESTED, properties)
await publishEvent(Event.AUTOMATION_TESTED, properties)
}
// TODO
// exports.run = () => {
// const properties = {}
// events.processEvent(Events.AUTOMATION_RUN, properties)
// }
export function stepCreated(automation: Automation, step: AutomationStep) {
export async function stepCreated(
automation: Automation,
step: AutomationStep
) {
const properties: AutomationStepCreatedEvent = {}
publishEvent(Event.AUTOMATION_STEP_CREATED, properties)
await publishEvent(Event.AUTOMATION_STEP_CREATED, properties)
}
export function stepDeleted(automation: Automation, step: AutomationStep) {
export async function stepDeleted(
automation: Automation,
step: AutomationStep
) {
const properties: AutomationStepDeletedEvent = {}
publishEvent(Event.AUTOMATION_STEP_DELETED, properties)
await publishEvent(Event.AUTOMATION_STEP_DELETED, properties)
}
export function triggerUpdated(
export async function triggerUpdated(
automation: Automation,
trigger: AutomationTrigger
) {
const properties: AutomationTriggerUpdatedEvent = {}
publishEvent(Event.AUTOMATION_TRIGGER_UPDATED, properties)
await publishEvent(Event.AUTOMATION_TRIGGER_UPDATED, properties)
}

View File

@ -7,17 +7,17 @@ import {
DatasourceDeletedEvent,
} from "@budibase/types"
export function created(datasource: Datasource) {
export async function created(datasource: Datasource) {
const properties: DatasourceCreatedEvent = {}
publishEvent(Event.DATASOURCE_CREATED, properties)
await publishEvent(Event.DATASOURCE_CREATED, properties)
}
export function updated(datasource: Datasource) {
export async function updated(datasource: Datasource) {
const properties: DatasourceUpdatedEvent = {}
publishEvent(Event.DATASOURCE_UPDATED, properties)
await publishEvent(Event.DATASOURCE_UPDATED, properties)
}
export function deleted(datasource: Datasource) {
export async function deleted(datasource: Datasource) {
const properties: DatasourceDeletedEvent = {}
publishEvent(Event.DATASOURCE_DELETED, properties)
await publishEvent(Event.DATASOURCE_DELETED, properties)
}

View File

@ -6,12 +6,12 @@ import {
SMTPUpdatedEvent,
} from "@budibase/types"
export function SMTPCreated(config: SMTPConfig) {
export async function SMTPCreated(config: SMTPConfig) {
const properties: SMTPCreatedEvent = {}
publishEvent(Event.EMAIL_SMTP_CREATED, properties)
await publishEvent(Event.EMAIL_SMTP_CREATED, properties)
}
export function SMTPUpdated(config: SMTPConfig) {
export async function SMTPUpdated(config: SMTPConfig) {
const properties: SMTPUpdatedEvent = {}
publishEvent(Event.EMAIL_SMTP_UPDATED, properties)
await publishEvent(Event.EMAIL_SMTP_UPDATED, properties)
}

View File

@ -6,12 +6,12 @@ import {
LayoutDeletedEvent,
} from "@budibase/types"
export function created(layout: Layout) {
export async function created(layout: Layout) {
const properties: LayoutCreatedEvent = {}
publishEvent(Event.LAYOUT_CREATED, properties)
await publishEvent(Event.LAYOUT_CREATED, properties)
}
export function deleted(layout: Layout) {
export async function deleted(layout: Layout) {
const properties: LayoutDeletedEvent = {}
publishEvent(Event.LAYOUT_DELETED, properties)
await publishEvent(Event.LAYOUT_DELETED, properties)
}

View File

@ -10,34 +10,34 @@ import {
} from "@budibase/types"
// TODO
export function updgraded(license: License) {
export async function updgraded(license: License) {
const properties: LicenseUpgradedEvent = {}
publishEvent(Event.LICENSE_UPGRADED, properties)
await publishEvent(Event.LICENSE_UPGRADED, properties)
}
// TODO
export function downgraded(license: License) {
export async function downgraded(license: License) {
const properties: LicenseDowngradedEvent = {}
publishEvent(Event.LICENSE_DOWNGRADED, properties)
await publishEvent(Event.LICENSE_DOWNGRADED, properties)
}
// TODO
export function updated(license: License) {
export async function updated(license: License) {
const properties: LicenseUpdatedEvent = {}
publishEvent(Event.LICENSE_UPDATED, properties)
await publishEvent(Event.LICENSE_UPDATED, properties)
}
// TODO
export function activated(license: License) {
export async function activated(license: License) {
const properties: LicenseActivatedEvent = {}
publishEvent(Event.LICENSE_ACTIVATED, properties)
await publishEvent(Event.LICENSE_ACTIVATED, properties)
}
// TODO
export function quotaExceeded(quotaName: string, value: number) {
export async function quotaExceeded(quotaName: string, value: number) {
const properties: LicenseQuotaExceededEvent = {
name: quotaName,
value,
}
publishEvent(Event.LICENSE_QUOTA_EXCEEDED, properties)
await publishEvent(Event.LICENSE_QUOTA_EXCEEDED, properties)
}

View File

@ -1,30 +1,35 @@
import { publishEvent } from "../events"
import { Event, VersionCheckedEvent } from "@budibase/types"
export function nameUpdated() {
export async function nameUpdated() {
const properties = {}
publishEvent(Event.ORG_NAME_UPDATED, properties)
await publishEvent(Event.ORG_NAME_UPDATED, properties)
}
export function logoUpdated() {
export async function logoUpdated() {
const properties = {}
publishEvent(Event.ORG_LOGO_UPDATED, properties)
await publishEvent(Event.ORG_LOGO_UPDATED, properties)
}
export function platformURLUpdated() {
export async function platformURLUpdated() {
const properties = {}
publishEvent(Event.ORG_PLATFORM_URL_UPDATED, properties)
await publishEvent(Event.ORG_PLATFORM_URL_UPDATED, properties)
}
export function versionChecked(version: number) {
export async function versionChecked(version: number) {
const properties: VersionCheckedEvent = {
version,
}
publishEvent(Event.UPDATE_VERSION_CHECKED, properties)
await publishEvent(Event.UPDATE_VERSION_CHECKED, properties)
}
// TODO
export function analyticsOptOut() {
export async function analyticsOptOut() {
const properties = {}
publishEvent(Event.ANALYTICS_OPT_OUT, properties)
await publishEvent(Event.ANALYTICS_OPT_OUT, properties)
}
export async function analyticsOptIn() {
const properties = {}
await publishEvent(Event.ANALYTICS_OPT_OUT, properties)
}

View File

@ -12,37 +12,31 @@ import {
/* eslint-disable */
export const created = (datasource: Datasource, query: Query) => {
export const created = async (datasource: Datasource, query: Query) => {
const properties: QueryCreatedEvent = {}
publishEvent(Event.QUERY_CREATED, properties)
await publishEvent(Event.QUERY_CREATED, properties)
}
export const updated = (datasource: Datasource, query: Query) => {
export const updated = async (datasource: Datasource, query: Query) => {
const properties: QueryUpdatedEvent = {}
publishEvent(Event.QUERY_UPDATED, properties)
await publishEvent(Event.QUERY_UPDATED, properties)
}
export const deleted = (datasource: Datasource, query: Query) => {
export const deleted = async (datasource: Datasource, query: Query) => {
const properties: QueryDeletedEvent = {}
publishEvent(Event.QUERY_DELETED, properties)
await publishEvent(Event.QUERY_DELETED, properties)
}
export const imported = (
export const imported = async (
datasource: Datasource,
importSource: any,
count: any
) => {
const properties: QueryImportedEvent = {}
publishEvent(Event.QUERY_IMPORT, properties)
await publishEvent(Event.QUERY_IMPORT, properties)
}
// TODO
// exports.run = () => {
// const properties = {}
// events.processEvent(Events.QUERY_RUN, properties)
// }
export const previewed = (datasource: Datasource) => {
export const previewed = async (datasource: Datasource) => {
const properties: QueryPreviewedEvent = {}
publishEvent(Event.QUERY_PREVIEWED, properties)
await publishEvent(Event.QUERY_PREVIEWED, properties)
}

View File

@ -12,27 +12,27 @@ import {
/* eslint-disable */
export function created(role: Role) {
export async function created(role: Role) {
const properties: RoleCreatedEvent = {}
publishEvent(Event.ROLE_CREATED, properties)
await publishEvent(Event.ROLE_CREATED, properties)
}
export function updated(role: Role) {
export async function updated(role: Role) {
const properties: RoleUpdatedEvent = {}
publishEvent(Event.ROLE_UPDATED, properties)
await publishEvent(Event.ROLE_UPDATED, properties)
}
export function deleted(role: Role) {
export async function deleted(role: Role) {
const properties: RoleDeletedEvent = {}
publishEvent(Event.ROLE_DELETED, properties)
await publishEvent(Event.ROLE_DELETED, properties)
}
export function assigned(user: User, role: string) {
export async function assigned(user: User, role: string) {
const properties: RoleAssignedEvent = {}
publishEvent(Event.ROLE_ASSIGNED, properties)
await publishEvent(Event.ROLE_ASSIGNED, properties)
}
export function unassigned(user: User, role: string) {
export async function unassigned(user: User, role: string) {
const properties: RoleUnassignedEvent = {}
publishEvent(Event.ROLE_UNASSIGNED, properties)
await publishEvent(Event.ROLE_UNASSIGNED, properties)
}

View File

@ -9,18 +9,18 @@ import {
/* eslint-disable */
export const created = (count: number) => {
export const created = async (count: number) => {
const properties: RowsCreatedEvent = {
count,
}
publishEvent(Event.ROWS_CREATED, properties)
await publishEvent(Event.ROWS_CREATED, properties)
}
export const imported = (
export const imported = async (
table: Table,
format: RowImportFormat,
count: number
) => {
const properties: RowsImportedEvent = {}
publishEvent(Event.ROWS_IMPORTED, properties)
await publishEvent(Event.ROWS_IMPORTED, properties)
}

View File

@ -6,12 +6,12 @@ import {
ScreenDeletedEvent,
} from "@budibase/types"
export function created(screen: Screen) {
export async function created(screen: Screen) {
const properties: ScreenCreatedEvent = {}
publishEvent(Event.SCREEN_CREATED, properties)
await publishEvent(Event.SCREEN_CREATED, properties)
}
export function deleted(screen: Screen) {
export async function deleted(screen: Screen) {
const properties: ScreenDeletedEvent = {}
publishEvent(Event.SCREEN_DELETED, properties)
await publishEvent(Event.SCREEN_DELETED, properties)
}

View File

@ -9,17 +9,17 @@ import {
/* eslint-disable */
export function servedBuilder(version: number) {
export async function servedBuilder(version: number) {
const properties: BuilderServedEvent = {}
publishEvent(Event.SERVED_BUILDER, properties)
await publishEvent(Event.SERVED_BUILDER, properties)
}
export function servedApp(app: App) {
export async function servedApp(app: App) {
const properties: AppServedEvent = {}
publishEvent(Event.SERVED_APP, properties)
await publishEvent(Event.SERVED_APP, properties)
}
export function servedAppPreview(app: App) {
export async function servedAppPreview(app: App) {
const properties: AppPreviewServedEvent = {}
publishEvent(Event.SERVED_APP_PREVIEW, properties)
await publishEvent(Event.SERVED_APP_PREVIEW, properties)
}

View File

@ -13,33 +13,27 @@ import {
/* eslint-disable */
export function created(table: Table) {
export async function created(table: Table) {
const properties: TableCreatedEvent = {}
publishEvent(Event.TABLE_CREATED, properties)
await publishEvent(Event.TABLE_CREATED, properties)
}
export function updated(table: Table) {
export async function updated(table: Table) {
const properties: TableUpdatedEvent = {}
publishEvent(Event.TABLE_UPDATED, properties)
await publishEvent(Event.TABLE_UPDATED, properties)
}
export function deleted(table: Table) {
export async function deleted(table: Table) {
const properties: TableDeletedEvent = {}
publishEvent(Event.TABLE_DELETED, properties)
await publishEvent(Event.TABLE_DELETED, properties)
}
export function exported(table: Table, format: TableExportFormat) {
export async function exported(table: Table, format: TableExportFormat) {
const properties: TableExportedEvent = {}
publishEvent(Event.TABLE_EXPORTED, properties)
await publishEvent(Event.TABLE_EXPORTED, properties)
}
export function imported(table: Table, format: TableImportFormat) {
export async function imported(table: Table, format: TableImportFormat) {
const properties: TableImportedEvent = {}
publishEvent(Event.TABLE_IMPORTED, properties)
}
// TODO
export function permissionUpdated() {
const properties = {}
publishEvent(Event.TABLE_PERMISSION_UPDATED, properties)
await publishEvent(Event.TABLE_IMPORTED, properties)
}

View File

@ -17,73 +17,73 @@ import {
/* eslint-disable */
export function created(user: User) {
export async function created(user: User) {
const properties: UserCreatedEvent = {}
publishEvent(Event.USER_CREATED, properties)
await publishEvent(Event.USER_CREATED, properties)
}
export function updated(user: User) {
export async function updated(user: User) {
const properties: UserUpdatedEvent = {}
publishEvent(Event.USER_UPDATED, properties)
await publishEvent(Event.USER_UPDATED, properties)
}
export function deleted(user: User) {
export async function deleted(user: User) {
const properties: UserDeletedEvent = {}
publishEvent(Event.USER_DELETED, properties)
await publishEvent(Event.USER_DELETED, properties)
}
// PERMISSIONS
export function permissionAdminAssigned(user: User) {
export async function permissionAdminAssigned(user: User) {
const properties: UserPermissionAssignedEvent = {}
publishEvent(Event.USER_PERMISSION_ADMIN_ASSIGNED, properties)
await publishEvent(Event.USER_PERMISSION_ADMIN_ASSIGNED, properties)
}
export function permissionAdminRemoved(user: User) {
export async function permissionAdminRemoved(user: User) {
const properties: UserPermissionRemovedEvent = {}
publishEvent(Event.USER_PERMISSION_ADMIN_REMOVED, properties)
await publishEvent(Event.USER_PERMISSION_ADMIN_REMOVED, properties)
}
export function permissionBuilderAssigned(user: User) {
export async function permissionBuilderAssigned(user: User) {
const properties: UserPermissionAssignedEvent = {}
publishEvent(Event.USER_PERMISSION_BUILDER_ASSIGNED, properties)
await publishEvent(Event.USER_PERMISSION_BUILDER_ASSIGNED, properties)
}
export function permissionBuilderRemoved(user: User) {
export async function permissionBuilderRemoved(user: User) {
const properties: UserPermissionRemovedEvent = {}
publishEvent(Event.USER_PERMISSION_BUILDER_REMOVED, properties)
await publishEvent(Event.USER_PERMISSION_BUILDER_REMOVED, properties)
}
// INVITE
export function invited(userInfo: any) {
export async function invited(userInfo: any) {
const properties: UserInvitedEvent = {}
publishEvent(Event.USER_INVITED, properties)
await publishEvent(Event.USER_INVITED, properties)
}
export function inviteAccepted(user: User) {
export async function inviteAccepted(user: User) {
const properties: UserInviteAcceptedEvent = {}
publishEvent(Event.USER_INVITED_ACCEPTED, properties)
await publishEvent(Event.USER_INVITED_ACCEPTED, properties)
}
// PASSWORD
export function passwordForceReset(user: User) {
export async function passwordForceReset(user: User) {
const properties: UserPasswordForceResetEvent = {}
publishEvent(Event.USER_PASSWORD_FORCE_RESET, properties)
await publishEvent(Event.USER_PASSWORD_FORCE_RESET, properties)
}
export function passwordUpdated(user: User) {
export async function passwordUpdated(user: User) {
const properties: UserPasswordUpdatedEvent = {}
publishEvent(Event.USER_PASSWORD_UPDATED, properties)
await publishEvent(Event.USER_PASSWORD_UPDATED, properties)
}
export function passwordResetRequested(user: User) {
export async function passwordResetRequested(user: User) {
const properties: UserPasswordResetRequestedEvent = {}
publishEvent(Event.USER_PASSWORD_RESET_REQUESTED, properties)
await publishEvent(Event.USER_PASSWORD_RESET_REQUESTED, properties)
}
export function passwordReset(user: User) {
export async function passwordReset(user: User) {
const properties: UserPasswordResetEvent = {}
publishEvent(Event.USER_PASSWORD_RESET, properties)
await publishEvent(Event.USER_PASSWORD_RESET, properties)
}

View File

@ -19,52 +19,52 @@ import {
/* eslint-disable */
export function created(view: View) {
export async function created(view: View) {
const properties: ViewCreatedEvent = {}
publishEvent(Event.VIEW_CREATED, properties)
await publishEvent(Event.VIEW_CREATED, properties)
}
export function updated(view: View) {
export async function updated(view: View) {
const properties: ViewUpdatedEvent = {}
publishEvent(Event.VIEW_UPDATED, properties)
await publishEvent(Event.VIEW_UPDATED, properties)
}
export function deleted() {
export async function deleted() {
const properties: ViewDeletedEvent = {}
publishEvent(Event.VIEW_DELETED, properties)
await publishEvent(Event.VIEW_DELETED, properties)
}
export function exported(table: Table, format: TableExportFormat) {
export async function exported(table: Table, format: TableExportFormat) {
const properties: ViewExportedEvent = {}
publishEvent(Event.VIEW_EXPORTED, properties)
await publishEvent(Event.VIEW_EXPORTED, properties)
}
export function filterCreated() {
export async function filterCreated() {
const properties: ViewFilterCreatedEvent = {}
publishEvent(Event.VIEW_FILTER_CREATED, properties)
await publishEvent(Event.VIEW_FILTER_CREATED, properties)
}
export function filterUpdated() {
export async function filterUpdated() {
const properties: ViewFilterUpdatedEvent = {}
publishEvent(Event.VIEW_FILTER_UPDATED, properties)
await publishEvent(Event.VIEW_FILTER_UPDATED, properties)
}
export function filterDeleted() {
export async function filterDeleted() {
const properties: ViewFilterDeletedEvent = {}
publishEvent(Event.VIEW_FILTER_DELETED, properties)
await publishEvent(Event.VIEW_FILTER_DELETED, properties)
}
export function calculationCreated(calculation: ViewCalculation) {
export async function calculationCreated(calculation: ViewCalculation) {
const properties: ViewCalculationCreatedEvent = {}
publishEvent(Event.VIEW_CALCULATION_CREATED, properties)
await publishEvent(Event.VIEW_CALCULATION_CREATED, properties)
}
export function calculationUpdated() {
export async function calculationUpdated() {
const properties: ViewCalculationUpdatedEvent = {}
publishEvent(Event.VIEW_CALCULATION_UPDATED, properties)
await publishEvent(Event.VIEW_CALCULATION_UPDATED, properties)
}
export function calculationDeleted() {
export async function calculationDeleted() {
const properties: ViewCalculationDeletedEvent = {}
publishEvent(Event.VIEW_CALCULATION_DELETED, properties)
await publishEvent(Event.VIEW_CALCULATION_DELETED, properties)
}

View File

@ -7,6 +7,7 @@ const env = require("../environment")
const { SEPARATOR, ViewNames, queryGlobalView } = require("../../db")
const { getGlobalDB, doInTenant } = require("../tenancy")
const { decrypt } = require("../security/encryption")
const context = require("../context")
function finalise(
ctx,
@ -132,7 +133,12 @@ module.exports = (
}
// isAuthenticated is a function, so use a variable to be able to check authed state
finalise(ctx, { authenticated, user, internal, version, publicEndpoint })
return next()
if (user && user.email) {
return context.doInUserContext(user, next)
} else {
return next
}
} catch (err) {
// invalid token, clear the cookie
if (err && err.name === "JsonWebTokenError") {
@ -141,7 +147,7 @@ module.exports = (
// allow configuring for public access
if ((opts && opts.publicAllowed) || publicEndpoint) {
finalise(ctx, { authenticated: false, version, publicEndpoint })
return next()
return context.doInUserContext({ _id: "public_user" }, next)
} else {
ctx.throw(err.status || 403, err)
}

View File

@ -1,6 +1,8 @@
jest.mock("../../../events", () => {
return {
analyticsEnabled: () => false,
analytics: {
enabled: () => false,
},
shutdown: () => {},
account: {
created: jest.fn(),

View File

@ -194,6 +194,6 @@ exports.platformLogout = async ({ ctx, userId, keepActiveSession }) => {
userId,
sessions.map(({ sessionId }) => sessionId)
)
events.auth.logout()
await events.auth.logout()
await userCache.invalidateUser(userId)
}

View File

@ -8,7 +8,7 @@ const SERVER_PORT = cypressConfig.env.PORT
const WORKER_PORT = cypressConfig.env.WORKER_PORT
process.env.NODE_ENV = "cypress"
process.env.ENABLE_ANALYTICS = "false"
process.env.ENABLE_ANALYTICS = "0"
process.env.JWT_SECRET = cypressConfig.env.JWT_SECRET
process.env.COUCH_URL = `leveldb://${tmpdir}/.data/`
process.env.SELF_HOSTED = 1

View File

@ -130,7 +130,7 @@
<Divider size="S" />
<Layout gap="XS" noPadding>
<Heading size="S">Analytics</Heading>
<Body size="S">Choose whether to opt-in or opt-out of analtics</Body>
<Body size="S">Choose whether to opt-in or opt-out of analytics.</Body>
</Layout>
<div class="fields">
<div class="field">

View File

@ -145,7 +145,7 @@
"@babel/core": "^7.14.3",
"@babel/preset-env": "^7.14.4",
"@budibase/standard-components": "^0.9.139",
"@budibase/types": "^1.0.126-alpha.0",
"@budibase/types": "^1.0.167-alpha.8",
"@jest/test-sequencer": "^24.8.0",
"@types/apidoc": "^0.50.0",
"@types/bson": "^4.2.0",

View File

@ -10,7 +10,7 @@ const WORKER_PORT = "4200"
// @ts-ignore
process.env.NODE_ENV = "cypress"
process.env.ENABLE_ANALYTICS = "false"
process.env.ENABLE_ANALYTICS = "0"
process.env.JWT_SECRET = "budibase"
process.env.COUCH_URL = `leveldb://${tmpdir}/.data/`
process.env.SELF_HOSTED = "1"

View File

@ -2,6 +2,6 @@ import { events } from "@budibase/backend-core"
export const isEnabled = async (ctx: any) => {
ctx.body = {
enabled: events.analyticsEnabled(),
enabled: events.analytics.enabled(),
}
}

View File

@ -292,14 +292,14 @@ const performAppCreate = async (ctx: any) => {
return newApplication
}
const creationEvents = (request: any, app: App) => {
let creationFns = []
const creationEvents = async (request: any, app: App) => {
let creationFns: ((app: App) => Promise<void>)[] = []
const body = request.body
if (body.useTemplate === "true") {
// from template
if (body.templateKey) {
creationFns.push(() => events.app.templateImported(body.templateKey))
creationFns.push(app => events.app.templateImported(body.templateKey))
}
// from file
else if (request.files?.templateFile) {
@ -313,12 +313,12 @@ const creationEvents = (request: any, app: App) => {
creationFns.push(events.app.created)
for (let fn of creationFns) {
fn(app)
await fn(app)
}
}
const appPostCreate = async (ctx: any, app: App) => {
creationEvents(ctx.request, app)
await creationEvents(ctx.request, app)
// app import & template creation
if (ctx.request.body.useTemplate === "true") {
const rows = await getUniqueRows([app.appId])
@ -363,7 +363,7 @@ export const update = async (ctx: any) => {
}
const app = await updateAppPackage(ctx.request.body, ctx.params.appId)
events.app.updated(app)
await events.app.updated(app)
ctx.status = 200
ctx.body = app
}
@ -386,7 +386,7 @@ export const updateClient = async (ctx: any) => {
revertableVersion: currentVersion,
}
const app = await updateAppPackage(appPackageUpdates, ctx.params.appId)
events.app.versionUpdated(app)
await events.app.versionUpdated(app)
ctx.status = 200
ctx.body = app
}
@ -410,7 +410,7 @@ export const revertClient = async (ctx: any) => {
revertableVersion: null,
}
const app = await updateAppPackage(appPackageUpdates, ctx.params.appId)
events.app.versionReverted(app)
await events.app.versionReverted(app)
ctx.status = 200
ctx.body = app
}
@ -429,10 +429,10 @@ const destroyApp = async (ctx: any) => {
if (isUnpublish) {
await quotas.removePublishedApp()
events.app.unpublished(app)
await events.app.unpublished(app)
} else {
await quotas.removeApp()
events.app.deleted(app)
await events.app.deleted(app)
}
/* istanbul ignore next */

View File

@ -71,9 +71,9 @@ exports.create = async function (ctx) {
newAuto: automation,
})
const response = await db.put(automation)
events.automation.created()
await events.automation.created()
for (let step of automation.definition.steps) {
events.automation.stepCreated(step)
await events.automation.stepCreated(step)
}
automation._rev = response.rev
@ -97,19 +97,20 @@ const getDeletedSteps = (oldAutomation, automation) => {
return oldAutomation.definition.steps.filter(s => !stepIds.includes(s.id))
}
const handleStepEvents = (oldAutomation, automation) => {
const handleStepEvents = async (oldAutomation, automation) => {
// new steps
const newSteps = getNewSteps(oldAutomation, automation)
for (let step of newSteps) {
events.automation.stepCreated(step)
await events.automation.stepCreated(step)
}
// old steps
const deletedSteps = getDeletedSteps(oldAutomation, automation)
for (let step of deletedSteps) {
events.automation.stepDeleted(step)
await events.automation.stepDeleted(step)
}
}
exports.update = async function (ctx) {
const db = getAppDB()
let automation = ctx.request.body
@ -133,7 +134,7 @@ exports.update = async function (ctx) {
: {}
// trigger has been updated, remove the test inputs
if (oldAutoTrigger && oldAutoTrigger.id !== newAutoTrigger.id) {
events.automation.triggerUpdated()
await events.automation.triggerUpdated()
await deleteEntityMetadata(
ctx.appId,
MetadataTypes.AUTOMATION_TEST_INPUT,
@ -141,7 +142,7 @@ exports.update = async function (ctx) {
)
}
handleStepEvents(oldAutomation, automation)
await handleStepEvents(oldAutomation, automation)
ctx.status = 200
ctx.body = {
@ -179,7 +180,7 @@ exports.destroy = async function (ctx) {
// delete metadata first
await cleanupAutomationMetadata(automationId)
ctx.body = await db.remove(automationId, ctx.params.rev)
events.automation.deleted()
await events.automation.deleted()
}
exports.getActionList = async function (ctx) {
@ -247,5 +248,5 @@ exports.test = async function (ctx) {
})
await clearTestFlag(automation._id)
ctx.body = response
events.automation.tested()
await events.automation.tested()
}

View File

@ -7,5 +7,5 @@ exports.exportAppDump = async function (ctx) {
const backupIdentifier = `${appName}-export-${new Date().getTime()}.txt`
ctx.attachment(backupIdentifier)
ctx.body = await streamBackup(appId)
events.app.exported()
await events.app.exported()
}

View File

@ -109,7 +109,7 @@ exports.update = async function (ctx) {
}
const response = await db.put(datasource)
events.datasource.updated()
await events.datasource.updated()
datasource._rev = response.rev
// Drain connection pools when configuration is changed
@ -144,7 +144,7 @@ exports.save = async function (ctx) {
}
const dbResp = await db.put(datasource)
events.datasource.created()
await events.datasource.created()
datasource._rev = dbResp.rev
// Drain connection pools when configuration is changed
@ -179,7 +179,7 @@ exports.destroy = async function (ctx) {
// delete the datasource
await db.remove(ctx.params.datasourceId, ctx.params.revId)
events.datasource.deleted()
await events.datasource.deleted()
ctx.message = `Datasource deleted.`
ctx.status = 200

View File

@ -195,7 +195,7 @@ const _deployApp = async function (ctx: any) {
app = await deployApp(deployment)
}
events.app.published(app)
await events.app.published(app)
ctx.body = deployment
}

View File

@ -118,7 +118,7 @@ exports.revert = async ctx => {
ctx.body = {
message: "Reverted changes successfully.",
}
events.app.reverted()
await events.app.reverted()
} catch (err) {
ctx.throw(400, `Unable to revert. ${err}`)
} finally {

View File

@ -20,7 +20,7 @@ exports.save = async function (ctx) {
layout._id = layout._id || generateLayoutID()
const response = await db.put(layout)
events.layout.created()
await events.layout.created()
layout._rev = response.rev
ctx.body = layout
@ -48,7 +48,7 @@ exports.destroy = async function (ctx) {
}
await db.remove(layoutId, layoutRev)
events.layout.deleted()
await events.layout.deleted()
ctx.body = { message: "Layout deleted successfully" }
ctx.status = 200
}

View File

@ -84,9 +84,9 @@ export class RestImporter {
const count = successQueries.length
const importSource = this.source.getImportSource()
const datasource: Datasource = await db.get(datasourceId)
events.query.imported(datasource, importSource, count)
await events.query.imported(datasource, importSource, count)
for (let query of successQueries) {
events.query.created(datasource, query)
await events.query.created(datasource, query)
}
return {

View File

@ -92,7 +92,7 @@ export async function save(ctx: any) {
}
const response = await db.put(query)
eventFn()
await eventFn()
query._rev = response.rev
ctx.body = query
@ -132,7 +132,7 @@ export async function preview(ctx: any) {
})
const { rows, keys, info, extra } = await quotas.addQuery(runFn)
events.query.previewed(datasource)
await events.query.previewed(datasource)
ctx.body = {
rows,
schemaFields: [...new Set(keys)],
@ -223,5 +223,5 @@ export async function destroy(ctx: any) {
await db.remove(ctx.params.queryId, ctx.params.revId)
ctx.message = `Query deleted.`
ctx.status = 200
events.query.deleted(datasource, query)
await events.query.deleted(datasource, query)
}

View File

@ -66,9 +66,9 @@ exports.save = async function (ctx) {
}
const result = await db.put(role)
if (isCreate) {
events.role.created(role)
await events.role.created(role)
} else {
events.role.updated(role)
await events.role.updated(role)
}
await updateRolesOnUserTable(db, _id, UpdateRolesOptions.CREATED)
role._rev = result.rev
@ -97,7 +97,7 @@ exports.destroy = async function (ctx) {
}
await db.remove(roleId, ctx.params.rev)
events.role.deleted(role)
await events.role.deleted(role)
await updateRolesOnUserTable(
db,
ctx.params.roleId,

View File

@ -33,7 +33,7 @@ exports.save = async ctx => {
const response = await db.put(screen)
if (eventFn) {
eventFn(screen)
await eventFn(screen)
}
ctx.message = `Screen ${screen.name} saved.`
ctx.body = {
@ -50,7 +50,7 @@ exports.destroy = async ctx => {
await db.remove(id, ctx.params.screenRev)
events.screen.deleted(screen)
await events.screen.deleted(screen)
ctx.body = {
message: "Screen deleted successfully",
}

View File

@ -43,7 +43,7 @@ async function prepareUpload({ s3Key, bucket, metadata, file }) {
exports.serveBuilder = async function (ctx) {
let builderPath = resolve(TOP_LEVEL_PATH, "builder")
await send(ctx, ctx.file, { root: builderPath })
events.serve.servedBuilder(version)
await events.serve.servedBuilder(version)
}
exports.uploadFile = async function (ctx) {
@ -94,9 +94,9 @@ exports.serveApp = async function (ctx) {
}
if (isDevAppID(appInfo.appId)) {
events.serve.servedAppPreview(appInfo)
await events.serve.servedAppPreview(appInfo)
} else {
events.serve.servedApp(appInfo)
await events.serve.servedApp(appInfo)
}
}

View File

@ -60,12 +60,12 @@ exports.save = async function (ctx) {
table.dataImport && table.dataImport.csvString ? "csv" : undefined
const savedTable = await pickApi({ table }).save(ctx)
if (!table._id) {
events.table.created(savedTable)
await events.table.created(savedTable)
} else {
events.table.updated(savedTable)
await events.table.updated(savedTable)
}
if (importFormat) {
events.table.imported(savedTable, importFormat)
await events.table.imported(savedTable, importFormat)
}
ctx.status = 200
ctx.message = `Table ${table.name} saved successfully.`
@ -78,7 +78,7 @@ exports.destroy = async function (ctx) {
const appId = ctx.appId
const tableId = ctx.params.tableId
const deletedTable = await pickApi({ tableId }).destroy(ctx)
events.table.deleted(deletedTable)
await events.table.deleted(deletedTable)
ctx.eventEmitter &&
ctx.eventEmitter.emitTable(`table:delete`, appId, deletedTable)
ctx.status = 200

View File

@ -149,7 +149,7 @@ export async function handleDataImport(user: any, table: any, dataImport: any) {
}
await quotas.addRows(finalData.length, () => db.bulkDocs(finalData))
events.rows.imported(table, "csv", finalData.length)
await events.rows.imported(table, "csv", finalData.length)
return table
}

View File

@ -39,7 +39,7 @@ exports.save = async ctx => {
existingTable.views[viewName] = existingTable.views[originalName]
}
await db.put(table)
handleViewEvents(existingTable.views[viewName], table.views[viewName])
await handleViewEvents(existingTable.views[viewName], table.views[viewName])
ctx.body = {
...table.views[viewToSave.name],
@ -47,16 +47,16 @@ exports.save = async ctx => {
}
}
const calculationEvents = (existingView, newView) => {
const calculationEvents = async (existingView, newView) => {
const existingCalculation = existingView && existingView.calculation
const newCalculation = newView && newView.calculation
if (existingCalculation && !newCalculation) {
events.view.calculationDeleted()
await events.view.calculationDeleted()
}
if (!existingCalculation && newCalculation) {
events.view.calculationCreated()
await events.view.calculationCreated()
}
if (
@ -64,11 +64,11 @@ const calculationEvents = (existingView, newView) => {
newCalculation &&
existingCalculation !== newCalculation
) {
events.view.calculationUpdated()
await events.view.calculationUpdated()
}
}
const filterEvents = (existingView, newView) => {
const filterEvents = async (existingView, newView) => {
const hasExistingFilters = !!(
existingView &&
existingView.filters &&
@ -77,11 +77,11 @@ const filterEvents = (existingView, newView) => {
const hasNewFilters = !!(newView && newView.filters && newView.filters.length)
if (hasExistingFilters && !hasNewFilters) {
events.view.filterDeleted()
await events.view.filterDeleted()
}
if (!hasExistingFilters && hasNewFilters) {
events.view.filterCreated()
await events.view.filterCreated()
}
if (
@ -89,18 +89,18 @@ const filterEvents = (existingView, newView) => {
hasNewFilters &&
!isEqual(existingView.filters, newView.filters)
) {
events.view.filterUpdated()
await events.view.filterUpdated()
}
}
const handleViewEvents = (existingView, newView) => {
const handleViewEvents = async (existingView, newView) => {
if (!existingView) {
events.view.created()
await events.view.created()
} else {
events.view.updated()
await events.view.updated()
}
calculationEvents(existingView, newView)
filterEvents(existingView, newView)
await calculationEvents(existingView, newView)
await filterEvents(existingView, newView)
}
exports.destroy = async ctx => {
@ -110,7 +110,7 @@ exports.destroy = async ctx => {
const table = await db.get(view.meta.tableId)
delete table.views[viewName]
await db.put(table)
events.view.deleted()
await events.view.deleted()
ctx.body = view
}
@ -182,8 +182,8 @@ exports.exportView = async ctx => {
ctx.body = apiFileReturn(exporter(headers, rows))
if (viewName.startsWith(DocumentTypes.TABLE)) {
events.table.exported(table, format)
await events.table.exported(table, format)
} else {
events.view.exported(table, format)
await events.view.exported(table, format)
}
}

View File

@ -5,10 +5,10 @@ export const backfill = async (appDb: any) => {
const app: App = await appDb.get(db.DocumentTypes.APP_METADATA)
if (db.isDevAppID(app.appId)) {
events.app.created(app)
await events.app.created(app)
}
if (db.isProdAppID(app.appId)) {
events.app.published(app)
await events.app.published(app)
}
}

View File

@ -16,10 +16,10 @@ export const backfill = async (appDb: any) => {
const automations = await getAutomations(appDb)
for (const automation of automations) {
events.automation.created(automation)
await events.automation.created(automation)
for (const step of automation.definition.steps) {
events.automation.stepCreated(automation, step)
await events.automation.stepCreated(automation, step)
}
}
}

View File

@ -16,7 +16,7 @@ export const backfill = async (appDb: any) => {
const datasources: Datasource[] = await getDatasources(appDb)
for (const datasource of datasources) {
events.datasource.created(datasource)
await events.datasource.created(datasource)
}
}
}

View File

@ -16,7 +16,7 @@ export const backfill = async (appDb: any) => {
const layouts: Layout[] = await getLayouts(appDb)
for (const layout of layouts) {
events.layout.created(layout)
await events.layout.created(layout)
}
}
}

View File

@ -27,7 +27,7 @@ export const backfill = async (appDb: any) => {
appDb,
query.datasourceId
)
events.query.created(datasource, query)
await events.query.created(datasource, query)
}
}
}

View File

@ -16,7 +16,7 @@ export const backfill = async (appDb: any) => {
const roles = await getRoles(appDb)
for (const role of roles) {
events.role.created(role)
await events.role.created(role)
}
}
}

View File

@ -16,7 +16,7 @@ export const backfill = async (appDb: any) => {
const screens = await getScreens(appDb)
for (const screen of screens) {
events.screen.created(screen)
await events.screen.created(screen)
}
}
}

View File

@ -16,18 +16,18 @@ export const backfill = async (appDb: any) => {
const tables = await getTables(appDb)
for (const table of tables) {
events.table.created(table)
await events.table.created(table)
if (table.views) {
for (const view of Object.values(table.views)) {
events.view.created(view)
await events.view.created(view)
if (view.calculation) {
events.view.calculationCreated(view.calculation)
await events.view.calculationCreated(view.calculation)
}
if (view.filters?.length) {
events.view.filterCreated()
await events.view.filterCreated()
}
}
}

View File

@ -25,16 +25,16 @@ export const backfill = async (globalDb: any) => {
for (const config of configs) {
if (isSMTPConfig(config)) {
events.email.SMTPCreated(config)
await events.email.SMTPCreated(config)
}
if (isGoogleConfig(config)) {
events.auth.SSOCreated("google")
await events.auth.SSOCreated("google")
if (config.config.activated) {
events.auth.SSOActivated("google")
await events.auth.SSOActivated("google")
}
}
if (isOIDCConfig(config)) {
events.auth.SSOCreated("oidc")
await events.auth.SSOCreated("oidc")
if (config.config.configs[0].activated) {
events.auth.SSOActivated("oidc")
}
@ -42,12 +42,12 @@ export const backfill = async (globalDb: any) => {
if (isSettingsConfig(config)) {
const company = config.config.company
if (company && company !== "Budibase") {
events.org.nameUpdated()
await events.org.nameUpdated()
}
const logoUrl = config.config.logoUrl
if (logoUrl) {
events.org.logoUpdated()
await events.org.logoUpdated()
}
const platformUrl = config.config.platformUrl
@ -56,7 +56,7 @@ export const backfill = async (globalDb: any) => {
platformUrl !== "http://localhost:10000" &&
env.SELF_HOSTED
) {
events.org.platformURLUpdated()
await events.org.platformURLUpdated()
}
}
}

View File

@ -11,6 +11,6 @@ export const backfill = async () => {
const rows: Row[] = await getUniqueRows(appIds)
const rowCount = rows ? rows.length : 0
if (rowCount) {
events.rows.created(rowCount)
await events.rows.created(rowCount)
}
}

View File

@ -19,19 +19,19 @@ export const backfill = async (globalDb: any) => {
const users = await getUsers(globalDb)
for (const user of users) {
events.user.created(user)
await events.user.created(user)
if (user.admin?.global) {
events.user.permissionAdminAssigned(user)
await events.user.permissionAdminAssigned(user)
}
if (user.builder?.global) {
events.user.permissionBuilderAssigned(user)
await events.user.permissionBuilderAssigned(user)
}
if (user.roles) {
for (const [appId, role] of Object.entries(user.roles)) {
events.role.assigned(user, role)
await events.role.assigned(user, role)
}
}
}

View File

@ -1,6 +1,6 @@
{
"name": "@budibase/types",
"version": "1.0.126-alpha.0",
"version": "1.0.167-alpha.8",
"description": "Budibase types",
"main": "src/index.ts",
"types": "src/index.ts",

View File

@ -1 +1,2 @@
export * from "./hosting"
export * from "./sessions"

View File

@ -0,0 +1,36 @@
import { User, Account } from "../documents"
import { Hosting } from "./hosting"
/**
* Account portal user session. Used for self hosted accounts only.
*/
export interface AccountUserSession {
_id: string
email: string
tenantId: string
accountPortalAccess: boolean
account: Account
}
/**
* Budibase user session.
*/
export interface BudibaseUserSession extends User {
_id: string // overwrite potentially undefined
account?: Account
accountPortalAccess?: boolean
}
export const isAccountSession = (
user: AccountUserSession | BudibaseUserSession
): user is AccountUserSession => {
return user.account?.hosting === Hosting.SELF
}
export const isUserSession = (
user: AccountUserSession | BudibaseUserSession
): user is BudibaseUserSession => {
return !user.account || user.account?.hosting === Hosting.CLOUD
}
export type SessionUser = AccountUserSession | BudibaseUserSession

View File

@ -1,7 +1,82 @@
import { Hosting } from "../../core"
export interface Account {
accountId: string
export interface CreateAccount {
email: string
tenantId: string
hosting: Hosting
verified: boolean
authType: AuthType
// optional fields - for sso based sign ups
registrationStep?: string
// profile
tenantName?: string
name?: string
size?: string
profession?: string
}
export interface CreatePassswordAccount extends CreateAccount {
password: string
}
export const isCreatePasswordAccount = (
account: CreateAccount
): account is CreatePassswordAccount => account.authType === AuthType.PASSWORD
export interface UpdateAccount {
stripeCustomerId?: string
licenseKey?: string
}
export interface Account extends CreateAccount {
// generated
accountId: string
createdAt: number
// registration
verified: boolean
verificationSent: boolean
// licensing
tier: string // deprecated
stripeCustomerId?: string
licenseKey?: string
}
export interface PasswordAccount extends Account {
password: string
}
export const isPasswordAccount = (
account: Account
): account is PasswordAccount =>
account.authType === AuthType.PASSWORD && account.hosting === Hosting.SELF
export interface CloudAccount extends Account {
password?: string
budibaseUserId: string
}
export const isCloudAccount = (account: Account): account is CloudAccount =>
account.hosting === Hosting.CLOUD
export const isSelfHostAccount = (account: Account) =>
account.hosting === Hosting.SELF
export const isSSOAccount = (account: Account): account is SSOAccount =>
account.authType === AuthType.SSO
export interface SSOAccount extends Account {
pictureUrl?: string
provider?: string
providerType?: string
oauth2?: OAuthTokens
thirdPartyProfile: any // TODO: define what the google profile looks like
}
export enum AuthType {
SSO = "sso",
PASSWORD = "password",
}
export interface OAuthTokens {
accessToken: string
refreshToken: string
}

View File

@ -8,6 +8,8 @@ export interface User extends Document {
admin?: {
global: boolean
}
providerType?: string
tenantId: string
}
export interface UserRoles {

View File

@ -42,6 +42,7 @@ export enum Event {
// ORG / ANALYTICS
ANALYTICS_OPT_OUT = "analytics:opt:out",
ANALYTICS_OPT_IN = "analytics:opt:in",
// APP
APP_CREATED = "app:created",
@ -88,7 +89,6 @@ export enum Event {
TABLE_EXPORTED = "table:exported",
TABLE_IMPORTED = "table:imported",
TABLE_DATA_IMPORTED = "table:data:imported",
TABLE_PERMISSION_UPDATED = "table:permission:updated",
// VIEW
VIEW_CREATED = "view:created",
@ -140,12 +140,6 @@ export enum Event {
ACCOUNT_VERIFIED = "account:verified",
}
export enum IdentityType {
TENANT = "tenant",
USER = "user",
ACCOUNT = "account",
}
export type RowImportFormat = "csv"
export type TableExportFormat = "json" | "csv"
export type TableImportFormat = "csv"

View File

@ -0,0 +1,29 @@
import { Hosting } from "../core"
export enum IdentityType {
USER = "user", // cloud and self hosted users
ACCOUNT = "account", // self hosted accounts
TENANT = "tenant", // cloud and self hosted tenants
}
export interface Identity {
id: string
tenantId: string
}
export interface UserIdentity extends Identity {
hosting: Hosting
type: IdentityType
authType: string
}
export interface BudibaseIdentity extends UserIdentity {
builder?: boolean
admin?: boolean
}
export interface AccountIdentity extends UserIdentity {
verified: boolean
profession: string | undefined
companySize: string | undefined
}

View File

@ -15,3 +15,4 @@ export * from "./serve"
export * from "./table"
export * from "./user"
export * from "./view"
export * from "./identification"

View File

@ -64,7 +64,7 @@
"server-destroy": "^1.0.1"
},
"devDependencies": {
"@budibase/types": "^1.0.126-alpha.0",
"@budibase/types": "^1.0.167-alpha.8",
"@types/jest": "^26.0.23",
"@types/koa": "^2.13.3",
"@types/koa-router": "^7.4.2",

View File

@ -71,7 +71,7 @@ export const authenticate = async (ctx: any, next: any) => {
"local",
async (err: any, user: any, info: any) => {
await authInternal(ctx, user, err, info)
events.auth.login("local")
await events.auth.login("local")
ctx.status = 200
}
)(ctx, next)
@ -112,7 +112,7 @@ export const reset = async (ctx: any) => {
user,
subject: "{{ company }} platform password reset",
})
events.user.passwordResetRequested(user)
await events.user.passwordResetRequested(user)
}
} catch (err) {
console.log(err)
@ -139,7 +139,7 @@ export const resetUpdate = async (ctx: any) => {
}
// remove password from the user before sending events
delete user.password
events.user.passwordReset(user)
await events.user.passwordReset(user)
} catch (err) {
ctx.throw(400, "Cannot reset password.")
}
@ -212,7 +212,7 @@ export const googleAuth = async (ctx: any, next: any) => {
{ successRedirect: "/", failureRedirect: "/error" },
async (err: any, user: any, info: any) => {
await authInternal(ctx, user, err, info)
events.auth.login("google")
await events.auth.login("google")
ctx.redirect("/")
}
)(ctx, next)
@ -256,7 +256,7 @@ export const oidcAuth = async (ctx: any, next: any) => {
{ successRedirect: "/", failureRedirect: "/error" },
async (err: any, user: any, info: any) => {
await authInternal(ctx, user, err, info)
events.auth.login("oidc")
await events.auth.login("oidc")
ctx.redirect("/")
}
)(ctx, next)

View File

@ -162,7 +162,7 @@ exports.save = async function (ctx) {
try {
const response = await db.put(ctx.request.body)
for (const fn of eventFns) {
fn()
await fn()
}
ctx.body = {
type,

View File

@ -145,8 +145,8 @@ exports.updateSelf = async ctx => {
// remove the old password from the user before sending events
delete user.password
events.user.updated(user)
await events.user.updated(user)
if (passwordChange) {
events.user.passwordUpdated(user)
await events.user.passwordUpdated(user)
}
}

View File

@ -123,7 +123,7 @@ export const invite = async (ctx: any) => {
ctx.body = {
message: "Invitation has been sent.",
}
events.user.invited(userInfo)
await events.user.invited(userInfo)
}
export const inviteAccept = async (ctx: any) => {
@ -139,7 +139,7 @@ export const inviteAccept = async (ctx: any) => {
email,
...info,
})
events.user.inviteAccepted(user)
await events.user.inviteAccepted(user)
return user
})
} catch (err: any) {

View File

@ -1,19 +1,19 @@
import { events } from "@budibase/backend-core"
import { User, UserRoles } from "@budibase/types"
export const handleDeleteEvents = (user: any) => {
events.user.deleted(user)
export const handleDeleteEvents = async (user: any) => {
await events.user.deleted(user)
if (isBuilder(user)) {
events.user.permissionBuilderRemoved(user)
await events.user.permissionBuilderRemoved(user)
}
if (isAdmin(user)) {
events.user.permissionAdminRemoved(user)
await events.user.permissionAdminRemoved(user)
}
}
const assignAppRoleEvents = (
const assignAppRoleEvents = async (
user: User,
roles: UserRoles,
existingRoles: UserRoles
@ -21,12 +21,12 @@ const assignAppRoleEvents = (
for (const [appId, role] of Object.entries(roles)) {
// app role in existing is not same as new
if (!existingRoles || existingRoles[appId] !== role) {
events.role.assigned(user, role)
await events.role.assigned(user, role)
}
}
}
const unassignAppRoleEvents = (
const unassignAppRoleEvents = async (
user: User,
roles: UserRoles,
existingRoles: UserRoles
@ -37,29 +37,29 @@ const unassignAppRoleEvents = (
for (const [appId, role] of Object.entries(existingRoles)) {
// app role in new is not same as existing
if (!roles || roles[appId] !== role) {
events.role.unassigned(user, role)
await events.role.unassigned(user, role)
}
}
}
const handleAppRoleEvents = (user: any, existingUser: any) => {
const handleAppRoleEvents = async (user: any, existingUser: any) => {
const roles = user.roles
const existingRoles = existingUser?.roles
assignAppRoleEvents(user, roles, existingRoles)
unassignAppRoleEvents(user, roles, existingRoles)
await assignAppRoleEvents(user, roles, existingRoles)
await unassignAppRoleEvents(user, roles, existingRoles)
}
export const handleSaveEvents = (user: any, existingUser: any) => {
export const handleSaveEvents = async (user: any, existingUser: any) => {
if (existingUser) {
events.user.updated(user)
await events.user.updated(user)
if (isRemovingBuilder(user, existingUser)) {
events.user.permissionBuilderRemoved(user)
await events.user.permissionBuilderRemoved(user)
}
if (isRemovingAdmin(user, existingUser)) {
events.user.permissionAdminRemoved(user)
await events.user.permissionAdminRemoved(user)
}
if (
@ -67,21 +67,21 @@ export const handleSaveEvents = (user: any, existingUser: any) => {
user.forceResetPassword &&
user.password
) {
events.user.passwordForceReset(user)
await events.user.passwordForceReset(user)
}
} else {
events.user.created(user)
await events.user.created(user)
}
if (isAddingBuilder(user, existingUser)) {
events.user.permissionBuilderAssigned(user)
await events.user.permissionBuilderAssigned(user)
}
if (isAddingAdmin(user, existingUser)) {
events.user.permissionAdminAssigned(user)
await events.user.permissionAdminAssigned(user)
}
handleAppRoleEvents(user, existingUser)
await handleAppRoleEvents(user, existingUser)
}
const isBuilder = (user: any) => user.builder && user.builder.global

View File

@ -129,7 +129,7 @@ export const save = async (
}
user._rev = response.rev
eventHelpers.handleSaveEvents(user, dbUser)
await eventHelpers.handleSaveEvents(user, dbUser)
await tenancy.tryAddTenant(tenantId, _id, email)
await cache.user.invalidateUser(response.id)
@ -169,7 +169,7 @@ export const destroy = async (id: string, currentUser: any) => {
await deprovisioning.removeUserFromInfoDB(dbUser)
await db.remove(dbUser._id, dbUser._rev)
eventHelpers.handleDeleteEvents(dbUser)
await eventHelpers.handleDeleteEvents(dbUser)
await quotas.removeUser(dbUser)
await cache.user.invalidateUser(dbUser._id)
await sessions.invalidateSessions(dbUser._id)