audit: production safety fixes, cleanup, and documentation overhaul
CRITICAL fixes: - Fix SQL injection in geoportal search (template literal in $queryRaw) - Preserve enrichment data during GIS re-sync (upsert update explicit fields only) - Fix ePay version race condition (advisory lock in transaction) - Add requireAuth() to compress-pdf and unlock routes (were unauthenticated) - Remove hardcoded Stirling PDF API key (env vars now required) IMPORTANT fixes: - Add admin role check on registratura debug-sequences endpoint - Fix reserved slot race condition with advisory lock in transaction - Use SSO identity in close-guard-dialog instead of hardcoded "Utilizator" - Storage DELETE catches only P2025 (not found), re-throws real errors - Add onDelete: SetNull for GisFeature → GisSyncRun relation - Move portal-only users to PORTAL_ONLY_USERS env var - Add security headers (X-Frame-Options, X-Content-Type-Options, Referrer-Policy) - Add periodic cleanup for eTerra/ePay session caches and progress store - Log warning when ePay dataDocument is missing (expiry fallback) Cleanup: - Delete orphaned rgi-test page (1086 lines, unregistered, inaccessible) - Delete legacy/ folder (5 files, unreferenced from src/) - Remove unused ensureBucketExists() from minio-client.ts Documentation: - Optimize CLAUDE.md: 464 → 197 lines (moved per-module details to docs/) - Create docs/ARCHITECTURE-QUICK.md (80 lines: data flow, deps, env vars) - Create docs/MODULE-MAP.md (140 lines: entry points, API routes, cross-deps) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -213,27 +213,33 @@ export async function POST(req: NextRequest) {
|
||||
let claimedSlotId: string | undefined;
|
||||
|
||||
if (isPastMonth && direction === "intrat") {
|
||||
// Try to claim a reserved slot
|
||||
const allEntries = await loadAllEntries(true);
|
||||
const slot = findAvailableReservedSlot(
|
||||
allEntries,
|
||||
company,
|
||||
docDate.getFullYear(),
|
||||
docDate.getMonth(),
|
||||
);
|
||||
// Try to claim a reserved slot — use advisory lock to prevent concurrent claims
|
||||
const lockKey = `reserved:${company}-${docDate.getFullYear()}-${docDate.getMonth()}`;
|
||||
const claimed = await prisma.$transaction(async (tx) => {
|
||||
await tx.$executeRaw`SELECT pg_advisory_xact_lock(hashtext(${lockKey}))`;
|
||||
const allEntries = await loadAllEntries(true);
|
||||
const slot = findAvailableReservedSlot(
|
||||
allEntries,
|
||||
company,
|
||||
docDate.getFullYear(),
|
||||
docDate.getMonth(),
|
||||
);
|
||||
if (!slot) return null;
|
||||
// Delete the placeholder slot within the lock
|
||||
await tx.keyValueStore.delete({
|
||||
where: { namespace_key: { namespace: "registratura", key: slot.id } },
|
||||
});
|
||||
return slot;
|
||||
});
|
||||
|
||||
if (slot) {
|
||||
// Claim the reserved slot — reuse its number
|
||||
registryNumber = slot.number;
|
||||
if (claimed) {
|
||||
registryNumber = claimed.number;
|
||||
registrationType = "reserved-claimed";
|
||||
claimedSlotId = slot.id;
|
||||
|
||||
// Delete the placeholder slot
|
||||
await deleteEntryFromDB(slot.id);
|
||||
claimedSlotId = claimed.id;
|
||||
|
||||
await logAuditEvent({
|
||||
entryId: slot.id,
|
||||
entryNumber: slot.number,
|
||||
entryId: claimed.id,
|
||||
entryNumber: claimed.number,
|
||||
action: "reserved_claimed",
|
||||
actor: actor.id,
|
||||
actorName: actor.name,
|
||||
|
||||
Reference in New Issue
Block a user