163 lines
4.8 KiB
Plaintext
163 lines
4.8 KiB
Plaintext
// P4 Files (AK-P4a) — Files platform primitive.
|
|
//
|
|
// AD-21: one self-contained schema file per module. `tenantId` /
|
|
// `createdByUserId` / `assignedToUserId` are intentionally BARE String scalars
|
|
// (no `@relation`) so this file has zero merge-conflict surface against
|
|
// `core.prisma`/`accounts.prisma` (no back-relation fields on Tenant/User
|
|
// needed). Referential integrity is carried by app-level scoping
|
|
// (withTenantScope, §7) plus the RLS policies from the merge migration (§8).
|
|
//
|
|
// Deliberately NOT reused: the legacy boilerplate models `FileObject`,
|
|
// `FileChunk`, `FileUploadProgress` in core.prisma. The models here are named
|
|
// differently (File, FileEntityLink, FileUpload, FileUploadChunk) to avoid a
|
|
// name collision and any core edit.
|
|
//
|
|
// Scope v1 (designer brief 2026-06-12): NO versioning (FileVersion = v2),
|
|
// no Office/Video previews, no resume upload, no OCR.
|
|
|
|
enum FilePreviewStatus {
|
|
pending
|
|
generating
|
|
ready
|
|
failed
|
|
not_supported
|
|
}
|
|
|
|
enum FileScanStatus {
|
|
pending
|
|
clean
|
|
infected
|
|
skipped
|
|
}
|
|
|
|
enum FileSource {
|
|
manual_upload
|
|
customer_portal
|
|
form_submission
|
|
email_attachment
|
|
automation
|
|
api
|
|
}
|
|
|
|
enum FileUploadStatus {
|
|
in_progress
|
|
completed
|
|
failed
|
|
expired
|
|
cancelled
|
|
}
|
|
|
|
enum FileEntityType {
|
|
contact
|
|
company
|
|
lead
|
|
deal
|
|
project
|
|
user
|
|
form_submission
|
|
order
|
|
task
|
|
appointment
|
|
}
|
|
|
|
/// A stored file, scoped per tenant. `storageKey` points at the original object
|
|
/// in the private Hetzner bucket (AD-23); delivery is only via the auth-proxy
|
|
/// route. Soft-delete via `archivedAt` (archive flag). Hard-delete is a separate
|
|
/// action (files.delete).
|
|
model File {
|
|
id String @id @default(cuid())
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
tenantId String
|
|
createdByUserId String
|
|
displayName String
|
|
mimeType String
|
|
sizeBytes Int
|
|
storageKey String
|
|
checksumSha256 String?
|
|
previewStatus FilePreviewStatus @default(pending)
|
|
previewStorageKey String?
|
|
thumbnailStorageKey String?
|
|
scanStatus FileScanStatus @default(pending)
|
|
assignedToUserId String?
|
|
tags String[] @default([])
|
|
notes String?
|
|
source FileSource @default(manual_upload)
|
|
// Customer-portal upload prepared (DoD 12): nullable attribution, full portal
|
|
// flow = v2. Defaults to empty/invisible.
|
|
createdByPortalUserId String?
|
|
isDemoData Boolean @default(false)
|
|
archivedAt DateTime?
|
|
|
|
links FileEntityLink[]
|
|
|
|
@@index([tenantId, archivedAt, createdAt(sort: Desc)])
|
|
@@index([tenantId, createdByUserId])
|
|
@@index([tenantId, assignedToUserId])
|
|
@@index([tenantId, checksumSha256])
|
|
}
|
|
|
|
/// Polymorphic link of a file to any entity (cross-cutting). `entityType` +
|
|
/// `entityId` address the target entity, `linkRole` is an optional role label
|
|
/// (e.g. "contract", "photo").
|
|
model FileEntityLink {
|
|
id String @id @default(cuid())
|
|
createdAt DateTime @default(now())
|
|
tenantId String
|
|
createdByUserId String
|
|
fileId String
|
|
entityType FileEntityType
|
|
entityId String
|
|
linkRole String?
|
|
|
|
file File @relation(fields: [fileId], references: [id], onDelete: Cascade)
|
|
|
|
@@unique([fileId, entityType, entityId])
|
|
@@index([tenantId, entityType, entityId])
|
|
@@index([tenantId, fileId])
|
|
}
|
|
|
|
/// Chunked-upload session (replaces spec `FileUploadProgress`, renamed to avoid
|
|
/// the core collision). No resume in v1 — this session only carries progress up
|
|
/// to `complete`. `expiresAt` = now + 24h; a cleanup cron sweeps expired
|
|
/// sessions + temp chunks.
|
|
model FileUpload {
|
|
id String @id @default(cuid())
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
tenantId String
|
|
createdByUserId String
|
|
originalFilename String
|
|
totalSizeBytes Int
|
|
mimeType String
|
|
totalChunks Int
|
|
receivedChunks Int @default(0)
|
|
status FileUploadStatus @default(in_progress)
|
|
targetFileId String?
|
|
expiresAt DateTime
|
|
failureReason String?
|
|
|
|
chunks FileUploadChunk[]
|
|
|
|
@@index([tenantId, createdByUserId, status])
|
|
@@index([expiresAt])
|
|
}
|
|
|
|
/// One uploaded chunk of a `FileUpload` session (replaces spec `FileChunk`).
|
|
/// Bytes live in temp storage (`storageKey`), not in the DB.
|
|
model FileUploadChunk {
|
|
id String @id @default(cuid())
|
|
tenantId String
|
|
uploadId String
|
|
chunkIndex Int
|
|
storageKey String
|
|
sizeBytes Int
|
|
checksumSha256 String?
|
|
receivedAt DateTime @default(now())
|
|
|
|
upload FileUpload @relation(fields: [uploadId], references: [id], onDelete: Cascade)
|
|
|
|
@@unique([uploadId, chunkIndex])
|
|
@@index([tenantId, uploadId])
|
|
}
|