mirror of
https://github.com/documenso/documenso.git
synced 2025-11-12 15:53:02 +10:00
feat: separate document data from document
This commit is contained in:
@ -10,6 +10,7 @@ import { redis } from '@documenso/lib/server-only/redis';
|
|||||||
import { Stripe, stripe } from '@documenso/lib/server-only/stripe';
|
import { Stripe, stripe } from '@documenso/lib/server-only/stripe';
|
||||||
import { prisma } from '@documenso/prisma';
|
import { prisma } from '@documenso/prisma';
|
||||||
import {
|
import {
|
||||||
|
DocumentDataType,
|
||||||
DocumentStatus,
|
DocumentStatus,
|
||||||
FieldType,
|
FieldType,
|
||||||
ReadStatus,
|
ReadStatus,
|
||||||
@ -85,16 +86,33 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
|||||||
|
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
|
|
||||||
|
const bytes64 = readFileSync('./public/documenso-supporter-pledge.pdf').toString('base64');
|
||||||
|
|
||||||
const document = await prisma.document.create({
|
const document = await prisma.document.create({
|
||||||
data: {
|
data: {
|
||||||
title: 'Documenso Supporter Pledge.pdf',
|
title: 'Documenso Supporter Pledge.pdf',
|
||||||
status: DocumentStatus.COMPLETED,
|
status: DocumentStatus.COMPLETED,
|
||||||
userId: user.id,
|
userId: user.id,
|
||||||
document: readFileSync('./public/documenso-supporter-pledge.pdf').toString('base64'),
|
|
||||||
created: now,
|
created: now,
|
||||||
|
documentData: {
|
||||||
|
create: {
|
||||||
|
type: DocumentDataType.BYTES_64,
|
||||||
|
data: bytes64,
|
||||||
|
initialData: bytes64,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
include: {
|
||||||
|
documentData: true,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const { documentData } = document;
|
||||||
|
|
||||||
|
if (!documentData) {
|
||||||
|
throw new Error(`Document ${document.id} has no document data`);
|
||||||
|
}
|
||||||
|
|
||||||
const recipient = await prisma.recipient.create({
|
const recipient = await prisma.recipient.create({
|
||||||
data: {
|
data: {
|
||||||
name: user.name ?? '',
|
name: user.name ?? '',
|
||||||
@ -122,16 +140,16 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (signatureDataUrl) {
|
if (signatureDataUrl) {
|
||||||
document.document = await insertImageInPDF(
|
documentData.data = await insertImageInPDF(
|
||||||
document.document,
|
documentData.data,
|
||||||
signatureDataUrl,
|
signatureDataUrl,
|
||||||
Number(field.positionX),
|
Number(field.positionX),
|
||||||
Number(field.positionY),
|
Number(field.positionY),
|
||||||
field.page,
|
field.page,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
document.document = await insertTextInPDF(
|
documentData.data = await insertTextInPDF(
|
||||||
document.document,
|
documentData.data,
|
||||||
signatureText ?? '',
|
signatureText ?? '',
|
||||||
Number(field.positionX),
|
Number(field.positionX),
|
||||||
Number(field.positionY),
|
Number(field.positionY),
|
||||||
@ -153,7 +171,11 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
|||||||
id: document.id,
|
id: document.id,
|
||||||
},
|
},
|
||||||
data: {
|
data: {
|
||||||
document: document.document,
|
documentData: {
|
||||||
|
update: {
|
||||||
|
data: documentData.data,
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
]);
|
]);
|
||||||
|
|||||||
@ -4,7 +4,8 @@ import { useState } from 'react';
|
|||||||
|
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
|
|
||||||
import { Document, Field, Recipient, User } from '@documenso/prisma/client';
|
import { Field, Recipient, User } from '@documenso/prisma/client';
|
||||||
|
import { DocumentWithData } from '@documenso/prisma/types/document-with-data';
|
||||||
import { cn } from '@documenso/ui/lib/utils';
|
import { cn } from '@documenso/ui/lib/utils';
|
||||||
import { Card, CardContent } from '@documenso/ui/primitives/card';
|
import { Card, CardContent } from '@documenso/ui/primitives/card';
|
||||||
import { AddFieldsFormPartial } from '@documenso/ui/primitives/document-flow/add-fields';
|
import { AddFieldsFormPartial } from '@documenso/ui/primitives/document-flow/add-fields';
|
||||||
@ -28,7 +29,7 @@ import { completeDocument } from '~/components/forms/edit-document/add-subject.a
|
|||||||
export type EditDocumentFormProps = {
|
export type EditDocumentFormProps = {
|
||||||
className?: string;
|
className?: string;
|
||||||
user: User;
|
user: User;
|
||||||
document: Document;
|
document: DocumentWithData;
|
||||||
recipients: Recipient[];
|
recipients: Recipient[];
|
||||||
fields: Field[];
|
fields: Field[];
|
||||||
};
|
};
|
||||||
@ -45,9 +46,11 @@ export const EditDocumentForm = ({
|
|||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
|
const { documentData } = document;
|
||||||
|
|
||||||
const [step, setStep] = useState<EditDocumentStep>('signers');
|
const [step, setStep] = useState<EditDocumentStep>('signers');
|
||||||
|
|
||||||
const documentUrl = `data:application/pdf;base64,${document.document}`;
|
const documentUrl = `data:application/pdf;base64,${documentData?.data}`;
|
||||||
|
|
||||||
const documentFlow: Record<EditDocumentStep, DocumentFlowStep> = {
|
const documentFlow: Record<EditDocumentStep, DocumentFlowStep> = {
|
||||||
signers: {
|
signers: {
|
||||||
|
|||||||
@ -36,10 +36,12 @@ export default async function DocumentPage({ params }: DocumentPageProps) {
|
|||||||
userId: session.id,
|
userId: session.id,
|
||||||
}).catch(() => null);
|
}).catch(() => null);
|
||||||
|
|
||||||
if (!document) {
|
if (!document || !document.documentData) {
|
||||||
redirect('/documents');
|
redirect('/documents');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const { documentData } = document;
|
||||||
|
|
||||||
const [recipients, fields] = await Promise.all([
|
const [recipients, fields] = await Promise.all([
|
||||||
await getRecipientsForDocument({
|
await getRecipientsForDocument({
|
||||||
documentId,
|
documentId,
|
||||||
@ -91,7 +93,7 @@ export default async function DocumentPage({ params }: DocumentPageProps) {
|
|||||||
|
|
||||||
{document.status === InternalDocumentStatus.COMPLETED && (
|
{document.status === InternalDocumentStatus.COMPLETED && (
|
||||||
<div className="mx-auto mt-12 max-w-2xl">
|
<div className="mx-auto mt-12 max-w-2xl">
|
||||||
<LazyPDFViewer document={`data:application/pdf;base64,${document.document}`} />
|
<LazyPDFViewer document={`data:application/pdf;base64,${documentData.data}`} />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -14,8 +14,17 @@ import {
|
|||||||
XCircle,
|
XCircle,
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import { useSession } from 'next-auth/react';
|
import { useSession } from 'next-auth/react';
|
||||||
|
import { match } from 'ts-pattern';
|
||||||
|
|
||||||
import { Document, DocumentStatus, Recipient, User } from '@documenso/prisma/client';
|
import {
|
||||||
|
Document,
|
||||||
|
DocumentDataType,
|
||||||
|
DocumentStatus,
|
||||||
|
Recipient,
|
||||||
|
User,
|
||||||
|
} from '@documenso/prisma/client';
|
||||||
|
import { DocumentWithData } from '@documenso/prisma/types/document-with-data';
|
||||||
|
import { trpc } from '@documenso/trpc/client';
|
||||||
import {
|
import {
|
||||||
DropdownMenu,
|
DropdownMenu,
|
||||||
DropdownMenuContent,
|
DropdownMenuContent,
|
||||||
@ -47,17 +56,42 @@ export const DataTableActionDropdown = ({ row }: DataTableActionDropdownProps) =
|
|||||||
const isComplete = row.status === DocumentStatus.COMPLETED;
|
const isComplete = row.status === DocumentStatus.COMPLETED;
|
||||||
// const isSigned = recipient?.signingStatus === SigningStatus.SIGNED;
|
// const isSigned = recipient?.signingStatus === SigningStatus.SIGNED;
|
||||||
|
|
||||||
const onDownloadClick = () => {
|
const onDownloadClick = async () => {
|
||||||
let decodedDocument = row.document;
|
let document: DocumentWithData | null = null;
|
||||||
|
|
||||||
try {
|
if (!recipient) {
|
||||||
decodedDocument = atob(decodedDocument);
|
document = await trpc.document.getDocumentById.query({
|
||||||
} catch (err) {
|
id: row.id,
|
||||||
// We're just going to ignore this error and try to download the document
|
});
|
||||||
console.error(err);
|
} else {
|
||||||
|
document = await trpc.document.getDocumentByToken.query({
|
||||||
|
token: recipient.token,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const documentBytes = Uint8Array.from(decodedDocument.split('').map((c) => c.charCodeAt(0)));
|
const documentData = document?.documentData;
|
||||||
|
|
||||||
|
if (!documentData) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const documentBytes = await match(documentData.type)
|
||||||
|
.with(DocumentDataType.BYTES, () =>
|
||||||
|
Uint8Array.from(documentData.data, (c) => c.charCodeAt(0)),
|
||||||
|
)
|
||||||
|
.with(DocumentDataType.BYTES_64, () =>
|
||||||
|
Uint8Array.from(
|
||||||
|
atob(documentData.data)
|
||||||
|
.split('')
|
||||||
|
.map((c) => c.charCodeAt(0)),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.with(DocumentDataType.S3_PATH, async () =>
|
||||||
|
fetch(documentData.data)
|
||||||
|
.then(async (res) => res.arrayBuffer())
|
||||||
|
.then((buffer) => new Uint8Array(buffer)),
|
||||||
|
)
|
||||||
|
.exhaustive();
|
||||||
|
|
||||||
const blob = new Blob([documentBytes], {
|
const blob = new Blob([documentBytes], {
|
||||||
type: 'application/pdf',
|
type: 'application/pdf',
|
||||||
|
|||||||
@ -30,10 +30,12 @@ export default async function CompletedSigningPage({
|
|||||||
token,
|
token,
|
||||||
}).catch(() => null);
|
}).catch(() => null);
|
||||||
|
|
||||||
if (!document) {
|
if (!document || !document.documentData) {
|
||||||
return notFound();
|
return notFound();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const { documentData } = document;
|
||||||
|
|
||||||
const [fields, recipient] = await Promise.all([
|
const [fields, recipient] = await Promise.all([
|
||||||
getFieldsForToken({ token }),
|
getFieldsForToken({ token }),
|
||||||
getRecipientByToken({ token }),
|
getRecipientByToken({ token }),
|
||||||
@ -91,7 +93,7 @@ export default async function CompletedSigningPage({
|
|||||||
<DownloadButton
|
<DownloadButton
|
||||||
className="flex-1"
|
className="flex-1"
|
||||||
fileName={document.title}
|
fileName={document.title}
|
||||||
document={document.status === DocumentStatus.COMPLETED ? document.document : undefined}
|
document={document.status === DocumentStatus.COMPLETED ? documentData.data : undefined}
|
||||||
disabled={document.status !== DocumentStatus.COMPLETED}
|
disabled={document.status !== DocumentStatus.COMPLETED}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -40,13 +40,15 @@ export default async function SigningPage({ params: { token } }: SigningPageProp
|
|||||||
viewedDocument({ token }),
|
viewedDocument({ token }),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
if (!document) {
|
if (!document || !document.documentData) {
|
||||||
return notFound();
|
return notFound();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const { documentData } = document;
|
||||||
|
|
||||||
const user = await getServerComponentSession();
|
const user = await getServerComponentSession();
|
||||||
|
|
||||||
const documentUrl = `data:application/pdf;base64,${document.document}`;
|
const documentUrl = `data:application/pdf;base64,${documentData.data}`;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SigningProvider email={recipient.email} fullName={recipient.name} signature={user?.signature}>
|
<SigningProvider email={recipient.email} fullName={recipient.name} signature={user?.signature}>
|
||||||
|
|||||||
@ -5,7 +5,7 @@ import { readFileSync } from 'fs';
|
|||||||
|
|
||||||
import { getServerSession } from '@documenso/lib/next-auth/get-server-session';
|
import { getServerSession } from '@documenso/lib/next-auth/get-server-session';
|
||||||
import { prisma } from '@documenso/prisma';
|
import { prisma } from '@documenso/prisma';
|
||||||
import { DocumentStatus } from '@documenso/prisma/client';
|
import { DocumentDataType, DocumentStatus } from '@documenso/prisma/client';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
TCreateDocumentRequestSchema,
|
TCreateDocumentRequestSchema,
|
||||||
@ -55,12 +55,20 @@ export default async function handler(
|
|||||||
|
|
||||||
const fileBuffer = readFileSync(file.filepath);
|
const fileBuffer = readFileSync(file.filepath);
|
||||||
|
|
||||||
|
const bytes64 = fileBuffer.toString('base64');
|
||||||
|
|
||||||
const document = await prisma.document.create({
|
const document = await prisma.document.create({
|
||||||
data: {
|
data: {
|
||||||
title: file.originalFilename ?? file.newFilename,
|
title: file.originalFilename ?? file.newFilename,
|
||||||
status: DocumentStatus.DRAFT,
|
status: DocumentStatus.DRAFT,
|
||||||
userId: user.id,
|
userId: user.id,
|
||||||
document: fileBuffer.toString('base64'),
|
documentData: {
|
||||||
|
create: {
|
||||||
|
type: DocumentDataType.BYTES_64,
|
||||||
|
data: bytes64,
|
||||||
|
initialData: bytes64,
|
||||||
|
},
|
||||||
|
},
|
||||||
created: new Date(),
|
created: new Date(),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@ -10,6 +10,7 @@ import { redis } from '@documenso/lib/server-only/redis';
|
|||||||
import { Stripe, stripe } from '@documenso/lib/server-only/stripe';
|
import { Stripe, stripe } from '@documenso/lib/server-only/stripe';
|
||||||
import { prisma } from '@documenso/prisma';
|
import { prisma } from '@documenso/prisma';
|
||||||
import {
|
import {
|
||||||
|
DocumentDataType,
|
||||||
DocumentStatus,
|
DocumentStatus,
|
||||||
FieldType,
|
FieldType,
|
||||||
ReadStatus,
|
ReadStatus,
|
||||||
@ -85,16 +86,33 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
|||||||
|
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
|
|
||||||
|
const bytes64 = readFileSync('./public/documenso-supporter-pledge.pdf').toString('base64');
|
||||||
|
|
||||||
const document = await prisma.document.create({
|
const document = await prisma.document.create({
|
||||||
data: {
|
data: {
|
||||||
title: 'Documenso Supporter Pledge.pdf',
|
title: 'Documenso Supporter Pledge.pdf',
|
||||||
status: DocumentStatus.COMPLETED,
|
status: DocumentStatus.COMPLETED,
|
||||||
userId: user.id,
|
userId: user.id,
|
||||||
document: readFileSync('./public/documenso-supporter-pledge.pdf').toString('base64'),
|
|
||||||
created: now,
|
created: now,
|
||||||
|
documentData: {
|
||||||
|
create: {
|
||||||
|
type: DocumentDataType.BYTES_64,
|
||||||
|
data: bytes64,
|
||||||
|
initialData: bytes64,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
include: {
|
||||||
|
documentData: true,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const { documentData } = document;
|
||||||
|
|
||||||
|
if (!documentData) {
|
||||||
|
throw new Error(`Document ${document.id} has no document data`);
|
||||||
|
}
|
||||||
|
|
||||||
const recipient = await prisma.recipient.create({
|
const recipient = await prisma.recipient.create({
|
||||||
data: {
|
data: {
|
||||||
name: user.name ?? '',
|
name: user.name ?? '',
|
||||||
@ -122,16 +140,16 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (signatureDataUrl) {
|
if (signatureDataUrl) {
|
||||||
document.document = await insertImageInPDF(
|
documentData.data = await insertImageInPDF(
|
||||||
document.document,
|
documentData.data,
|
||||||
signatureDataUrl,
|
signatureDataUrl,
|
||||||
field.positionX.toNumber(),
|
field.positionX.toNumber(),
|
||||||
field.positionY.toNumber(),
|
field.positionY.toNumber(),
|
||||||
field.page,
|
field.page,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
document.document = await insertTextInPDF(
|
documentData.data = await insertTextInPDF(
|
||||||
document.document,
|
documentData.data,
|
||||||
signatureText ?? '',
|
signatureText ?? '',
|
||||||
field.positionX.toNumber(),
|
field.positionX.toNumber(),
|
||||||
field.positionY.toNumber(),
|
field.positionY.toNumber(),
|
||||||
@ -153,7 +171,11 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
|||||||
id: document.id,
|
id: document.id,
|
||||||
},
|
},
|
||||||
data: {
|
data: {
|
||||||
document: document.document,
|
documentData: {
|
||||||
|
update: {
|
||||||
|
data: documentData.data,
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
]);
|
]);
|
||||||
|
|||||||
@ -12,6 +12,8 @@
|
|||||||
],
|
],
|
||||||
"scripts": {},
|
"scripts": {},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@aws-sdk/s3-request-presigner": "^3.405.0",
|
||||||
|
"@aws-sdk/client-s3": "^3.405.0",
|
||||||
"@documenso/email": "*",
|
"@documenso/email": "*",
|
||||||
"@documenso/prisma": "*",
|
"@documenso/prisma": "*",
|
||||||
"@next-auth/prisma-adapter": "1.0.7",
|
"@next-auth/prisma-adapter": "1.0.7",
|
||||||
|
|||||||
10
packages/lib/server-only/document/create-document.ts
Normal file
10
packages/lib/server-only/document/create-document.ts
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
'use server';
|
||||||
|
|
||||||
|
export type CreateDocumentOptions = {
|
||||||
|
userId: number;
|
||||||
|
fileName: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const createDocument = () => {
|
||||||
|
//
|
||||||
|
};
|
||||||
@ -11,5 +11,8 @@ export const getDocumentById = async ({ id, userId }: GetDocumentByIdOptions) =>
|
|||||||
id,
|
id,
|
||||||
userId,
|
userId,
|
||||||
},
|
},
|
||||||
|
include: {
|
||||||
|
documentData: true,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@ -17,6 +17,7 @@ export const getDocumentAndSenderByToken = async ({
|
|||||||
},
|
},
|
||||||
include: {
|
include: {
|
||||||
User: true,
|
User: true,
|
||||||
|
documentData: true,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -18,8 +18,15 @@ export const sealDocument = async ({ documentId }: SealDocumentOptions) => {
|
|||||||
where: {
|
where: {
|
||||||
id: documentId,
|
id: documentId,
|
||||||
},
|
},
|
||||||
|
include: {
|
||||||
|
documentData: true,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (!document.documentData) {
|
||||||
|
throw new Error(`Document ${document.id} has no document data`);
|
||||||
|
}
|
||||||
|
|
||||||
if (document.status !== DocumentStatus.COMPLETED) {
|
if (document.status !== DocumentStatus.COMPLETED) {
|
||||||
throw new Error(`Document ${document.id} has not been completed`);
|
throw new Error(`Document ${document.id} has not been completed`);
|
||||||
}
|
}
|
||||||
@ -48,7 +55,7 @@ export const sealDocument = async ({ documentId }: SealDocumentOptions) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// !: Need to write the fields onto the document as a hard copy
|
// !: Need to write the fields onto the document as a hard copy
|
||||||
const { document: pdfData } = document;
|
const { data: pdfData } = document.documentData;
|
||||||
|
|
||||||
const doc = await PDFDocument.load(pdfData);
|
const doc = await PDFDocument.load(pdfData);
|
||||||
|
|
||||||
@ -64,7 +71,11 @@ export const sealDocument = async ({ documentId }: SealDocumentOptions) => {
|
|||||||
status: DocumentStatus.COMPLETED,
|
status: DocumentStatus.COMPLETED,
|
||||||
},
|
},
|
||||||
data: {
|
data: {
|
||||||
document: Buffer.from(pdfBytes).toString('base64'),
|
documentData: {
|
||||||
|
update: {
|
||||||
|
data: Buffer.from(pdfBytes).toString('base64'),
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@ -0,0 +1,19 @@
|
|||||||
|
-- CreateEnum
|
||||||
|
CREATE TYPE "DocumentDataType" AS ENUM ('S3_PATH', 'BYTES', 'BYTES_64');
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "DocumentData" (
|
||||||
|
"id" TEXT NOT NULL,
|
||||||
|
"type" "DocumentDataType" NOT NULL,
|
||||||
|
"data" TEXT NOT NULL,
|
||||||
|
"initialData" TEXT NOT NULL,
|
||||||
|
"documentId" INTEGER NOT NULL,
|
||||||
|
|
||||||
|
CONSTRAINT "DocumentData_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE UNIQUE INDEX "DocumentData_documentId_key" ON "DocumentData"("documentId");
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "DocumentData" ADD CONSTRAINT "DocumentData_documentId_fkey" FOREIGN KEY ("documentId") REFERENCES "Document"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
@ -0,0 +1,14 @@
|
|||||||
|
INSERT INTO
|
||||||
|
"DocumentData" ("id", "type", "data", "initialData", "documentId") (
|
||||||
|
SELECT
|
||||||
|
CAST(gen_random_uuid() AS TEXT),
|
||||||
|
'BYTES_64',
|
||||||
|
d."document",
|
||||||
|
d."document",
|
||||||
|
d."id"
|
||||||
|
FROM
|
||||||
|
"Document" d
|
||||||
|
WHERE
|
||||||
|
d."id" IS NOT NULL
|
||||||
|
AND d."document" IS NOT NULL
|
||||||
|
);
|
||||||
@ -0,0 +1,3 @@
|
|||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "Document" ADD COLUMN "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
ADD COLUMN "updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP;
|
||||||
@ -0,0 +1,8 @@
|
|||||||
|
/*
|
||||||
|
Warnings:
|
||||||
|
|
||||||
|
- You are about to drop the column `document` on the `Document` table. All the data in the column will be lost.
|
||||||
|
|
||||||
|
*/
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "Document" DROP COLUMN "document";
|
||||||
@ -91,15 +91,35 @@ enum DocumentStatus {
|
|||||||
}
|
}
|
||||||
|
|
||||||
model Document {
|
model Document {
|
||||||
id Int @id @default(autoincrement())
|
id Int @id @default(autoincrement())
|
||||||
created DateTime @default(now())
|
created DateTime @default(now())
|
||||||
userId Int
|
userId Int
|
||||||
User User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
User User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||||
title String
|
title String
|
||||||
status DocumentStatus @default(DRAFT)
|
status DocumentStatus @default(DRAFT)
|
||||||
document String
|
Recipient Recipient[]
|
||||||
Recipient Recipient[]
|
Field Field[]
|
||||||
Field Field[]
|
documentData DocumentData?
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
updatedAt DateTime @updatedAt @default(now())
|
||||||
|
}
|
||||||
|
|
||||||
|
enum DocumentDataType {
|
||||||
|
S3_PATH
|
||||||
|
BYTES
|
||||||
|
BYTES_64
|
||||||
|
}
|
||||||
|
|
||||||
|
model DocumentData {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
type DocumentDataType
|
||||||
|
data String
|
||||||
|
initialData String
|
||||||
|
documentId Int
|
||||||
|
|
||||||
|
Document Document @relation(fields: [documentId], references: [id], onDelete: Cascade)
|
||||||
|
|
||||||
|
@@unique([documentId])
|
||||||
}
|
}
|
||||||
|
|
||||||
enum ReadStatus {
|
enum ReadStatus {
|
||||||
|
|||||||
5
packages/prisma/types/document-with-data.ts
Normal file
5
packages/prisma/types/document-with-data.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
import { Document, DocumentData } from '@documenso/prisma/client';
|
||||||
|
|
||||||
|
export type DocumentWithData = Document & {
|
||||||
|
documentData?: DocumentData | null;
|
||||||
|
};
|
||||||
@ -1,17 +1,63 @@
|
|||||||
import { TRPCError } from '@trpc/server';
|
import { TRPCError } from '@trpc/server';
|
||||||
|
|
||||||
|
import { getDocumentById } from '@documenso/lib/server-only/document/get-document-by-id';
|
||||||
|
import { getDocumentAndSenderByToken } from '@documenso/lib/server-only/document/get-document-by-token';
|
||||||
import { sendDocument } from '@documenso/lib/server-only/document/send-document';
|
import { sendDocument } from '@documenso/lib/server-only/document/send-document';
|
||||||
import { setFieldsForDocument } from '@documenso/lib/server-only/field/set-fields-for-document';
|
import { setFieldsForDocument } from '@documenso/lib/server-only/field/set-fields-for-document';
|
||||||
import { setRecipientsForDocument } from '@documenso/lib/server-only/recipient/set-recipients-for-document';
|
import { setRecipientsForDocument } from '@documenso/lib/server-only/recipient/set-recipients-for-document';
|
||||||
|
|
||||||
import { authenticatedProcedure, router } from '../trpc';
|
import { authenticatedProcedure, procedure, router } from '../trpc';
|
||||||
import {
|
import {
|
||||||
|
ZGetDocumentByIdQuerySchema,
|
||||||
|
ZGetDocumentByTokenQuerySchema,
|
||||||
ZSendDocumentMutationSchema,
|
ZSendDocumentMutationSchema,
|
||||||
ZSetFieldsForDocumentMutationSchema,
|
ZSetFieldsForDocumentMutationSchema,
|
||||||
ZSetRecipientsForDocumentMutationSchema,
|
ZSetRecipientsForDocumentMutationSchema,
|
||||||
} from './schema';
|
} from './schema';
|
||||||
|
|
||||||
export const documentRouter = router({
|
export const documentRouter = router({
|
||||||
|
getDocumentById: authenticatedProcedure
|
||||||
|
.input(ZGetDocumentByIdQuerySchema)
|
||||||
|
.query(async ({ input, ctx }) => {
|
||||||
|
try {
|
||||||
|
const { id } = input;
|
||||||
|
|
||||||
|
console.log({
|
||||||
|
id,
|
||||||
|
userId: ctx.user.id,
|
||||||
|
});
|
||||||
|
|
||||||
|
return await getDocumentById({
|
||||||
|
id,
|
||||||
|
userId: ctx.user.id,
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
|
||||||
|
throw new TRPCError({
|
||||||
|
code: 'BAD_REQUEST',
|
||||||
|
message: 'We were unable to find this document. Please try again later.',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
|
||||||
|
getDocumentByToken: procedure.input(ZGetDocumentByTokenQuerySchema).query(async ({ input }) => {
|
||||||
|
try {
|
||||||
|
const { token } = input;
|
||||||
|
|
||||||
|
return await getDocumentAndSenderByToken({
|
||||||
|
token,
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
|
||||||
|
throw new TRPCError({
|
||||||
|
code: 'BAD_REQUEST',
|
||||||
|
message: 'We were unable to find this document. Please try again later.',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
|
||||||
setRecipientsForDocument: authenticatedProcedure
|
setRecipientsForDocument: authenticatedProcedure
|
||||||
.input(ZSetRecipientsForDocumentMutationSchema)
|
.input(ZSetRecipientsForDocumentMutationSchema)
|
||||||
.mutation(async ({ input, ctx }) => {
|
.mutation(async ({ input, ctx }) => {
|
||||||
|
|||||||
@ -2,6 +2,18 @@ import { z } from 'zod';
|
|||||||
|
|
||||||
import { FieldType } from '@documenso/prisma/client';
|
import { FieldType } from '@documenso/prisma/client';
|
||||||
|
|
||||||
|
export const ZGetDocumentByIdQuerySchema = z.object({
|
||||||
|
id: z.number().min(1),
|
||||||
|
});
|
||||||
|
|
||||||
|
export type TGetDocumentByIdQuerySchema = z.infer<typeof ZGetDocumentByIdQuerySchema>;
|
||||||
|
|
||||||
|
export const ZGetDocumentByTokenQuerySchema = z.object({
|
||||||
|
token: z.string().min(1),
|
||||||
|
});
|
||||||
|
|
||||||
|
export type TGetDocumentByTokenQuerySchema = z.infer<typeof ZGetDocumentByTokenQuerySchema>;
|
||||||
|
|
||||||
export const ZSetRecipientsForDocumentMutationSchema = z.object({
|
export const ZSetRecipientsForDocumentMutationSchema = z.object({
|
||||||
documentId: z.number(),
|
documentId: z.number(),
|
||||||
recipients: z.array(
|
recipients: z.array(
|
||||||
|
|||||||
7
packages/tsconfig/process-env.d.ts
vendored
7
packages/tsconfig/process-env.d.ts
vendored
@ -13,6 +13,13 @@ declare namespace NodeJS {
|
|||||||
NEXT_PRIVATE_STRIPE_API_KEY: string;
|
NEXT_PRIVATE_STRIPE_API_KEY: string;
|
||||||
NEXT_PRIVATE_STRIPE_WEBHOOK_SECRET: string;
|
NEXT_PRIVATE_STRIPE_WEBHOOK_SECRET: string;
|
||||||
|
|
||||||
|
NEXT_PRIVATE_UPLOAD_TRANSPORT?: 'database' | 's3';
|
||||||
|
NEXT_PRIVATE_UPLOAD_ENDPOINT?: string;
|
||||||
|
NEXT_PRIVATE_UPLOAD_REGION?: string;
|
||||||
|
NEXT_PRIVATE_UPLOAD_BUCKET?: string;
|
||||||
|
NEXT_PRIVATE_UPLOAD_ACCESS_KEY_ID?: string;
|
||||||
|
NEXT_PRIVATE_UPLOAD_SECRET_ACCESS_KEY?: string;
|
||||||
|
|
||||||
NEXT_PRIVATE_SMTP_TRANSPORT?: 'mailchannels' | 'smtp-auth' | 'smtp-api';
|
NEXT_PRIVATE_SMTP_TRANSPORT?: 'mailchannels' | 'smtp-auth' | 'smtp-api';
|
||||||
|
|
||||||
NEXT_PRIVATE_MAILCHANNELS_API_KEY?: string;
|
NEXT_PRIVATE_MAILCHANNELS_API_KEY?: string;
|
||||||
|
|||||||
Reference in New Issue
Block a user