Skip to main content

PDF Template System - Immediate Action Plan

Created: October 20, 2025
Status: Core Backend Complete, Production Blockers Identified
Target: Production-Ready in 10-14 hours


🎯 Executive Summary​

The PDF Template System is 89% complete with all core services operational. However, there are 4 critical blockers that must be addressed before production deployment.

Bottom Line:

  • βœ… System compiles successfully
  • βœ… Core functionality works
  • πŸ”΄ Authentication not enforced (CRITICAL SECURITY ISSUE)
  • 🟑 Async processing not configured (FUNCTIONAL ISSUE)

🚨 Critical Blockers (Must Fix Before Deployment)​

1. πŸ”΄ CRITICAL: Add Authentication Guards​

Time: 30 minutes
Priority: P0 (BLOCKING)

Problem:

// Current: ANYONE can upload, modify, or delete templates!
@Controller("pdf/templates")
export class TemplateController {
@Post("upload") // ❌ NO AUTH
async uploadTemplate(...) { ... }
}

Fix:

// Required: Enforce authentication
@Controller("pdf/templates")
@UseGuards(AuthGuard) // βœ… Add this
export class TemplateController {

@Post("upload") // βœ… Now protected
async uploadTemplate(...) { ... }

@IsPublic() // Keep public only for monitoring
@Get("health")
async healthCheck(...) { ... }
}

Files to modify:

  • apps/backend/src/pdf/infrastructure/controllers/template.controller.ts

Steps:

  1. Add @UseGuards(AuthGuard) decorator to TemplateController class (line 40)
  2. Keep @IsPublic() only on health and cache/stats endpoints
  3. Verify @FirebaseUser() decorator is used correctly in all methods
  4. Test with real authentication token

Testing:

# Should fail without token
curl -X POST http://localhost:4000/pdf/templates/upload

# Should succeed with token
curl -X POST http://localhost:4000/pdf/templates/upload \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{"name":"Test","documentType":"sale",...}'

2. 🟑 HIGH: Configure BullMQ for Async Preview Generation​

Time: 4-6 hours
Priority: P1 (HIGH)

Problem:

// Current: Preview generation is commented out
// await this.previewQueue.add('generate-preview', {...}); // ❌ TODO

Dependencies:

  • βœ… @nestjs/bull installed (v11.0.4)
  • βœ… bullmq installed (v5.61.0)
  • ❌ Redis not configured
  • ❌ Queue not registered
  • ❌ Processor not implemented

Fix Steps:

Step 1: Update PdfModule (10 minutes)​

File: apps/backend/src/pdf/pdf.module.ts

import { BullModule } from '@nestjs/bull';

@Module({
imports: [
DatabaseModule,
BullModule.forRoot({
redis: {
host: process.env.REDIS_HOST || 'localhost',
port: parseInt(process.env.REDIS_PORT || '6379', 10),
password: process.env.REDIS_PASSWORD,
},
}),
BullModule.registerQueue({
name: 'preview-generation',
defaultJobOptions: {
attempts: 3,
backoff: {
type: 'exponential',
delay: 2000,
},
removeOnComplete: 100,
removeOnFail: 500,
},
}),
],
// ... rest of module
})

Step 2: Create PreviewGenerationProcessor (2-3 hours)​

File: apps/backend/src/pdf/infrastructure/queues/preview-generation.processor.ts

import { Process, Processor } from '@nestjs/bull';
import { Job } from 'bull';
import { Injectable, Logger } from '@nestjs/common';
import { TemplateRepository } from '../repositories/template.repository';
import * as handlebars from 'handlebars';

@Processor('preview-generation')
@Injectable()
export class PreviewGenerationProcessor {
private readonly logger = new Logger(PreviewGenerationProcessor.name);

constructor(
private readonly templateRepository: TemplateRepository,
// TODO: Add PdfGenerator and StorageService
) {}

@Process('generate-preview')
async handlePreviewGeneration(job: Job<{ templateId: string; documentType: string }>) {
const { templateId, documentType } = job.data;

try {
this.logger.log(`Generating preview for template ${templateId}`);

// 1. Update status to processing
await this.templateRepository.update(templateId, {
previewStatus: 'processing',
});

// 2. Fetch template
const template = await this.templateRepository.findById(templateId);
if (!template) {
throw new Error(`Template ${templateId} not found`);
}

// 3. Generate sample data
const sampleData = this.generateSampleData(documentType);

// 4. Compile and render
const compiledTemplate = handlebars.compile(template.htmlTemplate);
const html = compiledTemplate(sampleData);

// 5. TODO: Generate PDF and screenshot
// const pdfBuffer = await this.pdfGenerator.generate(html, pdfOptions);
// const screenshotBuffer = await this.generateScreenshot(html);

// 6. TODO: Upload to storage
// const previewUrl = await this.storageService.uploadImage(...);

// 7. Update template with preview URL
await this.templateRepository.update(templateId, {
previewStatus: 'completed',
// previewImageUrl: previewUrl,
});

this.logger.log(`Preview generated successfully for template ${templateId}`);
} catch (error) {
this.logger.error(`Preview generation failed for template ${templateId}:`, error);

await this.templateRepository.update(templateId, {
previewStatus: 'failed',
});

throw error;
}
}

private generateSampleData(documentType: string): any {
const sampleDataMap = {
sale: {
documentNumber: 'SALE-001',
saleDate: new Date().toISOString(),
customer: { name: 'John Doe', email: 'john@example.com' },
items: [
{ name: 'Product A', quantity: 2, price: 100, total: 200 },
{ name: 'Product B', quantity: 1, price: 50, total: 50 },
],
subtotal: 250,
tax: 25,
totalAmount: 275,
},
// Add other document types
};
return sampleDataMap[documentType] || {};
}
}

