/* eslint-disable @typescript-eslint/no-explicit-any */ import { Optional } from '@speckle/shared' import knex from '@/db/knex' import { BaseMetaRecord } from '@/modules/core/helpers/meta' import { Knex } from 'knex' import { reduce } from 'lodash' type BaseInnerSchemaConfig = { /** * Table name */ name: T /** * Get `knex(tableName)` QueryBuilder instance. Use the generic argument to type the results of the query. */ knex: () => Knex.QueryBuilder /** * Get names of table columns. The names can be prefixed with the table name or not, depending * on whether `withoutTablePrefix` was set when accessing the helper. */ col: { [colName in C]: string } /** * All of the column names in an array */ cols: string[] } type BaseSchemaConfig> = BC & { /** * Return schema helper with custom configuration options */ with: (params?: SchemaConfigParams) => BC /** * Helper with withoutTablePrefix set to true */ withoutTablePrefix: BC } type InnerSchemaConfig< T extends string, C extends string, M extends Optional> > = BaseInnerSchemaConfig & { /** * Associated meta table helper, if any */ meta: M } export type SchemaConfig< T extends string, C extends string, M extends Optional> > = BaseSchemaConfig> type MetaInnerSchemaConfig< T extends string, C extends string, MK extends string > = BaseInnerSchemaConfig & { /** * Get meta keys individually */ metaKey: { [keyName in MK]: string } /** * Get all available meta keys */ metaKeys: string[] /** * Column in the meta table that identifies an entity from the associated parent table. * E.g. In the users_meta table this column is 'user_id' - it identifies the user for which the meta value is stored */ parentIdentityCol: string } export type MetaSchemaConfig< T extends string, C extends string, MK extends string > = BaseSchemaConfig> type SchemaConfigParams = { /** * Configure `col` properties to not have the table name prefixed. For the most part you want the prefix, * cause this helps in queries with JOINS (when multiple tables have a col with the same name), but you don't * want the prefix when triggering UPDATE queries, because the `SET = ` syntax doesn't support * column names with table prefixes. */ withoutTablePrefix?: boolean /** * Configure a custom table prefix that will be attached to column names. This will be relevant when you're * building subqueries or joining a table onto itself. */ withCustomTablePrefix?: string } const createBaseInnerSchemaConfigBuilder = (tableName: T, columns: C[]) => (params: SchemaConfigParams = {}): BaseInnerSchemaConfig => { const aliasedTableName = params.withCustomTablePrefix ? `${tableName} as ${params.withCustomTablePrefix}` : tableName const colName = (col: string) => { if (params.withoutTablePrefix) return col const prefix = params.withCustomTablePrefix || tableName return `${prefix}.${col}` } return { name: aliasedTableName as T, knex: () => knex(aliasedTableName), col: reduce( columns, (prev, curr) => { prev[curr] = colName(curr) return prev }, {} as Record ), cols: columns.map(colName) } } /** * Create table schema helper * @param tableName * @param columns */ function buildTableHelper< T extends string, C extends string, M extends Optional> >(tableName: T, columns: C[], metaTable?: M): SchemaConfig { const buildBaseConfig = createBaseInnerSchemaConfigBuilder(tableName, columns) const buildInnerConfig = ( params: SchemaConfigParams = {} ): InnerSchemaConfig => ({ ...buildBaseConfig(params), meta: metaTable as M }) return { ...buildInnerConfig(), with: buildInnerConfig, withoutTablePrefix: buildInnerConfig({ withoutTablePrefix: true }) } } /** * Create meta table schema helper */ function buildMetaTableHelper( tableName: T, extraColumns: C[], metaKeys: MK[], parentIdentityCol: C ): MetaSchemaConfig { const baseColumns: Array = [ 'key', 'value', 'createdAt', 'updatedAt' ] const buildBaseConfig = createBaseInnerSchemaConfigBuilder(tableName, [ ...extraColumns, ...baseColumns ]) const buildInnerMetaConfig = ( params: SchemaConfigParams = {} ): MetaInnerSchemaConfig => ({ ...buildBaseConfig(params), metaKeys, metaKey: reduce( [...metaKeys, ...baseColumns], (prev, curr) => { prev[curr] = curr return prev }, {} as Record ), parentIdentityCol }) return { ...buildInnerMetaConfig(), with: buildInnerMetaConfig, withoutTablePrefix: buildInnerMetaConfig({ withoutTablePrefix: true }) } } /* * TABLE HELPERS * The generated helpers are used like this: * * Streams.name - TableName * Streams.col.id - Get column names * Streams.knex() - Get knex() instance for this specific table * * Streams.with({...}) - configure helper, e.g. disable table name being prefixed to col names: * Streams.with({withoutTablePrefix: true}).col.id * * Streams.withoutTablePrefix.col.id - Shorthand for accessing columns without the table prefix * * META TABLE HELPERS * Largely the same, but also hold extra props like `metaKeys` that store allowed meta keys */ export const Streams = buildTableHelper('streams', [ 'id', 'name', 'description', 'isPublic', 'clonedFrom', 'createdAt', 'updatedAt', 'allowPublicComments', 'isDiscoverable' ]) export const StreamAcl = buildTableHelper('stream_acl', [ 'userId', 'resourceId', 'role' ]) export const StreamFavorites = buildTableHelper('stream_favorites', [ 'streamId', 'userId', 'createdAt', 'cursor' ]) export const UsersMeta = buildMetaTableHelper( 'users_meta', ['userId', 'key', 'value', 'createdAt', 'updatedAt'], ['isOnboardingFinished', 'foo', 'bar'], 'userId' ) export const Users = buildTableHelper( 'users', [ 'id', 'suuid', 'createdAt', 'name', 'bio', 'company', 'email', 'verified', 'avatar', 'profiles', 'passwordDigest', 'ip' ], UsersMeta ) export const ServerAcl = buildTableHelper('server_acl', ['userId', 'role']) export const Comments = buildTableHelper('comments', [ 'id', 'streamId', 'authorId', 'createdAt', 'updatedAt', 'text', 'screenshot', 'data', 'archived', 'parentComment' ]) export const CommentLinks = buildTableHelper('comment_links', [ 'commentId', 'resourceId', 'resourceType' ]) export const CommentViews = buildTableHelper('comment_views', [ 'commentId', 'userId', 'viewedAt' ]) export const ServerInvites = buildTableHelper('server_invites', [ 'id', 'target', 'inviterId', 'createdAt', 'used', 'message', 'resourceTarget', 'resourceId', 'role', 'token' ]) export const PasswordResetTokens = buildTableHelper('pwdreset_tokens', [ 'id', 'email', 'createdAt' ]) export const RefreshTokens = buildTableHelper('refresh_tokens', [ 'id', 'tokenDigest', 'appId', 'userId', 'createdAt', 'lifespan' ]) export const AuthorizationCodes = buildTableHelper('authorization_codes', [ 'id', 'appId', 'userId', 'challenge', 'createdAt', 'lifespan' ]) export const ApiTokens = buildTableHelper('api_tokens', [ 'id', 'tokenDigest', 'owner', 'name', 'lastChars', 'revoked', 'lifespan', 'createdAt', 'lastUsed' ]) export const EmailVerifications = buildTableHelper('email_verifications', [ 'id', 'email', 'createdAt', 'used' ]) export const ServerAccessRequests = buildTableHelper('server_access_requests', [ 'id', 'requesterId', 'resourceType', 'resourceId', 'createdAt', 'updatedAt' ]) export const StreamActivity = buildTableHelper('stream_activity', [ 'streamId', 'time', 'resourceType', 'resourceId', 'actionType', 'userId', 'info', 'message' ]) export const UserNotificationPreferences = buildTableHelper( 'user_notification_preferences', ['userId', 'preferences'] ) export const Commits = buildTableHelper('commits', [ 'id', 'referencedObject', 'author', 'message', 'createdAt', 'sourceApplication', 'totalChildrenCount', 'parents' ]) export const StreamCommits = buildTableHelper('stream_commits', [ 'streamId', 'commitId' ]) export const BranchCommits = buildTableHelper('branch_commits', [ 'branchId', 'commitId' ]) export const Branches = buildTableHelper('branches', [ 'id', 'streamId', 'authorId', 'name', 'description', 'createdAt', 'updatedAt' ]) export const ScheduledTasks = buildTableHelper('scheduled_tasks', [ 'taskName', 'lockExpiresAt' ]) export const Objects = buildTableHelper('objects', [ 'id', 'speckleType', 'totalChildrenCount', 'totalChildrenCountByDepth', 'createdAt', 'data', 'streamId' ]) export const FileUploads = buildTableHelper('file_uploads', [ 'id', 'streamId', 'branchName', 'userId', 'fileName', 'fileType', 'fileSize', 'uploadComplete', 'uploadDate', 'convertedStatus', 'convertedLastUpdate', 'convertedMessage', 'convertedCommitId' ]) export { knex }