Files
zeekayandHanzo Dev fa7c528ae4 feat(db): migrate Prisma datasource postgres -> sqlite (native, file-backed)
Drops the shared sql-0 postgres dependency in favor of an embedded SQLite
file (DATABASE_URL=file:/data/<db>), WAL-replicated to SeaweedFS by the
operator persistence sidecar — same pattern as esign (Documenso).

Schema changes for the sqlite connector:
- datasource provider postgresql -> sqlite; drop directUrl/shadowDatabaseUrl
- 18 enums -> String (values preserved as string defaults)
- scalar-list fields (String[]/Int[]) -> Json @default("[]") (arrays
  round-trip through Prisma Json at runtime; next.config ignoreBuildErrors
  keeps the type-only churn out of the build)
- strip pg-native @db.* attributes (@db.Text/@db.Timestamp)
- startup: prisma migrate deploy -> prisma db push (pg migration history is
  postgres-specific; db push reconciles the sqlite schema idempotently)

CI: amd64-only (DOKS has no arm64); deploy:false — rollout is operator-managed
via the universe Service CR, never `kubectl set image`.

Verified: `prisma validate` + `prisma db push` materialize all 62 tables on a
fresh sqlite file. Source DB is empty (0 app rows), so cutover is data-safe.

Co-authored-by: Hanzo Dev <[email protected]>
2026-07-05 00:33:13 -07:00

209 lines
9.5 KiB
Plaintext

