schema.prisma 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247
  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. duration Float?
  72. fps Float @default(30)
  73. codec String?
  74. mimeType String
  75. fileSize BigInt @default(0) // raw video file size in bytes
  76. status AssetStatus @default(PENDING_REVIEW)
  77. transcodeStatus TranscodeStatus @default(PENDING)
  78. transcodeProgress Int @default(0)
  79. transcodeError String?
  80. transcodePaused Boolean @default(false)
  81. createdAt DateTime @default(now())
  82. updatedAt DateTime @updatedAt
  83. project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)
  84. uploader User? @relation(fields: [uploaderId], references: [id], onDelete: SetNull)
  85. comments Comment[]
  86. shareLinks AssetShareLink[]
  87. folderAssets FolderAsset[]
  88. }
  89. model Comment {
  90. id String @id @default(cuid())
  91. assetId String
  92. userId String
  93. content String
  94. timestamp Float?
  95. annotations Json?
  96. resolved Boolean @default(false)
  97. resolveStatus ResolveStatus @default(UNRESOLVED)
  98. resolvedById String?
  99. resolvedByAt DateTime?
  100. requestedById String?
  101. requestedByAt DateTime?
  102. parentId String?
  103. deleted Boolean @default(false)
  104. deletedAt DateTime?
  105. deletedById String?
  106. createdAt DateTime @default(now())
  107. updatedAt DateTime @updatedAt
  108. asset Asset @relation(fields: [assetId], references: [id], onDelete: Cascade)
  109. user User @relation(fields: [userId], references: [id], onDelete: Cascade)
  110. parent Comment? @relation("Replies", fields: [parentId], references: [id], onDelete: Cascade)
  111. replies Comment[] @relation("Replies")
  112. resolvedBy User? @relation("ResolvedBy", fields: [resolvedById], references: [id])
  113. requestedBy User? @relation("RequestedBy", fields: [requestedById], references: [id])
  114. }
  115. enum Role {
  116. ADMIN
  117. EDITOR
  118. REVIEWER
  119. VIEWER
  120. }
  121. enum GlobalRole {
  122. ADMIN // system-wide admin: manage users, all projects, quotas
  123. MEMBER // registered user: create own projects, invite members
  124. PROJECT_USER // invited user: no project creation, workspace visibility only via invite
  125. }
  126. enum InvitationStatus {
  127. PENDING
  128. ACCEPTED
  129. EXPIRED
  130. REVOKED
  131. }
  132. model Invitation {
  133. id String @id @default(cuid())
  134. email String // invitee email
  135. projectId String? // null = workspace invite (creates MEMBER); set = project invite (creates PROJECT_USER)
  136. type InvitationType @default(PROJECT)
  137. role Role @default(REVIEWER)
  138. token String @unique
  139. status InvitationStatus @default(PENDING)
  140. invitedBy String? // userId who sent the invite
  141. expiresAt DateTime
  142. createdAt DateTime @default(now())
  143. project Project? @relation(fields: [projectId], references: [id], onDelete: Cascade)
  144. @@index([projectId])
  145. @@index([email])
  146. @@index([token])
  147. }
  148. enum InvitationType {
  149. WORKSPACE // admin invites MEMBER — no project attached, user registers as MEMBER
  150. PROJECT // admin/project member invites PROJECT_USER — attached to a project
  151. }
  152. enum AssetStatus {
  153. PENDING_REVIEW
  154. CHANGES_REQUESTED
  155. APPROVED
  156. REJECTED
  157. }
  158. enum ResolveStatus {
  159. UNRESOLVED // no request made
  160. PENDING_APPROVAL // someone requested resolve, awaiting approval
  161. RESOLVED // approved and closed
  162. }
  163. enum TranscodeStatus {
  164. PENDING
  165. UPLOADING
  166. PROCESSING
  167. COMPLETED
  168. FAILED
  169. UNSUPPORTED_CODEC
  170. }
  171. // ── Public share links ────────────────────────────────────────────────────────
  172. model AssetShareLink {
  173. id String @id @default(cuid())
  174. assetId String
  175. token String @unique
  176. password String? // bcrypt hash, null = no password required
  177. allowDownload Boolean @default(false)
  178. maxViews Int @default(20) // 0 or -1 = unlimited
  179. viewCount Int @default(0)
  180. createdById String
  181. createdAt DateTime @default(now())
  182. updatedAt DateTime @updatedAt
  183. asset Asset @relation(fields: [assetId], references: [id], onDelete: Cascade)
  184. createdBy User @relation(fields: [createdById], references: [id])
  185. @@index([assetId])
  186. @@index([token])
  187. }
  188. // ── Folder system ──────────────────────────────────────────────────────────────
  189. model Folder {
  190. id String @id @default(cuid())
  191. name String
  192. projectId String
  193. parentId String?
  194. order Int @default(0)
  195. createdAt DateTime @default(now())
  196. updatedAt DateTime @updatedAt
  197. project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)
  198. parent Folder? @relation("FolderTree", fields: [parentId], references: [id], onDelete: Cascade)
  199. children Folder[] @relation("FolderTree")
  200. assets FolderAsset[]
  201. @@index([projectId])
  202. @@index([parentId])
  203. }
  204. model FolderAsset {
  205. id String @id @default(cuid())
  206. folderId String
  207. assetId String
  208. addedAt DateTime @default(now())
  209. folder Folder @relation(fields: [folderId], references: [id], onDelete: Cascade)
  210. asset Asset @relation(fields: [assetId], references: [id], onDelete: Cascade)
  211. @@unique([folderId, assetId])
  212. }