feat: prefill fields via api (#1261)

## Description

Configure the advanced field via API.

## Checklist

<!--- Please check the boxes that apply to this pull request. -->
<!--- You can add or remove items as needed. -->

- [x] I have tested these changes locally and they work as expected.
- [ ] I have added/updated tests that prove the effectiveness of these
changes.
- [x] I have updated the documentation to reflect these changes, if
applicable.
- [x] I have followed the project's coding style guidelines.
- [ ] I have addressed the code review feedback from the previous
submission, if applicable.


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

- **New Features**
- Enhanced API functionality to support field metadata during field
creation.
- Introduced validation checks for field metadata to ensure necessary
information is provided for advanced field types.

- **Bug Fixes**
- Improved error handling during field creation to return properly
formatted error responses.

- **Documentation**
- Updated API schemas to include field metadata, enhancing data
validation and response structures.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
Catalin Pit
2024-08-26 12:37:56 +03:00
committed by GitHub
parent 9223527b6f
commit 0829311214
4 changed files with 106 additions and 35 deletions

2
.gitignore vendored
View File

@@ -1,5 +1,7 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
packages/prisma/generated/types.ts
# dependencies # dependencies
node_modules node_modules
.pnp .pnp

View File

@@ -31,6 +31,7 @@ import { createDocumentFromTemplateLegacy } from '@documenso/lib/server-only/tem
import { deleteTemplate } from '@documenso/lib/server-only/template/delete-template'; import { deleteTemplate } from '@documenso/lib/server-only/template/delete-template';
import { findTemplates } from '@documenso/lib/server-only/template/find-templates'; import { findTemplates } from '@documenso/lib/server-only/template/find-templates';
import { getTemplateById } from '@documenso/lib/server-only/template/get-template-by-id'; import { getTemplateById } from '@documenso/lib/server-only/template/get-template-by-id';
import { ZFieldMetaSchema } from '@documenso/lib/types/field-meta';
import { extractNextApiRequestMetadata } from '@documenso/lib/universal/extract-request-metadata'; import { extractNextApiRequestMetadata } from '@documenso/lib/universal/extract-request-metadata';
import { getFile } from '@documenso/lib/universal/upload/get-file'; import { getFile } from '@documenso/lib/universal/upload/get-file';
import { putPdfFile } from '@documenso/lib/universal/upload/put-file'; import { putPdfFile } from '@documenso/lib/universal/upload/put-file';
@@ -869,7 +870,17 @@ export const ApiContractV1Implementation = createNextRoute(ApiContractV1, {
createField: authenticatedMiddleware(async (args, user, team) => { createField: authenticatedMiddleware(async (args, user, team) => {
const { id: documentId } = args.params; const { id: documentId } = args.params;
const { recipientId, type, pageNumber, pageWidth, pageHeight, pageX, pageY } = args.body; const { recipientId, type, pageNumber, pageWidth, pageHeight, pageX, pageY, fieldMeta } =
args.body;
if (pageNumber <= 0) {
return {
status: 400,
body: {
message: 'Invalid page number',
},
};
}
const document = await getDocumentById({ const document = await getDocumentById({
id: Number(documentId), id: Number(documentId),
@@ -918,41 +929,47 @@ export const ApiContractV1Implementation = createNextRoute(ApiContractV1, {
}; };
} }
const field = await createField({ try {
documentId: Number(documentId), const field = await createField({
recipientId: Number(recipientId),
userId: user.id,
teamId: team?.id,
type,
pageNumber,
pageX,
pageY,
pageWidth,
pageHeight,
requestMetadata: extractNextApiRequestMetadata(args.req),
});
const remappedField = {
id: field.id,
documentId: field.documentId,
recipientId: field.recipientId ?? -1,
type: field.type,
pageNumber: field.page,
pageX: Number(field.positionX),
pageY: Number(field.positionY),
pageWidth: Number(field.width),
pageHeight: Number(field.height),
customText: field.customText,
inserted: field.inserted,
};
return {
status: 200,
body: {
...remappedField,
documentId: Number(documentId), documentId: Number(documentId),
}, recipientId: Number(recipientId),
}; userId: user.id,
teamId: team?.id,
type,
pageNumber,
pageX,
pageY,
pageWidth,
pageHeight,
fieldMeta,
requestMetadata: extractNextApiRequestMetadata(args.req),
});
const remappedField = {
id: field.id,
documentId: field.documentId,
recipientId: field.recipientId ?? -1,
type: field.type,
pageNumber: field.page,
pageX: Number(field.positionX),
pageY: Number(field.positionY),
pageWidth: Number(field.width),
pageHeight: Number(field.height),
customText: field.customText,
fieldMeta: ZFieldMetaSchema.parse(field.fieldMeta),
inserted: field.inserted,
};
return {
status: 200,
body: {
...remappedField,
documentId: Number(documentId),
},
};
} catch (err) {
return AppError.toRestAPIError(err);
}
}), }),
updateField: authenticatedMiddleware(async (args, user, team) => { updateField: authenticatedMiddleware(async (args, user, team) => {

View File

@@ -5,6 +5,7 @@ import { DATE_FORMATS, DEFAULT_DOCUMENT_DATE_FORMAT } from '@documenso/lib/const
import '@documenso/lib/constants/time-zones'; import '@documenso/lib/constants/time-zones';
import { DEFAULT_DOCUMENT_TIME_ZONE, TIME_ZONES } from '@documenso/lib/constants/time-zones'; import { DEFAULT_DOCUMENT_TIME_ZONE, TIME_ZONES } from '@documenso/lib/constants/time-zones';
import { ZUrlSchema } from '@documenso/lib/schemas/common'; import { ZUrlSchema } from '@documenso/lib/schemas/common';
import { ZFieldMetaSchema } from '@documenso/lib/types/field-meta';
import { import {
DocumentDataType, DocumentDataType,
FieldType, FieldType,
@@ -300,6 +301,7 @@ export const ZCreateFieldMutationSchema = z.object({
pageY: z.number(), pageY: z.number(),
pageWidth: z.number(), pageWidth: z.number(),
pageHeight: z.number(), pageHeight: z.number(),
fieldMeta: ZFieldMetaSchema,
}); });
export type TCreateFieldMutationSchema = z.infer<typeof ZCreateFieldMutationSchema>; export type TCreateFieldMutationSchema = z.infer<typeof ZCreateFieldMutationSchema>;
@@ -323,6 +325,7 @@ export const ZSuccessfulFieldResponseSchema = z.object({
pageWidth: z.number(), pageWidth: z.number(),
pageHeight: z.number(), pageHeight: z.number(),
customText: z.string(), customText: z.string(),
fieldMeta: ZFieldMetaSchema,
inserted: z.boolean(), inserted: z.boolean(),
}); });

View File

@@ -1,6 +1,16 @@
import { match } from 'ts-pattern';
import { prisma } from '@documenso/prisma'; import { prisma } from '@documenso/prisma';
import type { FieldType, Team } from '@documenso/prisma/client'; import type { FieldType, Team } from '@documenso/prisma/client';
import {
ZCheckboxFieldMeta,
ZDropdownFieldMeta,
ZNumberFieldMeta,
ZRadioFieldMeta,
ZTextFieldMeta,
} from '../../types/field-meta';
import type { TFieldMetaSchema as FieldMeta } from '../../types/field-meta';
import type { RequestMetadata } from '../../universal/extract-request-metadata'; import type { RequestMetadata } from '../../universal/extract-request-metadata';
import { createDocumentAuditLogData } from '../../utils/document-audit-logs'; import { createDocumentAuditLogData } from '../../utils/document-audit-logs';
@@ -15,6 +25,7 @@ export type CreateFieldOptions = {
pageY: number; pageY: number;
pageWidth: number; pageWidth: number;
pageHeight: number; pageHeight: number;
fieldMeta?: FieldMeta;
requestMetadata?: RequestMetadata; requestMetadata?: RequestMetadata;
}; };
@@ -29,6 +40,7 @@ export const createField = async ({
pageY, pageY,
pageWidth, pageWidth,
pageHeight, pageHeight,
fieldMeta,
requestMetadata, requestMetadata,
}: CreateFieldOptions) => { }: CreateFieldOptions) => {
const document = await prisma.document.findFirst({ const document = await prisma.document.findFirst({
@@ -85,6 +97,42 @@ export const createField = async ({
}); });
} }
const advancedField = ['NUMBER', 'RADIO', 'CHECKBOX', 'DROPDOWN', 'TEXT'].includes(type);
if (advancedField && !fieldMeta) {
throw new Error(
'Field meta is required for this type of field. Please provide the appropriate field meta object.',
);
}
if (fieldMeta && fieldMeta.type.toLowerCase() !== String(type).toLowerCase()) {
throw new Error('Field meta type does not match the field type');
}
const result = match(type)
.with('RADIO', () => {
return ZRadioFieldMeta.safeParse(fieldMeta);
})
.with('CHECKBOX', () => {
return ZCheckboxFieldMeta.safeParse(fieldMeta);
})
.with('DROPDOWN', () => {
return ZDropdownFieldMeta.safeParse(fieldMeta);
})
.with('NUMBER', () => {
return ZNumberFieldMeta.safeParse(fieldMeta);
})
.with('TEXT', () => {
return ZTextFieldMeta.safeParse(fieldMeta);
})
.otherwise(() => {
return { success: false, data: {} };
});
if (!result.success) {
throw new Error('Field meta parsing failed');
}
const field = await prisma.field.create({ const field = await prisma.field.create({
data: { data: {
documentId, documentId,
@@ -97,6 +145,7 @@ export const createField = async ({
height: pageHeight, height: pageHeight,
customText: '', customText: '',
inserted: false, inserted: false,
fieldMeta: advancedField ? result.data : undefined,
}, },
include: { include: {
Recipient: true, Recipient: true,