Current state:
- No health check endpoint exists.
AppControlleronly hasGET /returning "Hello World!". - BullMQ is configured in
app.module.tsviaBullModule.forRoot()with a Redis connection. - The
clip-generationqueue is registered inclips.module.tsandjobs.module.ts. RedisServiceexists with aping()method.ClipsService.refreshQueueDepth()already callsclipQueue.getJobCounts().
Implementation plan:
- Create
src/health/health.module.ts,src/health/health.service.ts, andsrc/health/health.controller.ts. HealthServicewill:- Ping Redis via
RedisService.getClient(). - Call
getJobCounts('active', 'waiting', 'failed')on theclip-generationqueue obtained fromBullModule.registerQueueor by injecting the queue. - Return status for each queue with active/waiting/failed counts.
- Ping Redis via
HealthControllerwill expose:GET /health— overall health (Redis + queue status).GET /health/queues— detailed per-queue status.
- Register
HealthModuleinAppModule. - Integrate
/healthinto the main health check flow (the root/healthendpoint aggregates Redis + queue results).
Current state:
ClipGenerationProcessorhas@OnWorkerEvent('completed')and@OnWorkerEvent('failed')handlers.- On completion,
onCompleted()updates the clip in Prisma and emits WebSocket progress. - On failure,
CLIP_GENERATION_FAILED_EVENTis emitted, handled byClipsService.handleClipGenerationFailed()which updates Video status tofailed. MailServiceexists with templated email methods but nothing for clip completion.EmailDeliveryServiceexists to enqueue emails via BullMQ.- No notification on success currently exists.
Implementation plan:
- Create a new event
CLIP_GENERATION_COMPLETED_EVENTinsrc/clips/clips.events.tswith payload{ videoId, clipId, clipUrl, userId }. - Emit
CLIP_GENERATION_COMPLETED_EVENTinClipGenerationProcessor.onCompleted()after the DB update succeeds. - Create
src/notifications/notifications.service.tsandsrc/notifications/notifications.module.ts:- Listen for
CLIP_GENERATION_COMPLETED_EVENTvia@OnEvent(). - Fetch the user's email from Prisma using
video.userId. - Fetch the video title.
- Send in-app notification via
ClipsGatewayto the user's room (user:${userId}). - Send email via
EmailDeliveryService.enqueue()with a "clip-ready" template containing the clips preview link.
- Listen for
- Add the preview link to the email:
${process.env.APP_BASE_URL}/videos/${videoId}. - Register
NotificationsModuleinAppModule.
Current state:
- Redis is configured in
app.module.tsviaBullModule.forRoot()with connection host/port. RedisServiceconnects usingioredisbut no persistence config is present.- No
docker-compose.ymlexists — Redis runs externally or via a separate setup. .env.examplecontains Redis host/port/password but no persistence settings.
Implementation plan:
- Update
app.module.tsBullModule.forRoot()to include explicitmaxRetriesPerRequestand keep the connection config. - Update
RedisServiceconstructor to accept optional persistence-related options if needed (e.g., lazyConnect stays true). - Create a
docker-compose.ymlwith a Redis service:- Image:
redis:7-alpine - Volumes:
redis_data:/data - Command:
redis-server --appendonly yes --save 60 1 --save 300 10 - This enables AOF (
appendonly yes) and RDB snapshots (save at 60s/1 key and 300s/10 keys).
- Image:
- Document the recovery process in a new
docs/redis-recovery.md:- Steps to restore from AOF/RDB.
- How to verify data integrity.
- How to handle failed persistence checks.
- Update
.env.examplewith persistence-related comments if needed.
Current state:
clip-generation.queue.tsdefinesCLIP_JOB_OPTIONSwithattempts: 3and exponential backoff.- After all retries, BullMQ moves jobs to the failed set automatically.
jobs.controller.tshasGET /jobs/failedandPOST /jobs/retry/:jobId.jobs.service.tsimplementsgetFailedJobs()andretryJob()usingclipQueue.getFailed().- No explicit dead letter queue configuration exists (no
removeOnFail: falseon the job options, no separate DLQ queue). - BullMQ natively keeps failed jobs in the failed set, but there is no dedicated DLQ queue for manual review.
Implementation plan:
- In
clip-generation.queue.ts, add a dead letter queue constant:DEAD_LETTER_QUEUE = 'clip-generation-dlq'. - Update
CLIP_JOB_OPTIONSto includeremoveOnFail: falseso failed jobs are retained for manual review. - In
jobs.module.ts, register the DLQ viaBullModule.registerQueue({ name: DEAD_LETTER_QUEUE }). - In
ClipGenerationProcessor.onFailed(), after handling the final failure, move the failed job to the DLQ:- Retrieve the failed job from the main queue.
- Add it to the DLQ with the same data +
failedReason,attemptsMade,finishedOn. - Optionally remove it from the main queue's failed set to avoid duplication.
- In
JobsService, add:getDeadLetterJobs()— reads from the DLQ and returns job details.retryDeadLetterJob(jobId)— moves a job from DLQ back to the main queue for re-processing.
- In
JobsController, add:GET /jobs/dead-letter— list dead letter jobs.POST /jobs/dead-letter/retry/:jobId— retry a dead letter job.