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:
- Add
@UseGuards(AuthGuard)decorator toTemplateControllerclass (line 40) - Keep
@IsPublic()only onhealthandcache/statsendpoints - Verify
@FirebaseUser()decorator is used correctly in all methods - 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/bullinstalled (v11.0.4) - β
bullmqinstalled (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β
| Task | Time | Priority |
|---|---|---|
| Add Authentication | 30 min | P0 (CRITICAL) |
| Configure BullMQ | 4-6 hours | P1 (HIGH) |
| Add Rate Limiting | 1-2 hours | P2 (MEDIUM) |
| Fix JSONB Handling | 10 min | P3 (LOW) |
| Testing & Verification | 2-3 hours | P0 (CRITICAL) |
| TOTAL | 10-14 hours |
π― Success Criteriaβ
Phase 1 is complete when:
- β All template endpoints require authentication
- β Audit trail captures real user IDs
- β Template upload triggers async preview generation
- β Preview status updates correctly (pending β processing β completed)
- β Rate limiting prevents abuse
- β Health check and cache stats remain public
- β All tests pass
- β No security vulnerabilities
π Next Steps (Phase 2+)β
After Phase 1 is complete:
-
Phase 2: Location Configuration (6-8 hours)
- Create
LocationTemplateConfigRepository - Create
LocationTemplateConfigController - Update
TemplateResolverService
- Create
-
Phase 3: Integration (12-18 hours)
- Integrate with existing PDF generation use cases
- Add template override parameters
- Add monitoring and metrics
-
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.