feat: fieldMeta parsing

This commit is contained in:
Catalin Pit
2025-10-30 16:43:54 +02:00
parent ab95e80987
commit 1e52493144

View File

@ -9,7 +9,7 @@ import { getEnvelopeWhereInput } from '@documenso/lib/server-only/envelope/get-e
import { createEnvelopeFields } from '@documenso/lib/server-only/field/create-envelope-fields'; import { createEnvelopeFields } from '@documenso/lib/server-only/field/create-envelope-fields';
import { createDocumentRecipients } from '@documenso/lib/server-only/recipient/create-document-recipients'; import { createDocumentRecipients } from '@documenso/lib/server-only/recipient/create-document-recipients';
import { createTemplateRecipients } from '@documenso/lib/server-only/recipient/create-template-recipients'; import { createTemplateRecipients } from '@documenso/lib/server-only/recipient/create-template-recipients';
import type { TFieldAndMeta } from '@documenso/lib/types/field-meta'; import { type TFieldAndMeta, ZFieldAndMetaSchema } from '@documenso/lib/types/field-meta';
import type { ApiRequestMetadata } from '@documenso/lib/universal/extract-request-metadata'; import type { ApiRequestMetadata } from '@documenso/lib/universal/extract-request-metadata';
import type { EnvelopeIdOptions } from '@documenso/lib/utils/envelope'; import type { EnvelopeIdOptions } from '@documenso/lib/utils/envelope';
import { mapSecondaryIdToTemplateId } from '@documenso/lib/utils/envelope'; import { mapSecondaryIdToTemplateId } from '@documenso/lib/utils/envelope';
@ -33,14 +33,12 @@ type PlaceholderInfo = {
placeholder: string; placeholder: string;
fieldType: string; fieldType: string;
recipient: string; recipient: string;
isRequired: string; fieldMeta: Record<string, string>;
page: number; page: number;
// PDF2JSON coordinates (in page units - these are relative to page dimensions)
x: number; x: number;
y: number; y: number;
width: number; width: number;
height: number; height: number;
// Page dimensions from PDF2JSON (in page units)
pageWidth: number; pageWidth: number;
pageHeight: number; pageHeight: number;
}; };
@ -121,7 +119,9 @@ export const extractPlaceholdersFromPDF = async (pdf: Buffer): Promise<Placehold
const placeholder = match[0]; const placeholder = match[0];
const placeholderData = match[1].split(',').map((part) => part.trim()); const placeholderData = match[1].split(',').map((part) => part.trim());
const [fieldType, recipient, isRequired] = placeholderData; const [fieldType, recipient, ...fieldMetaData] = placeholderData;
const fieldMeta = Object.fromEntries(fieldMetaData.map((meta) => meta.split('=')));
/* /*
Find the position of where the placeholder starts in the text Find the position of where the placeholder starts in the text
@ -157,7 +157,7 @@ export const extractPlaceholdersFromPDF = async (pdf: Buffer): Promise<Placehold
placeholder, placeholder,
fieldType, fieldType,
recipient, recipient,
isRequired, fieldMeta,
page: pageIndex + 1, page: pageIndex + 1,
x, x,
y, y,
@ -272,6 +272,51 @@ const parseFieldType = (fieldTypeString: string): FieldType => {
}); });
}; };
/*
Transform raw field metadata from placeholder format to schema format.
Users should provide properly capitalized property names (e.g., readOnly, fontSize, textAlign).
Converts string values to proper types (booleans, numbers).
*/
const transformFieldMeta = (
rawMeta: Record<string, string>,
fieldType: FieldType,
): Record<string, unknown> | undefined => {
if (fieldType === FieldType.SIGNATURE || fieldType === FieldType.FREE_SIGNATURE) {
return undefined;
}
if (Object.keys(rawMeta).length === 0) {
return undefined;
}
const fieldTypeString = String(fieldType).toLowerCase();
const transformed: Record<string, unknown> = {
type: fieldTypeString,
};
for (const [key, value] of Object.entries(rawMeta)) {
if (key === 'readOnly' || key === 'required') {
transformed[key] = value === 'true';
} else if (
key === 'fontSize' ||
key === 'maxValue' ||
key === 'minValue' ||
key === 'characterLimit'
) {
const numValue = Number(value);
if (!Number.isNaN(numValue)) {
transformed[key] = numValue;
}
} else {
transformed[key] = value;
}
}
return transformed;
};
export const insertFieldsFromPlaceholdersInPDF = async ( export const insertFieldsFromPlaceholdersInPDF = async (
pdf: Buffer, pdf: Buffer,
userId: number, userId: number,
@ -349,7 +394,7 @@ export const insertFieldsFromPlaceholdersInPDF = async (
}); });
createdRecipients = recipients; createdRecipients = recipients;
} else { } else if (envelope.type === EnvelopeType.TEMPLATE) {
const templateId = const templateId =
envelopeId.type === 'templateId' envelopeId.type === 'templateId'
? envelopeId.id ? envelopeId.id
@ -363,6 +408,10 @@ export const insertFieldsFromPlaceholdersInPDF = async (
}); });
createdRecipients = recipients; createdRecipients = recipients;
} else {
throw new AppError(AppErrorCode.INVALID_BODY, {
message: `Invalid envelope type: ${envelope.type}`,
});
} }
const fieldsToCreate: FieldToCreate[] = []; const fieldsToCreate: FieldToCreate[] = [];
@ -396,19 +445,21 @@ export const insertFieldsFromPlaceholdersInPDF = async (
// Default height percentage if too small (use 2% as a reasonable default) // Default height percentage if too small (use 2% as a reasonable default)
const finalHeightPercent = heightPercent > 0.01 ? heightPercent : 2; const finalHeightPercent = heightPercent > 0.01 ? heightPercent : 2;
const baseField = { const transformedFieldMeta = transformFieldMeta(placeholder.fieldMeta, fieldType);
const baseFieldAndMeta: TFieldAndMeta = ZFieldAndMetaSchema.parse({
type: fieldType,
fieldMeta: transformedFieldMeta,
});
fieldsToCreate.push({
...baseFieldAndMeta,
recipientId, recipientId,
pageNumber: placeholder.page, pageNumber: placeholder.page,
pageX: xPercent, pageX: xPercent,
pageY: yPercent, pageY: yPercent,
width: widthPercent, width: widthPercent,
height: finalHeightPercent, height: finalHeightPercent,
};
fieldsToCreate.push({
type: fieldType,
fieldMeta: undefined,
...baseField,
}); });
} }