import { getPayload as _getPayload } from "payload"; import type { SanitizedConfig } from "payload"; import type { AuditLogProtocol } from "@repo/core-shared/di"; import type { IDataRectify } from "./data-rectify.interface"; import type { DsrCollectionCustom } from "./dsr-collection-custom"; type PayloadDoc = Record; type PayloadAPI = { find(args: { collection: string; where: Record; overrideAccess: true; limit: number; }): Promise<{ docs: PayloadDoc[] }>; update(args: { collection: string; id: string; data: Record; overrideAccess: true; }): Promise; }; type GetPayload = (args: { config: SanitizedConfig }) => Promise; /** * Payload-backed IDataRectify. Verifies the target field is PII-tagged before * updating and emits a RESTRICT audit entry with `reason: "art-16-request"` as * the tamper-evident record of the Art. 16 correction. */ export class PayloadDataRectify implements IDataRectify { constructor( private readonly config: SanitizedConfig, private readonly auditLog: AuditLogProtocol, private readonly getPayloadFn: GetPayload = _getPayload as unknown as GetPayload, ) {} async updateSubjectField( subjectId: string, collectionSlug: string, field: string, value: unknown, ): Promise { const collectionCfg = this.config.collections.find( (c) => c.slug === collectionSlug, ); if (!collectionCfg) { throw new Error(`Collection "${collectionSlug}" not found in config`); } const custom = (collectionCfg.custom ?? {}) as DsrCollectionCustom; if (!custom.subject) { throw new Error( `Collection "${collectionSlug}" has no DSR subject linkage`, ); } if (!custom.pii?.[field]) { throw new Error( `Field "${field}" in "${collectionSlug}" is not tagged as PII`, ); } const { field: subjectField } = custom.subject; const where = subjectField === "id" ? { id: { equals: subjectId } } : { [subjectField]: { equals: subjectId } }; const payload = await this.getPayloadFn({ config: this.config }); const result = await payload.find({ collection: collectionSlug, where, overrideAccess: true, limit: 1000, }); for (const doc of result.docs) { await payload.update({ collection: collectionSlug, id: String(doc["id"]), data: { [field]: value }, overrideAccess: true, }); } await this.auditLog.record({ actorId: subjectId, actorType: "user", actorRoles: [], action: "RESTRICT", resource: { type: collectionSlug }, changedFields: [field], at: new Date(), scope: { feature: "core-dsr", environment: process.env["NODE_ENV"] ?? "development", tenant: "default", }, reason: "art-16-request", from: { ipTruncated: "system", userAgent: "system" }, containsPii: false, outcome: "success", }); } }