mirror of
https://github.com/documenso/documenso.git
synced 2025-11-13 00:03:33 +10:00
feat: create from template
This commit is contained in:
@ -1,6 +1,6 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { useEffect, useState } from 'react';
|
import { useState } from 'react';
|
||||||
|
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
|
|
||||||
@ -72,16 +72,6 @@ export const ApiTokenForm = ({ className }: ApiTokenFormProps) => {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (newlyCreatedToken) {
|
|
||||||
const timer = setTimeout(() => {
|
|
||||||
setNewlyCreatedToken('');
|
|
||||||
}, 30000);
|
|
||||||
|
|
||||||
return () => clearTimeout(timer);
|
|
||||||
}
|
|
||||||
}, [newlyCreatedToken]);
|
|
||||||
|
|
||||||
const copyToken = async (token: string) => {
|
const copyToken = async (token: string) => {
|
||||||
try {
|
try {
|
||||||
const copied = await copy(token);
|
const copied = await copy(token);
|
||||||
@ -166,6 +156,7 @@ export const ApiTokenForm = ({ className }: ApiTokenFormProps) => {
|
|||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<div className="flex flex-col gap-4 md:flex-row">
|
||||||
<FormField
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name="expirationDate"
|
name="expirationDate"
|
||||||
@ -176,7 +167,7 @@ export const ApiTokenForm = ({ className }: ApiTokenFormProps) => {
|
|||||||
<div className="flex items-center gap-x-4">
|
<div className="flex items-center gap-x-4">
|
||||||
<FormControl className="flex-1">
|
<FormControl className="flex-1">
|
||||||
<Select onValueChange={field.onChange} disabled={noExpirationDate}>
|
<Select onValueChange={field.onChange} disabled={noExpirationDate}>
|
||||||
<SelectTrigger className="w-[180px]">
|
<SelectTrigger className="w-full">
|
||||||
<SelectValue placeholder="Choose..." />
|
<SelectValue placeholder="Choose..." />
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
@ -199,9 +190,10 @@ export const ApiTokenForm = ({ className }: ApiTokenFormProps) => {
|
|||||||
control={form.control}
|
control={form.control}
|
||||||
name="enabled"
|
name="enabled"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem className="flex items-center gap-2">
|
<FormItem className="">
|
||||||
<FormLabel className="text-muted-foreground mt-2">Never expire</FormLabel>
|
<FormLabel className="text-muted-foreground mt-2">Never expire</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
|
<div className="block md:py-1.5">
|
||||||
<Switch
|
<Switch
|
||||||
className="bg-background"
|
className="bg-background"
|
||||||
checked={field.value}
|
checked={field.value}
|
||||||
@ -210,11 +202,13 @@ export const ApiTokenForm = ({ className }: ApiTokenFormProps) => {
|
|||||||
field.onChange(val);
|
field.onChange(val);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
type="submit"
|
type="submit"
|
||||||
|
|||||||
@ -5,7 +5,7 @@ import { ApiContractV1 } from '@documenso/api/v1/contract';
|
|||||||
import { ApiContractV1Implementation } from '@documenso/api/v1/implementation';
|
import { ApiContractV1Implementation } from '@documenso/api/v1/implementation';
|
||||||
|
|
||||||
const nextRouteHandler = createNextRouter(ApiContractV1, ApiContractV1Implementation, {
|
const nextRouteHandler = createNextRouter(ApiContractV1, ApiContractV1Implementation, {
|
||||||
responseValidation: false,
|
responseValidation: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||||
|
|||||||
@ -3,6 +3,8 @@ import { initContract } from '@ts-rest/core';
|
|||||||
import {
|
import {
|
||||||
ZSendDocumentForSigningMutationSchema as SendDocumentMutationSchema,
|
ZSendDocumentForSigningMutationSchema as SendDocumentMutationSchema,
|
||||||
ZAuthorizationHeadersSchema,
|
ZAuthorizationHeadersSchema,
|
||||||
|
ZCreateDocumentFromTemplateMutationResponseSchema,
|
||||||
|
ZCreateDocumentFromTemplateMutationSchema,
|
||||||
ZCreateDocumentMutationResponseSchema,
|
ZCreateDocumentMutationResponseSchema,
|
||||||
ZCreateDocumentMutationSchema,
|
ZCreateDocumentMutationSchema,
|
||||||
ZCreateFieldMutationSchema,
|
ZCreateFieldMutationSchema,
|
||||||
@ -13,6 +15,7 @@ import {
|
|||||||
ZGetDocumentsQuerySchema,
|
ZGetDocumentsQuerySchema,
|
||||||
ZSuccessfulDocumentResponseSchema,
|
ZSuccessfulDocumentResponseSchema,
|
||||||
ZSuccessfulFieldResponseSchema,
|
ZSuccessfulFieldResponseSchema,
|
||||||
|
ZSuccessfulGetDocumentResponseSchema,
|
||||||
ZSuccessfulRecipientResponseSchema,
|
ZSuccessfulRecipientResponseSchema,
|
||||||
ZSuccessfulResponseSchema,
|
ZSuccessfulResponseSchema,
|
||||||
ZSuccessfulSigningResponseSchema,
|
ZSuccessfulSigningResponseSchema,
|
||||||
@ -41,7 +44,7 @@ export const ApiContractV1 = c.router(
|
|||||||
method: 'GET',
|
method: 'GET',
|
||||||
path: '/api/v1/documents/:id',
|
path: '/api/v1/documents/:id',
|
||||||
responses: {
|
responses: {
|
||||||
200: ZSuccessfulDocumentResponseSchema,
|
200: ZSuccessfulGetDocumentResponseSchema,
|
||||||
401: ZUnsuccessfulResponseSchema,
|
401: ZUnsuccessfulResponseSchema,
|
||||||
404: ZUnsuccessfulResponseSchema,
|
404: ZUnsuccessfulResponseSchema,
|
||||||
},
|
},
|
||||||
@ -60,6 +63,18 @@ export const ApiContractV1 = c.router(
|
|||||||
summary: 'Upload a new document and get a presigned URL',
|
summary: 'Upload a new document and get a presigned URL',
|
||||||
},
|
},
|
||||||
|
|
||||||
|
createDocumentFromTemplate: {
|
||||||
|
method: 'POST',
|
||||||
|
path: '/api/v1/templates/:templateId/create-document',
|
||||||
|
body: ZCreateDocumentFromTemplateMutationSchema,
|
||||||
|
responses: {
|
||||||
|
200: ZCreateDocumentFromTemplateMutationResponseSchema,
|
||||||
|
401: ZUnsuccessfulResponseSchema,
|
||||||
|
404: ZUnsuccessfulResponseSchema,
|
||||||
|
},
|
||||||
|
summary: 'Upload a new document and get a presigned URL',
|
||||||
|
},
|
||||||
|
|
||||||
sendDocument: {
|
sendDocument: {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
path: '/api/v1/documents/:id/send',
|
path: '/api/v1/documents/:id/send',
|
||||||
|
|||||||
@ -1,11 +1,13 @@
|
|||||||
import { createNextRoute } from '@ts-rest/next';
|
import { createNextRoute } from '@ts-rest/next';
|
||||||
|
|
||||||
import { createDocumentData } from '@documenso/lib/server-only/document-data/create-document-data';
|
import { createDocumentData } from '@documenso/lib/server-only/document-data/create-document-data';
|
||||||
|
import { upsertDocumentMeta } from '@documenso/lib/server-only/document-meta/upsert-document-meta';
|
||||||
import { createDocument } from '@documenso/lib/server-only/document/create-document';
|
import { createDocument } from '@documenso/lib/server-only/document/create-document';
|
||||||
import { deleteDocument } from '@documenso/lib/server-only/document/delete-document';
|
import { deleteDocument } from '@documenso/lib/server-only/document/delete-document';
|
||||||
import { findDocuments } from '@documenso/lib/server-only/document/find-documents';
|
import { findDocuments } from '@documenso/lib/server-only/document/find-documents';
|
||||||
import { getDocumentById } from '@documenso/lib/server-only/document/get-document-by-id';
|
import { getDocumentById } from '@documenso/lib/server-only/document/get-document-by-id';
|
||||||
import { sendDocument } from '@documenso/lib/server-only/document/send-document';
|
import { sendDocument } from '@documenso/lib/server-only/document/send-document';
|
||||||
|
import { updateDocument } from '@documenso/lib/server-only/document/update-document';
|
||||||
import { createField } from '@documenso/lib/server-only/field/create-field';
|
import { createField } from '@documenso/lib/server-only/field/create-field';
|
||||||
import { deleteField } from '@documenso/lib/server-only/field/delete-field';
|
import { deleteField } from '@documenso/lib/server-only/field/delete-field';
|
||||||
import { getFieldById } from '@documenso/lib/server-only/field/get-field-by-id';
|
import { getFieldById } from '@documenso/lib/server-only/field/get-field-by-id';
|
||||||
@ -15,6 +17,7 @@ import { getRecipientById } from '@documenso/lib/server-only/recipient/get-recip
|
|||||||
import { getRecipientsForDocument } from '@documenso/lib/server-only/recipient/get-recipients-for-document';
|
import { getRecipientsForDocument } from '@documenso/lib/server-only/recipient/get-recipients-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 { updateRecipient } from '@documenso/lib/server-only/recipient/update-recipient';
|
import { updateRecipient } from '@documenso/lib/server-only/recipient/update-recipient';
|
||||||
|
import { createDocumentFromTemplate } from '@documenso/lib/server-only/template/create-document-from-template';
|
||||||
import { getPresignPostUrl } from '@documenso/lib/universal/upload/server-actions';
|
import { getPresignPostUrl } from '@documenso/lib/universal/upload/server-actions';
|
||||||
import { DocumentDataType, DocumentStatus, SigningStatus } from '@documenso/prisma/client';
|
import { DocumentDataType, DocumentStatus, SigningStatus } from '@documenso/prisma/client';
|
||||||
|
|
||||||
@ -42,10 +45,17 @@ export const ApiContractV1Implementation = createNextRoute(ApiContractV1, {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const document = await getDocumentById({ id: Number(documentId), userId: user.id });
|
const document = await getDocumentById({ id: Number(documentId), userId: user.id });
|
||||||
|
const recipients = await getRecipientsForDocument({
|
||||||
|
documentId: Number(documentId),
|
||||||
|
userId: user.id,
|
||||||
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
status: 200,
|
status: 200,
|
||||||
body: document,
|
body: {
|
||||||
|
...document,
|
||||||
|
recipients,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
return {
|
return {
|
||||||
@ -124,6 +134,8 @@ export const ApiContractV1Implementation = createNextRoute(ApiContractV1, {
|
|||||||
documentId: document.id,
|
documentId: document.id,
|
||||||
recipients: recipients.map((recipient) => ({
|
recipients: recipients.map((recipient) => ({
|
||||||
recipientId: recipient.id,
|
recipientId: recipient.id,
|
||||||
|
name: recipient.name,
|
||||||
|
email: recipient.email,
|
||||||
token: recipient.token,
|
token: recipient.token,
|
||||||
role: recipient.role,
|
role: recipient.role,
|
||||||
})),
|
})),
|
||||||
@ -139,6 +151,53 @@ export const ApiContractV1Implementation = createNextRoute(ApiContractV1, {
|
|||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
|
|
||||||
|
createDocumentFromTemplate: authenticatedMiddleware(async (args, user) => {
|
||||||
|
const { body, params } = args;
|
||||||
|
|
||||||
|
const templateId = Number(params.templateId);
|
||||||
|
|
||||||
|
const fileName = body.title.endsWith('.pdf') ? body.title : `${body.title}.pdf`;
|
||||||
|
|
||||||
|
const document = await createDocumentFromTemplate({
|
||||||
|
templateId,
|
||||||
|
userId: user.id,
|
||||||
|
recipients: body.recipients,
|
||||||
|
});
|
||||||
|
|
||||||
|
await updateDocument({
|
||||||
|
documentId: document.id,
|
||||||
|
userId: user.id,
|
||||||
|
data: {
|
||||||
|
title: body.title,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (body.meta) {
|
||||||
|
await upsertDocumentMeta({
|
||||||
|
documentId: document.id,
|
||||||
|
userId: user.id,
|
||||||
|
subject: body.meta.subject,
|
||||||
|
message: body.meta.message,
|
||||||
|
dateFormat: body.meta.dateFormat,
|
||||||
|
timezone: body.meta.timezone,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
status: 200,
|
||||||
|
body: {
|
||||||
|
documentId: document.id,
|
||||||
|
recipients: document.Recipient.map((recipient) => ({
|
||||||
|
recipientId: recipient.id,
|
||||||
|
name: recipient.name,
|
||||||
|
email: recipient.email,
|
||||||
|
token: recipient.token,
|
||||||
|
role: recipient.role,
|
||||||
|
})),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
|
||||||
sendDocument: authenticatedMiddleware(async (args, user) => {
|
sendDocument: authenticatedMiddleware(async (args, user) => {
|
||||||
const { id } = args.params;
|
const { id } = args.params;
|
||||||
|
|
||||||
@ -461,6 +520,7 @@ export const ApiContractV1Implementation = createNextRoute(ApiContractV1, {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const remappedField = {
|
const remappedField = {
|
||||||
|
id: field.id,
|
||||||
documentId: field.documentId,
|
documentId: field.documentId,
|
||||||
recipientId: field.recipientId ?? -1,
|
recipientId: field.recipientId ?? -1,
|
||||||
type: field.type,
|
type: field.type,
|
||||||
@ -545,6 +605,7 @@ export const ApiContractV1Implementation = createNextRoute(ApiContractV1, {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const remappedField = {
|
const remappedField = {
|
||||||
|
id: updatedField.id,
|
||||||
documentId: updatedField.documentId,
|
documentId: updatedField.documentId,
|
||||||
recipientId: updatedField.recipientId ?? -1,
|
recipientId: updatedField.recipientId ?? -1,
|
||||||
type: updatedField.type,
|
type: updatedField.type,
|
||||||
@ -635,6 +696,7 @@ export const ApiContractV1Implementation = createNextRoute(ApiContractV1, {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const remappedField = {
|
const remappedField = {
|
||||||
|
id: deletedField.id,
|
||||||
documentId: deletedField.documentId,
|
documentId: deletedField.documentId,
|
||||||
recipientId: deletedField.recipientId ?? -1,
|
recipientId: deletedField.recipientId ?? -1,
|
||||||
type: deletedField.type,
|
type: deletedField.type,
|
||||||
|
|||||||
@ -16,7 +16,10 @@ export const authenticatedMiddleware = <
|
|||||||
) => {
|
) => {
|
||||||
return async (args: T) => {
|
return async (args: T) => {
|
||||||
try {
|
try {
|
||||||
const { authorization: token } = args.req.headers;
|
const { authorization } = args.req.headers;
|
||||||
|
|
||||||
|
// Support for both "Authorization: Bearer api_xxx" and "Authorization: api_xxx"
|
||||||
|
const [token] = (authorization || '').split('Bearer ').filter((s) => s.length > 0);
|
||||||
|
|
||||||
if (!token) {
|
if (!token) {
|
||||||
throw new Error('Token was not provided for authenticated middleware');
|
throw new Error('Token was not provided for authenticated middleware');
|
||||||
@ -26,6 +29,7 @@ export const authenticatedMiddleware = <
|
|||||||
|
|
||||||
return await handler(args, user);
|
return await handler(args, user);
|
||||||
} catch (_err) {
|
} catch (_err) {
|
||||||
|
console.log({ _err });
|
||||||
return {
|
return {
|
||||||
status: 401,
|
status: 401,
|
||||||
body: {
|
body: {
|
||||||
|
|||||||
@ -12,8 +12,8 @@ import {
|
|||||||
* Documents
|
* Documents
|
||||||
*/
|
*/
|
||||||
export const ZGetDocumentsQuerySchema = z.object({
|
export const ZGetDocumentsQuerySchema = z.object({
|
||||||
page: z.number().min(1).optional().default(1),
|
page: z.coerce.number().min(1).optional().default(1),
|
||||||
perPage: z.number().min(1).optional().default(1),
|
perPage: z.coerce.number().min(1).optional().default(1),
|
||||||
});
|
});
|
||||||
|
|
||||||
export type TGetDocumentsQuerySchema = z.infer<typeof ZGetDocumentsQuerySchema>;
|
export type TGetDocumentsQuerySchema = z.infer<typeof ZGetDocumentsQuerySchema>;
|
||||||
@ -33,6 +33,14 @@ export const ZSuccessfulDocumentResponseSchema = z.object({
|
|||||||
completedAt: z.date().nullable(),
|
completedAt: z.date().nullable(),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const ZSuccessfulGetDocumentResponseSchema = ZSuccessfulDocumentResponseSchema.extend({
|
||||||
|
recipients: z.lazy(() => z.array(ZSuccessfulRecipientResponseSchema)),
|
||||||
|
});
|
||||||
|
|
||||||
|
export type TSuccessfulGetDocumentResponseSchema = z.infer<
|
||||||
|
typeof ZSuccessfulGetDocumentResponseSchema
|
||||||
|
>;
|
||||||
|
|
||||||
export type TSuccessfulDocumentResponseSchema = z.infer<typeof ZSuccessfulDocumentResponseSchema>;
|
export type TSuccessfulDocumentResponseSchema = z.infer<typeof ZSuccessfulDocumentResponseSchema>;
|
||||||
|
|
||||||
export const ZSendDocumentForSigningMutationSchema = null;
|
export const ZSendDocumentForSigningMutationSchema = null;
|
||||||
@ -84,6 +92,48 @@ export type TCreateDocumentMutationResponseSchema = z.infer<
|
|||||||
typeof ZCreateDocumentMutationResponseSchema
|
typeof ZCreateDocumentMutationResponseSchema
|
||||||
>;
|
>;
|
||||||
|
|
||||||
|
export const ZCreateDocumentFromTemplateMutationSchema = z.object({
|
||||||
|
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(),
|
||||||
|
})
|
||||||
|
.partial()
|
||||||
|
.optional(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export type TCreateDocumentFromTemplateMutationSchema = z.infer<
|
||||||
|
typeof ZCreateDocumentFromTemplateMutationSchema
|
||||||
|
>;
|
||||||
|
|
||||||
|
export const ZCreateDocumentFromTemplateMutationResponseSchema = z.object({
|
||||||
|
documentId: z.number(),
|
||||||
|
recipients: z.array(
|
||||||
|
z.object({
|
||||||
|
recipientId: z.number(),
|
||||||
|
name: z.string(),
|
||||||
|
email: z.string().email().min(1),
|
||||||
|
token: z.string(),
|
||||||
|
role: z.nativeEnum(RecipientRole).optional().default(RecipientRole.SIGNER),
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
});
|
||||||
|
|
||||||
|
export type TCreateDocumentFromTemplateMutationResponseSchema = z.infer<
|
||||||
|
typeof ZCreateDocumentFromTemplateMutationResponseSchema
|
||||||
|
>;
|
||||||
|
|
||||||
export const ZCreateRecipientMutationSchema = z.object({
|
export const ZCreateRecipientMutationSchema = z.object({
|
||||||
name: z.string().min(1),
|
name: z.string().min(1),
|
||||||
email: z.string().email().min(1),
|
email: z.string().email().min(1),
|
||||||
@ -105,7 +155,9 @@ export type TDeleteRecipientMutationSchema = typeof ZDeleteRecipientMutationSche
|
|||||||
|
|
||||||
export const ZSuccessfulRecipientResponseSchema = z.object({
|
export const ZSuccessfulRecipientResponseSchema = z.object({
|
||||||
id: z.number(),
|
id: z.number(),
|
||||||
documentId: z.number(),
|
// !: This handles the fact that we have null documentId's for templates
|
||||||
|
// !: while we won't need the default we must add it to satisfy typescript
|
||||||
|
documentId: z.number().nullish().default(-1),
|
||||||
email: z.string().email().min(1),
|
email: z.string().email().min(1),
|
||||||
name: z.string(),
|
name: z.string(),
|
||||||
role: z.nativeEnum(RecipientRole),
|
role: z.nativeEnum(RecipientRole),
|
||||||
|
|||||||
@ -22,9 +22,14 @@ export const getUserByApiToken = async ({ token }: { token: string }) => {
|
|||||||
throw new Error('Invalid token');
|
throw new Error('Invalid token');
|
||||||
}
|
}
|
||||||
|
|
||||||
const tokenObject = user.ApiToken.find((apiToken) => apiToken.token === hashedToken);
|
const retrievedToken = user.ApiToken.find((apiToken) => apiToken.token === hashedToken);
|
||||||
|
|
||||||
if (!tokenObject || new Date(tokenObject.expires) < new Date()) {
|
// This should be impossible but we need to satisfy TypeScript
|
||||||
|
if (!retrievedToken) {
|
||||||
|
throw new Error('Invalid token');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (retrievedToken.expires && retrievedToken.expires < new Date()) {
|
||||||
throw new Error('Expired token');
|
throw new Error('Expired token');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,14 +1,21 @@
|
|||||||
import { nanoid } from '@documenso/lib/universal/id';
|
import { nanoid } from '@documenso/lib/universal/id';
|
||||||
import { prisma } from '@documenso/prisma';
|
import { prisma } from '@documenso/prisma';
|
||||||
import type { TCreateDocumentFromTemplateMutationSchema } from '@documenso/trpc/server/template-router/schema';
|
import type { RecipientRole } from '@documenso/prisma/client';
|
||||||
|
|
||||||
export type CreateDocumentFromTemplateOptions = TCreateDocumentFromTemplateMutationSchema & {
|
export type CreateDocumentFromTemplateOptions = {
|
||||||
|
templateId: number;
|
||||||
userId: number;
|
userId: number;
|
||||||
|
recipients?: {
|
||||||
|
name?: string;
|
||||||
|
email: string;
|
||||||
|
role?: RecipientRole;
|
||||||
|
}[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export const createDocumentFromTemplate = async ({
|
export const createDocumentFromTemplate = async ({
|
||||||
templateId,
|
templateId,
|
||||||
userId,
|
userId,
|
||||||
|
recipients,
|
||||||
}: CreateDocumentFromTemplateOptions) => {
|
}: CreateDocumentFromTemplateOptions) => {
|
||||||
const template = await prisma.template.findUnique({
|
const template = await prisma.template.findUnique({
|
||||||
where: {
|
where: {
|
||||||
@ -63,7 +70,11 @@ export const createDocumentFromTemplate = async ({
|
|||||||
},
|
},
|
||||||
|
|
||||||
include: {
|
include: {
|
||||||
Recipient: true,
|
Recipient: {
|
||||||
|
orderBy: {
|
||||||
|
id: 'asc',
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -88,5 +99,34 @@ export const createDocumentFromTemplate = async ({
|
|||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (recipients && recipients.length > 0) {
|
||||||
|
document.Recipient = await Promise.all(
|
||||||
|
recipients.map(async (recipient, index) => {
|
||||||
|
const existingRecipient = document.Recipient.at(index);
|
||||||
|
|
||||||
|
return await prisma.recipient.upsert({
|
||||||
|
where: {
|
||||||
|
documentId_email: {
|
||||||
|
documentId: document.id,
|
||||||
|
email: existingRecipient?.email ?? recipient.email,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
update: {
|
||||||
|
name: recipient.name,
|
||||||
|
email: recipient.email,
|
||||||
|
role: recipient.role,
|
||||||
|
},
|
||||||
|
create: {
|
||||||
|
documentId: document.id,
|
||||||
|
email: recipient.email,
|
||||||
|
name: recipient.name,
|
||||||
|
role: recipient.role,
|
||||||
|
token: nanoid(),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return document;
|
return document;
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user