Step 3: Update UploadTemplateUseCase (15 minutes)​

File: apps/backend/src/pdf/application/use-cases/upload-template.use-case.ts

import { InjectQueue } from '@nestjs/bull';
import { Queue } from 'bull';

@Injectable()
export class UploadTemplateUseCase {
constructor(
// ... existing dependencies
@InjectQueue('preview-generation') private readonly previewQueue: Queue,
) {}

async execute(dto: UploadTemplateDto, userContext: UserContext) {
// ... existing code ...

// 4. Queue preview generation asynchronously
await this.previewQueue.add('generate-preview', {
templateId: template.id,
documentType: template.documentType,
});

// ... rest of method
}
}

Step 4: Add Environment Variables (5 minutes)​

File: .env or Doppler

REDIS_HOST=localhost
REDIS_PORT=6379
REDIS_PASSWORD=

Step 5: Update Docker Compose (if using Docker)​

File: docker-compose.yml

services:
redis:
image: redis:7-alpine
ports:
- "6379:6379"
volumes:
- redis-data:/data

volumes:
redis-data:

Step 6: Test​

# Start Redis
docker-compose up -d redis

# Or use local Redis
redis-server

# Upload a template and check queue
curl -X POST http://localhost:4000/pdf/templates/upload \
-H "Authorization: Bearer YOUR_TOKEN" \
-d '{"name":"Test Template",...}'

# Check preview status
curl http://localhost:4000/pdf/templates/{id} \
-H "Authorization: Bearer YOUR_TOKEN"

3. 🟑 MEDIUM: Add Rate Limiting​

Time: 1-2 hours
Priority: P2 (MEDIUM)

Problem: No protection against DoS attacks via template upload or PDF generation spam.

Fix:

Step 1: Install Throttler (2 minutes)​

cd apps/backend
pnpm add @nestjs/throttler

Step 2: Configure in AppModule (5 minutes)​

File: apps/backend/src/app.module.ts

import { ThrottlerModule, ThrottlerGuard } from '@nestjs/throttler';
import { APP_GUARD } from '@nestjs/core';

@Module({
imports: [
// ... existing imports
ThrottlerModule.forRoot([{
name: 'short',
ttl: 1000,
limit: 3,
}, {
name: 'medium',
ttl: 10000,
limit: 20,
}, {
name: 'long',
ttl: 60000,
limit: 100,
}]),
],
providers: [
// ... existing providers
{
provide: APP_GUARD,
useClass: ThrottlerGuard,
},
],
})

Step 3: Add Throttle to Controller (10 minutes)​

File: apps/backend/src/pdf/infrastructure/controllers/template.controller.ts

import { Throttle, SkipThrottle } from '@nestjs/throttler';

@Controller("pdf/templates")
@UseGuards(AuthGuard)
export class TemplateController {

@Post("upload")
@Throttle({ short: { limit: 2, ttl: 1000 } }) // 2 per second
@Throttle({ long: { limit: 10, ttl: 60000 } }) // 10 per minute
async uploadTemplate(...) { ... }

@Post(":id/test")
@Throttle({ short: { limit: 5, ttl: 1000 } }) // 5 per second
@Throttle({ long: { limit: 100, ttl: 60000 } }) // 100 per minute
async testTemplate(...) { ... }

@SkipThrottle() // No rate limit on health check
@IsPublic()
@Get("health")
async healthCheck(...) { ... }
}

4. 🟒 LOW: Fix JSONB Handling​

Time: 10 minutes
Priority: P3 (LOW - Works but suboptimal)

Problem: Unnecessary JSON.stringify() calls for JSONB fields (Kysely auto-converts).

Fix:

File: apps/backend/src/pdf/infrastructure/repositories/template.repository.ts

// Before (lines 66-74)
pageConfig: data.pageConfig
? JSON.stringify(data.pageConfig) as any // ❌ Unnecessary
: null,
validationErrors: data.validationErrors
? JSON.stringify(data.validationErrors) as any // ❌ Unnecessary
: null,