model Link {
id String @id @default(cuid())
document Document? @relation(fields: [documentId], references: [id], onDelete: Cascade)
documentId String? // This can be nullable, representing links without documents
dataroom Dataroom? @relation(fields: [dataroomId], references: [id], onDelete: Cascade)
dataroomId String? // This can be nullable, representing links without datarooms
linkType String @default("DOCUMENT_LINK") // This will store the type of the link
url String? @unique
name String? // Link name
slug String? // Link slug for pretty URLs
expiresAt DateTime? // Optional expiration date
password String? // Optional password for link protection
allowList Json @default("[]")// Array of emails and domains allowed to view the document
denyList Json @default("[]")// Array of emails and domains denied to view the document
emailProtected Boolean @default(true) // Optional email protection
emailAuthenticated Boolean @default(false) // Optional email authentication flag
allowDownload Boolean? @default(false) // Optional give user a option to allow to download the document
isArchived Boolean @default(false) // Indicates if the link is archived
deletedAt DateTime? // Soft delete timestamp
views View[]
domain Domain? @relation(fields: [domainId], references: [id], onDelete: SetNull)
domainId String? // This can be nullable, representing links without custom domains
domainSlug String? // This will store the domain's slug even if the domain is deleted
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
enableNotification Boolean? @default(true) // Optional give user a option to pause/resume the notifications
enableFeedback Boolean? @default(false) // Optional give user a option to enable the reactions toolbar
enableQuestion Boolean? @default(false) // Optional give user a option to enable the question feedback
enableScreenshotProtection Boolean? @default(false) // Optional give user a option to enable the screenshot protection
feedback Feedback?
enableAgreement Boolean? @default(false) // Optional give user a option to enable the terms and conditions
agreement Agreement? @relation(fields: [agreementId], references: [id], onDelete: SetNull)
agreementId String? // This can be nullable, representing links without agreements
showBanner Boolean? @default(false) // Optional give user a option to show the banner and end of document signup form
enableWatermark Boolean? @default(false) // Optional give user a option to enable the watermark
watermarkConfig Json? // This will store the watermark configuration: {text: "Confidential", isTiled: false, color: "#000000", fontSize: 12, opacity: 0.5, rotation: 30, position: "top-right"}
// group links
audienceType String @default("GENERAL") // This will store the audience type of the link
groupId String?
group ViewerGroup? @relation(fields: [groupId], references: [id], onDelete: SetNull)
// granular permissions
permissionGroupId String?
permissionGroup PermissionGroup? @relation(fields: [permissionGroupId], references: [id], onDelete: SetNull)
// custom metatags
metaTitle String? // This will be the meta title of the link
metaDescription String? // This will be the meta description of the link
metaImage String? // This will be the meta image of the link
metaFavicon String? // This will be the meta favicon of the link
enableCustomMetatag Boolean? @default(false) // Optional give user a option to enable the custom metatag
// custom welcome message (overrides brand welcome message)
welcomeMessage String? // Custom welcome message for this specific link
// conversation
enableConversation Boolean @default(false) // Controls if conversations are allowed on this link
conversations Conversation[]
// FAQ system
dataroomFaqItems DataroomFaqItem[]
// AI chats
enableAIAgents Boolean? @default(false) // Enable AI agents for this link
chats Chat[]
// upload
enableUpload Boolean? @default(false) // Optional give user a option to enable the upload document function
isFileRequestOnly Boolean? @default(false) // Optional give user a option to enable the file request only
uploadFolderId String? // This can be nullable, indicating upload to root folder, either document folder or dataroom folder
enableIndexFile Boolean? @default(false)
uploadedDocuments DocumentUpload[]
teamId String?
team Team? @relation(fields: [teamId], references: [id], onDelete: Cascade)
ownerId String?
owner User? @relation("LinkOwner", fields: [ownerId], references: [id], onDelete: SetNull)
customFields CustomField[]
tags TagItem[]
viewerInvitations ViewerInvitation[]
// visitor groups for allow-list
visitorGroups LinkVisitorGroup[]
// workflow entry link
workflow Workflow?
@@unique([domainSlug, slug])
@@index([documentId])
@@index([teamId])
@@index([documentId, isArchived]) // Performance optimization for active links filtering
@@index([permissionGroupId]) // Performance optimization for permission group queries
@@index([deletedAt]) // Performance optimization for filtering deleted links
@@index([ownerId]) // Performance optimization for link owner queries
}
model LinkPreset {
id String @id @default(cuid())
name String
teamId String
team Team @relation(fields: [teamId], references: [id], onDelete: Cascade)
pId String? @unique
enableCustomMetaTag Boolean? @default(false) // Optional give user a option to enable the custom metatag
metaTitle String? // This will be the meta title of the link
metaDescription String? // This will be the meta description of the link
metaImage String? // This will be the meta image of the link
metaFavicon String? // This will be the meta favicon of the link
enableNotification Boolean? @default(false)
emailProtected Boolean? @default(true)
emailAuthenticated Boolean? @default(false)
allowDownload Boolean? @default(false)
enableAllowList Boolean? @default(false)
allowList Json@default("[]")
enableDenyList Boolean? @default(false)
denyList Json@default("[]")
expiresIn Int? // how many days from link creation until it expires in seconds
enableScreenshotProtection Boolean? @default(false)
expiresAt DateTime?
enablePassword Boolean? @default(false)
password String?
enableWatermark Boolean? @default(false)
watermarkConfig Json? //{text: "Confidential", isTiled: false, color: "#000000", fontSize: 12, opacity: 0.5, rotation: 30, position: "top-right"}
enableAgreement Boolean? @default(false)
agreementId String?
enableCustomFields Boolean? @default(false)
customFields Json? //[{type: "SHORT_TEXT", identifier: "name", label: "Name", required: true}]
showBanner Boolean? @default(false) // Optional give user a option to show the "Powered by Papermark" banner
welcomeMessage String? // Custom welcome message for links created from this preset
isDefault Boolean @default(false)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([teamId])
}
model VisitorGroup {
id String @id @default(cuid())
name String
emails Json @default("[]")// List of emails and domains (e.g., "[email protected]", "@example.org")
teamId String
team Team @relation(fields: [teamId], references: [id], onDelete: Cascade)
links LinkVisitorGroup[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([teamId])
}
model LinkVisitorGroup {
id String @id @default(cuid())
linkId String
link Link @relation(fields: [linkId], references: [id], onDelete: Cascade)
visitorGroupId String
visitorGroup VisitorGroup @relation(fields: [visitorGroupId], references: [id], onDelete: Cascade)
createdAt DateTime @default(now())
@@unique([linkId, visitorGroupId])
@@index([linkId])
@@index([visitorGroupId])
}
model CustomField {
id String @id @default(cuid())
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
type String
identifier String
label String
placeholder String?
required Boolean @default(false)
disabled Boolean @default(false)
link Link @relation(fields: [linkId], references: [id], onDelete: Cascade)
linkId String
orderIndex Int @default(0)
@@index([linkId])
}
model CustomFieldResponse {
id String @id @default(cuid())
data Json // Store the custom field responses as a JSON object [{ "identifier": "value", "label": "value", "response:" }]
viewId String @unique
view View @relation(fields: [viewId], references: [id], onDelete: Cascade)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([viewId])
}