Files
portfolio/docs/sprint-artifacts/3-1-create-pdf-generation-api-route.md
T
mahdiarghyani 43ad1bb5be feat(pdf): complete Epic 3 - PDF Export functionality
Story 3-1: Create PDF Generation API Route
- Add server/api/resume/pdf.get.ts with Puppeteer integration
- Support both dev (puppeteer) and prod (puppeteer-core + chromium)
- Navigate to /resume?print=true for WYSIWYG capture
- Return PDF with proper headers (Content-Type, Content-Disposition)
- Add error handling with 500 status and JSON response
- Update vercel.json with function config (memory: 1024, maxDuration: 10)

Story 3-2: Create PDF Download Composable
- Add app/composables/useResumePdf.ts
- Implement isGenerating ref for loading state
- Implement downloadPdf() with blob handling
- Add toast notifications for errors
- Revoke object URL to prevent memory leaks

Story 3-3: Connect Download Button to PDF Generation
- Update ResumeDownloadButton.vue to use useResumePdf()
- Bind :loading and :disabled to isGenerating
- Connect @click to downloadPdf
- Remove placeholder handler

Dependencies Added:
- puppeteer ^24.31.0
- puppeteer-core ^24.31.0
- @sparticuz/chromium ^141.0.0

Closes Epic 3
Closes Story 3-1, 3-2, 3-3
2025-12-01 12:20:30 +03:30

176 lines
4.5 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Story 3.1: Create PDF Generation API Route
Status: done
## Story
As a system,
I want a server endpoint that generates PDF from the resume page,
so that users get consistent, high-quality PDF output.
## Acceptance Criteria
1. **AC1:** Given a GET request to `/api/resume/pdf`, when the server processes the request, then it returns a PDF binary with Content-Type `application/pdf`
2. **AC2:** Response includes `Content-Disposition: attachment; filename="Ali_Arghyani_Resume.pdf"`
3. **AC3:** PDF matches the web preview exactly (WYSIWYG)
4. **AC4:** PDF text is selectable and copy-able (ATS-compatible)
5. **AC5:** PDF is A4 format (210mm × 297mm)
6. **AC6:** PDF generation completes in under 3 seconds
7. **AC7:** Given an error occurs, when caught, then it returns status 500 with JSON error message
8. **AC8:** Timeout is set to 10 seconds max
## Tasks / Subtasks
- [ ] Create API route file (AC: #1, #2)
- [ ] Create `server/api/resume/pdf.get.ts`
- [ ] Set up Nuxt event handler with `defineEventHandler`
- [ ] Configure response headers (Content-Type, Content-Disposition)
- [ ] Implement Puppeteer PDF generation (AC: #3-#6)
- [ ] Import puppeteer (dev) or puppeteer-core + chromium (prod)
- [ ] Get base URL from request headers
- [ ] Launch browser in headless mode
- [ ] Navigate to `/resume?print=true`
- [ ] Wait for `networkidle0` (fonts loaded)
- [ ] Generate PDF with options: format A4, printBackground true
- [ ] Close browser in finally block
- [ ] Return PDF buffer
- [ ] Add error handling (AC: #7, #8)
- [ ] Wrap in try-catch block
- [ ] Set timeout to 10 seconds
- [ ] Return 500 status with error JSON on failure
- [ ] Log errors to console
- [ ] Configure for Vercel deployment
- [ ] Use environment detection for puppeteer vs puppeteer-core
- [ ] Import @sparticuz/chromium for production
- [ ] Update vercel.json with function config (memory: 1024, maxDuration: 10)
- [ ] Test API endpoint
- [ ] Test locally with `curl` or browser
- [ ] Verify PDF opens correctly
- [ ] Check text is selectable
- [ ] Measure generation time
- [ ] Test error handling
## Dev Notes
### Architecture Alignment
**From Architecture Doc:**
- File location: `server/api/resume/pdf.get.ts`
- Uses Puppeteer server-side (ADR-001)
- Navigates to `/resume?print=true` for WYSIWYG
- Returns PDF buffer with proper headers
**From Tech Spec Epic 3:**
- AC1-AC4 map to this story
- Performance target: < 3 seconds
- Memory limit: 1024MB on Vercel
### Implementation Notes
**Local Development (puppeteer with bundled Chromium):**
```typescript
import puppeteer from 'puppeteer'
const browser = await puppeteer.launch({
headless: true
})
```
**Production (Vercel with @sparticuz/chromium):**
```typescript
import puppeteer from 'puppeteer-core'
import chromium from '@sparticuz/chromium'
const browser = await puppeteer.launch({
args: chromium.args,
executablePath: await chromium.executablePath(),
headless: chromium.headless
})
```
**PDF Generation Options:**
```typescript
const pdf = await page.pdf({
format: 'A4',
printBackground: true,
margin: { top: 0, right: 0, bottom: 0, left: 0 }
})
```
**Response Headers:**
```typescript
setResponseHeaders(event, {
'Content-Type': 'application/pdf',
'Content-Disposition': 'attachment; filename="Ali_Arghyani_Resume.pdf"'
})
```
### Dependencies
**Install:**
```bash
pnpm add puppeteer puppeteer-core @sparticuz/chromium
```
**vercel.json:**
```json
{
"functions": {
"server/api/resume/pdf.get.ts": {
"memory": 1024,
"maxDuration": 10
}
}
}
```
### Testing Checklist
- [ ] API returns PDF binary
- [ ] Content-Type is application/pdf
- [ ] Content-Disposition has correct filename
- [ ] PDF opens in viewer
- [ ] Text is selectable in PDF
- [ ] Colors are correct (blue headers)
- [ ] Generation time < 3 seconds
- [ ] Error returns 500 with JSON
- [ ] Works on Vercel deployment
### References
- [Source: docs/architecture.md#API-Contracts]
- [Source: docs/architecture.md#Novel-Pattern-WYSIWYG-PDF-Export]
- [Source: docs/architecture.md#Deployment-Architecture]
- [Source: docs/sprint-artifacts/tech-spec-epic-3.md#AC1-AC4]
## Dev Agent Record
### Context Reference
<!-- Will be filled by SM agent -->
### Agent Model Used
<!-- Will be filled by dev agent -->
### Debug Log References
<!-- Will be filled by dev agent during implementation -->
### Completion Notes List
<!-- Will be filled by dev agent after completion -->
### File List
<!-- Will be filled by dev agent with created/modified files -->
---
**Change Log:**
- 2025-12-01: Story drafted by SM agent (Bob)