Un pot de crédits global (BASIC 100 / PRO 1 000 / BUSINESS 4 000) remplace les seaux par fonction côté débit. Packs one-shot S/M/L, admin sans seaux legacy, usage multi-features, hints de coût, anti-flash thème et hydratation admin. Inclut aussi le pipeline slides renforcé et la page publique polishée.
69 lines
1.9 KiB
TypeScript
69 lines
1.9 KiB
TypeScript
import prisma from '@/lib/prisma'
|
|
|
|
export type AuditAction =
|
|
| 'LOGIN'
|
|
| 'LOGOUT'
|
|
| 'USER_CREATED'
|
|
| 'AI_REQUEST'
|
|
| 'DATA_EXPORT'
|
|
| 'ACCOUNT_DELETED'
|
|
| 'PASSWORD_RESET'
|
|
| 'AI_CONSENT_GRANTED'
|
|
| 'AI_CONSENT_REVOKED'
|
|
| 'SUBSCRIPTION_OVERRIDE'
|
|
| 'BILLING_CONFIG_UPDATED'
|
|
| 'PLAN_ENTITLEMENT_UPDATED'
|
|
| 'QUOTA_RESET'
|
|
|
|
export interface AuditLogParams {
|
|
userId?: string | null
|
|
action: AuditAction
|
|
resource?: string
|
|
metadata?: Record<string, unknown>
|
|
ip?: string
|
|
userAgent?: string
|
|
}
|
|
|
|
/** Fire-and-forget — never throws, safe to call anywhere */
|
|
export function logAuditEvent(params: AuditLogParams): void {
|
|
prisma.auditLog
|
|
.create({
|
|
data: {
|
|
userId: params.userId ?? null,
|
|
action: params.action,
|
|
resource: params.resource ?? null,
|
|
metadata: params.metadata ? (params.metadata as any) : undefined,
|
|
ip: params.ip ?? null,
|
|
userAgent: params.userAgent ?? null,
|
|
},
|
|
})
|
|
.catch((err: unknown) => console.error('[audit-log] write failed:', err))
|
|
}
|
|
|
|
/** Awaitable version for cases where ordering matters */
|
|
export async function logAuditEventAsync(params: AuditLogParams): Promise<void> {
|
|
try {
|
|
await prisma.auditLog.create({
|
|
data: {
|
|
userId: params.userId ?? null,
|
|
action: params.action,
|
|
resource: params.resource ?? null,
|
|
metadata: params.metadata ? (params.metadata as any) : undefined,
|
|
ip: params.ip ?? null,
|
|
userAgent: params.userAgent ?? null,
|
|
},
|
|
})
|
|
} catch (err) {
|
|
console.error('[audit-log] write failed:', err)
|
|
}
|
|
}
|
|
|
|
/** Extract IP from request headers (works behind Cloudflare / nginx) */
|
|
export function getClientIp(request: Request): string | undefined {
|
|
const cf = request.headers.get('cf-connecting-ip')
|
|
if (cf) return cf
|
|
const xff = request.headers.get('x-forwarded-for')
|
|
if (xff) return xff.split(',')[0].trim()
|
|
return undefined
|
|
}
|