fix(folders): logging, files-rescue, hard-delete wiring, audit logs
- A6: logger import + warn calls in document-folders.service.ts - G-C1: re-parent files (not just documents) in deleteFolderSoftRescue - A4: importer sets files.folder_id (was only setting documents.folder_id) - A7 + G-C3: demote system folder + nullify scratchpadNotes in client-hard-delete - Defense-in-depth portId on folder-move UPDATE - Audit logs for createFolder, syncEntityFolderName, archive/restore suffix - portId in companies/yachts archive log context - Row-count telemetry in backfill CLI Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -39,11 +39,13 @@ import { files, documents, formSubmissions } from '@/lib/db/schema/documents';
|
||||
import { documentSends } from '@/lib/db/schema/brochures';
|
||||
import { emailThreads } from '@/lib/db/schema/email';
|
||||
import { reminders } from '@/lib/db/schema/operations';
|
||||
import { scratchpadNotes } from '@/lib/db/schema/system';
|
||||
import { user as authUser } from '@/lib/db/schema/users';
|
||||
import { redis } from '@/lib/redis';
|
||||
import { sendEmail } from '@/lib/email';
|
||||
import { logger } from '@/lib/logger';
|
||||
import { createAuditLog, type AuditMeta } from '@/lib/audit';
|
||||
import { demoteSystemFolderOnEntityDelete } from '@/lib/services/document-folders.service';
|
||||
import { ConflictError, NotFoundError, ValidationError } from '@/lib/errors';
|
||||
|
||||
const CODE_TTL_SECONDS = 10 * 60;
|
||||
@@ -203,6 +205,14 @@ export async function hardDeleteClient(args: {
|
||||
.update(documentSends)
|
||||
.set({ clientId: null })
|
||||
.where(eq(documentSends.clientId, args.clientId));
|
||||
// G-C2: scratchpad_notes.linked_client_id is RESTRICT (default for no
|
||||
// onDelete clause). Any rep who linked a scratchpad note to this client
|
||||
// would otherwise throw an FK violation when we try to delete the
|
||||
// client row below. Nullify so the note survives the hard-delete.
|
||||
await tx
|
||||
.update(scratchpadNotes)
|
||||
.set({ linkedClientId: null })
|
||||
.where(eq(scratchpadNotes.linkedClientId, args.clientId));
|
||||
|
||||
// client_merge_log.surviving_client_id has no cascade and is
|
||||
// notNull → must be deleted explicitly. Merged records remain in
|
||||
@@ -218,6 +228,18 @@ export async function hardDeleteClient(args: {
|
||||
await tx.delete(clients).where(eq(clients.id, args.clientId));
|
||||
});
|
||||
|
||||
// G-C3 / A7: demote the system-managed folder so the partial unique
|
||||
// index `uniq_document_folders_entity` releases its slot. Done as a
|
||||
// post-commit fire-and-forget — folder hygiene is non-essential to the
|
||||
// delete being durable, and we don't want a folder-table glitch to
|
||||
// un-delete the client by aborting the outer transaction.
|
||||
void demoteSystemFolderOnEntityDelete(args.portId, 'client', args.clientId).catch((err) => {
|
||||
logger.error(
|
||||
{ err, clientId: args.clientId, portId: args.portId },
|
||||
'hardDeleteClient: failed to demote system folder',
|
||||
);
|
||||
});
|
||||
|
||||
void createAuditLog({
|
||||
portId: args.portId,
|
||||
userId: args.requesterUserId,
|
||||
|
||||
Reference in New Issue
Block a user