@allbluecn/database 0.4.4 → 0.4.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (25) hide show
  1. package/migrations/20260804174501_init/migration.sql +1764 -0
  2. package/migrations/20260805174545_add_story_activity_is_ai/migration.sql +2 -0
  3. package/migrations/20260806035202_story_archived/migration.sql +6 -0
  4. package/migrations/20260806061157_builtin_independent_project/migration.sql +26 -0
  5. package/migrations/20260806144250_tanstack_ai_persistence/migration.sql +129 -0
  6. package/migrations/20260809080004_add_monitoring_observability/migration.sql +75 -0
  7. package/migrations/20260811112502_add_workspace/migration.sql +89 -0
  8. package/migrations/20260811153719_add_hosted_ai_tables/migration.sql +119 -0
  9. package/migrations/20260812043944_add_billing_tables/migration.sql +86 -0
  10. package/migrations/20260813040200_sub3_stripe_subscription_unique/migration.sql +6 -0
  11. package/migrations/20260813061345_add_enterprise_license_sso_audit_permissions/migration.sql +169 -0
  12. package/migrations/20260813132026_add_user_locale/migration.sql +2 -0
  13. package/migrations/20260814000000_add_story_composite_index/migration.sql +2 -0
  14. package/migrations/20260814161622_add_user_default_workspace_id/migration.sql +2 -0
  15. package/migrations/20260815025746_p2_seat_addon/migration.sql +15 -0
  16. package/migrations/20260815120000_creem_billing_neutralization/migration.sql +122 -0
  17. package/migrations/20260816212027_compliance_content/migration.sql +3 -0
  18. package/migrations/20260817204928_p6_user_agents_skills/migration.sql +57 -0
  19. package/migrations/20260818094327_user_level_subscription/migration.sql +43 -0
  20. package/migrations/20260819014924_team_membership_invitation/migration.sql +65 -0
  21. package/migrations/migration_lock.toml +3 -0
  22. package/package.json +8 -3
  23. package/prisma.config.ts +18 -0
  24. package/schema.prisma +2014 -0
  25. package/schema.sqlite.prisma +1584 -0
