resume-server / src /services /pdfService.js
hari7775's picture
Deploy: Updated generator service (v1.0.3)
c89bb66
Raw History Blame Contribute Delete
3.8 kB
import { renderJakeTemplate } from '../../templateRenderer.js';
import { DOWNLOAD_CLEANUP_DELAY_MS } from '../config.js';
import { BadRequestError, ForbiddenError, NotFoundError } from '../exception/errors.js';
import { createJob, getJob, deleteJob, getJobPosition } from './jobStore.js';
import { generateContentHash, getCachedPdf } from './pdfCache.js';
import { queue, enqueueRenderJob } from './jobRunner.js';
import { recordPdfGeneration } from './usageService.js';
// A cached PDF is still a generation the user received, so it is counted too.
async function createCachedJob({ uid, metadata, usageDate }, cachedPdf) {
await recordPdfGeneration(uid, usageDate);
return createJob({
userId: uid,
status: 'completed',
metadata,
result: cachedPdf.buffer,
fromCache: true
});
}
// Submit a job from ready-made HTML
export async function submitHtmlJob({ uid, html, metadata, usageDate }) {
if (!html) throw new BadRequestError('HTML content is required');
const cacheKey = generateContentHash(html, metadata);
// RAM cache hit: instant PDF retrieval (0ms render time)
const cachedPdf = getCachedPdf(cacheKey);
if (cachedPdf) {
const job = await createCachedJob({ uid, metadata, usageDate }, cachedPdf);
console.log(`[RAM Cache Hit] Served cached PDF instantly for job ${job.id} (user: ${uid})`);
return { jobId: job.id, position: 0, cached: true };
}
const job = createJob({ userId: uid, status: 'queued', metadata, cacheKey, usageDate });
// If all workers are occupied, position is queue backlog (queue.size + 1)
const initialPosition = queue.pending >= queue.concurrency ? (queue.size + 1) : 0;
enqueueRenderJob(job, html, 'Job');
const position = initialPosition > 0 ? initialPosition : getJobPosition(job.id);
return { jobId: job.id, position, queueSize: queue.size, pending: queue.pending };
}
// Submit a job from resume JSON, rendered server-side with the template renderer
export async function submitResumeJsonJob({ uid, data, metadata, usageDate }) {
if (!data) throw new BadRequestError('Resume data is required');
const cacheKey = generateContentHash(JSON.stringify(data), metadata);
const jobMetadata = { ...metadata, filename: metadata?.filename || data.personal?.fullName || 'resume' };
const cachedPdf = getCachedPdf(cacheKey);
if (cachedPdf) {
const job = await createCachedJob({ uid, metadata: jobMetadata, usageDate }, cachedPdf);
console.log(`[RAM Cache Hit] Served cached JSON PDF instantly for job ${job.id} (user: ${uid})`);
return { jobId: job.id, position: 0, cached: true };
}
const html = renderJakeTemplate(data);
const job = createJob({ userId: uid, status: 'queued', metadata: jobMetadata, cacheKey, usageDate });
enqueueRenderJob(job, html, 'JSON Job');
return { jobId: job.id, position: getJobPosition(job.id) };
}
export function getJobStatus(jobId) {
const job = getJob(jobId);
if (!job) throw new NotFoundError('Job not found');
return {
status: job.status,
position: getJobPosition(job.id),
error: job.error
};
}
// Returns the finished PDF for its owner and schedules the job's cleanup
export function takePdfForDownload(jobId, uid) {
const job = getJob(jobId);
if (!job || job.status !== 'completed') throw new NotFoundError('PDF not ready or job not found');
// Ensure the authenticated requester owns this job
if (job.userId && job.userId !== uid) {
throw new ForbiddenError('You do not have permission to download this PDF.');
}
setTimeout(() => deleteJob(job.id), DOWNLOAD_CLEANUP_DELAY_MS);
return { buffer: job.result, filename: job.metadata?.filename || 'resume' };
}