File size: 6,583 Bytes
db242f8
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
// Menu
// Website
// User
// Product
// Order
// OpenAI API
// Chat Message

generator client {
  provider = "prisma-client-js"
  output   = "./client"
}

datasource db {
  provider = "postgresql"
  url      = env("DATABASE_URL")
}

// =======
// |WebSite|
// =======
model Announcement {
  id        Int     @id @default(autoincrement())
  title     String
  content   String
  sortOrder Float?
  isHidden  Boolean @default(false)

  // metadata
  createdAt DateTime @default(now()) @db.Timestamp(6)
  updatedAt DateTime @default(now()) @updatedAt
}

model Setting {
  key   String @id
  value Json?
}

model Cache {
  key   String @id
  value Json?
}

// =======
// |User|
// =======

enum Role {
  Admin
  User
}

model User {
  id       Int     @id @default(autoincrement())
  role     Role    @default(User)
  name     String? @unique // 用户名
  email    String? @unique // 邮箱
  phone    String? @unique // 手机 +86
  password String? @db.Text // 密码

  // relation
  oauths       OAuth[]
  orders       Order[]
  chatSessions ChatSession[]
  chatMessages ChatMessage[]

  createdAt DateTime @default(now()) @db.Timestamp(6) // 注册时间
  updatedAt DateTime @default(now()) @updatedAt
  isBlocked Boolean  @default(false) // 是否被封禁
}

enum OAuthProvider {
  Apple
  Github
  Google
  Wechat
  Microsofe
}

model OAuth {
  id           Int           @id @default(autoincrement())
  provider     OAuthProvider
  providerId   String
  accessToken  String
  refreshToken String
  expiredAt    DateTime

  data Json? // 自定义数据

  // relation
  user   User @relation(fields: [userId], references: [id])
  userId Int

  // metadata
  createdAt DateTime @default(now()) @db.Timestamp(6) // 注册时间
  updatedAt DateTime @default(now()) @updatedAt

  @@unique([provider, providerId])
}

// ==========
// |Product|
// ==========
model Model {
  id    Int     @id @default(autoincrement())
  name  String  @unique // 模型实际的名称
  label String? // 展示的名称,为空则使用实际名称

  price      Int // 单位为分
  isDisabled Boolean @default(false) // 是否启用
  sortOrder  Float?

  // relation
  products ModelInProduct[]
  messages ChatMessage[]

  @@unique([id, name])
}

model Product {
  id       Int      @id @default(autoincrement())
  name     String   @unique
  features String[]

  isHidden Boolean @default(false) // 是否隐藏

  price     Int // 单位为分,产品单价
  stock     Int    @default(-1) // 库存
  sortOrder Float?

  // 产品的持续时间
  // -1 表示一次性商品
  // 2592000 严格表示一个月,非固定 30 天,可能为 28/29/30/31 天
  // 7776000 严格表示一个季度,同上
  // 31104000 严格表示一年,即 365 或 366 天
  duration Int

  models ModelInProduct[]

  // relation
  order      Order[]
  category   Category? @relation(fields: [categoryId], references: [id])
  categoryId Int?
}

// 套餐次数上线
model ModelInProduct {
  // 在duration时间内限制使用times次
  times    Int // 限制次数
  duration Int // 单位为秒,限制持续时间 若为 0 则不限制 如每三小时小时

  // 对应的 Model
  model   Model @relation(fields: [modelId], references: [id])
  modelId Int

  // 对应的 Product
  product   Product @relation(fields: [productId], references: [id])
  productId Int

  @@id([modelId, productId])
}

// 产品分类,用于标签化管理产品
model Category {
  id   Int    @id @default(autoincrement())
  name String @unique

  isHidden  Boolean @default(false) // 是否隐藏
  sortOrder Float?

  products Product[]
}

enum OrderStatus {
  Pending // 待支付
  Paid // 已支付
  Failed // 支付失败
  Refunded // 已退款
}

enum OrderType {
  Subscription // 订阅
  OneTime // 一次性
}

// 订单
model Order {
  id     String      @id @unique
  type   OrderType
  status OrderStatus @default(Pending)

  count  Int @default(1) // 数量
  amount Int // 订单金额,单位为分

  startAt DateTime?
  endAt   DateTime?

  isCurrent Boolean @default(false) // 是否为当前订单

  // relation

  user   User @relation(fields: [userId], references: [id])
  userId Int

  product   Product @relation(fields: [productId], references: [id])
  productId Int

  // metadata
  createdAt DateTime @default(now())
  updatedAt DateTime @default(now()) @updatedAt

  rawData Json? // 支付的原始数据
}

// =============
// |OpenAI API|
// =============
enum OpenAIKeyStatus {
  Active
  Disabled
  Expired
}

model OpenAIKey {
  id     Int             @id @default(autoincrement())
  key    String          @unique
  url    String?
  status OpenAIKeyStatus @default(Active)

  weight     Int?
  total      Float?
  usage      Float?
  tokenUsage Float?
  priceRatio Float?
  rateLimit  Int?
  note       String? @db.Text

  // metadata
  createdAt DateTime  @default(now())
  updatedAt DateTime  @default(now()) @updatedAt
  expiredAt DateTime?
}

// ===============
// |Chat Message|
// ===============

model ChatSession {
  id String @id @default(uuid())

  // 主题,使用模型总结
  // TODO 当用户手动重命名后,不再使用模型总结
  topic              String?
  lastSummarizeIndex Int?

  memoryPrompt String?
  messages     ChatMessage[]

  // relation
  user   User @relation(fields: [userId], references: [id])
  userId Int

  // metadata
  isDeleted Boolean  @default(false) // 用户删除
  isBlocked Boolean  @default(false) // 封禁
  createdAt DateTime @default(now())
  updatedAt DateTime @default(now()) @updatedAt
}

enum ChatMessageRole {
  System
  User
  Assistant
}

model ChatMessage {
  id String @id @default(uuid())

  role    ChatMessageRole
  content String
  // Tokenizer number
  token   Int?

  // relation
  chatSession   ChatSession? @relation(fields: [chatSessionId], references: [id])
  chatSessionId String?

  model   Model? @relation(fields: [modelId], references: [id])
  modelId Int?

  user   User @relation(fields: [userId], references: [id])
  userId Int

  // metadata
  isDeleted Boolean  @default(false) // usually deleted by user
  isBlocked Boolean  @default(false) // usually blocked by admin or policy reasons
  createdAt DateTime @default(now())
  updatedAt DateTime @default(now()) @updatedAt
}

// 用户设置
model ChatSetting {
  userId              Int  @id
  historyMessageCount Int?

  submitKey   String? // 提交键
  tightBorder Boolean @default(false) // 紧凑模式

  defalutModelId  Int?
  temperature     Float?
  maxTokens       Int?
  presencePenalty Float?
}