mirror of
https://github.com/AmruthPillai/Reactive-Resume.git
synced 2026-07-25 01:15:26 +10:00
feat: add PDF download endpoint and export createLocalizedResumeDocument function
This commit is contained in:
@@ -0,0 +1,13 @@
|
||||
import type { ResumeData } from "@reactive-resume/schema/resume/data";
|
||||
import type { Template } from "@reactive-resume/schema/templates";
|
||||
import { renderToBuffer } from "@react-pdf/renderer";
|
||||
import { createLocalizedResumeDocument } from "./pdf-document";
|
||||
|
||||
export const createResumePdfFile = async (data: ResumeData, filename: string, template?: Template) => {
|
||||
const document = await createLocalizedResumeDocument(data, template);
|
||||
const buffer = await renderToBuffer(document);
|
||||
const bytes = new Uint8Array(new ArrayBuffer(buffer.byteLength));
|
||||
bytes.set(buffer);
|
||||
|
||||
return new File([bytes], filename, { type: "application/pdf" });
|
||||
};
|
||||
@@ -21,7 +21,7 @@ export const useLocalizedResumeDocument = (data?: ResumeData, template?: Templat
|
||||
}, [data, template, sectionTitleResolver]);
|
||||
};
|
||||
|
||||
const createLocalizedResumeDocument = async (data: ResumeData, template?: Template) => {
|
||||
export const createLocalizedResumeDocument = async (data: ResumeData, template?: Template) => {
|
||||
const sectionTitleResolver = await createSectionTitleResolverForLocale(data.metadata.page.locale);
|
||||
|
||||
return (
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
import { ORPCError } from "@orpc/server";
|
||||
import z from "zod";
|
||||
import { protectedProcedure } from "@reactive-resume/api/context";
|
||||
import { resumeService } from "@reactive-resume/api/services/resume";
|
||||
import { generateFilename } from "@reactive-resume/utils/file";
|
||||
import { createResumePdfFile } from "@/libs/resume/pdf-document.server";
|
||||
|
||||
export const downloadResumePdfProcedure = protectedProcedure
|
||||
.route({
|
||||
method: "GET",
|
||||
path: "/resumes/{id}/pdf",
|
||||
tags: ["Resumes"],
|
||||
operationId: "downloadResumePdf",
|
||||
summary: "Download resume as PDF",
|
||||
description:
|
||||
"Generates a PDF for the specified resume and returns it as a forced download. Only resumes belonging to the authenticated user can be downloaded. Requires authentication.",
|
||||
successDescription: "The generated resume PDF.",
|
||||
outputStructure: "detailed",
|
||||
})
|
||||
.input(z.object({ id: z.string().describe("The ID of the resume.") }))
|
||||
.output(
|
||||
z.object({
|
||||
headers: z.object({
|
||||
"content-disposition": z.string(),
|
||||
}),
|
||||
body: z.file().mime("application/pdf"),
|
||||
}),
|
||||
)
|
||||
.handler(async ({ context, input }) => {
|
||||
const resume = await resumeService.getById({ id: input.id, userId: context.user.id });
|
||||
const filename = generateFilename(resume.name, "pdf");
|
||||
|
||||
try {
|
||||
const body = await createResumePdfFile(resume.data, filename);
|
||||
|
||||
return {
|
||||
headers: {
|
||||
"content-disposition": `attachment; filename="${filename}"`,
|
||||
},
|
||||
body,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error("[PDF API] Failed to render resume PDF", { resumeId: input.id, error });
|
||||
throw new ORPCError("INTERNAL_SERVER_ERROR", { message: "Failed to generate resume PDF" });
|
||||
}
|
||||
});
|
||||
@@ -9,9 +9,18 @@ import router from "@reactive-resume/api/routers";
|
||||
import { env } from "@reactive-resume/env/server";
|
||||
import { resumeDataSchema } from "@reactive-resume/schema/resume/data";
|
||||
import { getLocale } from "@/libs/locale";
|
||||
import { downloadResumePdfProcedure } from "./-helpers/resume-pdf";
|
||||
|
||||
const openAPIRouter = {
|
||||
...router,
|
||||
resume: {
|
||||
...router.resume,
|
||||
downloadPdf: downloadResumePdfProcedure,
|
||||
},
|
||||
};
|
||||
|
||||
async function handler({ request }: { request: Request }) {
|
||||
const openAPIHandler = new OpenAPIHandler(router, {
|
||||
const openAPIHandler = new OpenAPIHandler(openAPIRouter, {
|
||||
plugins: [
|
||||
new BatchHandlerPlugin(),
|
||||
new RequestHeadersPlugin(),
|
||||
@@ -34,7 +43,7 @@ async function handler({ request }: { request: Request }) {
|
||||
const locale = await getLocale();
|
||||
|
||||
if (request.method === "GET" && (request.url.endsWith("/spec.json") || request.url.endsWith("/spec"))) {
|
||||
const spec = await openAPIGenerator.generate(router, {
|
||||
const spec = await openAPIGenerator.generate(openAPIRouter, {
|
||||
info: {
|
||||
title: "Reactive Resume",
|
||||
version: __APP_VERSION__,
|
||||
|
||||
Reference in New Issue
Block a user