Akihira77 revised this gist 2 weeks ago. Go to revision
1 file changed, 684 insertions
schema.prisma(file created)
| @@ -0,0 +1,684 @@ | |||
| 1 | + | // This is your Prisma schema file, | |
| 2 | + | // learn more about it in the docs: https://pris.ly/d/prisma-schema | |
| 3 | + | ||
| 4 | + | // Looking for ways to speed up your queries, or scale easily with your serverless or edge functions? | |
| 5 | + | // Try Prisma Accelerate: https://pris.ly/cli/accelerate-init | |
| 6 | + | ||
| 7 | + | generator client { | |
| 8 | + | provider = "prisma-client-js" | |
| 9 | + | previewFeatures = [] | |
| 10 | + | } | |
| 11 | + | ||
| 12 | + | datasource db { | |
| 13 | + | provider = "mysql" | |
| 14 | + | url = env("DATABASE_URL") | |
| 15 | + | } | |
| 16 | + | ||
| 17 | + | /** | |
| 18 | + | * Enums | |
| 19 | + | */ | |
| 20 | + | // Enums for Document | |
| 21 | + | enum DocumentType { | |
| 22 | + | Design_Development | |
| 23 | + | Detail_Engineering_Design | |
| 24 | + | For_Construction | |
| 25 | + | Shop_Drawing | |
| 26 | + | Sitemap | |
| 27 | + | As_Build_Drawing | |
| 28 | + | ||
| 29 | + | Work_Permit | |
| 30 | + | Method_Of_Work | |
| 31 | + | Material_Testing | |
| 32 | + | Site_Instruction | |
| 33 | + | } | |
| 34 | + | ||
| 35 | + | enum VersionStatus { | |
| 36 | + | IN_REVIEW | |
| 37 | + | APPROVED | |
| 38 | + | APPROVED_WITH_NOTES | |
| 39 | + | NEED_IMPROVEMENT | |
| 40 | + | REJECTED | |
| 41 | + | REJECTED_WITH_NOTES | |
| 42 | + | REJECTED_RESUBMISSION | |
| 43 | + | SCHEDULED // version reserved on document-service for a not-yet-sent scheduled transmittal | |
| 44 | + | } | |
| 45 | + | ||
| 46 | + | enum RevisionStatus { | |
| 47 | + | IN_REVIEW | |
| 48 | + | APPROVED | |
| 49 | + | APPROVED_WITH_NOTES | |
| 50 | + | NEED_IMPROVEMENT | |
| 51 | + | REJECTED | |
| 52 | + | REJECTED_WITH_NOTES | |
| 53 | + | REJECTED_RESUBMISSION | |
| 54 | + | } | |
| 55 | + | ||
| 56 | + | enum DocumentPageStatus { | |
| 57 | + | queue // Initial state | |
| 58 | + | extracting_preview // Phase 1: Extracting preview (150 DPI) | |
| 59 | + | preview_ready // Phase 1 complete: Preview available | |
| 60 | + | extracting_quality // Phase 2: Extracting full quality | |
| 61 | + | compressing // Optional: WebP compression | |
| 62 | + | ready // Final state: Full quality available | |
| 63 | + | } | |
| 64 | + | ||
| 65 | + | // Enums for Queue | |
| 66 | + | enum QueueDownloadStatus { | |
| 67 | + | PENDING | |
| 68 | + | IN_PROGRESS | |
| 69 | + | COMPLETED | |
| 70 | + | FAILED | |
| 71 | + | EXPIRED | |
| 72 | + | } | |
| 73 | + | ||
| 74 | + | enum QueueDownloadType { | |
| 75 | + | DIRECTORY | |
| 76 | + | DOCUMENT | |
| 77 | + | } | |
| 78 | + | ||
| 79 | + | enum QueueExtractorStatus { | |
| 80 | + | PENDING | |
| 81 | + | IN_PROGRESS | |
| 82 | + | COMPLETED | |
| 83 | + | FAILED | |
| 84 | + | EXPIRED | |
| 85 | + | } | |
| 86 | + | ||
| 87 | + | // Enums for Change Log (CDC) | |
| 88 | + | enum Operation { | |
| 89 | + | CREATE | |
| 90 | + | UPDATE | |
| 91 | + | DELETE | |
| 92 | + | } | |
| 93 | + | ||
| 94 | + | enum AutodeskTranslationStatus { | |
| 95 | + | PENDING | |
| 96 | + | IN_PROGRESS | |
| 97 | + | SUCCESS | |
| 98 | + | FAILED | |
| 99 | + | } | |
| 100 | + | ||
| 101 | + | /** | |
| 102 | + | * Models | |
| 103 | + | */ | |
| 104 | + | model Folder { | |
| 105 | + | id String @id @unique @db.VarChar(50) | |
| 106 | + | projectId String @db.VarChar(100) | |
| 107 | + | parentId String? @db.VarChar(50) | |
| 108 | + | modelType DocumentType? | |
| 109 | + | name String @db.VarChar(255) | |
| 110 | + | description String? @db.VarChar(500) | |
| 111 | + | companyId String? @db.VarChar(50) | |
| 112 | + | createdBy String? @db.VarChar(150) | |
| 113 | + | createdAt DateTime @default(now()) | |
| 114 | + | updatedBy String? @db.VarChar(150) | |
| 115 | + | updatedAt DateTime? | |
| 116 | + | inTrash Boolean @default(false) | |
| 117 | + | ||
| 118 | + | parent Folder? @relation("ParentChildren", fields: [parentId], references: [id], onDelete: Cascade) | |
| 119 | + | children Folder[] @relation("ParentChildren") | |
| 120 | + | documentVersions Version[] | |
| 121 | + | documentRevisions Revision[] | |
| 122 | + | ||
| 123 | + | @@index([projectId, parentId, modelType, companyId]) | |
| 124 | + | @@map("folders") | |
| 125 | + | } | |
| 126 | + | ||
| 127 | + | model Document { | |
| 128 | + | id String @id @unique @db.VarChar(50) | |
| 129 | + | projectId String @db.VarChar(100) | |
| 130 | + | companyId String? @db.VarChar(50) | |
| 131 | + | modelType DocumentType? | |
| 132 | + | filename String @db.VarChar(255) | |
| 133 | + | inTrash Boolean @default(false) | |
| 134 | + | activeVersionId String? @unique @db.VarChar(50) | |
| 135 | + | activeRevisionId String? @unique @db.VarChar(50) | |
| 136 | + | activeTransmittalId String? @db.VarChar(255) | |
| 137 | + | ||
| 138 | + | uploadedBy String? @db.VarChar(150) | |
| 139 | + | uploadedAt DateTime? @default(now()) | |
| 140 | + | updatedBy String? @db.VarChar(150) | |
| 141 | + | updatedAt DateTime? | |
| 142 | + | ||
| 143 | + | activeVersion Version? @relation("DocumentActiveVersion", fields: [activeVersionId], references: [id]) | |
| 144 | + | activeRevision Revision? @relation("DocumentActiveRevision", fields: [activeRevisionId], references: [id]) | |
| 145 | + | versions Version[] @relation("DocumentVersions") | |
| 146 | + | revisions Revision[] @relation("DocumentRevisions") | |
| 147 | + | relations Document[] @relation("DocumentRelation") | |
| 148 | + | related Document[] @relation("DocumentRelation") | |
| 149 | + | comments DocumentComments[] | |
| 150 | + | pages DocumentPages[] | |
| 151 | + | document3DMetadata Document3DMetadata[] @relation("Document3DMetadatas") | |
| 152 | + | ||
| 153 | + | @@index([projectId, modelType, activeTransmittalId]) | |
| 154 | + | @@map("documents") | |
| 155 | + | } | |
| 156 | + | ||
| 157 | + | model DocumentPages { | |
| 158 | + | id String @id @unique @db.VarChar(50) | |
| 159 | + | documentId String @db.VarChar(50) | |
| 160 | + | versionId String @db.VarChar(50) | |
| 161 | + | revisionId String @db.VarChar(50) | |
| 162 | + | queueId String? @db.VarChar(50) // Optional: tracks which queue job created this page | |
| 163 | + | status DocumentPageStatus @default(queue) | |
| 164 | + | ||
| 165 | + | pageOrder Int @default(0) | |
| 166 | + | pageName String? @db.VarChar(255) | |
| 167 | + | pageType String? @db.VarChar(255) | |
| 168 | + | pageContent String? @db.Text | |
| 169 | + | pageWidth Float @default(0) | |
| 170 | + | pageHeight Float @default(0) | |
| 171 | + | ||
| 172 | + | filePath String? @db.VarChar(512) // Increased from 255 for SHA256 hash-based paths | |
| 173 | + | fileSize Float @default(0) | |
| 174 | + | fileExtension String? @db.VarChar(10) | |
| 175 | + | fileMimeType String? @db.VarChar(255) | |
| 176 | + | ||
| 177 | + | // DPI tracking for two-phase extraction | |
| 178 | + | dpi Int @default(150) // Current DPI (150 for preview, 300-600 for quality) | |
| 179 | + | targetDpi Int? // Target DPI detected from PDF analysis | |
| 180 | + | extractionPreset String? @db.VarChar(50) // 'preview' | 'technical' | 'technical_archival' | 'graphics' | |
| 181 | + | ||
| 182 | + | // Dual file paths for preview/quality images | |
| 183 | + | previewPath String? @db.VarChar(512) // Increased from 255 for SHA256 hash-based paths | |
| 184 | + | qualityPath String? @db.VarChar(512) // Increased from 255 for SHA256 hash-based paths | |
| 185 | + | ||
| 186 | + | compressed Boolean @default(false) | |
| 187 | + | ||
| 188 | + | createdBy String? @db.VarChar(150) | |
| 189 | + | createdAt DateTime @default(now()) | |
| 190 | + | updatedBy String? @db.VarChar(150) | |
| 191 | + | updatedAt DateTime? @updatedAt | |
| 192 | + | ||
| 193 | + | // Relations | |
| 194 | + | document Document @relation(fields: [documentId], references: [id], onDelete: Cascade) | |
| 195 | + | version Version @relation(fields: [versionId], references: [id], onDelete: Cascade) | |
| 196 | + | revision Revision @relation(fields: [revisionId], references: [id], onDelete: Cascade) | |
| 197 | + | queue QueueExtractor? @relation(fields: [queueId], references: [id], onDelete: SetNull) | |
| 198 | + | ||
| 199 | + | @@index([documentId]) | |
| 200 | + | @@index([versionId]) | |
| 201 | + | @@index([revisionId]) | |
| 202 | + | @@index([queueId]) | |
| 203 | + | @@index([documentId, pageOrder]) | |
| 204 | + | @@index([status]) | |
| 205 | + | @@index([documentId, versionId, revisionId]) | |
| 206 | + | @@map("document_pages") | |
| 207 | + | } | |
| 208 | + | ||
| 209 | + | model DocumentComments { | |
| 210 | + | id String @id @unique @db.VarChar(50) | |
| 211 | + | parentId String? @db.VarChar(50) // For nested comments/replies | |
| 212 | + | documentId String @db.VarChar(50) | |
| 213 | + | comments String @db.Text | |
| 214 | + | editorData String? @db.Text // JSON data from Editor.js | |
| 215 | + | editorConfig String? @db.Text // Editor.js configuration | |
| 216 | + | createdBy String? @db.VarChar(150) | |
| 217 | + | createdAt DateTime @default(now()) | |
| 218 | + | updatedBy String? @db.VarChar(150) | |
| 219 | + | updatedAt DateTime? | |
| 220 | + | deletedBy String? @db.VarChar(150) | |
| 221 | + | deletedAt DateTime? | |
| 222 | + | ||
| 223 | + | // Relations | |
| 224 | + | document Document @relation(fields: [documentId], references: [id], onDelete: Cascade) | |
| 225 | + | parent DocumentComments? @relation("DocumentCommentReplies", fields: [parentId], references: [id], onDelete: Restrict) | |
| 226 | + | replies DocumentComments[] @relation("DocumentCommentReplies") | |
| 227 | + | attachments DocumentCommentAttachments[] | |
| 228 | + | ||
| 229 | + | @@index([documentId]) | |
| 230 | + | @@index([parentId]) | |
| 231 | + | @@index([createdBy]) | |
| 232 | + | @@index([deletedAt]) // For soft delete queries | |
| 233 | + | @@map("document_comments") | |
| 234 | + | } | |
| 235 | + | ||
| 236 | + | model DocumentCommentAttachments { | |
| 237 | + | id String @id @unique @db.VarChar(50) | |
| 238 | + | commentId String @db.VarChar(50) | |
| 239 | + | fileName String @db.VarChar(255) | |
| 240 | + | path String @db.Text | |
| 241 | + | extension String @db.VarChar(10) | |
| 242 | + | createdBy String? @db.VarChar(150) | |
| 243 | + | createdAt DateTime @default(now()) | |
| 244 | + | updatedBy String? @db.VarChar(150) | |
| 245 | + | updatedAt DateTime? | |
| 246 | + | ||
| 247 | + | comment DocumentComments @relation(fields: [commentId], references: [id], onDelete: Cascade) | |
| 248 | + | ||
| 249 | + | @@index([commentId]) | |
| 250 | + | @@map("document_comment_attachments") | |
| 251 | + | } | |
| 252 | + | ||
| 253 | + | // ------------------------------------- | |
| 254 | + | // 3D Model Metadata (Autodesk APS) | |
| 255 | + | // ------------------------------------- | |
| 256 | + | model Document3DMetadata { | |
| 257 | + | id String @id @unique @db.VarChar(50) | |
| 258 | + | documentId String @db.VarChar(50) | |
| 259 | + | versionId String @db.VarChar(50) | |
| 260 | + | revisionId String @unique @db.VarChar(50) | |
| 261 | + | ||
| 262 | + | s3Key String @db.VarChar(512) | |
| 263 | + | ||
| 264 | + | autodeskObjectId String? @db.Text | |
| 265 | + | autodeskUrn String? @db.Text // autodeskObjectId Base64 encoded URN untuk Viewer | |
| 266 | + | bucketKey String @db.VarChar(100) | |
| 267 | + | ||
| 268 | + | // Translation / Conversion Status | |
| 269 | + | translationStatus AutodeskTranslationStatus @default(PENDING) | |
| 270 | + | translationProgress String? @db.VarChar(50) | |
| 271 | + | translationErrorMessage String? @db.Text | |
| 272 | + | ||
| 273 | + | createdBy String? @db.VarChar(150) // uuid | |
| 274 | + | createdAt DateTime @default(now()) | |
| 275 | + | updatedBy String? @db.VarChar(150) // uuid | |
| 276 | + | updatedAt DateTime? @updatedAt | |
| 277 | + | ||
| 278 | + | document Document @relation(name: "Document3DMetadatas", fields: [documentId], references: [id], onDelete: Cascade) | |
| 279 | + | version Version @relation(name: "Version3DMetadatas", fields: [versionId], references: [id], onDelete: Cascade) | |
| 280 | + | revision Revision @relation(name: "Revision3DMetadata", fields: [revisionId], references: [id], onDelete: Cascade) | |
| 281 | + | ||
| 282 | + | @@index([documentId]) | |
| 283 | + | @@index([translationStatus]) | |
| 284 | + | @@map("document_3d_metadatas") | |
| 285 | + | } | |
| 286 | + | ||
| 287 | + | model Version { | |
| 288 | + | id String @id @unique @db.VarChar(50) | |
| 289 | + | key String @unique | |
| 290 | + | documentId String @db.VarChar(50) | |
| 291 | + | folderId String? @db.VarChar(50) | |
| 292 | + | version Int @default(0) | |
| 293 | + | filePath String @db.VarChar(512) // Increased from 255 for SHA256 hash-based paths | |
| 294 | + | fileSize Float | |
| 295 | + | fileHash Json? // { value: string, type: 'sha256', calculatedAt: ISO datetime } | |
| 296 | + | status VersionStatus? | |
| 297 | + | remark String? @db.Text | |
| 298 | + | description String? @db.Text | |
| 299 | + | companyId String? @db.VarChar(50) | |
| 300 | + | inTrash Boolean @default(false) | |
| 301 | + | transmittalId String? @db.VarChar(255) | |
| 302 | + | transmittalStatus String? @db.VarChar(255) | |
| 303 | + | transmittalTimestamp DateTime? | |
| 304 | + | publishedRevisionId String? @unique @db.VarChar(50) // Track which revision was published for this version | |
| 305 | + | ||
| 306 | + | archivedBy String? @db.VarChar(150) | |
| 307 | + | archivedAt Int? @db.UnsignedInt | |
| 308 | + | approvedBy String? @db.VarChar(150) | |
| 309 | + | approvedAt Int? @db.UnsignedInt | |
| 310 | + | uploadedBy String? @db.VarChar(150) | |
| 311 | + | uploadedAt DateTime @default(now()) | |
| 312 | + | updatedBy String? @db.VarChar(150) | |
| 313 | + | updatedAt DateTime? | |
| 314 | + | ||
| 315 | + | folder Folder? @relation(fields: [folderId], references: [id]) | |
| 316 | + | document Document @relation("DocumentVersions", fields: [documentId], references: [id], onDelete: Cascade) | |
| 317 | + | documentActive Document? @relation("DocumentActiveVersion") | |
| 318 | + | publishedRevision Revision? @relation("VersionPublishedRevision", fields: [publishedRevisionId], references: [id]) | |
| 319 | + | revisions Revision[] @relation("VersionRevisions") | |
| 320 | + | pages DocumentPages[] | |
| 321 | + | document3DMetadatas Document3DMetadata[] @relation("Version3DMetadatas") | |
| 322 | + | ||
| 323 | + | @@index([documentId, folderId, version, companyId, transmittalId]) | |
| 324 | + | @@map("versions") | |
| 325 | + | } | |
| 326 | + | ||
| 327 | + | model Revision { | |
| 328 | + | id String @id @unique @db.VarChar(50) | |
| 329 | + | key String @unique | |
| 330 | + | documentId String @db.VarChar(50) | |
| 331 | + | folderId String? @db.VarChar(50) | |
| 332 | + | versionId String @db.VarChar(50) // Reference to the version this revision belongs to | |
| 333 | + | revision Int @default(0) | |
| 334 | + | filePath String @db.VarChar(512) // Increased from 255 for SHA256 hash-based paths | |
| 335 | + | fileSize Float | |
| 336 | + | fileHash Json? // { value: string, type: 'sha256', calculatedAt: ISO datetime } | |
| 337 | + | status RevisionStatus @default(IN_REVIEW) | |
| 338 | + | remark String? @db.Text | |
| 339 | + | description String? @db.Text | |
| 340 | + | companyId String? @db.VarChar(50) | |
| 341 | + | inTrash Boolean @default(false) | |
| 342 | + | transmittalId String? @db.VarChar(255) | |
| 343 | + | transmittalStatus String? @db.VarChar(255) | |
| 344 | + | transmittalTimestamp DateTime? | |
| 345 | + | ||
| 346 | + | archivedBy String? @db.VarChar(150) | |
| 347 | + | archivedAt Int? @db.UnsignedInt | |
| 348 | + | approvedBy String? @db.VarChar(150) | |
| 349 | + | approvedAt Int? @db.UnsignedInt | |
| 350 | + | uploadedBy String? @db.VarChar(150) | |
| 351 | + | uploadedAt DateTime @default(now()) | |
| 352 | + | updatedBy String? @db.VarChar(150) | |
| 353 | + | updatedAt DateTime? | |
| 354 | + | ||
| 355 | + | folder Folder? @relation(fields: [folderId], references: [id]) | |
| 356 | + | version Version @relation("VersionRevisions", fields: [versionId], references: [id], onDelete: Cascade) | |
| 357 | + | document Document @relation("DocumentRevisions", fields: [documentId], references: [id], onDelete: Cascade) | |
| 358 | + | documentActive Document? @relation("DocumentActiveRevision") | |
| 359 | + | versionPublished Version? @relation("VersionPublishedRevision") | |
| 360 | + | pages DocumentPages[] | |
| 361 | + | document3DMetadata Document3DMetadata? @relation("Revision3DMetadata") | |
| 362 | + | ||
| 363 | + | @@index([documentId, folderId, versionId, revision, companyId, transmittalId]) | |
| 364 | + | @@map("revisions") | |
| 365 | + | } | |
| 366 | + | ||
| 367 | + | model QueueDownload { | |
| 368 | + | id String @id @unique @db.VarChar(50) | |
| 369 | + | projectId String @db.VarChar(100) | |
| 370 | + | companyId String? @db.VarChar(50) | |
| 371 | + | directoryId String? @db.VarChar(50) // Made optional for document type | |
| 372 | + | modelType DocumentType? | |
| 373 | + | ||
| 374 | + | // Queue type and data for flexible downloads | |
| 375 | + | queueType QueueDownloadType @default(DIRECTORY) | |
| 376 | + | queueData Json? // Store the flexible data array as JSON | |
| 377 | + | archiveName String? @db.VarChar(255) // Store the generated archive name | |
| 378 | + | ||
| 379 | + | status QueueDownloadStatus @default(PENDING) | |
| 380 | + | progress Float @default(0) // 0-100 percentage | |
| 381 | + | ||
| 382 | + | // Metadata | |
| 383 | + | totalSize BigInt? // Total size in bytes | |
| 384 | + | compressedSize BigInt? // Final compressed size in bytes | |
| 385 | + | totalDirectories Int? // Total directories count | |
| 386 | + | totalDocuments Int? // Total documents count | |
| 387 | + | ||
| 388 | + | // File paths | |
| 389 | + | workingPath String? @db.VarChar(500) // Temporary working directory path | |
| 390 | + | finalPath String? @db.VarChar(500) // Final compressed file path | |
| 391 | + | downloadUrl String? @db.VarChar(500) // Download URL/path | |
| 392 | + | ||
| 393 | + | // Timestamps | |
| 394 | + | requestedBy String? @db.VarChar(150) | |
| 395 | + | requestedAt DateTime @default(now()) | |
| 396 | + | startedAt DateTime? | |
| 397 | + | completedAt DateTime? | |
| 398 | + | expiresAt DateTime? // Auto-expire after 1 day | |
| 399 | + | ||
| 400 | + | // Error handling | |
| 401 | + | errorMessage String? @db.Text | |
| 402 | + | retryCount Int @default(0) | |
| 403 | + | ||
| 404 | + | @@index([projectId, companyId, status]) | |
| 405 | + | @@index([status, expiresAt]) | |
| 406 | + | @@index([queueType]) | |
| 407 | + | @@map("queue_downloads") | |
| 408 | + | } | |
| 409 | + | ||
| 410 | + | model QueueExtractor { | |
| 411 | + | id String @id @unique @db.VarChar(50) | |
| 412 | + | projectId String @db.VarChar(100) | |
| 413 | + | companyId String? @db.VarChar(50) | |
| 414 | + | queueType String? // Null for now as per requirement | |
| 415 | + | queueData Json // Store document info: { documentId, versionId, revisionId?, modelType? } | |
| 416 | + | ||
| 417 | + | status QueueExtractorStatus @default(PENDING) | |
| 418 | + | progress Float @default(0) // 0-100 percentage | |
| 419 | + | ||
| 420 | + | // Metadata | |
| 421 | + | totalSize BigInt? // Total size in bytes | |
| 422 | + | compressedSize BigInt? // Final compressed size in bytes (after WebP conversion) | |
| 423 | + | totalPages Int @default(0) // Total pages extracted | |
| 424 | + | ||
| 425 | + | // File paths | |
| 426 | + | workingPath String? @db.VarChar(500) // Temporary working directory path | |
| 427 | + | finalPath String? @db.VarChar(500) // Final output directory path | |
| 428 | + | ||
| 429 | + | // Timestamps | |
| 430 | + | requestedBy String? @db.VarChar(150) | |
| 431 | + | requestedAt DateTime @default(now()) | |
| 432 | + | startedAt DateTime? | |
| 433 | + | completedAt DateTime? | |
| 434 | + | ||
| 435 | + | // Error handling | |
| 436 | + | errorMessage String? @db.Text | |
| 437 | + | retryCount Int @default(0) | |
| 438 | + | ||
| 439 | + | // Phase tracking for two-phase extraction | |
| 440 | + | queuePhase String? @db.VarChar(20) // 'preview' | 'quality' | |
| 441 | + | detectedDpi Int? // Max DPI detected from pdfimages -list | |
| 442 | + | detectedType String? @db.VarChar(50) // 'technical' | 'graphics' | 'mixed' | |
| 443 | + | selectedPreset String? @db.VarChar(50) // Final preset used for extraction | |
| 444 | + | ||
| 445 | + | // Relations | |
| 446 | + | pages DocumentPages[] | |
| 447 | + | ||
| 448 | + | @@index([projectId, companyId, status]) | |
| 449 | + | @@index([status]) | |
| 450 | + | @@map("queue_extractors") | |
| 451 | + | } | |
| 452 | + | ||
| 453 | + | model ChangeLog { | |
| 454 | + | id String @id @default(cuid()) | |
| 455 | + | tableName String | |
| 456 | + | recordId String | |
| 457 | + | operation Operation | |
| 458 | + | timestamp DateTime @default(now()) | |
| 459 | + | processed Boolean @default(false) | |
| 460 | + | ||
| 461 | + | @@map("change_logs") | |
| 462 | + | } | |
| 463 | + | ||
| 464 | + | // ------------------------------------- | |
| 465 | + | // Metrics scope-related models | |
| 466 | + | // ------------------------------------- | |
| 467 | + | // Metrics for tracking the usage of the system | |
| 468 | + | model Metrics { | |
| 469 | + | id String @id @default(uuid()) | |
| 470 | + | projectId String @db.VarChar(100) | |
| 471 | + | companyId String? @db.VarChar(50) | |
| 472 | + | ||
| 473 | + | totalUsers Int @default(0) @map("total_users") | |
| 474 | + | totalDocuments Int @default(0) @map("total_documents") | |
| 475 | + | totalDocumentsPublish Int @default(0) @map("total_documents_publish") | |
| 476 | + | totalDocumentsType Json @default("{}") @map("total_documents_type") | |
| 477 | + | totalDocumentsPublishType Json @default("{}") @map("total_documents_publish_type") | |
| 478 | + | totalDocumentsTrashed Int @default(0) @map("total_documents_trashed") | |
| 479 | + | totalDocumentsTrashedType Json @default("{}") @map("total_documents_trashed_type") | |
| 480 | + | totalDocumentsExtracted Int @default(0) @map("total_documents_extracted") | |
| 481 | + | totalStatus Json @default("{}") @map("total_status") | |
| 482 | + | totalStatusType Json @default("{}") @map("total_status_type") | |
| 483 | + | totalStatusPublish Json @default("{}") @map("total_status_publish") | |
| 484 | + | totalStatusPublishType Json @default("{}") @map("total_status_publish_type") | |
| 485 | + | totalDirectories Int @default(0) @map("total_directories") | |
| 486 | + | totalDirectoriesTrashed Int @default(0) @map("total_directories_trashed") | |
| 487 | + | totalVersions Int @default(0) @map("total_versions") | |
| 488 | + | totalRevisions Int @default(0) @map("total_revisions") | |
| 489 | + | totalDocumentComments Int @default(0) @map("total_doc_comments") | |
| 490 | + | totalDocumentCommentReplies Int @default(0) @map("total_doc_comment_replies") | |
| 491 | + | totalDocumentCommentAttachments Int @default(0) @map("total_doc_comment_attachments") | |
| 492 | + | usedStorage Float @default(0.0) @map("used_storage") | |
| 493 | + | usedStorageFormatted String @default("0 B") @map("used_storage_formatted") @db.VarChar(100) | |
| 494 | + | usedStorageDetailed Json @default("{}") @map("used_storage_detailed") | |
| 495 | + | ||
| 496 | + | createdAt DateTime @default(now()) | |
| 497 | + | updatedAt DateTime? | |
| 498 | + | deletedAt DateTime? | |
| 499 | + | ||
| 500 | + | @@index([projectId, companyId]) | |
| 501 | + | @@index([projectId, createdAt]) | |
| 502 | + | @@index([projectId, updatedAt]) | |
| 503 | + | @@index([projectId, companyId, deletedAt]) | |
| 504 | + | @@map("metrics") | |
| 505 | + | } | |
| 506 | + | ||
| 507 | + | // ------------------------------------- | |
| 508 | + | // Audit log scope-related models | |
| 509 | + | // ------------------------------------- | |
| 510 | + | // Audit logs for security tracking | |
| 511 | + | model AuditLog { | |
| 512 | + | id String @id @default(uuid()) | |
| 513 | + | user_id String? | |
| 514 | + | action String // 'login', 'logout', 'create', 'update', 'delete', 'view' | |
| 515 | + | resource String? // table name or resource type | |
| 516 | + | resource_id String? | |
| 517 | + | old_values Json? | |
| 518 | + | new_values Json? | |
| 519 | + | ip_address String? | |
| 520 | + | user_agent String? | |
| 521 | + | location String? // geo location | |
| 522 | + | status String @default("success") // 'success', 'failed', 'blocked' | |
| 523 | + | created_at DateTime @default(now()) | |
| 524 | + | ||
| 525 | + | @@index([user_id]) | |
| 526 | + | @@index([action]) | |
| 527 | + | @@index([resource]) | |
| 528 | + | @@index([resource_id]) | |
| 529 | + | @@index([status]) | |
| 530 | + | @@index([created_at]) | |
| 531 | + | @@map("audit_logs") | |
| 532 | + | } | |
| 533 | + | ||
| 534 | + | // ============================================================================ | |
| 535 | + | // MPP Gantt Cache | |
| 536 | + | // Menyimpan hasil raw ekstraksi Aspose Tasks Cloud agar tidak perlu | |
| 537 | + | // memanggil API Aspose setiap kali view Gantt dibuka. | |
| 538 | + | // | |
| 539 | + | // Hierarki relasi: | |
| 540 | + | // MppSnapshot (1) ──< MppTaskItem (tasks.taskItem[]) | |
| 541 | + | // ──< MppTaskDetail (taskDetails[]) | |
| 542 | + | // ──< MppTaskLink (taskLinks[]) | |
| 543 | + | // ──< MppResource (resources.resourceItem[]) | |
| 544 | + | // ──< MppTaskAssignment (taskAssignments[].list[] — di-flatten) | |
| 545 | + | // ============================================================================ | |
| 546 | + | ||
| 547 | + | model MppSnapshot { | |
| 548 | + | id String @id @default(uuid()) @db.VarChar(50) | |
| 549 | + | projectId String @db.VarChar(100) | |
| 550 | + | fileName String @db.VarChar(255) | |
| 551 | + | /// Path folder di Aspose Cloud Storage (opsional) | |
| 552 | + | folder String? @db.VarChar(500) | |
| 553 | + | extractedAt DateTime @default(now()) | |
| 554 | + | ||
| 555 | + | taskItems MppTaskItem[] | |
| 556 | + | taskDetails MppTaskDetail[] | |
| 557 | + | taskLinks MppTaskLink[] | |
| 558 | + | resources MppResource[] | |
| 559 | + | taskAssignments MppTaskAssignment[] | |
| 560 | + | ||
| 561 | + | /// Satu entri cache per kombinasi projectId + fileName | |
| 562 | + | @@unique([projectId, fileName], name: "projectId_fileName") | |
| 563 | + | @@index([projectId]) | |
| 564 | + | @@map("mpp_snapshots") | |
| 565 | + | } | |
| 566 | + | ||
| 567 | + | model MppTaskItemLink { | |
| 568 | + | id String @id @default(uuid()) @db.VarChar(50) | |
| 569 | + | href String | |
| 570 | + | rel String | |
| 571 | + | type String? | |
| 572 | + | title String? | |
| 573 | + | mppTaskItems MppTaskItem[] | |
| 574 | + | ||
| 575 | + | @@map("mpp_task_item_links") | |
| 576 | + | } | |
| 577 | + | ||
| 578 | + | /// tasks.taskItem[] — daftar ringkas task (uid, nama, tanggal, durasi) | |
| 579 | + | model MppTaskItem { | |
| 580 | + | id String @id @default(uuid()) @db.VarChar(50) | |
| 581 | + | snapshotId String @db.VarChar(50) | |
| 582 | + | uid Int | |
| 583 | + | /// id (bukan uid) dari Aspose — urutan task di dalam file | |
| 584 | + | asposeId Int @map("aspose_id") | |
| 585 | + | name String @db.VarChar(500) | |
| 586 | + | start DateTime | |
| 587 | + | finish DateTime | |
| 588 | + | duration String @db.VarChar(50) | |
| 589 | + | link MppTaskItemLink @relation(fields: [mppTaskItemLinkId], references: [id]) | |
| 590 | + | ||
| 591 | + | snapshot MppSnapshot @relation(fields: [snapshotId], references: [id], onDelete: Cascade) | |
| 592 | + | mppTaskItemLinkId String @db.VarChar(50) | |
| 593 | + | ||
| 594 | + | @@unique([snapshotId, uid]) | |
| 595 | + | @@index([snapshotId]) | |
| 596 | + | @@map("mpp_task_items") | |
| 597 | + | } | |
| 598 | + | ||
| 599 | + | /// taskDetails[] — detail lengkap per task termasuk hierarki dan prioritas | |
| 600 | + | model MppTaskDetail { | |
| 601 | + | id String @id @default(uuid()) @db.VarChar(50) | |
| 602 | + | snapshotId String @db.VarChar(50) | |
| 603 | + | uid Int | |
| 604 | + | asposeId Int @map("aspose_id") | |
| 605 | + | name String @db.VarChar(500) | |
| 606 | + | start DateTime | |
| 607 | + | finish DateTime | |
| 608 | + | duration String @db.VarChar(50) | |
| 609 | + | /// 0–100 | |
| 610 | + | percentComplete Int @default(0) @map("percent_complete") | |
| 611 | + | /// Numerik Aspose: 500=Normal, 700=High, 300=Low, dst. | |
| 612 | + | priority Int @default(500) | |
| 613 | + | isSummary Boolean @default(false) @map("is_summary") | |
| 614 | + | isMilestone Boolean @default(false) @map("is_milestone") | |
| 615 | + | outlineLevel Int @default(0) @map("outline_level") | |
| 616 | + | outlineNumber String? @map("outline_number") @db.VarChar(50) | |
| 617 | + | wbs String? @db.VarChar(50) | |
| 618 | + | /// Array uid child task, disimpan sebagai JSON int[]. | |
| 619 | + | /// Kunci untuk invert parent-child di aggregator. | |
| 620 | + | subtasksUids Json @map("subtasks_uids") | |
| 621 | + | /// Objek Task lengkap dari Aspose — untuk keperluan di masa depan | |
| 622 | + | /// tanpa harus memanggil API lagi. | |
| 623 | + | rawData Json @map("raw_data") | |
| 624 | + | ||
| 625 | + | snapshot MppSnapshot @relation(fields: [snapshotId], references: [id], onDelete: Cascade) | |
| 626 | + | ||
| 627 | + | @@unique([snapshotId, uid]) | |
| 628 | + | @@index([snapshotId]) | |
| 629 | + | @@map("mpp_task_details") | |
| 630 | + | } | |
| 631 | + | ||
| 632 | + | /// taskLinks[] — dependency antar task (FinishToStart, dll.) | |
| 633 | + | model MppTaskLink { | |
| 634 | + | id String @id @default(uuid()) @db.VarChar(50) | |
| 635 | + | snapshotId String @db.VarChar(50) | |
| 636 | + | /// Index 1-based dari Aspose (dipakai untuk update/delete link) | |
| 637 | + | asposeIndex Int @map("aspose_index") | |
| 638 | + | predecessorUid Int @map("predecessor_uid") | |
| 639 | + | successorUid Int @map("successor_uid") | |
| 640 | + | /// "FinishToStart" | "StartToStart" | "FinishToFinish" | "StartToFinish" | |
| 641 | + | linkType String @map("link_type") @db.VarChar(50) | |
| 642 | + | lag Int @default(0) | |
| 643 | + | lagFormat String? @map("lag_format") @db.VarChar(50) | |
| 644 | + | ||
| 645 | + | snapshot MppSnapshot @relation(fields: [snapshotId], references: [id], onDelete: Cascade) | |
| 646 | + | ||
| 647 | + | @@unique([snapshotId, asposeIndex]) | |
| 648 | + | @@index([snapshotId]) | |
| 649 | + | @@map("mpp_task_links") | |
| 650 | + | } | |
| 651 | + | ||
| 652 | + | /// resources.resourceItem[] — daftar resource/orang dalam proyek | |
| 653 | + | model MppResource { | |
| 654 | + | id String @id @default(uuid()) @db.VarChar(50) | |
| 655 | + | snapshotId String @db.VarChar(50) | |
| 656 | + | uid Int | |
| 657 | + | asposeId Int @map("aspose_id") | |
| 658 | + | /// null untuk resource uid=0 (placeholder "Unassigned" bawaan Aspose) | |
| 659 | + | name String? @db.VarChar(500) | |
| 660 | + | ||
| 661 | + | snapshot MppSnapshot @relation(fields: [snapshotId], references: [id], onDelete: Cascade) | |
| 662 | + | ||
| 663 | + | @@unique([snapshotId, uid]) | |
| 664 | + | @@index([snapshotId]) | |
| 665 | + | @@map("mpp_resources") | |
| 666 | + | } | |
| 667 | + | ||
| 668 | + | /// taskAssignments[].list[] — flatten dari semua container per task. | |
| 669 | + | /// Menyimpan pasangan taskUid↔resourceUid untuk resolve PIC di aggregator. | |
| 670 | + | model MppTaskAssignment { | |
| 671 | + | id String @id @default(uuid()) @db.VarChar(50) | |
| 672 | + | snapshotId String @db.VarChar(50) | |
| 673 | + | /// uid assignment dari Aspose | |
| 674 | + | uid Int | |
| 675 | + | taskUid Int @map("task_uid") | |
| 676 | + | resourceUid Int @map("resource_uid") | |
| 677 | + | ||
| 678 | + | snapshot MppSnapshot @relation(fields: [snapshotId], references: [id], onDelete: Cascade) | |
| 679 | + | ||
| 680 | + | @@unique([snapshotId, uid]) | |
| 681 | + | @@index([snapshotId]) | |
| 682 | + | @@index([snapshotId, taskUid]) | |
| 683 | + | @@map("mpp_task_assignments") | |
| 684 | + | } | |
Newer
Older