feat(ee): audit logs (#1977)

feat: clickhouse driver
* sync
* updates
This commit is contained in:
Philip Okugbe
2026-03-01 01:29:03 +00:00
committed by GitHub
parent 85ce0d32bf
commit 69d7532c6c
62 changed files with 2600 additions and 191 deletions
@@ -0,0 +1,60 @@
import { Kysely, sql } from 'kysely';
export async function up(db: Kysely<any>): Promise<void> {
await db.schema
.createTable('audit')
.ifNotExists()
.addColumn('id', 'uuid', (col) =>
col.primaryKey().defaultTo(sql`gen_uuid_v7()`),
)
.addColumn('workspace_id', 'uuid', (col) =>
col.notNull().references('workspaces.id').onDelete('cascade'),
)
.addColumn('actor_id', 'uuid')
.addColumn('actor_type', 'varchar', (col) =>
col.notNull().defaultTo('user'),
)
.addColumn('event', 'varchar', (col) => col.notNull())
.addColumn('resource_type', 'varchar', (col) => col.notNull())
.addColumn('resource_id', 'uuid')
.addColumn('space_id', 'uuid')
.addColumn('changes', 'jsonb')
.addColumn('metadata', 'jsonb')
.addColumn('ip_address', sql`inet`)
.addColumn('created_at', 'timestamptz', (col) =>
col.notNull().defaultTo(sql`now()`),
)
.execute();
await db.schema
.createIndex('idx_audit_workspace_id')
.ifNotExists()
.on('audit')
.columns(['workspace_id', 'id desc'])
.execute();
// add new workspace columns
await db.schema
.alterTable('workspaces')
.addColumn('audit_retention_days', 'int8', (col) => col)
.execute();
await db.schema
.alterTable('workspaces')
.addColumn('trash_retention_days', 'int8', (col) => col)
.execute();
}
export async function down(db: Kysely<any>): Promise<void> {
await db.schema
.alterTable('workspaces')
.dropColumn('audit_retention_days')
.execute();
await db.schema
.alterTable('workspaces')
.dropColumn('trash_retention_days')
.execute();
await db.schema.dropTable('audit').execute();
}
@@ -136,15 +136,23 @@ export class ShareRepo {
await query.execute();
}
async deleteBySpaceId(spaceId: string): Promise<void> {
await this.db
async deleteBySpaceId(
spaceId: string,
trx?: KyselyTransaction,
): Promise<void> {
const db = dbOrTx(this.db, trx);
await db
.deleteFrom('shares')
.where('spaceId', '=', spaceId)
.execute();
}
async deleteByWorkspaceId(workspaceId: string): Promise<void> {
await this.db
async deleteByWorkspaceId(
workspaceId: string,
trx?: KyselyTransaction,
): Promise<void> {
const db = dbOrTx(this.db, trx);
await db
.deleteFrom('shares')
.where('workspaceId', '=', workspaceId)
.execute();
@@ -94,8 +94,10 @@ export class SpaceRepo {
workspaceId: string,
prefKey: string,
prefValue: string | boolean,
trx?: KyselyTransaction,
) {
return this.db
const db = dbOrTx(this.db, trx);
return db
.updateTable('spaces')
.set({
settings: sql`COALESCE(settings, '{}'::jsonb)
@@ -38,9 +38,9 @@ export class UserTokenRepo {
async insertUserToken(
insertableUserToken: InsertableUserToken,
trx?: KyselyTransaction,
opts?: { trx?: KyselyTransaction },
) {
const db = dbOrTx(this.db, trx);
const db = dbOrTx(this.db, opts?.trx);
return db
.insertInto('userTokens')
.values(insertableUserToken)
@@ -33,6 +33,7 @@ export class WorkspaceRepo {
'enforceSso',
'plan',
'enforceMfa',
'trashRetentionDays',
];
constructor(@InjectKysely() private readonly db: KyselyDB) {}
@@ -162,8 +163,10 @@ export class WorkspaceRepo {
workspaceId: string,
prefKey: string,
prefValue: string | boolean,
trx?: KyselyTransaction,
) {
return this.db
const db = dbOrTx(this.db, trx);
return db
.updateTable('workspaces')
.set({
settings: sql`COALESCE(settings, '{}'::jsonb)
@@ -180,8 +183,10 @@ export class WorkspaceRepo {
workspaceId: string,
prefKey: string,
prefValue: string | boolean,
trx?: KyselyTransaction,
) {
return this.db
const db = dbOrTx(this.db, trx);
return db
.updateTable('workspaces')
.set({
settings: sql`COALESCE(settings, '{}'::jsonb)
@@ -198,8 +203,10 @@ export class WorkspaceRepo {
workspaceId: string,
prefKey: string,
prefValue: string | boolean,
trx?: KyselyTransaction,
) {
return this.db
const db = dbOrTx(this.db, trx);
return db
.updateTable('workspaces')
.set({
settings: sql`COALESCE(settings, '{}'::jsonb)
@@ -211,4 +218,5 @@ export class WorkspaceRepo {
.returning(this.baseFields)
.executeTakeFirst();
}
}
+19 -2
View File
@@ -61,6 +61,21 @@ export interface Attachments {
workspaceId: string;
}
export interface Audit {
actorId: string | null;
actorType: Generated<string>;
changes: Json | null;
createdAt: Generated<Timestamp>;
event: string;
id: Generated<string>;
ipAddress: string | null;
metadata: Json | null;
resourceId: string | null;
resourceType: string;
spaceId: string | null;
workspaceId: string;
}
export interface AuthAccounts {
authProviderId: string | null;
createdAt: Generated<Timestamp>;
@@ -339,6 +354,8 @@ export interface WorkspaceInvitations {
}
export interface Workspaces {
auditRetentionDays: Generated<number>;
trashRetentionDays: Generated<number>;
billingEmail: string | null;
createdAt: Generated<Timestamp>;
customDomain: string | null;
@@ -415,6 +432,7 @@ export interface PagePermissions {
export interface DB {
apiKeys: ApiKeys;
attachments: Attachments;
audit: Audit;
authAccounts: AuthAccounts;
authProviders: AuthProviders;
backlinks: Backlinks;
@@ -425,9 +443,8 @@ export interface DB {
groupUsers: GroupUsers;
notifications: Notifications;
pageAccess: PageAccess;
pageHierarchy: PageHierarchy;
pageHistory: PageHistory;
pagePermissions: PagePermissions;
pageHistory: PageHistory;
pages: Pages;
shares: Shares;
spaceMembers: SpaceMembers;
@@ -24,6 +24,7 @@ import {
UserMfa as _UserMFA,
ApiKeys,
Watchers,
Audit as _Audit,
} from './db';
import { PageEmbeddings } from '@docmost/db/types/embeddings.types';
@@ -155,3 +156,8 @@ export type UpdatablePageAccess = Updateable<Omit<_PageAccess, 'id'>>;
export type PagePermission = Selectable<_PagePermissions>;
export type InsertablePagePermission = Insertable<_PagePermissions>;
export type UpdatablePagePermission = Updateable<Omit<_PagePermissions, 'id'>>;
// Audit
export type Audit = Selectable<_Audit>;
export type InsertableAudit = Insertable<_Audit>;
export type UpdatableAudit = Updateable<Omit<_Audit, 'id'>>;