feat(db): tighten chk_system_folder_shape, add recommender FK + composite indexes

- Fix A5: chk_system_folder_shape NULL escape
- Fix Audit 17 G-I4: berthRecommendations.interestId FK with cascade
- Add (port_id, client_id) / (port_id, company_id) / (port_id, yacht_id) composite indexes on files + documents for aggregated-projection performance

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-11 13:47:52 +02:00
parent 0804944647
commit 1b00c8a7a2
3 changed files with 110 additions and 1 deletions

View File

@@ -14,6 +14,7 @@ import {
import { ports } from './ports';
import { clients } from './clients';
import { yachts } from './yachts';
import { interests } from './interests';
export const berths = pgTable(
'berths',
@@ -131,7 +132,9 @@ export const berthRecommendations = pgTable(
id: text('id')
.primaryKey()
.$defaultFn(() => crypto.randomUUID()),
interestId: text('interest_id').notNull(), // references interests.id
interestId: text('interest_id')
.notNull()
.references(() => interests.id, { onDelete: 'cascade' }),
berthId: text('berth_id')
.notNull()
.references(() => berths.id, { onDelete: 'cascade' }),

View File

@@ -50,6 +50,12 @@ export const files = pgTable(
index('idx_files_company').on(table.companyId),
index('idx_files_folder').on(table.folderId),
index('idx_files_port_folder').on(table.portId, table.folderId),
// Composite indexes for the aggregated-projection queries
// (`listFilesAggregatedByEntity`) — every join carries a defense-in-
// depth `port_id` filter so the leading column matters at scale.
index('idx_files_port_client').on(table.portId, table.clientId),
index('idx_files_port_company').on(table.portId, table.companyId),
index('idx_files_port_yacht').on(table.portId, table.yachtId),
],
);
@@ -109,6 +115,12 @@ export const documents = pgTable(
index('idx_docs_file_id').on(table.fileId),
index('idx_docs_signed_file_id').on(table.signedFileId),
index('idx_docs_folder').on(table.folderId),
// Composite indexes for the aggregated-projection queries
// (`listInflightWorkflowsAggregatedByEntity`) — every join carries a
// defense-in-depth `port_id` filter so the leading column matters at scale.
index('idx_docs_port_client').on(table.portId, table.clientId),
index('idx_docs_port_company').on(table.portId, table.companyId),
index('idx_docs_port_yacht').on(table.portId, table.yachtId),
],
);