// After
pageConfig: data.pageConfig || null, // βœ… Kysely handles it
validationErrors: data.validationErrors || null, // βœ… Kysely handles it

πŸ“‹ Phase 1 Complete Checklist​

After completing these 4 fixes, verify:

  • βœ… Authentication enforced on all protected endpoints
  • βœ… Upload endpoint returns 401 without token
  • βœ… Upload endpoint works with valid token
  • βœ… BullMQ queue processes jobs
  • βœ… Preview status updates to "processing" then "completed"
  • βœ… Rate limiting prevents spam
  • βœ… Health check remains public
  • βœ… Cache stats remain public
  • βœ… JSONB fields store/retrieve correctly
  • βœ… Audit trail captures user IDs correctly

πŸ§ͺ Testing Script​

#!/bin/bash

# Set your auth token
TOKEN="your-firebase-token-here"
BACKEND_URL="http://localhost:3000"

echo "=== Testing Authentication ==="

# Should fail (401)
curl -X POST $BACKEND_URL/pdf/templates/upload \
-H "Content-Type: application/json" \
-d '{"name":"Test"}'

# Should succeed
curl -X POST $BACKEND_URL/pdf/templates/upload \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"createdBy": "user-id",
"name": "Test Template",
"documentType": "sale",
"templateFormat": "standard_a4",
"htmlTemplate": "<!DOCTYPE html><html><body>{{documentNumber}}</body></html>"
}'

echo "=== Testing Rate Limiting ==="

# Should succeed (first request)
curl -X POST $BACKEND_URL/pdf/templates/upload \
-H "Authorization: Bearer $TOKEN" \
-d '...'

# Should fail after 10 requests in 1 minute (429)
for i in {1..15}; do
curl -X POST $BACKEND_URL/pdf/templates/upload \
-H "Authorization: Bearer $TOKEN" \
-d '...'
sleep 1
done

echo "=== Testing Public Endpoints ==="

# Should succeed without token
curl $BACKEND_URL/pdf/templates/health
curl $BACKEND_URL/pdf/templates/cache/stats

echo "=== Testing BullMQ ==="

# Upload template
TEMPLATE_ID=$(curl -X POST $BACKEND_URL/pdf/templates/upload \
-H "Authorization: Bearer $TOKEN" \
-d '...' | jq -r '.id')

# Check preview status (should be "pending")
curl $BACKEND_URL/pdf/templates/$TEMPLATE_ID \
-H "Authorization: Bearer $TOKEN"

# Wait 5 seconds
sleep 5

# Check preview status (should be "completed")
curl $BACKEND_URL/pdf/templates/$TEMPLATE_ID \
-H "Authorization: Bearer $TOKEN"

echo "βœ… All tests completed!"

πŸ“Š Time Estimates​

TaskTimePriority
Add Authentication30 minP0 (CRITICAL)
Configure BullMQ4-6 hoursP1 (HIGH)
Add Rate Limiting1-2 hoursP2 (MEDIUM)
Fix JSONB Handling10 minP3 (LOW)
Testing & Verification2-3 hoursP0 (CRITICAL)
TOTAL10-14 hours

🎯 Success Criteria​

Phase 1 is complete when:

  1. βœ… All template endpoints require authentication
  2. βœ… Audit trail captures real user IDs
  3. βœ… Template upload triggers async preview generation
  4. βœ… Preview status updates correctly (pending β†’ processing β†’ completed)
  5. βœ… Rate limiting prevents abuse
  6. βœ… Health check and cache stats remain public
  7. βœ… All tests pass
  8. βœ… No security vulnerabilities

πŸš€ Next Steps (Phase 2+)​

After Phase 1 is complete:

  1. Phase 2: Location Configuration (6-8 hours)

    • Create LocationTemplateConfigRepository
    • Create LocationTemplateConfigController
    • Update TemplateResolverService
  2. Phase 3: Integration (12-18 hours)

    • Integrate with existing PDF generation use cases
    • Add template override parameters
    • Add monitoring and metrics
  3. Phase 4: Testing & Polish (22-32 hours)

    • Unit tests
    • Integration tests
    • Enhanced features (duplication, rollback, analytics)

πŸ“ž Support & Resources​

  • Architecture: docs/pdf-template/pdf-template-configuration-system.md
  • Implementation Status: docs/pdf-template/BACKEND-IMPLEMENTATION-STATUS.md
  • Checklist: docs/pdf-template/pdf-template-implementation-checklist.md
  • BullMQ Guide: docs/pdf-template/REDIS-BULLMQ-SETUP.md

Remember: The system is 89% complete. Focus on Phase 1 (authentication, BullMQ, rate limiting) to make it production-ready. The rest can be incremental improvements.