feat: add create document beta endpoint (#1584)
This commit is contained in:
@@ -4,6 +4,19 @@ import { DEFAULT_DOCUMENT_TIME_ZONE } from './time-zones';
|
||||
|
||||
export const DEFAULT_DOCUMENT_DATE_FORMAT = 'yyyy-MM-dd hh:mm a';
|
||||
|
||||
export const VALID_DATE_FORMAT_VALUES = [
|
||||
DEFAULT_DOCUMENT_DATE_FORMAT,
|
||||
'yyyy-MM-dd',
|
||||
'dd/MM/yyyy hh:mm a',
|
||||
'MM/dd/yyyy hh:mm a',
|
||||
'yyyy-MM-dd HH:mm',
|
||||
'yy-MM-dd hh:mm a',
|
||||
'yyyy-MM-dd HH:mm:ss',
|
||||
'MMMM dd, yyyy hh:mm a',
|
||||
'EEEE, MMMM dd, yyyy hh:mm a',
|
||||
"yyyy-MM-dd'T'HH:mm:ss.SSSXXX",
|
||||
] as const;
|
||||
|
||||
export const DATE_FORMATS = [
|
||||
{
|
||||
key: 'yyyy-MM-dd_hh:mm_a',
|
||||
@@ -55,7 +68,11 @@ export const DATE_FORMATS = [
|
||||
label: 'ISO 8601',
|
||||
value: "yyyy-MM-dd'T'HH:mm:ss.SSSXXX",
|
||||
},
|
||||
];
|
||||
] satisfies {
|
||||
key: string;
|
||||
label: string;
|
||||
value: (typeof VALID_DATE_FORMAT_VALUES)[number];
|
||||
}[];
|
||||
|
||||
export const convertToLocalSystemFormat = (
|
||||
customText: string,
|
||||
|
||||
248
packages/lib/server-only/document/create-document-v2.ts
Normal file
248
packages/lib/server-only/document/create-document-v2.ts
Normal file
@@ -0,0 +1,248 @@
|
||||
import { isUserEnterprise } from '@documenso/ee/server-only/util/is-document-enterprise';
|
||||
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
||||
import { normalizePdf as makeNormalizedPdf } from '@documenso/lib/server-only/pdf/normalize-pdf';
|
||||
import { DOCUMENT_AUDIT_LOG_TYPE } from '@documenso/lib/types/document-audit-logs';
|
||||
import type { ApiRequestMetadata } from '@documenso/lib/universal/extract-request-metadata';
|
||||
import { nanoid } from '@documenso/lib/universal/id';
|
||||
import { createDocumentAuditLogData } from '@documenso/lib/utils/document-audit-logs';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import type { DocumentVisibility, TemplateMeta } from '@documenso/prisma/client';
|
||||
import {
|
||||
DocumentSource,
|
||||
RecipientRole,
|
||||
SendStatus,
|
||||
SigningStatus,
|
||||
WebhookTriggerEvents,
|
||||
} from '@documenso/prisma/client';
|
||||
import { TeamMemberRole } from '@documenso/prisma/client';
|
||||
import type { TCreateDocumentV2Request } from '@documenso/trpc/server/document-router/schema';
|
||||
|
||||
import type { TDocumentAccessAuthTypes, TDocumentActionAuthTypes } from '../../types/document-auth';
|
||||
import type { TDocumentFormValues } from '../../types/document-form-values';
|
||||
import {
|
||||
ZWebhookDocumentSchema,
|
||||
mapDocumentToWebhookDocumentPayload,
|
||||
} from '../../types/webhook-payload';
|
||||
import { getFile } from '../../universal/upload/get-file';
|
||||
import { putPdfFile } from '../../universal/upload/put-file';
|
||||
import { createDocumentAuthOptions, createRecipientAuthOptions } from '../../utils/document-auth';
|
||||
import { determineDocumentVisibility } from '../../utils/document-visibility';
|
||||
import { triggerWebhook } from '../webhooks/trigger/trigger-webhook';
|
||||
|
||||
export type CreateDocumentOptions = {
|
||||
userId: number;
|
||||
teamId?: number;
|
||||
documentDataId: string;
|
||||
normalizePdf?: boolean;
|
||||
data: {
|
||||
title: string;
|
||||
externalId?: string;
|
||||
visibility?: DocumentVisibility;
|
||||
globalAccessAuth?: TDocumentAccessAuthTypes;
|
||||
globalActionAuth?: TDocumentActionAuthTypes;
|
||||
formValues?: TDocumentFormValues;
|
||||
recipients: TCreateDocumentV2Request['recipients'];
|
||||
};
|
||||
meta?: Partial<Omit<TemplateMeta, 'id' | 'templateId'>>;
|
||||
requestMetadata: ApiRequestMetadata;
|
||||
};
|
||||
|
||||
export const createDocumentV2 = async ({
|
||||
userId,
|
||||
teamId,
|
||||
documentDataId,
|
||||
normalizePdf,
|
||||
data,
|
||||
meta,
|
||||
requestMetadata,
|
||||
}: CreateDocumentOptions) => {
|
||||
const { title, formValues } = data;
|
||||
|
||||
const team = teamId
|
||||
? await prisma.team.findFirst({
|
||||
where: {
|
||||
id: teamId,
|
||||
members: {
|
||||
some: {
|
||||
userId,
|
||||
},
|
||||
},
|
||||
},
|
||||
include: {
|
||||
teamGlobalSettings: true,
|
||||
members: {
|
||||
where: {
|
||||
userId: userId,
|
||||
},
|
||||
select: {
|
||||
role: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
: null;
|
||||
|
||||
if (teamId !== undefined && !team) {
|
||||
throw new AppError(AppErrorCode.NOT_FOUND, {
|
||||
message: 'Team not found',
|
||||
});
|
||||
}
|
||||
|
||||
if (normalizePdf) {
|
||||
const documentData = await prisma.documentData.findFirst({
|
||||
where: {
|
||||
id: documentDataId,
|
||||
},
|
||||
});
|
||||
|
||||
if (documentData) {
|
||||
const buffer = await getFile(documentData);
|
||||
|
||||
const normalizedPdf = await makeNormalizedPdf(Buffer.from(buffer));
|
||||
|
||||
const newDocumentData = await putPdfFile({
|
||||
name: title.endsWith('.pdf') ? title : `${title}.pdf`,
|
||||
type: 'application/pdf',
|
||||
arrayBuffer: async () => Promise.resolve(normalizedPdf),
|
||||
});
|
||||
|
||||
// eslint-disable-next-line require-atomic-updates
|
||||
documentDataId = newDocumentData.id;
|
||||
}
|
||||
}
|
||||
|
||||
const authOptions = createDocumentAuthOptions({
|
||||
globalAccessAuth: data?.globalAccessAuth || null,
|
||||
globalActionAuth: data?.globalActionAuth || null,
|
||||
});
|
||||
|
||||
const recipientsHaveActionAuth = data.recipients?.some((recipient) => recipient.actionAuth);
|
||||
|
||||
// Check if user has permission to set the global action auth.
|
||||
if (authOptions.globalActionAuth || recipientsHaveActionAuth) {
|
||||
const isDocumentEnterprise = await isUserEnterprise({
|
||||
userId,
|
||||
teamId,
|
||||
});
|
||||
|
||||
if (!isDocumentEnterprise) {
|
||||
throw new AppError(AppErrorCode.UNAUTHORIZED, {
|
||||
message: 'You do not have permission to set the action auth',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const visibility = determineDocumentVisibility(
|
||||
team?.teamGlobalSettings?.documentVisibility,
|
||||
team?.members[0].role ?? TeamMemberRole.MEMBER,
|
||||
);
|
||||
|
||||
return await prisma.$transaction(async (tx) => {
|
||||
const document = await tx.document.create({
|
||||
data: {
|
||||
title,
|
||||
externalId: data.externalId,
|
||||
documentDataId,
|
||||
userId,
|
||||
teamId,
|
||||
authOptions,
|
||||
visibility,
|
||||
formValues,
|
||||
source: DocumentSource.DOCUMENT,
|
||||
documentMeta: {
|
||||
create: {
|
||||
...meta,
|
||||
signingOrder: meta?.signingOrder || undefined,
|
||||
emailSettings: meta?.emailSettings || undefined,
|
||||
language: meta?.language || team?.teamGlobalSettings?.documentLanguage,
|
||||
typedSignatureEnabled:
|
||||
meta?.typedSignatureEnabled ?? team?.teamGlobalSettings?.typedSignatureEnabled,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
await Promise.all(
|
||||
(data.recipients || []).map(async (recipient) => {
|
||||
const recipientAuthOptions = createRecipientAuthOptions({
|
||||
accessAuth: recipient.accessAuth || null,
|
||||
actionAuth: recipient.actionAuth || null,
|
||||
});
|
||||
|
||||
await tx.recipient.create({
|
||||
data: {
|
||||
documentId: document.id,
|
||||
name: recipient.name,
|
||||
email: recipient.email,
|
||||
role: recipient.role,
|
||||
signingOrder: recipient.signingOrder,
|
||||
token: nanoid(),
|
||||
sendStatus: recipient.role === RecipientRole.CC ? SendStatus.SENT : SendStatus.NOT_SENT,
|
||||
signingStatus:
|
||||
recipient.role === RecipientRole.CC ? SigningStatus.SIGNED : SigningStatus.NOT_SIGNED,
|
||||
authOptions: recipientAuthOptions,
|
||||
fields: {
|
||||
createMany: {
|
||||
data: (recipient.fields || []).map((field) => ({
|
||||
documentId: document.id,
|
||||
type: field.type,
|
||||
page: field.pageNumber,
|
||||
positionX: field.pageX,
|
||||
positionY: field.pageY,
|
||||
width: field.width,
|
||||
height: field.height,
|
||||
customText: '',
|
||||
inserted: false,
|
||||
fieldMeta: field.fieldMeta,
|
||||
})),
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
}),
|
||||
);
|
||||
|
||||
// Todo: Is it necessary to create a full audit log with all fields and recipients audit logs?
|
||||
|
||||
await tx.documentAuditLog.create({
|
||||
data: createDocumentAuditLogData({
|
||||
type: DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_CREATED,
|
||||
documentId: document.id,
|
||||
metadata: requestMetadata,
|
||||
data: {
|
||||
title,
|
||||
source: {
|
||||
type: DocumentSource.DOCUMENT,
|
||||
},
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
const createdDocument = await tx.document.findFirst({
|
||||
where: {
|
||||
id: document.id,
|
||||
},
|
||||
include: {
|
||||
documentData: true,
|
||||
documentMeta: true,
|
||||
recipients: true,
|
||||
fields: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!createdDocument) {
|
||||
throw new AppError(AppErrorCode.NOT_FOUND, {
|
||||
message: 'Document not found',
|
||||
});
|
||||
}
|
||||
|
||||
await triggerWebhook({
|
||||
event: WebhookTriggerEvents.DOCUMENT_CREATED,
|
||||
data: ZWebhookDocumentSchema.parse(mapDocumentToWebhookDocumentPayload(createdDocument)),
|
||||
userId,
|
||||
teamId,
|
||||
});
|
||||
|
||||
return createdDocument;
|
||||
});
|
||||
};
|
||||
@@ -6,7 +6,7 @@ import { DOCUMENT_AUDIT_LOG_TYPE } from '@documenso/lib/types/document-audit-log
|
||||
import type { ApiRequestMetadata } from '@documenso/lib/universal/extract-request-metadata';
|
||||
import { createDocumentAuditLogData } from '@documenso/lib/utils/document-audit-logs';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import { DocumentSource, DocumentVisibility, WebhookTriggerEvents } from '@documenso/prisma/client';
|
||||
import { DocumentSource, WebhookTriggerEvents } from '@documenso/prisma/client';
|
||||
import type { Team, TeamGlobalSettings } from '@documenso/prisma/client';
|
||||
import { TeamMemberRole } from '@documenso/prisma/client';
|
||||
|
||||
@@ -16,6 +16,7 @@ import {
|
||||
} from '../../types/webhook-payload';
|
||||
import { getFile } from '../../universal/upload/get-file';
|
||||
import { putPdfFile } from '../../universal/upload/put-file';
|
||||
import { determineDocumentVisibility } from '../../utils/document-visibility';
|
||||
import { triggerWebhook } from '../webhooks/trigger/trigger-webhook';
|
||||
|
||||
export type CreateDocumentOptions = {
|
||||
@@ -88,25 +89,6 @@ export const createDocument = async ({
|
||||
userTeamRole = teamWithUserRole.members[0]?.role;
|
||||
}
|
||||
|
||||
const determineVisibility = (
|
||||
globalVisibility: DocumentVisibility | null | undefined,
|
||||
userRole: TeamMemberRole,
|
||||
): DocumentVisibility => {
|
||||
if (globalVisibility) {
|
||||
return globalVisibility;
|
||||
}
|
||||
|
||||
if (userRole === TeamMemberRole.ADMIN) {
|
||||
return DocumentVisibility.ADMIN;
|
||||
}
|
||||
|
||||
if (userRole === TeamMemberRole.MANAGER) {
|
||||
return DocumentVisibility.MANAGER_AND_ABOVE;
|
||||
}
|
||||
|
||||
return DocumentVisibility.EVERYONE;
|
||||
};
|
||||
|
||||
if (normalizePdf) {
|
||||
const documentData = await prisma.documentData.findFirst({
|
||||
where: {
|
||||
@@ -138,7 +120,7 @@ export const createDocument = async ({
|
||||
documentDataId,
|
||||
userId,
|
||||
teamId,
|
||||
visibility: determineVisibility(
|
||||
visibility: determineDocumentVisibility(
|
||||
team?.teamGlobalSettings?.documentVisibility,
|
||||
userTeamRole ?? TeamMemberRole.MEMBER,
|
||||
),
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import { DOCUMENT_AUDIT_LOG_TYPE } from '@documenso/lib/types/document-audit-logs';
|
||||
import type { TFieldMetaSchema } from '@documenso/lib/types/field-meta';
|
||||
import type { TFieldAndMeta } from '@documenso/lib/types/field-meta';
|
||||
import type { ApiRequestMetadata } from '@documenso/lib/universal/extract-request-metadata';
|
||||
import { createDocumentAuditLogData } from '@documenso/lib/utils/document-audit-logs';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import type { FieldType } from '@documenso/prisma/client';
|
||||
|
||||
import { AppError, AppErrorCode } from '../../errors/app-error';
|
||||
import { canRecipientFieldsBeModified } from '../../utils/recipients';
|
||||
@@ -12,16 +11,14 @@ export interface CreateDocumentFieldsOptions {
|
||||
userId: number;
|
||||
teamId?: number;
|
||||
documentId: number;
|
||||
fields: {
|
||||
fields: (TFieldAndMeta & {
|
||||
recipientId: number;
|
||||
type: FieldType;
|
||||
pageNumber: number;
|
||||
pageX: number;
|
||||
pageY: number;
|
||||
width: number;
|
||||
height: number;
|
||||
fieldMeta?: TFieldMetaSchema;
|
||||
}[];
|
||||
})[];
|
||||
requestMetadata: ApiRequestMetadata;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
'use server';
|
||||
|
||||
import { nanoid } from 'nanoid';
|
||||
import type { z } from 'zod';
|
||||
|
||||
import {
|
||||
DIRECT_TEMPLATE_RECIPIENT_EMAIL,
|
||||
@@ -9,7 +8,6 @@ import {
|
||||
} from '@documenso/lib/constants/direct-templates';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import type { Recipient } from '@documenso/prisma/client';
|
||||
import { TemplateDirectLinkSchema } from '@documenso/prisma/generated/zod';
|
||||
|
||||
import { AppError, AppErrorCode } from '../../errors/app-error';
|
||||
|
||||
@@ -20,18 +18,12 @@ export type CreateTemplateDirectLinkOptions = {
|
||||
directRecipientId?: number;
|
||||
};
|
||||
|
||||
export const ZCreateTemplateDirectLinkResponseSchema = TemplateDirectLinkSchema;
|
||||
|
||||
export type TCreateTemplateDirectLinkResponse = z.infer<
|
||||
typeof ZCreateTemplateDirectLinkResponseSchema
|
||||
>;
|
||||
|
||||
export const createTemplateDirectLink = async ({
|
||||
templateId,
|
||||
userId,
|
||||
teamId,
|
||||
directRecipientId,
|
||||
}: CreateTemplateDirectLinkOptions): Promise<TCreateTemplateDirectLinkResponse> => {
|
||||
}: CreateTemplateDirectLinkOptions) => {
|
||||
const template = await prisma.template.findFirst({
|
||||
where: {
|
||||
id: templateId,
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
import { omit } from 'remeda';
|
||||
import type { z } from 'zod';
|
||||
|
||||
import { nanoid } from '@documenso/lib/universal/id';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import type { Prisma } from '@documenso/prisma/client';
|
||||
import { TemplateSchema } from '@documenso/prisma/generated/zod';
|
||||
import type { TDuplicateTemplateMutationSchema } from '@documenso/trpc/server/template-router/schema';
|
||||
|
||||
export type DuplicateTemplateOptions = TDuplicateTemplateMutationSchema & {
|
||||
@@ -12,15 +10,11 @@ export type DuplicateTemplateOptions = TDuplicateTemplateMutationSchema & {
|
||||
teamId?: number;
|
||||
};
|
||||
|
||||
export const ZDuplicateTemplateResponseSchema = TemplateSchema;
|
||||
|
||||
export type TDuplicateTemplateResponse = z.infer<typeof ZDuplicateTemplateResponseSchema>;
|
||||
|
||||
export const duplicateTemplate = async ({
|
||||
templateId,
|
||||
userId,
|
||||
teamId,
|
||||
}: DuplicateTemplateOptions): Promise<TDuplicateTemplateResponse> => {
|
||||
}: DuplicateTemplateOptions) => {
|
||||
const template = await prisma.template.findUnique({
|
||||
where: {
|
||||
id: templateId,
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { match } from 'ts-pattern';
|
||||
import type { z } from 'zod';
|
||||
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import {
|
||||
@@ -8,18 +7,9 @@ import {
|
||||
TeamMemberRole,
|
||||
type Template,
|
||||
} from '@documenso/prisma/client';
|
||||
import {
|
||||
DocumentDataSchema,
|
||||
FieldSchema,
|
||||
RecipientSchema,
|
||||
TeamSchema,
|
||||
TemplateDirectLinkSchema,
|
||||
TemplateMetaSchema,
|
||||
TemplateSchema,
|
||||
} from '@documenso/prisma/generated/zod';
|
||||
|
||||
import { AppError, AppErrorCode } from '../../errors/app-error';
|
||||
import { type FindResultResponse, ZFindResultResponse } from '../../types/search-params';
|
||||
import { type FindResultResponse } from '../../types/search-params';
|
||||
|
||||
export type FindTemplatesOptions = {
|
||||
userId: number;
|
||||
@@ -29,36 +19,13 @@ export type FindTemplatesOptions = {
|
||||
perPage?: number;
|
||||
};
|
||||
|
||||
export const ZFindTemplatesResponseSchema = ZFindResultResponse.extend({
|
||||
data: TemplateSchema.extend({
|
||||
templateDocumentData: DocumentDataSchema,
|
||||
team: TeamSchema.pick({
|
||||
id: true,
|
||||
url: true,
|
||||
}).nullable(),
|
||||
fields: FieldSchema.array(),
|
||||
recipients: RecipientSchema.array(),
|
||||
templateMeta: TemplateMetaSchema.pick({
|
||||
signingOrder: true,
|
||||
distributionMethod: true,
|
||||
}).nullable(),
|
||||
directLink: TemplateDirectLinkSchema.pick({
|
||||
token: true,
|
||||
enabled: true,
|
||||
}).nullable(),
|
||||
}).array(), // Todo: openapi.
|
||||
});
|
||||
|
||||
export type TFindTemplatesResponse = z.infer<typeof ZFindTemplatesResponseSchema>;
|
||||
export type FindTemplateRow = TFindTemplatesResponse['data'][number];
|
||||
|
||||
export const findTemplates = async ({
|
||||
userId,
|
||||
teamId,
|
||||
type,
|
||||
page = 1,
|
||||
perPage = 10,
|
||||
}: FindTemplatesOptions): Promise<TFindTemplatesResponse> => {
|
||||
}: FindTemplatesOptions) => {
|
||||
const whereFilter: Prisma.TemplateWhereInput[] = [];
|
||||
|
||||
if (teamId === undefined) {
|
||||
@@ -112,7 +79,6 @@ export const findTemplates = async ({
|
||||
AND: whereFilter,
|
||||
},
|
||||
include: {
|
||||
templateDocumentData: true,
|
||||
team: {
|
||||
select: {
|
||||
id: true,
|
||||
|
||||
@@ -1,15 +1,4 @@
|
||||
import type { z } from 'zod';
|
||||
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import {
|
||||
DocumentDataSchema,
|
||||
FieldSchema,
|
||||
RecipientSchema,
|
||||
TemplateDirectLinkSchema,
|
||||
TemplateMetaSchema,
|
||||
TemplateSchema,
|
||||
UserSchema,
|
||||
} from '@documenso/prisma/generated/zod';
|
||||
|
||||
import { AppError, AppErrorCode } from '../../errors/app-error';
|
||||
|
||||
@@ -19,26 +8,7 @@ export type GetTemplateByIdOptions = {
|
||||
teamId?: number;
|
||||
};
|
||||
|
||||
export const ZGetTemplateByIdResponseSchema = TemplateSchema.extend({
|
||||
directLink: TemplateDirectLinkSchema.nullable(),
|
||||
templateDocumentData: DocumentDataSchema,
|
||||
templateMeta: TemplateMetaSchema.nullable(),
|
||||
recipients: RecipientSchema.array(),
|
||||
fields: FieldSchema.array(),
|
||||
user: UserSchema.pick({
|
||||
id: true,
|
||||
name: true,
|
||||
email: true,
|
||||
}),
|
||||
});
|
||||
|
||||
export type TGetTemplateByIdResponse = z.infer<typeof ZGetTemplateByIdResponseSchema>;
|
||||
|
||||
export const getTemplateById = async ({
|
||||
id,
|
||||
userId,
|
||||
teamId,
|
||||
}: GetTemplateByIdOptions): Promise<TGetTemplateByIdResponse> => {
|
||||
export const getTemplateById = async ({ id, userId, teamId }: GetTemplateByIdOptions) => {
|
||||
const template = await prisma.template.findFirst({
|
||||
where: {
|
||||
id,
|
||||
|
||||
@@ -1,7 +1,4 @@
|
||||
import type { z } from 'zod';
|
||||
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import { TemplateSchema } from '@documenso/prisma/generated/zod';
|
||||
|
||||
import { AppError, AppErrorCode } from '../../errors/app-error';
|
||||
|
||||
@@ -11,15 +8,11 @@ export type MoveTemplateToTeamOptions = {
|
||||
userId: number;
|
||||
};
|
||||
|
||||
export const ZMoveTemplateToTeamResponseSchema = TemplateSchema;
|
||||
|
||||
export type TMoveTemplateToTeamResponse = z.infer<typeof ZMoveTemplateToTeamResponseSchema>;
|
||||
|
||||
export const moveTemplateToTeam = async ({
|
||||
templateId,
|
||||
teamId,
|
||||
userId,
|
||||
}: MoveTemplateToTeamOptions): Promise<TMoveTemplateToTeamResponse> => {
|
||||
}: MoveTemplateToTeamOptions) => {
|
||||
return await prisma.$transaction(async (tx) => {
|
||||
const template = await tx.template.findFirst({
|
||||
where: {
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
'use server';
|
||||
|
||||
import type { z } from 'zod';
|
||||
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import { TemplateDirectLinkSchema } from '@documenso/prisma/generated/zod';
|
||||
|
||||
import { AppError, AppErrorCode } from '../../errors/app-error';
|
||||
|
||||
@@ -14,18 +11,12 @@ export type ToggleTemplateDirectLinkOptions = {
|
||||
enabled: boolean;
|
||||
};
|
||||
|
||||
export const ZToggleTemplateDirectLinkResponseSchema = TemplateDirectLinkSchema;
|
||||
|
||||
export type TToggleTemplateDirectLinkResponse = z.infer<
|
||||
typeof ZToggleTemplateDirectLinkResponseSchema
|
||||
>;
|
||||
|
||||
export const toggleTemplateDirectLink = async ({
|
||||
templateId,
|
||||
userId,
|
||||
teamId,
|
||||
enabled,
|
||||
}: ToggleTemplateDirectLinkOptions): Promise<TToggleTemplateDirectLinkResponse> => {
|
||||
}: ToggleTemplateDirectLinkOptions) => {
|
||||
const template = await prisma.template.findFirst({
|
||||
where: {
|
||||
id: templateId,
|
||||
|
||||
@@ -1,11 +1,8 @@
|
||||
'use server';
|
||||
|
||||
import type { z } from 'zod';
|
||||
|
||||
import { isUserEnterprise } from '@documenso/ee/server-only/util/is-document-enterprise';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import type { DocumentVisibility, Template, TemplateMeta } from '@documenso/prisma/client';
|
||||
import { TemplateSchema } from '@documenso/prisma/generated/zod';
|
||||
|
||||
import { AppError, AppErrorCode } from '../../errors/app-error';
|
||||
import type { TDocumentAccessAuthTypes, TDocumentActionAuthTypes } from '../../types/document-auth';
|
||||
@@ -28,17 +25,13 @@ export type UpdateTemplateOptions = {
|
||||
meta?: Partial<Omit<TemplateMeta, 'id' | 'templateId'>>;
|
||||
};
|
||||
|
||||
export const ZUpdateTemplateResponseSchema = TemplateSchema;
|
||||
|
||||
export type TUpdateTemplateResponse = z.infer<typeof ZUpdateTemplateResponseSchema>;
|
||||
|
||||
export const updateTemplate = async ({
|
||||
userId,
|
||||
teamId,
|
||||
templateId,
|
||||
meta = {},
|
||||
data = {},
|
||||
}: UpdateTemplateOptions): Promise<TUpdateTemplateResponse> => {
|
||||
}: UpdateTemplateOptions) => {
|
||||
const template = await prisma.template.findFirstOrThrow({
|
||||
where: {
|
||||
id: templateId,
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,7 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
import { FieldType } from '@documenso/prisma/client';
|
||||
|
||||
export const ZBaseFieldMeta = z.object({
|
||||
label: z.string().optional(),
|
||||
placeholder: z.string().optional(),
|
||||
@@ -114,3 +116,53 @@ export type TFieldMetaNotOptionalSchema = z.infer<typeof ZFieldMetaNotOptionalSc
|
||||
export const ZFieldMetaSchema = ZFieldMetaNotOptionalSchema.optional();
|
||||
|
||||
export type TFieldMetaSchema = z.infer<typeof ZFieldMetaSchema>;
|
||||
|
||||
export const ZFieldAndMetaSchema = z.discriminatedUnion('type', [
|
||||
z.object({
|
||||
type: z.literal(FieldType.SIGNATURE),
|
||||
// Do not use z.undefined(), or void since this will create an invalid schema for Speakeasy SDK generation.
|
||||
fieldMeta: z.literal(undefined),
|
||||
}),
|
||||
z.object({
|
||||
type: z.literal(FieldType.FREE_SIGNATURE),
|
||||
fieldMeta: z.literal(undefined), // Same as above.
|
||||
}),
|
||||
z.object({
|
||||
type: z.literal(FieldType.INITIALS),
|
||||
fieldMeta: ZInitialsFieldMeta.optional(),
|
||||
}),
|
||||
z.object({
|
||||
type: z.literal(FieldType.NAME),
|
||||
fieldMeta: ZNameFieldMeta.optional(),
|
||||
}),
|
||||
z.object({
|
||||
type: z.literal(FieldType.EMAIL),
|
||||
fieldMeta: ZEmailFieldMeta.optional(),
|
||||
}),
|
||||
z.object({
|
||||
type: z.literal(FieldType.DATE),
|
||||
fieldMeta: ZDateFieldMeta.optional(),
|
||||
}),
|
||||
z.object({
|
||||
type: z.literal(FieldType.TEXT),
|
||||
fieldMeta: ZTextFieldMeta.optional(),
|
||||
}),
|
||||
z.object({
|
||||
type: z.literal(FieldType.NUMBER),
|
||||
fieldMeta: ZNumberFieldMeta.optional(),
|
||||
}),
|
||||
z.object({
|
||||
type: z.literal(FieldType.RADIO),
|
||||
fieldMeta: ZRadioFieldMeta.optional(),
|
||||
}),
|
||||
z.object({
|
||||
type: z.literal(FieldType.CHECKBOX),
|
||||
fieldMeta: ZCheckboxFieldMeta.optional(),
|
||||
}),
|
||||
z.object({
|
||||
type: z.literal(FieldType.DROPDOWN),
|
||||
fieldMeta: ZDropdownFieldMeta.optional(),
|
||||
}),
|
||||
]);
|
||||
|
||||
export type TFieldAndMeta = z.infer<typeof ZFieldAndMetaSchema>;
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
import { FieldSchema } from '@documenso/prisma/generated/zod';
|
||||
|
||||
/**
|
||||
@@ -28,3 +30,22 @@ export const ZFieldSchema = FieldSchema.pick({
|
||||
inserted: true,
|
||||
fieldMeta: true,
|
||||
});
|
||||
|
||||
export const ZFieldPageNumberSchema = z
|
||||
.number()
|
||||
.min(1)
|
||||
.describe('The page number the field will be on.');
|
||||
|
||||
export const ZFieldPageXSchema = z
|
||||
.number()
|
||||
.min(0)
|
||||
.describe('The X coordinate of where the field will be placed.');
|
||||
|
||||
export const ZFieldPageYSchema = z
|
||||
.number()
|
||||
.min(0)
|
||||
.describe('The Y coordinate of where the field will be placed.');
|
||||
|
||||
export const ZFieldWidthSchema = z.number().min(1).describe('The width of the field.');
|
||||
|
||||
export const ZFieldHeightSchema = z.number().min(1).describe('The height of the field.');
|
||||
|
||||
119
packages/lib/types/template.ts
Normal file
119
packages/lib/types/template.ts
Normal file
@@ -0,0 +1,119 @@
|
||||
import type { z } from 'zod';
|
||||
|
||||
import {
|
||||
DocumentDataSchema,
|
||||
TeamSchema,
|
||||
TemplateDirectLinkSchema,
|
||||
TemplateMetaSchema,
|
||||
TemplateSchema,
|
||||
UserSchema,
|
||||
} from '@documenso/prisma/generated/zod';
|
||||
|
||||
import { ZFieldSchema } from './field';
|
||||
import { ZRecipientLiteSchema } from './recipient';
|
||||
|
||||
/**
|
||||
* The full template response schema.
|
||||
*
|
||||
* Mainly used for returning a single template from the API.
|
||||
*/
|
||||
export const ZTemplateSchema = TemplateSchema.pick({
|
||||
type: true,
|
||||
visibility: true,
|
||||
id: true,
|
||||
externalId: true,
|
||||
title: true,
|
||||
userId: true,
|
||||
teamId: true,
|
||||
authOptions: true,
|
||||
templateDocumentDataId: true,
|
||||
createdAt: true,
|
||||
updatedAt: true,
|
||||
publicTitle: true,
|
||||
publicDescription: true,
|
||||
}).extend({
|
||||
// Todo: Maybe we want to alter this a bit since this returns a lot of data.
|
||||
templateDocumentData: DocumentDataSchema.pick({
|
||||
type: true,
|
||||
id: true,
|
||||
data: true,
|
||||
initialData: true,
|
||||
}),
|
||||
templateMeta: TemplateMetaSchema.pick({
|
||||
id: true,
|
||||
subject: true,
|
||||
message: true,
|
||||
timezone: true,
|
||||
dateFormat: true,
|
||||
signingOrder: true,
|
||||
typedSignatureEnabled: true,
|
||||
distributionMethod: true,
|
||||
templateId: true,
|
||||
redirectUrl: true,
|
||||
language: true,
|
||||
emailSettings: true,
|
||||
}).nullable(),
|
||||
directLink: TemplateDirectLinkSchema.nullable(),
|
||||
user: UserSchema.pick({
|
||||
id: true,
|
||||
name: true,
|
||||
email: true,
|
||||
}),
|
||||
recipients: ZRecipientLiteSchema.array(),
|
||||
fields: ZFieldSchema.array(),
|
||||
});
|
||||
|
||||
export type TTemplate = z.infer<typeof ZTemplateSchema>;
|
||||
|
||||
/**
|
||||
* A lite version of the template response schema without relations.
|
||||
*/
|
||||
export const ZTemplateLiteSchema = TemplateSchema.pick({
|
||||
type: true,
|
||||
visibility: true,
|
||||
id: true,
|
||||
externalId: true,
|
||||
title: true,
|
||||
userId: true,
|
||||
teamId: true,
|
||||
authOptions: true,
|
||||
templateDocumentDataId: true,
|
||||
createdAt: true,
|
||||
updatedAt: true,
|
||||
publicTitle: true,
|
||||
publicDescription: true,
|
||||
});
|
||||
|
||||
/**
|
||||
* A version of the template response schema when returning multiple template at once from a single API endpoint.
|
||||
*/
|
||||
export const ZTemplateManySchema = TemplateSchema.pick({
|
||||
type: true,
|
||||
visibility: true,
|
||||
id: true,
|
||||
externalId: true,
|
||||
title: true,
|
||||
userId: true,
|
||||
teamId: true,
|
||||
authOptions: true,
|
||||
templateDocumentDataId: true,
|
||||
createdAt: true,
|
||||
updatedAt: true,
|
||||
publicTitle: true,
|
||||
publicDescription: true,
|
||||
}).extend({
|
||||
team: TeamSchema.pick({
|
||||
id: true,
|
||||
url: true,
|
||||
}).nullable(),
|
||||
fields: ZFieldSchema.array(),
|
||||
recipients: ZRecipientLiteSchema.array(),
|
||||
templateMeta: TemplateMetaSchema.pick({
|
||||
signingOrder: true,
|
||||
distributionMethod: true,
|
||||
}).nullable(),
|
||||
directLink: TemplateDirectLinkSchema.pick({
|
||||
token: true,
|
||||
enabled: true,
|
||||
}).nullable(),
|
||||
});
|
||||
20
packages/lib/utils/document-visibility.ts
Normal file
20
packages/lib/utils/document-visibility.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import { DocumentVisibility, TeamMemberRole } from '@documenso/prisma/client';
|
||||
|
||||
export const determineDocumentVisibility = (
|
||||
globalVisibility: DocumentVisibility | null | undefined,
|
||||
userRole: TeamMemberRole,
|
||||
): DocumentVisibility => {
|
||||
if (globalVisibility) {
|
||||
return globalVisibility;
|
||||
}
|
||||
|
||||
if (userRole === TeamMemberRole.ADMIN) {
|
||||
return DocumentVisibility.ADMIN;
|
||||
}
|
||||
|
||||
if (userRole === TeamMemberRole.MANAGER) {
|
||||
return DocumentVisibility.MANAGER_AND_ABOVE;
|
||||
}
|
||||
|
||||
return DocumentVisibility.EVERYONE;
|
||||
};
|
||||
Reference in New Issue
Block a user