mirror of
https://github.com/documenso/documenso.git
synced 2025-11-14 00:32:43 +10:00
feat: document authoring
This commit is contained in:
@ -0,0 +1,92 @@
|
||||
'use server';
|
||||
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import { DocumentStatus, SigningStatus } from '@documenso/prisma/client';
|
||||
|
||||
import { sealDocument } from './seal-document';
|
||||
|
||||
export type CompleteDocumentWithTokenOptions = {
|
||||
token: string;
|
||||
documentId: number;
|
||||
};
|
||||
|
||||
export const completeDocumentWithToken = async ({
|
||||
token,
|
||||
documentId,
|
||||
}: CompleteDocumentWithTokenOptions) => {
|
||||
'use server';
|
||||
|
||||
const document = await prisma.document.findFirstOrThrow({
|
||||
where: {
|
||||
id: documentId,
|
||||
Recipient: {
|
||||
some: {
|
||||
token,
|
||||
},
|
||||
},
|
||||
},
|
||||
include: {
|
||||
Recipient: {
|
||||
where: {
|
||||
token,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (document.status === DocumentStatus.COMPLETED) {
|
||||
throw new Error(`Document ${document.id} has already been completed`);
|
||||
}
|
||||
|
||||
if (document.Recipient.length === 0) {
|
||||
throw new Error(`Document ${document.id} has no recipient with token ${token}`);
|
||||
}
|
||||
|
||||
const [recipient] = document.Recipient;
|
||||
|
||||
if (recipient.signingStatus === SigningStatus.SIGNED) {
|
||||
throw new Error(`Recipient ${recipient.id} has already signed`);
|
||||
}
|
||||
|
||||
const fields = await prisma.field.findMany({
|
||||
where: {
|
||||
documentId: document.id,
|
||||
recipientId: recipient.id,
|
||||
},
|
||||
});
|
||||
|
||||
if (fields.some((field) => !field.inserted)) {
|
||||
throw new Error(`Recipient ${recipient.id} has unsigned fields`);
|
||||
}
|
||||
|
||||
await prisma.recipient.update({
|
||||
where: {
|
||||
id: recipient.id,
|
||||
},
|
||||
data: {
|
||||
signingStatus: SigningStatus.SIGNED,
|
||||
signedAt: new Date(),
|
||||
},
|
||||
});
|
||||
|
||||
const documents = await prisma.document.updateMany({
|
||||
where: {
|
||||
id: document.id,
|
||||
Recipient: {
|
||||
every: {
|
||||
signingStatus: SigningStatus.SIGNED,
|
||||
},
|
||||
},
|
||||
},
|
||||
data: {
|
||||
status: DocumentStatus.COMPLETED,
|
||||
},
|
||||
});
|
||||
|
||||
console.log('documents', documents);
|
||||
|
||||
if (documents.count > 0) {
|
||||
console.log('sealing document');
|
||||
sealDocument({ documentId: document.id });
|
||||
}
|
||||
};
|
||||
30
packages/lib/server-only/document/get-document-by-token.ts
Normal file
30
packages/lib/server-only/document/get-document-by-token.ts
Normal file
@ -0,0 +1,30 @@
|
||||
import { prisma } from '@documenso/prisma';
|
||||
|
||||
export interface GetDocumentAndSenderByTokenOptions {
|
||||
token: string;
|
||||
}
|
||||
|
||||
export const getDocumentAndSenderByToken = async ({
|
||||
token,
|
||||
}: GetDocumentAndSenderByTokenOptions) => {
|
||||
const result = await prisma.document.findFirstOrThrow({
|
||||
where: {
|
||||
Recipient: {
|
||||
some: {
|
||||
token,
|
||||
},
|
||||
},
|
||||
},
|
||||
include: {
|
||||
User: true,
|
||||
},
|
||||
});
|
||||
|
||||
// eslint-disable-next-line no-unused-vars, @typescript-eslint/no-unused-vars
|
||||
const { password: _password, ...User } = result.User;
|
||||
|
||||
return {
|
||||
...result,
|
||||
User,
|
||||
};
|
||||
};
|
||||
74
packages/lib/server-only/document/seal-document.ts
Normal file
74
packages/lib/server-only/document/seal-document.ts
Normal file
@ -0,0 +1,74 @@
|
||||
'use server';
|
||||
|
||||
import { PDFDocument } from 'pdf-lib';
|
||||
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import { DocumentStatus, SigningStatus } from '@documenso/prisma/client';
|
||||
|
||||
import { insertFieldInPDF } from '../pdf/insert-field-in-pdf';
|
||||
|
||||
export type SealDocumentOptions = {
|
||||
documentId: number;
|
||||
};
|
||||
|
||||
export const sealDocument = async ({ documentId }: SealDocumentOptions) => {
|
||||
'use server';
|
||||
|
||||
const document = await prisma.document.findFirstOrThrow({
|
||||
where: {
|
||||
id: documentId,
|
||||
},
|
||||
});
|
||||
|
||||
if (document.status !== DocumentStatus.COMPLETED) {
|
||||
throw new Error(`Document ${document.id} has not been completed`);
|
||||
}
|
||||
|
||||
const recipients = await prisma.recipient.findMany({
|
||||
where: {
|
||||
documentId: document.id,
|
||||
},
|
||||
});
|
||||
|
||||
if (recipients.some((recipient) => recipient.signingStatus !== SigningStatus.SIGNED)) {
|
||||
throw new Error(`Document ${document.id} has unsigned recipients`);
|
||||
}
|
||||
|
||||
const fields = await prisma.field.findMany({
|
||||
where: {
|
||||
documentId: document.id,
|
||||
},
|
||||
include: {
|
||||
Signature: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (fields.some((field) => !field.inserted)) {
|
||||
throw new Error(`Document ${document.id} has unsigned fields`);
|
||||
}
|
||||
|
||||
// !: Need to write the fields onto the document as a hard copy
|
||||
const { document: pdfData } = document;
|
||||
|
||||
const doc = await PDFDocument.load(pdfData);
|
||||
|
||||
for (const field of fields) {
|
||||
console.log('inserting field', {
|
||||
...field,
|
||||
Signature: null,
|
||||
});
|
||||
await insertFieldInPDF(doc, field);
|
||||
}
|
||||
|
||||
const pdfBytes = await doc.save();
|
||||
|
||||
await prisma.document.update({
|
||||
where: {
|
||||
id: document.id,
|
||||
status: DocumentStatus.COMPLETED,
|
||||
},
|
||||
data: {
|
||||
document: Buffer.from(pdfBytes).toString('base64'),
|
||||
},
|
||||
});
|
||||
};
|
||||
@ -48,12 +48,15 @@ export const sendDocument = async ({ documentId, userId }: SendDocumentOptions)
|
||||
return;
|
||||
}
|
||||
|
||||
const assetBaseUrl = process.env.NEXT_PUBLIC_SITE_URL || 'http://localhost:3000';
|
||||
const signDocumentLink = `${process.env.NEXT_PUBLIC_SITE_URL}/sign/${recipient.token}`;
|
||||
|
||||
const template = createElement(DocumentInviteEmailTemplate, {
|
||||
documentName: document.title,
|
||||
assetBaseUrl: process.env.NEXT_PUBLIC_SITE_URL || 'http://localhost:3000',
|
||||
inviterName: user.name || undefined,
|
||||
inviterEmail: user.email,
|
||||
signDocumentLink: 'https://example.com',
|
||||
assetBaseUrl,
|
||||
signDocumentLink,
|
||||
});
|
||||
|
||||
mailer.sendMail({
|
||||
|
||||
29
packages/lib/server-only/document/viewed-document.ts
Normal file
29
packages/lib/server-only/document/viewed-document.ts
Normal file
@ -0,0 +1,29 @@
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import { ReadStatus } from '@documenso/prisma/client';
|
||||
|
||||
export type ViewedDocumentOptions = {
|
||||
token: string;
|
||||
};
|
||||
|
||||
export const viewedDocument = async ({ token }: ViewedDocumentOptions) => {
|
||||
const recipient = await prisma.recipient.findFirst({
|
||||
where: {
|
||||
token,
|
||||
readStatus: ReadStatus.NOT_OPENED,
|
||||
},
|
||||
});
|
||||
|
||||
if (!recipient) {
|
||||
console.warn(`No recipient found for token ${token}`);
|
||||
return;
|
||||
}
|
||||
|
||||
await prisma.recipient.update({
|
||||
where: {
|
||||
id: recipient.id,
|
||||
},
|
||||
data: {
|
||||
readStatus: ReadStatus.OPENED,
|
||||
},
|
||||
});
|
||||
};
|
||||
Reference in New Issue
Block a user