diff --git a/package-lock.json b/package-lock.json index 5194b44e2..046b294b9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7415,16 +7415,16 @@ "crypto-js": "^4.2.0" } }, - "node_modules/@yarnpkg/lockfile": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz", - "integrity": "sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ==" - }, "node_modules/@vvo/tzdb": { "version": "6.117.0", "resolved": "https://registry.npmjs.org/@vvo/tzdb/-/tzdb-6.117.0.tgz", "integrity": "sha512-vZkfoag1kHqItK/zebxT0Fkt3R/zscjgD+Ib7kaAdum0Sz9psXDfVHPW1Benv91d02zPWlLIvZtjBmzX4a+6fw==" }, + "node_modules/@yarnpkg/lockfile": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz", + "integrity": "sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ==" + }, "node_modules/abbrev": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", @@ -15182,17 +15182,6 @@ "tslib": "^2.0.3" } }, - "node_modules/node-abi": { - "version": "3.51.0", - "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.51.0.tgz", - "integrity": "sha512-SQkEP4hmNWjlniS5zdnfIXTk1x7Ome85RDzHlTbBtzE97Gfwz/Ipw4v/Ryk20DWIy3yCNVLVlGKApCnmvYoJbA==", - "dependencies": { - "semver": "^7.3.5" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/node-abort-controller": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/node-abort-controller/-/node-abort-controller-3.1.1.tgz", @@ -18264,82 +18253,6 @@ "sha.js": "bin.js" } }, - "node_modules/sharp": { - "version": "0.32.5", - "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.32.5.tgz", - "integrity": "sha512-0dap3iysgDkNaPOaOL4X/0akdu0ma62GcdC2NBQ+93eqpePdDdr2/LM0sFdDSMmN7yS+odyZtPsb7tx/cYBKnQ==", - "hasInstallScript": true, - "dependencies": { - "color": "^4.2.3", - "detect-libc": "^2.0.2", - "node-addon-api": "^6.1.0", - "prebuild-install": "^7.1.1", - "semver": "^7.5.4", - "simple-get": "^4.0.1", - "tar-fs": "^3.0.4", - "tunnel-agent": "^0.6.0" - }, - "engines": { - "node": ">=14.15.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/sharp/node_modules/decompress-response": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", - "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", - "dependencies": { - "mimic-response": "^3.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/sharp/node_modules/mimic-response": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", - "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/sharp/node_modules/node-addon-api": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-6.1.0.tgz", - "integrity": "sha512-+eawOlIgy680F0kBzPUNFhMZGtJ1YmqM6l4+Crf4IkImjYrO/mqPwRMh352g23uIaQKFItcQ64I7KMaJxHgAVA==" - }, - "node_modules/sharp/node_modules/simple-get": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz", - "integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "dependencies": { - "decompress-response": "^6.0.0", - "once": "^1.3.1", - "simple-concat": "^1.0.0" - } - }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", diff --git a/packages/api/v1/contract.ts b/packages/api/v1/contract.ts index a7dd27f7c..b0796815c 100644 --- a/packages/api/v1/contract.ts +++ b/packages/api/v1/contract.ts @@ -3,6 +3,7 @@ import { initContract } from '@ts-rest/core'; import { ZSendDocumentForSigningMutationSchema as SendDocumentMutationSchema, ZAuthorizationHeadersSchema, + ZCreateDocumentMutationResponseSchema, ZCreateDocumentMutationSchema, ZCreateFieldMutationSchema, ZCreateRecipientMutationSchema, @@ -18,7 +19,6 @@ import { ZUnsuccessfulResponseSchema, ZUpdateFieldMutationSchema, ZUpdateRecipientMutationSchema, - ZUploadDocumentSuccessfulSchema, } from './schema'; const c = initContract(); @@ -53,7 +53,7 @@ export const ApiContractV1 = c.router( path: '/api/v1/documents', body: ZCreateDocumentMutationSchema, responses: { - 200: ZUploadDocumentSuccessfulSchema, + 200: ZCreateDocumentMutationResponseSchema, 401: ZUnsuccessfulResponseSchema, 404: ZUnsuccessfulResponseSchema, }, diff --git a/packages/api/v1/implementation.ts b/packages/api/v1/implementation.ts index e9b710c46..4e4af848d 100644 --- a/packages/api/v1/implementation.ts +++ b/packages/api/v1/implementation.ts @@ -1,5 +1,7 @@ import { createNextRoute } from '@ts-rest/next'; +import { createDocumentData } from '@documenso/lib/server-only/document-data/create-document-data'; +import { createDocument } from '@documenso/lib/server-only/document/create-document'; import { deleteDocument } from '@documenso/lib/server-only/document/delete-document'; import { findDocuments } from '@documenso/lib/server-only/document/find-documents'; import { getDocumentById } from '@documenso/lib/server-only/document/get-document-by-id'; @@ -14,7 +16,7 @@ import { getRecipientsForDocument } from '@documenso/lib/server-only/recipient/g import { setRecipientsForDocument } from '@documenso/lib/server-only/recipient/set-recipients-for-document'; import { updateRecipient } from '@documenso/lib/server-only/recipient/update-recipient'; import { getPresignPostUrl } from '@documenso/lib/universal/upload/server-actions'; -import { DocumentStatus, SigningStatus } from '@documenso/prisma/client'; +import { DocumentDataType, DocumentStatus, SigningStatus } from '@documenso/prisma/client'; import { ApiContractV1 } from './contract'; import { authenticatedMiddleware } from './middleware/authenticated'; @@ -81,17 +83,50 @@ export const ApiContractV1Implementation = createNextRoute(ApiContractV1, { } }), - createDocument: authenticatedMiddleware(async (args, _user) => { + createDocument: authenticatedMiddleware(async (args, user) => { const { body } = args; try { - const { url, key } = await getPresignPostUrl(body.fileName, body.contentType); + if (process.env.NEXT_PUBLIC_UPLOAD_TRANSPORT !== 's3') { + return { + status: 500, + body: { + message: 'Create document is not available without S3 transport.', + }, + }; + } + + const fileName = body.title.endsWith('.pdf') ? body.title : `${body.title}.pdf`; + + const { url, key } = await getPresignPostUrl(fileName, 'application/pdf'); + + const documentData = await createDocumentData({ + data: key, + type: DocumentDataType.S3_PATH, + }); + + const document = await createDocument({ + title: body.title, + userId: user.id, + documentDataId: documentData.id, + }); + + const recipients = await setRecipientsForDocument({ + userId: user.id, + documentId: document.id, + recipients: body.recipients, + }); return { status: 200, body: { - url, - key, + uploadUrl: url, + documentId: document.id, + recipients: recipients.map((recipient) => ({ + recipientId: recipient.id, + token: recipient.token, + role: recipient.role, + })), }, }; } catch (err) { @@ -184,7 +219,7 @@ export const ApiContractV1Implementation = createNextRoute(ApiContractV1, { createRecipient: authenticatedMiddleware(async (args, user) => { const { id: documentId } = args.params; - const { name, email } = args.body; + const { name, email, role } = args.body; const document = await getDocumentById({ id: Number(documentId), @@ -234,6 +269,7 @@ export const ApiContractV1Implementation = createNextRoute(ApiContractV1, { { email, name, + role, }, ], }); @@ -246,7 +282,10 @@ export const ApiContractV1Implementation = createNextRoute(ApiContractV1, { return { status: 200, - body: newRecipient, + body: { + ...newRecipient, + documentId: Number(documentId), + }, }; } catch (err) { return { @@ -260,7 +299,7 @@ export const ApiContractV1Implementation = createNextRoute(ApiContractV1, { updateRecipient: authenticatedMiddleware(async (args, user) => { const { id: documentId, recipientId } = args.params; - const { name, email } = args.body; + const { name, email, role } = args.body; const document = await getDocumentById({ id: Number(documentId), @@ -290,6 +329,7 @@ export const ApiContractV1Implementation = createNextRoute(ApiContractV1, { recipientId: Number(recipientId), email, name, + role, }).catch(() => null); if (!updatedRecipient) { @@ -303,7 +343,10 @@ export const ApiContractV1Implementation = createNextRoute(ApiContractV1, { return { status: 200, - body: updatedRecipient, + body: { + ...updatedRecipient, + documentId: Number(documentId), + }, }; }), @@ -349,7 +392,10 @@ export const ApiContractV1Implementation = createNextRoute(ApiContractV1, { return { status: 200, - body: deletedRecipient, + body: { + ...deletedRecipient, + documentId: Number(documentId), + }, }; }), @@ -429,7 +475,10 @@ export const ApiContractV1Implementation = createNextRoute(ApiContractV1, { return { status: 200, - body: remappedField, + body: { + ...remappedField, + documentId: Number(documentId), + }, }; }), @@ -510,7 +559,10 @@ export const ApiContractV1Implementation = createNextRoute(ApiContractV1, { return { status: 200, - body: remappedField, + body: { + ...remappedField, + documentId: Number(documentId), + }, }; }), @@ -597,7 +649,10 @@ export const ApiContractV1Implementation = createNextRoute(ApiContractV1, { return { status: 200, - body: remappedField, + body: { + ...remappedField, + documentId: Number(documentId), + }, }; }), }); diff --git a/packages/api/v1/schema.ts b/packages/api/v1/schema.ts index f6fba2f0f..091e01fb6 100644 --- a/packages/api/v1/schema.ts +++ b/packages/api/v1/schema.ts @@ -1,6 +1,12 @@ import { z } from 'zod'; -import { FieldType, ReadStatus, SendStatus, SigningStatus } from '@documenso/prisma/client'; +import { + FieldType, + ReadStatus, + RecipientRole, + SendStatus, + SigningStatus, +} from '@documenso/prisma/client'; /** * Documents @@ -41,15 +47,45 @@ export const ZUploadDocumentSuccessfulSchema = z.object({ export type TUploadDocumentSuccessfulSchema = z.infer; export const ZCreateDocumentMutationSchema = z.object({ - fileName: z.string(), - contentType: z.string().default('PDF'), + title: z.string().min(1), + recipients: z.array( + z.object({ + name: z.string().min(1), + email: z.string().email().min(1), + role: z.nativeEnum(RecipientRole).optional().default(RecipientRole.SIGNER), + }), + ), + meta: z.object({ + subject: z.string(), + message: z.string(), + timezone: z.string(), + dateFormat: z.string(), + redirectUrl: z.string(), + }), }); export type TCreateDocumentMutationSchema = z.infer; +export const ZCreateDocumentMutationResponseSchema = z.object({ + uploadUrl: z.string().min(1), + documentId: z.number(), + recipients: z.array( + z.object({ + recipientId: z.number(), + token: z.string(), + role: z.nativeEnum(RecipientRole), + }), + ), +}); + +export type TCreateDocumentMutationResponseSchema = z.infer< + typeof ZCreateDocumentMutationResponseSchema +>; + export const ZCreateRecipientMutationSchema = z.object({ name: z.string().min(1), email: z.string().email().min(1), + role: z.nativeEnum(RecipientRole).optional().default(RecipientRole.SIGNER), }); /** @@ -70,6 +106,7 @@ export const ZSuccessfulRecipientResponseSchema = z.object({ documentId: z.number(), email: z.string().email().min(1), name: z.string(), + role: z.nativeEnum(RecipientRole), token: z.string(), // !: Not used for now // expired: z.string(), diff --git a/packages/lib/server-only/recipient/update-recipient.ts b/packages/lib/server-only/recipient/update-recipient.ts index 0b1fa046d..e1d28ca13 100644 --- a/packages/lib/server-only/recipient/update-recipient.ts +++ b/packages/lib/server-only/recipient/update-recipient.ts @@ -1,10 +1,12 @@ import { prisma } from '@documenso/prisma'; +import type { RecipientRole } from '@documenso/prisma/client'; export type UpdateRecipientOptions = { documentId: number; recipientId: number; email?: string; name?: string; + role?: RecipientRole; }; export const updateRecipient = async ({ @@ -12,6 +14,7 @@ export const updateRecipient = async ({ recipientId, email, name, + role, }: UpdateRecipientOptions) => { const recipient = await prisma.recipient.findFirst({ where: { @@ -31,6 +34,7 @@ export const updateRecipient = async ({ data: { email: email?.toLowerCase() ?? recipient.email, name: name ?? recipient.name, + role: role ?? recipient.role, }, });