mirror of
https://github.com/documenso/documenso.git
synced 2026-07-27 02:15:05 +10:00
chore: merge main, resolve biome formatting conflicts
Merge origin/main into feat/document-file-conversion. Conflicts were format-only (Tailwind class ordering, single-line vs multi-line) plus two semantic merges: - files.helpers.ts: combine main's pending-PDF download path with the branch's original-source-file (DOCX/PNG/JPEG) download path - download-pdf.ts: combine main's versionToFilenameSuffix helper with the branch's server-provided Content-Disposition filename support
This commit is contained in:
@@ -1,17 +1,13 @@
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import type { User } from '@prisma/client';
|
||||
import { UserSecurityAuditLogType } from '@prisma/client';
|
||||
|
||||
import { prisma } from '@documenso/prisma';
|
||||
|
||||
import { AppError, AppErrorCode } from '../../errors/app-error';
|
||||
import type { RequestMetadata } from '../../universal/extract-request-metadata';
|
||||
import { validateTwoFactorAuthentication } from './validate-2fa';
|
||||
|
||||
type DisableTwoFactorAuthenticationOptions = {
|
||||
user: Pick<
|
||||
User,
|
||||
'id' | 'email' | 'twoFactorEnabled' | 'twoFactorSecret' | 'twoFactorBackupCodes'
|
||||
>;
|
||||
user: Pick<User, 'id' | 'email' | 'twoFactorEnabled' | 'twoFactorSecret' | 'twoFactorBackupCodes'>;
|
||||
totpCode?: string;
|
||||
backupCode?: string;
|
||||
requestMetadata?: RequestMetadata;
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
import { createElement } from 'react';
|
||||
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import { EnvelopeType } from '@prisma/client';
|
||||
|
||||
import { mailer } from '@documenso/email/mailer';
|
||||
import { AccessAuth2FAEmailTemplate } from '@documenso/email/templates/access-auth-2fa';
|
||||
import { isRecipientEmailValidForSending } from '@documenso/lib/utils/recipients';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import { EnvelopeType } from '@prisma/client';
|
||||
import { createElement } from 'react';
|
||||
|
||||
import { getI18nInstance } from '../../../client-only/providers/i18n-server';
|
||||
import { NEXT_PUBLIC_WEBAPP_URL } from '../../../constants/app';
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { type User, UserSecurityAuditLogType } from '@prisma/client';
|
||||
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import { type User, UserSecurityAuditLogType } from '@prisma/client';
|
||||
|
||||
import { AppError } from '../../errors/app-error';
|
||||
import type { RequestMetadata } from '../../universal/extract-request-metadata';
|
||||
|
||||
@@ -25,9 +25,7 @@ export const getBackupCodes = ({ user }: GetBackupCodesOptions) => {
|
||||
throw new Error('User has no backup codes');
|
||||
}
|
||||
|
||||
const secret = Buffer.from(symmetricDecrypt({ key, data: user.twoFactorBackupCodes })).toString(
|
||||
'utf-8',
|
||||
);
|
||||
const secret = Buffer.from(symmetricDecrypt({ key, data: user.twoFactorBackupCodes })).toString('utf-8');
|
||||
|
||||
const data = JSON.parse(secret);
|
||||
|
||||
|
||||
@@ -6,8 +6,6 @@ type IsTwoFactorAuthenticationEnabledOptions = {
|
||||
user: User;
|
||||
};
|
||||
|
||||
export const isTwoFactorAuthenticationEnabled = ({
|
||||
user,
|
||||
}: IsTwoFactorAuthenticationEnabledOptions) => {
|
||||
export const isTwoFactorAuthenticationEnabled = ({ user }: IsTwoFactorAuthenticationEnabledOptions) => {
|
||||
return user.twoFactorEnabled && typeof DOCUMENSO_ENCRYPTION_KEY === 'string';
|
||||
};
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import { type User } from '@prisma/client';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import type { User } from '@prisma/client';
|
||||
import { base32 } from '@scure/base';
|
||||
import crypto from 'crypto';
|
||||
import { createTOTPKeyURI } from 'oslo/otp';
|
||||
|
||||
import { prisma } from '@documenso/prisma';
|
||||
|
||||
import { DOCUMENSO_ENCRYPTION_KEY } from '../../constants/crypto';
|
||||
import { symmetricEncrypt } from '../../universal/crypto';
|
||||
|
||||
@@ -14,9 +13,7 @@ type SetupTwoFactorAuthenticationOptions = {
|
||||
|
||||
const ISSUER = 'Documenso';
|
||||
|
||||
export const setupTwoFactorAuthentication = async ({
|
||||
user,
|
||||
}: SetupTwoFactorAuthenticationOptions) => {
|
||||
export const setupTwoFactorAuthentication = async ({ user }: SetupTwoFactorAuthenticationOptions) => {
|
||||
const key = DOCUMENSO_ENCRYPTION_KEY;
|
||||
|
||||
if (!key) {
|
||||
|
||||
@@ -7,10 +7,7 @@ import { verifyBackupCode } from './verify-backup-code';
|
||||
type ValidateTwoFactorAuthenticationOptions = {
|
||||
totpCode?: string;
|
||||
backupCode?: string;
|
||||
user: Pick<
|
||||
User,
|
||||
'id' | 'email' | 'twoFactorEnabled' | 'twoFactorSecret' | 'twoFactorBackupCodes'
|
||||
>;
|
||||
user: Pick<User, 'id' | 'email' | 'twoFactorEnabled' | 'twoFactorSecret' | 'twoFactorBackupCodes'>;
|
||||
};
|
||||
|
||||
export const validateTwoFactorAuthentication = async ({
|
||||
|
||||
@@ -30,9 +30,7 @@ export const verifyTwoFactorAuthenticationToken = async ({
|
||||
throw new Error('user missing 2fa secret');
|
||||
}
|
||||
|
||||
const secret = Buffer.from(symmetricDecrypt({ key, data: user.twoFactorSecret })).toString(
|
||||
'utf-8',
|
||||
);
|
||||
const secret = Buffer.from(symmetricDecrypt({ key, data: user.twoFactorSecret })).toString('utf-8');
|
||||
|
||||
const decodedSecret = base32.decode(secret);
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { compare } from '@node-rs/bcrypt';
|
||||
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import { compare } from '@node-rs/bcrypt';
|
||||
|
||||
type VerifyPasswordOptions = {
|
||||
userId: number;
|
||||
|
||||
@@ -5,10 +5,7 @@ import { getBackupCodes } from './get-backup-code';
|
||||
import { validateTwoFactorAuthentication } from './validate-2fa';
|
||||
|
||||
type ViewBackupCodesOptions = {
|
||||
user: Pick<
|
||||
User,
|
||||
'id' | 'email' | 'twoFactorEnabled' | 'twoFactorSecret' | 'twoFactorBackupCodes'
|
||||
>;
|
||||
user: Pick<User, 'id' | 'email' | 'twoFactorEnabled' | 'twoFactorSecret' | 'twoFactorBackupCodes'>;
|
||||
token: string;
|
||||
};
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { EnvelopeType, type Prisma } from '@prisma/client';
|
||||
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import { EnvelopeType, type Prisma } from '@prisma/client';
|
||||
|
||||
import type { FindResultResponse } from '../../types/search-params';
|
||||
|
||||
@@ -10,11 +9,7 @@ export interface AdminFindDocumentsOptions {
|
||||
perPage?: number;
|
||||
}
|
||||
|
||||
export const adminFindDocuments = async ({
|
||||
query,
|
||||
page = 1,
|
||||
perPage = 10,
|
||||
}: AdminFindDocumentsOptions) => {
|
||||
export const adminFindDocuments = async ({ query, page = 1, perPage = 10 }: AdminFindDocumentsOptions) => {
|
||||
let termFilters: Prisma.EnvelopeWhereInput | undefined = !query
|
||||
? undefined
|
||||
: {
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { DocumentStatus, EnvelopeType, RecipientRole, SigningStatus } from '@prisma/client';
|
||||
|
||||
import { kyselyPrisma, sql } from '@documenso/prisma';
|
||||
import { DocumentStatus, EnvelopeType, RecipientRole, SigningStatus } from '@prisma/client';
|
||||
|
||||
import type { FindResultResponse } from '../../types/search-params';
|
||||
|
||||
@@ -35,9 +34,7 @@ export const adminFindUnsealedDocuments = async ({
|
||||
.where('Envelope.type', '=', sql.lit(EnvelopeType.DOCUMENT))
|
||||
.where('Envelope.deletedAt', 'is', null)
|
||||
// Must have at least one recipient.
|
||||
.where((eb) =>
|
||||
eb.exists(eb.selectFrom('Recipient').whereRef('Recipient.envelopeId', '=', 'Envelope.id')),
|
||||
)
|
||||
.where((eb) => eb.exists(eb.selectFrom('Recipient').whereRef('Recipient.envelopeId', '=', 'Envelope.id')))
|
||||
// Document is ready to seal: all recipients are SIGNED/CC, or any recipient REJECTED.
|
||||
.where((eb) =>
|
||||
eb.or([
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
import { createElement } from 'react';
|
||||
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import { DocumentStatus, SendStatus } from '@prisma/client';
|
||||
|
||||
import { mailer } from '@documenso/email/mailer';
|
||||
import DocumentCancelTemplate from '@documenso/email/templates/document-cancel';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import { DocumentStatus, SendStatus } from '@prisma/client';
|
||||
import { createElement } from 'react';
|
||||
|
||||
import { getI18nInstance } from '../../client-only/providers/i18n-server';
|
||||
import { NEXT_PUBLIC_WEBAPP_URL } from '../../constants/app';
|
||||
@@ -23,10 +21,7 @@ export type AdminSuperDeleteDocumentOptions = {
|
||||
requestMetadata?: RequestMetadata;
|
||||
};
|
||||
|
||||
export const adminSuperDeleteDocument = async ({
|
||||
envelopeId,
|
||||
requestMetadata,
|
||||
}: AdminSuperDeleteDocumentOptions) => {
|
||||
export const adminSuperDeleteDocument = async ({ envelopeId, requestMetadata }: AdminSuperDeleteDocumentOptions) => {
|
||||
const envelope = await prisma.envelope.findUnique({
|
||||
where: {
|
||||
id: envelopeId,
|
||||
@@ -61,20 +56,12 @@ export const adminSuperDeleteDocument = async ({
|
||||
|
||||
const { status, user } = envelope;
|
||||
|
||||
const isDocumentDeletedEmailEnabled = extractDerivedDocumentEmailSettings(
|
||||
envelope.documentMeta,
|
||||
).documentDeleted;
|
||||
const isDocumentDeletedEmailEnabled = extractDerivedDocumentEmailSettings(envelope.documentMeta).documentDeleted;
|
||||
|
||||
const recipientsToNotify = envelope.recipients.filter((recipient) =>
|
||||
isRecipientEmailValidForSending(recipient),
|
||||
);
|
||||
const recipientsToNotify = envelope.recipients.filter((recipient) => isRecipientEmailValidForSending(recipient));
|
||||
|
||||
// if the document is pending, send cancellation emails to all recipients
|
||||
if (
|
||||
status === DocumentStatus.PENDING &&
|
||||
recipientsToNotify.length > 0 &&
|
||||
isDocumentDeletedEmailEnabled
|
||||
) {
|
||||
if (status === DocumentStatus.PENDING && recipientsToNotify.length > 0 && isDocumentDeletedEmailEnabled) {
|
||||
await Promise.all(
|
||||
recipientsToNotify.map(async (recipient) => {
|
||||
if (recipient.sendStatus !== SendStatus.SENT) {
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { EnvelopeType } from '@prisma/client';
|
||||
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import { ExtendedDocumentStatus } from '@documenso/prisma/types/extended-document-status';
|
||||
import { EnvelopeType } from '@prisma/client';
|
||||
|
||||
export const getDocumentStats = async () => {
|
||||
const counts = await prisma.envelope.groupBy({
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import type { EnvelopeType } from '@prisma/client';
|
||||
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import type { EnvelopeType } from '@prisma/client';
|
||||
|
||||
import { AppError, AppErrorCode } from '../../errors/app-error';
|
||||
import type { EnvelopeIdOptions } from '../../utils/envelope';
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import type { DocumentStatus } from '@prisma/client';
|
||||
import { EnvelopeType } from '@prisma/client';
|
||||
|
||||
import type { DateRange } from '@documenso/lib/types/search-params';
|
||||
import { kyselyPrisma, sql } from '@documenso/prisma';
|
||||
import type { DocumentStatus } from '@prisma/client';
|
||||
import { EnvelopeType } from '@prisma/client';
|
||||
|
||||
export type OrganisationSummary = {
|
||||
totalTeams: number;
|
||||
@@ -260,10 +259,7 @@ async function getDocumentInsights(
|
||||
|
||||
countQuery = countQuery.select(({ fn }) => [fn.countAll().as('count')]);
|
||||
|
||||
const [documents, countResult] = await Promise.all([
|
||||
documentsQuery.execute(),
|
||||
countQuery.execute(),
|
||||
]);
|
||||
const [documents, countResult] = await Promise.all([documentsQuery.execute(), countQuery.execute()]);
|
||||
|
||||
const count = Number((countResult[0] as { count: number })?.count || 0);
|
||||
|
||||
@@ -302,9 +298,7 @@ async function getOrganisationSummary(
|
||||
.where('e.type', '=', sql.lit(EnvelopeType.DOCUMENT))
|
||||
.select([
|
||||
sql<number>`count(e.id)`.as('totalDocuments'),
|
||||
sql<number>`count(case when e.status in ('DRAFT', 'PENDING') then 1 end)`.as(
|
||||
'activeDocuments',
|
||||
),
|
||||
sql<number>`count(case when e.status in ('DRAFT', 'PENDING') then 1 end)`.as('activeDocuments'),
|
||||
sql<number>`count(case when e.status = 'COMPLETED' then 1 end)`.as('completedDocuments'),
|
||||
sql<number>`count(case when e.status = 'COMPLETED' then 1 end)`.as('volumeAllTime'),
|
||||
(createdAtFrom
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { ReadStatus, SendStatus, SigningStatus } from '@prisma/client';
|
||||
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import { ReadStatus, SendStatus, SigningStatus } from '@prisma/client';
|
||||
|
||||
export const getRecipientsStats = async () => {
|
||||
const results = await prisma.recipient.groupBy({
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { DocumentStatus, EnvelopeType } from '@prisma/client';
|
||||
|
||||
import type { DateRange } from '@documenso/lib/types/search-params';
|
||||
import { kyselyPrisma, sql } from '@documenso/prisma';
|
||||
import { DocumentStatus, EnvelopeType } from '@prisma/client';
|
||||
|
||||
export type OrganisationInsights = {
|
||||
id: number;
|
||||
@@ -37,10 +36,7 @@ export async function getSigningVolume({
|
||||
eb.or([
|
||||
eb('o.name', 'ilike', `%${search}%`),
|
||||
eb.exists(
|
||||
eb
|
||||
.selectFrom('Team as t')
|
||||
.whereRef('t.organisationId', '=', 'o.id')
|
||||
.where('t.name', 'ilike', `%${search}%`),
|
||||
eb.selectFrom('Team as t').whereRef('t.organisationId', '=', 'o.id').where('t.name', 'ilike', `%${search}%`),
|
||||
),
|
||||
]),
|
||||
)
|
||||
@@ -82,10 +78,7 @@ export async function getSigningVolume({
|
||||
eb.or([
|
||||
eb('o.name', 'ilike', `%${search}%`),
|
||||
eb.exists(
|
||||
eb
|
||||
.selectFrom('Team as t')
|
||||
.whereRef('t.organisationId', '=', 'o.id')
|
||||
.where('t.name', 'ilike', `%${search}%`),
|
||||
eb.selectFrom('Team as t').whereRef('t.organisationId', '=', 'o.id').where('t.name', 'ilike', `%${search}%`),
|
||||
),
|
||||
]),
|
||||
)
|
||||
@@ -153,10 +146,7 @@ export async function getOrganisationInsights({
|
||||
eb.or([
|
||||
eb('o.name', 'ilike', `%${search}%`),
|
||||
eb.exists(
|
||||
eb
|
||||
.selectFrom('Team as t')
|
||||
.whereRef('t.organisationId', '=', 'o.id')
|
||||
.where('t.name', 'ilike', `%${search}%`),
|
||||
eb.selectFrom('Team as t').whereRef('t.organisationId', '=', 'o.id').where('t.name', 'ilike', `%${search}%`),
|
||||
),
|
||||
]),
|
||||
)
|
||||
@@ -165,9 +155,7 @@ export async function getOrganisationInsights({
|
||||
'o.createdAt as createdAt',
|
||||
'o.customerId as customerId',
|
||||
sql<string>`COALESCE(o.name, 'Unknown')`.as('name'),
|
||||
sql<string>`CASE WHEN s.status IS NOT NULL THEN s.status ELSE NULL END`.as(
|
||||
'subscriptionStatus',
|
||||
),
|
||||
sql<string>`CASE WHEN s.status IS NOT NULL THEN s.status ELSE NULL END`.as('subscriptionStatus'),
|
||||
eb
|
||||
.selectFrom('Team as t')
|
||||
.whereRef('t.organisationId', '=', 'o.id')
|
||||
@@ -212,10 +200,7 @@ export async function getOrganisationInsights({
|
||||
eb.or([
|
||||
eb('o.name', 'ilike', `%${search}%`),
|
||||
eb.exists(
|
||||
eb
|
||||
.selectFrom('Team as t')
|
||||
.whereRef('t.organisationId', '=', 'o.id')
|
||||
.where('t.name', 'ilike', `%${search}%`),
|
||||
eb.selectFrom('Team as t').whereRef('t.organisationId', '=', 'o.id').where('t.name', 'ilike', `%${search}%`),
|
||||
),
|
||||
]),
|
||||
)
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { DateTime } from 'luxon';
|
||||
|
||||
import { kyselyPrisma, prisma, sql } from '@documenso/prisma';
|
||||
import { SubscriptionStatus, UserSecurityAuditLogType } from '@documenso/prisma/client';
|
||||
import { DateTime } from 'luxon';
|
||||
|
||||
export const getUsersCount = async () => {
|
||||
return await prisma.user.count();
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { type RecipientRole, SigningStatus } from '@prisma/client';
|
||||
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import { type RecipientRole, SigningStatus } from '@prisma/client';
|
||||
|
||||
export type UpdateRecipientOptions = {
|
||||
id: number;
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import type { Role } from '@prisma/client';
|
||||
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import type { Role } from '@prisma/client';
|
||||
|
||||
export type UpdateUserOptions = {
|
||||
id: number;
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import { createCanvas, loadImage } from '@napi-rs/canvas';
|
||||
import { DocumentStatus, type Field, RecipientRole } from '@prisma/client';
|
||||
import { generateObject } from 'ai';
|
||||
import pMap from 'p-map';
|
||||
|
||||
import { prisma } from '@documenso/prisma';
|
||||
|
||||
import { AppError, AppErrorCode } from '../../../../errors/app-error';
|
||||
import { getFileServerSide } from '../../../../universal/upload/get-file.server';
|
||||
import { resizeImageToGeminiImage } from '../../../../utils/images/resize-image-to-gemini-image';
|
||||
@@ -12,18 +11,10 @@ import { getEnvelopeById } from '../../../envelope/get-envelope-by-id';
|
||||
import { createEnvelopeRecipients } from '../../../recipient/create-envelope-recipients';
|
||||
import { vertex } from '../../google';
|
||||
import { pdfToImages } from '../../pdf-to-images';
|
||||
import {
|
||||
buildRecipientContextMessage,
|
||||
normalizeDetectedField,
|
||||
resolveRecipientFromKey,
|
||||
} from './helpers';
|
||||
import { buildRecipientContextMessage, normalizeDetectedField, resolveRecipientFromKey } from './helpers';
|
||||
import { SYSTEM_PROMPT } from './prompt';
|
||||
import { ZSubmitDetectedFieldsInputSchema } from './schema';
|
||||
import type {
|
||||
NormalizedFieldWithContext,
|
||||
NormalizedFieldWithPage,
|
||||
RecipientContext,
|
||||
} from './types';
|
||||
import type { NormalizedFieldWithContext, NormalizedFieldWithPage, RecipientContext } from './types';
|
||||
|
||||
export type DetectFieldsFromEnvelopeOptions = {
|
||||
context?: string;
|
||||
@@ -245,12 +236,7 @@ type DetectFieldsFromPageOptions = {
|
||||
context?: string;
|
||||
};
|
||||
|
||||
const detectFieldsFromPage = async ({
|
||||
image,
|
||||
pageNumber,
|
||||
recipients,
|
||||
context,
|
||||
}: DetectFieldsFromPageOptions) => {
|
||||
const detectFieldsFromPage = async ({ image, pageNumber, recipients, context }: DetectFieldsFromPageOptions) => {
|
||||
// Resize to 1000x1000 for consistent coordinate mapping
|
||||
const resizedImage = await resizeImageToGeminiImage({ image });
|
||||
|
||||
|
||||
@@ -29,9 +29,7 @@ const ZBox2DSchema = z.array(z.number().min(0).max(1000)).length(4);
|
||||
* Schema for a detected field.
|
||||
*/
|
||||
export const ZDetectedFieldSchema = z.object({
|
||||
type: ZDetectableFieldType.describe(
|
||||
`The field type based on nearby labels and visual appearance`,
|
||||
),
|
||||
type: ZDetectableFieldType.describe(`The field type based on nearby labels and visual appearance`),
|
||||
recipientKey: z
|
||||
.string()
|
||||
.describe(
|
||||
|
||||
@@ -73,10 +73,7 @@ export type DetectRecipientsFromPdfOptions = {
|
||||
onProgress?: (progress: DetectRecipientsProgress) => void;
|
||||
};
|
||||
|
||||
export const detectRecipientsFromPdf = async ({
|
||||
pdfBytes,
|
||||
onProgress,
|
||||
}: DetectRecipientsFromPdfOptions) => {
|
||||
export const detectRecipientsFromPdf = async ({ pdfBytes, onProgress }: DetectRecipientsFromPdfOptions) => {
|
||||
const pageImages = await pdfToImages(pdfBytes);
|
||||
|
||||
if (pageImages.length === 0) {
|
||||
@@ -105,10 +102,7 @@ const formatDetectedRecipients = (recipients: TDetectedRecipientSchema[]) => {
|
||||
return `\n\nRecipients detected so far:\n${formatted}`;
|
||||
};
|
||||
|
||||
const isDuplicateRecipient = (
|
||||
recipient: TDetectedRecipientSchema,
|
||||
existing: TDetectedRecipientSchema,
|
||||
) => {
|
||||
const isDuplicateRecipient = (recipient: TDetectedRecipientSchema, existing: TDetectedRecipientSchema) => {
|
||||
if (recipient.email && existing.email) {
|
||||
return recipient.email.toLowerCase() === existing.email.toLowerCase();
|
||||
}
|
||||
@@ -120,10 +114,7 @@ const isDuplicateRecipient = (
|
||||
return false;
|
||||
};
|
||||
|
||||
const mergeRecipients = (
|
||||
existingRecipients: TDetectedRecipientSchema[],
|
||||
newRecipients: TDetectedRecipientSchema[],
|
||||
) => {
|
||||
const mergeRecipients = (existingRecipients: TDetectedRecipientSchema[], newRecipients: TDetectedRecipientSchema[]) => {
|
||||
const merged = [...existingRecipients];
|
||||
|
||||
for (const recipient of newRecipients) {
|
||||
@@ -169,10 +160,7 @@ Please analyze these pages and submit any recipients you find using the tool. I
|
||||
Please analyze these pages and submit any NEW recipients you find (not already listed above) using the tool.`;
|
||||
};
|
||||
|
||||
const detectRecipientsFromImages = async ({
|
||||
images,
|
||||
onProgress,
|
||||
}: DetectRecipientsFromImagesOptions) => {
|
||||
const detectRecipientsFromImages = async ({ images, onProgress }: DetectRecipientsFromImagesOptions) => {
|
||||
const imageChunks = chunk(images, MAX_PAGES_PER_CHUNK);
|
||||
|
||||
const totalChunks = imageChunks.length;
|
||||
|
||||
@@ -16,9 +16,7 @@ export const ZDetectedRecipientSchema = z.object({
|
||||
export type TDetectedRecipientSchema = z.infer<typeof ZDetectedRecipientSchema>;
|
||||
|
||||
export const ZDetectedRecipientsSchema = z.object({
|
||||
recipients: z
|
||||
.array(ZDetectedRecipientSchema)
|
||||
.describe('The list of detected recipients from the document'),
|
||||
recipients: z.array(ZDetectedRecipientSchema).describe('The list of detected recipients from the document'),
|
||||
});
|
||||
|
||||
export type TDetectedRecipientsSchema = z.infer<typeof ZDetectedRecipientsSchema>;
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import type { Passkey } from '@prisma/client';
|
||||
import { generateAuthenticationOptions } from '@simplewebauthn/server';
|
||||
import type { AuthenticatorTransportFuture } from '@simplewebauthn/server';
|
||||
import { generateAuthenticationOptions } from '@simplewebauthn/server';
|
||||
import { isoBase64URL } from '@simplewebauthn/server/helpers';
|
||||
import { DateTime } from 'luxon';
|
||||
|
||||
import { prisma } from '@documenso/prisma';
|
||||
|
||||
import { AppError, AppErrorCode } from '../../errors/app-error';
|
||||
import { getAuthenticatorOptions } from '../../utils/authenticator';
|
||||
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import { generateRegistrationOptions } from '@simplewebauthn/server';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import type { AuthenticatorTransportFuture } from '@simplewebauthn/server';
|
||||
import { generateRegistrationOptions } from '@simplewebauthn/server';
|
||||
import { isoBase64URL } from '@simplewebauthn/server/helpers';
|
||||
import { DateTime } from 'luxon';
|
||||
|
||||
import { prisma } from '@documenso/prisma';
|
||||
|
||||
import { PASSKEY_TIMEOUT } from '../../constants/auth';
|
||||
import { getAuthenticatorOptions } from '../../utils/authenticator';
|
||||
|
||||
@@ -12,9 +11,7 @@ type CreatePasskeyRegistrationOptions = {
|
||||
userId: number;
|
||||
};
|
||||
|
||||
export const createPasskeyRegistrationOptions = async ({
|
||||
userId,
|
||||
}: CreatePasskeyRegistrationOptions) => {
|
||||
export const createPasskeyRegistrationOptions = async ({ userId }: CreatePasskeyRegistrationOptions) => {
|
||||
const user = await prisma.user.findFirstOrThrow({
|
||||
where: {
|
||||
id: userId,
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import { generateAuthenticationOptions } from '@simplewebauthn/server';
|
||||
import { DateTime } from 'luxon';
|
||||
|
||||
import { prisma } from '@documenso/prisma';
|
||||
|
||||
import { getAuthenticatorOptions } from '../../utils/authenticator';
|
||||
|
||||
type CreatePasskeySigninOptions = {
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import { UserSecurityAuditLogType } from '@prisma/client';
|
||||
import { verifyRegistrationResponse } from '@simplewebauthn/server';
|
||||
import type { RegistrationResponseJSON } from '@simplewebauthn/server';
|
||||
import { isoBase64URL } from '@simplewebauthn/server/helpers';
|
||||
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import { UserSecurityAuditLogType } from '@prisma/client';
|
||||
import type { RegistrationResponseJSON } from '@simplewebauthn/server';
|
||||
import { verifyRegistrationResponse } from '@simplewebauthn/server';
|
||||
import { isoBase64URL } from '@simplewebauthn/server/helpers';
|
||||
|
||||
import { MAXIMUM_PASSKEYS } from '../../constants/auth';
|
||||
import { AppError, AppErrorCode } from '../../errors/app-error';
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { UserSecurityAuditLogType } from '@prisma/client';
|
||||
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import { UserSecurityAuditLogType } from '@prisma/client';
|
||||
|
||||
import type { RequestMetadata } from '../../universal/extract-request-metadata';
|
||||
|
||||
@@ -10,11 +9,7 @@ export interface DeletePasskeyOptions {
|
||||
requestMetadata?: RequestMetadata;
|
||||
}
|
||||
|
||||
export const deletePasskey = async ({
|
||||
userId,
|
||||
passkeyId,
|
||||
requestMetadata,
|
||||
}: DeletePasskeyOptions) => {
|
||||
export const deletePasskey = async ({ userId, passkeyId, requestMetadata }: DeletePasskeyOptions) => {
|
||||
await prisma.passkey.findFirstOrThrow({
|
||||
where: {
|
||||
id: passkeyId,
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import type { Passkey } from '@prisma/client';
|
||||
import { Prisma } from '@prisma/client';
|
||||
|
||||
import { prisma } from '@documenso/prisma';
|
||||
|
||||
import type { FindResultResponse } from '../../types/search-params';
|
||||
|
||||
export interface FindPasskeysOptions {
|
||||
@@ -17,13 +16,7 @@ export interface FindPasskeysOptions {
|
||||
};
|
||||
}
|
||||
|
||||
export const findPasskeys = async ({
|
||||
userId,
|
||||
query = '',
|
||||
page = 1,
|
||||
perPage = 10,
|
||||
orderBy,
|
||||
}: FindPasskeysOptions) => {
|
||||
export const findPasskeys = async ({ userId, query = '', page = 1, perPage = 10, orderBy }: FindPasskeysOptions) => {
|
||||
const orderByColumn = orderBy?.column ?? 'lastUsedAt';
|
||||
const orderByDirection = orderBy?.direction ?? 'desc';
|
||||
const orderByNulls: Prisma.NullsOrder | undefined = orderBy?.nulls ?? 'last';
|
||||
|
||||
@@ -1,17 +1,12 @@
|
||||
import { createElement } from 'react';
|
||||
|
||||
import { msg } from '@lingui/core/macro';
|
||||
|
||||
import { mailer } from '@documenso/email/mailer';
|
||||
import { ConfirmEmailTemplate } from '@documenso/email/templates/confirm-email';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import { createElement } from 'react';
|
||||
|
||||
import { getI18nInstance } from '../../client-only/providers/i18n-server';
|
||||
import { NEXT_PUBLIC_WEBAPP_URL } from '../../constants/app';
|
||||
import {
|
||||
DOCUMENSO_INTERNAL_EMAIL,
|
||||
USER_SIGNUP_VERIFICATION_TOKEN_IDENTIFIER,
|
||||
} from '../../constants/email';
|
||||
import { DOCUMENSO_INTERNAL_EMAIL, USER_SIGNUP_VERIFICATION_TOKEN_IDENTIFIER } from '../../constants/email';
|
||||
import { renderEmailWithI18N } from '../../utils/render-email-with-i18n';
|
||||
|
||||
export interface SendConfirmationEmailProps {
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
import { createElement } from 'react';
|
||||
|
||||
import { msg } from '@lingui/core/macro';
|
||||
|
||||
import { mailer } from '@documenso/email/mailer';
|
||||
import { ForgotPasswordTemplate } from '@documenso/email/templates/forgot-password';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import { createElement } from 'react';
|
||||
|
||||
import { getI18nInstance } from '../../client-only/providers/i18n-server';
|
||||
import { NEXT_PUBLIC_WEBAPP_URL } from '../../constants/app';
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import { createElement } from 'react';
|
||||
|
||||
import { mailer } from '@documenso/email/mailer';
|
||||
import { ResetPasswordTemplate } from '@documenso/email/templates/reset-password';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import { createElement } from 'react';
|
||||
|
||||
import { NEXT_PUBLIC_WEBAPP_URL } from '../../constants/app';
|
||||
import { env } from '../../utils/env';
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { UserSecurityAuditLogType } from '@prisma/client';
|
||||
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import { UserSecurityAuditLogType } from '@prisma/client';
|
||||
|
||||
import type { RequestMetadata } from '../../universal/extract-request-metadata';
|
||||
|
||||
@@ -11,12 +10,7 @@ export interface UpdateAuthenticatorsOptions {
|
||||
requestMetadata?: RequestMetadata;
|
||||
}
|
||||
|
||||
export const updatePasskey = async ({
|
||||
userId,
|
||||
passkeyId,
|
||||
name,
|
||||
requestMetadata,
|
||||
}: UpdateAuthenticatorsOptions) => {
|
||||
export const updatePasskey = async ({ userId, passkeyId, name, requestMetadata }: UpdateAuthenticatorsOptions) => {
|
||||
const passkey = await prisma.passkey.findFirstOrThrow({
|
||||
where: {
|
||||
id: passkeyId,
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
import { IS_BILLING_ENABLED } from '../../constants/app';
|
||||
import type { TCssVarsSchema } from '../../types/css-vars';
|
||||
import { ZCssVarsSchema } from '../../types/css-vars';
|
||||
import { getOrganisationClaimByTeamId } from '../organisation/get-organisation-claims';
|
||||
import { getTeamSettings } from '../team/get-team-settings';
|
||||
|
||||
export type RecipientBrandingPayload = {
|
||||
allowCustomBranding: boolean;
|
||||
hidePoweredBy: boolean;
|
||||
colors: TCssVarsSchema | null;
|
||||
css: string | null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Resolve the branding payload for a recipient-facing route, given the team
|
||||
* the envelope/document belongs to. Reads inherited team-or-org branding settings,
|
||||
* checks the org's claim flags, and returns a payload safe to send to the client.
|
||||
*
|
||||
* Returns a minimal disabled payload if the team is not on a plan that allows
|
||||
* custom branding.
|
||||
*/
|
||||
export const loadRecipientBrandingByTeamId = async ({
|
||||
teamId,
|
||||
}: {
|
||||
teamId: number;
|
||||
}): Promise<RecipientBrandingPayload> => {
|
||||
const billingEnabled = IS_BILLING_ENABLED();
|
||||
|
||||
const [settings, claim] = await Promise.all([
|
||||
getTeamSettings({ teamId }),
|
||||
billingEnabled ? getOrganisationClaimByTeamId({ teamId }).catch(() => null) : Promise.resolve(null),
|
||||
]);
|
||||
|
||||
const allowCustomBranding = !billingEnabled || claim?.flags?.embedSigningWhiteLabel === true;
|
||||
const hidePoweredBy = !billingEnabled || claim?.flags?.hidePoweredBy === true;
|
||||
|
||||
if (!allowCustomBranding) {
|
||||
return {
|
||||
allowCustomBranding: false,
|
||||
hidePoweredBy,
|
||||
colors: null,
|
||||
css: null,
|
||||
};
|
||||
}
|
||||
|
||||
// brandingColors is stored as JSON; parse defensively. Drop unknown keys via Zod.
|
||||
const parsedColors = settings.brandingColors ? ZCssVarsSchema.safeParse(settings.brandingColors) : null;
|
||||
|
||||
return {
|
||||
allowCustomBranding: true,
|
||||
hidePoweredBy,
|
||||
colors: parsedColors?.success ? parsedColors.data : null,
|
||||
css: settings.brandingCss && settings.brandingCss.length > 0 ? settings.brandingCss : null,
|
||||
};
|
||||
};
|
||||
@@ -11,8 +11,7 @@ export const getCertificateStatus = () => {
|
||||
return { isAvailable: true };
|
||||
}
|
||||
|
||||
const defaultPath =
|
||||
env('NODE_ENV') === 'production' ? '/opt/documenso/cert.p12' : './example/cert.p12';
|
||||
const defaultPath = env('NODE_ENV') === 'production' ? '/opt/documenso/cert.p12' : './example/cert.p12';
|
||||
|
||||
const filePath = env('NEXT_PRIVATE_SIGNING_LOCAL_FILE_PATH') || defaultPath;
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
import { DOCUMENSO_ENCRYPTION_SECONDARY_KEY } from '@documenso/lib/constants/crypto';
|
||||
import { symmetricEncrypt } from '@documenso/lib/universal/crypto';
|
||||
import { z } from 'zod';
|
||||
|
||||
export const ZEncryptedDataSchema = z.object({
|
||||
data: z.string(),
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import type { DocumentDataType } from '@prisma/client';
|
||||
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import type { DocumentDataType } from '@prisma/client';
|
||||
|
||||
export type CreateDocumentDataOptions = {
|
||||
type: DocumentDataType;
|
||||
|
||||
@@ -1,16 +1,8 @@
|
||||
import {
|
||||
type DocumentDistributionMethod,
|
||||
type DocumentSigningOrder,
|
||||
EnvelopeType,
|
||||
} from '@prisma/client';
|
||||
|
||||
import { DOCUMENT_AUDIT_LOG_TYPE } from '@documenso/lib/types/document-audit-logs';
|
||||
import type { ApiRequestMetadata } from '@documenso/lib/universal/extract-request-metadata';
|
||||
import {
|
||||
createDocumentAuditLogData,
|
||||
diffDocumentMetaChanges,
|
||||
} from '@documenso/lib/utils/document-audit-logs';
|
||||
import { createDocumentAuditLogData, diffDocumentMetaChanges } from '@documenso/lib/utils/document-audit-logs';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import { type DocumentDistributionMethod, type DocumentSigningOrder, EnvelopeType } from '@prisma/client';
|
||||
|
||||
import type { SupportedLanguageCodes } from '../../constants/i18n';
|
||||
import { AppError, AppErrorCode } from '../../errors/app-error';
|
||||
|
||||
@@ -1,3 +1,10 @@
|
||||
import { DEFAULT_DOCUMENT_DATE_FORMAT } from '@documenso/lib/constants/date-formats';
|
||||
import { DEFAULT_DOCUMENT_TIME_ZONE } from '@documenso/lib/constants/time-zones';
|
||||
import { DOCUMENT_AUDIT_LOG_TYPE, RECIPIENT_DIFF_TYPE } from '@documenso/lib/types/document-audit-logs';
|
||||
import type { RequestMetadata } from '@documenso/lib/universal/extract-request-metadata';
|
||||
import { fieldsContainUnsignedRequiredField } from '@documenso/lib/utils/advanced-fields-helpers';
|
||||
import { createDocumentAuditLogData } from '@documenso/lib/utils/document-audit-logs';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import {
|
||||
DocumentSigningOrder,
|
||||
DocumentStatus,
|
||||
@@ -10,25 +17,11 @@ import {
|
||||
} from '@prisma/client';
|
||||
import { DateTime } from 'luxon';
|
||||
|
||||
import { DEFAULT_DOCUMENT_DATE_FORMAT } from '@documenso/lib/constants/date-formats';
|
||||
import { DEFAULT_DOCUMENT_TIME_ZONE } from '@documenso/lib/constants/time-zones';
|
||||
import {
|
||||
DOCUMENT_AUDIT_LOG_TYPE,
|
||||
RECIPIENT_DIFF_TYPE,
|
||||
} from '@documenso/lib/types/document-audit-logs';
|
||||
import type { RequestMetadata } from '@documenso/lib/universal/extract-request-metadata';
|
||||
import { fieldsContainUnsignedRequiredField } from '@documenso/lib/utils/advanced-fields-helpers';
|
||||
import { createDocumentAuditLogData } from '@documenso/lib/utils/document-audit-logs';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
|
||||
import { AppError, AppErrorCode } from '../../errors/app-error';
|
||||
import { jobs } from '../../jobs/client';
|
||||
import type { TRecipientAccessAuth } from '../../types/document-auth';
|
||||
import { DocumentAuth } from '../../types/document-auth';
|
||||
import {
|
||||
ZWebhookDocumentSchema,
|
||||
mapEnvelopeToWebhookDocumentPayload,
|
||||
} from '../../types/webhook-payload';
|
||||
import { mapEnvelopeToWebhookDocumentPayload, ZWebhookDocumentSchema } from '../../types/webhook-payload';
|
||||
import { extractDocumentAuthMethods } from '../../utils/document-auth';
|
||||
import type { EnvelopeIdOptions } from '../../utils/envelope';
|
||||
import { mapSecondaryIdToDocumentId, unsafeBuildEnvelopeIdQuery } from '../../utils/envelope';
|
||||
@@ -117,9 +110,7 @@ export const completeDocumentWithToken = async ({
|
||||
});
|
||||
|
||||
if (!isRecipientsTurn) {
|
||||
throw new Error(
|
||||
`Recipient ${recipient.id} attempted to complete the document before it was their turn`,
|
||||
);
|
||||
throw new Error(`Recipient ${recipient.id} attempted to complete the document before it was their turn`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -189,9 +180,7 @@ export const completeDocumentWithToken = async ({
|
||||
});
|
||||
|
||||
// This should be scoped to the current recipient.
|
||||
const uninsertedDateFields = fields.filter(
|
||||
(field) => field.type === FieldType.DATE && !field.inserted,
|
||||
);
|
||||
const uninsertedDateFields = fields.filter((field) => field.type === FieldType.DATE && !field.inserted);
|
||||
|
||||
let recipientName = recipient.name;
|
||||
let recipientEmail = recipient.email;
|
||||
|
||||
@@ -1,22 +1,17 @@
|
||||
import { createElement } from 'react';
|
||||
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import type { DocumentMeta, Envelope, Recipient, User } from '@prisma/client';
|
||||
import { DocumentStatus, EnvelopeType, SendStatus, WebhookTriggerEvents } from '@prisma/client';
|
||||
|
||||
import { mailer } from '@documenso/email/mailer';
|
||||
import DocumentCancelTemplate from '@documenso/email/templates/document-cancel';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import type { DocumentMeta, Envelope, Recipient, User } from '@prisma/client';
|
||||
import { DocumentStatus, EnvelopeType, SendStatus, WebhookTriggerEvents } from '@prisma/client';
|
||||
import { createElement } from 'react';
|
||||
|
||||
import { getI18nInstance } from '../../client-only/providers/i18n-server';
|
||||
import { NEXT_PUBLIC_WEBAPP_URL } from '../../constants/app';
|
||||
import { AppError, AppErrorCode } from '../../errors/app-error';
|
||||
import { DOCUMENT_AUDIT_LOG_TYPE } from '../../types/document-audit-logs';
|
||||
import { extractDerivedDocumentEmailSettings } from '../../types/document-email';
|
||||
import {
|
||||
ZWebhookDocumentSchema,
|
||||
mapEnvelopeToWebhookDocumentPayload,
|
||||
} from '../../types/webhook-payload';
|
||||
import { mapEnvelopeToWebhookDocumentPayload, ZWebhookDocumentSchema } from '../../types/webhook-payload';
|
||||
import type { ApiRequestMetadata } from '../../universal/extract-request-metadata';
|
||||
import { isDocumentCompleted } from '../../utils/document';
|
||||
import { createDocumentAuditLogData } from '../../utils/document-audit-logs';
|
||||
@@ -34,12 +29,7 @@ export type DeleteDocumentOptions = {
|
||||
requestMetadata: ApiRequestMetadata;
|
||||
};
|
||||
|
||||
export const deleteDocument = async ({
|
||||
id,
|
||||
userId,
|
||||
teamId,
|
||||
requestMetadata,
|
||||
}: DeleteDocumentOptions) => {
|
||||
export const deleteDocument = async ({ id, userId, teamId, requestMetadata }: DeleteDocumentOptions) => {
|
||||
const user = await prisma.user.findUnique({
|
||||
where: {
|
||||
id: userId,
|
||||
@@ -131,11 +121,7 @@ type HandleDocumentOwnerDeleteOptions = {
|
||||
requestMetadata: ApiRequestMetadata;
|
||||
};
|
||||
|
||||
const handleDocumentOwnerDelete = async ({
|
||||
envelope,
|
||||
user,
|
||||
requestMetadata,
|
||||
}: HandleDocumentOwnerDeleteOptions) => {
|
||||
const handleDocumentOwnerDelete = async ({ envelope, user, requestMetadata }: HandleDocumentOwnerDeleteOptions) => {
|
||||
if (envelope.deletedAt) {
|
||||
return;
|
||||
}
|
||||
@@ -199,9 +185,7 @@ const handleDocumentOwnerDelete = async ({
|
||||
});
|
||||
});
|
||||
|
||||
const isEnvelopeDeleteEmailEnabled = extractDerivedDocumentEmailSettings(
|
||||
envelope.documentMeta,
|
||||
).documentDeleted;
|
||||
const isEnvelopeDeleteEmailEnabled = extractDerivedDocumentEmailSettings(envelope.documentMeta).documentDeleted;
|
||||
|
||||
if (!isEnvelopeDeleteEmailEnabled) {
|
||||
return deletedEnvelope;
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { type DocumentAuditLog, EnvelopeType, type Prisma } from '@prisma/client';
|
||||
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import { type DocumentAuditLog, EnvelopeType, type Prisma } from '@prisma/client';
|
||||
|
||||
import { AppError, AppErrorCode } from '../../errors/app-error';
|
||||
import { DOCUMENT_AUDIT_LOG_TYPE } from '../../types/document-audit-logs';
|
||||
@@ -99,7 +98,7 @@ export const findDocumentAuditLogs = async ({
|
||||
}),
|
||||
]);
|
||||
|
||||
let nextCursor: string | undefined = undefined;
|
||||
let nextCursor: string | undefined;
|
||||
|
||||
const parsedData = data.map((auditLog) => parseDocumentAuditLogData(auditLog));
|
||||
|
||||
|
||||
@@ -1,16 +1,20 @@
|
||||
import { kyselyPrisma, prisma, sql } from '@documenso/prisma';
|
||||
import type { DB } from '@documenso/prisma/generated/types';
|
||||
import { ExtendedDocumentStatus } from '@documenso/prisma/types/extended-document-status';
|
||||
import type { DocumentSource, Envelope, Team, TeamEmail } from '@prisma/client';
|
||||
import { DocumentVisibility } from '@prisma/client';
|
||||
import { DocumentStatus } from '@prisma/client';
|
||||
import { EnvelopeType, RecipientRole, SigningStatus, TeamMemberRole } from '@prisma/client';
|
||||
import {
|
||||
DocumentStatus,
|
||||
DocumentVisibility,
|
||||
EnvelopeType,
|
||||
RecipientRole,
|
||||
SigningStatus,
|
||||
TeamMemberRole,
|
||||
} from '@prisma/client';
|
||||
import type { Expression, ExpressionBuilder, SelectQueryBuilder, SqlBool } from 'kysely';
|
||||
import { DateTime } from 'luxon';
|
||||
import { match } from 'ts-pattern';
|
||||
|
||||
import { kyselyPrisma, prisma, sql } from '@documenso/prisma';
|
||||
import type { DB } from '@documenso/prisma/generated/types';
|
||||
import { ExtendedDocumentStatus } from '@documenso/prisma/types/extended-document-status';
|
||||
|
||||
import { type FindResultResponse } from '../../types/search-params';
|
||||
import type { FindResultResponse } from '../../types/search-params';
|
||||
import { maskRecipientTokensForDocument } from '../../utils/mask-recipient-tokens-for-document';
|
||||
import { getTeamById } from '../team/get-team';
|
||||
|
||||
@@ -137,18 +141,14 @@ export const findDocuments = async ({
|
||||
// folder, period, sender, source, template, and search.
|
||||
|
||||
const buildBaseQuery = () => {
|
||||
let qb = kyselyPrisma.$kysely
|
||||
.selectFrom('Envelope')
|
||||
.select(['Envelope.id', 'Envelope.createdAt']);
|
||||
let qb = kyselyPrisma.$kysely.selectFrom('Envelope').select(['Envelope.id', 'Envelope.createdAt']);
|
||||
|
||||
// Type must be DOCUMENT (enum cast requires raw sql — this is the one escape hatch)
|
||||
qb = qb.where('Envelope.type', '=', sql.lit(EnvelopeType.DOCUMENT));
|
||||
|
||||
// Folder filter
|
||||
qb =
|
||||
folderId !== undefined
|
||||
? qb.where('Envelope.folderId', '=', folderId)
|
||||
: qb.where('Envelope.folderId', 'is', null);
|
||||
folderId !== undefined ? qb.where('Envelope.folderId', '=', folderId) : qb.where('Envelope.folderId', 'is', null);
|
||||
|
||||
// Period filter
|
||||
if (period) {
|
||||
@@ -220,10 +220,7 @@ export const findDocuments = async ({
|
||||
eb.or([
|
||||
eb('Envelope.userId', '=', user.id),
|
||||
eb.and([
|
||||
eb('Envelope.status', 'in', [
|
||||
sql.lit(DocumentStatus.COMPLETED),
|
||||
sql.lit(DocumentStatus.PENDING),
|
||||
]),
|
||||
eb('Envelope.status', 'in', [sql.lit(DocumentStatus.COMPLETED), sql.lit(DocumentStatus.PENDING)]),
|
||||
recipientExists(eb, user.email),
|
||||
]),
|
||||
]),
|
||||
@@ -311,10 +308,7 @@ export const findDocuments = async ({
|
||||
DocumentVisibility.MANAGER_AND_ABOVE,
|
||||
DocumentVisibility.ADMIN,
|
||||
])
|
||||
.with(TeamMemberRole.MANAGER, () => [
|
||||
DocumentVisibility.EVERYONE,
|
||||
DocumentVisibility.MANAGER_AND_ABOVE,
|
||||
])
|
||||
.with(TeamMemberRole.MANAGER, () => [DocumentVisibility.EVERYONE, DocumentVisibility.MANAGER_AND_ABOVE])
|
||||
.otherwise(() => [DocumentVisibility.EVERYONE]);
|
||||
|
||||
// Visibility: meets role threshold OR directly involved
|
||||
@@ -331,15 +325,11 @@ export const findDocuments = async ({
|
||||
|
||||
// Deleted filter for team path
|
||||
const teamDeletedFilter = (eb: EnvelopeExpressionBuilder) => {
|
||||
const branches = [
|
||||
eb.and([eb('Envelope.teamId', '=', teamData.id), eb('Envelope.deletedAt', 'is', null)]),
|
||||
];
|
||||
const branches = [eb.and([eb('Envelope.teamId', '=', teamData.id), eb('Envelope.deletedAt', 'is', null)])];
|
||||
|
||||
if (teamEmail) {
|
||||
branches.push(eb.and([senderEmailIs(eb, teamEmail), eb('Envelope.deletedAt', 'is', null)]));
|
||||
branches.push(
|
||||
recipientExists(eb, teamEmail, (reb) => reb('Recipient.documentDeletedAt', 'is', null)),
|
||||
);
|
||||
branches.push(recipientExists(eb, teamEmail, (reb) => reb('Recipient.documentDeletedAt', 'is', null)));
|
||||
}
|
||||
|
||||
return eb.or(branches);
|
||||
@@ -353,10 +343,7 @@ export const findDocuments = async ({
|
||||
if (teamEmail) {
|
||||
accessBranches.push(senderEmailIs(eb, teamEmail));
|
||||
accessBranches.push(
|
||||
eb.and([
|
||||
eb('status', '!=', sql.lit(ExtendedDocumentStatus.DRAFT)),
|
||||
recipientExists(eb, teamEmail),
|
||||
]),
|
||||
eb.and([eb('status', '!=', sql.lit(ExtendedDocumentStatus.DRAFT)), recipientExists(eb, teamEmail)]),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -368,23 +355,21 @@ export const findDocuments = async ({
|
||||
return null;
|
||||
}
|
||||
|
||||
return qb
|
||||
.where('Envelope.status', '!=', sql.lit(ExtendedDocumentStatus.DRAFT))
|
||||
.where((eb) =>
|
||||
eb.and([
|
||||
visibilityFilter(eb),
|
||||
// Single EXISTS check: the team-email recipient must be NOT_SIGNED,
|
||||
// non-CC, and not soft-deleted. Replaces teamDeletedFilter + separate
|
||||
// recipientExists, eliminating a hashed SubPlan (~79k rows).
|
||||
recipientExists(eb, teamEmail, (reb) =>
|
||||
reb.and([
|
||||
reb('Recipient.documentDeletedAt', 'is', null),
|
||||
reb('Recipient.signingStatus', '=', sql.lit(SigningStatus.NOT_SIGNED)),
|
||||
reb('Recipient.role', '!=', sql.lit(RecipientRole.CC)),
|
||||
]),
|
||||
),
|
||||
]),
|
||||
);
|
||||
return qb.where('Envelope.status', '!=', sql.lit(ExtendedDocumentStatus.DRAFT)).where((eb) =>
|
||||
eb.and([
|
||||
visibilityFilter(eb),
|
||||
// Single EXISTS check: the team-email recipient must be NOT_SIGNED,
|
||||
// non-CC, and not soft-deleted. Replaces teamDeletedFilter + separate
|
||||
// recipientExists, eliminating a hashed SubPlan (~79k rows).
|
||||
recipientExists(eb, teamEmail, (reb) =>
|
||||
reb.and([
|
||||
reb('Recipient.documentDeletedAt', 'is', null),
|
||||
reb('Recipient.signingStatus', '=', sql.lit(SigningStatus.NOT_SIGNED)),
|
||||
reb('Recipient.role', '!=', sql.lit(RecipientRole.CC)),
|
||||
]),
|
||||
),
|
||||
]),
|
||||
);
|
||||
})
|
||||
.with(ExtendedDocumentStatus.DRAFT, () =>
|
||||
qb.where('Envelope.status', '=', sql.lit(ExtendedDocumentStatus.DRAFT)).where((eb) => {
|
||||
@@ -466,10 +451,7 @@ export const findDocuments = async ({
|
||||
const offset = Math.max(page - 1, 0) * perPage;
|
||||
|
||||
// Data query: paginated, executed directly via Kysely query builder
|
||||
const dataQuery = filteredQuery
|
||||
.orderBy(`Envelope.${orderByColumn}`, orderByDirection)
|
||||
.limit(perPage)
|
||||
.offset(offset);
|
||||
const dataQuery = filteredQuery.orderBy(`Envelope.${orderByColumn}`, orderByDirection).limit(perPage).offset(offset);
|
||||
|
||||
// Count query: either windowed (fast, capped) or full (exact, for API consumers).
|
||||
const baseCountQuery = filteredQuery.clearSelect().select('Envelope.id');
|
||||
@@ -482,10 +464,7 @@ export const findDocuments = async ({
|
||||
.selectFrom(baseCountQuery.as('filtered'))
|
||||
.select(({ fn }) => fn.count<number>('id').as('total'));
|
||||
|
||||
const [dataResult, countResult] = await Promise.all([
|
||||
dataQuery.execute(),
|
||||
countQuery.executeTakeFirstOrThrow(),
|
||||
]);
|
||||
const [dataResult, countResult] = await Promise.all([dataQuery.execute(), countQuery.executeTakeFirstOrThrow()]);
|
||||
|
||||
const ids = dataResult.map((row) => row.id);
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { DocumentStatus, EnvelopeType } from '@prisma/client';
|
||||
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import { DocumentStatus, EnvelopeType } from '@prisma/client';
|
||||
|
||||
import { mapSecondaryIdToDocumentId } from '../../utils/envelope';
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { EnvelopeType } from '@prisma/client';
|
||||
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import { EnvelopeType } from '@prisma/client';
|
||||
|
||||
import { AppError, AppErrorCode } from '../../errors/app-error';
|
||||
import type { TDocumentAuthMethods } from '../../types/document-auth';
|
||||
|
||||
@@ -7,9 +7,7 @@ export type GetDocumentCertificateAuditLogsOptions = {
|
||||
envelopeId: string;
|
||||
};
|
||||
|
||||
export const getDocumentCertificateAuditLogs = async ({
|
||||
envelopeId,
|
||||
}: GetDocumentCertificateAuditLogsOptions) => {
|
||||
export const getDocumentCertificateAuditLogs = async ({ envelopeId }: GetDocumentCertificateAuditLogsOptions) => {
|
||||
const rawAuditLogs = await prisma.documentAuditLog.findMany({
|
||||
where: {
|
||||
envelopeId,
|
||||
|
||||
@@ -9,11 +9,7 @@ export type GetDocumentWithDetailsByIdOptions = {
|
||||
teamId: number;
|
||||
};
|
||||
|
||||
export const getDocumentWithDetailsById = async ({
|
||||
id,
|
||||
userId,
|
||||
teamId,
|
||||
}: GetDocumentWithDetailsByIdOptions) => {
|
||||
export const getDocumentWithDetailsById = async ({ id, userId, teamId }: GetDocumentWithDetailsByIdOptions) => {
|
||||
const envelope = await getEnvelopeById({
|
||||
id,
|
||||
type: EnvelopeType.DOCUMENT,
|
||||
|
||||
@@ -4,9 +4,7 @@ export type GetRecipientOrSenderByShareLinkSlugOptions = {
|
||||
slug: string;
|
||||
};
|
||||
|
||||
export const getRecipientOrSenderByShareLinkSlug = async ({
|
||||
slug,
|
||||
}: GetRecipientOrSenderByShareLinkSlugOptions) => {
|
||||
export const getRecipientOrSenderByShareLinkSlug = async ({ slug }: GetRecipientOrSenderByShareLinkSlugOptions) => {
|
||||
const { envelopeId, email } = await prisma.documentShareLink.findFirstOrThrow({
|
||||
where: {
|
||||
slug,
|
||||
|
||||
@@ -1,17 +1,10 @@
|
||||
import {
|
||||
DocumentStatus,
|
||||
EnvelopeType,
|
||||
RecipientRole,
|
||||
SigningStatus,
|
||||
TeamMemberRole,
|
||||
} from '@prisma/client';
|
||||
import type { Expression, ExpressionBuilder, SelectQueryBuilder, SqlBool } from 'kysely';
|
||||
import { DateTime } from 'luxon';
|
||||
|
||||
import type { PeriodSelectorValue } from '@documenso/lib/server-only/document/find-documents';
|
||||
import { kyselyPrisma, prisma, sql } from '@documenso/prisma';
|
||||
import type { DB } from '@documenso/prisma/generated/types';
|
||||
import { ExtendedDocumentStatus } from '@documenso/prisma/types/extended-document-status';
|
||||
import { DocumentStatus, EnvelopeType, RecipientRole, SigningStatus, TeamMemberRole } from '@prisma/client';
|
||||
import type { Expression, ExpressionBuilder, SelectQueryBuilder, SqlBool } from 'kysely';
|
||||
import { DateTime } from 'luxon';
|
||||
|
||||
import { STATS_COUNT_CAP } from '../../constants/document';
|
||||
import { TEAM_DOCUMENT_VISIBILITY_MAP } from '../../constants/teams';
|
||||
@@ -87,14 +80,7 @@ const cappedCount = async (qb: EnvelopeQueryBuilder): Promise<number> => {
|
||||
return Math.min(Number(result.total ?? 0), STATS_COUNT_CAP);
|
||||
};
|
||||
|
||||
export const getStats = async ({
|
||||
userId,
|
||||
teamId,
|
||||
period,
|
||||
search = '',
|
||||
folderId,
|
||||
senderIds,
|
||||
}: GetStatsInput) => {
|
||||
export const getStats = async ({ userId, teamId, period, search = '', folderId, senderIds }: GetStatsInput) => {
|
||||
const user = await prisma.user.findFirstOrThrow({
|
||||
where: { id: userId },
|
||||
select: { id: true, email: true },
|
||||
@@ -113,18 +99,14 @@ export const getStats = async ({
|
||||
// ─── Base query builder ──────────────────────────────────────────────
|
||||
|
||||
const buildBaseQuery = (): EnvelopeQueryBuilder => {
|
||||
let qb: EnvelopeQueryBuilder = kyselyPrisma.$kysely
|
||||
.selectFrom('Envelope')
|
||||
.select('Envelope.id');
|
||||
let qb: EnvelopeQueryBuilder = kyselyPrisma.$kysely.selectFrom('Envelope').select('Envelope.id');
|
||||
|
||||
// Type = DOCUMENT
|
||||
qb = qb.where('Envelope.type', '=', sql.lit(EnvelopeType.DOCUMENT));
|
||||
|
||||
// Folder filter
|
||||
qb =
|
||||
folderId !== undefined
|
||||
? qb.where('Envelope.folderId', '=', folderId)
|
||||
: qb.where('Envelope.folderId', 'is', null);
|
||||
folderId !== undefined ? qb.where('Envelope.folderId', '=', folderId) : qb.where('Envelope.folderId', 'is', null);
|
||||
|
||||
// Period filter
|
||||
if (period) {
|
||||
@@ -181,15 +163,11 @@ export const getStats = async ({
|
||||
]);
|
||||
|
||||
const teamDeletedFilter = (eb: EnvelopeExpressionBuilder) => {
|
||||
const branches = [
|
||||
eb.and([eb('Envelope.teamId', '=', team.id), eb('Envelope.deletedAt', 'is', null)]),
|
||||
];
|
||||
const branches = [eb.and([eb('Envelope.teamId', '=', team.id), eb('Envelope.deletedAt', 'is', null)])];
|
||||
|
||||
if (teamEmail) {
|
||||
branches.push(eb.and([senderEmailIs(eb, teamEmail), eb('Envelope.deletedAt', 'is', null)]));
|
||||
branches.push(
|
||||
recipientExists(eb, teamEmail, (reb) => reb('Recipient.documentDeletedAt', 'is', null)),
|
||||
);
|
||||
branches.push(recipientExists(eb, teamEmail, (reb) => reb('Recipient.documentDeletedAt', 'is', null)));
|
||||
}
|
||||
|
||||
return eb.or(branches);
|
||||
@@ -254,9 +232,7 @@ export const getStats = async ({
|
||||
if (teamEmail) {
|
||||
accessBranches.push(senderEmailIs(eb, teamEmail));
|
||||
accessBranches.push(
|
||||
recipientExists(eb, teamEmail, (reb) =>
|
||||
reb('Recipient.signingStatus', '=', sql.lit(SigningStatus.REJECTED)),
|
||||
),
|
||||
recipientExists(eb, teamEmail, (reb) => reb('Recipient.signingStatus', '=', sql.lit(SigningStatus.REJECTED))),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,19 +1,17 @@
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import type { Envelope, Recipient } from '@prisma/client';
|
||||
import { verifyAuthenticationResponse } from '@simplewebauthn/server';
|
||||
import { isoBase64URL } from '@simplewebauthn/server/helpers';
|
||||
import { match } from 'ts-pattern';
|
||||
|
||||
import { prisma } from '@documenso/prisma';
|
||||
|
||||
import { validateTwoFactorTokenFromEmail } from '../2fa/email/validate-2fa-token-from-email';
|
||||
import { verifyTwoFactorAuthenticationToken } from '../2fa/verify-2fa-token';
|
||||
import { verifyPassword } from '../2fa/verify-password';
|
||||
import { AppError, AppErrorCode } from '../../errors/app-error';
|
||||
import type { TDocumentAuth, TDocumentAuthMethods } from '../../types/document-auth';
|
||||
import { DocumentAuth } from '../../types/document-auth';
|
||||
import type { TAuthenticationResponseJSONSchema } from '../../types/webauthn';
|
||||
import { getAuthenticatorOptions } from '../../utils/authenticator';
|
||||
import { extractDocumentAuthMethods } from '../../utils/document-auth';
|
||||
import { validateTwoFactorTokenFromEmail } from '../2fa/email/validate-2fa-token-from-email';
|
||||
import { verifyTwoFactorAuthenticationToken } from '../2fa/verify-2fa-token';
|
||||
import { verifyPassword } from '../2fa/verify-password';
|
||||
|
||||
type IsRecipientAuthorizedOptions = {
|
||||
// !: Probably find a better name than 'ACCESS_2FA' if requirements change.
|
||||
@@ -71,10 +69,7 @@ export const isRecipientAuthorized = async ({
|
||||
.exhaustive();
|
||||
|
||||
// Early true return when auth is not required.
|
||||
if (
|
||||
authMethods.length === 0 ||
|
||||
authMethods.some((method) => method === DocumentAuth.EXPLICIT_NONE)
|
||||
) {
|
||||
if (authMethods.length === 0 || authMethods.some((method) => method === DocumentAuth.EXPLICIT_NONE)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { DocumentStatus, EnvelopeType, SigningStatus } from '@prisma/client';
|
||||
|
||||
import { jobs } from '@documenso/lib/jobs/client';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import { DocumentStatus, EnvelopeType, SigningStatus } from '@prisma/client';
|
||||
|
||||
import { AppError, AppErrorCode } from '../../errors/app-error';
|
||||
import { DOCUMENT_AUDIT_LOG_TYPE } from '../../types/document-audit-logs';
|
||||
@@ -18,12 +17,7 @@ export type RejectDocumentWithTokenOptions = {
|
||||
requestMetadata?: RequestMetadata;
|
||||
};
|
||||
|
||||
export async function rejectDocumentWithToken({
|
||||
token,
|
||||
id,
|
||||
reason,
|
||||
requestMetadata,
|
||||
}: RejectDocumentWithTokenOptions) {
|
||||
export async function rejectDocumentWithToken({ token, id, reason, requestMetadata }: RejectDocumentWithTokenOptions) {
|
||||
// Find the recipient and document in a single query
|
||||
const recipient = await prisma.recipient.findFirst({
|
||||
where: {
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
import { createElement } from 'react';
|
||||
|
||||
import { mailer } from '@documenso/email/mailer';
|
||||
import { DocumentInviteEmailTemplate } from '@documenso/email/templates/document-invite';
|
||||
import { resolveExpiresAt } from '@documenso/lib/constants/envelope-expiration';
|
||||
import { RECIPIENT_ROLE_TO_EMAIL_TYPE, RECIPIENT_ROLES_DESCRIPTION } from '@documenso/lib/constants/recipient-roles';
|
||||
import { DOCUMENT_AUDIT_LOG_TYPE } from '@documenso/lib/types/document-audit-logs';
|
||||
import type { ApiRequestMetadata } from '@documenso/lib/universal/extract-request-metadata';
|
||||
import { createDocumentAuditLogData } from '@documenso/lib/utils/document-audit-logs';
|
||||
import { renderCustomEmailTemplate } from '@documenso/lib/utils/render-custom-email-template';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import {
|
||||
DocumentStatus,
|
||||
@@ -9,27 +16,12 @@ import {
|
||||
SigningStatus,
|
||||
WebhookTriggerEvents,
|
||||
} from '@prisma/client';
|
||||
|
||||
import { mailer } from '@documenso/email/mailer';
|
||||
import { DocumentInviteEmailTemplate } from '@documenso/email/templates/document-invite';
|
||||
import { resolveExpiresAt } from '@documenso/lib/constants/envelope-expiration';
|
||||
import {
|
||||
RECIPIENT_ROLES_DESCRIPTION,
|
||||
RECIPIENT_ROLE_TO_EMAIL_TYPE,
|
||||
} from '@documenso/lib/constants/recipient-roles';
|
||||
import { DOCUMENT_AUDIT_LOG_TYPE } from '@documenso/lib/types/document-audit-logs';
|
||||
import type { ApiRequestMetadata } from '@documenso/lib/universal/extract-request-metadata';
|
||||
import { createDocumentAuditLogData } from '@documenso/lib/utils/document-audit-logs';
|
||||
import { renderCustomEmailTemplate } from '@documenso/lib/utils/render-custom-email-template';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import { createElement } from 'react';
|
||||
|
||||
import { getI18nInstance } from '../../client-only/providers/i18n-server';
|
||||
import { NEXT_PUBLIC_WEBAPP_URL } from '../../constants/app';
|
||||
import { extractDerivedDocumentEmailSettings } from '../../types/document-email';
|
||||
import {
|
||||
ZWebhookDocumentSchema,
|
||||
mapEnvelopeToWebhookDocumentPayload,
|
||||
} from '../../types/webhook-payload';
|
||||
import { mapEnvelopeToWebhookDocumentPayload, ZWebhookDocumentSchema } from '../../types/webhook-payload';
|
||||
import { isDocumentCompleted } from '../../utils/document';
|
||||
import type { EnvelopeIdOptions } from '../../utils/envelope';
|
||||
import { isRecipientEmailValidForSending } from '../../utils/recipients';
|
||||
@@ -46,13 +38,7 @@ export type ResendDocumentOptions = {
|
||||
requestMetadata: ApiRequestMetadata;
|
||||
};
|
||||
|
||||
export const resendDocument = async ({
|
||||
id,
|
||||
userId,
|
||||
recipients,
|
||||
teamId,
|
||||
requestMetadata,
|
||||
}: ResendDocumentOptions) => {
|
||||
export const resendDocument = async ({ id, userId, recipients, teamId, requestMetadata }: ResendDocumentOptions) => {
|
||||
const user = await prisma.user.findFirstOrThrow({
|
||||
where: {
|
||||
id: userId,
|
||||
@@ -134,15 +120,14 @@ export const resendDocument = async ({
|
||||
return envelope;
|
||||
}
|
||||
|
||||
const { branding, emailLanguage, organisationType, senderEmail, replyToEmail } =
|
||||
await getEmailContext({
|
||||
emailType: 'RECIPIENT',
|
||||
source: {
|
||||
type: 'team',
|
||||
teamId: envelope.teamId,
|
||||
},
|
||||
meta: envelope.documentMeta,
|
||||
});
|
||||
const { branding, emailLanguage, organisationType, senderEmail, replyToEmail } = await getEmailContext({
|
||||
emailType: 'RECIPIENT',
|
||||
source: {
|
||||
type: 'team',
|
||||
teamId: envelope.teamId,
|
||||
},
|
||||
meta: envelope.documentMeta,
|
||||
});
|
||||
|
||||
await Promise.all(
|
||||
recipientsToRemind.map(async (recipient) => {
|
||||
@@ -157,9 +142,7 @@ export const resendDocument = async ({
|
||||
const { email, name } = recipient;
|
||||
const selfSigner = email === user.email;
|
||||
|
||||
const recipientActionVerb = i18n
|
||||
._(RECIPIENT_ROLES_DESCRIPTION[recipient.role].actionVerb)
|
||||
.toLowerCase();
|
||||
const recipientActionVerb = i18n._(RECIPIENT_ROLES_DESCRIPTION[recipient.role].actionVerb).toLowerCase();
|
||||
|
||||
let emailMessage = envelope.documentMeta.message || '';
|
||||
let emailSubject = i18n._(msg`Reminder: Please ${recipientActionVerb} this document`);
|
||||
@@ -172,9 +155,7 @@ export const resendDocument = async ({
|
||||
}
|
||||
|
||||
if (organisationType === OrganisationType.ORGANISATION) {
|
||||
emailSubject = i18n._(
|
||||
msg`Reminder: ${envelope.team.name} invited you to ${recipientActionVerb} a document`,
|
||||
);
|
||||
emailSubject = i18n._(msg`Reminder: ${envelope.team.name} invited you to ${recipientActionVerb} a document`);
|
||||
emailMessage =
|
||||
envelope.documentMeta.message ||
|
||||
i18n._(
|
||||
@@ -229,10 +210,7 @@ export const resendDocument = async ({
|
||||
from: senderEmail,
|
||||
replyTo: replyToEmail,
|
||||
subject: envelope.documentMeta.subject
|
||||
? renderCustomEmailTemplate(
|
||||
i18n._(msg`Reminder: ${envelope.documentMeta.subject}`),
|
||||
customEmailTemplate,
|
||||
)
|
||||
? renderCustomEmailTemplate(i18n._(msg`Reminder: ${envelope.documentMeta.subject}`), customEmailTemplate)
|
||||
: emailSubject,
|
||||
html,
|
||||
text,
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import { formatDocumentsPath, getHighestTeamRoleInGroup } from '@documenso/lib/utils/teams';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import type { Prisma } from '@prisma/client';
|
||||
import { DocumentStatus, DocumentVisibility, EnvelopeType, TeamMemberRole } from '@prisma/client';
|
||||
import { match } from 'ts-pattern';
|
||||
|
||||
import { formatDocumentsPath, getHighestTeamRoleInGroup } from '@documenso/lib/utils/teams';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
|
||||
import { mapSecondaryIdToDocumentId } from '../../utils/envelope';
|
||||
import { getUserTeamGroups } from '../team/get-user-team-groups';
|
||||
|
||||
@@ -14,11 +13,7 @@ export type SearchDocumentsWithKeywordOptions = {
|
||||
limit?: number;
|
||||
};
|
||||
|
||||
export const searchDocumentsWithKeyword = async ({
|
||||
query,
|
||||
userId,
|
||||
limit = 20,
|
||||
}: SearchDocumentsWithKeywordOptions) => {
|
||||
export const searchDocumentsWithKeyword = async ({ query, userId, limit = 20 }: SearchDocumentsWithKeywordOptions) => {
|
||||
if (!query.trim()) {
|
||||
return [];
|
||||
}
|
||||
@@ -135,10 +130,7 @@ export const searchDocumentsWithKeyword = async ({
|
||||
|
||||
let path: string;
|
||||
|
||||
if (
|
||||
envelope.userId === user.id ||
|
||||
(envelope.teamId && teamGroupsByTeamId.has(envelope.teamId))
|
||||
) {
|
||||
if (envelope.userId === user.id || (envelope.teamId && teamGroupsByTeamId.has(envelope.teamId))) {
|
||||
path = `${formatDocumentsPath(envelope.team.url)}/${legacyDocumentId}`;
|
||||
} else {
|
||||
const signingToken = envelope.recipients.find((r) => r.email === user.email)?.token;
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
import { createElement } from 'react';
|
||||
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import { DocumentSource, EnvelopeType } from '@prisma/client';
|
||||
|
||||
import { mailer } from '@documenso/email/mailer';
|
||||
import { DocumentCompletedEmailTemplate } from '@documenso/email/templates/document-completed';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import { DocumentSource, EnvelopeType } from '@prisma/client';
|
||||
import { createElement } from 'react';
|
||||
|
||||
import { getI18nInstance } from '../../client-only/providers/i18n-server';
|
||||
import { NEXT_PUBLIC_WEBAPP_URL } from '../../constants/app';
|
||||
@@ -86,8 +84,7 @@ export const sendCompletedEmail = async ({ id, requestMetadata }: SendDocumentOp
|
||||
const file = await getFileServerSide(envelopeItem.documentData);
|
||||
|
||||
// Use the envelope title for version 1, and the envelope item title for version 2.
|
||||
const fileNameToUse =
|
||||
envelope.internalVersion === 1 ? envelope.title : envelopeItem.title + '.pdf';
|
||||
const fileNameToUse = envelope.internalVersion === 1 ? envelope.title : envelopeItem.title + '.pdf';
|
||||
|
||||
return {
|
||||
filename: fileNameToUse.endsWith('.pdf') ? fileNameToUse : fileNameToUse + '.pdf',
|
||||
@@ -104,9 +101,7 @@ export const sendCompletedEmail = async ({ id, requestMetadata }: SendDocumentOp
|
||||
)}/${envelope.id}`;
|
||||
|
||||
if (envelope.team?.url) {
|
||||
documentOwnerDownloadLink = `${NEXT_PUBLIC_WEBAPP_URL()}/t/${envelope.team.url}/documents/${
|
||||
envelope.id
|
||||
}`;
|
||||
documentOwnerDownloadLink = `${NEXT_PUBLIC_WEBAPP_URL()}/t/${envelope.team.url}/documents/${envelope.id}`;
|
||||
}
|
||||
|
||||
const emailSettings = extractDerivedDocumentEmailSettings(envelope.documentMeta);
|
||||
@@ -120,8 +115,7 @@ export const sendCompletedEmail = async ({ id, requestMetadata }: SendDocumentOp
|
||||
// - Recipient emails are disabled
|
||||
if (
|
||||
isOwnerDocumentCompletedEmailEnabled &&
|
||||
(!envelope.recipients.find((recipient) => recipient.email === owner.email) ||
|
||||
!isDocumentCompletedEmailEnabled)
|
||||
(!envelope.recipients.find((recipient) => recipient.email === owner.email) || !isDocumentCompletedEmailEnabled)
|
||||
) {
|
||||
const template = createElement(DocumentCompletedEmailTemplate, {
|
||||
documentName: envelope.title,
|
||||
@@ -177,9 +171,7 @@ export const sendCompletedEmail = async ({ id, requestMetadata }: SendDocumentOp
|
||||
return;
|
||||
}
|
||||
|
||||
const recipientsToNotify = envelope.recipients.filter((recipient) =>
|
||||
isRecipientEmailValidForSending(recipient),
|
||||
);
|
||||
const recipientsToNotify = envelope.recipients.filter((recipient) => isRecipientEmailValidForSending(recipient));
|
||||
|
||||
await Promise.all(
|
||||
recipientsToNotify.map(async (recipient) => {
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
import { createElement } from 'react';
|
||||
|
||||
import { msg } from '@lingui/core/macro';
|
||||
|
||||
import { mailer } from '@documenso/email/mailer';
|
||||
import { DocumentSuperDeleteEmailTemplate } from '@documenso/email/templates/document-super-delete';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import { createElement } from 'react';
|
||||
|
||||
import { getI18nInstance } from '../../client-only/providers/i18n-server';
|
||||
import { NEXT_PUBLIC_WEBAPP_URL } from '../../constants/app';
|
||||
@@ -42,9 +40,7 @@ export const sendDeleteEmail = async ({ envelopeId, reason }: SendDeleteEmailOpt
|
||||
});
|
||||
}
|
||||
|
||||
const isDocumentDeletedEmailEnabled = extractDerivedDocumentEmailSettings(
|
||||
envelope.documentMeta,
|
||||
).documentDeleted;
|
||||
const isDocumentDeletedEmailEnabled = extractDerivedDocumentEmailSettings(envelope.documentMeta).documentDeleted;
|
||||
|
||||
if (!isDocumentDeletedEmailEnabled) {
|
||||
return;
|
||||
|
||||
@@ -1,3 +1,9 @@
|
||||
import { resolveExpiresAt } from '@documenso/lib/constants/envelope-expiration';
|
||||
import { DOCUMENT_AUDIT_LOG_TYPE } from '@documenso/lib/types/document-audit-logs';
|
||||
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 { checkboxValidationSigns } from '@documenso/ui/primitives/document-flow/field-items-advanced-settings/constants';
|
||||
import type { DocumentData, Envelope, EnvelopeItem, Field, Recipient } from '@prisma/client';
|
||||
import {
|
||||
DocumentSigningOrder,
|
||||
@@ -10,13 +16,6 @@ import {
|
||||
WebhookTriggerEvents,
|
||||
} from '@prisma/client';
|
||||
|
||||
import { resolveExpiresAt } from '@documenso/lib/constants/envelope-expiration';
|
||||
import { DOCUMENT_AUDIT_LOG_TYPE } from '@documenso/lib/types/document-audit-logs';
|
||||
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 { checkboxValidationSigns } from '@documenso/ui/primitives/document-flow/field-items-advanced-settings/constants';
|
||||
|
||||
import { validateCheckboxLength } from '../../advanced-fields-validation/validate-checkbox';
|
||||
import { DIRECT_TEMPLATE_RECIPIENT_EMAIL } from '../../constants/direct-templates';
|
||||
import { AppError, AppErrorCode } from '../../errors/app-error';
|
||||
@@ -30,20 +29,14 @@ import {
|
||||
ZRadioFieldMeta,
|
||||
ZTextFieldMeta,
|
||||
} from '../../types/field-meta';
|
||||
import {
|
||||
ZWebhookDocumentSchema,
|
||||
mapEnvelopeToWebhookDocumentPayload,
|
||||
} from '../../types/webhook-payload';
|
||||
import { mapEnvelopeToWebhookDocumentPayload, ZWebhookDocumentSchema } from '../../types/webhook-payload';
|
||||
import { getFileServerSide } from '../../universal/upload/get-file.server';
|
||||
import { putNormalizedPdfFileServerSide } from '../../universal/upload/put-file.server';
|
||||
import { isDocumentCompleted } from '../../utils/document';
|
||||
import { extractDocumentAuthMethods } from '../../utils/document-auth';
|
||||
import { type EnvelopeIdOptions, mapSecondaryIdToDocumentId } from '../../utils/envelope';
|
||||
import { toCheckboxCustomText, toRadioCustomText } from '../../utils/fields';
|
||||
import {
|
||||
getRecipientsWithMissingFields,
|
||||
isRecipientEmailValidForSending,
|
||||
} from '../../utils/recipients';
|
||||
import { getRecipientsWithMissingFields, isRecipientEmailValidForSending } from '../../utils/recipients';
|
||||
import { getEnvelopeWhereInput } from '../envelope/get-envelope-by-id';
|
||||
import { insertFormValuesInPdf } from '../pdf/insert-form-values-in-pdf';
|
||||
import { triggerWebhook } from '../webhooks/trigger/trigger-webhook';
|
||||
@@ -56,13 +49,7 @@ export type SendDocumentOptions = {
|
||||
requestMetadata: ApiRequestMetadata;
|
||||
};
|
||||
|
||||
export const sendDocument = async ({
|
||||
id,
|
||||
userId,
|
||||
teamId,
|
||||
sendEmail,
|
||||
requestMetadata,
|
||||
}: SendDocumentOptions) => {
|
||||
export const sendDocument = async ({ id, userId, teamId, sendEmail, requestMetadata }: SendDocumentOptions) => {
|
||||
const { envelopeWhereInput } = await getEnvelopeWhereInput({
|
||||
id,
|
||||
type: EnvelopeType.DOCUMENT,
|
||||
@@ -156,10 +143,7 @@ export const sendDocument = async ({
|
||||
});
|
||||
|
||||
// Validate that recipients who require fields (e.g., signers need signature fields) have them.
|
||||
const recipientsWithMissingFields = getRecipientsWithMissingFields(
|
||||
envelope.recipients,
|
||||
envelope.fields,
|
||||
);
|
||||
const recipientsWithMissingFields = getRecipientsWithMissingFields(envelope.recipients, envelope.fields);
|
||||
|
||||
if (recipientsWithMissingFields.length > 0) {
|
||||
const missingRecipientDescriptions = recipientsWithMissingFields
|
||||
@@ -172,8 +156,7 @@ export const sendDocument = async ({
|
||||
}
|
||||
|
||||
const allRecipientsHaveNoActionToTake = envelope.recipients.every(
|
||||
(recipient) =>
|
||||
recipient.role === RecipientRole.CC || recipient.signingStatus === SigningStatus.SIGNED,
|
||||
(recipient) => recipient.role === RecipientRole.CC || recipient.signingStatus === SigningStatus.SIGNED,
|
||||
);
|
||||
|
||||
if (allRecipientsHaveNoActionToTake) {
|
||||
@@ -456,11 +439,7 @@ export const extractFieldAutoInsertValues = (
|
||||
|
||||
// Auto insert checkbox fields with the pre-checked values.
|
||||
if (field.type === FieldType.CHECKBOX) {
|
||||
const {
|
||||
values = [],
|
||||
validationRule,
|
||||
validationLength,
|
||||
} = ZCheckboxFieldMeta.parse(field.fieldMeta);
|
||||
const { values = [], validationRule, validationLength } = ZCheckboxFieldMeta.parse(field.fieldMeta);
|
||||
|
||||
const checkedIndices: number[] = [];
|
||||
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
import { createElement } from 'react';
|
||||
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import { EnvelopeType } from '@prisma/client';
|
||||
|
||||
import { mailer } from '@documenso/email/mailer';
|
||||
import { DocumentPendingEmailTemplate } from '@documenso/email/templates/document-pending';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import { EnvelopeType } from '@prisma/client';
|
||||
import { createElement } from 'react';
|
||||
|
||||
import { getI18nInstance } from '../../client-only/providers/i18n-server';
|
||||
import { NEXT_PUBLIC_WEBAPP_URL } from '../../constants/app';
|
||||
@@ -58,9 +56,7 @@ export const sendPendingEmail = async ({ id, recipientId }: SendPendingEmailOpti
|
||||
meta: envelope.documentMeta,
|
||||
});
|
||||
|
||||
const isDocumentPendingEmailEnabled = extractDerivedDocumentEmailSettings(
|
||||
envelope.documentMeta,
|
||||
).documentPending;
|
||||
const isDocumentPendingEmailEnabled = extractDerivedDocumentEmailSettings(envelope.documentMeta).documentPending;
|
||||
|
||||
if (!isDocumentPendingEmailEnabled) {
|
||||
return;
|
||||
|
||||
@@ -1,15 +1,11 @@
|
||||
import { EnvelopeType, ReadStatus, SendStatus, WebhookTriggerEvents } from '@prisma/client';
|
||||
|
||||
import { DOCUMENT_AUDIT_LOG_TYPE } from '@documenso/lib/types/document-audit-logs';
|
||||
import type { RequestMetadata } from '@documenso/lib/universal/extract-request-metadata';
|
||||
import { createDocumentAuditLogData } from '@documenso/lib/utils/document-audit-logs';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import { EnvelopeType, ReadStatus, SendStatus, WebhookTriggerEvents } from '@prisma/client';
|
||||
|
||||
import type { TDocumentAccessAuthTypes } from '../../types/document-auth';
|
||||
import {
|
||||
ZWebhookDocumentSchema,
|
||||
mapEnvelopeToWebhookDocumentPayload,
|
||||
} from '../../types/webhook-payload';
|
||||
import { mapEnvelopeToWebhookDocumentPayload, ZWebhookDocumentSchema } from '../../types/webhook-payload';
|
||||
import { triggerWebhook } from '../webhooks/trigger/trigger-webhook';
|
||||
|
||||
export type ViewedDocumentOptions = {
|
||||
@@ -18,11 +14,7 @@ export type ViewedDocumentOptions = {
|
||||
requestMetadata?: RequestMetadata;
|
||||
};
|
||||
|
||||
export const viewedDocument = async ({
|
||||
token,
|
||||
recipientAccessAuth,
|
||||
requestMetadata,
|
||||
}: ViewedDocumentOptions) => {
|
||||
export const viewedDocument = async ({ token, recipientAccessAuth, requestMetadata }: ViewedDocumentOptions) => {
|
||||
const recipient = await prisma.recipient.findFirst({
|
||||
where: {
|
||||
token,
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import { P, match } from 'ts-pattern';
|
||||
|
||||
import type { BrandingSettings } from '@documenso/email/providers/branding';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import type {
|
||||
@@ -9,11 +7,8 @@ import type {
|
||||
OrganisationEmail,
|
||||
OrganisationType,
|
||||
} from '@documenso/prisma/client';
|
||||
import {
|
||||
EmailDomainStatus,
|
||||
type OrganisationClaim,
|
||||
type OrganisationGlobalSettings,
|
||||
} from '@documenso/prisma/client';
|
||||
import { EmailDomainStatus, type OrganisationClaim, type OrganisationGlobalSettings } from '@documenso/prisma/client';
|
||||
import { match, P } from 'ts-pattern';
|
||||
|
||||
import { DOCUMENSO_INTERNAL_EMAIL } from '../../constants/email';
|
||||
import { AppError, AppErrorCode } from '../../errors/app-error';
|
||||
@@ -80,9 +75,7 @@ type EmailContextResponse = {
|
||||
emailLanguage: string;
|
||||
};
|
||||
|
||||
export const getEmailContext = async (
|
||||
options: GetEmailContextOptions,
|
||||
): Promise<EmailContextResponse> => {
|
||||
export const getEmailContext = async (options: GetEmailContextOptions): Promise<EmailContextResponse> => {
|
||||
const { source, meta } = options;
|
||||
|
||||
let emailContext: Omit<EmailContextResponse, 'senderEmail' | 'replyToEmail' | 'emailLanguage'>;
|
||||
@@ -208,18 +201,11 @@ const handleTeamEmailContext = async (teamId: number) => {
|
||||
|
||||
const allowedEmails = getAllowedEmails(organisation);
|
||||
|
||||
const teamSettings = extractDerivedTeamSettings(
|
||||
organisation.organisationGlobalSettings,
|
||||
team.teamGlobalSettings,
|
||||
);
|
||||
const teamSettings = extractDerivedTeamSettings(organisation.organisationGlobalSettings, team.teamGlobalSettings);
|
||||
|
||||
return {
|
||||
allowedEmails,
|
||||
branding: teamGlobalSettingsToBranding(
|
||||
teamSettings,
|
||||
teamId,
|
||||
claims.flags.hidePoweredBy ?? false,
|
||||
),
|
||||
branding: teamGlobalSettingsToBranding(teamSettings, teamId, claims.flags.hidePoweredBy ?? false),
|
||||
settings: teamSettings,
|
||||
claims,
|
||||
organisationType: organisation.type,
|
||||
|
||||
@@ -32,8 +32,7 @@ export const createEmbeddingPresignToken = async ({
|
||||
const minExpirationMinutes = isDevelopment ? 0 : 5;
|
||||
|
||||
// Ensure expiresIn is at least the minimum allowed value
|
||||
const effectiveExpiresIn =
|
||||
expiresIn !== undefined && expiresIn >= minExpirationMinutes ? expiresIn : 60; // Default to 1 hour if not specified or below minimum
|
||||
const effectiveExpiresIn = expiresIn !== undefined && expiresIn >= minExpirationMinutes ? expiresIn : 60; // Default to 1 hour if not specified or below minimum
|
||||
|
||||
const expiresAt = now.plus({ minutes: effectiveExpiresIn });
|
||||
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import type { JWTPayload } from 'jose';
|
||||
import { decodeJwt, jwtVerify } from 'jose';
|
||||
|
||||
import { prisma } from '@documenso/prisma';
|
||||
|
||||
import { AppError, AppErrorCode } from '../../errors/app-error';
|
||||
|
||||
export type VerifyEmbeddingPresignTokenOptions = {
|
||||
@@ -10,10 +9,7 @@ export type VerifyEmbeddingPresignTokenOptions = {
|
||||
scope?: string;
|
||||
};
|
||||
|
||||
export const verifyEmbeddingPresignToken = async ({
|
||||
token,
|
||||
scope,
|
||||
}: VerifyEmbeddingPresignTokenOptions) => {
|
||||
export const verifyEmbeddingPresignToken = async ({ token, scope }: VerifyEmbeddingPresignTokenOptions) => {
|
||||
// First decode the JWT to get the claims without verification
|
||||
let decodedToken: JWTPayload;
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { DocumentStatus } from '@prisma/client';
|
||||
|
||||
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import { DocumentStatus } from '@prisma/client';
|
||||
|
||||
import { buildTeamWhereQuery } from '../../utils/teams';
|
||||
|
||||
@@ -15,12 +14,7 @@ export type CreateAttachmentOptions = {
|
||||
};
|
||||
};
|
||||
|
||||
export const createAttachment = async ({
|
||||
envelopeId,
|
||||
teamId,
|
||||
userId,
|
||||
data,
|
||||
}: CreateAttachmentOptions) => {
|
||||
export const createAttachment = async ({ envelopeId, teamId, userId, data }: CreateAttachmentOptions) => {
|
||||
const envelope = await prisma.envelope.findFirst({
|
||||
where: {
|
||||
id: envelopeId,
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { DocumentStatus } from '@prisma/client';
|
||||
|
||||
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import { DocumentStatus } from '@prisma/client';
|
||||
|
||||
import { buildTeamWhereQuery } from '../../utils/teams';
|
||||
|
||||
|
||||
@@ -6,10 +6,7 @@ export type FindAttachmentsByTokenOptions = {
|
||||
token: string;
|
||||
};
|
||||
|
||||
export const findAttachmentsByToken = async ({
|
||||
envelopeId,
|
||||
token,
|
||||
}: FindAttachmentsByTokenOptions) => {
|
||||
export const findAttachmentsByToken = async ({ envelopeId, token }: FindAttachmentsByTokenOptions) => {
|
||||
const envelope = await prisma.envelope.findFirst({
|
||||
where: {
|
||||
id: envelopeId,
|
||||
@@ -42,10 +39,7 @@ export type FindAttachmentsByTeamOptions = {
|
||||
teamId: number;
|
||||
};
|
||||
|
||||
export const findAttachmentsByTeam = async ({
|
||||
envelopeId,
|
||||
teamId,
|
||||
}: FindAttachmentsByTeamOptions) => {
|
||||
export const findAttachmentsByTeam = async ({ envelopeId, teamId }: FindAttachmentsByTeamOptions) => {
|
||||
const envelope = await prisma.envelope.findFirst({
|
||||
where: {
|
||||
id: envelopeId,
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { DocumentStatus } from '@prisma/client';
|
||||
|
||||
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import { DocumentStatus } from '@prisma/client';
|
||||
|
||||
import { buildTeamWhereQuery } from '../../utils/teams';
|
||||
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import type { Envelope, EnvelopeItem, Recipient } from '@prisma/client';
|
||||
|
||||
import {
|
||||
convertPlaceholdersToFieldInputs,
|
||||
extractPdfPlaceholders,
|
||||
@@ -13,6 +11,7 @@ import { prefixedId } from '@documenso/lib/universal/id';
|
||||
import { putPdfFileServerSide } from '@documenso/lib/universal/upload/put-file.server';
|
||||
import { createDocumentAuditLogData } from '@documenso/lib/utils/document-audit-logs';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import type { Envelope, EnvelopeItem, Recipient } from '@prisma/client';
|
||||
|
||||
type UnsafeCreateEnvelopeItemsOptions = {
|
||||
files: {
|
||||
@@ -43,8 +42,7 @@ export const UNSAFE_createEnvelopeItems = async ({
|
||||
user,
|
||||
apiRequestMetadata,
|
||||
}: UnsafeCreateEnvelopeItemsOptions) => {
|
||||
const currentHighestOrderValue =
|
||||
envelope.envelopeItems[envelope.envelopeItems.length - 1]?.order ?? 1;
|
||||
const currentHighestOrderValue = envelope.envelopeItems[envelope.envelopeItems.length - 1]?.order ?? 1;
|
||||
|
||||
// For each file: normalize, extract & clean placeholders, then upload.
|
||||
const envelopeItemsToCreate = await Promise.all(
|
||||
@@ -128,9 +126,7 @@ export const UNSAFE_createEnvelopeItems = async ({
|
||||
continue;
|
||||
}
|
||||
|
||||
const createdItem = createdItems.find(
|
||||
(ci) => ci.documentDataId === uploadedItem.documentDataId,
|
||||
);
|
||||
const createdItem = createdItems.find((ci) => ci.documentDataId === uploadedItem.documentDataId);
|
||||
|
||||
if (!createdItem) {
|
||||
continue;
|
||||
@@ -139,12 +135,7 @@ export const UNSAFE_createEnvelopeItems = async ({
|
||||
const fieldsToCreate = convertPlaceholdersToFieldInputs(
|
||||
uploadedItem.placeholders,
|
||||
(recipientPlaceholder, placeholder) =>
|
||||
findRecipientByPlaceholder(
|
||||
recipientPlaceholder,
|
||||
placeholder,
|
||||
orderedRecipients,
|
||||
orderedRecipients,
|
||||
),
|
||||
findRecipientByPlaceholder(recipientPlaceholder, placeholder, orderedRecipients, orderedRecipients),
|
||||
createdItem.id,
|
||||
);
|
||||
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
import type { Envelope, Field, Recipient } from '@prisma/client';
|
||||
|
||||
import { normalizePdf } 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 { putPdfFileServerSide } from '@documenso/lib/universal/upload/put-file.server';
|
||||
import { createDocumentAuditLogData } from '@documenso/lib/utils/document-audit-logs';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import type { Envelope, Field, Recipient } from '@prisma/client';
|
||||
|
||||
import { convertPlaceholdersToFieldInputs, extractPdfPlaceholders } from '../pdf/auto-place-fields';
|
||||
import { findRecipientByPlaceholder } from '../pdf/helpers';
|
||||
@@ -154,12 +153,7 @@ export const UNSAFE_replaceEnvelopeItemPdf = async ({
|
||||
const fieldsToCreate = convertPlaceholdersToFieldInputs(
|
||||
placeholders,
|
||||
(recipientPlaceholder, placeholder) =>
|
||||
findRecipientByPlaceholder(
|
||||
recipientPlaceholder,
|
||||
placeholder,
|
||||
orderedRecipients,
|
||||
orderedRecipients,
|
||||
),
|
||||
findRecipientByPlaceholder(recipientPlaceholder, placeholder, orderedRecipients, orderedRecipients),
|
||||
updatedItem.id,
|
||||
);
|
||||
|
||||
@@ -211,7 +205,7 @@ export const UNSAFE_replaceEnvelopeItemPdf = async ({
|
||||
},
|
||||
});
|
||||
|
||||
let fields: Field[] | undefined = undefined;
|
||||
let fields: Field[] | undefined;
|
||||
|
||||
if (didFieldsChange) {
|
||||
try {
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import type { EnvelopeItem, EnvelopeType } from '@prisma/client';
|
||||
|
||||
import { DOCUMENT_AUDIT_LOG_TYPE } from '@documenso/lib/types/document-audit-logs';
|
||||
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 { EnvelopeItem, EnvelopeType } from '@prisma/client';
|
||||
|
||||
type UnsafeUpdateEnvelopeItemsOptions = {
|
||||
envelopeId: string;
|
||||
|
||||
@@ -1,14 +1,3 @@
|
||||
import type { DocumentMeta, DocumentVisibility, TemplateType } from '@prisma/client';
|
||||
import {
|
||||
DocumentSource,
|
||||
EnvelopeType,
|
||||
FolderType,
|
||||
RecipientRole,
|
||||
SendStatus,
|
||||
SigningStatus,
|
||||
WebhookTriggerEvents,
|
||||
} from '@prisma/client';
|
||||
|
||||
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
||||
import type { PlaceholderInfo } from '@documenso/lib/server-only/pdf/auto-place-fields';
|
||||
import { convertPlaceholdersToFieldInputs } from '@documenso/lib/server-only/pdf/auto-place-fields';
|
||||
@@ -20,6 +9,16 @@ import type { ApiRequestMetadata } from '@documenso/lib/universal/extract-reques
|
||||
import { nanoid, prefixedId } from '@documenso/lib/universal/id';
|
||||
import { createDocumentAuditLogData } from '@documenso/lib/utils/document-audit-logs';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import type { DocumentMeta, DocumentVisibility, TemplateType } from '@prisma/client';
|
||||
import {
|
||||
DocumentSource,
|
||||
EnvelopeType,
|
||||
FolderType,
|
||||
RecipientRole,
|
||||
SendStatus,
|
||||
SigningStatus,
|
||||
WebhookTriggerEvents,
|
||||
} from '@prisma/client';
|
||||
|
||||
import type {
|
||||
TDocumentAccessAuthTypes,
|
||||
@@ -30,10 +29,7 @@ import type {
|
||||
import type { TDocumentFormValues } from '../../types/document-form-values';
|
||||
import type { TEnvelopeAttachmentType } from '../../types/envelope-attachment';
|
||||
import type { TFieldAndMeta } from '../../types/field-meta';
|
||||
import {
|
||||
ZWebhookDocumentSchema,
|
||||
mapEnvelopeToWebhookDocumentPayload,
|
||||
} from '../../types/webhook-payload';
|
||||
import { mapEnvelopeToWebhookDocumentPayload, ZWebhookDocumentSchema } from '../../types/webhook-payload';
|
||||
import { getFileServerSide } from '../../universal/upload/get-file.server';
|
||||
import { putPdfFileServerSide } from '../../universal/upload/put-file.server';
|
||||
import { extractDerivedDocumentMeta } from '../../utils/document';
|
||||
@@ -266,11 +262,7 @@ export const createEnvelope = async ({
|
||||
const timezoneToUse = meta?.timezone || settings.documentTimezone || userTimezone;
|
||||
|
||||
const getValidatedDelegatedOwner = async () => {
|
||||
if (
|
||||
!settings.delegateDocumentOwnership ||
|
||||
!delegatedDocumentOwner ||
|
||||
requestMetadata.source === 'app'
|
||||
) {
|
||||
if (!settings.delegateDocumentOwnership || !delegatedDocumentOwner || requestMetadata.source === 'app') {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -368,13 +360,11 @@ export const createEnvelope = async ({
|
||||
? ZDefaultRecipientsSchema.parse(settings.defaultRecipients)
|
||||
: [];
|
||||
|
||||
const mappedDefaultRecipients: CreateEnvelopeRecipientOptions[] = defaultRecipients.map(
|
||||
(recipient) => ({
|
||||
email: recipient.email,
|
||||
name: recipient.name,
|
||||
role: recipient.role,
|
||||
}),
|
||||
);
|
||||
const mappedDefaultRecipients: CreateEnvelopeRecipientOptions[] = defaultRecipients.map((recipient) => ({
|
||||
email: recipient.email,
|
||||
name: recipient.name,
|
||||
role: recipient.role,
|
||||
}));
|
||||
|
||||
const allRecipients = [...(data.recipients || []), ...mappedDefaultRecipients];
|
||||
|
||||
@@ -426,8 +416,7 @@ export const createEnvelope = async ({
|
||||
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,
|
||||
signingStatus: recipient.role === RecipientRole.CC ? SigningStatus.SIGNED : SigningStatus.NOT_SIGNED,
|
||||
authOptions: recipientAuthOptions,
|
||||
fields: {
|
||||
createMany: {
|
||||
@@ -440,9 +429,7 @@ export const createEnvelope = async ({
|
||||
);
|
||||
|
||||
// Create fields from PDF placeholders (extracted at upload time).
|
||||
const itemsWithPlaceholders = envelopeItems.filter(
|
||||
(item) => item.placeholders && item.placeholders.length > 0,
|
||||
);
|
||||
const itemsWithPlaceholders = envelopeItems.filter((item) => item.placeholders && item.placeholders.length > 0);
|
||||
|
||||
if (itemsWithPlaceholders.length > 0) {
|
||||
// Collect all unique recipient placeholder references (e.g. "r1", "r2").
|
||||
@@ -472,23 +459,18 @@ export const createEnvelope = async ({
|
||||
|
||||
// If recipients were not provided, create placeholder recipients even when defaults exist.
|
||||
if (shouldCreatePlaceholderRecipients) {
|
||||
const existingRecipientEmails = new Set(
|
||||
availableRecipients.map((recipient) => recipient.email.toLowerCase()),
|
||||
);
|
||||
const existingRecipientEmails = new Set(availableRecipients.map((recipient) => recipient.email.toLowerCase()));
|
||||
|
||||
const placeholderRecipients = Array.from(
|
||||
uniqueRecipientRefs.entries(),
|
||||
([recipientIndex, name]) => ({
|
||||
envelopeId: envelope.id,
|
||||
email: `recipient.${recipientIndex}@documenso.com`,
|
||||
name,
|
||||
role: RecipientRole.SIGNER,
|
||||
signingOrder: recipientIndex,
|
||||
token: nanoid(),
|
||||
sendStatus: SendStatus.NOT_SENT,
|
||||
signingStatus: SigningStatus.NOT_SIGNED,
|
||||
}),
|
||||
).filter((recipient) => !existingRecipientEmails.has(recipient.email.toLowerCase()));
|
||||
const placeholderRecipients = Array.from(uniqueRecipientRefs.entries(), ([recipientIndex, name]) => ({
|
||||
envelopeId: envelope.id,
|
||||
email: `recipient.${recipientIndex}@documenso.com`,
|
||||
name,
|
||||
role: RecipientRole.SIGNER,
|
||||
signingOrder: recipientIndex,
|
||||
token: nanoid(),
|
||||
sendStatus: SendStatus.NOT_SENT,
|
||||
signingStatus: SigningStatus.NOT_SIGNED,
|
||||
})).filter((recipient) => !existingRecipientEmails.has(recipient.email.toLowerCase()));
|
||||
|
||||
if (placeholderRecipients.length > 0) {
|
||||
await tx.recipient.createMany({
|
||||
@@ -504,9 +486,7 @@ export const createEnvelope = async ({
|
||||
}
|
||||
|
||||
for (const item of itemsWithPlaceholders) {
|
||||
const envelopeItem = envelope.envelopeItems.find(
|
||||
(ei) => ei.documentDataId === item.documentDataId,
|
||||
);
|
||||
const envelopeItem = envelope.envelopeItems.find((ei) => ei.documentDataId === item.documentDataId);
|
||||
|
||||
if (!envelopeItem) {
|
||||
continue;
|
||||
|
||||
@@ -1,14 +1,10 @@
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import { DocumentSource, EnvelopeType, WebhookTriggerEvents } from '@prisma/client';
|
||||
import pMap from 'p-map';
|
||||
import { omit } from 'remeda';
|
||||
|
||||
import { prisma } from '@documenso/prisma';
|
||||
|
||||
import { AppError, AppErrorCode } from '../../errors/app-error';
|
||||
import {
|
||||
ZWebhookDocumentSchema,
|
||||
mapEnvelopeToWebhookDocumentPayload,
|
||||
} from '../../types/webhook-payload';
|
||||
import { mapEnvelopeToWebhookDocumentPayload, ZWebhookDocumentSchema } from '../../types/webhook-payload';
|
||||
import { nanoid, prefixedId } from '../../universal/id';
|
||||
import type { EnvelopeIdOptions } from '../../utils/envelope';
|
||||
import { getEnvelopeWhereInput } from '../envelope/get-envelope-by-id';
|
||||
@@ -26,17 +22,8 @@ export interface DuplicateEnvelopeOptions {
|
||||
};
|
||||
}
|
||||
|
||||
export const duplicateEnvelope = async ({
|
||||
id,
|
||||
userId,
|
||||
teamId,
|
||||
overrides,
|
||||
}: DuplicateEnvelopeOptions) => {
|
||||
const {
|
||||
duplicateAsTemplate = false,
|
||||
includeRecipients = true,
|
||||
includeFields = true,
|
||||
} = overrides ?? {};
|
||||
export const duplicateEnvelope = async ({ id, userId, teamId, overrides }: DuplicateEnvelopeOptions) => {
|
||||
const { duplicateAsTemplate = false, includeRecipients = true, includeFields = true } = overrides ?? {};
|
||||
|
||||
const { envelopeWhereInput } = await getEnvelopeWhereInput({
|
||||
id,
|
||||
@@ -135,8 +122,7 @@ export const duplicateEnvelope = async ({
|
||||
templateType: duplicatedTemplateType,
|
||||
publicTitle: envelope.publicTitle ?? undefined,
|
||||
publicDescription: envelope.publicDescription ?? undefined,
|
||||
source:
|
||||
targetType === EnvelopeType.DOCUMENT ? DocumentSource.DOCUMENT : DocumentSource.TEMPLATE,
|
||||
source: targetType === EnvelopeType.DOCUMENT ? DocumentSource.DOCUMENT : DocumentSource.TEMPLATE,
|
||||
},
|
||||
include: {
|
||||
recipients: true,
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import type { DocumentSource, DocumentStatus, Envelope, EnvelopeType } from '@prisma/client';
|
||||
import type { Expression, ExpressionBuilder, SelectQueryBuilder, SqlBool } from 'kysely';
|
||||
|
||||
import { kyselyPrisma, prisma, sql } from '@documenso/prisma';
|
||||
import type { DB } from '@documenso/prisma/generated/types';
|
||||
import type { DocumentSource, DocumentStatus, Envelope, EnvelopeType } from '@prisma/client';
|
||||
import type { Expression, ExpressionBuilder, SelectQueryBuilder, SqlBool } from 'kysely';
|
||||
|
||||
import { TEAM_DOCUMENT_VISIBILITY_MAP } from '../../constants/teams';
|
||||
import type { FindResultResponse } from '../../types/search-params';
|
||||
@@ -133,9 +132,7 @@ export const findEnvelopes = async ({
|
||||
|
||||
// Folder filter
|
||||
qb =
|
||||
folderId !== undefined
|
||||
? qb.where('Envelope.folderId', '=', folderId)
|
||||
: qb.where('Envelope.folderId', 'is', null);
|
||||
folderId !== undefined ? qb.where('Envelope.folderId', '=', folderId) : qb.where('Envelope.folderId', 'is', null);
|
||||
|
||||
// Exclude soft-deleted envelopes
|
||||
qb = qb.where('Envelope.deletedAt', 'is', null);
|
||||
@@ -223,10 +220,7 @@ export const findEnvelopes = async ({
|
||||
|
||||
const offset = Math.max(page - 1, 0) * perPage;
|
||||
|
||||
const dataQuery = qb
|
||||
.orderBy(`Envelope.${orderByColumn}`, orderByDirection)
|
||||
.limit(perPage)
|
||||
.offset(offset);
|
||||
const dataQuery = qb.orderBy(`Envelope.${orderByColumn}`, orderByDirection).limit(perPage).offset(offset);
|
||||
|
||||
// Count query: either windowed (fast, capped) or full (exact, for API consumers).
|
||||
const baseCountQuery = qb.clearSelect().select('Envelope.id');
|
||||
@@ -239,10 +233,7 @@ export const findEnvelopes = async ({
|
||||
.selectFrom(baseCountQuery.as('filtered'))
|
||||
.select(({ fn }) => fn.count<number>('id').as('total'));
|
||||
|
||||
const [dataResult, countResult] = await Promise.all([
|
||||
dataQuery.execute(),
|
||||
countQuery.executeTakeFirstOrThrow(),
|
||||
]);
|
||||
const [dataResult, countResult] = await Promise.all([dataQuery.execute(), countQuery.executeTakeFirstOrThrow()]);
|
||||
|
||||
const ids = dataResult.map((row) => row.id);
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import type { EnvelopeType } from '@prisma/client';
|
||||
|
||||
import { getEnvelopeWhereInput } from '@documenso/lib/server-only/envelope/get-envelope-by-id';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import type { EnvelopeType } from '@prisma/client';
|
||||
|
||||
import { AppError, AppErrorCode } from '../../errors/app-error';
|
||||
import type { EnvelopeIdOptions } from '../../utils/envelope';
|
||||
@@ -27,12 +26,7 @@ export type GetEditorEnvelopeByIdOptions = {
|
||||
type: EnvelopeType | null;
|
||||
};
|
||||
|
||||
export const getEditorEnvelopeById = async ({
|
||||
id,
|
||||
userId,
|
||||
teamId,
|
||||
type,
|
||||
}: GetEditorEnvelopeByIdOptions) => {
|
||||
export const getEditorEnvelopeById = async ({ id, userId, teamId, type }: GetEditorEnvelopeByIdOptions) => {
|
||||
const { envelopeWhereInput } = await getEnvelopeWhereInput({
|
||||
id,
|
||||
userId,
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import type { EnvelopeType, Prisma } from '@prisma/client';
|
||||
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import type { EnvelopeType, Prisma } from '@prisma/client';
|
||||
|
||||
import { TEAM_DOCUMENT_VISIBILITY_MAP } from '../../constants/teams';
|
||||
import { AppError, AppErrorCode } from '../../errors/app-error';
|
||||
@@ -126,12 +125,7 @@ export type GetEnvelopeWhereInputOptions = {
|
||||
*
|
||||
* NOTE: Be extremely careful when modifying this function. Needs at minimum two reviewers to approve any changes.
|
||||
*/
|
||||
export const getEnvelopeWhereInput = async ({
|
||||
id,
|
||||
userId,
|
||||
teamId,
|
||||
type,
|
||||
}: GetEnvelopeWhereInputOptions) => {
|
||||
export const getEnvelopeWhereInput = async ({ id, userId, teamId, type }: GetEnvelopeWhereInputOptions) => {
|
||||
// Backup validation incase something goes wrong.
|
||||
if (!id.id || !userId || !teamId || type === undefined) {
|
||||
console.error(`[CRTICAL ERROR]: MUST NEVER HAPPEN`);
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import { DocumentStatus, EnvelopeType } from '@prisma/client';
|
||||
import { match } from 'ts-pattern';
|
||||
|
||||
import { prisma } from '@documenso/prisma';
|
||||
|
||||
import { AppError, AppErrorCode } from '../../errors/app-error';
|
||||
import { DocumentAccessAuth, type TDocumentAuthMethods } from '../../types/document-auth';
|
||||
import { extractDocumentAuthMethods } from '../../utils/document-auth';
|
||||
@@ -84,9 +83,7 @@ export const getEnvelopeForDirectTemplateSigning = async ({
|
||||
},
|
||||
});
|
||||
|
||||
const recipient = (envelope?.recipients || []).find(
|
||||
(r) => r.id === envelope?.directLink?.directTemplateRecipientId,
|
||||
);
|
||||
const recipient = (envelope?.recipients || []).find((r) => r.id === envelope?.directLink?.directTemplateRecipientId);
|
||||
|
||||
if (!envelope || !recipient) {
|
||||
throw new AppError(AppErrorCode.NOT_FOUND, {
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
import { DocumentSigningOrder, DocumentStatus, EnvelopeType, SigningStatus } from '@prisma/client';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import DocumentMetaSchema from '@documenso/prisma/generated/zod/modelSchema/DocumentMetaSchema';
|
||||
import EnvelopeItemSchema from '@documenso/prisma/generated/zod/modelSchema/EnvelopeItemSchema';
|
||||
@@ -8,6 +5,8 @@ import EnvelopeSchema from '@documenso/prisma/generated/zod/modelSchema/Envelope
|
||||
import SignatureSchema from '@documenso/prisma/generated/zod/modelSchema/SignatureSchema';
|
||||
import TeamSchema from '@documenso/prisma/generated/zod/modelSchema/TeamSchema';
|
||||
import UserSchema from '@documenso/prisma/generated/zod/modelSchema/UserSchema';
|
||||
import { DocumentSigningOrder, DocumentStatus, EnvelopeType, SigningStatus } from '@prisma/client';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { AppError, AppErrorCode } from '../../errors/app-error';
|
||||
import type { TDocumentAuthMethods } from '../../types/document-auth';
|
||||
@@ -264,10 +263,7 @@ export const getEnvelopeForRecipientSigning = async ({
|
||||
|
||||
const currentRecipientIndex = envelope.recipients.findIndex((r) => r.token === token);
|
||||
|
||||
if (
|
||||
envelope.documentMeta.signingOrder === DocumentSigningOrder.SEQUENTIAL &&
|
||||
currentRecipientIndex !== -1
|
||||
) {
|
||||
if (envelope.documentMeta.signingOrder === DocumentSigningOrder.SEQUENTIAL && currentRecipientIndex !== -1) {
|
||||
for (let i = 0; i < currentRecipientIndex; i++) {
|
||||
if (envelope.recipients[i].signingStatus !== SigningStatus.SIGNED) {
|
||||
isRecipientsTurn = false;
|
||||
@@ -291,12 +287,8 @@ export const getEnvelopeForRecipientSigning = async ({
|
||||
recipient,
|
||||
recipientSignature,
|
||||
isRecipientsTurn,
|
||||
isCompleted:
|
||||
recipient.signingStatus === SigningStatus.SIGNED ||
|
||||
envelope.status === DocumentStatus.COMPLETED,
|
||||
isRejected:
|
||||
recipient.signingStatus === SigningStatus.REJECTED ||
|
||||
envelope.status === DocumentStatus.REJECTED,
|
||||
isCompleted: recipient.signingStatus === SigningStatus.SIGNED || envelope.status === DocumentStatus.COMPLETED,
|
||||
isRejected: recipient.signingStatus === SigningStatus.REJECTED || envelope.status === DocumentStatus.REJECTED,
|
||||
isExpired: isRecipientExpired(recipient),
|
||||
sender,
|
||||
settings: {
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { DocumentStatus, EnvelopeType } from '@prisma/client';
|
||||
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import { DocumentStatus, EnvelopeType } from '@prisma/client';
|
||||
|
||||
import { AppError, AppErrorCode } from '../../errors/app-error';
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import type { EnvelopeType, Prisma } from '@prisma/client';
|
||||
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import type { EnvelopeType, Prisma } from '@prisma/client';
|
||||
|
||||
import { TEAM_DOCUMENT_VISIBILITY_MAP } from '../../constants/teams';
|
||||
import { AppError, AppErrorCode } from '../../errors/app-error';
|
||||
@@ -42,12 +41,7 @@ export type GetEnvelopesByIdsOptions = {
|
||||
*
|
||||
* NOTE: Be extremely careful when modifying this function. Needs at minimum two reviewers to approve any changes.
|
||||
*/
|
||||
export const getEnvelopesByIds = async ({
|
||||
ids,
|
||||
userId,
|
||||
teamId,
|
||||
type,
|
||||
}: GetEnvelopesByIdsOptions) => {
|
||||
export const getEnvelopesByIds = async ({ ids, userId, teamId, type }: GetEnvelopesByIdsOptions) => {
|
||||
const { envelopeWhereInput } = await getMultipleEnvelopeWhereInput({
|
||||
ids,
|
||||
userId,
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { DocumentStatus, EnvelopeType } from '@prisma/client';
|
||||
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import { DocumentStatus, EnvelopeType } from '@prisma/client';
|
||||
|
||||
import { deletedAccountServiceAccount } from '../user/service-accounts/deleted-account';
|
||||
|
||||
|
||||
@@ -5,10 +5,7 @@ export type TransferTeamEnvelopesOptions = {
|
||||
targetTeamId: number;
|
||||
};
|
||||
|
||||
export const transferTeamEnvelopes = async ({
|
||||
sourceTeamId,
|
||||
targetTeamId,
|
||||
}: TransferTeamEnvelopesOptions) => {
|
||||
export const transferTeamEnvelopes = async ({ sourceTeamId, targetTeamId }: TransferTeamEnvelopesOptions) => {
|
||||
await prisma.envelope.updateMany({
|
||||
where: {
|
||||
teamId: sourceTeamId,
|
||||
|
||||
@@ -1,20 +1,16 @@
|
||||
import type { DocumentMeta, DocumentVisibility, Prisma, TemplateType } from '@prisma/client';
|
||||
import { DocumentStatus, EnvelopeType, FolderType, WebhookTriggerEvents } from '@prisma/client';
|
||||
import { isDeepEqual } from 'remeda';
|
||||
|
||||
import { DOCUMENT_AUDIT_LOG_TYPE } from '@documenso/lib/types/document-audit-logs';
|
||||
import type { ApiRequestMetadata } from '@documenso/lib/universal/extract-request-metadata';
|
||||
import type { CreateDocumentAuditLogDataResponse } from '@documenso/lib/utils/document-audit-logs';
|
||||
import { createDocumentAuditLogData } from '@documenso/lib/utils/document-audit-logs';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import type { DocumentMeta, DocumentVisibility, Prisma, TemplateType } from '@prisma/client';
|
||||
import { DocumentStatus, EnvelopeType, FolderType, WebhookTriggerEvents } from '@prisma/client';
|
||||
import { isDeepEqual } from 'remeda';
|
||||
|
||||
import { TEAM_DOCUMENT_VISIBILITY_MAP } from '../../constants/teams';
|
||||
import { AppError, AppErrorCode } from '../../errors/app-error';
|
||||
import type { TDocumentAccessAuthTypes, TDocumentActionAuthTypes } from '../../types/document-auth';
|
||||
import {
|
||||
ZWebhookDocumentSchema,
|
||||
mapEnvelopeToWebhookDocumentPayload,
|
||||
} from '../../types/webhook-payload';
|
||||
import { mapEnvelopeToWebhookDocumentPayload, ZWebhookDocumentSchema } from '../../types/webhook-payload';
|
||||
import { createDocumentAuthOptions, extractDocumentAuthMethods } from '../../utils/document-auth';
|
||||
import type { EnvelopeIdOptions } from '../../utils/envelope';
|
||||
import { buildTeamWhereQuery, canAccessTeamDocument } from '../../utils/teams';
|
||||
@@ -80,10 +76,7 @@ export const updateEnvelope = async ({
|
||||
});
|
||||
}
|
||||
|
||||
if (
|
||||
envelope.type !== EnvelopeType.TEMPLATE &&
|
||||
(data.publicTitle || data.publicDescription || data.templateType)
|
||||
) {
|
||||
if (envelope.type !== EnvelopeType.TEMPLATE && (data.publicTitle || data.publicDescription || data.templateType)) {
|
||||
throw new AppError(AppErrorCode.INVALID_BODY, {
|
||||
message: 'You cannot update the template fields for document type envelopes',
|
||||
});
|
||||
@@ -97,11 +90,7 @@ export const updateEnvelope = async ({
|
||||
const isEnvelopeOwner = envelope.userId === userId;
|
||||
|
||||
// Validate whether the new visibility setting is allowed for the current user.
|
||||
if (
|
||||
!isEnvelopeOwner &&
|
||||
data?.visibility &&
|
||||
!canAccessTeamDocument(team.currentTeamRole, data.visibility)
|
||||
) {
|
||||
if (!isEnvelopeOwner && data?.visibility && !canAccessTeamDocument(team.currentTeamRole, data.visibility)) {
|
||||
throw new AppError(AppErrorCode.UNAUTHORIZED, {
|
||||
message: 'You do not have permission to update the envelope visibility',
|
||||
});
|
||||
@@ -115,10 +104,8 @@ export const updateEnvelope = async ({
|
||||
const documentGlobalActionAuth = documentAuthOption?.globalActionAuth ?? null;
|
||||
|
||||
// If the new global auth values aren't passed in, fallback to the current document values.
|
||||
const newGlobalAccessAuth =
|
||||
data?.globalAccessAuth === undefined ? documentGlobalAccessAuth : data.globalAccessAuth;
|
||||
const newGlobalActionAuth =
|
||||
data?.globalActionAuth === undefined ? documentGlobalActionAuth : data.globalActionAuth;
|
||||
const newGlobalAccessAuth = data?.globalAccessAuth === undefined ? documentGlobalAccessAuth : data.globalAccessAuth;
|
||||
const newGlobalActionAuth = data?.globalActionAuth === undefined ? documentGlobalActionAuth : data.globalActionAuth;
|
||||
|
||||
// Check if user has permission to set the global action auth.
|
||||
if (newGlobalActionAuth.length > 0 && !envelope.team.organisation.organisationClaim.flags.cfr21) {
|
||||
@@ -150,7 +137,7 @@ export const updateEnvelope = async ({
|
||||
}
|
||||
}
|
||||
|
||||
let folderUpdateQuery: Prisma.FolderUpdateOneWithoutEnvelopesNestedInput | undefined = undefined;
|
||||
let folderUpdateQuery: Prisma.FolderUpdateOneWithoutEnvelopesNestedInput | undefined;
|
||||
|
||||
// Validate folder ID.
|
||||
if (data.folderId) {
|
||||
@@ -191,28 +178,19 @@ export const updateEnvelope = async ({
|
||||
const isTitleSame = data.title === undefined || data.title === envelope.title;
|
||||
const isExternalIdSame = data.externalId === undefined || data.externalId === envelope.externalId;
|
||||
const isGlobalAccessSame =
|
||||
documentGlobalAccessAuth === undefined ||
|
||||
isDeepEqual(documentGlobalAccessAuth, newGlobalAccessAuth);
|
||||
documentGlobalAccessAuth === undefined || isDeepEqual(documentGlobalAccessAuth, newGlobalAccessAuth);
|
||||
const isGlobalActionSame =
|
||||
documentGlobalActionAuth === undefined ||
|
||||
isDeepEqual(documentGlobalActionAuth, newGlobalActionAuth);
|
||||
const isDocumentVisibilitySame =
|
||||
data.visibility === undefined || data.visibility === envelope.visibility;
|
||||
documentGlobalActionAuth === undefined || isDeepEqual(documentGlobalActionAuth, newGlobalActionAuth);
|
||||
const isDocumentVisibilitySame = data.visibility === undefined || data.visibility === envelope.visibility;
|
||||
const isFolderSame = data.folderId === undefined || data.folderId === envelope.folderId;
|
||||
const isTemplateTypeSame =
|
||||
data.templateType === undefined || data.templateType === envelope.templateType;
|
||||
const isTemplateTypeSame = data.templateType === undefined || data.templateType === envelope.templateType;
|
||||
const isPublicDescriptionSame =
|
||||
data.publicDescription === undefined || data.publicDescription === envelope.publicDescription;
|
||||
const isPublicTitleSame =
|
||||
data.publicTitle === undefined || data.publicTitle === envelope.publicTitle;
|
||||
const isPublicTitleSame = data.publicTitle === undefined || data.publicTitle === envelope.publicTitle;
|
||||
|
||||
const auditLogs: CreateDocumentAuditLogDataResponse[] = [];
|
||||
|
||||
if (
|
||||
!isTitleSame &&
|
||||
envelope.status !== DocumentStatus.DRAFT &&
|
||||
envelope.status !== DocumentStatus.PENDING
|
||||
) {
|
||||
if (!isTitleSame && envelope.status !== DocumentStatus.DRAFT && envelope.status !== DocumentStatus.PENDING) {
|
||||
throw new AppError(AppErrorCode.INVALID_BODY, {
|
||||
message: 'Envelope title can only be updated while in draft or pending status',
|
||||
});
|
||||
@@ -370,11 +348,7 @@ export const updateEnvelope = async ({
|
||||
}
|
||||
|
||||
// deconstruct to remove the recipients and documentMeta from the returned object since they aren't needed and can be large.
|
||||
const {
|
||||
recipients: _recipients,
|
||||
documentMeta: _documentMeta,
|
||||
...finalEnvelope
|
||||
} = updatedEnvelope;
|
||||
const { recipients: _recipients, documentMeta: _documentMeta, ...finalEnvelope } = updatedEnvelope;
|
||||
|
||||
return finalEnvelope;
|
||||
};
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
import { PDF } from '@libpdf/core';
|
||||
import { EnvelopeType } from '@prisma/client';
|
||||
|
||||
import { DOCUMENT_AUDIT_LOG_TYPE } from '@documenso/lib/types/document-audit-logs';
|
||||
import type { TFieldAndMeta } from '@documenso/lib/types/field-meta';
|
||||
import type { ApiRequestMetadata } from '@documenso/lib/universal/extract-request-metadata';
|
||||
@@ -8,6 +5,8 @@ import { getFileServerSide } from '@documenso/lib/universal/upload/get-file.serv
|
||||
import { putPdfFileServerSide } from '@documenso/lib/universal/upload/put-file.server';
|
||||
import { createDocumentAuditLogData } from '@documenso/lib/utils/document-audit-logs';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import { PDF } from '@libpdf/core';
|
||||
import { EnvelopeType } from '@prisma/client';
|
||||
|
||||
import { AppError, AppErrorCode } from '../../errors/app-error';
|
||||
import type { EnvelopeIdOptions } from '../../utils/envelope';
|
||||
@@ -157,8 +156,7 @@ export const createEnvelopeFields = async ({
|
||||
// Check whether the recipient associated with the field can have new fields created.
|
||||
if (!canRecipientFieldsBeModified(recipient, envelope.fields)) {
|
||||
throw new AppError(AppErrorCode.INVALID_REQUEST, {
|
||||
message:
|
||||
'Recipient type cannot have fields, or they have already interacted with the document.',
|
||||
message: 'Recipient type cannot have fields, or they have already interacted with the document.',
|
||||
});
|
||||
}
|
||||
|
||||
@@ -265,9 +263,7 @@ export const createEnvelopeFields = async ({
|
||||
if (envelope.type === EnvelopeType.DOCUMENT) {
|
||||
await tx.documentAuditLog.createMany({
|
||||
data: newlyCreatedFields.map((createdField) => {
|
||||
const recipient = validatedFields.find(
|
||||
(field) => field.recipientId === createdField.recipientId,
|
||||
);
|
||||
const recipient = validatedFields.find((field) => field.recipientId === createdField.recipientId);
|
||||
|
||||
return createDocumentAuditLogData({
|
||||
type: DOCUMENT_AUDIT_LOG_TYPE.FIELD_CREATED,
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import { EnvelopeType } from '@prisma/client';
|
||||
|
||||
import { DOCUMENT_AUDIT_LOG_TYPE } from '@documenso/lib/types/document-audit-logs';
|
||||
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 { EnvelopeType } from '@prisma/client';
|
||||
|
||||
import { AppError, AppErrorCode } from '../../errors/app-error';
|
||||
import { canRecipientFieldsBeModified } from '../../utils/recipients';
|
||||
@@ -16,12 +15,7 @@ export interface DeleteDocumentFieldOptions {
|
||||
requestMetadata: ApiRequestMetadata;
|
||||
}
|
||||
|
||||
export const deleteDocumentField = async ({
|
||||
userId,
|
||||
teamId,
|
||||
fieldId,
|
||||
requestMetadata,
|
||||
}: DeleteDocumentFieldOptions) => {
|
||||
export const deleteDocumentField = async ({ userId, teamId, fieldId, requestMetadata }: DeleteDocumentFieldOptions) => {
|
||||
// Unauthenticated check, we do the real check later.
|
||||
const field = await prisma.field.findFirst({
|
||||
where: {
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { EnvelopeType } from '@prisma/client';
|
||||
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import { EnvelopeType } from '@prisma/client';
|
||||
|
||||
import { AppError, AppErrorCode } from '../../errors/app-error';
|
||||
import { buildTeamWhereQuery } from '../../utils/teams';
|
||||
@@ -12,11 +11,7 @@ export interface DeleteTemplateFieldOptions {
|
||||
fieldId: number;
|
||||
}
|
||||
|
||||
export const deleteTemplateField = async ({
|
||||
userId,
|
||||
teamId,
|
||||
fieldId,
|
||||
}: DeleteTemplateFieldOptions): Promise<void> => {
|
||||
export const deleteTemplateField = async ({ userId, teamId, fieldId }: DeleteTemplateFieldOptions): Promise<void> => {
|
||||
const field = await prisma.field.findFirst({
|
||||
where: {
|
||||
id: fieldId,
|
||||
|
||||
@@ -1,14 +1,11 @@
|
||||
import { SigningStatus } from '@prisma/client';
|
||||
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import { SigningStatus } from '@prisma/client';
|
||||
|
||||
export type GetCompletedFieldsForDocumentOptions = {
|
||||
documentId: number;
|
||||
};
|
||||
|
||||
export const getCompletedFieldsForDocument = async ({
|
||||
documentId,
|
||||
}: GetCompletedFieldsForDocumentOptions) => {
|
||||
export const getCompletedFieldsForDocument = async ({ documentId }: GetCompletedFieldsForDocumentOptions) => {
|
||||
return await prisma.field.findMany({
|
||||
where: {
|
||||
documentId,
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { EnvelopeType, SigningStatus } from '@prisma/client';
|
||||
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import { EnvelopeType, SigningStatus } from '@prisma/client';
|
||||
|
||||
export type GetCompletedFieldsForTokenOptions = {
|
||||
token: string;
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import type { EnvelopeType } from '@prisma/client';
|
||||
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import type { EnvelopeType } from '@prisma/client';
|
||||
|
||||
import { AppError, AppErrorCode } from '../../errors/app-error';
|
||||
import { mapFieldToLegacyField } from '../../utils/fields';
|
||||
@@ -14,12 +13,7 @@ export type GetFieldByIdOptions = {
|
||||
envelopeType?: EnvelopeType;
|
||||
};
|
||||
|
||||
export const getFieldById = async ({
|
||||
userId,
|
||||
teamId,
|
||||
fieldId,
|
||||
envelopeType,
|
||||
}: GetFieldByIdOptions) => {
|
||||
export const getFieldById = async ({ userId, teamId, fieldId, envelopeType }: GetFieldByIdOptions) => {
|
||||
const field = await prisma.field.findFirst({
|
||||
where: {
|
||||
id: fieldId,
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { EnvelopeType, FieldType, RecipientRole, SigningStatus } from '@prisma/client';
|
||||
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import { EnvelopeType, FieldType, RecipientRole, SigningStatus } from '@prisma/client';
|
||||
|
||||
export type GetFieldsForTokenOptions = {
|
||||
token: string;
|
||||
@@ -35,6 +34,7 @@ export const getFieldsForToken = async ({ token }: GetFieldsForTokenOptions) =>
|
||||
signingOrder: {
|
||||
gte: recipient.signingOrder ?? 0,
|
||||
},
|
||||
envelopeId: recipient.envelopeId,
|
||||
},
|
||||
envelope: {
|
||||
id: recipient.envelopeId,
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import { DocumentStatus, RecipientRole, SigningStatus } from '@prisma/client';
|
||||
|
||||
import { DOCUMENT_AUDIT_LOG_TYPE } from '@documenso/lib/types/document-audit-logs';
|
||||
import type { RequestMetadata } from '@documenso/lib/universal/extract-request-metadata';
|
||||
import { createDocumentAuditLogData } from '@documenso/lib/utils/document-audit-logs';
|
||||
import { assertRecipientNotExpired } from '@documenso/lib/utils/recipients';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import { DocumentStatus, RecipientRole, SigningStatus } from '@prisma/client';
|
||||
|
||||
export type RemovedSignedFieldWithTokenOptions = {
|
||||
token: string;
|
||||
@@ -38,6 +37,7 @@ export const removeSignedFieldWithToken = async ({
|
||||
signingStatus: {
|
||||
not: SigningStatus.SIGNED,
|
||||
},
|
||||
envelopeId: recipient.envelopeId,
|
||||
}),
|
||||
},
|
||||
},
|
||||
@@ -59,10 +59,7 @@ export const removeSignedFieldWithToken = async ({
|
||||
|
||||
assertRecipientNotExpired(recipient);
|
||||
|
||||
if (
|
||||
recipient?.signingStatus === SigningStatus.SIGNED ||
|
||||
field.recipient.signingStatus === SigningStatus.SIGNED
|
||||
) {
|
||||
if (recipient?.signingStatus === SigningStatus.SIGNED || field.recipient.signingStatus === SigningStatus.SIGNED) {
|
||||
throw new Error(`Recipient ${recipient.id} has already signed`);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
import { EnvelopeType, type Field, FieldType } from '@prisma/client';
|
||||
import { isDeepEqual } from 'remeda';
|
||||
|
||||
import { validateCheckboxField } from '@documenso/lib/advanced-fields-validation/validate-checkbox';
|
||||
import { validateDropdownField } from '@documenso/lib/advanced-fields-validation/validate-dropdown';
|
||||
import { validateNumberField } from '@documenso/lib/advanced-fields-validation/validate-number';
|
||||
@@ -18,11 +15,10 @@ import {
|
||||
ZTextFieldMeta,
|
||||
} from '@documenso/lib/types/field-meta';
|
||||
import type { ApiRequestMetadata } from '@documenso/lib/universal/extract-request-metadata';
|
||||
import {
|
||||
createDocumentAuditLogData,
|
||||
diffFieldChanges,
|
||||
} from '@documenso/lib/utils/document-audit-logs';
|
||||
import { createDocumentAuditLogData, diffFieldChanges } from '@documenso/lib/utils/document-audit-logs';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import { EnvelopeType, type Field, FieldType } from '@prisma/client';
|
||||
import { isDeepEqual } from 'remeda';
|
||||
|
||||
import { AppError, AppErrorCode } from '../../errors/app-error';
|
||||
import type { EnvelopeIdOptions } from '../../utils/envelope';
|
||||
@@ -93,9 +89,7 @@ export const setFieldsForDocument = async ({
|
||||
const recipient = envelope.recipients.find((recipient) => recipient.id === field.recipientId);
|
||||
|
||||
// Check whether the field is being attached to an allowed envelope item.
|
||||
const foundEnvelopeItem = envelope.envelopeItems.find(
|
||||
(envelopeItem) => envelopeItem.id === field.envelopeItemId,
|
||||
);
|
||||
const foundEnvelopeItem = envelope.envelopeItems.find((envelopeItem) => envelopeItem.id === field.envelopeItemId);
|
||||
|
||||
if (!foundEnvelopeItem) {
|
||||
throw new AppError(AppErrorCode.INVALID_REQUEST, {
|
||||
@@ -111,22 +105,16 @@ export const setFieldsForDocument = async ({
|
||||
}
|
||||
|
||||
// Check whether the existing field can be modified.
|
||||
if (
|
||||
existing &&
|
||||
hasFieldBeenChanged(existing, field) &&
|
||||
!canRecipientFieldsBeModified(recipient, existingFields)
|
||||
) {
|
||||
if (existing && hasFieldBeenChanged(existing, field) && !canRecipientFieldsBeModified(recipient, existingFields)) {
|
||||
throw new AppError(AppErrorCode.INVALID_REQUEST, {
|
||||
message:
|
||||
'Cannot modify a field where the recipient has already interacted with the document',
|
||||
message: 'Cannot modify a field where the recipient has already interacted with the document',
|
||||
});
|
||||
}
|
||||
|
||||
// Prevent creating new fields when recipient has interacted with the document.
|
||||
if (!existing && !canRecipientFieldsBeModified(recipient, existingFields)) {
|
||||
throw new AppError(AppErrorCode.INVALID_REQUEST, {
|
||||
message:
|
||||
'Cannot modify a field where the recipient has already interacted with the document',
|
||||
message: 'Cannot modify a field where the recipient has already interacted with the document',
|
||||
});
|
||||
}
|
||||
|
||||
@@ -158,11 +146,7 @@ export const setFieldsForDocument = async ({
|
||||
if (field.type === FieldType.NUMBER && field.fieldMeta) {
|
||||
const numberFieldParsedMeta = ZNumberFieldMeta.parse(field.fieldMeta);
|
||||
|
||||
const errors = validateNumberField(
|
||||
String(numberFieldParsedMeta.value || ''),
|
||||
numberFieldParsedMeta,
|
||||
false,
|
||||
);
|
||||
const errors = validateNumberField(String(numberFieldParsedMeta.value || ''), numberFieldParsedMeta, false);
|
||||
|
||||
if (errors.length > 0) {
|
||||
throw new Error(errors.join(', '));
|
||||
@@ -181,18 +165,14 @@ export const setFieldsForDocument = async ({
|
||||
throw new Error(errors.join(', '));
|
||||
}
|
||||
} else {
|
||||
throw new Error(
|
||||
'To proceed further, please set at least one value for the Checkbox field',
|
||||
);
|
||||
throw new Error('To proceed further, please set at least one value for the Checkbox field');
|
||||
}
|
||||
}
|
||||
|
||||
if (field.type === FieldType.RADIO) {
|
||||
if (field.fieldMeta) {
|
||||
const radioFieldParsedMeta = ZRadioFieldMeta.parse(field.fieldMeta);
|
||||
const checkedRadioFieldValue = radioFieldParsedMeta.values?.find(
|
||||
(option) => option.checked,
|
||||
)?.value;
|
||||
const checkedRadioFieldValue = radioFieldParsedMeta.values?.find((option) => option.checked)?.value;
|
||||
|
||||
const errors = validateRadioField(checkedRadioFieldValue, radioFieldParsedMeta);
|
||||
|
||||
@@ -200,9 +180,7 @@ export const setFieldsForDocument = async ({
|
||||
throw new Error(errors.join('. '));
|
||||
}
|
||||
} else {
|
||||
throw new Error(
|
||||
'To proceed further, please set at least one value for the Radio field',
|
||||
);
|
||||
throw new Error('To proceed further, please set at least one value for the Radio field');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -215,9 +193,7 @@ export const setFieldsForDocument = async ({
|
||||
throw new Error(errors.join('. '));
|
||||
}
|
||||
} else {
|
||||
throw new Error(
|
||||
'To proceed further, please set at least one value for the Dropdown field',
|
||||
);
|
||||
throw new Error('To proceed further, please set at least one value for the Dropdown field');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import { EnvelopeType, FieldType } from '@prisma/client';
|
||||
|
||||
import { validateCheckboxField } from '@documenso/lib/advanced-fields-validation/validate-checkbox';
|
||||
import { validateDropdownField } from '@documenso/lib/advanced-fields-validation/validate-dropdown';
|
||||
import { validateNumberField } from '@documenso/lib/advanced-fields-validation/validate-number';
|
||||
@@ -16,6 +14,7 @@ import {
|
||||
ZTextFieldMeta,
|
||||
} from '@documenso/lib/types/field-meta';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import { EnvelopeType, FieldType } from '@prisma/client';
|
||||
|
||||
import { AppError, AppErrorCode } from '../../errors/app-error';
|
||||
import type { EnvelopeIdOptions } from '../../utils/envelope';
|
||||
@@ -41,12 +40,7 @@ export type SetFieldsForTemplateOptions = {
|
||||
}[];
|
||||
};
|
||||
|
||||
export const setFieldsForTemplate = async ({
|
||||
userId,
|
||||
teamId,
|
||||
id,
|
||||
fields,
|
||||
}: SetFieldsForTemplateOptions) => {
|
||||
export const setFieldsForTemplate = async ({ userId, teamId, id, fields }: SetFieldsForTemplateOptions) => {
|
||||
const { envelopeWhereInput } = await getEnvelopeWhereInput({
|
||||
id,
|
||||
type: EnvelopeType.TEMPLATE,
|
||||
@@ -89,9 +83,7 @@ export const setFieldsForTemplate = async ({
|
||||
const recipient = envelope.recipients.find((recipient) => recipient.id === field.recipientId);
|
||||
|
||||
// Check whether the field is being attached to an allowed envelope item.
|
||||
const foundEnvelopeItem = envelope.envelopeItems.find(
|
||||
(envelopeItem) => envelopeItem.id === field.envelopeItemId,
|
||||
);
|
||||
const foundEnvelopeItem = envelope.envelopeItems.find((envelopeItem) => envelopeItem.id === field.envelopeItemId);
|
||||
|
||||
if (!foundEnvelopeItem) {
|
||||
throw new AppError(AppErrorCode.INVALID_REQUEST, {
|
||||
@@ -131,10 +123,7 @@ export const setFieldsForTemplate = async ({
|
||||
|
||||
if (field.type === FieldType.NUMBER && field.fieldMeta) {
|
||||
const numberFieldParsedMeta = ZNumberFieldMeta.parse(field.fieldMeta);
|
||||
const errors = validateNumberField(
|
||||
String(numberFieldParsedMeta.value || ''),
|
||||
numberFieldParsedMeta,
|
||||
);
|
||||
const errors = validateNumberField(String(numberFieldParsedMeta.value || ''), numberFieldParsedMeta);
|
||||
if (errors.length > 0) {
|
||||
throw new Error(errors.join(', '));
|
||||
}
|
||||
@@ -159,9 +148,7 @@ export const setFieldsForTemplate = async ({
|
||||
throw new Error('Radio field is missing required metadata');
|
||||
}
|
||||
const radioFieldParsedMeta = ZRadioFieldMeta.parse(field.fieldMeta);
|
||||
const checkedRadioFieldValue = radioFieldParsedMeta.values?.find(
|
||||
(option) => option.checked,
|
||||
)?.value;
|
||||
const checkedRadioFieldValue = radioFieldParsedMeta.values?.find((option) => option.checked)?.value;
|
||||
const errors = validateRadioField(checkedRadioFieldValue, radioFieldParsedMeta);
|
||||
if (errors.length > 0) {
|
||||
throw new Error(errors.join('. '));
|
||||
|
||||
@@ -1,8 +1,3 @@
|
||||
import { DocumentStatus, FieldType, RecipientRole, SigningStatus } from '@prisma/client';
|
||||
import { DateTime } from 'luxon';
|
||||
import { isDeepEqual } from 'remeda';
|
||||
import { match } from 'ts-pattern';
|
||||
|
||||
import { validateCheckboxField } from '@documenso/lib/advanced-fields-validation/validate-checkbox';
|
||||
import { validateDropdownField } from '@documenso/lib/advanced-fields-validation/validate-dropdown';
|
||||
import { validateNumberField } from '@documenso/lib/advanced-fields-validation/validate-number';
|
||||
@@ -10,6 +5,10 @@ import { validateRadioField } from '@documenso/lib/advanced-fields-validation/va
|
||||
import { validateTextField } from '@documenso/lib/advanced-fields-validation/validate-text';
|
||||
import { fromCheckboxValue } from '@documenso/lib/universal/field-checkbox';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import { DocumentStatus, FieldType, RecipientRole, SigningStatus } from '@prisma/client';
|
||||
import { DateTime } from 'luxon';
|
||||
import { isDeepEqual } from 'remeda';
|
||||
import { match } from 'ts-pattern';
|
||||
|
||||
import { AUTO_SIGNABLE_FIELD_TYPES } from '../../constants/autosign';
|
||||
import { DEFAULT_DOCUMENT_DATE_FORMAT } from '../../constants/date-formats';
|
||||
@@ -78,6 +77,7 @@ export const signFieldWithToken = async ({
|
||||
signingOrder: {
|
||||
gte: recipient.signingOrder ?? 0,
|
||||
},
|
||||
envelopeId: recipient.envelopeId,
|
||||
}),
|
||||
},
|
||||
},
|
||||
@@ -111,10 +111,7 @@ export const signFieldWithToken = async ({
|
||||
|
||||
assertRecipientNotExpired(recipient);
|
||||
|
||||
if (
|
||||
recipient.signingStatus === SigningStatus.SIGNED ||
|
||||
field.recipient.signingStatus === SigningStatus.SIGNED
|
||||
) {
|
||||
if (recipient.signingStatus === SigningStatus.SIGNED || field.recipient.signingStatus === SigningStatus.SIGNED) {
|
||||
throw new Error(`Recipient ${recipient.id} has already signed`);
|
||||
}
|
||||
|
||||
@@ -190,8 +187,7 @@ export const signFieldWithToken = async ({
|
||||
},
|
||||
});
|
||||
|
||||
const isSignatureField =
|
||||
field.type === FieldType.SIGNATURE || field.type === FieldType.FREE_SIGNATURE;
|
||||
const isSignatureField = field.type === FieldType.SIGNATURE || field.type === FieldType.FREE_SIGNATURE;
|
||||
|
||||
let customText = !isSignatureField ? value : undefined;
|
||||
|
||||
@@ -294,27 +290,14 @@ export const signFieldWithToken = async ({
|
||||
type,
|
||||
data: signatureImageAsBase64 || typedSignature || '',
|
||||
}))
|
||||
.with(
|
||||
FieldType.DATE,
|
||||
FieldType.EMAIL,
|
||||
FieldType.NAME,
|
||||
FieldType.TEXT,
|
||||
FieldType.INITIALS,
|
||||
(type) => ({
|
||||
type,
|
||||
data: updatedField.customText,
|
||||
}),
|
||||
)
|
||||
.with(
|
||||
FieldType.NUMBER,
|
||||
FieldType.RADIO,
|
||||
FieldType.CHECKBOX,
|
||||
FieldType.DROPDOWN,
|
||||
(type) => ({
|
||||
type,
|
||||
data: updatedField.customText,
|
||||
}),
|
||||
)
|
||||
.with(FieldType.DATE, FieldType.EMAIL, FieldType.NAME, FieldType.TEXT, FieldType.INITIALS, (type) => ({
|
||||
type,
|
||||
data: updatedField.customText,
|
||||
}))
|
||||
.with(FieldType.NUMBER, FieldType.RADIO, FieldType.CHECKBOX, FieldType.DROPDOWN, (type) => ({
|
||||
type,
|
||||
data: updatedField.customText,
|
||||
}))
|
||||
.exhaustive(),
|
||||
fieldSecurity: derivedRecipientActionAuth
|
||||
? {
|
||||
|
||||
@@ -1,16 +1,12 @@
|
||||
import { EnvelopeType, type FieldType } from '@prisma/client';
|
||||
|
||||
import { DOCUMENT_AUDIT_LOG_TYPE } from '@documenso/lib/types/document-audit-logs';
|
||||
import type { TFieldMetaSchema } from '@documenso/lib/types/field-meta';
|
||||
import type { ApiRequestMetadata } from '@documenso/lib/universal/extract-request-metadata';
|
||||
import {
|
||||
createDocumentAuditLogData,
|
||||
diffFieldChanges,
|
||||
} from '@documenso/lib/utils/document-audit-logs';
|
||||
import { createDocumentAuditLogData, diffFieldChanges } from '@documenso/lib/utils/document-audit-logs';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import { EnvelopeType, type FieldType } from '@prisma/client';
|
||||
|
||||
import { AppError, AppErrorCode } from '../../errors/app-error';
|
||||
import { type EnvelopeIdOptions } from '../../utils/envelope';
|
||||
import type { EnvelopeIdOptions } from '../../utils/envelope';
|
||||
import { mapFieldToLegacyField } from '../../utils/fields';
|
||||
import { canRecipientFieldsBeModified } from '../../utils/recipients';
|
||||
import { getEnvelopeWhereInput } from '../envelope/get-envelope-by-id';
|
||||
@@ -79,9 +75,7 @@ export const updateEnvelopeFields = async ({
|
||||
});
|
||||
}
|
||||
|
||||
const recipient = envelope.recipients.find(
|
||||
(recipient) => recipient.id === originalField.recipientId,
|
||||
);
|
||||
const recipient = envelope.recipients.find((recipient) => recipient.id === originalField.recipientId);
|
||||
|
||||
// Each field MUST have a recipient associated with it.
|
||||
if (!recipient) {
|
||||
@@ -93,8 +87,7 @@ export const updateEnvelopeFields = async ({
|
||||
// Check whether the recipient associated with the field can be modified.
|
||||
if (!canRecipientFieldsBeModified(recipient, envelope.fields)) {
|
||||
throw new AppError(AppErrorCode.INVALID_REQUEST, {
|
||||
message:
|
||||
'Cannot modify a field where the recipient has already interacted with the document',
|
||||
message: 'Cannot modify a field where the recipient has already interacted with the document',
|
||||
});
|
||||
}
|
||||
|
||||
@@ -102,20 +95,13 @@ export const updateEnvelopeFields = async ({
|
||||
const fieldMetaType = field.fieldMeta?.type || originalField.fieldMeta?.type;
|
||||
|
||||
// Not going to mess with V1 envelopes.
|
||||
if (
|
||||
envelope.internalVersion === 2 &&
|
||||
fieldMetaType &&
|
||||
fieldMetaType.toLowerCase() !== fieldType.toLowerCase()
|
||||
) {
|
||||
if (envelope.internalVersion === 2 && fieldMetaType && fieldMetaType.toLowerCase() !== fieldType.toLowerCase()) {
|
||||
throw new AppError(AppErrorCode.INVALID_REQUEST, {
|
||||
message: 'Field meta type does not match the field type',
|
||||
});
|
||||
}
|
||||
|
||||
if (
|
||||
field.envelopeItemId &&
|
||||
!envelope.envelopeItems.some((item) => item.id === field.envelopeItemId)
|
||||
) {
|
||||
if (field.envelopeItemId && !envelope.envelopeItems.some((item) => item.id === field.envelopeItemId)) {
|
||||
throw new AppError(AppErrorCode.INVALID_REQUEST, {
|
||||
message: 'Envelope item not found',
|
||||
});
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { EnvelopeType } from '@prisma/client';
|
||||
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import { EnvelopeType } from '@prisma/client';
|
||||
|
||||
import { TEAM_DOCUMENT_VISIBILITY_MAP } from '../../constants/teams';
|
||||
import type { TFolderType } from '../../types/folder-type';
|
||||
@@ -13,12 +12,7 @@ export interface FindFoldersInternalOptions {
|
||||
type?: TFolderType;
|
||||
}
|
||||
|
||||
export const findFoldersInternal = async ({
|
||||
userId,
|
||||
teamId,
|
||||
parentId,
|
||||
type,
|
||||
}: FindFoldersInternalOptions) => {
|
||||
export const findFoldersInternal = async ({ userId, teamId, parentId, type }: FindFoldersInternalOptions) => {
|
||||
const team = await getTeamById({ userId, teamId });
|
||||
|
||||
const visibilityFilters = {
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import type { Prisma } from '@prisma/client';
|
||||
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import type { Prisma } from '@prisma/client';
|
||||
|
||||
import { TEAM_DOCUMENT_VISIBILITY_MAP } from '../../constants/teams';
|
||||
import type { TFolderType } from '../../types/folder-type';
|
||||
@@ -17,14 +16,7 @@ export interface FindFoldersOptions {
|
||||
perPage?: number;
|
||||
}
|
||||
|
||||
export const findFolders = async ({
|
||||
userId,
|
||||
teamId,
|
||||
parentId,
|
||||
type,
|
||||
page = 1,
|
||||
perPage = 10,
|
||||
}: FindFoldersOptions) => {
|
||||
export const findFolders = async ({ userId, teamId, parentId, type, page = 1, perPage = 10 }: FindFoldersOptions) => {
|
||||
const team = await getTeamById({ userId, teamId });
|
||||
|
||||
const whereClause: Prisma.FolderWhereInput = {
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import { TeamMemberRole } from '@prisma/client';
|
||||
import { match } from 'ts-pattern';
|
||||
|
||||
import { prisma } from '@documenso/prisma';
|
||||
|
||||
import { DocumentVisibility } from '../../types/document-visibility';
|
||||
import type { TFolderType } from '../../types/folder-type';
|
||||
import { getTeamById } from '../team/get-team';
|
||||
@@ -14,22 +13,13 @@ export interface GetFolderBreadcrumbsOptions {
|
||||
type?: TFolderType;
|
||||
}
|
||||
|
||||
export const getFolderBreadcrumbs = async ({
|
||||
userId,
|
||||
teamId,
|
||||
folderId,
|
||||
type,
|
||||
}: GetFolderBreadcrumbsOptions) => {
|
||||
export const getFolderBreadcrumbs = async ({ userId, teamId, folderId, type }: GetFolderBreadcrumbsOptions) => {
|
||||
const team = await getTeamById({ userId, teamId });
|
||||
|
||||
const visibilityFilters = match(team.currentTeamRole)
|
||||
.with(TeamMemberRole.ADMIN, () => ({
|
||||
visibility: {
|
||||
in: [
|
||||
DocumentVisibility.EVERYONE,
|
||||
DocumentVisibility.MANAGER_AND_ABOVE,
|
||||
DocumentVisibility.ADMIN,
|
||||
],
|
||||
in: [DocumentVisibility.EVERYONE, DocumentVisibility.MANAGER_AND_ABOVE, DocumentVisibility.ADMIN],
|
||||
},
|
||||
}))
|
||||
.with(TeamMemberRole.MANAGER, () => ({
|
||||
|
||||
@@ -9,7 +9,7 @@ import {
|
||||
NEXT_PUBLIC_WEBAPP_URL,
|
||||
USE_INTERNAL_URL_BROWSERLESS,
|
||||
} from '../../constants/app';
|
||||
import { type SupportedLanguageCodes, isValidLanguageCode } from '../../constants/i18n';
|
||||
import { isValidLanguageCode, type SupportedLanguageCodes } from '../../constants/i18n';
|
||||
import { env } from '../../utils/env';
|
||||
import { encryptSecondaryData } from '../crypto/encrypt';
|
||||
|
||||
@@ -57,9 +57,7 @@ export const getAuditLogsPdf = async ({ documentId, language }: GetAuditLogsPdfO
|
||||
{
|
||||
name: 'language',
|
||||
value: lang,
|
||||
url: USE_INTERNAL_URL_BROWSERLESS()
|
||||
? NEXT_PUBLIC_WEBAPP_URL()
|
||||
: NEXT_PRIVATE_INTERNAL_WEBAPP_URL(),
|
||||
url: USE_INTERNAL_URL_BROWSERLESS() ? NEXT_PUBLIC_WEBAPP_URL() : NEXT_PRIVATE_INTERNAL_WEBAPP_URL(),
|
||||
},
|
||||
]);
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ import {
|
||||
NEXT_PUBLIC_WEBAPP_URL,
|
||||
USE_INTERNAL_URL_BROWSERLESS,
|
||||
} from '../../constants/app';
|
||||
import { type SupportedLanguageCodes, isValidLanguageCode } from '../../constants/i18n';
|
||||
import { isValidLanguageCode, type SupportedLanguageCodes } from '../../constants/i18n';
|
||||
import { env } from '../../utils/env';
|
||||
import { encryptSecondaryData } from '../crypto/encrypt';
|
||||
|
||||
@@ -57,9 +57,7 @@ export const getCertificatePdf = async ({ documentId, language }: GetCertificate
|
||||
{
|
||||
name: 'lang',
|
||||
value: lang,
|
||||
url: USE_INTERNAL_URL_BROWSERLESS()
|
||||
? NEXT_PUBLIC_WEBAPP_URL()
|
||||
: NEXT_PRIVATE_INTERNAL_WEBAPP_URL(),
|
||||
url: USE_INTERNAL_URL_BROWSERLESS() ? NEXT_PUBLIC_WEBAPP_URL() : NEXT_PRIVATE_INTERNAL_WEBAPP_URL(),
|
||||
},
|
||||
]);
|
||||
|
||||
|
||||
@@ -16,8 +16,7 @@ import { SUBSCRIPTION_CLAIM_FEATURE_FLAGS } from '../../types/subscription';
|
||||
import { env } from '../../utils/env';
|
||||
|
||||
const LICENSE_KEY = env('NEXT_PRIVATE_DOCUMENSO_LICENSE_KEY');
|
||||
const LICENSE_SERVER_URL =
|
||||
env('INTERNAL_OVERRIDE_LICENSE_SERVER_URL') || 'https://license.documenso.com';
|
||||
const LICENSE_SERVER_URL = env('INTERNAL_OVERRIDE_LICENSE_SERVER_URL') || 'https://license.documenso.com';
|
||||
|
||||
declare global {
|
||||
// eslint-disable-next-line no-var
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import type { OrganisationGroup, OrganisationMemberRole } from '@prisma/client';
|
||||
import { OrganisationGroupType, OrganisationMemberInviteStatus } from '@prisma/client';
|
||||
|
||||
import { prisma } from '@documenso/prisma';
|
||||
|
||||
import { AppError, AppErrorCode } from '../../errors/app-error';
|
||||
import { jobs } from '../../jobs/client';
|
||||
import { generateDatabaseId } from '../../universal/id';
|
||||
@@ -11,9 +10,7 @@ export type AcceptOrganisationInvitationOptions = {
|
||||
token: string;
|
||||
};
|
||||
|
||||
export const acceptOrganisationInvitation = async ({
|
||||
token,
|
||||
}: AcceptOrganisationInvitationOptions) => {
|
||||
export const acceptOrganisationInvitation = async ({ token }: AcceptOrganisationInvitationOptions) => {
|
||||
const organisationMemberInvite = await prisma.organisationMemberInvite.findFirst({
|
||||
where: {
|
||||
token,
|
||||
@@ -101,8 +98,7 @@ export const addUserToOrganisation = async ({
|
||||
}) => {
|
||||
const organisationGroupToUse = organisationGroups.find(
|
||||
(group) =>
|
||||
group.type === OrganisationGroupType.INTERNAL_ORGANISATION &&
|
||||
group.organisationRole === organisationMemberRole,
|
||||
group.type === OrganisationGroupType.INTERNAL_ORGANISATION && group.organisationRole === organisationMemberRole,
|
||||
);
|
||||
|
||||
if (!organisationGroupToUse) {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user