Merge branch 'main' into feat/public-completed-document-access

This commit is contained in:
ephraimduncan
2026-04-22 18:29:11 +00:00
625 changed files with 48707 additions and 14739 deletions
+4
View File
@@ -13,6 +13,10 @@ const prisma = remember(
() =>
new PrismaClient({
datasourceUrl: getDatabaseUrl(),
transactionOptions: {
maxWait: 5000,
timeout: 10000,
},
}),
);
@@ -0,0 +1,14 @@
-- AlterEnum
-- This migration adds more than one value to an enum.
-- With PostgreSQL versions 11 and earlier, this is not possible
-- in a single migration. This can be worked around by creating
-- multiple migrations, each migration adding only one value to
-- the enum.
ALTER TYPE "WebhookTriggerEvents" ADD VALUE 'DOCUMENT_RECIPIENT_COMPLETED';
ALTER TYPE "WebhookTriggerEvents" ADD VALUE 'DOCUMENT_REMINDER_SENT';
ALTER TYPE "WebhookTriggerEvents" ADD VALUE 'TEMPLATE_CREATED';
ALTER TYPE "WebhookTriggerEvents" ADD VALUE 'TEMPLATE_UPDATED';
ALTER TYPE "WebhookTriggerEvents" ADD VALUE 'TEMPLATE_DELETED';
ALTER TYPE "WebhookTriggerEvents" ADD VALUE 'TEMPLATE_USED';
@@ -0,0 +1,17 @@
-- CreateExtension
CREATE EXTENSION IF NOT EXISTS pg_trgm;
-- CreateIndex
CREATE INDEX IF NOT EXISTS "Recipient_email_documentDeletedAt_envelopeId_idx" ON "Recipient"("email", "documentDeletedAt", "envelopeId");
-- CreateIndex
CREATE INDEX IF NOT EXISTS "Recipient_email_envelopeId_idx" ON "Recipient"("email", "envelopeId");
-- CreateIndex
CREATE INDEX IF NOT EXISTS "Recipient_email_signingStatus_envelopeId_role_idx" ON "Recipient"("email", "signingStatus", "envelopeId", "role");
-- CreateIndex
CREATE INDEX IF NOT EXISTS "Recipient_email_trgm_idx" ON "Recipient" USING GIN ("email" gin_trgm_ops);
-- CreateIndex
CREATE INDEX IF NOT EXISTS "Recipient_name_trgm_idx" ON "Recipient" USING GIN ("name" gin_trgm_ops);
@@ -0,0 +1 @@
ALTER TYPE "TemplateType" ADD VALUE 'ORGANISATION';
@@ -0,0 +1,16 @@
-- AlterTable
ALTER TABLE "DocumentMeta" ADD COLUMN "reminderSettings" JSONB;
-- AlterTable
ALTER TABLE "OrganisationGlobalSettings" ADD COLUMN "reminderSettings" JSONB;
-- AlterTable
ALTER TABLE "TeamGlobalSettings" ADD COLUMN "reminderSettings" JSONB;
-- AlterTable
ALTER TABLE "Recipient" ADD COLUMN "sentAt" TIMESTAMP(3),
ADD COLUMN "lastReminderSentAt" TIMESTAMP(3),
ADD COLUMN "nextReminderAt" TIMESTAMP(3);
-- CreateIndex
CREATE INDEX "Recipient_nextReminderAt_idx" ON "Recipient"("nextReminderAt");
@@ -0,0 +1,8 @@
-- DropForeignKey
ALTER TABLE "DocumentAuditLog" DROP CONSTRAINT "DocumentAuditLog_envelopeId_fkey";
-- AlterTable
ALTER TABLE "DocumentAuditLog" ALTER COLUMN "envelopeId" DROP NOT NULL;
-- AddForeignKey
ALTER TABLE "DocumentAuditLog" ADD CONSTRAINT "DocumentAuditLog_envelopeId_fkey" FOREIGN KEY ("envelopeId") REFERENCES "Envelope"("id") ON DELETE SET NULL ON UPDATE CASCADE;
+1 -1
View File
@@ -22,7 +22,7 @@
},
"dependencies": {
"@prisma/client": "^6.19.0",
"kysely": "0.28.8",
"kysely": "0.28.16",
"nanoid": "^5.1.6",
"prisma": "^6.19.0",
"prisma-extension-kysely": "^3.0.0",
+27 -5
View File
@@ -173,6 +173,12 @@ enum WebhookTriggerEvents {
DOCUMENT_REJECTED
DOCUMENT_CANCELLED
RECIPIENT_EXPIRED
DOCUMENT_RECIPIENT_COMPLETED
DOCUMENT_REMINDER_SENT
TEMPLATE_CREATED
TEMPLATE_UPDATED
TEMPLATE_DELETED
TEMPLATE_USED
}
model Webhook {
@@ -461,7 +467,7 @@ model EnvelopeItem {
model DocumentAuditLog {
id String @id @default(cuid())
envelopeId String
envelopeId String?
createdAt DateTime @default(now())
type String
data Json
@@ -473,7 +479,7 @@ model DocumentAuditLog {
userAgent String?
ipAddress String?
envelope Envelope @relation(fields: [envelopeId], references: [id], onDelete: Cascade)
envelope Envelope? @relation(fields: [envelopeId], references: [id], onDelete: SetNull)
@@index([envelopeId])
}
@@ -502,7 +508,7 @@ enum DocumentDistributionMethod {
NONE
}
/// @zod.import(["import { ZDocumentEmailSettingsSchema } from '@documenso/lib/types/document-email';", "import { ZEnvelopeExpirationPeriod as ZEnvelopeExpirationPeriodSchema } from '@documenso/lib/constants/envelope-expiration';"])
/// @zod.import(["import { ZDocumentEmailSettingsSchema } from '@documenso/lib/types/document-email';", "import { ZEnvelopeExpirationPeriod as ZEnvelopeExpirationPeriodSchema } from '@documenso/lib/constants/envelope-expiration';", "import { ZEnvelopeReminderSettings as ZEnvelopeReminderSettingsSchema } from '@documenso/lib/constants/envelope-reminder';"])
model DocumentMeta {
id String @id @default(cuid())
subject String?
@@ -526,6 +532,8 @@ model DocumentMeta {
envelopeExpirationPeriod Json? /// [EnvelopeExpirationPeriod] @zod.custom.use(ZEnvelopeExpirationPeriodSchema)
reminderSettings Json? /// [EnvelopeReminderSettings] @zod.custom.use(ZEnvelopeReminderSettingsSchema)
envelope Envelope?
}
@@ -582,7 +590,10 @@ model Recipient {
expired DateTime? // deprecated Not in use. To be removed in a future migration.
expiresAt DateTime?
expirationNotifiedAt DateTime?
sentAt DateTime?
signedAt DateTime?
lastReminderSentAt DateTime?
nextReminderAt DateTime?
authOptions Json? /// [RecipientAuthOptions] @zod.custom.use(ZRecipientAuthOptionsSchema)
signingOrder Int? /// @zod.number.describe("The order in which the recipient should sign the document. Only works if the document is set to sequential signing.")
rejectionReason String?
@@ -599,6 +610,12 @@ model Recipient {
@@index([envelopeId])
@@index([signedAt])
@@index([expiresAt])
@@index([email, documentDeletedAt, envelopeId], map: "Recipient_email_documentDeletedAt_envelopeId_idx")
@@index([email, envelopeId], map: "Recipient_email_envelopeId_idx")
@@index([email, signingStatus, envelopeId, role], map: "Recipient_email_signingStatus_envelopeId_role_idx")
@@index([nextReminderAt])
@@index([email(ops: raw("gin_trgm_ops"))], map: "Recipient_email_trgm_idx", type: Gin)
@@index([name(ops: raw("gin_trgm_ops"))], map: "Recipient_name_trgm_idx", type: Gin)
}
enum FieldType {
@@ -815,7 +832,7 @@ enum OrganisationMemberInviteStatus {
DECLINED
}
/// @zod.import(["import { ZDocumentEmailSettingsSchema } from '@documenso/lib/types/document-email';", "import { ZDefaultRecipientsSchema } from '@documenso/lib/types/default-recipients';", "import { ZEnvelopeExpirationPeriod as ZEnvelopeExpirationPeriodSchema } from '@documenso/lib/constants/envelope-expiration';"])
/// @zod.import(["import { ZDocumentEmailSettingsSchema } from '@documenso/lib/types/document-email';", "import { ZDefaultRecipientsSchema } from '@documenso/lib/types/default-recipients';", "import { ZEnvelopeExpirationPeriod as ZEnvelopeExpirationPeriodSchema } from '@documenso/lib/constants/envelope-expiration';", "import { ZEnvelopeReminderSettings as ZEnvelopeReminderSettingsSchema } from '@documenso/lib/constants/envelope-reminder';"])
model OrganisationGlobalSettings {
id String @id
organisation Organisation?
@@ -850,11 +867,13 @@ model OrganisationGlobalSettings {
envelopeExpirationPeriod Json? /// [EnvelopeExpirationPeriod] @zod.custom.use(ZEnvelopeExpirationPeriodSchema)
reminderSettings Json? /// [EnvelopeReminderSettings] @zod.custom.use(ZEnvelopeReminderSettingsSchema)
// AI features settings.
aiFeaturesEnabled Boolean @default(false)
}
/// @zod.import(["import { ZDocumentEmailSettingsSchema } from '@documenso/lib/types/document-email';", "import { ZDefaultRecipientsSchema } from '@documenso/lib/types/default-recipients';", "import { ZEnvelopeExpirationPeriod as ZEnvelopeExpirationPeriodSchema } from '@documenso/lib/constants/envelope-expiration';"])
/// @zod.import(["import { ZDocumentEmailSettingsSchema } from '@documenso/lib/types/document-email';", "import { ZDefaultRecipientsSchema } from '@documenso/lib/types/default-recipients';", "import { ZEnvelopeExpirationPeriod as ZEnvelopeExpirationPeriodSchema } from '@documenso/lib/constants/envelope-expiration';", "import { ZEnvelopeReminderSettings as ZEnvelopeReminderSettingsSchema } from '@documenso/lib/constants/envelope-reminder';"])
model TeamGlobalSettings {
id String @id
team Team?
@@ -890,6 +909,8 @@ model TeamGlobalSettings {
envelopeExpirationPeriod Json? /// [EnvelopeExpirationPeriod] @zod.custom.use(ZEnvelopeExpirationPeriodSchema)
reminderSettings Json? /// [EnvelopeReminderSettings] @zod.custom.use(ZEnvelopeReminderSettingsSchema)
// AI features settings.
aiFeaturesEnabled Boolean?
}
@@ -946,6 +967,7 @@ model TeamEmailVerification {
enum TemplateType {
PUBLIC
PRIVATE
ORGANISATION
}
model TemplateDirectLink {
+25
View File
@@ -6,6 +6,18 @@ import { match } from 'ts-pattern';
import { createEnvelope } from '@documenso/lib/server-only/envelope/create-envelope';
import { incrementDocumentId } from '@documenso/lib/server-only/envelope/increment-id';
import {
FIELD_CHECKBOX_META_DEFAULT_VALUES,
FIELD_DATE_META_DEFAULT_VALUES,
FIELD_DROPDOWN_META_DEFAULT_VALUES,
FIELD_EMAIL_META_DEFAULT_VALUES,
FIELD_INITIALS_META_DEFAULT_VALUES,
FIELD_NAME_META_DEFAULT_VALUES,
FIELD_NUMBER_META_DEFAULT_VALUES,
FIELD_RADIO_META_DEFAULT_VALUES,
FIELD_SIGNATURE_META_DEFAULT_VALUES,
FIELD_TEXT_META_DEFAULT_VALUES,
} from '@documenso/lib/types/field-meta';
import { prefixedId } from '@documenso/lib/universal/id';
import { prisma } from '..';
@@ -576,6 +588,19 @@ export const seedPendingDocumentWithFullFields = async ({
height: new Prisma.Decimal(5),
envelopeId: document.id,
envelopeItemId: firstItem.id,
fieldMeta: match(fieldType)
.with(FieldType.DATE, () => FIELD_DATE_META_DEFAULT_VALUES)
.with(FieldType.EMAIL, () => FIELD_EMAIL_META_DEFAULT_VALUES)
.with(FieldType.NAME, () => FIELD_NAME_META_DEFAULT_VALUES)
.with(FieldType.SIGNATURE, () => FIELD_SIGNATURE_META_DEFAULT_VALUES)
.with(FieldType.TEXT, () => FIELD_TEXT_META_DEFAULT_VALUES)
.with(FieldType.NUMBER, () => FIELD_NUMBER_META_DEFAULT_VALUES)
.with(FieldType.CHECKBOX, () => FIELD_CHECKBOX_META_DEFAULT_VALUES)
.with(FieldType.RADIO, () => FIELD_RADIO_META_DEFAULT_VALUES)
.with(FieldType.DROPDOWN, () => FIELD_DROPDOWN_META_DEFAULT_VALUES)
.with(FieldType.INITIALS, () => FIELD_INITIALS_META_DEFAULT_VALUES)
.with(FieldType.FREE_SIGNATURE, () => undefined)
.exhaustive(),
})),
},
},
@@ -0,0 +1,77 @@
import { nanoid } from 'nanoid';
import { prisma } from '..';
import { DocumentStatus } from '../client';
import { seedBlankDocument } from './documents';
import { seedUser } from './users';
const MEDIUM_ACCOUNT_EMAIL = 'medium-account@documenso.com';
const DOCUMENT_COUNT = 1000;
const STATUSES = [DocumentStatus.DRAFT, DocumentStatus.PENDING, DocumentStatus.COMPLETED];
export const seedDatabase = async () => {
const existingUser = await prisma.user.findFirst({
where: {
email: MEDIUM_ACCOUNT_EMAIL,
},
});
if (existingUser) {
return;
}
console.log(`[SEEDING]: Creating medium account with ${DOCUMENT_COUNT} documents...`);
const { user, team } = await seedUser({
name: 'Medium Account',
email: MEDIUM_ACCOUNT_EMAIL,
});
const recipientEmails = Array.from({ length: 20 }, (_, i) => `recipient-${i}@test.documenso.com`);
for (let i = 0; i < DOCUMENT_COUNT; i++) {
const status = STATUSES[i % STATUSES.length];
const document = await seedBlankDocument(user, team.id, {
key: `medium-${i}`,
createDocumentOptions: {
title: `[MEDIUM] Document ${i} - ${status}`,
status,
},
});
// Add 1-3 recipients per document.
const recipientCount = (i % 3) + 1;
for (let r = 0; r < recipientCount; r++) {
await prisma.recipient.create({
data: {
email: recipientEmails[(i + r) % recipientEmails.length],
name: `Recipient ${r}`,
token: nanoid(),
envelopeId: document.id,
},
});
}
// Also create some documents where the medium user is a recipient (not owner).
// Every 5th document, create an extra doc owned by a "ghost" sender with medium user as recipient.
if (i % 5 === 0) {
await prisma.recipient.create({
data: {
email: MEDIUM_ACCOUNT_EMAIL,
name: 'Medium Account',
token: nanoid(),
envelopeId: document.id,
},
});
}
if (i % 100 === 0) {
console.log(`[SEEDING]: Created ${i}/${DOCUMENT_COUNT} documents...`);
}
}
console.log(`[SEEDING]: Medium account seeded with ${DOCUMENT_COUNT} documents.`);
};