diff --git a/src/common/log.entity.ts b/src/common/log.entity.ts new file mode 100644 index 0000000..0b3fb01 --- /dev/null +++ b/src/common/log.entity.ts @@ -0,0 +1,72 @@ +/** + * Copyright 2024 IBM Corp. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { ChangeSetType, Entity, ManyToOne, PrimaryKey, Property, ref, Ref } from '@mikro-orm/core'; +import { User } from '@zilliz/milvus2-sdk-node'; +import { requestContext } from '@fastify/request-context'; + +import { generatePrefixedObjectId } from '@/utils/id.js'; +import { ProjectPrincipal } from '@/administration/entities/project-principal.entity'; + +@Entity() +export class Log { + @PrimaryKey({ fieldName: '_id' }) + id = generatePrefixedObjectId('log'); + + @Property() + createdAt: Date = new Date(); + + @ManyToOne() + projectPrincipal?: Ref; + + @ManyToOne() + user?: Ref; + + @Property() + entity?: string; + + @Property() + entityId?: string; + + @Property() + type?: ChangeSetType; + + @Property() + change?: any; + + @Property() + additionalData: any; + + constructor({ entity, entityId, type, change, additionalData }: LogInput) { + const user = requestContext.get('user'); + if (user) { + this.user = ref(user); + } + const projectPrincipal = requestContext.get('projectPrincipal'); + if (projectPrincipal) { + this.projectPrincipal = ref(projectPrincipal); + } + this.entity = entity; + this.entityId = entityId; + this.type = type; + this.change = change; + this.additionalData = additionalData; + } +} + +export type LogInput = Partial< + Pick +>; diff --git a/src/common/log.subscriber.ts b/src/common/log.subscriber.ts new file mode 100644 index 0000000..634e7c5 --- /dev/null +++ b/src/common/log.subscriber.ts @@ -0,0 +1,85 @@ +import { ChangeSetType, EventSubscriber, FlushEventArgs } from '@mikro-orm/core'; + +import { Log } from './log.entity'; +import { BaseEntity } from './base.entity'; + +import { inJob, inSeeder } from '@/context'; + +const loggedEntities: { [key: string]: { types?: ChangeSetType[]; entities: string[] } } = { + Assistant: { + entities: ['project', 'agent'] + }, + Artifact: { + entities: ['project', 'thread', 'name'] + }, + Chat: { + types: [ChangeSetType.CREATE], + entities: ['artifact'] + }, + Message: { + entities: ['project'] + }, + Thread: { + entities: ['project'] + }, + Tool: { + entities: ['project', 'name'] + }, + VectorStore: { + entities: ['project', 'name'] + }, + VectorStoreFile: { + entities: ['project', 'file'] + }, + File: { + entities: ['project', 'filename'] + }, + Run: { + entities: ['project', 'assistant', 'status'] + }, + User: { + entities: ['email'] + }, + Organization: { + entities: ['name'] + }, + Project: { + entities: ['name', 'organization'] + }, + ApiKey: { + entities: ['project'] + } +}; + +export class LogSubscriber implements EventSubscriber { + onFlush(args: FlushEventArgs): void | Promise { + args.uow.getChangeSets().forEach((cs) => { + if ( + loggedEntities[cs.name] && + (loggedEntities[cs.name].types?.includes(cs.type) ?? true) && + inSeeder() === false + ) { + if (cs.type === ChangeSetType.DELETE && inJob()) return; + + const log = new Log({ + entity: cs.name, + entityId: cs.entity?.id, + type: cs.type, + change: cs.type === ChangeSetType.UPDATE ? cs.payload : undefined, + additionalData: + loggedEntities[cs.name].entities.reduce( + (acc, name) => ({ + ...acc, + [name]: + cs.entity[name].entity instanceof BaseEntity + ? cs.entity[name].id + : cs.entity[name] + }), + {} + ) ?? undefined + }); + args.uow.computeChangeSet(log); + } + }); + } +} diff --git a/src/mikro-orm.config.ts b/src/mikro-orm.config.ts index 28faa15..b75531d 100644 --- a/src/mikro-orm.config.ts +++ b/src/mikro-orm.config.ts @@ -25,6 +25,7 @@ import { Migrator } from '@mikro-orm/migrations-mongodb'; import { SeedManager } from '@mikro-orm/seeder'; import { MONGODB_CA_CERT, MONGODB_DATABASE_NAME, MONGODB_URL } from './config.js'; +import { LogSubscriber } from './common/log.subscriber.js'; if (process.env.NODE_ENV === 'production') { process.env.MIKRO_ORM_NO_COLOR = 'true'; @@ -69,6 +70,7 @@ const config: Options = { ...createMongoTLSConfig() }, extensions: [Migrator, SeedManager], + subscribers: [new LogSubscriber()], migrations: { path: './dist/migrations', pathTs: './migrations',