From 4b90adde6baf5714f9bb27f655af0981c4309d7a Mon Sep 17 00:00:00 2001 From: Catalin Pit <25515812+catalinpit@users.noreply.github.com> Date: Fri, 19 Apr 2024 14:04:11 +0300 Subject: [PATCH] feat: download completed docs via api (#1078) ## Description Allow users to download a completed document via API. ## Testing Performed Tested the code locally by trying to download both draft and completed docs. Works as expected. ## Checklist - [x] I have tested these changes locally and they work as expected. - [ ] I have added/updated tests that prove the effectiveness of these changes. - [ ] 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. ## Summary by CodeRabbit - **New Features** - Implemented functionality to download signed documents directly from the app. --- packages/api/v1/contract.ts | 12 ++++++ packages/api/v1/implementation.ts | 67 ++++++++++++++++++++++++++++++- packages/api/v1/schema.ts | 4 ++ 3 files changed, 82 insertions(+), 1 deletion(-) diff --git a/packages/api/v1/contract.ts b/packages/api/v1/contract.ts index 162cdcf9d..ca2b6e2f5 100644 --- a/packages/api/v1/contract.ts +++ b/packages/api/v1/contract.ts @@ -11,6 +11,7 @@ import { ZDeleteDocumentMutationSchema, ZDeleteFieldMutationSchema, ZDeleteRecipientMutationSchema, + ZDownloadDocumentSuccessfulSchema, ZGetDocumentsQuerySchema, ZSendDocumentForSigningMutationSchema, ZSuccessfulDocumentResponseSchema, @@ -51,6 +52,17 @@ export const ApiContractV1 = c.router( summary: 'Get a single document', }, + downloadSignedDocument: { + method: 'GET', + path: '/api/v1/documents/:id/download', + responses: { + 200: ZDownloadDocumentSuccessfulSchema, + 401: ZUnsuccessfulResponseSchema, + 404: ZUnsuccessfulResponseSchema, + }, + summary: 'Download a signed document when the storage transport is S3', + }, + createDocument: { method: 'POST', path: '/api/v1/documents', diff --git a/packages/api/v1/implementation.ts b/packages/api/v1/implementation.ts index d9bc1a6d7..8ee0350bd 100644 --- a/packages/api/v1/implementation.ts +++ b/packages/api/v1/implementation.ts @@ -23,7 +23,10 @@ import { createDocumentFromTemplate } from '@documenso/lib/server-only/template/ import { extractNextApiRequestMetadata } from '@documenso/lib/universal/extract-request-metadata'; import { getFile } from '@documenso/lib/universal/upload/get-file'; import { putFile } from '@documenso/lib/universal/upload/put-file'; -import { getPresignPostUrl } from '@documenso/lib/universal/upload/server-actions'; +import { + getPresignGetUrl, + getPresignPostUrl, +} from '@documenso/lib/universal/upload/server-actions'; import { DocumentDataType, DocumentStatus, SigningStatus } from '@documenso/prisma/client'; import { ApiContractV1 } from './contract'; @@ -83,6 +86,68 @@ export const ApiContractV1Implementation = createNextRoute(ApiContractV1, { } }), + downloadSignedDocument: authenticatedMiddleware(async (args, user, team) => { + const { id: documentId } = args.params; + + try { + if (process.env.NEXT_PUBLIC_UPLOAD_TRANSPORT !== 's3') { + return { + status: 500, + body: { + message: 'Please make sure the storage transport is set to S3.', + }, + }; + } + + const document = await getDocumentById({ + id: Number(documentId), + userId: user.id, + teamId: team?.id, + }); + + if (!document || !document.documentDataId) { + return { + status: 404, + body: { + message: 'Document not found', + }, + }; + } + + if (DocumentDataType.S3_PATH !== document.documentData.type) { + return { + status: 400, + body: { + message: 'Invalid document data type', + }, + }; + } + + if (document.status !== DocumentStatus.COMPLETED) { + return { + status: 400, + body: { + message: 'Document is not completed yet.', + }, + }; + } + + const { url } = await getPresignGetUrl(document.documentData.data); + + return { + status: 200, + body: { downloadUrl: url }, + }; + } catch (err) { + return { + status: 500, + body: { + message: 'Error downloading the document. Please try again.', + }, + }; + } + }), + deleteDocument: authenticatedMiddleware(async (args, user, team) => { const { id: documentId } = args.params; diff --git a/packages/api/v1/schema.ts b/packages/api/v1/schema.ts index 01f6e2d58..be0ea1271 100644 --- a/packages/api/v1/schema.ts +++ b/packages/api/v1/schema.ts @@ -53,6 +53,10 @@ export const ZUploadDocumentSuccessfulSchema = z.object({ key: z.string(), }); +export const ZDownloadDocumentSuccessfulSchema = z.object({ + downloadUrl: z.string(), +}); + export type TUploadDocumentSuccessfulSchema = z.infer; export const ZCreateDocumentMutationSchema = z.object({