Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/analytics/analytics.service.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { Logger } from '@nestjs/common';
import { Counter, Histogram } from 'prom-client';
import { Logger, BadRequestException } from '@nestjs/common';
import { Counter, Histogram } from 'prom-client';
import { Injectable, Logger, BadRequestException } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
Expand Down
61 changes: 39 additions & 22 deletions src/audit-log/audit-log.entity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,59 +4,64 @@ import {
PrimaryGeneratedColumn,
CreateDateColumn,
Index,
VersionColumn,
} from 'typeorm';
import { AuditAction, AuditSeverity, AuditCategory } from './enums/audit-action.enum';
import { AuditAction, AuditSeverity, AuditCategory, HttpMethod } from './enums/audit-action.enum';

/**
* Represents the audit Log entity.
* Immutable audit log record.
*
* Rows are append-only — never updated or soft-deleted. Expiry is handled
* exclusively via `applyRetentionPolicy`, which hard-deletes rows whose
* `retentionUntil` has passed.
*
* Index strategy:
* Composite (column + timestamp) indexes support the most common queries:
* "all events for user X, newest first", "all CRITICAL events this week", etc.
* The `retentionUntil` index supports efficient bulk-delete during retention
* policy runs without a full table scan.
*/
@Entity('audit_logs')
@Index(['userId', 'timestamp'])
@Index(['action', 'timestamp'])
@Index(['category', 'timestamp'])
@Index(['severity', 'timestamp'])
@Index(['entityType', 'entityId'])
@Index(['ipAddress'])
@Index(['ipAddress', 'timestamp'])
@Index(['timestamp'])
@Index(['retentionUntil']) // required for efficient retention policy deletes
export class AuditLog {
@PrimaryGeneratedColumn('uuid')
id: string;

@VersionColumn()
version: number;
// ── Actor ──────────────────────────────────────────────────────────────────

@Column({ name: 'user_id', nullable: true })
userId: string | null;

@Column({ name: 'user_email', nullable: true })
userEmail: string | null;

@Column({
type: 'enum',
enum: AuditAction,
})
// ── Event classification ───────────────────────────────────────────────────

@Column({ type: 'enum', enum: AuditAction })
action: AuditAction;

@Column({
type: 'enum',
enum: AuditCategory,
})
@Column({ type: 'enum', enum: AuditCategory })
category: AuditCategory;

@Column({
type: 'enum',
enum: AuditSeverity,
default: AuditSeverity.INFO,
})
@Column({ type: 'enum', enum: AuditSeverity, default: AuditSeverity.INFO })
severity: AuditSeverity;

// ── Target entity ──────────────────────────────────────────────────────────

@Column({ name: 'entity_type', nullable: true })
entityType: string | null;

@Column({ name: 'entity_id', nullable: true })
entityId: string | null;

// ── Payload ────────────────────────────────────────────────────────────────

@Column({ type: 'text', nullable: true })
description: string | null;

Expand All @@ -69,6 +74,8 @@ export class AuditLog {
@Column({ name: 'new_values', type: 'jsonb', nullable: true })
newValues: Record<string, unknown> | null;

// ── Request context ────────────────────────────────────────────────────────

@Column({ name: 'ip_address', nullable: true })
ipAddress: string | null;

Expand All @@ -84,21 +91,31 @@ export class AuditLog {
@Column({ name: 'api_endpoint', nullable: true })
apiEndpoint: string | null;

@Column({ name: 'http_method', nullable: true })
httpMethod: string | null;
/** Constrained to known HTTP verbs — free strings invite silent typos. */
@Column({ name: 'http_method', type: 'enum', enum: HttpMethod, nullable: true })
httpMethod: HttpMethod | null;

@Column({ name: 'status_code', nullable: true })
statusCode: number | null;

@Column({ name: 'response_time_ms', nullable: true })
responseTimeMs: number | null;

// ── Multi-tenancy ──────────────────────────────────────────────────────────

@Column({ name: 'tenant_id', nullable: true })
tenantId: string | null;

// ── Timestamps ─────────────────────────────────────────────────────────────

@CreateDateColumn({ name: 'timestamp' })
timestamp: Date;

/**
* Absolute expiry date for this record.
* Null means the record is kept indefinitely (e.g. CRITICAL severity logs).
* Indexed — see class-level @Index.
*/
@Column({ name: 'retention_until', nullable: true })
retentionUntil: Date | null;
}
}
Loading
Loading