Merge branch 'main' into feat/add-kysely
This commit is contained in:
@@ -0,0 +1,49 @@
|
||||
-- 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 "UserSecurityAuditLogType" ADD VALUE 'PASSKEY_CREATED';
|
||||
ALTER TYPE "UserSecurityAuditLogType" ADD VALUE 'PASSKEY_DELETED';
|
||||
ALTER TYPE "UserSecurityAuditLogType" ADD VALUE 'PASSKEY_UPDATED';
|
||||
ALTER TYPE "UserSecurityAuditLogType" ADD VALUE 'SIGN_IN_PASSKEY_FAIL';
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "Passkey" (
|
||||
"id" TEXT NOT NULL,
|
||||
"userId" INTEGER NOT NULL,
|
||||
"name" TEXT NOT NULL,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"lastUsedAt" TIMESTAMP(3),
|
||||
"credentialId" BYTEA NOT NULL,
|
||||
"credentialPublicKey" BYTEA NOT NULL,
|
||||
"counter" BIGINT NOT NULL,
|
||||
"credentialDeviceType" TEXT NOT NULL,
|
||||
"credentialBackedUp" BOOLEAN NOT NULL,
|
||||
"transports" TEXT[],
|
||||
|
||||
CONSTRAINT "Passkey_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "AnonymousVerificationToken" (
|
||||
"id" TEXT NOT NULL,
|
||||
"token" TEXT NOT NULL,
|
||||
"expiresAt" TIMESTAMP(3) NOT NULL,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "AnonymousVerificationToken_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "AnonymousVerificationToken_id_key" ON "AnonymousVerificationToken"("id");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "AnonymousVerificationToken_token_key" ON "AnonymousVerificationToken"("token");
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Passkey" ADD CONSTRAINT "Passkey_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
@@ -0,0 +1,5 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE "Document" ADD COLUMN "authOptions" JSONB;
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "Recipient" ADD COLUMN "authOptions" JSONB;
|
||||
@@ -0,0 +1,18 @@
|
||||
/*
|
||||
Warnings:
|
||||
|
||||
- A unique constraint covering the columns `[secondaryId]` on the table `VerificationToken` will be added. If there are existing duplicate values, this will fail.
|
||||
- The required column `secondaryId` was added to the `VerificationToken` table with a prisma-level default value. This is not possible if the table is not empty. Please add this column as optional, then populate it before making it required.
|
||||
|
||||
*/
|
||||
-- AlterTable
|
||||
ALTER TABLE "VerificationToken" ADD COLUMN "secondaryId" TEXT;
|
||||
|
||||
-- Set all null secondaryId fields to a uuid
|
||||
UPDATE "VerificationToken" SET "secondaryId" = gen_random_uuid()::text WHERE "secondaryId" IS NULL;
|
||||
|
||||
-- Restrict the VerificationToken to required
|
||||
ALTER TABLE "VerificationToken" ALTER COLUMN "secondaryId" SET NOT NULL;
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "VerificationToken_secondaryId_key" ON "VerificationToken"("secondaryId");
|
||||
@@ -0,0 +1,2 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE "Document" ADD COLUMN "formValues" JSONB;
|
||||
@@ -0,0 +1,13 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE "Recipient" ADD COLUMN "documentDeletedAt" TIMESTAMP(3);
|
||||
|
||||
-- Hard delete all PENDING documents that have been soft deleted
|
||||
DELETE FROM "Document" WHERE "deletedAt" IS NOT NULL AND "status" = 'PENDING';
|
||||
|
||||
-- Update all recipients who are the owner of the document and where the document has deletedAt set to not null
|
||||
UPDATE "Recipient"
|
||||
SET "documentDeletedAt" = "Document"."deletedAt"
|
||||
FROM "Document", "User"
|
||||
WHERE "Recipient"."documentId" = "Document"."id"
|
||||
AND "Recipient"."email" = "User"."email"
|
||||
AND "Document"."deletedAt" IS NOT NULL;
|
||||
@@ -0,0 +1,11 @@
|
||||
/*
|
||||
Warnings:
|
||||
|
||||
- Made the column `recipientId` on table `Field` required. This step will fail if there are existing NULL values in that column.
|
||||
|
||||
*/
|
||||
-- Drop all Fields where the recipientId is null
|
||||
DELETE FROM "Field" WHERE "recipientId" IS NULL;
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "Field" ALTER COLUMN "recipientId" SET NOT NULL;
|
||||
@@ -0,0 +1,23 @@
|
||||
-- DropForeignKey
|
||||
ALTER TABLE "PasswordResetToken" DROP CONSTRAINT "PasswordResetToken_userId_fkey";
|
||||
|
||||
-- DropForeignKey
|
||||
ALTER TABLE "Signature" DROP CONSTRAINT "Signature_fieldId_fkey";
|
||||
|
||||
-- DropForeignKey
|
||||
ALTER TABLE "Team" DROP CONSTRAINT "Team_ownerUserId_fkey";
|
||||
|
||||
-- DropForeignKey
|
||||
ALTER TABLE "TeamMember" DROP CONSTRAINT "TeamMember_userId_fkey";
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "PasswordResetToken" ADD CONSTRAINT "PasswordResetToken_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Signature" ADD CONSTRAINT "Signature_fieldId_fkey" FOREIGN KEY ("fieldId") REFERENCES "Field"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Team" ADD CONSTRAINT "Team_ownerUserId_fkey" FOREIGN KEY ("ownerUserId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "TeamMember" ADD CONSTRAINT "TeamMember_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
@@ -0,0 +1,22 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE "Template" ADD COLUMN "authOptions" JSONB;
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "TemplateMeta" (
|
||||
"id" TEXT NOT NULL,
|
||||
"subject" TEXT,
|
||||
"message" TEXT,
|
||||
"timezone" TEXT DEFAULT 'Etc/UTC',
|
||||
"password" TEXT,
|
||||
"dateFormat" TEXT DEFAULT 'yyyy-MM-dd hh:mm a',
|
||||
"templateId" INTEGER NOT NULL,
|
||||
"redirectUrl" TEXT,
|
||||
|
||||
CONSTRAINT "TemplateMeta_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "TemplateMeta_templateId_key" ON "TemplateMeta"("templateId");
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "TemplateMeta" ADD CONSTRAINT "TemplateMeta_templateId_fkey" FOREIGN KEY ("templateId") REFERENCES "Template"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
@@ -52,6 +52,7 @@ model User {
|
||||
securityAuditLogs UserSecurityAuditLog[]
|
||||
Webhooks Webhook[]
|
||||
siteSettings SiteSettings[]
|
||||
passkeys Passkey[]
|
||||
|
||||
@@index([email])
|
||||
}
|
||||
@@ -68,12 +69,16 @@ enum UserSecurityAuditLogType {
|
||||
ACCOUNT_SSO_LINK
|
||||
AUTH_2FA_DISABLE
|
||||
AUTH_2FA_ENABLE
|
||||
PASSKEY_CREATED
|
||||
PASSKEY_DELETED
|
||||
PASSKEY_UPDATED
|
||||
PASSWORD_RESET
|
||||
PASSWORD_UPDATE
|
||||
SIGN_OUT
|
||||
SIGN_IN
|
||||
SIGN_IN_FAIL
|
||||
SIGN_IN_2FA_FAIL
|
||||
SIGN_IN_PASSKEY_FAIL
|
||||
}
|
||||
|
||||
model UserSecurityAuditLog {
|
||||
@@ -93,17 +98,42 @@ model PasswordResetToken {
|
||||
createdAt DateTime @default(now())
|
||||
expiry DateTime
|
||||
userId Int
|
||||
User User @relation(fields: [userId], references: [id])
|
||||
User User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||
}
|
||||
|
||||
model Passkey {
|
||||
id String @id @default(cuid())
|
||||
userId Int
|
||||
name String
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @default(now())
|
||||
lastUsedAt DateTime?
|
||||
credentialId Bytes
|
||||
credentialPublicKey Bytes
|
||||
counter BigInt
|
||||
credentialDeviceType String
|
||||
credentialBackedUp Boolean
|
||||
transports String[]
|
||||
|
||||
User User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||
}
|
||||
|
||||
model AnonymousVerificationToken {
|
||||
id String @id @unique @default(cuid())
|
||||
token String @unique
|
||||
expiresAt DateTime
|
||||
createdAt DateTime @default(now())
|
||||
}
|
||||
|
||||
model VerificationToken {
|
||||
id Int @id @default(autoincrement())
|
||||
identifier String
|
||||
token String @unique
|
||||
expires DateTime
|
||||
createdAt DateTime @default(now())
|
||||
userId Int
|
||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||
id Int @id @default(autoincrement())
|
||||
secondaryId String @unique @default(cuid())
|
||||
identifier String
|
||||
token String @unique
|
||||
expires DateTime
|
||||
createdAt DateTime @default(now())
|
||||
userId Int
|
||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||
}
|
||||
|
||||
enum WebhookTriggerEvents {
|
||||
@@ -226,6 +256,8 @@ model Document {
|
||||
id Int @id @default(autoincrement())
|
||||
userId Int
|
||||
User User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||
authOptions Json?
|
||||
formValues Json?
|
||||
title String
|
||||
status DocumentStatus @default(DRAFT)
|
||||
Recipient Recipient[]
|
||||
@@ -315,22 +347,24 @@ enum RecipientRole {
|
||||
}
|
||||
|
||||
model Recipient {
|
||||
id Int @id @default(autoincrement())
|
||||
documentId Int?
|
||||
templateId Int?
|
||||
email String @db.VarChar(255)
|
||||
name String @default("") @db.VarChar(255)
|
||||
token String
|
||||
expired DateTime?
|
||||
signedAt DateTime?
|
||||
role RecipientRole @default(SIGNER)
|
||||
readStatus ReadStatus @default(NOT_OPENED)
|
||||
signingStatus SigningStatus @default(NOT_SIGNED)
|
||||
sendStatus SendStatus @default(NOT_SENT)
|
||||
Document Document? @relation(fields: [documentId], references: [id], onDelete: Cascade)
|
||||
Template Template? @relation(fields: [templateId], references: [id], onDelete: Cascade)
|
||||
Field Field[]
|
||||
Signature Signature[]
|
||||
id Int @id @default(autoincrement())
|
||||
documentId Int?
|
||||
templateId Int?
|
||||
email String @db.VarChar(255)
|
||||
name String @default("") @db.VarChar(255)
|
||||
token String
|
||||
documentDeletedAt DateTime?
|
||||
expired DateTime?
|
||||
signedAt DateTime?
|
||||
authOptions Json?
|
||||
role RecipientRole @default(SIGNER)
|
||||
readStatus ReadStatus @default(NOT_OPENED)
|
||||
signingStatus SigningStatus @default(NOT_SIGNED)
|
||||
sendStatus SendStatus @default(NOT_SENT)
|
||||
Document Document? @relation(fields: [documentId], references: [id], onDelete: Cascade)
|
||||
Template Template? @relation(fields: [templateId], references: [id], onDelete: Cascade)
|
||||
Field Field[]
|
||||
Signature Signature[]
|
||||
|
||||
@@unique([documentId, email])
|
||||
@@unique([templateId, email])
|
||||
@@ -353,7 +387,7 @@ model Field {
|
||||
secondaryId String @unique @default(cuid())
|
||||
documentId Int?
|
||||
templateId Int?
|
||||
recipientId Int?
|
||||
recipientId Int
|
||||
type FieldType
|
||||
page Int
|
||||
positionX Decimal @default(0)
|
||||
@@ -364,7 +398,7 @@ model Field {
|
||||
inserted Boolean
|
||||
Document Document? @relation(fields: [documentId], references: [id], onDelete: Cascade)
|
||||
Template Template? @relation(fields: [templateId], references: [id], onDelete: Cascade)
|
||||
Recipient Recipient? @relation(fields: [recipientId], references: [id], onDelete: Cascade)
|
||||
Recipient Recipient @relation(fields: [recipientId], references: [id], onDelete: Cascade)
|
||||
Signature Signature?
|
||||
|
||||
@@index([documentId])
|
||||
@@ -381,7 +415,7 @@ model Signature {
|
||||
typedSignature String?
|
||||
|
||||
Recipient Recipient @relation(fields: [recipientId], references: [id], onDelete: Cascade)
|
||||
Field Field @relation(fields: [fieldId], references: [id], onDelete: Restrict)
|
||||
Field Field @relation(fields: [fieldId], references: [id], onDelete: Cascade)
|
||||
|
||||
@@index([recipientId])
|
||||
}
|
||||
@@ -423,7 +457,7 @@ model Team {
|
||||
emailVerification TeamEmailVerification?
|
||||
transferVerification TeamTransferVerification?
|
||||
|
||||
owner User @relation(fields: [ownerUserId], references: [id])
|
||||
owner User @relation(fields: [ownerUserId], references: [id], onDelete: Cascade)
|
||||
subscription Subscription?
|
||||
|
||||
document Document[]
|
||||
@@ -449,7 +483,7 @@ model TeamMember {
|
||||
createdAt DateTime @default(now())
|
||||
role TeamMemberRole
|
||||
userId Int
|
||||
user User @relation(fields: [userId], references: [id])
|
||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||
team Team @relation(fields: [teamId], references: [id], onDelete: Cascade)
|
||||
|
||||
@@unique([userId, teamId])
|
||||
@@ -505,15 +539,29 @@ enum TemplateType {
|
||||
PRIVATE
|
||||
}
|
||||
|
||||
model TemplateMeta {
|
||||
id String @id @default(cuid())
|
||||
subject String?
|
||||
message String?
|
||||
timezone String? @default("Etc/UTC") @db.Text
|
||||
password String?
|
||||
dateFormat String? @default("yyyy-MM-dd hh:mm a") @db.Text
|
||||
templateId Int @unique
|
||||
template Template @relation(fields: [templateId], references: [id], onDelete: Cascade)
|
||||
redirectUrl String?
|
||||
}
|
||||
|
||||
model Template {
|
||||
id Int @id @default(autoincrement())
|
||||
type TemplateType @default(PRIVATE)
|
||||
id Int @id @default(autoincrement())
|
||||
type TemplateType @default(PRIVATE)
|
||||
title String
|
||||
userId Int
|
||||
teamId Int?
|
||||
authOptions Json?
|
||||
templateMeta TemplateMeta?
|
||||
templateDocumentDataId String
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @default(now()) @updatedAt
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @default(now()) @updatedAt
|
||||
|
||||
team Team? @relation(fields: [teamId], references: [id], onDelete: Cascade)
|
||||
templateDocumentData DocumentData @relation(fields: [templateDocumentDataId], references: [id], onDelete: Cascade)
|
||||
@@ -530,5 +578,5 @@ model SiteSettings {
|
||||
data Json
|
||||
lastModifiedByUserId Int?
|
||||
lastModifiedAt DateTime @default(now())
|
||||
lastModifiedByUser User? @relation(fields: [lastModifiedByUserId], references: [id])
|
||||
lastModifiedByUser User? @relation(fields: [lastModifiedByUserId], references: [id], onDelete: SetNull)
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { User } from '@prisma/client';
|
||||
import type { Document, User } from '@prisma/client';
|
||||
import { nanoid } from 'nanoid';
|
||||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
@@ -33,19 +33,19 @@ export const seedDocuments = async (documents: DocumentToSeed[]) => {
|
||||
documents.map(async (document, i) =>
|
||||
match(document.type)
|
||||
.with(DocumentStatus.DRAFT, async () =>
|
||||
createDraftDocument(document.sender, document.recipients, {
|
||||
seedDraftDocument(document.sender, document.recipients, {
|
||||
key: i,
|
||||
createDocumentOptions: document.documentOptions,
|
||||
}),
|
||||
)
|
||||
.with(DocumentStatus.PENDING, async () =>
|
||||
createPendingDocument(document.sender, document.recipients, {
|
||||
seedPendingDocument(document.sender, document.recipients, {
|
||||
key: i,
|
||||
createDocumentOptions: document.documentOptions,
|
||||
}),
|
||||
)
|
||||
.with(DocumentStatus.COMPLETED, async () =>
|
||||
createCompletedDocument(document.sender, document.recipients, {
|
||||
seedCompletedDocument(document.sender, document.recipients, {
|
||||
key: i,
|
||||
createDocumentOptions: document.documentOptions,
|
||||
}),
|
||||
@@ -55,7 +55,37 @@ export const seedDocuments = async (documents: DocumentToSeed[]) => {
|
||||
);
|
||||
};
|
||||
|
||||
const createDraftDocument = async (
|
||||
export const seedBlankDocument = async (owner: User, options: CreateDocumentOptions = {}) => {
|
||||
const { key, createDocumentOptions = {} } = options;
|
||||
|
||||
const documentData = await prisma.documentData.create({
|
||||
data: {
|
||||
type: DocumentDataType.BYTES_64,
|
||||
data: examplePdf,
|
||||
initialData: examplePdf,
|
||||
},
|
||||
});
|
||||
|
||||
return await prisma.document.create({
|
||||
data: {
|
||||
title: `[TEST] Document ${key} - Draft`,
|
||||
status: DocumentStatus.DRAFT,
|
||||
documentDataId: documentData.id,
|
||||
userId: owner.id,
|
||||
...createDocumentOptions,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const unseedDocument = async (documentId: number) => {
|
||||
await prisma.document.delete({
|
||||
where: {
|
||||
id: documentId,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const seedDraftDocument = async (
|
||||
sender: User,
|
||||
recipients: (User | string)[],
|
||||
options: CreateDocumentOptions = {},
|
||||
@@ -114,6 +144,8 @@ const createDraftDocument = async (
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
return document;
|
||||
};
|
||||
|
||||
type CreateDocumentOptions = {
|
||||
@@ -121,7 +153,7 @@ type CreateDocumentOptions = {
|
||||
createDocumentOptions?: Partial<Prisma.DocumentUncheckedCreateInput>;
|
||||
};
|
||||
|
||||
const createPendingDocument = async (
|
||||
export const seedPendingDocument = async (
|
||||
sender: User,
|
||||
recipients: (User | string)[],
|
||||
options: CreateDocumentOptions = {},
|
||||
@@ -180,9 +212,153 @@ const createPendingDocument = async (
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
return prisma.document.findFirstOrThrow({
|
||||
where: {
|
||||
id: document.id,
|
||||
},
|
||||
include: {
|
||||
Recipient: true,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const createCompletedDocument = async (
|
||||
export const seedPendingDocumentNoFields = async ({
|
||||
owner,
|
||||
recipients,
|
||||
updateDocumentOptions,
|
||||
}: {
|
||||
owner: User;
|
||||
recipients: (User | string)[];
|
||||
updateDocumentOptions?: Partial<Prisma.DocumentUncheckedUpdateInput>;
|
||||
}) => {
|
||||
const document: Document = await seedBlankDocument(owner);
|
||||
|
||||
for (const recipient of recipients) {
|
||||
const email = typeof recipient === 'string' ? recipient : recipient.email;
|
||||
const name = typeof recipient === 'string' ? recipient : recipient.name ?? '';
|
||||
|
||||
await prisma.recipient.create({
|
||||
data: {
|
||||
email,
|
||||
name,
|
||||
token: nanoid(),
|
||||
readStatus: ReadStatus.OPENED,
|
||||
sendStatus: SendStatus.SENT,
|
||||
signingStatus: SigningStatus.NOT_SIGNED,
|
||||
signedAt: new Date(),
|
||||
Document: {
|
||||
connect: {
|
||||
id: document.id,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
const createdRecipients = await prisma.recipient.findMany({
|
||||
where: {
|
||||
documentId: document.id,
|
||||
},
|
||||
include: {
|
||||
Field: true,
|
||||
},
|
||||
});
|
||||
|
||||
const latestDocument = updateDocumentOptions
|
||||
? await prisma.document.update({
|
||||
where: {
|
||||
id: document.id,
|
||||
},
|
||||
data: updateDocumentOptions,
|
||||
})
|
||||
: document;
|
||||
|
||||
return {
|
||||
document: latestDocument,
|
||||
recipients: createdRecipients,
|
||||
};
|
||||
};
|
||||
|
||||
export const seedPendingDocumentWithFullFields = async ({
|
||||
owner,
|
||||
recipients,
|
||||
recipientsCreateOptions,
|
||||
updateDocumentOptions,
|
||||
fields = [FieldType.DATE, FieldType.EMAIL, FieldType.NAME, FieldType.SIGNATURE, FieldType.TEXT],
|
||||
}: {
|
||||
owner: User;
|
||||
recipients: (User | string)[];
|
||||
recipientsCreateOptions?: Partial<Prisma.RecipientCreateInput>[];
|
||||
updateDocumentOptions?: Partial<Prisma.DocumentUncheckedUpdateInput>;
|
||||
fields?: FieldType[];
|
||||
}) => {
|
||||
const document: Document = await seedBlankDocument(owner);
|
||||
|
||||
for (const [recipientIndex, recipient] of recipients.entries()) {
|
||||
const email = typeof recipient === 'string' ? recipient : recipient.email;
|
||||
const name = typeof recipient === 'string' ? recipient : recipient.name ?? '';
|
||||
|
||||
await prisma.recipient.create({
|
||||
data: {
|
||||
email,
|
||||
name,
|
||||
token: nanoid(),
|
||||
readStatus: ReadStatus.OPENED,
|
||||
sendStatus: SendStatus.SENT,
|
||||
signingStatus: SigningStatus.NOT_SIGNED,
|
||||
signedAt: new Date(),
|
||||
Document: {
|
||||
connect: {
|
||||
id: document.id,
|
||||
},
|
||||
},
|
||||
Field: {
|
||||
createMany: {
|
||||
data: fields.map((fieldType, fieldIndex) => ({
|
||||
page: 1,
|
||||
type: fieldType,
|
||||
inserted: false,
|
||||
customText: name,
|
||||
positionX: new Prisma.Decimal((recipientIndex + 1) * 5),
|
||||
positionY: new Prisma.Decimal((fieldIndex + 1) * 5),
|
||||
width: new Prisma.Decimal(5),
|
||||
height: new Prisma.Decimal(5),
|
||||
documentId: document.id,
|
||||
})),
|
||||
},
|
||||
},
|
||||
...(recipientsCreateOptions?.[recipientIndex] ?? {}),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
const createdRecipients = await prisma.recipient.findMany({
|
||||
where: {
|
||||
documentId: document.id,
|
||||
},
|
||||
include: {
|
||||
Field: true,
|
||||
},
|
||||
});
|
||||
|
||||
const latestDocument = await prisma.document.update({
|
||||
where: {
|
||||
id: document.id,
|
||||
},
|
||||
data: {
|
||||
...updateDocumentOptions,
|
||||
status: DocumentStatus.PENDING,
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
document: latestDocument,
|
||||
recipients: createdRecipients,
|
||||
};
|
||||
};
|
||||
|
||||
export const seedCompletedDocument = async (
|
||||
sender: User,
|
||||
recipients: (User | string)[],
|
||||
options: CreateDocumentOptions = {},
|
||||
@@ -241,6 +417,8 @@ const createCompletedDocument = async (
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
return document;
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,223 +0,0 @@
|
||||
import type { User } from '@prisma/client';
|
||||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
|
||||
import { hashSync } from '@documenso/lib/server-only/auth/hash';
|
||||
|
||||
import { prisma } from '..';
|
||||
import {
|
||||
DocumentDataType,
|
||||
DocumentStatus,
|
||||
FieldType,
|
||||
Prisma,
|
||||
ReadStatus,
|
||||
SendStatus,
|
||||
SigningStatus,
|
||||
} from '../client';
|
||||
|
||||
const PULL_REQUEST_NUMBER = 711;
|
||||
const EMAIL_DOMAIN = `pr-${PULL_REQUEST_NUMBER}.documenso.com`;
|
||||
|
||||
export const TEST_USERS = [
|
||||
{
|
||||
name: 'Sender 1',
|
||||
email: `sender1@${EMAIL_DOMAIN}`,
|
||||
password: 'Password123',
|
||||
},
|
||||
{
|
||||
name: 'Sender 2',
|
||||
email: `sender2@${EMAIL_DOMAIN}`,
|
||||
password: 'Password123',
|
||||
},
|
||||
{
|
||||
name: 'Sender 3',
|
||||
email: `sender3@${EMAIL_DOMAIN}`,
|
||||
password: 'Password123',
|
||||
},
|
||||
] as const;
|
||||
|
||||
const examplePdf = fs
|
||||
.readFileSync(path.join(__dirname, '../../../assets/example.pdf'))
|
||||
.toString('base64');
|
||||
|
||||
export const seedDatabase = async () => {
|
||||
const users = await Promise.all(
|
||||
TEST_USERS.map(async (u) =>
|
||||
prisma.user.create({
|
||||
data: {
|
||||
name: u.name,
|
||||
email: u.email,
|
||||
password: hashSync(u.password),
|
||||
emailVerified: new Date(),
|
||||
url: u.email,
|
||||
},
|
||||
}),
|
||||
),
|
||||
);
|
||||
|
||||
const [user1, user2, user3] = users;
|
||||
|
||||
await createDraftDocument(user1, [user2, user3]);
|
||||
await createPendingDocument(user1, [user2, user3]);
|
||||
await createCompletedDocument(user1, [user2, user3]);
|
||||
};
|
||||
|
||||
const createDraftDocument = async (sender: User, recipients: User[]) => {
|
||||
const documentData = await prisma.documentData.create({
|
||||
data: {
|
||||
type: DocumentDataType.BYTES_64,
|
||||
data: examplePdf,
|
||||
initialData: examplePdf,
|
||||
},
|
||||
});
|
||||
|
||||
const document = await prisma.document.create({
|
||||
data: {
|
||||
title: `[${PULL_REQUEST_NUMBER}] Document 1 - Draft`,
|
||||
status: DocumentStatus.DRAFT,
|
||||
documentDataId: documentData.id,
|
||||
userId: sender.id,
|
||||
},
|
||||
});
|
||||
|
||||
for (const recipient of recipients) {
|
||||
const index = recipients.indexOf(recipient);
|
||||
|
||||
await prisma.recipient.create({
|
||||
data: {
|
||||
email: String(recipient.email),
|
||||
name: String(recipient.name),
|
||||
token: `draft-token-${index}`,
|
||||
readStatus: ReadStatus.NOT_OPENED,
|
||||
sendStatus: SendStatus.NOT_SENT,
|
||||
signingStatus: SigningStatus.NOT_SIGNED,
|
||||
signedAt: new Date(),
|
||||
Document: {
|
||||
connect: {
|
||||
id: document.id,
|
||||
},
|
||||
},
|
||||
Field: {
|
||||
create: {
|
||||
page: 1,
|
||||
type: FieldType.NAME,
|
||||
inserted: true,
|
||||
customText: String(recipient.name),
|
||||
positionX: new Prisma.Decimal(1),
|
||||
positionY: new Prisma.Decimal(1),
|
||||
width: new Prisma.Decimal(1),
|
||||
height: new Prisma.Decimal(1),
|
||||
documentId: document.id,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const createPendingDocument = async (sender: User, recipients: User[]) => {
|
||||
const documentData = await prisma.documentData.create({
|
||||
data: {
|
||||
type: DocumentDataType.BYTES_64,
|
||||
data: examplePdf,
|
||||
initialData: examplePdf,
|
||||
},
|
||||
});
|
||||
|
||||
const document = await prisma.document.create({
|
||||
data: {
|
||||
title: `[${PULL_REQUEST_NUMBER}] Document 1 - Pending`,
|
||||
status: DocumentStatus.PENDING,
|
||||
documentDataId: documentData.id,
|
||||
userId: sender.id,
|
||||
},
|
||||
});
|
||||
|
||||
for (const recipient of recipients) {
|
||||
const index = recipients.indexOf(recipient);
|
||||
|
||||
await prisma.recipient.create({
|
||||
data: {
|
||||
email: String(recipient.email),
|
||||
name: String(recipient.name),
|
||||
token: `pending-token-${index}`,
|
||||
readStatus: ReadStatus.OPENED,
|
||||
sendStatus: SendStatus.SENT,
|
||||
signingStatus: SigningStatus.SIGNED,
|
||||
signedAt: new Date(),
|
||||
Document: {
|
||||
connect: {
|
||||
id: document.id,
|
||||
},
|
||||
},
|
||||
Field: {
|
||||
create: {
|
||||
page: 1,
|
||||
type: FieldType.NAME,
|
||||
inserted: true,
|
||||
customText: String(recipient.name),
|
||||
positionX: new Prisma.Decimal(1),
|
||||
positionY: new Prisma.Decimal(1),
|
||||
width: new Prisma.Decimal(1),
|
||||
height: new Prisma.Decimal(1),
|
||||
documentId: document.id,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const createCompletedDocument = async (sender: User, recipients: User[]) => {
|
||||
const documentData = await prisma.documentData.create({
|
||||
data: {
|
||||
type: DocumentDataType.BYTES_64,
|
||||
data: examplePdf,
|
||||
initialData: examplePdf,
|
||||
},
|
||||
});
|
||||
|
||||
const document = await prisma.document.create({
|
||||
data: {
|
||||
title: `[${PULL_REQUEST_NUMBER}] Document 1 - Completed`,
|
||||
status: DocumentStatus.COMPLETED,
|
||||
documentDataId: documentData.id,
|
||||
completedAt: new Date(),
|
||||
userId: sender.id,
|
||||
},
|
||||
});
|
||||
|
||||
for (const recipient of recipients) {
|
||||
const index = recipients.indexOf(recipient);
|
||||
|
||||
await prisma.recipient.create({
|
||||
data: {
|
||||
email: String(recipient.email),
|
||||
name: String(recipient.name),
|
||||
token: `completed-token-${index}`,
|
||||
readStatus: ReadStatus.OPENED,
|
||||
sendStatus: SendStatus.SENT,
|
||||
signingStatus: SigningStatus.SIGNED,
|
||||
signedAt: new Date(),
|
||||
Document: {
|
||||
connect: {
|
||||
id: document.id,
|
||||
},
|
||||
},
|
||||
Field: {
|
||||
create: {
|
||||
page: 1,
|
||||
type: FieldType.NAME,
|
||||
inserted: true,
|
||||
customText: String(recipient.name),
|
||||
positionX: new Prisma.Decimal(1),
|
||||
positionY: new Prisma.Decimal(1),
|
||||
width: new Prisma.Decimal(1),
|
||||
height: new Prisma.Decimal(1),
|
||||
documentId: document.id,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -1,168 +0,0 @@
|
||||
import type { User } from '@prisma/client';
|
||||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
|
||||
import { hashSync } from '@documenso/lib/server-only/auth/hash';
|
||||
|
||||
import { prisma } from '..';
|
||||
import {
|
||||
DocumentDataType,
|
||||
DocumentStatus,
|
||||
FieldType,
|
||||
Prisma,
|
||||
ReadStatus,
|
||||
SendStatus,
|
||||
SigningStatus,
|
||||
} from '../client';
|
||||
|
||||
//
|
||||
// https://github.com/documenso/documenso/pull/713
|
||||
//
|
||||
|
||||
const PULL_REQUEST_NUMBER = 713;
|
||||
|
||||
const EMAIL_DOMAIN = `pr-${PULL_REQUEST_NUMBER}.documenso.com`;
|
||||
|
||||
export const TEST_USERS = [
|
||||
{
|
||||
name: 'User 1',
|
||||
email: `user1@${EMAIL_DOMAIN}`,
|
||||
password: 'Password123',
|
||||
},
|
||||
{
|
||||
name: 'User 2',
|
||||
email: `user2@${EMAIL_DOMAIN}`,
|
||||
password: 'Password123',
|
||||
},
|
||||
] as const;
|
||||
|
||||
const examplePdf = fs
|
||||
.readFileSync(path.join(__dirname, '../../../assets/example.pdf'))
|
||||
.toString('base64');
|
||||
|
||||
export const seedDatabase = async () => {
|
||||
const users = await Promise.all(
|
||||
TEST_USERS.map(async (u) =>
|
||||
prisma.user.create({
|
||||
data: {
|
||||
name: u.name,
|
||||
email: u.email,
|
||||
password: hashSync(u.password),
|
||||
emailVerified: new Date(),
|
||||
url: u.email,
|
||||
},
|
||||
}),
|
||||
),
|
||||
);
|
||||
|
||||
const [user1, user2] = users;
|
||||
|
||||
await createSentDocument(user1, [user2]);
|
||||
await createReceivedDocument(user2, [user1]);
|
||||
};
|
||||
|
||||
const createSentDocument = async (sender: User, recipients: User[]) => {
|
||||
const documentData = await prisma.documentData.create({
|
||||
data: {
|
||||
type: DocumentDataType.BYTES_64,
|
||||
data: examplePdf,
|
||||
initialData: examplePdf,
|
||||
},
|
||||
});
|
||||
|
||||
const document = await prisma.document.create({
|
||||
data: {
|
||||
title: `[${PULL_REQUEST_NUMBER}] Document - Sent`,
|
||||
status: DocumentStatus.PENDING,
|
||||
documentDataId: documentData.id,
|
||||
userId: sender.id,
|
||||
},
|
||||
});
|
||||
|
||||
for (const recipient of recipients) {
|
||||
const index = recipients.indexOf(recipient);
|
||||
|
||||
await prisma.recipient.create({
|
||||
data: {
|
||||
email: String(recipient.email),
|
||||
name: String(recipient.name),
|
||||
token: `sent-token-${index}`,
|
||||
readStatus: ReadStatus.NOT_OPENED,
|
||||
sendStatus: SendStatus.SENT,
|
||||
signingStatus: SigningStatus.NOT_SIGNED,
|
||||
signedAt: new Date(),
|
||||
Document: {
|
||||
connect: {
|
||||
id: document.id,
|
||||
},
|
||||
},
|
||||
Field: {
|
||||
create: {
|
||||
page: 1,
|
||||
type: FieldType.NAME,
|
||||
inserted: true,
|
||||
customText: String(recipient.name),
|
||||
positionX: new Prisma.Decimal(1),
|
||||
positionY: new Prisma.Decimal(1),
|
||||
width: new Prisma.Decimal(1),
|
||||
height: new Prisma.Decimal(1),
|
||||
documentId: document.id,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const createReceivedDocument = async (sender: User, recipients: User[]) => {
|
||||
const documentData = await prisma.documentData.create({
|
||||
data: {
|
||||
type: DocumentDataType.BYTES_64,
|
||||
data: examplePdf,
|
||||
initialData: examplePdf,
|
||||
},
|
||||
});
|
||||
|
||||
const document = await prisma.document.create({
|
||||
data: {
|
||||
title: `[${PULL_REQUEST_NUMBER}] Document - Received`,
|
||||
status: DocumentStatus.PENDING,
|
||||
documentDataId: documentData.id,
|
||||
userId: sender.id,
|
||||
},
|
||||
});
|
||||
|
||||
for (const recipient of recipients) {
|
||||
const index = recipients.indexOf(recipient);
|
||||
|
||||
await prisma.recipient.create({
|
||||
data: {
|
||||
email: String(recipient.email),
|
||||
name: String(recipient.name),
|
||||
token: `received-token-${index}`,
|
||||
readStatus: ReadStatus.NOT_OPENED,
|
||||
sendStatus: SendStatus.SENT,
|
||||
signingStatus: SigningStatus.NOT_SIGNED,
|
||||
signedAt: new Date(),
|
||||
Document: {
|
||||
connect: {
|
||||
id: document.id,
|
||||
},
|
||||
},
|
||||
Field: {
|
||||
create: {
|
||||
page: 1,
|
||||
type: FieldType.NAME,
|
||||
inserted: true,
|
||||
customText: String(recipient.name),
|
||||
positionX: new Prisma.Decimal(1),
|
||||
positionY: new Prisma.Decimal(1),
|
||||
width: new Prisma.Decimal(1),
|
||||
height: new Prisma.Decimal(1),
|
||||
documentId: document.id,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -1,29 +0,0 @@
|
||||
import { hashSync } from '@documenso/lib/server-only/auth/hash';
|
||||
|
||||
import { prisma } from '..';
|
||||
|
||||
//
|
||||
// https://github.com/documenso/documenso/pull/713
|
||||
//
|
||||
|
||||
const PULL_REQUEST_NUMBER = 718;
|
||||
|
||||
const EMAIL_DOMAIN = `pr-${PULL_REQUEST_NUMBER}.documenso.com`;
|
||||
|
||||
export const TEST_USER = {
|
||||
name: 'User 1',
|
||||
email: `user1@${EMAIL_DOMAIN}`,
|
||||
password: 'Password123',
|
||||
} as const;
|
||||
|
||||
export const seedDatabase = async () => {
|
||||
await prisma.user.create({
|
||||
data: {
|
||||
name: TEST_USER.name,
|
||||
email: TEST_USER.email,
|
||||
password: hashSync(TEST_USER.password),
|
||||
emailVerified: new Date(),
|
||||
url: TEST_USER.email,
|
||||
},
|
||||
});
|
||||
};
|
||||
19
packages/prisma/seed/subscriptions.ts
Normal file
19
packages/prisma/seed/subscriptions.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import { prisma } from '..';
|
||||
|
||||
export const seedTestEmail = () => `user-${Date.now()}@test.documenso.com`;
|
||||
|
||||
type SeedSubscriptionOptions = {
|
||||
userId: number;
|
||||
priceId: string;
|
||||
};
|
||||
|
||||
export const seedUserSubscription = async ({ userId, priceId }: SeedSubscriptionOptions) => {
|
||||
return await prisma.subscription.create({
|
||||
data: {
|
||||
userId,
|
||||
planId: Date.now().toString(),
|
||||
priceId,
|
||||
status: 'ACTIVE',
|
||||
},
|
||||
});
|
||||
};
|
||||
@@ -1,8 +1,11 @@
|
||||
import { customAlphabet } from 'nanoid';
|
||||
|
||||
import { prisma } from '..';
|
||||
import { TeamMemberInviteStatus, TeamMemberRole } from '../client';
|
||||
import { seedUser } from './users';
|
||||
|
||||
const EMAIL_DOMAIN = `test.documenso.com`;
|
||||
const nanoid = customAlphabet('1234567890abcdef', 10);
|
||||
|
||||
type SeedTeamOptions = {
|
||||
createTeamMembers?: number;
|
||||
@@ -13,7 +16,7 @@ export const seedTeam = async ({
|
||||
createTeamMembers = 0,
|
||||
createTeamEmail,
|
||||
}: SeedTeamOptions = {}) => {
|
||||
const teamUrl = `team-${Date.now()}`;
|
||||
const teamUrl = `team-${nanoid()}`;
|
||||
const teamEmail = createTeamEmail === true ? `${teamUrl}@${EMAIL_DOMAIN}` : createTeamEmail;
|
||||
|
||||
const teamOwner = await seedUser({
|
||||
|
||||
@@ -2,6 +2,7 @@ import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
|
||||
import { prisma } from '..';
|
||||
import type { Prisma, User } from '../client';
|
||||
import { DocumentDataType, ReadStatus, RecipientRole, SendStatus, SigningStatus } from '../client';
|
||||
|
||||
const examplePdf = fs
|
||||
@@ -14,6 +15,32 @@ type SeedTemplateOptions = {
|
||||
teamId?: number;
|
||||
};
|
||||
|
||||
type CreateTemplateOptions = {
|
||||
key?: string | number;
|
||||
createTemplateOptions?: Partial<Prisma.TemplateUncheckedCreateInput>;
|
||||
};
|
||||
|
||||
export const seedBlankTemplate = async (owner: User, options: CreateTemplateOptions = {}) => {
|
||||
const { key, createTemplateOptions = {} } = options;
|
||||
|
||||
const documentData = await prisma.documentData.create({
|
||||
data: {
|
||||
type: DocumentDataType.BYTES_64,
|
||||
data: examplePdf,
|
||||
initialData: examplePdf,
|
||||
},
|
||||
});
|
||||
|
||||
return await prisma.template.create({
|
||||
data: {
|
||||
title: `[TEST] Template ${key}`,
|
||||
templateDocumentDataId: documentData.id,
|
||||
userId: owner.id,
|
||||
...createTemplateOptions,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const seedTemplate = async (options: SeedTemplateOptions) => {
|
||||
const { title = 'Untitled', userId, teamId } = options;
|
||||
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
import { customAlphabet } from 'nanoid';
|
||||
|
||||
import { hashSync } from '@documenso/lib/server-only/auth/hash';
|
||||
|
||||
import { prisma } from '..';
|
||||
|
||||
export const seedTestEmail = () => `user-${Date.now()}@test.documenso.com`;
|
||||
|
||||
type SeedUserOptions = {
|
||||
name?: string;
|
||||
email?: string;
|
||||
@@ -9,12 +13,22 @@ type SeedUserOptions = {
|
||||
verified?: boolean;
|
||||
};
|
||||
|
||||
const nanoid = customAlphabet('1234567890abcdef', 10);
|
||||
|
||||
export const seedUser = async ({
|
||||
name = `user-${Date.now()}`,
|
||||
email = `user-${Date.now()}@test.documenso.com`,
|
||||
name,
|
||||
email,
|
||||
password = 'password',
|
||||
verified = true,
|
||||
}: SeedUserOptions = {}) => {
|
||||
if (!name) {
|
||||
name = nanoid();
|
||||
}
|
||||
|
||||
if (!email) {
|
||||
email = `${nanoid()}@test.documenso.com`;
|
||||
}
|
||||
|
||||
return await prisma.user.create({
|
||||
data: {
|
||||
name,
|
||||
|
||||
@@ -1,4 +1,10 @@
|
||||
import { Document, Recipient } from '@documenso/prisma/client';
|
||||
import type {
|
||||
Document,
|
||||
DocumentData,
|
||||
DocumentMeta,
|
||||
Field,
|
||||
Recipient,
|
||||
} from '@documenso/prisma/client';
|
||||
|
||||
export type DocumentWithRecipientAndSender = Omit<Document, 'document'> & {
|
||||
recipient: Recipient;
|
||||
@@ -10,3 +16,10 @@ export type DocumentWithRecipientAndSender = Omit<Document, 'document'> & {
|
||||
subject: string;
|
||||
description: string;
|
||||
};
|
||||
|
||||
export type DocumentWithDetails = Document & {
|
||||
documentData: DocumentData;
|
||||
documentMeta: DocumentMeta | null;
|
||||
Recipient: Recipient[];
|
||||
Field: Field[];
|
||||
};
|
||||
|
||||
19
packages/prisma/types/template.ts
Normal file
19
packages/prisma/types/template.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import type {
|
||||
DocumentData,
|
||||
Field,
|
||||
Recipient,
|
||||
Template,
|
||||
TemplateMeta,
|
||||
} from '@documenso/prisma/client';
|
||||
|
||||
export type TemplateWithData = Template & {
|
||||
templateDocumentData?: DocumentData | null;
|
||||
templateMeta?: TemplateMeta | null;
|
||||
};
|
||||
|
||||
export type TemplateWithDetails = Template & {
|
||||
templateDocumentData: DocumentData;
|
||||
templateMeta: TemplateMeta | null;
|
||||
Recipient: Recipient[];
|
||||
Field: Field[];
|
||||
};
|
||||
Reference in New Issue
Block a user