Initial commit: Port Nimara CRM (Layers 0-4)
Full CRM rebuild with Next.js 15, TypeScript, Tailwind, Drizzle ORM,
PostgreSQL, Redis, BullMQ, MinIO, and Socket.io. Includes 461 source
files covering clients, berths, interests/pipeline, documents/EOI,
expenses/invoices, email, notifications, dashboard, admin, and
client portal. CI/CD via Gitea Actions with Docker builds.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 11:52:51 +01:00
import { Worker , type Job } from 'bullmq' ;
fix(ops): /health DB+Redis checks, validated env.REDIS_URL across workers, error_events 90d retention
Three audit-pass-#3 findings, all in the "wakes you at 3am" category.
- /api/public/health now runs DB SELECT 1 + Redis PING in parallel and
returns 503 + a degraded payload when either fails. Anonymous probes
(no X-Intake-Secret) still get a flat {status:'ok'} so generic uptime
monitors keep working; authenticated probes see the dep results.
- All worker entrypoints (ai, bulk, documents, email, export, import,
maintenance, notifications, reports, webhooks) and src/lib/redis.ts
now use env.REDIS_URL (Zod-validated at boot) instead of
process.env.REDIS_URL!. Previously a missing env let the app start
silently and fail at first job pickup.
- maintenance worker gains an `error-events-retention` case that
delete()s rows older than 90 days from error_events. scheduler.ts
registers it at 06:00 daily. Closes the contract from migration
0040 which declared the table "pruned at 90 days" but had no
implementation.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 14:59:07 +02:00
import { env } from '@/lib/env' ;
Initial commit: Port Nimara CRM (Layers 0-4)
Full CRM rebuild with Next.js 15, TypeScript, Tailwind, Drizzle ORM,
PostgreSQL, Redis, BullMQ, MinIO, and Socket.io. Includes 461 source
files covering clients, berths, interests/pipeline, documents/EOI,
expenses/invoices, email, notifications, dashboard, admin, and
client portal. CI/CD via Gitea Actions with Docker builds.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 11:52:51 +01:00
import type { ConnectionOptions } from 'bullmq' ;
import { logger } from '@/lib/logger' ;
2026-05-06 20:44:38 +02:00
import { attachWorkerAudit } from '@/lib/queue/audit-helpers' ;
Initial commit: Port Nimara CRM (Layers 0-4)
Full CRM rebuild with Next.js 15, TypeScript, Tailwind, Drizzle ORM,
PostgreSQL, Redis, BullMQ, MinIO, and Socket.io. Includes 461 source
files covering clients, berths, interests/pipeline, documents/EOI,
expenses/invoices, email, notifications, dashboard, admin, and
client portal. CI/CD via Gitea Actions with Docker builds.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 11:52:51 +01:00
import { QUEUE_CONFIGS } from '@/lib/queue' ;
export const documentsWorker = new Worker (
'documents' ,
async ( job : Job ) = > {
logger . info ( { jobId : job.id , jobName : job.name } , 'Processing documents job' ) ;
switch ( job . name ) {
case 'signature-poll' : {
const { processDocumensoPoll } = await import ( '@/jobs/processors/documenso-poll' ) ;
await processDocumensoPoll ( ) ;
break ;
}
2026-05-06 19:12:55 +02:00
case 'documenso-void' : {
// Async cleanup of a Documenso envelope. Producers: smart-archive
// (when the operator opts to void in-flight envelopes during
// client archive). BullMQ retries with exponential backoff per
// QUEUE_CONFIGS; permanently-failed jobs land in the DLQ via
// the failed-job listener.
const { documentId , documensoId , portId } = job . data as {
documentId : string ;
documensoId : string ;
portId : string ;
} ;
if ( ! documensoId ) {
logger . warn ( { documentId } , 'documenso-void: no documensoId, skipping' ) ;
return ;
}
const { voidDocument } = await import ( '@/lib/services/documenso-client' ) ;
await voidDocument ( documensoId , portId ) ;
logger . info ( { documentId , documensoId , portId } , 'Documenso envelope voided' ) ;
break ;
}
Initial commit: Port Nimara CRM (Layers 0-4)
Full CRM rebuild with Next.js 15, TypeScript, Tailwind, Drizzle ORM,
PostgreSQL, Redis, BullMQ, MinIO, and Socket.io. Includes 461 source
files covering clients, berths, interests/pipeline, documents/EOI,
expenses/invoices, email, notifications, dashboard, admin, and
client portal. CI/CD via Gitea Actions with Docker builds.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 11:52:51 +01:00
default :
logger . warn ( { jobName : job.name } , 'Unknown documents job' ) ;
}
} ,
{
fix(ops): /health DB+Redis checks, validated env.REDIS_URL across workers, error_events 90d retention
Three audit-pass-#3 findings, all in the "wakes you at 3am" category.
- /api/public/health now runs DB SELECT 1 + Redis PING in parallel and
returns 503 + a degraded payload when either fails. Anonymous probes
(no X-Intake-Secret) still get a flat {status:'ok'} so generic uptime
monitors keep working; authenticated probes see the dep results.
- All worker entrypoints (ai, bulk, documents, email, export, import,
maintenance, notifications, reports, webhooks) and src/lib/redis.ts
now use env.REDIS_URL (Zod-validated at boot) instead of
process.env.REDIS_URL!. Previously a missing env let the app start
silently and fail at first job pickup.
- maintenance worker gains an `error-events-retention` case that
delete()s rows older than 90 days from error_events. scheduler.ts
registers it at 06:00 daily. Closes the contract from migration
0040 which declared the table "pruned at 90 days" but had no
implementation.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 14:59:07 +02:00
connection : { url : env.REDIS_URL } as ConnectionOptions ,
Initial commit: Port Nimara CRM (Layers 0-4)
Full CRM rebuild with Next.js 15, TypeScript, Tailwind, Drizzle ORM,
PostgreSQL, Redis, BullMQ, MinIO, and Socket.io. Includes 461 source
files covering clients, berths, interests/pipeline, documents/EOI,
expenses/invoices, email, notifications, dashboard, admin, and
client portal. CI/CD via Gitea Actions with Docker builds.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 11:52:51 +01:00
concurrency : QUEUE_CONFIGS.documents.concurrency ,
} ,
) ;
2026-05-06 22:31:52 +02:00
documentsWorker . on ( 'failed' , async ( job , err ) = > {
Initial commit: Port Nimara CRM (Layers 0-4)
Full CRM rebuild with Next.js 15, TypeScript, Tailwind, Drizzle ORM,
PostgreSQL, Redis, BullMQ, MinIO, and Socket.io. Includes 461 source
files covering clients, berths, interests/pipeline, documents/EOI,
expenses/invoices, email, notifications, dashboard, admin, and
client portal. CI/CD via Gitea Actions with Docker builds.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 11:52:51 +01:00
logger . error ( { jobId : job?.id , jobName : job?.name , err } , 'Documents job failed' ) ;
2026-05-06 22:31:52 +02:00
// Final-attempt failure on documenso-void → notify all super admins
// so they can void the envelope manually in Documenso. Without this
// alert hook, a persistent 401/403 from Documenso retries until
// BullMQ exhausts attempts and the failure disappears into the
// audit log unnoticed.
if ( job ? . name === 'documenso-void' && job . attemptsMade >= ( job . opts . attempts ? ? 1 ) ) {
try {
const { documentId , documensoId , portId } = ( job . data ? ? { } ) as {
documentId? : string ;
documensoId? : string ;
portId? : string ;
} ;
if ( ! documentId || ! documensoId ) return ;
const { db } = await import ( '@/lib/db' ) ;
const { userProfiles } = await import ( '@/lib/db/schema/users' ) ;
const { createNotification } = await import ( '@/lib/services/notifications.service' ) ;
const { eq , and } = await import ( 'drizzle-orm' ) ;
const superAdmins = await db
. select ( { userId : userProfiles.userId } )
. from ( userProfiles )
. where ( and ( eq ( userProfiles . isSuperAdmin , true ) , eq ( userProfiles . isActive , true ) ) ) ;
// createNotification requires a portId; if the job didn't carry
// one we can't tag the notification — bail out cleanly.
if ( ! portId ) return ;
for ( const admin of superAdmins ) {
void createNotification ( {
portId ,
userId : admin.userId ,
type : 'system_alert' ,
title : 'Documenso void failed' ,
description : ` Document ${ documentId . slice ( 0 , 8 ) } … could not be voided in Documenso after ${ job . attemptsMade } attempts. Void manually in Documenso if still active. ` ,
link : ` /admin/documents ` ,
entityType : 'document' ,
entityId : documentId ,
dedupeKey : ` doc:void_failed: ${ documentId } ` ,
cooldownMs : 0 ,
} ) ;
}
} catch ( notifyErr ) {
logger . error ( { notifyErr } , 'Failed to alert super-admins of documenso-void DLQ' ) ;
}
}
Initial commit: Port Nimara CRM (Layers 0-4)
Full CRM rebuild with Next.js 15, TypeScript, Tailwind, Drizzle ORM,
PostgreSQL, Redis, BullMQ, MinIO, and Socket.io. Includes 461 source
files covering clients, berths, interests/pipeline, documents/EOI,
expenses/invoices, email, notifications, dashboard, admin, and
client portal. CI/CD via Gitea Actions with Docker builds.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 11:52:51 +01:00
} ) ;
2026-05-06 20:44:38 +02:00
attachWorkerAudit ( documentsWorker , 'documents' ) ;