import { Context, Effect, Layer } from "effect"; import { randomUUID } from "node:crypto"; import { DatabaseService } from "../../database/db.js"; import { DatabaseError } from "../../../shared/types.js"; export interface CreateAuditLogData { userId: string | null; action: AuditAction; resourceType?: string | null; resourceId?: string | null; ipAddress?: string | null; userAgent?: string | null; details?: string | null; } export type AuditAction = | "USER_REGISTERED" | "USER_LOGIN" | "USER_LOGOUT" | "LOGIN_FAILED" | "PASSWORD_CHANGED" | "PASSWORD_RESET_REQUESTED" | "PASSWORD_RESET_COMPLETED" | "EMAIL_VERIFIED" | "EMAIL_CHANGED" | "PROFILE_UPDATED" | "ACCOUNT_LOCKED" | "ACCOUNT_UNLOCKED" | "SESSION_CREATED" | "SESSION_REVOKED" | "TOKEN_REFRESHED" | "OAUTH_LINKED" | "OAUTH_UNLINKED" | "TWO_FACTOR_ENABLED" | "TWO_FACTOR_DISABLED"; export class AuditLogService extends Context.Tag("AuditLogService")< AuditLogService, { readonly log: ( data: CreateAuditLogData ) => Effect.Effect; } >() { static Live = Layer.effect( this, Effect.gen(function* (_) { const db = yield* _(DatabaseService); const log = ( data: CreateAuditLogData ): Effect.Effect => Effect.gen(function* (_) { const id = randomUUID(); const now = Date.now(); yield* _( db.run( `INSERT INTO audit_logs ( id, user_id, action, resource_type, resource_id, ip_address, user_agent, details, created_at ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`, [ id, data.userId, data.action, data.resourceType ?? null, data.resourceId ?? null, data.ipAddress ?? null, data.userAgent ?? null, data.details ?? null, now, ] ) ); }); return { log }; }) ); }