task-b complate
This commit is contained in:
98
src/features/foundation/audit-log/service.ts
Normal file
98
src/features/foundation/audit-log/service.ts
Normal file
@@ -0,0 +1,98 @@
|
||||
import { headers } from 'next/headers';
|
||||
import { trAuditLogs } from '@/db/schema';
|
||||
import { db } from '@/lib/db';
|
||||
import { getCurrentUserContext } from '@/features/foundation/auth-context/service';
|
||||
import type { AuditLogInput, AuditLogRecord } from './types';
|
||||
|
||||
async function resolveAuditContext(input: AuditLogInput) {
|
||||
const context = await getCurrentUserContext();
|
||||
const organizationId = input.organizationId ?? context?.activeOrganization?.id ?? null;
|
||||
const userId = input.userId ?? context?.user.id ?? null;
|
||||
|
||||
if (!organizationId) {
|
||||
throw new Error('organizationId is required for audit logging');
|
||||
}
|
||||
|
||||
if (!userId) {
|
||||
throw new Error('userId is required for audit logging');
|
||||
}
|
||||
|
||||
return {
|
||||
organizationId,
|
||||
userId
|
||||
};
|
||||
}
|
||||
|
||||
async function resolveRequestId(inputRequestId?: string | null) {
|
||||
if (inputRequestId) {
|
||||
return inputRequestId;
|
||||
}
|
||||
|
||||
try {
|
||||
const requestHeaders = await headers();
|
||||
|
||||
return requestHeaders.get('x-request-id');
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function mapAuditRecord(row: typeof trAuditLogs.$inferSelect): AuditLogRecord {
|
||||
return {
|
||||
id: row.id,
|
||||
organizationId: row.organizationId,
|
||||
branchId: row.branchId,
|
||||
userId: row.userId,
|
||||
entityType: row.entityType,
|
||||
entityId: row.entityId,
|
||||
action: row.action,
|
||||
beforeData: row.beforeData,
|
||||
afterData: row.afterData,
|
||||
requestId: row.requestId,
|
||||
createdAt: row.createdAt.toISOString()
|
||||
};
|
||||
}
|
||||
|
||||
export async function auditAction(input: AuditLogInput) {
|
||||
const { organizationId, userId } = await resolveAuditContext(input);
|
||||
const requestId = await resolveRequestId(input.requestId);
|
||||
const [created] = await db
|
||||
.insert(trAuditLogs)
|
||||
.values({
|
||||
id: crypto.randomUUID(),
|
||||
organizationId,
|
||||
branchId: input.branchId ?? null,
|
||||
userId,
|
||||
entityType: input.entityType,
|
||||
entityId: input.entityId,
|
||||
action: input.action,
|
||||
beforeData:
|
||||
input.beforeData === undefined ? null : JSON.parse(JSON.stringify(input.beforeData)),
|
||||
afterData: input.afterData === undefined ? null : JSON.parse(JSON.stringify(input.afterData)),
|
||||
requestId
|
||||
})
|
||||
.returning();
|
||||
|
||||
return mapAuditRecord(created);
|
||||
}
|
||||
|
||||
export async function auditCreate(input: Omit<AuditLogInput, 'action'>) {
|
||||
return auditAction({
|
||||
...input,
|
||||
action: 'create'
|
||||
});
|
||||
}
|
||||
|
||||
export async function auditUpdate(input: Omit<AuditLogInput, 'action'>) {
|
||||
return auditAction({
|
||||
...input,
|
||||
action: 'update'
|
||||
});
|
||||
}
|
||||
|
||||
export async function auditDelete(input: Omit<AuditLogInput, 'action'>) {
|
||||
return auditAction({
|
||||
...input,
|
||||
action: 'delete'
|
||||
});
|
||||
}
|
||||
25
src/features/foundation/audit-log/types.ts
Normal file
25
src/features/foundation/audit-log/types.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
export interface AuditLogInput {
|
||||
organizationId?: string;
|
||||
branchId?: string | null;
|
||||
userId?: string;
|
||||
entityType: string;
|
||||
entityId: string;
|
||||
action: string;
|
||||
beforeData?: unknown;
|
||||
afterData?: unknown;
|
||||
requestId?: string | null;
|
||||
}
|
||||
|
||||
export interface AuditLogRecord {
|
||||
id: string;
|
||||
organizationId: string;
|
||||
branchId: string | null;
|
||||
userId: string;
|
||||
entityType: string;
|
||||
entityId: string;
|
||||
action: string;
|
||||
beforeData: unknown;
|
||||
afterData: unknown;
|
||||
requestId: string | null;
|
||||
createdAt: string;
|
||||
}
|
||||
Reference in New Issue
Block a user