feat(document-templates): delete TipTap-to-pdfme bridge
Phase 1 / commit 12 of 14 — strips out the 571-line tiptap-to-pdfme
serializer and every code path that depended on it. TipTap document
templates remain as Documenso-template seed bodies; the CRM no longer
renders them to PDF in-app.
Deleted:
src/lib/pdf/tiptap-to-pdfme.ts (571 LOC)
src/lib/pdf/templates/eoi-standard-inapp.ts (337 LOC)
src/app/api/v1/admin/templates/preview/route.ts
src/app/api/v1/document-templates/[id]/generate/route.ts
src/app/api/v1/document-templates/[id]/generate-and-send/route.ts
src/lib/services/document-templates.ts:generateFromTemplate (~140 LOC)
src/lib/services/document-templates.ts:generateAndSend (~40 LOC)
src/lib/validators/document-templates.ts:generateAndSendSchema
src/lib/validators/document-templates.ts:previewAdminTemplateSchema
tests/unit/tiptap-serializer.test.ts (old bridge tests)
Preserved as src/lib/pdf/tiptap-validation.ts (~70 LOC):
- validateTipTapDocument() — still used to reject unsupported nodes
on save in the admin template editor
- TEMPLATE_VARIABLES — drives the merge-token picker in the
admin template form + preview UI
generateAndSign() now throws a clear ValidationError when a non-EOI
template tries the in-app pathway. Use a Documenso template, or wait
for the deferred AcroForm-fill admin-upload feature.
seed-data.ts: "Standard EOI (in-app)" template row now seeds with stub
bodyHtml + small MERGE_FIELDS array; the deleted HTML helper was never
actually rendered (in-app EOI is pdf-lib AcroForm fill on the source
PDF — generateEoiPdfFromTemplate, unchanged).
After this commit, pdfme has zero callers left. Commit 14 drops the
deps and the generate.ts shim.
1298/1298 vitest green.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,74 +0,0 @@
|
||||
import { NextResponse } from 'next/server';
|
||||
|
||||
import { withAuth, withPermission } from '@/lib/api/helpers';
|
||||
import { parseBody } from '@/lib/api/route-helpers';
|
||||
import { errorResponse, ValidationError } from '@/lib/errors';
|
||||
import { generatePdf } from '@/lib/pdf/generate';
|
||||
import {
|
||||
validateTipTapDocument,
|
||||
tipTapToPdfmeTemplate,
|
||||
buildContentInputsFromDoc,
|
||||
substituteVariables,
|
||||
type TipTapNode,
|
||||
} from '@/lib/pdf/tiptap-to-pdfme';
|
||||
import { previewAdminTemplateSchema } from '@/lib/validators/document-templates';
|
||||
|
||||
/**
|
||||
* POST /api/v1/admin/templates/preview
|
||||
*
|
||||
* Generates a preview PDF from a TipTap JSON content block.
|
||||
* Returns { data: { pdfBase64: string } } - the client can render this
|
||||
* in an <iframe src="data:application/pdf;base64,..."> or open in a new tab.
|
||||
*
|
||||
* Body:
|
||||
* content: TipTap JSON document
|
||||
* sampleData?: Record<string, string> - variable substitutions
|
||||
*/
|
||||
export const POST = withAuth(
|
||||
withPermission('document_templates', 'manage', async (req, _ctx) => {
|
||||
try {
|
||||
const body = await parseBody(req, previewAdminTemplateSchema);
|
||||
|
||||
const doc = body.content as unknown as TipTapNode;
|
||||
const sampleData = body.sampleData ?? {};
|
||||
|
||||
// Validate content nodes
|
||||
const unsupported = validateTipTapDocument(doc);
|
||||
if (unsupported.length > 0) {
|
||||
throw new ValidationError(
|
||||
`Content contains unsupported node types: ${unsupported.join(', ')}`,
|
||||
);
|
||||
}
|
||||
|
||||
// Substitute variables in text nodes
|
||||
const substitutedDoc = substituteInDoc(doc, sampleData);
|
||||
|
||||
// Convert to pdfme template + inputs
|
||||
const template = tipTapToPdfmeTemplate(substitutedDoc);
|
||||
const inputs = buildContentInputsFromDoc(substitutedDoc, template);
|
||||
|
||||
const pdfBytes = await generatePdf(template, inputs);
|
||||
const pdfBase64 = Buffer.from(pdfBytes).toString('base64');
|
||||
|
||||
return NextResponse.json({ data: { pdfBase64 } });
|
||||
} catch (error) {
|
||||
return errorResponse(error);
|
||||
}
|
||||
}),
|
||||
);
|
||||
|
||||
/**
|
||||
* Deeply substitutes {{variable}} tokens in all text nodes of a TipTap doc.
|
||||
*/
|
||||
function substituteInDoc(node: TipTapNode, data: Record<string, string>): TipTapNode {
|
||||
if (node.type === 'text' && node.text) {
|
||||
return { ...node, text: substituteVariables(node.text, data) };
|
||||
}
|
||||
if (node.content) {
|
||||
return {
|
||||
...node,
|
||||
content: node.content.map((child) => substituteInDoc(child, data)),
|
||||
};
|
||||
}
|
||||
return node;
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
import { NextResponse } from 'next/server';
|
||||
|
||||
import { withAuth, withPermission } from '@/lib/api/helpers';
|
||||
import { parseBody } from '@/lib/api/route-helpers';
|
||||
import { errorResponse } from '@/lib/errors';
|
||||
import { generateAndSend } from '@/lib/services/document-templates';
|
||||
import { generateAndSendSchema } from '@/lib/validators/document-templates';
|
||||
|
||||
export const POST = withAuth(
|
||||
withPermission('documents', 'create', async (req, ctx, params) => {
|
||||
try {
|
||||
const body = await parseBody(req, generateAndSendSchema);
|
||||
const result = await generateAndSend(
|
||||
params.id!,
|
||||
ctx.portId,
|
||||
{
|
||||
clientId: body.clientId,
|
||||
interestId: body.interestId,
|
||||
berthId: body.berthId,
|
||||
},
|
||||
body.recipientEmail,
|
||||
{
|
||||
userId: ctx.userId,
|
||||
portId: ctx.portId,
|
||||
ipAddress: ctx.ipAddress,
|
||||
userAgent: ctx.userAgent,
|
||||
},
|
||||
);
|
||||
return NextResponse.json({ data: result }, { status: 201 });
|
||||
} catch (error) {
|
||||
return errorResponse(error);
|
||||
}
|
||||
}),
|
||||
);
|
||||
@@ -1,24 +0,0 @@
|
||||
import { NextResponse } from 'next/server';
|
||||
|
||||
import { withAuth, withPermission } from '@/lib/api/helpers';
|
||||
import { parseBody } from '@/lib/api/route-helpers';
|
||||
import { errorResponse } from '@/lib/errors';
|
||||
import { generateFromTemplate } from '@/lib/services/document-templates';
|
||||
import { generateSchema } from '@/lib/validators/document-templates';
|
||||
|
||||
export const POST = withAuth(
|
||||
withPermission('documents', 'create', async (req, ctx, params) => {
|
||||
try {
|
||||
const body = await parseBody(req, generateSchema);
|
||||
const result = await generateFromTemplate(params.id!, ctx.portId, body, {
|
||||
userId: ctx.userId,
|
||||
portId: ctx.portId,
|
||||
ipAddress: ctx.ipAddress,
|
||||
userAgent: ctx.userAgent,
|
||||
});
|
||||
return NextResponse.json({ data: result }, { status: 201 });
|
||||
} catch (error) {
|
||||
return errorResponse(error);
|
||||
}
|
||||
}),
|
||||
);
|
||||
Reference in New Issue
Block a user