mirror of
https://github.com/BetterCorp/BetterFrame.git
synced 2026-05-27 22:56:34 +00:00
Each service plugin now independently initializes its own DB connection via shared/db/init.ts instead of depending on a central service-store plugin. This removes the inter-plugin dependency ordering and the plugin-registry singleton, making each service self-contained. - Move db-adapter, repository, mappers, migrations, adapters to shared/db/ - Create shared/db/config.ts (reusable dbConfigSchema) and init.ts - Delete service-store plugin and plugin-registry - Add db config block to each service's ConfigSchema + sec-config template - Move event_log purge timer into service-admin-http - Update all import paths across shared modules and plugins Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
75 lines
2.2 KiB
TypeScript
75 lines
2.2 KiB
TypeScript
/**
|
|
* Audit logging helper — single inline call from route handlers.
|
|
*
|
|
* audit(repo, event, "user.login", { result: "ok" });
|
|
* audit(repo, event, "firmware.upload", { resource_id: rel.id, metadata: { version, channel } });
|
|
*
|
|
* Pulls actor + ip out of the h3 event context. Never throws — logging
|
|
* failure must not break the caller's request.
|
|
*/
|
|
import type { Repository } from "./db/repository.js";
|
|
import type { AuditActorType, AuditResult } from "./types.js";
|
|
|
|
interface AuditCtx {
|
|
context?: {
|
|
user?: { id?: number; username?: string };
|
|
apiKeyPrefix?: string;
|
|
session?: unknown;
|
|
};
|
|
req?: { headers: { get(name: string): string | null } };
|
|
}
|
|
|
|
export interface AuditInput {
|
|
resource_type?: string;
|
|
resource_id?: string | number;
|
|
metadata?: Record<string, unknown>;
|
|
result?: AuditResult;
|
|
/** Override actor (e.g. when system performs action on behalf of nobody). */
|
|
actor_type?: AuditActorType;
|
|
actor_id?: number | null;
|
|
actor_label?: string | null;
|
|
}
|
|
|
|
export async function audit(
|
|
repo: Repository,
|
|
event: AuditCtx | null,
|
|
action: string,
|
|
input: AuditInput = {},
|
|
): Promise<void> {
|
|
try {
|
|
const ctx = event?.context;
|
|
let actor_type: AuditActorType = input.actor_type ?? "system";
|
|
let actor_id: number | null = input.actor_id ?? null;
|
|
let actor_label: string | null = input.actor_label ?? null;
|
|
|
|
if (!input.actor_type && ctx) {
|
|
if (ctx.apiKeyPrefix) {
|
|
actor_type = "api_key";
|
|
actor_label = ctx.apiKeyPrefix;
|
|
} else if (ctx.user) {
|
|
actor_type = "user";
|
|
actor_id = ctx.user.id ?? null;
|
|
actor_label = ctx.user.username ?? null;
|
|
}
|
|
}
|
|
|
|
const headers = event?.req?.headers;
|
|
const ip = headers?.get("x-real-ip")
|
|
?? headers?.get("x-forwarded-for")?.split(",")[0]?.trim()
|
|
?? null;
|
|
|
|
await repo.insertAudit({
|
|
actor_type,
|
|
actor_id,
|
|
actor_label,
|
|
action,
|
|
resource_type: input.resource_type ?? null,
|
|
resource_id: input.resource_id != null ? String(input.resource_id) : null,
|
|
ip,
|
|
metadata: input.metadata ?? {},
|
|
result: input.result ?? "ok",
|
|
});
|
|
} catch {
|
|
/* never throw from audit */
|
|
}
|
|
}
|