diff --git a/apps/remix/app/components/embed/embed-direct-template-client-page.tsx b/apps/remix/app/components/embed/embed-direct-template-client-page.tsx index 09f6a91d2..0158d83a8 100644 --- a/apps/remix/app/components/embed/embed-direct-template-client-page.tsx +++ b/apps/remix/app/components/embed/embed-direct-template-client-page.tsx @@ -3,7 +3,7 @@ import { useEffect, useLayoutEffect, useState } from 'react'; import { msg } from '@lingui/core/macro'; import { useLingui } from '@lingui/react'; import { Trans } from '@lingui/react/macro'; -import type { DocumentMeta, Recipient, Signature, TemplateMeta } from '@prisma/client'; +import type { DocumentMeta, Recipient, Signature } from '@prisma/client'; import { type DocumentData, type Field, FieldType } from '@prisma/client'; import { LucideChevronDown, LucideChevronUp } from 'lucide-react'; import { DateTime } from 'luxon'; @@ -48,7 +48,7 @@ export type EmbedDirectTemplateClientPageProps = { documentData: DocumentData; recipient: Recipient; fields: Field[]; - metadata?: DocumentMeta | TemplateMeta | null; + metadata?: DocumentMeta | null; hidePoweredBy?: boolean; allowWhiteLabelling?: boolean; }; diff --git a/apps/remix/app/components/embed/embed-document-fields.tsx b/apps/remix/app/components/embed/embed-document-fields.tsx index 561fdf4cb..dc1115226 100644 --- a/apps/remix/app/components/embed/embed-document-fields.tsx +++ b/apps/remix/app/components/embed/embed-document-fields.tsx @@ -1,4 +1,4 @@ -import type { DocumentMeta, TemplateMeta } from '@prisma/client'; +import type { DocumentMeta } from '@prisma/client'; import { type Field, FieldType } from '@prisma/client'; import { match } from 'ts-pattern'; @@ -33,7 +33,7 @@ import { DocumentSigningTextField } from '~/components/general/document-signing/ export type EmbedDocumentFieldsProps = { fields: Field[]; metadata?: Pick< - DocumentMeta | TemplateMeta, + DocumentMeta, | 'timezone' | 'dateFormat' | 'typedSignatureEnabled' diff --git a/apps/remix/app/components/embed/embed-document-signing-page.tsx b/apps/remix/app/components/embed/embed-document-signing-page.tsx index ef2eedc1c..0274fb26f 100644 --- a/apps/remix/app/components/embed/embed-document-signing-page.tsx +++ b/apps/remix/app/components/embed/embed-document-signing-page.tsx @@ -3,7 +3,7 @@ import { useEffect, useId, useLayoutEffect, useMemo, useState } from 'react'; import { msg } from '@lingui/core/macro'; import { useLingui } from '@lingui/react'; import { Trans } from '@lingui/react/macro'; -import type { DocumentMeta, TemplateMeta } from '@prisma/client'; +import type { DocumentMeta } from '@prisma/client'; import { type DocumentData, type Field, @@ -50,7 +50,7 @@ export type EmbedSignDocumentClientPageProps = { recipient: RecipientWithFields; fields: Field[]; completedFields: DocumentField[]; - metadata?: DocumentMeta | TemplateMeta | null; + metadata?: DocumentMeta | null; isCompleted?: boolean; hidePoweredBy?: boolean; allowWhitelabelling?: boolean; diff --git a/packages/api/v1/implementation.ts b/packages/api/v1/implementation.ts index e8afd9af4..25e7e6932 100644 --- a/packages/api/v1/implementation.ts +++ b/packages/api/v1/implementation.ts @@ -554,6 +554,12 @@ export const ApiContractV1Implementation = tsr.router(ApiContractV1, { status: 200, body: { ...template, + templateMeta: template.templateMeta + ? { + ...template.templateMeta, + templateId: template.id, + } + : null, Field: template.fields.map((field) => ({ ...field, fieldMeta: field.fieldMeta ? ZFieldMetaSchema.parse(field.fieldMeta) : null, diff --git a/packages/lib/server-only/document/create-document-v2.ts b/packages/lib/server-only/document/create-document-v2.ts index a381fd238..08d62436e 100644 --- a/packages/lib/server-only/document/create-document-v2.ts +++ b/packages/lib/server-only/document/create-document-v2.ts @@ -1,4 +1,4 @@ -import type { DocumentVisibility, TemplateMeta } from '@prisma/client'; +import type { DocumentMeta, DocumentVisibility } from '@prisma/client'; import { DocumentSource, RecipientRole, @@ -46,7 +46,7 @@ export type CreateDocumentOptions = { formValues?: TDocumentFormValues; recipients: TCreateDocumentV2Request['recipients']; }; - meta?: Partial>; + meta?: Partial>; requestMetadata: ApiRequestMetadata; }; diff --git a/packages/lib/server-only/document/get-document-meta-by-document-id.ts b/packages/lib/server-only/document/get-document-meta-by-document-id.ts deleted file mode 100644 index 575ba5d6e..000000000 --- a/packages/lib/server-only/document/get-document-meta-by-document-id.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { prisma } from '@documenso/prisma'; - -export interface GetDocumentMetaByDocumentIdOptions { - id: number; -} - -export const getDocumentMetaByDocumentId = async ({ id }: GetDocumentMetaByDocumentIdOptions) => { - return await prisma.documentMeta.findFirstOrThrow({ - where: { - documentId: id, - }, - }); -}; diff --git a/packages/lib/server-only/template/create-template.ts b/packages/lib/server-only/template/create-template.ts index 5019407a2..a733501cd 100644 --- a/packages/lib/server-only/template/create-template.ts +++ b/packages/lib/server-only/template/create-template.ts @@ -1,4 +1,4 @@ -import type { DocumentVisibility, Template, TemplateMeta } from '@prisma/client'; +import type { DocumentMeta, DocumentVisibility, Template } from '@prisma/client'; import type { z } from 'zod'; import { prisma } from '@documenso/prisma'; @@ -26,7 +26,7 @@ export type CreateTemplateOptions = { publicDescription?: string; type?: Template['type']; }; - meta?: Partial>; + meta?: Partial>; }; export const ZCreateTemplateResponseSchema = TemplateSchema; diff --git a/packages/lib/server-only/template/update-template.ts b/packages/lib/server-only/template/update-template.ts index 65314dd48..3270c13f9 100644 --- a/packages/lib/server-only/template/update-template.ts +++ b/packages/lib/server-only/template/update-template.ts @@ -1,4 +1,4 @@ -import type { DocumentVisibility, Template, TemplateMeta } from '@prisma/client'; +import type { DocumentMeta, DocumentVisibility, Template } from '@prisma/client'; import { prisma } from '@documenso/prisma'; @@ -22,7 +22,7 @@ export type UpdateTemplateOptions = { type?: Template['type']; useLegacyFieldInsertion?: boolean; }; - meta?: Partial>; + meta?: Partial>; }; export const updateTemplate = async ({ diff --git a/packages/lib/types/template.ts b/packages/lib/types/template.ts index 1171ad18d..de6708c7c 100644 --- a/packages/lib/types/template.ts +++ b/packages/lib/types/template.ts @@ -1,10 +1,10 @@ import type { z } from 'zod'; import { DocumentDataSchema } from '@documenso/prisma/generated/zod/modelSchema/DocumentDataSchema'; +import { DocumentMetaSchema } from '@documenso/prisma/generated/zod/modelSchema/DocumentMetaSchema'; import { FolderSchema } from '@documenso/prisma/generated/zod/modelSchema/FolderSchema'; import TeamSchema from '@documenso/prisma/generated/zod/modelSchema/TeamSchema'; import { TemplateDirectLinkSchema } from '@documenso/prisma/generated/zod/modelSchema/TemplateDirectLinkSchema'; -import { TemplateMetaSchema } from '@documenso/prisma/generated/zod/modelSchema/TemplateMetaSchema'; import { TemplateSchema } from '@documenso/prisma/generated/zod/modelSchema/TemplateSchema'; import { UserSchema } from '@documenso/prisma/generated/zod/modelSchema/UserSchema'; @@ -39,7 +39,7 @@ export const ZTemplateSchema = TemplateSchema.pick({ data: true, initialData: true, }), - templateMeta: TemplateMetaSchema.pick({ + templateMeta: DocumentMetaSchema.pick({ id: true, subject: true, message: true, @@ -129,7 +129,7 @@ export const ZTemplateManySchema = TemplateSchema.pick({ }).nullable(), fields: ZFieldSchema.array(), recipients: ZRecipientLiteSchema.array(), - templateMeta: TemplateMetaSchema.pick({ + templateMeta: DocumentMetaSchema.pick({ signingOrder: true, distributionMethod: true, }).nullable(), diff --git a/packages/lib/utils/document.ts b/packages/lib/utils/document.ts index 8bae70ca2..db2310a8f 100644 --- a/packages/lib/utils/document.ts +++ b/packages/lib/utils/document.ts @@ -1,9 +1,4 @@ -import type { - Document, - DocumentMeta, - OrganisationGlobalSettings, - TemplateMeta, -} from '@prisma/client'; +import type { Document, DocumentMeta, OrganisationGlobalSettings } from '@prisma/client'; import { DocumentDistributionMethod, DocumentSigningOrder, DocumentStatus } from '@prisma/client'; import { DEFAULT_DOCUMENT_TIME_ZONE } from '../constants/time-zones'; @@ -29,7 +24,7 @@ export const isDocumentCompleted = (document: Pick | Documen */ export const extractDerivedDocumentMeta = ( settings: Omit, - overrideMeta: Partial | undefined | null, + overrideMeta: Partial | undefined | null, ) => { const meta = overrideMeta ?? {}; @@ -58,5 +53,5 @@ export const extractDerivedDocumentMeta = ( emailReplyTo: meta.emailReplyTo ?? settings.emailReplyTo, emailSettings: meta.emailSettings || settings.emailDocumentSettings || DEFAULT_DOCUMENT_EMAIL_SETTINGS, - } satisfies Omit; + } satisfies Omit; }; diff --git a/packages/prisma/migrations/20250821063052_remove_template_metadata/migration.sql b/packages/prisma/migrations/20250821063052_remove_template_metadata/migration.sql new file mode 100644 index 000000000..6cb085e93 --- /dev/null +++ b/packages/prisma/migrations/20250821063052_remove_template_metadata/migration.sql @@ -0,0 +1,57 @@ +-- DropForeignKey +ALTER TABLE "TemplateMeta" DROP CONSTRAINT "TemplateMeta_templateId_fkey"; + +-- AlterTable +ALTER TABLE "DocumentMeta" ADD COLUMN "templateId" INTEGER, +ALTER COLUMN "documentId" DROP NOT NULL; + +-- [CUSTOM_CHANGE] Migrate existing TemplateMeta to DocumentMeta +INSERT INTO "DocumentMeta" ( + "id", + "subject", + "message", + "timezone", + "password", + "dateFormat", + "redirectUrl", + "signingOrder", + "allowDictateNextSigner", + "typedSignatureEnabled", + "uploadSignatureEnabled", + "drawSignatureEnabled", + "language", + "distributionMethod", + "emailSettings", + "emailReplyTo", + "emailId", + "templateId" +) +SELECT + gen_random_uuid()::text, -- Generate new CUID-like IDs to avoid collisions + "subject", + "message", + "timezone", + "password", + "dateFormat", + "redirectUrl", + "signingOrder", + "allowDictateNextSigner", + "typedSignatureEnabled", + "uploadSignatureEnabled", + "drawSignatureEnabled", + "language", + "distributionMethod", + "emailSettings", + "emailReplyTo", + "emailId", + "templateId" +FROM "TemplateMeta"; + +-- DropTable +DROP TABLE "TemplateMeta"; + +-- CreateIndex +CREATE UNIQUE INDEX "DocumentMeta_templateId_key" ON "DocumentMeta"("templateId"); + +-- AddForeignKey +ALTER TABLE "DocumentMeta" ADD CONSTRAINT "DocumentMeta_templateId_fkey" FOREIGN KEY ("templateId") REFERENCES "Template"("id") ON DELETE CASCADE ON UPDATE CASCADE; diff --git a/packages/prisma/schema.prisma b/packages/prisma/schema.prisma index 14eb7f626..b1c14d798 100644 --- a/packages/prisma/schema.prisma +++ b/packages/prisma/schema.prisma @@ -456,8 +456,6 @@ model DocumentMeta { timezone String? @default("Etc/UTC") @db.Text password String? dateFormat String? @default("yyyy-MM-dd hh:mm a") @db.Text - documentId Int @unique - document Document @relation(fields: [documentId], references: [id], onDelete: Cascade) redirectUrl String? signingOrder DocumentSigningOrder @default(PARALLEL) allowDictateNextSigner Boolean @default(false) @@ -472,6 +470,12 @@ model DocumentMeta { emailSettings Json? /// [DocumentEmailSettings] @zod.custom.use(ZDocumentEmailSettingsSchema) emailReplyTo String? emailId String? + + documentId Int? @unique + document Document? @relation(fields: [documentId], references: [id], onDelete: Cascade) + + templateId Int? @unique + template Template? @relation(fields: [templateId], references: [id], onDelete: Cascade) } enum ReadStatus { @@ -842,32 +846,6 @@ enum TemplateType { PRIVATE } -/// @zod.import(["import { ZDocumentEmailSettingsSchema } from '@documenso/lib/types/document-email';"]) -model TemplateMeta { - id String @id @default(cuid()) - subject String? - message String? - timezone String? @default("Etc/UTC") @db.Text - password String? - dateFormat String? @default("yyyy-MM-dd hh:mm a") @db.Text - signingOrder DocumentSigningOrder? @default(PARALLEL) - allowDictateNextSigner Boolean @default(false) - distributionMethod DocumentDistributionMethod @default(EMAIL) - - typedSignatureEnabled Boolean @default(true) - uploadSignatureEnabled Boolean @default(true) - drawSignatureEnabled Boolean @default(true) - - templateId Int @unique - template Template @relation(fields: [templateId], references: [id], onDelete: Cascade) - redirectUrl String? - language String @default("en") - - emailSettings Json? /// [DocumentEmailSettings] @zod.custom.use(ZDocumentEmailSettingsSchema) - emailReplyTo String? - emailId String? -} - /// @zod.import(["import { ZDocumentAuthOptionsSchema } from '@documenso/lib/types/document-auth';"]) model Template { id Int @id @default(autoincrement()) @@ -876,7 +854,7 @@ model Template { title String visibility DocumentVisibility @default(EVERYONE) authOptions Json? /// [DocumentAuthOptions] @zod.custom.use(ZDocumentAuthOptionsSchema) - templateMeta TemplateMeta? + templateMeta DocumentMeta? templateDocumentDataId String createdAt DateTime @default(now()) updatedAt DateTime @default(now()) @updatedAt diff --git a/packages/trpc/server/embedding-router/create-embedding-template.ts b/packages/trpc/server/embedding-router/create-embedding-template.ts index 5dbc40cd9..d7c1aeb21 100644 --- a/packages/trpc/server/embedding-router/create-embedding-template.ts +++ b/packages/trpc/server/embedding-router/create-embedding-template.ts @@ -94,7 +94,7 @@ export const createEmbeddingTemplateRoute = procedure emailSettings: meta.emailSettings, }; - await prisma.templateMeta.upsert({ + await prisma.documentMeta.upsert({ where: { templateId: template.id, }, diff --git a/packages/ui/components/document/document-read-only-fields.tsx b/packages/ui/components/document/document-read-only-fields.tsx index 0786357d1..e2eb2f0be 100644 --- a/packages/ui/components/document/document-read-only-fields.tsx +++ b/packages/ui/components/document/document-read-only-fields.tsx @@ -2,7 +2,7 @@ import { useState } from 'react'; import { useLingui } from '@lingui/react'; import { Trans } from '@lingui/react/macro'; -import type { DocumentMeta, Field, Recipient, TemplateMeta } from '@prisma/client'; +import type { DocumentMeta, Field, Recipient } from '@prisma/client'; import { SigningStatus } from '@prisma/client'; import { Clock, EyeOffIcon } from 'lucide-react'; @@ -36,7 +36,7 @@ const getRecipientDisplayText = (recipient: { name: string; email: string }) => export type DocumentReadOnlyFieldsProps = { fields: DocumentField[]; - documentMeta?: Pick; + documentMeta?: Pick; showFieldStatus?: boolean; diff --git a/packages/ui/primitives/document-flow/field-content.tsx b/packages/ui/primitives/document-flow/field-content.tsx index f8eec0092..4ea926013 100644 --- a/packages/ui/primitives/document-flow/field-content.tsx +++ b/packages/ui/primitives/document-flow/field-content.tsx @@ -1,5 +1,5 @@ import { useLingui } from '@lingui/react'; -import type { DocumentMeta, Signature, TemplateMeta } from '@prisma/client'; +import type { DocumentMeta, Signature } from '@prisma/client'; import { FieldType } from '@prisma/client'; import { ChevronDown } from 'lucide-react'; @@ -27,7 +27,7 @@ type FieldIconProps = { fieldMeta?: TFieldMetaSchema | null; signature?: Signature | null; }; - documentMeta?: Pick; + documentMeta?: Pick; }; /**