Track HiveGrid Cloud planning reference
This commit is contained in:
1 parent
b3d5ea6764
commit
e341c5938a
5784 files changed
+1104929
-3
No files matched your search
@@ -0,0 +1,84 @@
|
||||
// P4 Files (AK-P4a) §12 — audit + activity wiring for the Files module.
|
||||
//
|
||||
// Audit (DoD 13): every write action records a tamper-evident AuditEvent (M001)
|
||||
// via foundation.auditEvent with the shared db client.
|
||||
//
|
||||
// Activity (DoD 14): user-facing timeline entries are logged AGAINST the linked
|
||||
// entity (the file itself is not an Activity subject in v1 — see decision 4 in
|
||||
// the build plan). Only FileEntityType values that map to an ActivitySubjectType
|
||||
// (contact/company/lead/deal/project/task) produce an activity; others are
|
||||
// skipped (no shared @hivegrid/activity edit).
|
||||
|
||||
import type { JsonValue } from "@toolctrl/foundation";
|
||||
import { createAuditEvent } from "@toolctrl/db/audit-events.server";
|
||||
import { logActivity, type ActivitySubjectType, type ActivityType } from "@hivegrid/activity";
|
||||
import type { FilesRouteContext } from "./fileAccess.server";
|
||||
import type { FileEntityType } from "./fileService.server";
|
||||
|
||||
export type FileAuditAction = "file.uploaded" | "file.linked" | "file.unlinked" | "file.archived" | "file.reactivated" | "file.deleted" | "file.updated";
|
||||
|
||||
export async function auditFileEvent(input: {
|
||||
ctx: Pick<FilesRouteContext, "tenantId" | "userId">;
|
||||
action: FileAuditAction;
|
||||
fileId: string;
|
||||
metadata?: Record<string, JsonValue>;
|
||||
}): Promise<void> {
|
||||
try {
|
||||
// createAuditEvent (M001) writes the tamper-evident, hash-chained AuditEvent
|
||||
// and defaults its client to the shared db singleton.
|
||||
await createAuditEvent({
|
||||
tenantId: input.ctx.tenantId,
|
||||
actorId: input.ctx.userId,
|
||||
actorType: "user",
|
||||
action: input.action,
|
||||
targetType: "file",
|
||||
targetId: input.fileId,
|
||||
metadata: input.metadata ?? null,
|
||||
});
|
||||
} catch {
|
||||
// Audit must never break the user action; the failure is logged upstream.
|
||||
}
|
||||
}
|
||||
|
||||
const ENTITY_TO_ACTIVITY_SUBJECT: Partial<Record<FileEntityType, ActivitySubjectType>> = {
|
||||
contact: "contact",
|
||||
company: "company",
|
||||
lead: "lead",
|
||||
deal: "deal",
|
||||
project: "project",
|
||||
task: "task",
|
||||
};
|
||||
|
||||
/**
|
||||
* Log a file-related activity against the linked entity's timeline. No-op when
|
||||
* the entity type has no Activity subject mapping (e.g. user/form_submission).
|
||||
*/
|
||||
export async function logFileActivity(input: {
|
||||
ctx: Pick<FilesRouteContext, "tenantId" | "userId">;
|
||||
entityType: FileEntityType;
|
||||
entityId: string;
|
||||
activityType: Extract<ActivityType, "file_uploaded">;
|
||||
summaryText: string;
|
||||
fileId: string;
|
||||
}): Promise<void> {
|
||||
const subjectType = ENTITY_TO_ACTIVITY_SUBJECT[input.entityType];
|
||||
if (!subjectType) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
// logActivity defaults its write client to the shared db singleton.
|
||||
await logActivity({
|
||||
tenantId: input.ctx.tenantId,
|
||||
actorUserId: input.ctx.userId,
|
||||
actorKind: "user",
|
||||
activityType: input.activityType,
|
||||
subjectType,
|
||||
subjectId: input.entityId,
|
||||
summaryText: input.summaryText,
|
||||
// The file is not an Activity subject in v1; carry its id in metadata.
|
||||
metadata: { fileId: input.fileId },
|
||||
});
|
||||
} catch {
|
||||
// Activity logging is best-effort; never break the user action.
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user