Last active
May 8, 2025 14:14
-
-
Save tengla/1c2cf5b40881e6a6d5463227a5527cbc to your computer and use it in GitHub Desktop.
Audit logging w decorators
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import "reflect-metadata/lite"; | |
| import EventEmitter from "events"; | |
| import { container, inject, injectable } from "tsyringe"; | |
| const em = new EventEmitter(); | |
| em.on("audit", (data) => { | |
| console.log(data); | |
| // I can maybe write to a audit log here (db, stdout, or file?)... | |
| }); | |
| function Audited<T extends (...args: any[]) => any>( | |
| action: "read" | "create" | "update" | "delete", | |
| entity: string, | |
| getRecordId: (result: ReturnType<T>) => number | |
| ): MethodDecorator { | |
| return function ( | |
| _: any, | |
| propertyKey: string | symbol, | |
| descriptor: any | |
| ): void { | |
| const originalMethod = descriptor.value!; | |
| descriptor.value = async function (...args: any[]) { | |
| if (Object.prototype.hasOwnProperty.call(this, "authUserService")) { | |
| const user = this.authUserService.getUser(); | |
| if (!user) { | |
| throw new Error("User not authenticated"); | |
| } | |
| const useCaseName = this.constructor.name; | |
| const result = await originalMethod.apply(this, args); | |
| const recordId = getRecordId(result); | |
| em.emit("audit", { | |
| action, | |
| entity, | |
| recordId, | |
| useCaseName, | |
| propertyKey, | |
| authUser: user, | |
| args | |
| }); | |
| return result; | |
| } | |
| }; | |
| }; | |
| } | |
| container.register("UserRepository", { | |
| useValue: { | |
| create: async (item: any) => { | |
| // Simulate writing to a database | |
| return { | |
| id: Math.random().toString(36).slice(2), | |
| ...item, | |
| }; | |
| } | |
| } | |
| }); | |
| container.register("AuthUserService", { | |
| useValue: { | |
| getUser() { | |
| return { | |
| id: 1, | |
| name: "Jane Doe", | |
| email: "[email protected]" | |
| } | |
| } | |
| } | |
| }); | |
| @injectable() | |
| class CreateUserUseCase { | |
| constructor( | |
| @inject("UserRepository") private userRepository: any, // Simulating a repository | |
| @inject("AuthUserService") private authUserService: { getUser(): any } // Simulating an auth service | |
| ) { } | |
| @Audited("create", "user", (result) => result.id) | |
| execute<T extends { name: string }>(item: T): T { | |
| // Simulate writing to a database and returning the item with an id | |
| return this.userRepository.create(item); | |
| } | |
| } | |
| const useCase = container.resolve(CreateUserUseCase); | |
| useCase.execute({ name: "John Doe", age: 29 }); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment