feat: add prisma json types (#1583)

This commit is contained in:
David Nguyen
2025-01-15 13:46:45 +11:00
committed by GitHub
parent 901be70f97
commit 5750f2b477
21 changed files with 174 additions and 188 deletions

View File

@ -1053,12 +1053,12 @@ export const ApiContractV1Implementation = createNextRoute(ApiContractV1, {
.with('TEXT', () => ZTextFieldMeta.safeParse(fieldMeta))
.with('SIGNATURE', 'INITIALS', 'DATE', 'EMAIL', 'NAME', () => ({
success: true,
data: {},
data: undefined,
}))
.with('FREE_SIGNATURE', () => ({
success: false,
error: 'FREE_SIGNATURE is not supported',
data: {},
data: undefined,
}))
.exhaustive();

View File

@ -27,7 +27,7 @@ function createTempPdfFile() {
'%PDF-1.4\n1 0 obj<</Type/Catalog/Pages 2 0 R>>endobj 2 0 obj<</Type/Pages/Kids[3 0 R]/Count 1>>endobj 3 0 obj<</Type/Page/MediaBox[0 0 612 792]/Parent 2 0 R>>endobj\nxref\n0 4\n0000000000 65535 f\n0000000009 00000 n\n0000000052 00000 n\n0000000101 00000 n\ntrailer<</Size 4/Root 1 0 R>>\nstartxref\n178\n%%EOF',
);
fs.writeFileSync(tempFilePath, pdfContent);
fs.writeFileSync(tempFilePath, new Uint8Array(pdfContent));
return tempFilePath;
}

View File

@ -13,7 +13,7 @@
"author": "",
"devDependencies": {
"@playwright/test": "^1.18.1",
"@types/node": "^20.8.2",
"@types/node": "^20",
"@documenso/lib": "*",
"@documenso/prisma": "*",
"@documenso/web": "*",

View File

@ -15,6 +15,6 @@
"eslint-plugin-package-json": "^0.10.4",
"eslint-plugin-react": "^7.34.0",
"eslint-plugin-unused-imports": "^3.1.0",
"typescript": "5.2.2"
"typescript": "5.6.2"
}
}
}

View File

@ -33,7 +33,7 @@ export const setupTwoFactorAuthentication = async ({
const accountName = user.email;
const uri = createTOTPKeyURI(ISSUER, accountName, secret);
const encodedSecret = base32.encode(secret);
const encodedSecret = base32.encode(new Uint8Array(secret));
await prisma.user.update({
where: {

View File

@ -65,11 +65,6 @@ export const updateField = async ({
},
});
const newFieldMeta = {
...(oldField.fieldMeta as FieldMeta),
...fieldMeta,
};
const field = prisma.$transaction(async (tx) => {
const updatedField = await tx.field.update({
where: {
@ -83,7 +78,7 @@ export const updateField = async ({
positionY: pageY,
width: pageWidth,
height: pageHeight,
fieldMeta: newFieldMeta,
fieldMeta,
},
include: {
recipient: true,

View File

@ -0,0 +1,8 @@
import { z } from 'zod';
export const ZDocumentFormValuesSchema = z.record(
z.string(),
z.union([z.string(), z.boolean(), z.number()]),
);
export type TDocumentFormValues = z.infer<typeof ZDocumentFormValuesSchema>;

View File

@ -9,36 +9,36 @@ export const ZBaseFieldMeta = z.object({
export type TBaseFieldMeta = z.infer<typeof ZBaseFieldMeta>;
export const ZInitialsFieldMeta = z.object({
type: z.literal('initials').default('initials'),
export const ZInitialsFieldMeta = ZBaseFieldMeta.extend({
type: z.literal('initials'),
fontSize: z.number().min(8).max(96).optional(),
});
export type TInitialsFieldMeta = z.infer<typeof ZInitialsFieldMeta>;
export const ZNameFieldMeta = z.object({
type: z.literal('name').default('name'),
export const ZNameFieldMeta = ZBaseFieldMeta.extend({
type: z.literal('name'),
fontSize: z.number().min(8).max(96).optional(),
});
export type TNameFieldMeta = z.infer<typeof ZNameFieldMeta>;
export const ZEmailFieldMeta = z.object({
type: z.literal('email').default('email'),
export const ZEmailFieldMeta = ZBaseFieldMeta.extend({
type: z.literal('email'),
fontSize: z.number().min(8).max(96).optional(),
});
export type TEmailFieldMeta = z.infer<typeof ZEmailFieldMeta>;
export const ZDateFieldMeta = z.object({
type: z.literal('date').default('date'),
export const ZDateFieldMeta = ZBaseFieldMeta.extend({
type: z.literal('date'),
fontSize: z.number().min(8).max(96).optional(),
});
export type TDateFieldMeta = z.infer<typeof ZDateFieldMeta>;
export const ZTextFieldMeta = ZBaseFieldMeta.extend({
type: z.literal('text').default('text'),
type: z.literal('text'),
text: z.string().optional(),
characterLimit: z.number().optional(),
fontSize: z.number().min(8).max(96).optional(),
@ -47,7 +47,7 @@ export const ZTextFieldMeta = ZBaseFieldMeta.extend({
export type TTextFieldMeta = z.infer<typeof ZTextFieldMeta>;
export const ZNumberFieldMeta = ZBaseFieldMeta.extend({
type: z.literal('number').default('number'),
type: z.literal('number'),
numberFormat: z.string().optional(),
value: z.string().optional(),
minValue: z.number().optional(),
@ -58,7 +58,7 @@ export const ZNumberFieldMeta = ZBaseFieldMeta.extend({
export type TNumberFieldMeta = z.infer<typeof ZNumberFieldMeta>;
export const ZRadioFieldMeta = ZBaseFieldMeta.extend({
type: z.literal('radio').default('radio'),
type: z.literal('radio'),
values: z
.array(
z.object({
@ -73,7 +73,7 @@ export const ZRadioFieldMeta = ZBaseFieldMeta.extend({
export type TRadioFieldMeta = z.infer<typeof ZRadioFieldMeta>;
export const ZCheckboxFieldMeta = ZBaseFieldMeta.extend({
type: z.literal('checkbox').default('checkbox'),
type: z.literal('checkbox'),
values: z
.array(
z.object({
@ -90,30 +90,27 @@ export const ZCheckboxFieldMeta = ZBaseFieldMeta.extend({
export type TCheckboxFieldMeta = z.infer<typeof ZCheckboxFieldMeta>;
export const ZDropdownFieldMeta = ZBaseFieldMeta.extend({
type: z.literal('dropdown').default('dropdown'),
type: z.literal('dropdown'),
values: z.array(z.object({ value: z.string() })).optional(),
defaultValue: z.string().optional(),
});
export type TDropdownFieldMeta = z.infer<typeof ZDropdownFieldMeta>;
/**
* This will parse empty objects to { "type": "initials" }
*
* Todo: Fix.
*/
export const ZFieldMetaSchema = z
.union([
ZBaseFieldMeta.extend(ZInitialsFieldMeta.shape),
ZBaseFieldMeta.extend(ZNameFieldMeta.shape),
ZBaseFieldMeta.extend(ZEmailFieldMeta.shape),
ZBaseFieldMeta.extend(ZDateFieldMeta.shape),
ZTextFieldMeta,
ZNumberFieldMeta,
ZRadioFieldMeta,
ZCheckboxFieldMeta,
ZDropdownFieldMeta,
])
.optional();
export const ZFieldMetaNotOptionalSchema = z.discriminatedUnion('type', [
ZInitialsFieldMeta,
ZNameFieldMeta,
ZEmailFieldMeta,
ZDateFieldMeta,
ZTextFieldMeta,
ZNumberFieldMeta,
ZRadioFieldMeta,
ZCheckboxFieldMeta,
ZDropdownFieldMeta,
]);
export type TFieldMetaNotOptionalSchema = z.infer<typeof ZFieldMetaNotOptionalSchema>;
export const ZFieldMetaSchema = ZFieldMetaNotOptionalSchema.optional();
export type TFieldMetaSchema = z.infer<typeof ZFieldMetaSchema>;

View File

@ -1,3 +1,4 @@
/// <reference types="@documenso/prisma/types/types.d.ts" />
import { PrismaClient } from '@prisma/client';
import { Kysely, PostgresAdapter, PostgresIntrospector, PostgresQueryCompiler } from 'kysely';
import kyselyExtension from 'prisma-extension-kysely';

View File

@ -30,9 +30,10 @@
"devDependencies": {
"dotenv": "^16.3.1",
"dotenv-cli": "^7.3.0",
"prisma-json-types-generator": "^3.2.2",
"prisma-kysely": "^1.8.0",
"tsx": "^4.11.0",
"typescript": "5.2.2",
"typescript": "5.6.2",
"zod-prisma-types": "3.1.9"
}
}

View File

@ -6,6 +6,10 @@ generator client {
provider = "prisma-client-js"
}
generator json {
provider = "prisma-json-types-generator"
}
generator zod {
provider = "zod-prisma-types"
createInputTypes = false
@ -297,14 +301,14 @@ enum DocumentVisibility {
ADMIN
}
/// @zod.import(["import { ZDocumentAuthOptionsSchema } from '@documenso/lib/types/document-auth';"])
/// @zod.import(["import { ZDocumentAuthOptionsSchema } from '@documenso/lib/types/document-auth';", "import { ZDocumentFormValuesSchema } from '@documenso/lib/types/document-form-values';"])
model Document {
id Int @id @default(autoincrement())
externalId String? /// @zod.string.describe("A custom external ID you can use to identify the document.")
userId Int /// @zod.number.describe("The ID of the user that created this document.")
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
authOptions Json? /// Todo: zod.custom.use(ZDocumentAuthOptionsSchema.describe("Hello"))
formValues Json?
authOptions Json? /// [DocumentAuthOptions] @zod.custom.use(ZDocumentAuthOptionsSchema)
formValues Json? /// [DocumentFormValues] @zod.custom.use(ZDocumentFormValuesSchema)
visibility DocumentVisibility @default(EVERYONE)
title String
status DocumentStatus @default(DRAFT)
@ -373,6 +377,7 @@ enum DocumentDistributionMethod {
NONE
}
/// @zod.import(["import { ZDocumentEmailSettingsSchema } from '@documenso/lib/types/document-email';"])
model DocumentMeta {
id String @id @default(cuid())
subject String?
@ -387,7 +392,7 @@ model DocumentMeta {
typedSignatureEnabled Boolean @default(true)
language String @default("en")
distributionMethod DocumentDistributionMethod @default(EMAIL)
emailSettings Json?
emailSettings Json? /// [DocumentEmailSettings] @zod.custom.use(ZDocumentEmailSettingsSchema)
}
enum ReadStatus {
@ -424,7 +429,7 @@ model Recipient {
documentDeletedAt DateTime?
expired DateTime?
signedAt DateTime?
authOptions Json? /// Todo: zod.custom.use(ZRecipientAuthOptionsSchema)
authOptions Json? /// [RecipientAuthOptions] @zod.custom.use(ZRecipientAuthOptionsSchema)
signingOrder Int? /// @zod.number.describe("The order in which the recipient should sign the document. Only works if the document is set to sequential signing.")
rejectionReason String?
role RecipientRole @default(SIGNER)
@ -457,6 +462,7 @@ enum FieldType {
DROPDOWN
}
/// @zod.import(["import { ZFieldMetaNotOptionalSchema } from '@documenso/lib/types/field-meta';"])
model Field {
id Int @id @default(autoincrement())
secondaryId String @unique @default(cuid())
@ -475,7 +481,7 @@ model Field {
template Template? @relation(fields: [templateId], references: [id], onDelete: Cascade)
recipient Recipient @relation(fields: [recipientId], references: [id], onDelete: Cascade)
signature Signature?
fieldMeta Json? // Todo: Fix ZFieldMetaSchema before using it here.
fieldMeta Json? /// [FieldMeta] @zod.custom.use(ZFieldMetaNotOptionalSchema)
@@index([documentId])
@@index([templateId])
@ -640,6 +646,7 @@ enum TemplateType {
PRIVATE
}
/// @zod.import(["import { ZDocumentEmailSettingsSchema } from '@documenso/lib/types/document-email';"])
model TemplateMeta {
id String @id @default(cuid())
subject String?
@ -655,9 +662,10 @@ model TemplateMeta {
template Template @relation(fields: [templateId], references: [id], onDelete: Cascade)
redirectUrl String?
language String @default("en")
emailSettings Json?
emailSettings Json? /// [DocumentEmailSettings] @zod.custom.use(ZDocumentEmailSettingsSchema)
}
/// @zod.import(["import { ZDocumentAuthOptionsSchema } from '@documenso/lib/types/document-auth';"])
model Template {
id Int @id @default(autoincrement())
externalId String?
@ -666,7 +674,7 @@ model Template {
userId Int
teamId Int?
visibility DocumentVisibility @default(EVERYONE)
authOptions Json?
authOptions Json? /// [DocumentAuthOptions] @zod.custom.use(ZDocumentAuthOptionsSchema)
templateMeta TemplateMeta?
templateDocumentDataId String
createdAt DateTime @default(now())

25
packages/prisma/types/types.d.ts vendored Normal file
View File

@ -0,0 +1,25 @@
/* eslint-disable @typescript-eslint/no-namespace */
import type {
TDocumentAuthOptions,
TRecipientAuthOptions,
} from '@documenso/lib/types/document-auth';
import type { TDocumentEmailSettings } from '@documenso/lib/types/document-email';
import type { TDocumentFormValues } from '@documenso/lib/types/document-form-values';
import type { TFieldMetaNotOptionalSchema } from '@documenso/lib/types/field-meta';
/**
* Global types for Prisma.Json instances.
*/
declare global {
namespace PrismaJson {
type DocumentFormValues = TDocumentFormValues;
type DocumentAuthOptions = TDocumentAuthOptions;
type DocumentEmailSettings = TDocumentEmailSettings;
type RecipientAuthOptions = TRecipientAuthOptions;
type FieldMeta = TFieldMetaNotOptionalSchema;
}
}
export {};

View File

@ -26,9 +26,9 @@ export const updateSigningPlaceholder = ({ pdf }: UpdateSigningPlaceholderOption
const newByteRange = `[${byteRange.join(' ')}]`.padEnd(byteRangeSlice.length, ' ');
const updatedPdf = Buffer.concat([
pdf.subarray(0, byteRangeStart),
Buffer.from(newByteRange),
pdf.subarray(byteRangeEnd + 1),
new Uint8Array(pdf.subarray(0, byteRangeStart)),
new Uint8Array(Buffer.from(newByteRange)),
new Uint8Array(pdf.subarray(byteRangeEnd + 1)),
]);
if (updatedPdf.length !== length) {

View File

@ -23,13 +23,14 @@ export const signWithGoogleCloudHSM = async ({ pdf }: SignWithGoogleCloudHSMOpti
process.env.NEXT_PRIVATE_SIGNING_GCLOUD_APPLICATION_CREDENTIALS_CONTENTS
) {
if (!fs.existsSync(process.env.GOOGLE_APPLICATION_CREDENTIALS)) {
fs.writeFileSync(
process.env.GOOGLE_APPLICATION_CREDENTIALS,
const contents = new Uint8Array(
Buffer.from(
process.env.NEXT_PRIVATE_SIGNING_GCLOUD_APPLICATION_CREDENTIALS_CONTENTS,
'base64',
),
);
fs.writeFileSync(process.env.GOOGLE_APPLICATION_CREDENTIALS, contents);
}
}
@ -38,8 +39,8 @@ export const signWithGoogleCloudHSM = async ({ pdf }: SignWithGoogleCloudHSMOpti
});
const pdfWithoutSignature = Buffer.concat([
pdfWithPlaceholder.subarray(0, byteRange[1]),
pdfWithPlaceholder.subarray(byteRange[2]),
new Uint8Array(pdfWithPlaceholder.subarray(0, byteRange[1])),
new Uint8Array(pdfWithPlaceholder.subarray(byteRange[2])),
]);
const signatureLength = byteRange[2] - byteRange[1];
@ -70,9 +71,9 @@ export const signWithGoogleCloudHSM = async ({ pdf }: SignWithGoogleCloudHSMOpti
const signatureAsHex = signature.toString('hex');
const signedPdf = Buffer.concat([
pdfWithPlaceholder.subarray(0, byteRange[1]),
Buffer.from(`<${signatureAsHex.padEnd(signatureLength - 2, '0')}>`),
pdfWithPlaceholder.subarray(byteRange[2]),
new Uint8Array(pdfWithPlaceholder.subarray(0, byteRange[1])),
new Uint8Array(Buffer.from(`<${signatureAsHex.padEnd(signatureLength - 2, '0')}>`)),
new Uint8Array(pdfWithPlaceholder.subarray(byteRange[2])),
]);
return signedPdf;

View File

@ -15,8 +15,8 @@ export const signWithLocalCert = async ({ pdf }: SignWithLocalCertOptions) => {
});
const pdfWithoutSignature = Buffer.concat([
pdfWithPlaceholder.subarray(0, byteRange[1]),
pdfWithPlaceholder.subarray(byteRange[2]),
new Uint8Array(pdfWithPlaceholder.subarray(0, byteRange[1])),
new Uint8Array(pdfWithPlaceholder.subarray(byteRange[2])),
]);
const signatureLength = byteRange[2] - byteRange[1];
@ -51,9 +51,9 @@ export const signWithLocalCert = async ({ pdf }: SignWithLocalCertOptions) => {
const signatureAsHex = signature.toString('hex');
const signedPdf = Buffer.concat([
pdfWithPlaceholder.subarray(0, byteRange[1]),
Buffer.from(`<${signatureAsHex.padEnd(signatureLength - 2, '0')}>`),
pdfWithPlaceholder.subarray(byteRange[2]),
new Uint8Array(pdfWithPlaceholder.subarray(0, byteRange[1])),
new Uint8Array(Buffer.from(`<${signatureAsHex.padEnd(signatureLength - 2, '0')}>`)),
new Uint8Array(pdfWithPlaceholder.subarray(byteRange[2])),
]);
return signedPdf;

View File

@ -23,7 +23,7 @@
"@types/react": "^18",
"@types/react-dom": "^18",
"react": "^18",
"typescript": "5.2.2"
"typescript": "5.6.2"
},
"dependencies": {
"@documenso/lib": "*",