@goscribe/server 1.2.0 → 1.3.1
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.
- package/check-difficulty.cjs +14 -0
- package/check-questions.cjs +14 -0
- package/db-summary.cjs +22 -0
- package/dist/context.d.ts +5 -1
- package/dist/lib/activity_human_description.d.ts +13 -0
- package/dist/lib/activity_human_description.js +221 -0
- package/dist/lib/activity_human_description.test.d.ts +1 -0
- package/dist/lib/activity_human_description.test.js +16 -0
- package/dist/lib/activity_log_service.d.ts +87 -0
- package/dist/lib/activity_log_service.js +276 -0
- package/dist/lib/activity_log_service.test.d.ts +1 -0
- package/dist/lib/activity_log_service.test.js +27 -0
- package/dist/lib/ai-session.d.ts +15 -2
- package/dist/lib/ai-session.js +147 -85
- package/dist/lib/constants.d.ts +13 -0
- package/dist/lib/constants.js +12 -0
- package/dist/lib/email.d.ts +11 -0
- package/dist/lib/email.js +193 -0
- package/dist/lib/env.d.ts +13 -0
- package/dist/lib/env.js +16 -0
- package/dist/lib/inference.d.ts +4 -1
- package/dist/lib/inference.js +3 -3
- package/dist/lib/logger.d.ts +4 -4
- package/dist/lib/logger.js +30 -8
- package/dist/lib/notification-service.d.ts +152 -0
- package/dist/lib/notification-service.js +473 -0
- package/dist/lib/notification-service.test.d.ts +1 -0
- package/dist/lib/notification-service.test.js +87 -0
- package/dist/lib/prisma.d.ts +2 -1
- package/dist/lib/prisma.js +5 -1
- package/dist/lib/pusher.d.ts +23 -0
- package/dist/lib/pusher.js +69 -5
- package/dist/lib/retry.d.ts +15 -0
- package/dist/lib/retry.js +37 -0
- package/dist/lib/storage.js +2 -2
- package/dist/lib/stripe.d.ts +9 -0
- package/dist/lib/stripe.js +36 -0
- package/dist/lib/subscription_service.d.ts +37 -0
- package/dist/lib/subscription_service.js +654 -0
- package/dist/lib/usage_service.d.ts +26 -0
- package/dist/lib/usage_service.js +59 -0
- package/dist/lib/worksheet-generation.d.ts +91 -0
- package/dist/lib/worksheet-generation.js +95 -0
- package/dist/lib/worksheet-generation.test.d.ts +1 -0
- package/dist/lib/worksheet-generation.test.js +20 -0
- package/dist/lib/workspace-access.d.ts +18 -0
- package/dist/lib/workspace-access.js +13 -0
- package/dist/routers/_app.d.ts +1349 -253
- package/dist/routers/_app.js +10 -0
- package/dist/routers/admin.d.ts +361 -0
- package/dist/routers/admin.js +633 -0
- package/dist/routers/annotations.d.ts +219 -0
- package/dist/routers/annotations.js +187 -0
- package/dist/routers/auth.d.ts +88 -7
- package/dist/routers/auth.js +339 -19
- package/dist/routers/chat.d.ts +6 -12
- package/dist/routers/copilot.d.ts +199 -0
- package/dist/routers/copilot.js +571 -0
- package/dist/routers/flashcards.d.ts +47 -81
- package/dist/routers/flashcards.js +143 -27
- package/dist/routers/members.d.ts +36 -7
- package/dist/routers/members.js +200 -19
- package/dist/routers/notifications.d.ts +99 -0
- package/dist/routers/notifications.js +127 -0
- package/dist/routers/payment.d.ts +89 -0
- package/dist/routers/payment.js +403 -0
- package/dist/routers/podcast.d.ts +8 -13
- package/dist/routers/podcast.js +54 -31
- package/dist/routers/studyguide.d.ts +1 -29
- package/dist/routers/studyguide.js +80 -71
- package/dist/routers/worksheets.d.ts +105 -38
- package/dist/routers/worksheets.js +258 -68
- package/dist/routers/workspace.d.ts +139 -60
- package/dist/routers/workspace.js +455 -315
- package/dist/scripts/purge-deleted-users.d.ts +1 -0
- package/dist/scripts/purge-deleted-users.js +149 -0
- package/dist/server.js +130 -10
- package/dist/services/flashcard-progress.service.d.ts +18 -66
- package/dist/services/flashcard-progress.service.js +51 -42
- package/dist/trpc.d.ts +20 -21
- package/dist/trpc.js +150 -1
- package/mcq-test.cjs +36 -0
- package/package.json +9 -2
- package/prisma/migrations/20260413143206_init/migration.sql +873 -0
- package/prisma/schema.prisma +471 -324
- package/src/context.ts +4 -1
- package/src/lib/activity_human_description.test.ts +28 -0
- package/src/lib/activity_human_description.ts +239 -0
- package/src/lib/activity_log_service.test.ts +37 -0
- package/src/lib/activity_log_service.ts +353 -0
- package/src/lib/ai-session.ts +79 -51
- package/src/lib/email.ts +213 -29
- package/src/lib/env.ts +23 -6
- package/src/lib/inference.ts +2 -2
- package/src/lib/notification-service.test.ts +106 -0
- package/src/lib/notification-service.ts +677 -0
- package/src/lib/prisma.ts +6 -1
- package/src/lib/pusher.ts +86 -2
- package/src/lib/stripe.ts +39 -0
- package/src/lib/subscription_service.ts +722 -0
- package/src/lib/usage_service.ts +74 -0
- package/src/lib/worksheet-generation.test.ts +31 -0
- package/src/lib/worksheet-generation.ts +139 -0
- package/src/routers/_app.ts +9 -0
- package/src/routers/admin.ts +710 -0
- package/src/routers/annotations.ts +41 -0
- package/src/routers/auth.ts +338 -28
- package/src/routers/copilot.ts +719 -0
- package/src/routers/flashcards.ts +201 -68
- package/src/routers/members.ts +280 -80
- package/src/routers/notifications.ts +142 -0
- package/src/routers/payment.ts +448 -0
- package/src/routers/podcast.ts +112 -83
- package/src/routers/studyguide.ts +12 -0
- package/src/routers/worksheets.ts +289 -66
- package/src/routers/workspace.ts +329 -122
- package/src/scripts/purge-deleted-users.ts +167 -0
- package/src/server.ts +137 -11
- package/src/services/flashcard-progress.service.ts +49 -37
- package/src/trpc.ts +184 -5
- package/test-generate.js +30 -0
- package/test-ratio.cjs +9 -0
- package/zod-test.cjs +22 -0
- package/prisma/migrations/20250826124819_add_worksheet_difficulty_and_estimated_time/migration.sql +0 -213
- package/prisma/migrations/20250826133236_add_worksheet_question_progress/migration.sql +0 -31
- package/prisma/seed.mjs +0 -135
package/prisma/schema.prisma
CHANGED
|
@@ -5,11 +5,11 @@ generator client {
|
|
|
5
5
|
datasource db {
|
|
6
6
|
provider = "postgresql"
|
|
7
7
|
url = env("DATABASE_URL")
|
|
8
|
-
directUrl = env("DIRECT_URL")
|
|
8
|
+
directUrl = env("DIRECT_URL")
|
|
9
9
|
}
|
|
10
10
|
|
|
11
11
|
//
|
|
12
|
-
// Enums
|
|
12
|
+
// Enums (match prisma1/migrations enum DDL)
|
|
13
13
|
//
|
|
14
14
|
enum ArtifactType {
|
|
15
15
|
STUDY_GUIDE
|
|
@@ -17,6 +17,7 @@ enum ArtifactType {
|
|
|
17
17
|
WORKSHEET
|
|
18
18
|
MEETING_SUMMARY
|
|
19
19
|
PODCAST_EPISODE
|
|
20
|
+
STORAGE
|
|
20
21
|
}
|
|
21
22
|
|
|
22
23
|
enum Difficulty {
|
|
@@ -34,60 +35,132 @@ enum QuestionType {
|
|
|
34
35
|
FILL_IN_THE_BLANK
|
|
35
36
|
}
|
|
36
37
|
|
|
38
|
+
enum CopilotMessageRole {
|
|
39
|
+
USER
|
|
40
|
+
ASSISTANT
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
enum NotificationPriority {
|
|
44
|
+
LOW
|
|
45
|
+
NORMAL
|
|
46
|
+
HIGH
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
enum InvoiceType {
|
|
50
|
+
SUBSCRIPTION
|
|
51
|
+
TOPUP
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
enum ActivityLogCategory {
|
|
55
|
+
AUTH
|
|
56
|
+
WORKSPACE
|
|
57
|
+
BILLING
|
|
58
|
+
ADMIN
|
|
59
|
+
CONTENT
|
|
60
|
+
SYSTEM
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
enum ActivityLogStatus {
|
|
64
|
+
SUCCESS
|
|
65
|
+
FAILURE
|
|
66
|
+
}
|
|
67
|
+
|
|
37
68
|
//
|
|
38
69
|
// NextAuth-compatible auth models (minimal)
|
|
39
70
|
//
|
|
40
71
|
model User {
|
|
41
|
-
id
|
|
42
|
-
name
|
|
43
|
-
email
|
|
44
|
-
emailVerified
|
|
45
|
-
passwordHash
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
versions ArtifactVersion[] @relation("UserArtifactVersions")
|
|
57
|
-
|
|
58
|
-
// Progress tracking
|
|
72
|
+
id String @id @default(cuid())
|
|
73
|
+
name String?
|
|
74
|
+
email String? @unique
|
|
75
|
+
emailVerified DateTime?
|
|
76
|
+
passwordHash String?
|
|
77
|
+
createdAt DateTime @default(now())
|
|
78
|
+
updatedAt DateTime @updatedAt
|
|
79
|
+
fileAssetId String?
|
|
80
|
+
roleId String?
|
|
81
|
+
deletedAt DateTime?
|
|
82
|
+
stripe_customer_id String? @unique
|
|
83
|
+
artifacts Artifact[] @relation("UserArtifacts")
|
|
84
|
+
versions ArtifactVersion[] @relation("UserArtifactVersions")
|
|
85
|
+
chats Chat[]
|
|
86
|
+
uploads FileAsset[] @relation("UserUploads")
|
|
59
87
|
flashcardProgress FlashcardProgress[] @relation("UserFlashcardProgress")
|
|
88
|
+
folders Folder[] @relation("UserFolders")
|
|
89
|
+
workspaces Workspace[] @relation("UserWorkspaces")
|
|
90
|
+
workspaceMemberships WorkspaceMember[]
|
|
91
|
+
invitedInWorkspaces Workspace[] @relation("WorkspaceSharedWith")
|
|
92
|
+
notifications Notification[]
|
|
93
|
+
actorNotifications Notification[] @relation("NotificationActor")
|
|
94
|
+
session Session[]
|
|
95
|
+
studyGuideComments StudyGuideComment[] @relation("UserStudyGuideComments")
|
|
96
|
+
highlights StudyGuideHighlight[] @relation("UserHighlights")
|
|
97
|
+
profilePicture FileAsset? @relation(fields: [fileAssetId], references: [id])
|
|
98
|
+
role Role? @relation(fields: [roleId], references: [id])
|
|
60
99
|
worksheetQuestionProgress WorksheetQuestionProgress[]
|
|
100
|
+
worksheetPresets WorksheetPreset[]
|
|
101
|
+
copilotConversations CopilotConversation[]
|
|
102
|
+
sentInvitations WorkspaceInvitation[] @relation("UserInvitations")
|
|
103
|
+
subscriptions Subscription[]
|
|
104
|
+
invoices Invoice[]
|
|
105
|
+
credits UserCredit[]
|
|
106
|
+
idempotencyRecords IdempotencyRecord[]
|
|
107
|
+
activityLogs ActivityLog[] @relation("ActivityLogActor")
|
|
108
|
+
passwordResetTokens PasswordResetToken[]
|
|
109
|
+
}
|
|
61
110
|
|
|
62
|
-
|
|
63
|
-
|
|
111
|
+
/// One-time tokens for forgot-password flow (token stored as SHA-256 hash of the secret from the email link).
|
|
112
|
+
model PasswordResetToken {
|
|
113
|
+
id String @id @default(cuid())
|
|
114
|
+
userId String
|
|
115
|
+
tokenHash String @unique
|
|
116
|
+
expiresAt DateTime
|
|
117
|
+
usedAt DateTime?
|
|
118
|
+
createdAt DateTime @default(now())
|
|
119
|
+
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
64
120
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
studyGuideComments StudyGuideComment[] @relation("UserStudyGuideComments")
|
|
121
|
+
@@index([userId])
|
|
122
|
+
}
|
|
68
123
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
fileAssetId String?
|
|
124
|
+
model Role {
|
|
125
|
+
id String @id @default(cuid())
|
|
126
|
+
name String @unique
|
|
127
|
+
users User[]
|
|
74
128
|
}
|
|
75
129
|
|
|
76
130
|
model Notification {
|
|
77
|
-
id
|
|
78
|
-
userId
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
131
|
+
id String @id @default(cuid())
|
|
132
|
+
userId String
|
|
133
|
+
actorUserId String?
|
|
134
|
+
workspaceId String?
|
|
135
|
+
type String
|
|
136
|
+
title String
|
|
137
|
+
body String
|
|
138
|
+
content String?
|
|
139
|
+
actionUrl String?
|
|
140
|
+
metadata Json?
|
|
141
|
+
priority NotificationPriority @default(NORMAL)
|
|
142
|
+
sourceId String?
|
|
143
|
+
read Boolean @default(false)
|
|
144
|
+
readAt DateTime?
|
|
145
|
+
deliveredAt DateTime?
|
|
146
|
+
createdAt DateTime @default(now())
|
|
147
|
+
updatedAt DateTime @updatedAt
|
|
148
|
+
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
149
|
+
actor User? @relation("NotificationActor", fields: [actorUserId], references: [id], onDelete: SetNull)
|
|
150
|
+
workspace Workspace? @relation(fields: [workspaceId], references: [id], onDelete: SetNull)
|
|
151
|
+
|
|
152
|
+
@@index([userId, read, createdAt(sort: Desc)])
|
|
153
|
+
@@index([userId, createdAt(sort: Desc)])
|
|
154
|
+
@@index([type, createdAt(sort: Desc)])
|
|
155
|
+
@@index([workspaceId])
|
|
156
|
+
@@unique([userId, type, sourceId])
|
|
84
157
|
}
|
|
85
158
|
|
|
86
159
|
model Session {
|
|
87
160
|
id String @id @default(cuid())
|
|
88
161
|
userId String
|
|
89
|
-
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
90
162
|
expires DateTime
|
|
163
|
+
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
91
164
|
}
|
|
92
165
|
|
|
93
166
|
model VerificationToken {
|
|
@@ -98,66 +171,49 @@ model VerificationToken {
|
|
|
98
171
|
@@unique([identifier, token])
|
|
99
172
|
}
|
|
100
173
|
|
|
101
|
-
//
|
|
102
|
-
// Filesystem-like structure
|
|
103
|
-
//
|
|
104
174
|
model Folder {
|
|
105
|
-
id
|
|
106
|
-
name
|
|
107
|
-
ownerId
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
workspaces Workspace[]
|
|
118
|
-
|
|
119
|
-
// Metadata
|
|
120
|
-
createdAt DateTime @default(now())
|
|
121
|
-
updatedAt DateTime @updatedAt
|
|
175
|
+
id String @id @default(cuid())
|
|
176
|
+
name String
|
|
177
|
+
ownerId String
|
|
178
|
+
parentId String?
|
|
179
|
+
createdAt DateTime @default(now())
|
|
180
|
+
updatedAt DateTime @updatedAt
|
|
181
|
+
color String @default("#9D00FF")
|
|
182
|
+
markerColor String?
|
|
183
|
+
owner User @relation("UserFolders", fields: [ownerId], references: [id], onDelete: Cascade)
|
|
184
|
+
parent Folder? @relation("FolderChildren", fields: [parentId], references: [id], onDelete: Cascade)
|
|
185
|
+
children Folder[] @relation("FolderChildren")
|
|
186
|
+
workspaces Workspace[]
|
|
122
187
|
|
|
123
|
-
// Helpful composite index: folders per owner + parent
|
|
124
188
|
@@index([ownerId, parentId])
|
|
125
189
|
}
|
|
126
190
|
|
|
127
191
|
model Workspace {
|
|
128
|
-
id
|
|
129
|
-
title
|
|
130
|
-
description
|
|
131
|
-
ownerId
|
|
132
|
-
owner
|
|
133
|
-
icon
|
|
134
|
-
color
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
folderId
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
// AI outputs for this workspace (study guides, flashcards, etc.)
|
|
154
|
-
artifacts Artifact[]
|
|
155
|
-
|
|
156
|
-
// Invitations
|
|
157
|
-
invitations WorkspaceInvitation[]
|
|
158
|
-
|
|
159
|
-
createdAt DateTime @default(now())
|
|
160
|
-
updatedAt DateTime @updatedAt
|
|
192
|
+
id String @id @default(cuid())
|
|
193
|
+
title String
|
|
194
|
+
description String?
|
|
195
|
+
ownerId String
|
|
196
|
+
owner User @relation("UserWorkspaces", fields: [ownerId], references: [id], onDelete: Cascade)
|
|
197
|
+
icon String @default("📄")
|
|
198
|
+
color String @default("#9D00FF")
|
|
199
|
+
markerColor String?
|
|
200
|
+
folderId String?
|
|
201
|
+
folder Folder? @relation(fields: [folderId], references: [id], onDelete: SetNull)
|
|
202
|
+
channels Channel[]
|
|
203
|
+
copilotConversations CopilotConversation[]
|
|
204
|
+
sharedWith User[] @relation("WorkspaceSharedWith")
|
|
205
|
+
members WorkspaceMember[]
|
|
206
|
+
fileBeingAnalyzed Boolean @default(false)
|
|
207
|
+
analysisProgress Json?
|
|
208
|
+
needsAnalysis Boolean @default(false)
|
|
209
|
+
uploads FileAsset[]
|
|
210
|
+
artifacts Artifact[]
|
|
211
|
+
worksheetPresets WorksheetPreset[]
|
|
212
|
+
invitations WorkspaceInvitation[]
|
|
213
|
+
notifications Notification[]
|
|
214
|
+
activityLogs ActivityLog[]
|
|
215
|
+
createdAt DateTime @default(now())
|
|
216
|
+
updatedAt DateTime @updatedAt
|
|
161
217
|
|
|
162
218
|
@@index([ownerId, folderId])
|
|
163
219
|
}
|
|
@@ -165,144 +221,132 @@ model Workspace {
|
|
|
165
221
|
model Channel {
|
|
166
222
|
id String @id @default(cuid())
|
|
167
223
|
workspaceId String
|
|
224
|
+
name String
|
|
225
|
+
createdAt DateTime @default(now())
|
|
168
226
|
workspace Workspace @relation(fields: [workspaceId], references: [id], onDelete: Cascade)
|
|
169
|
-
|
|
170
|
-
name String
|
|
171
|
-
|
|
172
|
-
createdAt DateTime @default(now())
|
|
173
|
-
chats Chat[]
|
|
227
|
+
chats Chat[]
|
|
174
228
|
|
|
175
229
|
@@index([workspaceId])
|
|
176
230
|
}
|
|
177
231
|
|
|
178
232
|
model Chat {
|
|
179
|
-
id String
|
|
233
|
+
id String @id @default(cuid())
|
|
180
234
|
channelId String
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
user User? @relation(fields: [userId], references: [id], onDelete: SetNull)
|
|
184
|
-
userId String?
|
|
185
|
-
message String // chat message content
|
|
186
|
-
|
|
235
|
+
userId String?
|
|
236
|
+
message String
|
|
187
237
|
updatedAt DateTime @updatedAt
|
|
188
238
|
createdAt DateTime @default(now())
|
|
239
|
+
channel Channel @relation(fields: [channelId], references: [id], onDelete: Cascade)
|
|
240
|
+
user User? @relation(fields: [userId], references: [id])
|
|
189
241
|
|
|
190
242
|
@@index([channelId, createdAt])
|
|
191
243
|
}
|
|
192
244
|
|
|
245
|
+
model CopilotConversation {
|
|
246
|
+
id String @id @default(cuid())
|
|
247
|
+
workspaceId String
|
|
248
|
+
workspace Workspace @relation(fields: [workspaceId], references: [id], onDelete: Cascade)
|
|
249
|
+
userId String
|
|
250
|
+
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
251
|
+
title String @default("New Chat")
|
|
252
|
+
messages CopilotMessage[]
|
|
253
|
+
createdAt DateTime @default(now())
|
|
254
|
+
updatedAt DateTime @updatedAt
|
|
255
|
+
|
|
256
|
+
@@index([workspaceId, userId, updatedAt])
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
model CopilotMessage {
|
|
260
|
+
id String @id @default(cuid())
|
|
261
|
+
conversationId String
|
|
262
|
+
conversation CopilotConversation @relation(fields: [conversationId], references: [id], onDelete: Cascade)
|
|
263
|
+
role CopilotMessageRole
|
|
264
|
+
content String
|
|
265
|
+
createdAt DateTime @default(now())
|
|
266
|
+
|
|
267
|
+
@@index([conversationId, createdAt])
|
|
268
|
+
}
|
|
269
|
+
|
|
193
270
|
//
|
|
194
271
|
// User uploads (source materials for AI)
|
|
195
272
|
//
|
|
196
273
|
model FileAsset {
|
|
197
|
-
id
|
|
198
|
-
workspaceId
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
userId String?
|
|
202
|
-
user User? @relation("UserUploads", fields: [userId], references: [id], onDelete: Cascade)
|
|
203
|
-
|
|
274
|
+
id String @id @default(cuid())
|
|
275
|
+
workspaceId String?
|
|
276
|
+
userId String?
|
|
204
277
|
name String
|
|
205
278
|
mimeType String
|
|
206
279
|
size Int
|
|
207
280
|
bucket String?
|
|
208
281
|
objectKey String?
|
|
209
|
-
url String?
|
|
210
|
-
checksum String?
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
User
|
|
282
|
+
url String?
|
|
283
|
+
checksum String?
|
|
284
|
+
meta Json?
|
|
285
|
+
createdAt DateTime @default(now())
|
|
286
|
+
aiTranscription Json? @default("{}")
|
|
287
|
+
user User? @relation("UserUploads", fields: [userId], references: [id], onDelete: Cascade)
|
|
288
|
+
workspace Workspace? @relation(fields: [workspaceId], references: [id], onDelete: Cascade)
|
|
289
|
+
User User[]
|
|
217
290
|
|
|
218
291
|
@@index([workspaceId])
|
|
219
292
|
@@index([userId, createdAt])
|
|
220
293
|
}
|
|
221
294
|
|
|
222
|
-
//
|
|
223
|
-
// AI Outputs (Artifacts) with Versioning
|
|
224
|
-
// - One Artifact per output stream (e.g., one Study Guide, with many versions)
|
|
225
|
-
// - Some artifact types (flashcards, worksheet) have child rows
|
|
226
|
-
//
|
|
227
295
|
model Artifact {
|
|
228
|
-
id
|
|
229
|
-
workspaceId
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
296
|
+
id String @id @default(cuid())
|
|
297
|
+
workspaceId String
|
|
298
|
+
type ArtifactType
|
|
299
|
+
title String
|
|
300
|
+
isArchived Boolean @default(false)
|
|
301
|
+
difficulty Difficulty?
|
|
302
|
+
estimatedTime String?
|
|
303
|
+
createdById String?
|
|
304
|
+
createdAt DateTime @default(now())
|
|
305
|
+
updatedAt DateTime @updatedAt
|
|
306
|
+
description String?
|
|
307
|
+
generating Boolean @default(false)
|
|
237
308
|
generatingMetadata Json?
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
createdById String?
|
|
247
|
-
createdBy User? @relation("UserArtifacts", fields: [createdById], references: [id], onDelete: SetNull)
|
|
248
|
-
|
|
249
|
-
versions ArtifactVersion[] // text/transcript versions etc.
|
|
250
|
-
flashcards Flashcard[] // only meaningful for FLASHCARD_SET
|
|
251
|
-
questions WorksheetQuestion[] // only meaningful for WORKSHEET
|
|
252
|
-
podcastSegments PodcastSegment[] // only meaningful for PODCAST_EPISODE
|
|
253
|
-
|
|
254
|
-
createdAt DateTime @default(now())
|
|
255
|
-
updatedAt DateTime @updatedAt
|
|
309
|
+
worksheetConfig Json?
|
|
310
|
+
imageObjectKey String?
|
|
311
|
+
createdBy User? @relation("UserArtifacts", fields: [createdById], references: [id])
|
|
312
|
+
workspace Workspace @relation(fields: [workspaceId], references: [id], onDelete: Cascade)
|
|
313
|
+
versions ArtifactVersion[]
|
|
314
|
+
flashcards Flashcard[]
|
|
315
|
+
podcastSegments PodcastSegment[]
|
|
316
|
+
questions WorksheetQuestion[]
|
|
256
317
|
|
|
257
318
|
@@index([workspaceId, type])
|
|
258
319
|
}
|
|
259
320
|
|
|
260
321
|
model ArtifactVersion {
|
|
261
|
-
id
|
|
262
|
-
artifactId
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
content String // rich text serialized as markdown/HTML stored as TEXT
|
|
267
|
-
|
|
268
|
-
// For Podcast episodes or other media, store URLs / durations / etc. in data
|
|
269
|
-
data Json? // e.g., { "audioUrl": "...", "durationSec": 312, "voice": "..." }
|
|
270
|
-
|
|
271
|
-
// Version sequencing (auto-increment per artifact)
|
|
272
|
-
version Int
|
|
273
|
-
|
|
322
|
+
id String @id @default(cuid())
|
|
323
|
+
artifactId String
|
|
324
|
+
content String
|
|
325
|
+
data Json?
|
|
326
|
+
version Int
|
|
274
327
|
createdById String?
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
highlights
|
|
279
|
-
|
|
280
|
-
createdAt DateTime @default(now())
|
|
328
|
+
createdAt DateTime @default(now())
|
|
329
|
+
artifact Artifact @relation(fields: [artifactId], references: [id], onDelete: Cascade)
|
|
330
|
+
createdBy User? @relation("UserArtifactVersions", fields: [createdById], references: [id])
|
|
331
|
+
highlights StudyGuideHighlight[]
|
|
281
332
|
|
|
282
|
-
@@unique([artifactId, version])
|
|
333
|
+
@@unique([artifactId, version])
|
|
283
334
|
@@index([artifactId])
|
|
284
335
|
}
|
|
285
336
|
|
|
286
|
-
//
|
|
287
|
-
// Study Guide Highlights and Comments
|
|
288
|
-
//
|
|
289
337
|
model StudyGuideHighlight {
|
|
290
|
-
id String
|
|
338
|
+
id String @id @default(cuid())
|
|
291
339
|
artifactVersionId String
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
comments StudyGuideComment[]
|
|
303
|
-
|
|
304
|
-
createdAt DateTime @default(now())
|
|
305
|
-
updatedAt DateTime @updatedAt
|
|
340
|
+
userId String
|
|
341
|
+
startOffset Int
|
|
342
|
+
endOffset Int
|
|
343
|
+
selectedText String
|
|
344
|
+
color String @default("#FBBF24")
|
|
345
|
+
createdAt DateTime @default(now())
|
|
346
|
+
updatedAt DateTime @updatedAt
|
|
347
|
+
comments StudyGuideComment[]
|
|
348
|
+
artifactVersion ArtifactVersion @relation(fields: [artifactVersionId], references: [id], onDelete: Cascade)
|
|
349
|
+
user User @relation("UserHighlights", fields: [userId], references: [id], onDelete: Cascade)
|
|
306
350
|
|
|
307
351
|
@@index([artifactVersionId, userId])
|
|
308
352
|
}
|
|
@@ -310,199 +354,302 @@ model StudyGuideHighlight {
|
|
|
310
354
|
model StudyGuideComment {
|
|
311
355
|
id String @id @default(cuid())
|
|
312
356
|
highlightId String
|
|
357
|
+
userId String
|
|
358
|
+
content String
|
|
359
|
+
createdAt DateTime @default(now())
|
|
360
|
+
updatedAt DateTime @updatedAt
|
|
313
361
|
highlight StudyGuideHighlight @relation(fields: [highlightId], references: [id], onDelete: Cascade)
|
|
314
|
-
|
|
315
|
-
userId String
|
|
316
|
-
user User @relation("UserStudyGuideComments", fields: [userId], references: [id], onDelete: Cascade)
|
|
317
|
-
content String
|
|
318
|
-
|
|
319
|
-
createdAt DateTime @default(now())
|
|
320
|
-
updatedAt DateTime @updatedAt
|
|
362
|
+
user User @relation("UserStudyGuideComments", fields: [userId], references: [id], onDelete: Cascade)
|
|
321
363
|
|
|
322
364
|
@@index([highlightId])
|
|
323
365
|
}
|
|
324
366
|
|
|
325
|
-
//
|
|
326
|
-
// Flashcards (child items of a FLASHCARD_SET Artifact)
|
|
327
|
-
//
|
|
328
367
|
model Flashcard {
|
|
329
|
-
id
|
|
330
|
-
artifactId
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
progress FlashcardProgress[]
|
|
340
|
-
|
|
341
|
-
createdAt DateTime @default(now())
|
|
368
|
+
id String @id @default(cuid())
|
|
369
|
+
artifactId String
|
|
370
|
+
front String
|
|
371
|
+
back String
|
|
372
|
+
tags String[]
|
|
373
|
+
order Int @default(0)
|
|
374
|
+
createdAt DateTime @default(now())
|
|
375
|
+
acceptedAnswers String[] @default([])
|
|
376
|
+
artifact Artifact @relation(fields: [artifactId], references: [id], onDelete: Cascade)
|
|
377
|
+
progress FlashcardProgress[]
|
|
342
378
|
|
|
343
379
|
@@index([artifactId])
|
|
344
380
|
}
|
|
345
381
|
|
|
346
|
-
//
|
|
347
|
-
// User Progress on Flashcards (spaced repetition, mastery tracking)
|
|
348
|
-
//
|
|
349
382
|
model FlashcardProgress {
|
|
350
|
-
id
|
|
351
|
-
userId
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
// Mastery level (0-100)
|
|
369
|
-
masteryLevel Int @default(0)
|
|
370
|
-
|
|
371
|
-
// Timestamps
|
|
372
|
-
lastStudiedAt DateTime?
|
|
373
|
-
nextReviewAt DateTime?
|
|
374
|
-
|
|
375
|
-
createdAt DateTime @default(now())
|
|
376
|
-
updatedAt DateTime @updatedAt
|
|
383
|
+
id String @id @default(cuid())
|
|
384
|
+
userId String
|
|
385
|
+
flashcardId String
|
|
386
|
+
timesStudied Int @default(0)
|
|
387
|
+
timesCorrect Int @default(0)
|
|
388
|
+
timesIncorrect Int @default(0)
|
|
389
|
+
timesIncorrectConsecutive Int @default(0)
|
|
390
|
+
easeFactor Float @default(2.5)
|
|
391
|
+
interval Int @default(0)
|
|
392
|
+
repetitions Int @default(0)
|
|
393
|
+
masteryLevel Int @default(0)
|
|
394
|
+
lastStudiedAt DateTime?
|
|
395
|
+
nextReviewAt DateTime?
|
|
396
|
+
createdAt DateTime @default(now())
|
|
397
|
+
updatedAt DateTime @updatedAt
|
|
398
|
+
flashcard Flashcard @relation(fields: [flashcardId], references: [id], onDelete: Cascade)
|
|
399
|
+
user User @relation("UserFlashcardProgress", fields: [userId], references: [id], onDelete: Cascade)
|
|
377
400
|
|
|
378
401
|
@@unique([userId, flashcardId])
|
|
379
402
|
@@index([userId, nextReviewAt])
|
|
380
403
|
@@index([flashcardId])
|
|
381
404
|
}
|
|
382
405
|
|
|
383
|
-
//
|
|
384
|
-
// Worksheet Questions (child items of a WORKSHEET Artifact)
|
|
385
|
-
//
|
|
386
406
|
model WorksheetQuestion {
|
|
387
|
-
id String
|
|
407
|
+
id String @id @default(cuid())
|
|
388
408
|
artifactId String
|
|
389
|
-
artifact Artifact @relation(fields: [artifactId], references: [id], onDelete: Cascade)
|
|
390
|
-
|
|
391
409
|
prompt String
|
|
392
410
|
answer String?
|
|
393
|
-
type QuestionType
|
|
394
|
-
difficulty Difficulty
|
|
395
|
-
order Int
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
progress WorksheetQuestionProgress[]
|
|
411
|
+
type QuestionType @default(TEXT)
|
|
412
|
+
difficulty Difficulty @default(MEDIUM)
|
|
413
|
+
order Int @default(0)
|
|
414
|
+
meta Json?
|
|
415
|
+
createdAt DateTime @default(now())
|
|
416
|
+
artifact Artifact @relation(fields: [artifactId], references: [id], onDelete: Cascade)
|
|
417
|
+
progress WorksheetQuestionProgress[]
|
|
401
418
|
|
|
402
419
|
@@index([artifactId])
|
|
403
420
|
}
|
|
404
421
|
|
|
405
|
-
//
|
|
406
|
-
// Per-user progress for Worksheet Questions
|
|
407
|
-
//
|
|
408
422
|
model WorksheetQuestionProgress {
|
|
409
423
|
id String @id @default(cuid())
|
|
410
424
|
worksheetQuestionId String
|
|
425
|
+
userId String
|
|
426
|
+
userAnswer String?
|
|
427
|
+
completedAt DateTime?
|
|
428
|
+
attempts Int @default(0)
|
|
429
|
+
timeSpentSec Int?
|
|
430
|
+
meta Json?
|
|
431
|
+
createdAt DateTime @default(now())
|
|
432
|
+
updatedAt DateTime @updatedAt
|
|
433
|
+
correct Boolean? @default(false)
|
|
434
|
+
modified Boolean @default(false)
|
|
435
|
+
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
411
436
|
worksheetQuestion WorksheetQuestion @relation(fields: [worksheetQuestionId], references: [id], onDelete: Cascade)
|
|
412
437
|
|
|
413
|
-
userId String
|
|
414
|
-
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
415
|
-
|
|
416
|
-
modified Boolean @default(false)
|
|
417
|
-
userAnswer String?
|
|
418
|
-
correct Boolean? @default(false)
|
|
419
|
-
completedAt DateTime?
|
|
420
|
-
attempts Int @default(0)
|
|
421
|
-
timeSpentSec Int?
|
|
422
|
-
meta Json?
|
|
423
|
-
|
|
424
|
-
createdAt DateTime @default(now())
|
|
425
|
-
updatedAt DateTime @updatedAt
|
|
426
|
-
|
|
427
438
|
@@unique([worksheetQuestionId, userId])
|
|
428
439
|
@@index([userId])
|
|
429
440
|
}
|
|
430
441
|
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
442
|
+
model WorksheetPreset {
|
|
443
|
+
id String @id @default(cuid())
|
|
444
|
+
userId String?
|
|
445
|
+
workspaceId String?
|
|
446
|
+
name String
|
|
447
|
+
isSystem Boolean @default(false)
|
|
448
|
+
config Json
|
|
449
|
+
createdAt DateTime @default(now())
|
|
450
|
+
updatedAt DateTime @updatedAt
|
|
451
|
+
user User? @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
452
|
+
workspace Workspace? @relation(fields: [workspaceId], references: [id], onDelete: Cascade)
|
|
453
|
+
|
|
454
|
+
@@index([userId, workspaceId])
|
|
455
|
+
@@index([isSystem])
|
|
456
|
+
}
|
|
457
|
+
|
|
434
458
|
model WorkspaceMember {
|
|
435
459
|
id String @id @default(cuid())
|
|
436
460
|
workspaceId String
|
|
461
|
+
userId String
|
|
462
|
+
role String @default("member")
|
|
463
|
+
joinedAt DateTime @default(now())
|
|
464
|
+
updatedAt DateTime @updatedAt
|
|
465
|
+
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
437
466
|
workspace Workspace @relation(fields: [workspaceId], references: [id], onDelete: Cascade)
|
|
438
467
|
|
|
439
|
-
userId
|
|
440
|
-
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
441
|
-
|
|
442
|
-
role String @default("member") // "owner", "admin", "member"
|
|
443
|
-
|
|
444
|
-
joinedAt DateTime @default(now())
|
|
445
|
-
updatedAt DateTime @updatedAt
|
|
446
|
-
|
|
447
|
-
@@unique([workspaceId, userId]) // One membership per user per workspace
|
|
468
|
+
@@unique([workspaceId, userId])
|
|
448
469
|
@@index([workspaceId])
|
|
449
470
|
@@index([userId])
|
|
450
471
|
}
|
|
451
472
|
|
|
452
|
-
//
|
|
453
|
-
// Workspace Invitations
|
|
454
|
-
//
|
|
455
473
|
model WorkspaceInvitation {
|
|
456
474
|
id String @id @default(cuid())
|
|
457
475
|
workspaceId String
|
|
476
|
+
email String
|
|
477
|
+
role String @default("member")
|
|
478
|
+
token String @unique @default(cuid())
|
|
479
|
+
invitedById String
|
|
480
|
+
acceptedAt DateTime?
|
|
481
|
+
expiresAt DateTime @default(dbgenerated("(now() + '7 days'::interval)"))
|
|
482
|
+
createdAt DateTime @default(now())
|
|
483
|
+
updatedAt DateTime @updatedAt
|
|
484
|
+
invitedBy User @relation("UserInvitations", fields: [invitedById], references: [id], onDelete: Cascade)
|
|
458
485
|
workspace Workspace @relation(fields: [workspaceId], references: [id], onDelete: Cascade)
|
|
459
486
|
|
|
460
|
-
email
|
|
461
|
-
|
|
462
|
-
|
|
487
|
+
@@unique([workspaceId, email])
|
|
488
|
+
@@index([token])
|
|
489
|
+
@@index([workspaceId])
|
|
490
|
+
}
|
|
463
491
|
|
|
464
|
-
|
|
465
|
-
|
|
492
|
+
model PodcastSegment {
|
|
493
|
+
id String @id @default(cuid())
|
|
494
|
+
artifactId String
|
|
495
|
+
title String
|
|
496
|
+
content String
|
|
497
|
+
startTime Int
|
|
498
|
+
duration Int
|
|
499
|
+
order Int
|
|
500
|
+
objectKey String?
|
|
501
|
+
audioUrl String?
|
|
502
|
+
keyPoints String[]
|
|
503
|
+
meta Json?
|
|
504
|
+
createdAt DateTime @default(now())
|
|
505
|
+
updatedAt DateTime @updatedAt
|
|
506
|
+
generating Boolean @default(false)
|
|
507
|
+
generatingMetadata Json?
|
|
508
|
+
artifact Artifact @relation(fields: [artifactId], references: [id], onDelete: Cascade)
|
|
466
509
|
|
|
467
|
-
|
|
468
|
-
|
|
510
|
+
@@index([artifactId, order])
|
|
511
|
+
@@index([artifactId, startTime])
|
|
512
|
+
}
|
|
469
513
|
|
|
470
|
-
|
|
471
|
-
|
|
514
|
+
model Invoice {
|
|
515
|
+
id String @id @default(cuid())
|
|
516
|
+
userId String
|
|
517
|
+
subscriptionId String?
|
|
518
|
+
stripeInvoiceId String @unique
|
|
519
|
+
amountPaid Int
|
|
520
|
+
status String
|
|
521
|
+
invoicePdfUrl String?
|
|
522
|
+
hostedInvoiceUrl String?
|
|
523
|
+
paidAt DateTime?
|
|
524
|
+
type InvoiceType @default(SUBSCRIPTION)
|
|
525
|
+
createdAt DateTime @default(now())
|
|
526
|
+
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
527
|
+
subscription Subscription? @relation(fields: [subscriptionId], references: [id])
|
|
472
528
|
|
|
473
|
-
@@
|
|
474
|
-
@@index([token])
|
|
475
|
-
@@index([workspaceId])
|
|
529
|
+
@@index([userId])
|
|
476
530
|
}
|
|
477
531
|
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
532
|
+
model Plan {
|
|
533
|
+
id String @id @default(cuid())
|
|
534
|
+
name String
|
|
535
|
+
description String?
|
|
536
|
+
type String
|
|
537
|
+
price Int
|
|
538
|
+
stripePriceId String
|
|
539
|
+
interval String?
|
|
540
|
+
active Boolean @default(true)
|
|
541
|
+
createdAt DateTime @default(now())
|
|
542
|
+
limit PlanLimit?
|
|
543
|
+
subscriptions Subscription[]
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
model PlanLimit {
|
|
547
|
+
id String @id @default(cuid())
|
|
548
|
+
planId String @unique
|
|
549
|
+
maxStorageBytes BigInt @default(0)
|
|
550
|
+
maxWorksheets Int @default(0)
|
|
551
|
+
maxFlashcards Int @default(0)
|
|
552
|
+
maxPodcasts Int @default(0)
|
|
553
|
+
maxStudyGuides Int @default(0)
|
|
554
|
+
createdAt DateTime @default(now())
|
|
555
|
+
updatedAt DateTime @updatedAt
|
|
556
|
+
plan Plan @relation(fields: [planId], references: [id], onDelete: Cascade)
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
model Subscription {
|
|
560
|
+
id String @id @default(cuid())
|
|
561
|
+
userId String
|
|
562
|
+
planId String
|
|
563
|
+
stripeSubscriptionId String @unique
|
|
564
|
+
stripeCustomerId String?
|
|
565
|
+
status String
|
|
566
|
+
currentPeriodStart DateTime
|
|
567
|
+
currentPeriodEnd DateTime
|
|
568
|
+
cancelAt DateTime?
|
|
569
|
+
canceledAt DateTime?
|
|
570
|
+
createdAt DateTime @default(now())
|
|
571
|
+
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
572
|
+
plan Plan @relation(fields: [planId], references: [id], onDelete: Cascade)
|
|
573
|
+
invoices Invoice[]
|
|
485
574
|
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
duration Int // Duration in seconds
|
|
490
|
-
order Int // Display order within the episode
|
|
575
|
+
@@index([userId])
|
|
576
|
+
@@index([planId])
|
|
577
|
+
}
|
|
491
578
|
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
579
|
+
model ResourcePrice {
|
|
580
|
+
id String @id @default(cuid())
|
|
581
|
+
resourceType ArtifactType @unique
|
|
582
|
+
priceCents Int
|
|
583
|
+
updatedAt DateTime @updatedAt
|
|
584
|
+
}
|
|
495
585
|
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
586
|
+
model UserCredit {
|
|
587
|
+
id String @id @default(cuid())
|
|
588
|
+
userId String
|
|
589
|
+
resourceType ArtifactType
|
|
590
|
+
amount Int @default(1)
|
|
591
|
+
stripeSessionId String @unique
|
|
592
|
+
createdAt DateTime @default(now())
|
|
593
|
+
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
499
594
|
|
|
500
|
-
|
|
501
|
-
|
|
595
|
+
@@index([userId])
|
|
596
|
+
}
|
|
502
597
|
|
|
503
|
-
|
|
504
|
-
|
|
598
|
+
model IdempotencyRecord {
|
|
599
|
+
id String @id @default(cuid())
|
|
600
|
+
userId String
|
|
601
|
+
planId String? // The plan being purchased
|
|
602
|
+
resourceType ArtifactType? // The resource for top-ups
|
|
603
|
+
stripeSessionId String? // The Stripe Checkout Session ID
|
|
604
|
+
status String @default("pending") // pending, completed, failed, expired
|
|
605
|
+
activeLockKey String? @unique // Unique lock: "user_id_plan_id" (Nullified when done)
|
|
606
|
+
|
|
607
|
+
createdAt DateTime @default(now())
|
|
608
|
+
updatedAt DateTime @updatedAt
|
|
609
|
+
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
610
|
+
|
|
611
|
+
@@index([userId, status])
|
|
612
|
+
@@index([activeLockKey])
|
|
613
|
+
}
|
|
505
614
|
|
|
506
|
-
|
|
507
|
-
|
|
615
|
+
model StripeEvent {
|
|
616
|
+
id String @id @default(cuid())
|
|
617
|
+
stripeEventId String @unique // Stripe event ID (e.g. evt_...)
|
|
618
|
+
type String // Event type (e.g. checkout.session.completed)
|
|
619
|
+
status String @default("pending") // pending, processed, failed
|
|
620
|
+
error String? // Error message if processing fails
|
|
621
|
+
processedAt DateTime?
|
|
622
|
+
createdAt DateTime @default(now())
|
|
623
|
+
updatedAt DateTime @updatedAt
|
|
624
|
+
|
|
625
|
+
@@index([stripeEventId])
|
|
508
626
|
}
|
|
627
|
+
|
|
628
|
+
/// Append-only system / user activity (tRPC and explicit events). Retention via admin or scheduled job.
|
|
629
|
+
model ActivityLog {
|
|
630
|
+
id String @id @default(cuid())
|
|
631
|
+
createdAt DateTime @default(now())
|
|
632
|
+
actorUserId String?
|
|
633
|
+
actor User? @relation("ActivityLogActor", fields: [actorUserId], references: [id], onDelete: SetNull)
|
|
634
|
+
actorEmailSnapshot String?
|
|
635
|
+
action String
|
|
636
|
+
category ActivityLogCategory
|
|
637
|
+
resourceType String?
|
|
638
|
+
resourceId String?
|
|
639
|
+
workspaceId String?
|
|
640
|
+
workspace Workspace? @relation(fields: [workspaceId], references: [id], onDelete: SetNull)
|
|
641
|
+
trpcPath String?
|
|
642
|
+
httpMethod String?
|
|
643
|
+
status ActivityLogStatus
|
|
644
|
+
errorCode String?
|
|
645
|
+
durationMs Int
|
|
646
|
+
ipAddress String?
|
|
647
|
+
userAgent String?
|
|
648
|
+
metadata Json?
|
|
649
|
+
|
|
650
|
+
@@index([createdAt(sort: Desc)])
|
|
651
|
+
@@index([actorUserId, createdAt(sort: Desc)])
|
|
652
|
+
@@index([workspaceId, createdAt(sort: Desc)])
|
|
653
|
+
@@index([category, createdAt(sort: Desc)])
|
|
654
|
+
}
|
|
655
|
+
|