import type * as interfaces from '../../ts_interfaces/index.ts'; export interface IAuditLogEntry { timestamp: number; actorUserId: string; action: string; targetType: string; targetId?: string; success: boolean; message?: string; metadata?: Record; } export class AuditLogger { constructor(private storageDirectory: string) {} public get auditLogPath(): string { return `${this.storageDirectory}/.objectstorage/audit.log`; } public async log(entry: Omit): Promise { const logEntry: IAuditLogEntry = { timestamp: Date.now(), ...entry, }; const dirPath = this.auditLogPath.substring(0, this.auditLogPath.lastIndexOf('/')); await Deno.mkdir(dirPath, { recursive: true }); await Deno.writeTextFile(this.auditLogPath, `${JSON.stringify(logEntry)}\n`, { append: true, create: true, mode: 0o600, }); await this.restrictAuditLogPermissions(); } public async listRecent(limit = 100): Promise { let content = ''; try { content = await Deno.readTextFile(this.auditLogPath); } catch (error) { if (error instanceof Deno.errors.NotFound) { return []; } throw error; } return content .trim() .split('\n') .filter(Boolean) .slice(-limit) .map((line) => JSON.parse(line) as interfaces.data.IAuditEntry) .reverse(); } private async restrictAuditLogPermissions(): Promise { try { await Deno.chmod(this.auditLogPath, 0o600); } catch { // chmod is not available on every platform Deno supports. } } }