@@ -0,0 +1,1584 @@
1
+ // SPIKE 0.2 结论:策略 2(双 schema/generated)✅ 验证通过
2
+ // 适配工作量:enum 6 定义+6 字段→String / @db.Text 21 处删除 / Json 37 处(25 保留 + 12 default 改 dbgenerated)/ String[] 1 处→Json / Unsupported 0 / @@schema 0
3
+ // ⚠️ 关键发现:Prisma 7.8 SQLite 下 Json @default("{}") 会生成非法 DEFAULT {}(裸 token),
4
+ // 必须改用 @default(dbgenerated("'{}'")) 传 SQL 字面量,否则 db push 报 "unrecognized token: {"
5
+ // generate:DATABASE_URL="file:./x.db" pnpm prisma generate --schema=./schema.sqlite.prisma → ./generated-sqlite/
6
+ // db push:DATABASE_URL="file:./x.db" pnpm prisma db push --schema=./schema.sqlite.prisma(已验证 54 表全部建成功)
7
+ // 此文件作为 Phase A.5(Prisma client 多 provider)的基础保留
8
+ generator client {
9
+ provider = "prisma-client"
10
+ output = "./generated-sqlite"
11
+ }
12
+
13
+ datasource db {
14
+ provider = "sqlite"
15
+ }
16
+
17
+ // 系统配置表
18
+ model SystemSetting {
19
+ id String @id @default(cuid())
20
+ key String @unique
21
+ value String
22
+ category String @default("general") // 配置分类
23
+ desc String? // 说明
24
+ createdAt DateTime @default(now())
25
+ updatedAt DateTime @updatedAt
26
+
27
+ @@index([category])
28
+ @@map("system_settings")
29
+ }
30
+
31
+ // 用户表
32
+ model User {
33
+ id String @id @default(cuid())
34
+ email String @unique
35
+ username String @unique
36
+ password String
37
+ role String @default("USER")
38
+ status String @default("ACTIVE")
39
+ createdAt DateTime @default(now())
40
+ updatedAt DateTime @updatedAt
41
+ lastLoginAt DateTime?
42
+
43
+ avatarConfig Json?
44
+ bgShape String?
45
+ bgColor String?
46
+
47
+ aiProvider String? // "openrouter" | "openai"
48
+ aiApiKey String? // 用户 AI API Key(TODO: 加密存储)
49
+ aiModel String? // 默认 "openai/gpt-4o-mini"
50
+ locale String? // 用户语言偏好(zh / en),null 表示未设置
51
+
52
+ sketchApiToken String?
53
+ timezone String? // 用户全局时区,如 "Asia/Shanghai"。null = 浏览器时区
54
+
55
+ /// 用户偏好的计费币种(子项目 3 新增)
56
+ preferredCurrency String @default("usd")
57
+
58
+ /// 用户默认 workspace ID(B3 精确化)
59
+ defaultWorkspaceId String?
60
+
61
+ invitationUses InvitationUse[]
62
+ loginLogs LoginLog[]
63
+ passwordResetTokens PasswordResetToken[]
64
+ prds PRD[]
65
+ tags Tag[]
66
+ knowledgeBases KnowledgeBase[]
67
+ knowledgeDocuments KnowledgeDocument[]
68
+ knowledgeShares KnowledgeShareRecord[]
69
+ legalRegulations LegalRegulation[]
70
+ industryCompliances IndustryCompliance[]
71
+ dictionaryTerms DictionaryTerm[]
72
+ websiteBookmarks WebsiteBookmark[]
73
+ bookmarkCategories BookmarkCategory[]
74
+ articleFavorites ArticleFavorite[]
75
+ anyhubApps AnyHubApp[]
76
+ anyhubMarketApps AnyHubMarketApp[]
77
+ reviewedAnyhubRecommendations AnyHubRecommendation[] @relation("reviewedAnyhubRecommendations")
78
+ anyhubRecommenders AnyHubRecommender[]
79
+ prdVersions PRDVersion[]
80
+ reviewComments ReviewComment[]
81
+ comments Comment[]
82
+ commentIgnores CommentIgnore[]
83
+ reviews Review[] @relation("reviewer")
84
+ designProjects DesignProject[]
85
+ chatThreads ChatThread[]
86
+ aiProviderConfigs AiProviderConfig[]
87
+ agentConversations AgentConversation[]
88
+ ownedProjects Project[] @relation("projectOwner")
89
+ projectMemberships ProjectMember[]
90
+ projectActivities ProjectActivity[]
91
+ ownedWorkspaces Workspace[] @relation("workspaceOwner")
92
+ workspaceMemberships TeamMembership[] @relation("teamOwner")
93
+ teamJoined TeamMembership? @relation("teamMember")
94
+ sentInvitations TeamInvitation[] @relation("invitationOwner")
95
+ aiUsageLogs AiUsageLog[]
96
+ agents UserAgent[]
97
+ skills UserSkill[]
98
+ subscription Subscription?
99
+
100
+ @@map("users")
101
+ }
102
+
103
+ // 项目
104
+ model Project {
105
+ id String @id @default(cuid())
106
+ displayId String? // 用户层项目ID,全大写字母数字,1-10位
107
+ name String
108
+ description String?
109
+ icon String @default("folder")
110
+ timezone String? // 项目时区,null = 跟随用户全局时区
111
+ ownerId String
112
+ owner User @relation("projectOwner", fields: [ownerId], references: [id])
113
+ workspaceId String?
114
+ workspace Workspace? @relation(fields: [workspaceId], references: [id], onDelete: SetNull)
115
+ members ProjectMember[]
116
+ activities ProjectActivity[]
117
+ createdAt DateTime @default(now())
118
+ updatedAt DateTime @updatedAt
119
+
120
+ @@unique([ownerId, displayId])
121
+ @@index([ownerId])
122
+ @@index([workspaceId])
123
+ @@map("projects")
124
+ }
125
+
126
+ // 项目成员
127
+ model ProjectMember {
128
+ id String @id @default(cuid())
129
+ projectId String
130
+ project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)
131
+ userId String
132
+ user User @relation(fields: [userId], references: [id], onDelete: Cascade)
133
+ role String @default("member") // owner | admin | member | viewer
134
+ status String @default("active") // active | invited | suspended
135
+ joinedAt DateTime @default(now())
136
+
137
+ @@unique([projectId, userId])
138
+ @@index([projectId])
139
+ @@map("project_members")
140
+ }
141
+
142
+ // 工作空间(项目的上一级容器)
143
+ model Workspace {
144
+ id String @id @default(cuid())
145
+ name String
146
+ icon String @default("folder")
147
+ description String?
148
+ ownerId String
149
+ owner User @relation("workspaceOwner", fields: [ownerId], references: [id])
150
+ projects Project[]
151
+ usageRecords UsageRecord[]
152
+ aiUsageLogs AiUsageLog[]
153
+ createdAt DateTime @default(now())
154
+ updatedAt DateTime @updatedAt
155
+
156
+ @@index([ownerId])
157
+ @@map("workspaces")
158
+ }
159
+
160
+ // 团队隶属关系
161
+ model TeamMembership {
162
+ id String @id @default(cuid())
163
+ ownerId String
164
+ owner User @relation("teamOwner", fields: [ownerId], references: [id], onDelete: Cascade)
165
+ memberId String
166
+ member User @relation("teamMember", fields: [memberId], references: [id], onDelete: Cascade)
167
+ seatType String @default("member")
168
+ joinedAt DateTime @default(now())
169
+ @@unique([memberId])
170
+ @@index([ownerId])
171
+ @@map("team_memberships")
172
+ }
173
+
174
+ // 团队邀请
175
+ model TeamInvitation {
176
+ id String @id @default(cuid())
177
+ ownerId String
178
+ owner User @relation("invitationOwner", fields: [ownerId], references: [id], onDelete: Cascade)
179
+ email String
180
+ seatType String @default("member")
181
+ token String @unique
182
+ channel String @default("email")
183
+ status String @default("pending")
184
+ expiresAt DateTime
185
+ createdAt DateTime @default(now())
186
+ acceptedBy String?
187
+ @@unique([ownerId, email])
188
+ @@index([ownerId])
189
+ @@index([email])
190
+ @@map("team_invitations")
191
+ }
192
+
193
+ // 项目活动记录
194
+ model ProjectActivity {
195
+ id String @id @default(cuid())
196
+ projectId String
197
+ project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)
198
+ userId String
199
+ user User @relation(fields: [userId], references: [id], onDelete: Cascade)
200
+ action String // created | updated | deleted | member_invited | member_joined | member_removed | role_changed | module_viewed
201
+ targetType String? // project | member | requirement | prd | design | testcase | bug | acceptance
202
+ targetId String?
203
+ metadata Json?
204
+ createdAt DateTime @default(now())
205
+
206
+ @@index([projectId, createdAt])
207
+ @@map("project_activities")
208
+ }
209
+
210
+ model ChatThread {
211
+ id String @id @default(cuid())
212
+ userId String
213
+ title String?
214
+ isArchived Boolean @default(false)
215
+ createdAt DateTime @default(now())
216
+ updatedAt DateTime @updatedAt
217
+
218
+ user User @relation(fields: [userId], references: [id], onDelete: Cascade)
219
+ messages ChatMessage[]
220
+ runs ChatRun[]
221
+ interrupts ChatInterrupt[]
222
+ generations GenerationRun[]
223
+ agentId String?
224
+ agent UserAgent? @relation(fields: [agentId], references: [id])
225
+
226
+ @@index([userId, updatedAt])
227
+ @@map("chat_threads")
228
+ }
229
+
230
+ model ChatMessage {
231
+ id String @id @default(cuid())
232
+ threadId String
233
+ role String
234
+ content String
235
+ parts Json?
236
+ thinking String?
237
+ citations Json?
238
+ createdAt DateTime @default(now())
239
+
240
+ thread ChatThread @relation(fields: [threadId], references: [id], onDelete: Cascade)
241
+
242
+ @@index([threadId, createdAt])
243
+ @@map("chat_messages")
244
+ }
245
+
246
+ model ChatRun {
247
+ id String @id @default(cuid())
248
+ threadId String
249
+ userId String
250
+ status String
251
+ startedAt DateTime @default(now())
252
+ finishedAt DateTime?
253
+ detachedSince DateTime?
254
+ usage Json?
255
+ error String?
256
+
257
+ thread ChatThread @relation(fields: [threadId], references: [id], onDelete: Cascade)
258
+ interrupts ChatInterrupt[]
259
+
260
+ @@index([threadId, startedAt])
261
+ @@index([userId, status])
262
+ @@map("chat_runs")
263
+ }
264
+
265
+ model ChatInterrupt {
266
+ id String @id @default(cuid())
267
+ runId String
268
+ threadId String
269
+ userId String
270
+ kind String
271
+ toolName String?
272
+ state String
273
+ payload Json?
274
+ resolution Json?
275
+ createdAt DateTime @default(now())
276
+ resolvedAt DateTime?
277
+
278
+ run ChatRun @relation(fields: [runId], references: [id], onDelete: Cascade)
279
+ thread ChatThread @relation(fields: [threadId], references: [id], onDelete: Cascade)
280
+
281
+ @@index([runId])
282
+ @@index([threadId, state])
283
+ @@map("chat_interrupts")
284
+ }
285
+
286
+ model GenerationRun {
287
+ id String @id @default(cuid())
288
+ threadId String
289
+ userId String
290
+ kind String
291
+ status String
292
+ input Json?
293
+ result Json?
294
+ artifactId String?
295
+ error String?
296
+ startedAt DateTime @default(now())
297
+ finishedAt DateTime?
298
+
299
+ thread ChatThread @relation(fields: [threadId], references: [id], onDelete: Cascade)
300
+
301
+ @@index([threadId, startedAt])
302
+ @@index([userId, status])
303
+ @@map("generation_runs")
304
+ }
305
+
306
+ // 登录日志
307
+ model LoginLog {
308
+ id String @id @default(cuid())
309
+ userId String
310
+ ip String?
311
+ userAgent String?
312
+ location String? // 地理位置,如 "北京, 中国";开发环境为 "本地"
313
+ success Boolean @default(true)
314
+ message String? // 失败原因(中文),成功时为 null
315
+ createdAt DateTime @default(now())
316
+
317
+ user User @relation(fields: [userId], references: [id], onDelete: Cascade)
318
+
319
+ @@map("login_logs")
320
+ }
321
+
322
+ // 邀请码
323
+ model InvitationCode {
324
+ id String @id @default(cuid())
325
+ code String @unique
326
+ maxUses Int @default(1)
327
+ usedCount Int @default(0)
328
+ expiresAt DateTime
329
+ createdAt DateTime @default(now())
330
+
331
+ uses InvitationUse[]
332
+
333
+ @@map("invitation_codes")
334
+ }
335
+
336
+ // 邀请码使用记录
337
+ model InvitationUse {
338
+ id String @id @default(cuid())
339
+ codeId String
340
+ userId String
341
+ usedAt DateTime @default(now())
342
+
343
+ code InvitationCode @relation(fields: [codeId], references: [id], onDelete: Cascade)
344
+ user User @relation(fields: [userId], references: [id], onDelete: Cascade)
345
+
346
+ @@map("invitation_uses")
347
+ }
348
+
349
+ // 密码重置令牌
350
+ model PasswordResetToken {
351
+ id String @id @default(cuid())
352
+ token String @unique
353
+ userId String
354
+ expiresAt DateTime
355
+ usedAt DateTime?
356
+ createdAt DateTime @default(now())
357
+
358
+ user User @relation(fields: [userId], references: [id], onDelete: Cascade)
359
+
360
+ @@map("password_reset_tokens")
361
+ }
362
+
363
+ // (enum 已移除:SQLite 无原生 enum,Role/UserStatus/IconType/MarketplaceAppStatus/ReviewStatus/ShareStatus 改为 String 存 TEXT)
364
+
365
+ // 知识库
366
+ model KnowledgeBase {
367
+ id String @id @default(cuid())
368
+ name String
369
+ description String?
370
+ icon String @default("notebook")
371
+ tags Json @default(dbgenerated("'[]'"))
372
+ visibility String @default("private")
373
+ userId String
374
+ isDeleted Boolean @default(false)
375
+ createdAt DateTime @default(now())
376
+ updatedAt DateTime @updatedAt
377
+
378
+ user User @relation(fields: [userId], references: [id], onDelete: Cascade)
379
+ documents KnowledgeDocument[]
380
+
381
+ @@index([userId])
382
+ @@map("knowledge_bases")
383
+ }
384
+
385
+ model KnowledgeDocument {
386
+ id String @id @default(cuid())
387
+ kbId String
388
+ parentId String?
389
+ title String
390
+ type String @default("doc")
391
+ content Json @default(dbgenerated("'{}'"))
392
+ icon String?
393
+ isHomepage Boolean @default(false)
394
+ sortOrder Int @default(0)
395
+ isDeleted Boolean @default(false)
396
+ createdBy String
397
+ tags Json @default(dbgenerated("'[]'"))
398
+ properties Json @default(dbgenerated("'{}'"))
399
+ createdAt DateTime @default(now())
400
+ updatedAt DateTime @updatedAt
401
+
402
+ kb KnowledgeBase @relation(fields: [kbId], references: [id], onDelete: Cascade)
403
+ parent KnowledgeDocument? @relation("DocumentTree", fields: [parentId], references: [id], onDelete: SetNull)
404
+ children KnowledgeDocument[] @relation("DocumentTree")
405
+ creator User @relation(fields: [createdBy], references: [id], onDelete: Cascade)
406
+
407
+ @@index([kbId])
408
+ @@index([parentId])
409
+ @@index([kbId, isHomepage])
410
+ @@map("knowledge_documents")
411
+ }
412
+
413
+ // PRD 文档表
414
+ model PRD {
415
+ id String @id @default(cuid())
416
+ icon String @default("file") // 图标名称
417
+ name String // PRD 名称
418
+ description String? // 介绍
419
+ settings Json? // 文档设置(布局、主题等)
420
+ userId String // 所属用户
421
+ isDeleted Boolean @default(false)
422
+ deletedAt DateTime?
423
+ createdAt DateTime @default(now())
424
+ updatedAt DateTime @updatedAt
425
+
426
+ user User @relation(fields: [userId], references: [id], onDelete: Cascade)
427
+ pages PRDPage[]
428
+ versions PRDVersion[]
429
+ reviews Review[]
430
+ comments Comment[]
431
+ shares Share[]
432
+ prdTags PRDTag[]
433
+ agentConversations AgentConversation[]
434
+
435
+ @@index([userId])
436
+ @@map("prds")
437
+ }
438
+
439
+ // Agent 原型生成会话
440
+ model AgentConversation {
441
+ id String @id @default(cuid())
442
+ userId String
443
+ prdId String
444
+ title String @default("新会话")
445
+ createdAt DateTime @default(now())
446
+ updatedAt DateTime @updatedAt
447
+
448
+ user User @relation(fields: [userId], references: [id], onDelete: Cascade)
449
+ prd PRD @relation(fields: [prdId], references: [id], onDelete: Cascade)
450
+ messages AgentMessage[]
451
+
452
+ @@index([userId, prdId])
453
+ @@map("agent_conversations")
454
+ }
455
+
456
+ // Agent 会话消息
457
+ model AgentMessage {
458
+ id String @id @default(cuid())
459
+ conversationId String
460
+ role String // "user" | "assistant"
461
+ content String // 消息文本
462
+ components Json? // AI 生成的组件数据(assistant 消息)
463
+ isError Boolean @default(false)
464
+ createdAt DateTime @default(now())
465
+
466
+ conversation AgentConversation @relation(fields: [conversationId], references: [id], onDelete: Cascade)
467
+
468
+ @@index([conversationId])
469
+ @@map("agent_messages")
470
+ }
471
+
472
+ // 基础组件表(后台维护)
473
+ model Component {
474
+ id String @id @default(cuid())
475
+ name String
476
+ description String? // 组件描述
477
+ type String // "component" | "block" | "template"
478
+ category String? // 分类
479
+ subCategory String? // 子分类
480
+ path String? // 树形分组路径(如 "基础组件/表单/输入框")
481
+ defaultWidth Int @default(200)
482
+ defaultHeight Int @default(40)
483
+ prototypeKey String? // unique key for registry lookup (e.g. "text", "button")
484
+ config Json // 属性描述 schema(props定义、slots、events)
485
+ defaultProps Json? // 默认属性值
486
+ defaultStyle Json? // 默认样式
487
+ preview String? // 预览图URL
488
+ version Int @default(1)
489
+ sortOrder Int @default(0) // 排序权重(越大越前)
490
+ source String @default("manual") // "npm" | "file" | "manual"
491
+ sourceId String? // npm 包名 / 文件名
492
+ isHidden Boolean @default(false) // 隐藏/显示
493
+ isDeleted Boolean @default(false)
494
+ createdAt DateTime @default(now())
495
+ updatedAt DateTime @updatedAt
496
+
497
+ blockItems BlockComponent[] @relation("componentRef")
498
+ instances ComponentInstance[] @relation("componentRef")
499
+ componentTags ComponentTag[]
500
+ tags Json @default(dbgenerated("'[]'"))
501
+
502
+ @@index([type, category])
503
+ @@index([isDeleted])
504
+ @@index([isHidden])
505
+ @@index([sortOrder])
506
+ @@map("components")
507
+ }
508
+
509
+ // 区块定义表(后台维护)
510
+ model Block {
511
+ id String @id @default(cuid())
512
+ name String
513
+ description String?
514
+ category String?
515
+ isDeleted Boolean @default(false)
516
+ createdAt DateTime @default(now())
517
+ updatedAt DateTime @updatedAt
518
+
519
+ items BlockComponent[]
520
+ instances BlockInstance[] @relation("blockRef")
521
+ templateItems TemplateBlock[] @relation("templateBlockRef")
522
+
523
+ @@index([isDeleted])
524
+ @@map("blocks")
525
+ }
526
+
527
+ // 区块内组件布局
528
+ model BlockComponent {
529
+ id String @id @default(cuid())
530
+ blockId String
531
+ componentId String
532
+ x Float @default(0)
533
+ y Float @default(0)
534
+ width Float @default(100)
535
+ height Float @default(100)
536
+ zIndex Int @default(0)
537
+ propsOverride Json? // 局部覆写属性
538
+ styleOverride Json? // 局部覆写样式
539
+
540
+ block Block @relation(fields: [blockId], references: [id], onDelete: Cascade)
541
+ component Component @relation("componentRef", fields: [componentId], references: [id])
542
+
543
+ @@map("block_components")
544
+ }
545
+
546
+ // 模板定义表(后台维护)
547
+ model Template {
548
+ id String @id @default(cuid())
549
+ name String
550
+ description String?
551
+ category String?
552
+ isDeleted Boolean @default(false)
553
+ createdAt DateTime @default(now())
554
+ updatedAt DateTime @updatedAt
555
+
556
+ items TemplateBlock[]
557
+
558
+ @@index([isDeleted])
559
+ @@map("templates")
560
+ }
561
+
562
+ // 模板内区块排布
563
+ model TemplateBlock {
564
+ id String @id @default(cuid())
565
+ templateId String
566
+ blockId String
567
+ order Int @default(0) // 排布顺序
568
+ x Float? @default(0)
569
+ y Float? @default(0)
570
+ width Float?
571
+ height Float?
572
+
573
+ template Template @relation(fields: [templateId], references: [id], onDelete: Cascade)
574
+ block Block @relation("templateBlockRef", fields: [blockId], references: [id])
575
+
576
+ @@map("template_blocks")
577
+ }
578
+
579
+ // PRD 页面/目录树节点
580
+ model PRDPage {
581
+ id String @id @default(cuid())
582
+ prdId String
583
+ parentId String? // 父节点,构建树形结构(null = 根级)
584
+ name String
585
+ order Int @default(0) // 排序
586
+ type String @default("page") // "page" | "folder"
587
+ canvas Json? // 原型画板数据
588
+ content Json? // PRD 文档内容
589
+ version Int @default(1) // 页面版本号
590
+ createdAt DateTime @default(now())
591
+ updatedAt DateTime @updatedAt
592
+
593
+ prd PRD @relation(fields: [prdId], references: [id], onDelete: Cascade)
594
+ parent PRDPage? @relation("PageHierarchy", fields: [parentId], references: [id], onDelete: Cascade)
595
+ children PRDPage[] @relation("PageHierarchy")
596
+ componentInstances ComponentInstance[]
597
+ blockInstances BlockInstance[]
598
+ pageVersions PageVersion[]
599
+
600
+ @@index([prdId, order])
601
+ @@map("prd_pages")
602
+ }
603
+
604
+ // 组件实例(画布上)
605
+ model ComponentInstance {
606
+ id String @id @default(cuid())
607
+ pageId String
608
+ componentId String? // null = 已解绑的游离副本
609
+ blockInstanceId String? // 所属区块实例
610
+ name String? // 实例别名
611
+ x Float @default(0)
612
+ y Float @default(0)
613
+ width Float @default(100)
614
+ height Float @default(100)
615
+ zIndex Int @default(0)
616
+ props Json? // 局部覆写属性
617
+ style Json? // 局部覆写样式
618
+ createdAt DateTime @default(now())
619
+ updatedAt DateTime @updatedAt
620
+
621
+ page PRDPage @relation(fields: [pageId], references: [id], onDelete: Cascade)
622
+ component Component? @relation("componentRef", fields: [componentId], references: [id])
623
+ blockInstance BlockInstance? @relation(fields: [blockInstanceId], references: [id])
624
+
625
+ @@index([pageId])
626
+ @@map("component_instances")
627
+ }
628
+
629
+ // 区块实例(画布上)
630
+ model BlockInstance {
631
+ id String @id @default(cuid())
632
+ pageId String
633
+ blockId String? // null = 已解绑的游离副本
634
+ name String? // 实例别名
635
+ x Float @default(0)
636
+ y Float @default(0)
637
+ width Float @default(400)
638
+ height Float @default(300)
639
+ zIndex Int @default(0)
640
+ props Json? // 局部覆写属性
641
+ style Json? // 局部覆写样式
642
+ createdAt DateTime @default(now())
643
+ updatedAt DateTime @updatedAt
644
+
645
+ page PRDPage @relation(fields: [pageId], references: [id], onDelete: Cascade)
646
+ block Block? @relation("blockRef", fields: [blockId], references: [id])
647
+ instances ComponentInstance[]
648
+
649
+ @@index([pageId])
650
+ @@map("block_instances")
651
+ }
652
+
653
+ // PRD 版本(手动发布创建)
654
+ model PRDVersion {
655
+ id String @id @default(cuid())
656
+ prdId String
657
+ major Int @default(1)
658
+ minor Int @default(0)
659
+ patch Int @default(0)
660
+ versionLabel String // 如 "v1", "v1.1", "v1.1.1"
661
+ parentVersion String? // 上一版本ID(版本链)
662
+ pageVersions Json // [{ pageId, pageName, version, canvas, content }]
663
+ notes String? // 发版说明
664
+ userId String
665
+ createdAt DateTime @default(now())
666
+ updatedAt DateTime?
667
+
668
+ prd PRD @relation(fields: [prdId], references: [id], onDelete: Cascade)
669
+ user User @relation(fields: [userId], references: [id])
670
+ reviews Review[]
671
+ shareVersions ShareVersion[]
672
+
673
+ @@index([prdId, createdAt])
674
+ @@index([userId])
675
+ @@map("prd_versions")
676
+ }
677
+
678
+ // 页面版本(独立的页面级别版本历史)
679
+ model PageVersion {
680
+ id String @id @default(cuid())
681
+ pageId String
682
+ version Int // 整数版本号,自增
683
+ notes String? // 版本说明(用户可编辑)
684
+ canvas Json? // 原型快照
685
+ content Json? // 文档快照
686
+ restoredFromVersion Int? // 从哪个版本号还原而来(null 表示非还原版本)
687
+ fromPRDVersionId String? // 来源:从哪个 PRD 版本创建(可选标记)
688
+ createdAt DateTime @default(now())
689
+
690
+ page PRDPage @relation(fields: [pageId], references: [id], onDelete: Cascade)
691
+
692
+ @@index([pageId, createdAt])
693
+ @@index([pageId, version])
694
+ @@map("page_versions")
695
+ }
696
+
697
+ // 评审工作流
698
+ model Review {
699
+ id String @id @default(cuid())
700
+ prdId String
701
+ versionId String
702
+ status String @default("PENDING")
703
+ reviewerId String?
704
+ notes String?
705
+ createdAt DateTime @default(now())
706
+ completedAt DateTime?
707
+
708
+ prd PRD @relation(fields: [prdId], references: [id], onDelete: Cascade)
709
+ version PRDVersion @relation(fields: [versionId], references: [id])
710
+ reviewer User? @relation("reviewer", fields: [reviewerId], references: [id])
711
+ comments ReviewComment[]
712
+
713
+ @@unique([prdId, versionId])
714
+ @@index([prdId])
715
+ @@index([versionId])
716
+ @@index([reviewerId])
717
+ @@map("reviews")
718
+ }
719
+
720
+ // 评审留言
721
+ model ReviewComment {
722
+ id String @id @default(cuid())
723
+ reviewId String
724
+ userId String
725
+ content String
726
+ pageId String? // 关联页面
727
+ elementId String? // 关联具体元素
728
+ parentId String? // 回复上级留言
729
+ createdAt DateTime @default(now())
730
+
731
+ review Review @relation(fields: [reviewId], references: [id], onDelete: Cascade)
732
+ user User @relation(fields: [userId], references: [id])
733
+ parent ReviewComment? @relation("ReviewCommentReplies", fields: [parentId], references: [id])
734
+ replies ReviewComment[] @relation("ReviewCommentReplies")
735
+
736
+ @@index([reviewId])
737
+ @@index([userId])
738
+ @@index([parentId])
739
+ @@map("review_comments")
740
+ }
741
+
742
+ // 评论(PRD 级别 / 页面级别 / 元素级别)
743
+ model Comment {
744
+ id String @id @default(cuid())
745
+ prdId String
746
+ pageId String? // 可选:针对某页面
747
+ elementId String? // 可选:针对某元素(组件/区块实例ID)
748
+ userId String
749
+ content String
750
+ parentId String? // 回复上级评论
751
+ position Json? // 文本选区位置 { from: number, to: number }
752
+ resolved Boolean @default(false)
753
+ createdAt DateTime @default(now())
754
+ updatedAt DateTime @updatedAt
755
+
756
+ prd PRD @relation(fields: [prdId], references: [id], onDelete: Cascade)
757
+ user User @relation(fields: [userId], references: [id])
758
+ parent Comment? @relation("CommentReplies", fields: [parentId], references: [id])
759
+ replies Comment[] @relation("CommentReplies")
760
+ ignores CommentIgnore[]
761
+
762
+ @@index([prdId])
763
+ @@index([userId])
764
+ @@index([parentId])
765
+ @@index([pageId])
766
+ @@map("comments")
767
+ }
768
+
769
+ model CommentIgnore {
770
+ id String @id @default(cuid())
771
+ userId String
772
+ commentId String
773
+ createdAt DateTime @default(now())
774
+
775
+ user User @relation(fields: [userId], references: [id])
776
+ comment Comment @relation(fields: [commentId], references: [id])
777
+
778
+ @@unique([userId, commentId])
779
+ @@index([userId])
780
+ @@index([commentId])
781
+ @@map("comment_ignores")
782
+ }
783
+
784
+ // 分享链接
785
+ model Share {
786
+ id String @id @default(cuid())
787
+ prdId String
788
+ token String @unique
789
+ type String @default("prd") // "prd" | 预留扩展
790
+ status String @default("ACTIVE")
791
+ password String? // 加密分享密码(4位,数字+大写+小写字母)
792
+ hasPassword Boolean @default(false)
793
+ expiresAt DateTime?
794
+ createdAt DateTime @default(now())
795
+ updatedAt DateTime @updatedAt
796
+
797
+ prd PRD @relation(fields: [prdId], references: [id], onDelete: Cascade)
798
+ shareVersions ShareVersion[]
799
+
800
+ @@index([token])
801
+ @@index([prdId, type, status])
802
+ @@index([status])
803
+ @@index([expiresAt])
804
+ @@map("shares")
805
+ }
806
+
807
+ // 标签表
808
+ model Tag {
809
+ id String @id @default(cuid())
810
+ name String
811
+ color Int @default(0)
812
+ sortOrder Int @default(0)
813
+ isHidden Boolean @default(false)
814
+ userId String
815
+ createdAt DateTime @default(now())
816
+ updatedAt DateTime @updatedAt
817
+
818
+ user User @relation(fields: [userId], references: [id], onDelete: Cascade)
819
+ prdTags PRDTag[]
820
+
821
+ @@unique([name, userId])
822
+ @@index([sortOrder])
823
+ @@index([userId])
824
+ @@map("tags")
825
+ }
826
+
827
+ // PRD-标签多对多
828
+ model PRDTag {
829
+ id String @id @default(cuid())
830
+ prdId String
831
+ tagId String
832
+ createdAt DateTime @default(now())
833
+
834
+ prd PRD @relation(fields: [prdId], references: [id], onDelete: Cascade)
835
+ tag Tag @relation(fields: [tagId], references: [id], onDelete: Cascade)
836
+
837
+ @@unique([prdId, tagId])
838
+ @@index([prdId])
839
+ @@index([tagId])
840
+ @@map("prd_tags")
841
+ }
842
+
843
+ // 组件-标签多对多
844
+ // TODO(ComponentTag 重新调整): Tag 已加 userId,ComponentTag 业务待后续调整。
845
+ // DB 端 FK 已 drop(迁移同步);component_tags 表保留,tagId 列 + 唯一约束 (componentId, tagId) 保留;
846
+ // 移除 Prisma 关系以避免与新 Tag 语义冲突。
847
+ model ComponentTag {
848
+ id String @id @default(cuid())
849
+ componentId String
850
+ tagId String
851
+
852
+ component Component @relation(fields: [componentId], references: [id], onDelete: Cascade)
853
+
854
+ @@unique([componentId, tagId])
855
+ @@map("component_tags")
856
+ }
857
+
858
+ model Reference {
859
+ id String @id @default(uuid())
860
+ sourceModule String
861
+ sourceEntityId String
862
+ targetModule String
863
+ targetEntityId String
864
+ createdAt DateTime @default(now())
865
+
866
+ @@index([sourceModule, sourceEntityId])
867
+ @@index([targetModule, targetEntityId])
868
+ @@map("references")
869
+ }
870
+
871
+ model ShareVersion {
872
+ id String @id @default(cuid())
873
+ shareId String
874
+ versionId String
875
+
876
+ share Share @relation(fields: [shareId], references: [id], onDelete: Cascade)
877
+ version PRDVersion @relation(fields: [versionId], references: [id], onDelete: Cascade)
878
+
879
+ @@unique([shareId, versionId])
880
+ @@index([shareId])
881
+ @@index([versionId])
882
+ @@map("share_versions")
883
+ }
884
+
885
+ // ========== AnyHub 模型 ==========
886
+
887
+ model AnyHubApp {
888
+ id String @id @default(cuid())
889
+ name String @unique
890
+ displayName String
891
+ description String?
892
+ version String @default("0.1.0")
893
+ source String
894
+ url String?
895
+ localPath String?
896
+ proxyBasePath String?
897
+ iconUrl String?
898
+ pinned Boolean @default(false)
899
+ status String @default("installing")
900
+ userId String
901
+ githubUrl String?
902
+ createdAt DateTime @default(now())
903
+ updatedAt DateTime @updatedAt
904
+
905
+ user User @relation(fields: [userId], references: [id], onDelete: Cascade)
906
+ permissions AnyHubPermission[]
907
+
908
+ @@index([userId])
909
+ @@index([pinned])
910
+ }
911
+
912
+ model AnyHubPermission {
913
+ id String @id @default(cuid())
914
+ appId String
915
+ capability String
916
+ granted Boolean @default(false)
917
+ userId String
918
+ createdAt DateTime @default(now())
919
+ updatedAt DateTime @updatedAt
920
+
921
+ app AnyHubApp @relation(fields: [appId], references: [id], onDelete: Cascade)
922
+
923
+ @@unique([appId, capability, userId])
924
+ @@index([appId])
925
+ }
926
+
927
+ model AnyHubMarketApp {
928
+ id String @id @default(cuid())
929
+ name String
930
+ displayName String
931
+ description String?
932
+ entryUrl String
933
+ icon String?
934
+ iconType String @default("ICON")
935
+ logo String?
936
+ category String?
937
+ tags String?
938
+ downloads Int @default(0)
939
+ capabilities Json
940
+ status String @default("DRAFT")
941
+ publishedAt DateTime?
942
+ userId String
943
+ recommendationId String? @unique
944
+ createdAt DateTime @default(now())
945
+ updatedAt DateTime @updatedAt
946
+
947
+ user User @relation(fields: [userId], references: [id])
948
+ recommendation AnyHubRecommendation? @relation("anyhubRecommendation", fields: [recommendationId], references: [id])
949
+
950
+ @@index([status])
951
+ @@index([category])
952
+ }
953
+
954
+ model AnyHubRecommendation {
955
+ id String @id @default(cuid())
956
+ githubUrl String @unique
957
+ name String?
958
+ displayName String?
959
+ description String?
960
+ status String @default("pending")
961
+ reviewedBy String?
962
+ reviewedAt DateTime?
963
+ marketplaceAppId String?
964
+ createdAt DateTime @default(now())
965
+ updatedAt DateTime @updatedAt
966
+
967
+ marketplaceApp AnyHubMarketApp? @relation("anyhubRecommendation")
968
+ reviewer User? @relation("reviewedAnyhubRecommendations", fields: [reviewedBy], references: [id])
969
+ recommenders AnyHubRecommender[]
970
+
971
+ @@index([status])
972
+ }
973
+
974
+ model AnyHubRecommender {
975
+ id String @id @default(cuid())
976
+ recommendationId String
977
+ userId String
978
+ reason String?
979
+ createdAt DateTime @default(now())
980
+
981
+ recommendation AnyHubRecommendation @relation(fields: [recommendationId], references: [id], onDelete: Cascade)
982
+ user User @relation(fields: [userId], references: [id], onDelete: Cascade)
983
+
984
+ @@unique([recommendationId, userId])
985
+ }
986
+
987
+ // 设计稿
988
+ model DesignProject {
989
+ id String @id @default(cuid())
990
+ name String
991
+ documentId String?
992
+ sketchName String?
993
+ version Int @default(1)
994
+ userId String
995
+ user User @relation(fields: [userId], references: [id])
996
+ artboards Artboard[]
997
+ createdAt DateTime @default(now())
998
+ updatedAt DateTime @updatedAt
999
+
1000
+ @@index([userId])
1001
+ @@map("design_projects")
1002
+ }
1003
+
1004
+ model Artboard {
1005
+ id String @id @default(cuid())
1006
+ projectId String
1007
+ project DesignProject @relation(fields: [projectId], references: [id], onDelete: Cascade)
1008
+ name String
1009
+ pageName String?
1010
+ width Int
1011
+ height Int
1012
+ pngPath String
1013
+ svgPath String?
1014
+ annotationJson Json?
1015
+ layerCount Int?
1016
+ sortOrder Int @default(0)
1017
+ createdAt DateTime @default(now())
1018
+ exports ArtboardExport[]
1019
+
1020
+ @@index([projectId])
1021
+ @@map("design_artboards")
1022
+ }
1023
+
1024
+ model ArtboardExport {
1025
+ id String @id @default(cuid())
1026
+ artboardId String
1027
+ artboard Artboard @relation(fields: [artboardId], references: [id], onDelete: Cascade)
1028
+ layerName String
1029
+ layerId String
1030
+ scale Int
1031
+ format String @default("png")
1032
+ filePath String
1033
+ width Int
1034
+ height Int
1035
+
1036
+ @@unique([artboardId, layerId, scale])
1037
+ @@map("design_artboard_exports")
1038
+ }
1039
+ // AI 服务商元数据(admin 维护)
1040
+ model AiProvider {
1041
+ id String @id @default(cuid())
1042
+ providerId String @unique // 业务标识 "openai" "anthropic",用于 adapter 路由与 AiProviderConfig 软关联
1043
+ displayName String
1044
+ brandName String
1045
+ description String
1046
+ category String @default("popular") // recommended|popular|local
1047
+ adapterKind String // openai|anthropic|gemini|groq|grok|openrouter|ollama|openai-compatible
1048
+ defaultBaseUrl String?
1049
+ keyUrl String?
1050
+ docsUrl String?
1051
+ keyPlaceholder String?
1052
+ iconSlug String?
1053
+ brandColor String @default("#6467F2")
1054
+ badge String? // 卡片角标文案(如「推荐」「热门」「NEW」),admin 可配
1055
+ isLocal Boolean @default(false)
1056
+ enabled Boolean @default(true) // 平台级开关:停用后未配置用户看不到,已配置用户继续可用
1057
+ models Json // AiModelMeta[](admin 可编辑)
1058
+ sortOrder Int @default(0)
1059
+ createdAt DateTime @default(now())
1060
+ updatedAt DateTime @updatedAt
1061
+
1062
+ @@map("ai_providers")
1063
+ }
1064
+
1065
+ // 用户级 AI 服务商配置
1066
+ model AiProviderConfig {
1067
+ id String @id @default(cuid())
1068
+ userId String
1069
+ providerId String // 业务 providerId,软关联 AiProvider.providerId(不设外键 cascade,避免 admin 删除丢失用户配置)
1070
+ apiKey String? // 加密存储 TODO
1071
+ baseUrl String?
1072
+ enabled Boolean @default(false) // 用户级开关:是否在对话模型选择器中暴露
1073
+ models Json? // string[] 用户启用的模型 ID(有序,顺序 = 模型选择器展示顺序)
1074
+ customModels Json? // AiModelMeta[] 用户私有的自定义/刷新获取的模型元数据,与 AiProvider.models 取并集按 id 去重
1075
+ createdAt DateTime @default(now())
1076
+ updatedAt DateTime @updatedAt
1077
+
1078
+ user User @relation(fields: [userId], references: [id], onDelete: Cascade)
1079
+
1080
+ @@unique([userId, providerId])
1081
+ @@index([userId])
1082
+ @@index([providerId])
1083
+ @@map("ai_provider_configs")
1084
+ }
1085
+
1086
+ // 第三方依赖版本管理(admin 决策是否升级)
1087
+ // currentVersion 实时读 monorepo 各 package.json,不存 DB;latestVersion 缓存自 npm registry
1088
+ model ThirdPartyDependency {
1089
+ id String @id @default(cuid())
1090
+ name String @unique // npm 包名,如 "@tiptap/core" / "mind-elixir"
1091
+ displayName String? // 展示名(默认取 name)
1092
+ description String? // 依赖用途说明(admin 可编辑)
1093
+ homepage String? // 官网 / 仓库
1094
+ category String @default("other") // framework|ui|editor|ai|state|tool|other
1095
+ isWatched Boolean @default(true) // 是否置顶「关注列表」
1096
+ priority String @default("normal") // high|normal|low
1097
+ latestVersion String? // 缓存的 npm registry latest 版本
1098
+ latestChangelog String? // 最新版本的更新日志(来自 GitHub Releases,markdown)
1099
+ lastSyncAt DateTime? // 最后一次成功同步 npm registry 的时间
1100
+ notes String? // 备注(升级注意事项等)
1101
+ sortOrder Int @default(0)
1102
+ createdAt DateTime @default(now())
1103
+ updatedAt DateTime @updatedAt
1104
+
1105
+ @@index([category])
1106
+ @@index([isWatched])
1107
+ @@map("third_party_dependencies")
1108
+ }
1109
+
1110
+ // ============ 公共知识库:法律法规 ============
1111
+ model LegalRegulation {
1112
+ id String @id @default(cuid())
1113
+ canonicalId String
1114
+ title String
1115
+ jurisdiction String
1116
+ region String?
1117
+ country String?
1118
+ category String
1119
+ issuingAuthority String?
1120
+ effectiveDate DateTime?
1121
+ summary String?
1122
+ content String?
1123
+ sourceUrl String?
1124
+ likeCount Int @default(0)
1125
+ tags Json @default(dbgenerated("'[]'"))
1126
+ visibility String @default("private")
1127
+ userId String
1128
+ isDeleted Boolean @default(false)
1129
+ createdAt DateTime @default(now())
1130
+ updatedAt DateTime @updatedAt
1131
+
1132
+ user User @relation(fields: [userId], references: [id], onDelete: Cascade)
1133
+
1134
+ @@index([canonicalId, visibility])
1135
+ @@index([userId, visibility])
1136
+ @@index([jurisdiction, category])
1137
+ @@map("legal_regulations")
1138
+ }
1139
+
1140
+ model IndustryCompliance {
1141
+ id String @id @default(cuid())
1142
+ canonicalId String
1143
+ title String
1144
+ industry String
1145
+ regulatoryBody String?
1146
+ region String?
1147
+ summary String?
1148
+ content String?
1149
+ requirements Json @default(dbgenerated("'[]'"))
1150
+ sourceUrl String?
1151
+ likeCount Int @default(0)
1152
+ tags Json @default(dbgenerated("'[]'"))
1153
+ visibility String @default("private")
1154
+ userId String
1155
+ isDeleted Boolean @default(false)
1156
+ createdAt DateTime @default(now())
1157
+ updatedAt DateTime @updatedAt
1158
+
1159
+ user User @relation(fields: [userId], references: [id], onDelete: Cascade)
1160
+
1161
+ @@index([canonicalId, visibility])
1162
+ @@index([userId, visibility])
1163
+ @@index([industry])
1164
+ @@map("industry_compliances")
1165
+ }
1166
+
1167
+ model DictionaryTerm {
1168
+ id String @id @default(cuid())
1169
+ canonicalId String
1170
+ title String
1171
+ industry String
1172
+ definition String?
1173
+ aliases Json @default(dbgenerated("'[]'"))
1174
+ examples Json @default(dbgenerated("'[]'"))
1175
+ referenceUrl String?
1176
+ tags Json @default(dbgenerated("'[]'"))
1177
+ visibility String @default("private")
1178
+ userId String
1179
+ isDeleted Boolean @default(false)
1180
+ createdAt DateTime @default(now())
1181
+ updatedAt DateTime @updatedAt
1182
+
1183
+ user User @relation(fields: [userId], references: [id], onDelete: Cascade)
1184
+
1185
+ @@index([canonicalId, visibility])
1186
+ @@index([userId, visibility])
1187
+ @@index([title])
1188
+ @@map("dictionary_terms")
1189
+ }
1190
+
1191
+ model WebsiteBookmark {
1192
+ id String @id @default(cuid())
1193
+ canonicalId String
1194
+ title String
1195
+ url String
1196
+ categoryId String?
1197
+ summary String?
1198
+ faviconUrl String?
1199
+ faviconSyncedAt DateTime?
1200
+ isGithub Boolean @default(false)
1201
+ githubOwner String?
1202
+ githubRepo String?
1203
+ githubStars Int?
1204
+ githubLatestVersion String?
1205
+ githubVersionDate DateTime?
1206
+ githubRepoUpdatedAt DateTime?
1207
+ githubSyncedAt DateTime?
1208
+ tags Json @default(dbgenerated("'[]'"))
1209
+ visibility String @default("private")
1210
+ userId String
1211
+ isDeleted Boolean @default(false)
1212
+ createdAt DateTime @default(now())
1213
+ updatedAt DateTime @updatedAt
1214
+
1215
+ user User @relation(fields: [userId], references: [id], onDelete: Cascade)
1216
+ category BookmarkCategory? @relation(fields: [categoryId], references: [id], onDelete: SetNull)
1217
+
1218
+ @@index([canonicalId, visibility])
1219
+ @@index([userId, visibility])
1220
+ @@index([categoryId])
1221
+ @@index([isGithub])
1222
+ @@map("website_bookmarks")
1223
+ }
1224
+
1225
+ model BookmarkCategory {
1226
+ id String @id @default(cuid())
1227
+ name String
1228
+ icon String @default("Folder")
1229
+ sortOrder Int @default(0)
1230
+ isDefault Boolean @default(false)
1231
+ userId String?
1232
+ createdAt DateTime @default(now())
1233
+ updatedAt DateTime @updatedAt
1234
+
1235
+ user User? @relation(fields: [userId], references: [id], onDelete: Cascade)
1236
+ bookmarks WebsiteBookmark[]
1237
+
1238
+ @@unique([userId, name])
1239
+ @@index([userId])
1240
+ @@map("bookmark_categories")
1241
+ }
1242
+
1243
+ model KnowledgeShareRecord {
1244
+ id String @id @default(cuid())
1245
+ entityType String
1246
+ canonicalId String
1247
+ userId String
1248
+ sharedAt DateTime @default(now())
1249
+
1250
+ user User @relation(fields: [userId], references: [id], onDelete: Cascade)
1251
+
1252
+ @@unique([entityType, canonicalId, userId])
1253
+ @@index([entityType, canonicalId])
1254
+ @@map("knowledge_share_records")
1255
+ }
1256
+
1257
+ model ArticleFavorite {
1258
+ id String @id @default(cuid())
1259
+ entityType String
1260
+ entityId String
1261
+ userId String
1262
+ createdAt DateTime @default(now())
1263
+
1264
+ user User @relation(fields: [userId], references: [id], onDelete: Cascade)
1265
+
1266
+ @@unique([entityType, entityId, userId])
1267
+ @@index([userId, entityType])
1268
+ @@map("article_favorites")
1269
+ }
1270
+
1271
+ // ── 子项目 2:托管 AI 与 BYOK 统一层 ──
1272
+
1273
+ /// 订阅计划定义(seed 数据)
1274
+ model Plan {
1275
+ id String @id @default(cuid())
1276
+ name String @unique
1277
+ displayName String
1278
+ description String?
1279
+ priceMonthly Int @default(0)
1280
+ priceYearly Int?
1281
+ currency String @default("usd")
1282
+ maxSeats Int?
1283
+ maxGuestSeats Int?
1284
+ maxWorkspaces Int?
1285
+ aiQuotaMonthly Int?
1286
+ features Json @default(dbgenerated("'{}'"))
1287
+ isPublic Boolean @default(true)
1288
+ sortOrder Int @default(0)
1289
+ createdAt DateTime @default(now())
1290
+ updatedAt DateTime @updatedAt
1291
+
1292
+ /// Provider 产品与价格同步(子项目 3 新增)
1293
+ providerPriceIdUsd String?
1294
+ providerPriceIdCny String?
1295
+ providerPriceIdUsdYearly String?
1296
+ providerPriceIdCnyYearly String?
1297
+
1298
+ seatPriceMonthly Int?
1299
+ providerSeatPriceIdUsd String?
1300
+ providerSeatPriceIdCny String?
1301
+
1302
+ agentQuota Int?
1303
+ skillQuota Int?
1304
+
1305
+ subscriptions Subscription[]
1306
+
1307
+ @@map("plans")
1308
+ }
1309
+
1310
+ /// 订阅记录(User 级,一人一订阅)
1311
+ model Subscription {
1312
+ id String @id @default(cuid())
1313
+ userId String @unique
1314
+ user User @relation(fields: [userId], references: [id])
1315
+ planId String
1316
+ plan Plan @relation(fields: [planId], references: [id])
1317
+ status String @default("ACTIVE")
1318
+ addonSeats Int @default(0)
1319
+
1320
+ provider String @default("creem")
1321
+ providerCustomerId String?
1322
+ providerSubscriptionId String? @unique
1323
+ providerPriceId String?
1324
+ providerSeatSubscriptionId String?
1325
+
1326
+ currentPeriodStart DateTime?
1327
+ currentPeriodEnd DateTime?
1328
+ cancelAtPeriodEnd Boolean @default(false)
1329
+ canceledAt DateTime?
1330
+ trialEndsAt DateTime?
1331
+
1332
+ createdAt DateTime @default(now())
1333
+ updatedAt DateTime @updatedAt
1334
+
1335
+ /// 关联发票(子项目 3 新增)
1336
+ invoices Invoice[]
1337
+
1338
+ @@map("subscriptions")
1339
+ }
1340
+
1341
+ /// 用量记录(按月聚合,用于额度判断)
1342
+ model UsageRecord {
1343
+ id String @id @default(cuid())
1344
+ workspaceId String
1345
+ workspace Workspace @relation(fields: [workspaceId], references: [id])
1346
+ metric String
1347
+ period String
1348
+ count Int @default(0)
1349
+ resetAt DateTime
1350
+
1351
+ createdAt DateTime @default(now())
1352
+ updatedAt DateTime @updatedAt
1353
+
1354
+ @@unique([workspaceId, metric, period])
1355
+ @@index([workspaceId, metric])
1356
+ @@map("usage_records")
1357
+ }
1358
+
1359
+ /// AI 用量明细(按次扣减 + 成本审计)
1360
+ model AiUsageLog {
1361
+ id String @id @default(cuid())
1362
+ workspaceId String
1363
+ workspace Workspace @relation(fields: [workspaceId], references: [id])
1364
+ userId String
1365
+ user User @relation(fields: [userId], references: [id])
1366
+ source String @default("byok")
1367
+ provider String
1368
+ model String
1369
+ inputTokens Int @default(0)
1370
+ outputTokens Int @default(0)
1371
+ costUsd String?
1372
+ success Boolean @default(true)
1373
+ errorMessage String?
1374
+ durationMs Int?
1375
+
1376
+ createdAt DateTime @default(now())
1377
+
1378
+ @@index([workspaceId, createdAt])
1379
+ @@index([userId, createdAt])
1380
+ @@index([workspaceId, source, createdAt])
1381
+ @@index([workspaceId, success, createdAt])
1382
+ @@map("ai_usage_logs")
1383
+ }
1384
+
1385
+ // ── 子项目 3:计费与订阅系统 ──
1386
+
1387
+ /// 账单记录(Webhook 同步)
1388
+ model Invoice {
1389
+ id String @id @default(cuid())
1390
+ subscriptionId String
1391
+ subscription Subscription @relation(fields: [subscriptionId], references: [id])
1392
+ providerInvoiceId String @unique
1393
+ providerEventId String?
1394
+ amountDue Int
1395
+ amountPaid Int
1396
+ currency String @default("usd")
1397
+ status String
1398
+ pdfUrl String?
1399
+ hostedInvoiceUrl String?
1400
+ periodStart DateTime
1401
+ periodEnd DateTime
1402
+ paidAt DateTime?
1403
+
1404
+ createdAt DateTime @default(now())
1405
+
1406
+ @@index([subscriptionId, createdAt])
1407
+ @@map("invoices")
1408
+ }
1409
+
1410
+ /// Provider Webhook 事件幂等记录
1411
+ model ProviderEvent {
1412
+ id String @id @default(cuid())
1413
+ providerEventId String @unique
1414
+ type String
1415
+ status String @default("processing")
1416
+ payload Json?
1417
+ errorMessage String?
1418
+ processedAt DateTime?
1419
+
1420
+ createdAt DateTime @default(now())
1421
+
1422
+ @@index([type, createdAt])
1423
+ @@map("provider_events")
1424
+ }
1425
+
1426
+ // ==================== 子项目 5:企业治理模型(sqlite 降级版)====================
1427
+
1428
+ model LicenseKey {
1429
+ id String @id @default(cuid())
1430
+ key String @unique
1431
+ signature String
1432
+ payload String @default(dbgenerated("'{}'"))
1433
+ customerId String
1434
+ customerName String
1435
+ customerEmail String
1436
+ plan String @default("enterprise")
1437
+ seats Int
1438
+ features String @default(dbgenerated("'{}'"))
1439
+ issuedAt DateTime @default(now())
1440
+ expiresAt DateTime?
1441
+ revokedAt DateTime?
1442
+ issuedById String?
1443
+
1444
+ @@map("license_keys")
1445
+ }
1446
+
1447
+ model LicenseActivation {
1448
+ id String @id @default(cuid())
1449
+ licenseId String
1450
+ instanceId String @unique
1451
+ fingerprint String
1452
+ activatedAt DateTime @default(now())
1453
+ lastSeenAt DateTime?
1454
+ revokedAt DateTime?
1455
+
1456
+ @@map("license_activations")
1457
+ }
1458
+
1459
+ model AuditLog {
1460
+ id String @id @default(cuid())
1461
+ workspaceId String?
1462
+ userId String?
1463
+ action String
1464
+ resourceType String
1465
+ resourceId String?
1466
+ metadata String @default(dbgenerated("'{}'"))
1467
+ ip String?
1468
+ userAgent String?
1469
+ createdAt DateTime @default(now())
1470
+
1471
+ @@index([workspaceId, createdAt])
1472
+ @@index([userId, createdAt])
1473
+ @@index([action])
1474
+ @@map("audit_logs")
1475
+ }
1476
+
1477
+ model SsoProvider {
1478
+ id String @id @default(cuid())
1479
+ workspaceId String
1480
+ type String
1481
+ name String
1482
+ config String @default(dbgenerated("'{}'"))
1483
+ domain String?
1484
+ enabled Boolean @default(true)
1485
+ createdAt DateTime @default(now())
1486
+ updatedAt DateTime @updatedAt
1487
+
1488
+ @@unique([workspaceId, type, domain])
1489
+ @@map("sso_providers")
1490
+ }
1491
+
1492
+ model SsoMapping {
1493
+ id String @id @default(cuid())
1494
+ providerId String
1495
+ externalId String
1496
+ userId String
1497
+ email String?
1498
+ createdAt DateTime @default(now())
1499
+
1500
+ @@unique([providerId, externalId])
1501
+ @@map("sso_mappings")
1502
+ }
1503
+
1504
+ model CustomRole {
1505
+ id String @id @default(cuid())
1506
+ workspaceId String
1507
+ name String
1508
+ permissions String @default(dbgenerated("'{}'"))
1509
+ isSystem Boolean @default(false)
1510
+ createdAt DateTime @default(now())
1511
+ updatedAt DateTime @updatedAt
1512
+
1513
+ @@unique([workspaceId, name])
1514
+ @@map("custom_roles")
1515
+ }
1516
+
1517
+ model RoleAssignment {
1518
+ id String @id @default(cuid())
1519
+ roleId String
1520
+ userId String
1521
+ createdAt DateTime @default(now())
1522
+
1523
+ @@unique([roleId, userId])
1524
+ @@map("role_assignments")
1525
+ }
1526
+
1527
+ model ComplianceExport {
1528
+ id String @id @default(cuid())
1529
+ type String
1530
+ scope String
1531
+ targetId String
1532
+ status String @default("pending")
1533
+ downloadUrl String?
1534
+ expiresAt DateTime?
1535
+ error String?
1536
+ content String?
1537
+ requestedBy String?
1538
+ createdAt DateTime @default(now())
1539
+ completedAt DateTime?
1540
+
1541
+ @@map("compliance_exports")
1542
+ }
1543
+
1544
+ // P6 用户自定义 Agent(人设/工具/模型/温度/开场白/建议问题/知识库绑定)
1545
+ model UserAgent {
1546
+ id String @id @default(cuid())
1547
+ userId String
1548
+ name String
1549
+ description String?
1550
+ avatarEmoji String?
1551
+ systemPrompt String
1552
+ allowedTools Json @default(dbgenerated("'[]'"))
1553
+ model String?
1554
+ temperature Float?
1555
+ greeting String?
1556
+ suggestedPrompts Json?
1557
+ knowledgeBaseIds Json @default(dbgenerated("'[]'"))
1558
+ createdAt DateTime @default(now())
1559
+ updatedAt DateTime @updatedAt
1560
+ isDeleted Boolean @default(false)
1561
+
1562
+ user User @relation(fields: [userId], references: [id], onDelete: Cascade)
1563
+ threads ChatThread[]
1564
+
1565
+ @@index([userId])
1566
+ @@map("user_agents")
1567
+ }
1568
+
1569
+ // P6 用户自定义 Skill(斜杠指令模板)
1570
+ model UserSkill {
1571
+ id String @id @default(cuid())
1572
+ userId String
1573
+ name String
1574
+ title String
1575
+ template String
1576
+ createdAt DateTime @default(now())
1577
+ updatedAt DateTime @updatedAt
1578
+ isDeleted Boolean @default(false)
1579
+
1580
+ user User @relation(fields: [userId], references: [id], onDelete: Cascade)
1581
+
1582
+ @@unique([userId, name])
1583
+ @@map("user_skills")
1584
+ }