Saltar al contenido principal

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.