schema.prisma 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255
  1. // This is your Prisma schema file,
  2. // learn more about it in the docs: https://pris.ly/d/prisma-schema
  3. generator client {
  4. provider = "prisma-client-js"
  5. }
  6. datasource db {
  7. provider = "postgresql"
  8. url = env("DATABASE_URL")
  9. }
  10. model User {
  11. id String @id @default(cuid())
  12. email String @unique
  13. name String
  14. password String
  15. avatarUrl String?
  16. globalRole GlobalRole @default(MEMBER)
  17. active Boolean @default(true)
  18. storageQuota BigInt @default(524288000) // 500 MB in bytes
  19. storageUsed BigInt @default(0) // bytes consumed
  20. createdAt DateTime @default(now())
  21. updatedAt DateTime @updatedAt
  22. memberships ProjectMember[] @relation("ProjectMembers")
  23. comments Comment[]
  24. projects Project[] @relation("ProjectOwner")
  25. resolvedComments Comment[] @relation("ResolvedBy")
  26. requestedComments Comment[] @relation("RequestedBy")
  27. assets Asset[]
  28. shareLinks AssetShareLink[]
  29. }
  30. model Project {
  31. id String @id @default(cuid())
  32. name String
  33. description String?
  34. ownerId String
  35. createdAt DateTime @default(now())
  36. updatedAt DateTime @updatedAt
  37. assets Asset[]
  38. members ProjectMember[] @relation("ProjectMembers")
  39. invitations Invitation[]
  40. owner User @relation("ProjectOwner", fields: [ownerId], references: [id])
  41. folders Folder[]
  42. }
  43. model SiteSetting {
  44. id String @id @default(cuid())
  45. name String @unique
  46. value String
  47. }
  48. model ProjectMember {
  49. id String @id @default(cuid())
  50. userId String
  51. projectId String
  52. role Role @default(REVIEWER)
  53. joinedAt DateTime @default(now())
  54. invitedBy String? // userId who sent the invite
  55. user User @relation("ProjectMembers", fields: [userId], references: [id], onDelete: Cascade)
  56. project Project @relation("ProjectMembers", fields: [projectId], references: [id], onDelete: Cascade)
  57. @@unique([userId, projectId])
  58. @@index([projectId])
  59. @@index([userId])
  60. }
  61. model Asset {
  62. id String @id @default(cuid())
  63. projectId String
  64. uploaderId String? // null for legacy assets before this feature
  65. title String
  66. filename String
  67. originalFilename String?
  68. filePath String
  69. thumbnail String?
  70. hlsPath String?
  71. originalFilePath String?
  72. maxResolution String?
  73. duration Float?
  74. fps Float @default(30)
  75. codec String?
  76. mimeType String
  77. fileSize BigInt @default(0) // raw video file size in bytes
  78. bitrate BigInt @default(0) // video bitrate in bits/s
  79. status AssetStatus @default(PENDING_REVIEW)
  80. transcodeStatus TranscodeStatus @default(PENDING)
  81. transcodeProgress Int @default(0)
  82. transcodeError String?
  83. transcodePaused Boolean @default(false)
  84. createdAt DateTime @default(now())
  85. updatedAt DateTime @updatedAt
  86. project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)
  87. uploader User? @relation(fields: [uploaderId], references: [id], onDelete: SetNull)
  88. comments Comment[]
  89. shareLinks AssetShareLink[]
  90. folderAssets FolderAsset[]
  91. @@index([transcodeStatus, transcodePaused]) // claimOneJob() hot path
  92. @@index([transcodeStatus, updatedAt]) // resetStuckJobs() watchdog
  93. }
  94. model Comment {
  95. id String @id @default(cuid())
  96. assetId String
  97. userId String?
  98. guestName String?
  99. content String
  100. timestamp Float?
  101. annotations Json?
  102. resolved Boolean @default(false)
  103. resolveStatus ResolveStatus @default(UNRESOLVED)
  104. resolvedById String?
  105. resolvedByAt DateTime?
  106. requestedById String?
  107. requestedByAt DateTime?
  108. parentId String?
  109. deleted Boolean @default(false)
  110. deletedAt DateTime?
  111. deletedById String?
  112. createdAt DateTime @default(now())
  113. updatedAt DateTime @updatedAt
  114. asset Asset @relation(fields: [assetId], references: [id], onDelete: Cascade)
  115. user User? @relation(fields: [userId], references: [id], onDelete: Cascade)
  116. parent Comment? @relation("Replies", fields: [parentId], references: [id], onDelete: Cascade)
  117. replies Comment[] @relation("Replies")
  118. resolvedBy User? @relation("ResolvedBy", fields: [resolvedById], references: [id])
  119. requestedBy User? @relation("RequestedBy", fields: [requestedById], references: [id])
  120. }
  121. enum Role {
  122. ADMIN
  123. EDITOR
  124. REVIEWER
  125. VIEWER
  126. }
  127. enum GlobalRole {
  128. ADMIN // system-wide admin: manage users, all projects, quotas
  129. MEMBER // registered user: create own projects, invite members
  130. PROJECT_USER // invited user: no project creation, workspace visibility only via invite
  131. }
  132. enum InvitationStatus {
  133. PENDING
  134. ACCEPTED
  135. EXPIRED
  136. REVOKED
  137. }
  138. model Invitation {
  139. id String @id @default(cuid())
  140. email String // invitee email
  141. projectId String? // null = workspace invite (creates MEMBER); set = project invite (creates PROJECT_USER)
  142. type InvitationType @default(PROJECT)
  143. role Role @default(REVIEWER)
  144. token String @unique
  145. status InvitationStatus @default(PENDING)
  146. invitedBy String? // userId who sent the invite
  147. expiresAt DateTime
  148. createdAt DateTime @default(now())
  149. project Project? @relation(fields: [projectId], references: [id], onDelete: Cascade)
  150. @@index([projectId])
  151. @@index([email])
  152. @@index([token])
  153. }
  154. enum InvitationType {
  155. WORKSPACE // admin invites MEMBER — no project attached, user registers as MEMBER
  156. PROJECT // admin/project member invites PROJECT_USER — attached to a project
  157. }
  158. enum AssetStatus {
  159. PENDING_REVIEW
  160. CHANGES_REQUESTED
  161. APPROVED
  162. REJECTED
  163. }
  164. enum ResolveStatus {
  165. UNRESOLVED // no request made
  166. PENDING_APPROVAL // someone requested resolve, awaiting approval
  167. RESOLVED // approved and closed
  168. }
  169. enum TranscodeStatus {
  170. PENDING
  171. UPLOADING
  172. PROCESSING
  173. COMPLETED
  174. FAILED
  175. UNSUPPORTED_CODEC
  176. }
  177. // ── Public share links ────────────────────────────────────────────────────────
  178. model AssetShareLink {
  179. id String @id @default(cuid())
  180. assetId String
  181. token String @unique
  182. password String? // bcrypt hash, null = no password required
  183. allowDownload Boolean @default(false)
  184. allowUnregisteredComments Boolean @default(false)
  185. maxViews Int @default(20) // 0 or -1 = unlimited
  186. viewCount Int @default(0)
  187. createdById String
  188. createdAt DateTime @default(now())
  189. updatedAt DateTime @updatedAt
  190. asset Asset @relation(fields: [assetId], references: [id], onDelete: Cascade)
  191. createdBy User @relation(fields: [createdById], references: [id])
  192. @@index([assetId])
  193. @@index([token])
  194. }
  195. // ── Folder system ──────────────────────────────────────────────────────────────
  196. model Folder {
  197. id String @id @default(cuid())
  198. name String
  199. projectId String
  200. parentId String?
  201. order Int @default(0)
  202. createdAt DateTime @default(now())
  203. updatedAt DateTime @updatedAt
  204. project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)
  205. parent Folder? @relation("FolderTree", fields: [parentId], references: [id], onDelete: Cascade)
  206. children Folder[] @relation("FolderTree")
  207. assets FolderAsset[]
  208. @@index([projectId])
  209. @@index([parentId])
  210. }
  211. model FolderAsset {
  212. id String @id @default(cuid())
  213. folderId String
  214. assetId String
  215. addedAt DateTime @default(now())
  216. folder Folder @relation(fields: [folderId], references: [id], onDelete: Cascade)
  217. asset Asset @relation(fields: [assetId], references: [id], onDelete: Cascade)
  218. @@unique([folderId, assetId])
  219. }