feat(resume): complete Epic 4 - Resume Content Strategy and Guidelines

- Update resume data with comprehensive professional summary and optimized work experience highlights
- Add resume content tech-spec and strategy documentation for 2025 best practices
- Create resume guidelines reference document for content consistency
- Enhance PDF composable with openPdf preview function alongside downloadPdf
- Expand CSS banner hiding rules to block certificates.dev promotional content
- Add research documentation on resume best practices and ATS optimization
- Update i18n translations for resume content in English and Persian
- Include sample Ali Arghyani resume PDF template in design assets
- Refactor resume.en.ts with improved professional positioning and achievement metrics
- Update API route documentation for PDF generation story artifacts
- Establish foundation for Epic 4 sprint planning with tech specifications
This commit is contained in:
mahdiarghyani
2025-12-02 12:54:26 +03:30
parent 43ad1bb5be
commit 9c40914d6a
20 changed files with 3889 additions and 123 deletions
+21 -28
View File
@@ -5,24 +5,25 @@ export default defineEventHandler(async (event) => {
let browser: Browser | null = null
try {
// Get base URL from request
const requestUrl = getRequestURL(event)
const baseUrl = `${requestUrl.protocol}//${requestUrl.host}`
const host = requestUrl.host.includes('192.168') || requestUrl.host.includes('localhost')
? 'localhost:5000'
: requestUrl.host
const baseUrl = `http://${host}`
const resumeUrl = `${baseUrl}/resume?print=true`
// Launch browser
console.log('[PDF API] Generating from:', resumeUrl)
const isDev = process.env.NODE_ENV === 'development'
if (isDev) {
// Development: use puppeteer with bundled Chromium
browser = await puppeteer.launch({
headless: true,
args: ['--no-sandbox', '--disable-setuid-sandbox'],
})
} else {
// Production: use puppeteer-core with @sparticuz/chromium
const chromium = await import('@sparticuz/chromium')
const puppeteerCore = await import('puppeteer-core')
browser = await puppeteerCore.default.launch({
args: chromium.default.args,
executablePath: await chromium.default.executablePath(),
@@ -30,51 +31,43 @@ export default defineEventHandler(async (event) => {
})
}
// Create page and navigate
const page = await browser.newPage()
await page.setViewport({ width: 794, height: 1123 })
// Set viewport to A4 dimensions
await page.setViewport({
width: 794, // A4 width in pixels at 96 DPI
height: 1123, // A4 height in pixels at 96 DPI
})
// Navigate to resume page with print mode
await page.goto(resumeUrl, {
const response = await page.goto(resumeUrl, {
waitUntil: 'networkidle0',
timeout: 10000,
timeout: 30000,
})
// Generate PDF
if (!response || !response.ok()) {
throw new Error(`Failed to load: ${response?.status()}`)
}
const pdf = await page.pdf({
format: 'A4',
printBackground: true,
margin: {
top: 0,
right: 0,
bottom: 0,
left: 0,
},
margin: { top: 0, right: 0, bottom: 0, left: 0 },
})
// Set response headers (AC1, AC2)
const query = getQuery(event)
const filename = (query.filename as string) || 'Ali_Arghyani_Resume.pdf'
const download = query.download === 'true'
setResponseHeaders(event, {
'Content-Type': 'application/pdf',
'Content-Disposition': 'attachment; filename="Ali_Arghyani_Resume.pdf"',
// inline = show in browser, attachment = force download
'Content-Disposition': `${download ? 'attachment' : 'inline'}; filename="${filename}"`,
})
return pdf
} catch (error) {
// Error handling (AC7)
console.error('PDF generation failed:', error)
setResponseStatus(event, 500)
return {
error: 'PDF generation failed',
message: error instanceof Error ? error.message : 'Unknown error',
}
} finally {
// Always close browser to prevent memory leaks
if (browser) {
await browser.close()
}