Initialize backendized SurClaw report system

- Add React/Vite frontend for login, dashboard, reports, templates, users, settings, AI, speech, and media workflows.

- Add NestJS/Prisma/PostgreSQL backend with auth, dashboard stats, reports, templates, users, departments, settings, files, AI, speech, audit logs, and HTML sanitization.

- Add Prisma schema, migrations, seed data, persistent app sessions, Docker/Nginx deployment files, and upload volume configuration.

- Add Vitest, Playwright, backend integration tests, and project documentation for requirements, design, permissions, API contracts, testing, deployment, security, and progress.

- Configure production local fallback switch and remove unused Gemini direct dependency/env wiring.
This commit is contained in:
2026-05-02 01:37:20 +08:00
commit 014aca8619
162 changed files with 27116 additions and 0 deletions

View File

@@ -0,0 +1,11 @@
import { Global, Module } from '@nestjs/common';
import { PrismaModule } from '../prisma/prisma.module.js';
import { AuditService } from './audit.service.js';
@Global()
@Module({
imports: [PrismaModule],
providers: [AuditService],
exports: [AuditService],
})
export class AuditModule {}

View File

@@ -0,0 +1,46 @@
import { Injectable } from '@nestjs/common';
import type { Prisma } from '@prisma/client';
import type { SafeUser } from '../auth/auth.types.js';
import { PrismaService } from '../prisma/prisma.service.js';
export interface AuditInput {
actor?: SafeUser | null;
action: string;
targetType: string;
targetId?: string | null;
departmentId?: string | null;
metadata?: Record<string, unknown>;
ip?: string | null;
userAgent?: string | null;
}
@Injectable()
export class AuditService {
constructor(private readonly prisma: PrismaService) {}
async record(input: AuditInput) {
const actor = input.actor || null;
const tenantId = actor?.tenantId;
if (!tenantId) return;
await this.prisma.auditLog.create({
data: {
tenantId,
actorUserId: actor.id,
actorRole: actor.role,
action: input.action,
targetType: input.targetType,
targetId: input.targetId || null,
departmentId: input.departmentId || actor.departmentId || null,
ip: input.ip || null,
userAgent: input.userAgent || null,
metadata: input.metadata ? (cleanJson(input.metadata) as Prisma.InputJsonValue) : undefined,
},
}).catch(() => {
// Auditing must not break the clinical workflow. Operational alerts can watch failed inserts.
});
}
}
const cleanJson = (value: Record<string, unknown>) =>
JSON.parse(JSON.stringify(value)) as Record<string, unknown>;