@goscribe/server 1.6.0 → 1.7.0

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 (281) hide show
  1. package/dist/generated/prisma/client.d.ts +224 -0
  2. package/dist/generated/prisma/client.js +34 -0
  3. package/dist/generated/prisma/commonInputTypes.d.ts +941 -0
  4. package/dist/generated/prisma/commonInputTypes.js +10 -0
  5. package/dist/generated/prisma/enums.d.ts +67 -0
  6. package/dist/generated/prisma/enums.js +66 -0
  7. package/dist/generated/prisma/internal/class.d.ts +539 -0
  8. package/dist/generated/prisma/internal/class.js +49 -0
  9. package/dist/generated/prisma/internal/prismaNamespace.d.ts +3924 -0
  10. package/dist/generated/prisma/internal/prismaNamespace.js +557 -0
  11. package/dist/generated/prisma/models/ActivityLog.d.ts +1847 -0
  12. package/dist/generated/prisma/models/ActivityLog.js +1 -0
  13. package/dist/generated/prisma/models/Artifact.d.ts +2345 -0
  14. package/dist/generated/prisma/models/Artifact.js +1 -0
  15. package/dist/generated/prisma/models/ArtifactVersion.d.ts +1550 -0
  16. package/dist/generated/prisma/models/ArtifactVersion.js +1 -0
  17. package/dist/generated/prisma/models/Channel.d.ts +1257 -0
  18. package/dist/generated/prisma/models/Channel.js +1 -0
  19. package/dist/generated/prisma/models/Chat.d.ts +1339 -0
  20. package/dist/generated/prisma/models/Chat.js +1 -0
  21. package/dist/generated/prisma/models/CopilotConversation.d.ts +1450 -0
  22. package/dist/generated/prisma/models/CopilotConversation.js +1 -0
  23. package/dist/generated/prisma/models/CopilotMessage.d.ts +1179 -0
  24. package/dist/generated/prisma/models/CopilotMessage.js +1 -0
  25. package/dist/generated/prisma/models/FileAsset.d.ts +1832 -0
  26. package/dist/generated/prisma/models/FileAsset.js +1 -0
  27. package/dist/generated/prisma/models/Flashcard.d.ts +1460 -0
  28. package/dist/generated/prisma/models/Flashcard.js +1 -0
  29. package/dist/generated/prisma/models/FlashcardProgress.d.ts +1782 -0
  30. package/dist/generated/prisma/models/FlashcardProgress.js +1 -0
  31. package/dist/generated/prisma/models/Folder.d.ts +1685 -0
  32. package/dist/generated/prisma/models/Folder.js +1 -0
  33. package/dist/generated/prisma/models/IdempotencyRecord.d.ts +1319 -0
  34. package/dist/generated/prisma/models/IdempotencyRecord.js +1 -0
  35. package/dist/generated/prisma/models/Invoice.d.ts +1586 -0
  36. package/dist/generated/prisma/models/Invoice.js +1 -0
  37. package/dist/generated/prisma/models/KnowledgeBase.d.ts +1721 -0
  38. package/dist/generated/prisma/models/KnowledgeBase.js +1 -0
  39. package/dist/generated/prisma/models/KnowledgeBaseChunk.d.ts +1333 -0
  40. package/dist/generated/prisma/models/KnowledgeBaseChunk.js +1 -0
  41. package/dist/generated/prisma/models/KnowledgeBaseDocument.d.ts +1695 -0
  42. package/dist/generated/prisma/models/KnowledgeBaseDocument.js +1 -0
  43. package/dist/generated/prisma/models/Notification.d.ts +1992 -0
  44. package/dist/generated/prisma/models/Notification.js +1 -0
  45. package/dist/generated/prisma/models/PasswordResetToken.d.ts +1210 -0
  46. package/dist/generated/prisma/models/PasswordResetToken.js +1 -0
  47. package/dist/generated/prisma/models/Plan.d.ts +1431 -0
  48. package/dist/generated/prisma/models/Plan.js +1 -0
  49. package/dist/generated/prisma/models/PlanLimit.d.ts +1328 -0
  50. package/dist/generated/prisma/models/PlanLimit.js +1 -0
  51. package/dist/generated/prisma/models/PodcastSegment.d.ts +1564 -0
  52. package/dist/generated/prisma/models/PodcastSegment.js +1 -0
  53. package/dist/generated/prisma/models/ResourcePrice.d.ts +1008 -0
  54. package/dist/generated/prisma/models/ResourcePrice.js +1 -0
  55. package/dist/generated/prisma/models/Role.d.ts +1065 -0
  56. package/dist/generated/prisma/models/Role.js +1 -0
  57. package/dist/generated/prisma/models/Session.d.ts +1105 -0
  58. package/dist/generated/prisma/models/Session.js +1 -0
  59. package/dist/generated/prisma/models/StripeEvent.d.ts +1081 -0
  60. package/dist/generated/prisma/models/StripeEvent.js +1 -0
  61. package/dist/generated/prisma/models/StudyGuideComment.d.ts +1321 -0
  62. package/dist/generated/prisma/models/StudyGuideComment.js +1 -0
  63. package/dist/generated/prisma/models/StudyGuideHighlight.d.ts +1629 -0
  64. package/dist/generated/prisma/models/StudyGuideHighlight.js +1 -0
  65. package/dist/generated/prisma/models/Subscription.d.ts +1677 -0
  66. package/dist/generated/prisma/models/Subscription.js +1 -0
  67. package/dist/generated/prisma/models/User.d.ts +7559 -0
  68. package/dist/generated/prisma/models/User.js +1 -0
  69. package/dist/generated/prisma/models/UserCredit.d.ts +1249 -0
  70. package/dist/generated/prisma/models/UserCredit.js +1 -0
  71. package/dist/generated/prisma/models/VerificationToken.d.ts +946 -0
  72. package/dist/generated/prisma/models/VerificationToken.js +1 -0
  73. package/dist/generated/prisma/models/WorksheetPreset.d.ts +1433 -0
  74. package/dist/generated/prisma/models/WorksheetPreset.js +1 -0
  75. package/dist/generated/prisma/models/WorksheetQuestion.d.ts +1491 -0
  76. package/dist/generated/prisma/models/WorksheetQuestion.js +1 -0
  77. package/dist/generated/prisma/models/WorksheetQuestionProgress.d.ts +1620 -0
  78. package/dist/generated/prisma/models/WorksheetQuestionProgress.js +1 -0
  79. package/dist/generated/prisma/models/Workspace.d.ts +3620 -0
  80. package/dist/generated/prisma/models/Workspace.js +1 -0
  81. package/dist/generated/prisma/models/WorkspaceInvitation.d.ts +1490 -0
  82. package/dist/generated/prisma/models/WorkspaceInvitation.js +1 -0
  83. package/dist/generated/prisma/models/WorkspaceKnowledgeBase.d.ts +1410 -0
  84. package/dist/generated/prisma/models/WorkspaceKnowledgeBase.js +1 -0
  85. package/dist/generated/prisma/models/WorkspaceMember.d.ts +1326 -0
  86. package/dist/generated/prisma/models/WorkspaceMember.js +1 -0
  87. package/dist/generated/prisma/models.d.ts +39 -0
  88. package/dist/generated/prisma/models.js +1 -0
  89. package/dist/src/context.d.ts +27 -0
  90. package/dist/src/context.js +33 -0
  91. package/dist/src/index.d.ts +3 -0
  92. package/dist/src/index.js +1 -0
  93. package/dist/src/lib/ai/config.d.ts +20 -0
  94. package/dist/src/lib/ai/config.js +31 -0
  95. package/dist/src/lib/ai/embedding-client.d.ts +8 -0
  96. package/dist/src/lib/ai/embedding-client.js +30 -0
  97. package/dist/src/lib/ai/index.d.ts +48 -0
  98. package/dist/src/lib/ai/index.js +29 -0
  99. package/dist/src/lib/ai/inference-backend/client.d.ts +28 -0
  100. package/dist/src/lib/ai/inference-backend/client.js +301 -0
  101. package/dist/src/lib/ai/inference-backend/mocks.d.ts +12 -0
  102. package/dist/src/lib/ai/inference-backend/mocks.js +133 -0
  103. package/dist/src/lib/ai/inference-backend/types.d.ts +44 -0
  104. package/dist/src/lib/ai/inference-backend/types.js +1 -0
  105. package/dist/src/lib/ai/json-parse.d.ts +2 -0
  106. package/dist/src/lib/ai/json-parse.js +34 -0
  107. package/dist/src/lib/ai/llm-client.d.ts +7 -0
  108. package/dist/src/lib/ai/llm-client.js +36 -0
  109. package/dist/src/lib/ai/mock.d.ts +2 -0
  110. package/dist/src/lib/ai/mock.js +10 -0
  111. package/dist/src/lib/ai/types.d.ts +9 -0
  112. package/dist/src/lib/ai/types.js +1 -0
  113. package/dist/src/lib/auth.d.ts +36 -0
  114. package/dist/src/lib/auth.js +117 -0
  115. package/dist/src/lib/chunking.d.ts +19 -0
  116. package/dist/src/lib/chunking.js +47 -0
  117. package/dist/src/lib/constants.d.ts +13 -0
  118. package/dist/src/lib/constants.js +12 -0
  119. package/dist/src/lib/curated-kb-seed.d.ts +12 -0
  120. package/dist/src/lib/curated-kb-seed.js +155 -0
  121. package/dist/src/lib/email.d.ts +11 -0
  122. package/dist/src/lib/email.js +152 -0
  123. package/dist/src/lib/embeddings.d.ts +2 -0
  124. package/dist/src/lib/embeddings.js +1 -0
  125. package/dist/src/lib/ensure-curated-kb-catalog.d.ts +6 -0
  126. package/dist/src/lib/ensure-curated-kb-catalog.js +53 -0
  127. package/dist/src/lib/env.d.ts +41 -0
  128. package/dist/src/lib/env.js +57 -0
  129. package/dist/src/lib/errors.d.ts +33 -0
  130. package/dist/src/lib/errors.js +78 -0
  131. package/dist/src/lib/file.d.ts +0 -0
  132. package/dist/src/lib/file.js +1 -0
  133. package/dist/src/lib/inference.d.ts +1 -0
  134. package/dist/src/lib/inference.js +1 -0
  135. package/dist/src/lib/kb-meta.d.ts +8 -0
  136. package/dist/src/lib/kb-meta.js +77 -0
  137. package/dist/src/lib/logger.d.ts +62 -0
  138. package/dist/src/lib/logger.js +364 -0
  139. package/dist/src/lib/pdf.d.ts +11 -0
  140. package/dist/src/lib/pdf.js +11 -0
  141. package/dist/src/lib/prisma.d.ts +3 -0
  142. package/dist/src/lib/prisma.js +15 -0
  143. package/dist/src/lib/pusher.d.ts +38 -0
  144. package/dist/src/lib/pusher.js +170 -0
  145. package/dist/src/lib/retry.d.ts +15 -0
  146. package/dist/src/lib/retry.js +37 -0
  147. package/dist/src/lib/storage.d.ts +11 -0
  148. package/dist/src/lib/storage.js +71 -0
  149. package/dist/src/lib/stripe.d.ts +10 -0
  150. package/dist/src/lib/stripe.js +36 -0
  151. package/dist/src/lib/validation.d.ts +51 -0
  152. package/dist/src/lib/validation.js +64 -0
  153. package/dist/src/lib/workspace-kb.d.ts +5 -0
  154. package/dist/src/lib/workspace-kb.js +7 -0
  155. package/dist/src/repositories/artifact.repository.d.ts +64 -0
  156. package/dist/src/repositories/artifact.repository.js +40 -0
  157. package/dist/src/repositories/base.repository.d.ts +14 -0
  158. package/dist/src/repositories/base.repository.js +14 -0
  159. package/dist/src/repositories/invitation.repository.d.ts +104 -0
  160. package/dist/src/repositories/invitation.repository.js +44 -0
  161. package/dist/src/repositories/notification.repository.d.ts +76 -0
  162. package/dist/src/repositories/notification.repository.js +44 -0
  163. package/dist/src/repositories/user.repository.d.ts +84 -0
  164. package/dist/src/repositories/user.repository.js +37 -0
  165. package/dist/src/repositories/workspace-member.repository.d.ts +35 -0
  166. package/dist/src/repositories/workspace-member.repository.js +31 -0
  167. package/dist/src/repositories/workspace.repository.d.ts +101 -0
  168. package/dist/src/repositories/workspace.repository.js +79 -0
  169. package/dist/src/routers/_app.d.ts +3464 -0
  170. package/dist/src/routers/_app.js +36 -0
  171. package/dist/src/routers/admin.d.ts +358 -0
  172. package/dist/src/routers/admin.js +105 -0
  173. package/dist/src/routers/annotations.d.ts +219 -0
  174. package/dist/src/routers/annotations.js +29 -0
  175. package/dist/src/routers/artifactVersions.d.ts +65 -0
  176. package/dist/src/routers/artifactVersions.js +14 -0
  177. package/dist/src/routers/auth.d.ts +161 -0
  178. package/dist/src/routers/auth.js +97 -0
  179. package/dist/src/routers/chat.d.ts +170 -0
  180. package/dist/src/routers/chat.js +32 -0
  181. package/dist/src/routers/copilot.d.ts +200 -0
  182. package/dist/src/routers/copilot.js +52 -0
  183. package/dist/src/routers/flashcards.d.ts +336 -0
  184. package/dist/src/routers/flashcards.js +93 -0
  185. package/dist/src/routers/knowledgeBase.d.ts +421 -0
  186. package/dist/src/routers/knowledgeBase.js +118 -0
  187. package/dist/src/routers/members.d.ts +169 -0
  188. package/dist/src/routers/members.js +47 -0
  189. package/dist/src/routers/notifications.d.ts +99 -0
  190. package/dist/src/routers/notifications.js +25 -0
  191. package/dist/src/routers/payment.d.ts +80 -0
  192. package/dist/src/routers/payment.js +21 -0
  193. package/dist/src/routers/podcast.d.ts +287 -0
  194. package/dist/src/routers/podcast.js +34 -0
  195. package/dist/src/routers/studyguide.d.ts +36 -0
  196. package/dist/src/routers/studyguide.js +8 -0
  197. package/dist/src/routers/worksheets.d.ts +429 -0
  198. package/dist/src/routers/worksheets.js +139 -0
  199. package/dist/src/routers/workspace.d.ts +563 -0
  200. package/dist/src/routers/workspace.js +104 -0
  201. package/dist/src/scripts/purge-deleted-users.d.ts +1 -0
  202. package/dist/src/scripts/purge-deleted-users.js +148 -0
  203. package/dist/src/server.d.ts +1 -0
  204. package/dist/src/server.js +190 -0
  205. package/dist/src/services/activity/activity-human-description.service.d.ts +13 -0
  206. package/dist/src/services/activity/activity-human-description.service.js +221 -0
  207. package/dist/src/services/activity/activity-human-description.service.test.d.ts +1 -0
  208. package/dist/src/services/activity/activity-human-description.service.test.js +16 -0
  209. package/dist/src/services/activity/activity-log.service.d.ts +87 -0
  210. package/dist/src/services/activity/activity-log.service.js +276 -0
  211. package/dist/src/services/activity/activity-log.service.test.d.ts +1 -0
  212. package/dist/src/services/activity/activity-log.service.test.js +27 -0
  213. package/dist/src/services/admin/admin.service.d.ts +270 -0
  214. package/dist/src/services/admin/admin.service.js +476 -0
  215. package/dist/src/services/ai/ai-session.service.d.ts +5 -0
  216. package/dist/src/services/ai/ai-session.service.js +4 -0
  217. package/dist/src/services/artifacts/annotation.service.d.ts +177 -0
  218. package/dist/src/services/artifacts/annotation.service.js +154 -0
  219. package/dist/src/services/artifacts/artifact-version.service.d.ts +38 -0
  220. package/dist/src/services/artifacts/artifact-version.service.js +129 -0
  221. package/dist/src/services/artifacts/chat.service.d.ts +127 -0
  222. package/dist/src/services/artifacts/chat.service.js +182 -0
  223. package/dist/src/services/artifacts/study-guide.service.d.ts +18 -0
  224. package/dist/src/services/artifacts/study-guide.service.js +65 -0
  225. package/dist/src/services/auth/auth.service.d.ts +94 -0
  226. package/dist/src/services/auth/auth.service.js +368 -0
  227. package/dist/src/services/base.service.d.ts +14 -0
  228. package/dist/src/services/base.service.js +14 -0
  229. package/dist/src/services/billing/payment.service.d.ts +44 -0
  230. package/dist/src/services/billing/payment.service.js +365 -0
  231. package/dist/src/services/billing/subscription.service.d.ts +37 -0
  232. package/dist/src/services/billing/subscription.service.js +654 -0
  233. package/dist/src/services/billing/usage.service.d.ts +47 -0
  234. package/dist/src/services/billing/usage.service.js +149 -0
  235. package/dist/src/services/content/copilot.service.d.ts +113 -0
  236. package/dist/src/services/content/copilot.service.js +439 -0
  237. package/dist/src/services/content/flashcard-progress.service.d.ts +159 -0
  238. package/dist/src/services/content/flashcard-progress.service.js +432 -0
  239. package/dist/src/services/content/flashcard.service.d.ts +184 -0
  240. package/dist/src/services/content/flashcard.service.js +339 -0
  241. package/dist/src/services/content/media-analysis.service.d.ts +23 -0
  242. package/dist/src/services/content/media-analysis.service.js +404 -0
  243. package/dist/src/services/content/podcast.service.d.ts +267 -0
  244. package/dist/src/services/content/podcast.service.js +653 -0
  245. package/dist/src/services/content/worksheet-content.service.d.ts +37 -0
  246. package/dist/src/services/content/worksheet-content.service.js +84 -0
  247. package/dist/src/services/content/worksheet-content.service.test.d.ts +1 -0
  248. package/dist/src/services/content/worksheet-content.service.test.js +69 -0
  249. package/dist/src/services/content/worksheet-generation.service.d.ts +91 -0
  250. package/dist/src/services/content/worksheet-generation.service.js +95 -0
  251. package/dist/src/services/content/worksheet-generation.service.test.d.ts +1 -0
  252. package/dist/src/services/content/worksheet-generation.service.test.js +20 -0
  253. package/dist/src/services/content/worksheet.service.d.ts +347 -0
  254. package/dist/src/services/content/worksheet.service.js +599 -0
  255. package/dist/src/services/knowledge/knowledge-base.service.d.ts +316 -0
  256. package/dist/src/services/knowledge/knowledge-base.service.js +544 -0
  257. package/dist/src/services/members/invitation.service.d.ts +66 -0
  258. package/dist/src/services/members/invitation.service.js +348 -0
  259. package/dist/src/services/members/member.service.d.ts +36 -0
  260. package/dist/src/services/members/member.service.js +193 -0
  261. package/dist/src/services/notifications/notification.service.d.ts +214 -0
  262. package/dist/src/services/notifications/notification.service.js +550 -0
  263. package/dist/src/services/notifications/notification.service.test.d.ts +1 -0
  264. package/dist/src/services/notifications/notification.service.test.js +87 -0
  265. package/dist/src/services/workspace/workspace-analytics.service.d.ts +24 -0
  266. package/dist/src/services/workspace/workspace-analytics.service.js +95 -0
  267. package/dist/src/services/workspace/workspace-kb.service.d.ts +40 -0
  268. package/dist/src/services/workspace/workspace-kb.service.js +184 -0
  269. package/dist/src/services/workspace/workspace.service.d.ts +263 -0
  270. package/dist/src/services/workspace/workspace.service.js +401 -0
  271. package/dist/src/trpc.d.ts +60 -0
  272. package/dist/src/trpc.js +217 -0
  273. package/dist/src/types/index.d.ts +126 -0
  274. package/dist/src/types/index.js +1 -0
  275. package/package.json +8 -9
  276. package/prisma/schema.prisma +3 -4
  277. package/prisma/seed.mjs +5 -2
  278. package/prisma.config.ts +16 -0
  279. package/src/lib/prisma.ts +18 -9
  280. package/src/scripts/purge-deleted-users.ts +1 -3
  281. package/tsconfig.json +3 -0
@@ -0,0 +1,148 @@
1
+ import { prisma as db } from '../lib/prisma.js';
2
+ import { supabaseClient } from '../lib/storage.js';
3
+ import { logger } from '../lib/logger.js';
4
+ import { notifyAdminsAccountPermanentlyDeleted } from '../services/notifications/notification.service.js';
5
+ import { ActivityLogCategory, ActivityLogStatus, } from '@prisma/client';
6
+ import { recordExplicitActivity } from '../services/activity/activity-log.service.js';
7
+ async function purgeDeletedUsers() {
8
+ try {
9
+ const startedAt = Date.now();
10
+ let status = ActivityLogStatus.SUCCESS;
11
+ let errorCode = null;
12
+ let purgedUsers = 0;
13
+ logger.info('Starting scheduled purge for deleted users...');
14
+ // Find users whose deletedAt is older than 30 days
15
+ const thirtyDaysAgo = new Date();
16
+ thirtyDaysAgo.setDate(thirtyDaysAgo.getDate() - 30);
17
+ const usersToPurge = await db.user.findMany({
18
+ where: {
19
+ deletedAt: {
20
+ lte: thirtyDaysAgo,
21
+ },
22
+ },
23
+ select: {
24
+ id: true,
25
+ name: true,
26
+ email: true,
27
+ profilePicture: {
28
+ select: {
29
+ objectKey: true,
30
+ }
31
+ }
32
+ },
33
+ });
34
+ if (usersToPurge.length === 0) {
35
+ logger.info('No users found past the 30-day grace period.');
36
+ await recordExplicitActivity({
37
+ db,
38
+ actorUserId: null,
39
+ action: 'cron.purgeDeletedUsers',
40
+ category: ActivityLogCategory.SYSTEM,
41
+ status: ActivityLogStatus.SUCCESS,
42
+ errorCode: null,
43
+ durationMs: Date.now() - startedAt,
44
+ forceWrite: true,
45
+ metadata: {
46
+ usersFound: 0,
47
+ usersPurged: 0,
48
+ graceDays: 30,
49
+ },
50
+ });
51
+ return;
52
+ }
53
+ logger.info(`Found ${usersToPurge.length} user(s) to purge. Processing...`);
54
+ for (const user of usersToPurge) {
55
+ logger.info(`Purging user: ${user.id} (${user.email})`);
56
+ try {
57
+ // 1. Delete associated files from Supabase Storage
58
+ // (Prisma cascade will delete the FileAsset DB record, but we need to delete the actual file first)
59
+ // Let's get all file assets owned by the user
60
+ const userFiles = await db.fileAsset.findMany({
61
+ where: { userId: user.id },
62
+ select: { objectKey: true, bucket: true },
63
+ });
64
+ for (const file of userFiles) {
65
+ if (file.objectKey && file.bucket) {
66
+ try {
67
+ const { error } = await supabaseClient.storage
68
+ .from(file.bucket)
69
+ .remove([file.objectKey]);
70
+ if (error) {
71
+ logger.error(`Failed to delete file from Supabase: ${file.objectKey}`, error);
72
+ }
73
+ else {
74
+ logger.info(`Deleted file from Supabase: ${file.objectKey}`);
75
+ }
76
+ }
77
+ catch (storageError) {
78
+ logger.error(`Exception during Supabase deletion for ${file.objectKey}:`, storageError);
79
+ }
80
+ }
81
+ }
82
+ await notifyAdminsAccountPermanentlyDeleted(db, {
83
+ id: user.id,
84
+ name: user.name,
85
+ email: user.email,
86
+ }).catch(() => { });
87
+ // 2. Finally, delete the User from the database
88
+ // (Prisma onDelete: Cascade will handle almost everything else depending on schema)
89
+ await db.user.delete({
90
+ where: { id: user.id },
91
+ });
92
+ purgedUsers += 1;
93
+ logger.info(`Successfully purged user: ${user.id}`);
94
+ }
95
+ catch (userError) {
96
+ status = ActivityLogStatus.FAILURE;
97
+ errorCode =
98
+ userError instanceof Error ? userError.message : String(userError);
99
+ logger.error(`Failed to purge user ${user.id}:`, userError);
100
+ }
101
+ }
102
+ if (status === ActivityLogStatus.SUCCESS) {
103
+ logger.info('Purge process completed successfully.');
104
+ }
105
+ else {
106
+ logger.warn('Purge process completed with failures.');
107
+ }
108
+ await recordExplicitActivity({
109
+ db,
110
+ actorUserId: null,
111
+ action: 'cron.purgeDeletedUsers',
112
+ category: ActivityLogCategory.SYSTEM,
113
+ status,
114
+ errorCode,
115
+ durationMs: Date.now() - startedAt,
116
+ forceWrite: true,
117
+ metadata: {
118
+ usersFound: usersToPurge.length,
119
+ usersPurged: purgedUsers,
120
+ graceDays: 30,
121
+ },
122
+ });
123
+ }
124
+ catch (error) {
125
+ const startedAt = Date.now();
126
+ await recordExplicitActivity({
127
+ db,
128
+ actorUserId: null,
129
+ action: 'cron.purgeDeletedUsers',
130
+ category: ActivityLogCategory.SYSTEM,
131
+ status: ActivityLogStatus.FAILURE,
132
+ errorCode: error instanceof Error ? error.message : String(error),
133
+ durationMs: Date.now() - startedAt,
134
+ forceWrite: true,
135
+ }).catch(() => { });
136
+ logger.error('Critical error during user purge process:', error);
137
+ }
138
+ finally {
139
+ await db.$disconnect();
140
+ }
141
+ }
142
+ // Run the script
143
+ purgeDeletedUsers().then(() => {
144
+ process.exit(0);
145
+ }).catch((error) => {
146
+ console.error("Unhandled error:", error);
147
+ process.exit(1);
148
+ });
@@ -0,0 +1 @@
1
+ import 'dotenv/config';
@@ -0,0 +1,190 @@
1
+ import 'dotenv/config';
2
+ import express from 'express';
3
+ import cors from 'cors';
4
+ import helmet from 'helmet';
5
+ import morgan from 'morgan';
6
+ import compression from 'compression';
7
+ import * as trpcExpress from '@trpc/server/adapters/express';
8
+ import { appRouter } from './routers/_app.js';
9
+ import { createContext } from './context.js';
10
+ import { prisma } from './lib/prisma.js';
11
+ import { logger } from './lib/logger.js';
12
+ import { supabaseClient } from './lib/storage.js';
13
+ import { ActivityLogCategory, ActivityLogStatus, } from '@prisma/client';
14
+ import { recordExplicitActivity } from './services/activity/activity-log.service.js';
15
+ const PORT = process.env.PORT ? Number(process.env.PORT) : 3001;
16
+ async function main() {
17
+ const app = express();
18
+ // Middlewares
19
+ app.use(cors({
20
+ origin: ['https://www.scribe.study', 'https://scribe.study', 'http://localhost:3000', 'http://localhost:3002'],
21
+ credentials: true, // allow cookies
22
+ methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
23
+ allowedHeaders: ['Content-Type', 'Authorization', 'Cookie', 'Set-Cookie'],
24
+ exposedHeaders: ['Set-Cookie'],
25
+ preflightContinue: false, // Important: stop further handling of OPTIONS
26
+ }));
27
+ app.use(helmet());
28
+ // Custom morgan middleware with logger integration
29
+ app.use(morgan('combined', {
30
+ stream: {
31
+ write: (message) => {
32
+ logger.info(message.trim(), 'HTTP');
33
+ }
34
+ }
35
+ }));
36
+ app.use(compression());
37
+ // Stripe Webhook (Must be before express.json() for raw body)
38
+ app.post('/stripe/webhook', express.raw({ type: 'application/json' }), async (req, res) => {
39
+ const sig = req.headers['stripe-signature'];
40
+ const { stripe } = await import('./lib/stripe.js');
41
+ const { env } = await import('./lib/env.js');
42
+ const subscriptionService = await import('./services/billing/subscription.service.js');
43
+ let event;
44
+ try {
45
+ if (!sig || !env.STRIPE_WEBHOOK_SECRET) {
46
+ throw new Error('Missing stripe-signature or STRIPE_WEBHOOK_SECRET');
47
+ }
48
+ event = stripe?.webhooks.constructEvent(req.body, sig, env.STRIPE_WEBHOOK_SECRET);
49
+ }
50
+ catch (err) {
51
+ logger.error(`Webhook signature verification failed: ${err.message}`, 'STRIPE');
52
+ await recordExplicitActivity({
53
+ db: prisma,
54
+ actorUserId: null,
55
+ action: 'stripe.webhook.signatureVerificationFailed',
56
+ category: ActivityLogCategory.SYSTEM,
57
+ status: ActivityLogStatus.FAILURE,
58
+ errorCode: err?.message ?? 'unknown',
59
+ durationMs: 0,
60
+ forceWrite: true,
61
+ }).catch(() => { });
62
+ return res.status(400).send(`Webhook Error: ${err.message}`);
63
+ }
64
+ // Handle the event
65
+ try {
66
+ const startedAt = Date.now();
67
+ let status = ActivityLogStatus.SUCCESS;
68
+ let errorCode = null;
69
+ // Best-effort: some webhook objects include metadata.userId
70
+ let actorUserId = undefined;
71
+ try {
72
+ const obj = event?.data?.object;
73
+ const md = obj?.metadata;
74
+ if (md && typeof md.userId === 'string')
75
+ actorUserId = md.userId;
76
+ }
77
+ catch {
78
+ actorUserId = undefined;
79
+ }
80
+ switch (event?.type) {
81
+ case 'checkout.session.completed':
82
+ await subscriptionService.handleCheckoutCompleted(event);
83
+ break;
84
+ case 'customer.subscription.created':
85
+ await subscriptionService.handleSubscriptionCreated(event);
86
+ break;
87
+ case 'customer.subscription.updated':
88
+ await subscriptionService.handleSubscriptionUpdated(event);
89
+ break;
90
+ case 'customer.subscription.deleted':
91
+ await subscriptionService.handleSubscriptionDeleted(event);
92
+ break;
93
+ case 'invoice.paid':
94
+ case 'invoice.payment_succeeded':
95
+ await subscriptionService.handleInvoicePaid(event);
96
+ break;
97
+ case 'invoice.payment_failed':
98
+ await subscriptionService.handlePaymentFailed(event);
99
+ break;
100
+ case 'payment_intent.payment_failed':
101
+ await subscriptionService.handlePaymentIntentFailed(event);
102
+ break;
103
+ default:
104
+ logger.debug(`Unhandled stripe event type: ${event?.type}`, 'STRIPE');
105
+ }
106
+ await recordExplicitActivity({
107
+ db: prisma,
108
+ actorUserId: actorUserId ?? undefined,
109
+ action: `stripe.webhook.${event?.type}`,
110
+ category: ActivityLogCategory.SYSTEM,
111
+ status,
112
+ errorCode,
113
+ durationMs: Date.now() - startedAt,
114
+ forceWrite: true,
115
+ metadata: {
116
+ stripeEventId: event?.id,
117
+ stripeEventType: event?.type,
118
+ },
119
+ }).catch(() => { });
120
+ res.json({ received: true });
121
+ }
122
+ catch (err) {
123
+ const startedAt = Date.now();
124
+ const message = err?.message ?? 'unknown';
125
+ await recordExplicitActivity({
126
+ db: prisma,
127
+ actorUserId: null,
128
+ action: `stripe.webhook.${event?.type}`,
129
+ category: ActivityLogCategory.SYSTEM,
130
+ status: ActivityLogStatus.FAILURE,
131
+ errorCode: message,
132
+ durationMs: Date.now() - startedAt,
133
+ forceWrite: true,
134
+ metadata: {
135
+ stripeEventId: event?.id,
136
+ stripeEventType: event?.type,
137
+ },
138
+ }).catch(() => { });
139
+ logger.error(`Error processing webhook event ${event?.type}: ${err.message}`, 'STRIPE');
140
+ res.status(500).send('Internal Server Error');
141
+ }
142
+ });
143
+ app.use(express.json({ limit: '50mb' }));
144
+ app.use(express.urlencoded({ limit: '50mb', extended: true }));
145
+ // Health (plain Express)
146
+ app.get('/', (_req, res) => {
147
+ res.json({ ok: true, service: 'trpc-express', ts: Date.now() });
148
+ });
149
+ app.get('/profile-picture/:objectKey', async (req, res) => {
150
+ try {
151
+ const { objectKey } = req.params;
152
+ const { data, error } = await supabaseClient.storage
153
+ .from('media')
154
+ .download(objectKey);
155
+ if (error || !data) {
156
+ logger.error(`Failed to download profile picture: ${error?.message}`, 'STORAGE');
157
+ return res.status(404).send('Not found');
158
+ }
159
+ const buffer = Buffer.from(await data.arrayBuffer());
160
+ res.setHeader('Content-Type', 'image/jpeg');
161
+ res.setHeader('Cache-Control', 'public, max-age=31536000');
162
+ res.setHeader('Cross-Origin-Resource-Policy', 'cross-origin');
163
+ res.send(buffer);
164
+ }
165
+ catch (err) {
166
+ logger.error('Error serving profile picture', 'STORAGE', undefined, err);
167
+ res.status(500).send('Internal Server Error');
168
+ }
169
+ });
170
+ // tRPC mounted under /trpc
171
+ app.use('/trpc', trpcExpress.createExpressMiddleware({
172
+ router: appRouter,
173
+ createContext,
174
+ }));
175
+ app.listen(PORT, async () => {
176
+ logger.info(`Server ready on http://localhost:${PORT}`, 'SERVER');
177
+ logger.info(`tRPC endpoint at http://localhost:${PORT}/trpc`, 'SERVER');
178
+ try {
179
+ const { ensureCuratedKbCatalog } = await import('./lib/ensure-curated-kb-catalog.js');
180
+ await ensureCuratedKbCatalog(prisma);
181
+ }
182
+ catch (err) {
183
+ logger.error('Failed to seed curated knowledge bases', 'KB', undefined, err instanceof Error ? err : undefined);
184
+ }
185
+ });
186
+ }
187
+ main().catch((err) => {
188
+ logger.error('Failed to start server', 'SERVER', undefined, err);
189
+ process.exit(1);
190
+ });
@@ -0,0 +1,13 @@
1
+ /**
2
+ * User-facing labels for tRPC paths shown in activity logs.
3
+ * Unknown paths get a best-effort title from the procedure name.
4
+ */
5
+ /**
6
+ * Paths whose curated description contains `query` (case-insensitive).
7
+ * Used so activity log search matches human-readable text, not only raw paths.
8
+ */
9
+ export declare function getTrpcPathsMatchingDescriptionSearch(query: string): string[];
10
+ /**
11
+ * Stable, human-readable line for UI and CSV exports.
12
+ */
13
+ export declare function getActivityHumanDescription(trpcPath: string | null | undefined, actionFallback?: string | null): string;
@@ -0,0 +1,221 @@
1
+ /**
2
+ * User-facing labels for tRPC paths shown in activity logs.
3
+ * Unknown paths get a best-effort title from the procedure name.
4
+ */
5
+ const PATH_LABELS = {
6
+ // Admin
7
+ "admin.getSystemStats": "Viewed system dashboard statistics",
8
+ "admin.listUsers": "Listed users (admin)",
9
+ "admin.listWorkspaces": "Listed workspaces (admin)",
10
+ "admin.updateUserRole": "Changed a user’s role",
11
+ "admin.listPlans": "Listed subscription plans",
12
+ "admin.upsertPlan": "Created or updated a plan",
13
+ "admin.deletePlan": "Deleted or deactivated a plan",
14
+ "admin.getUserInvoices": "Viewed a user’s invoices",
15
+ "admin.getUserDetailedInfo": "Viewed a user’s profile (admin)",
16
+ "admin.debugInvoices": "Ran invoice debug (admin)",
17
+ "admin.listResourcePrices": "Listed resource prices",
18
+ "admin.upsertResourcePrice": "Updated a resource price",
19
+ "admin.listRecentInvoices": "Viewed recent invoices",
20
+ "admin.activityList": "Viewed activity logs (admin)",
21
+ "admin.activityExportCsv": "Exported activity logs to CSV",
22
+ "admin.activityPurgeRetention": "Purged old activity logs (retention)",
23
+ // Workspace
24
+ "workspace.list": "Listed your workspaces",
25
+ "workspace.getTree": "Loaded folders and workspace tree",
26
+ "workspace.create": "Created a workspace",
27
+ "workspace.createFolder": "Created a folder",
28
+ "workspace.updateFolder": "Updated a folder",
29
+ "workspace.deleteFolder": "Deleted a folder",
30
+ "workspace.get": "Opened a workspace",
31
+ "workspace.getStats": "Loaded storage and usage stats",
32
+ "workspace.getStudyAnalytics": "Loaded study streak and analytics",
33
+ "workspace.update": "Updated workspace settings",
34
+ "workspace.delete": "Deleted a workspace",
35
+ "workspace.getFolderInformation": "Loaded folder details",
36
+ "workspace.getSharedWith": "Loaded who a workspace is shared with",
37
+ "workspace.uploadFiles": "Uploaded files",
38
+ "workspace.deleteFiles": "Deleted files",
39
+ "workspace.getFileUploadUrl": "Requested an upload URL",
40
+ "workspace.uploadAndAnalyzeMedia": "Uploaded and analyzed media",
41
+ "workspace.search": "Searched workspaces and content",
42
+ // Payment
43
+ "payment.getPlans": "Viewed available plans",
44
+ "payment.createCheckoutSession": "Started checkout",
45
+ "payment.confirmCheckoutSuccess": "Confirmed checkout",
46
+ "payment.createResourcePurchaseSession": "Started a resource purchase",
47
+ "payment.getUsageOverview": "Viewed plan usage and limits",
48
+ "payment.getResourcePrices": "Viewed resource prices",
49
+ // Notifications
50
+ "notifications.list": "Loaded notifications",
51
+ "notifications.unreadCount": "Checked unread notifications",
52
+ "notifications.markRead": "Marked a notification as read",
53
+ "notifications.markManyRead": "Marked notifications as read",
54
+ "notifications.markAllRead": "Marked all notifications as read",
55
+ "notifications.delete": "Deleted a notification",
56
+ // Members
57
+ "members.getMembers": "Listed workspace members",
58
+ "members.getCurrentUserRole": "Checked your role in a workspace",
59
+ "members.inviteMember": "Sent a workspace invite",
60
+ "members.getInvitations": "Listed pending invites",
61
+ "members.acceptInvite": "Accepted a workspace invite",
62
+ "members.changeMemberRole": "Changed a member’s role",
63
+ "members.removeMember": "Removed a workspace member",
64
+ "members.getPendingInvitations": "Listed invitations",
65
+ "members.cancelInvitation": "Canceled an invitation",
66
+ "members.resendInvitation": "Resent an invitation",
67
+ "members.getAllInvitationsDebug": "Listed invitations (debug)",
68
+ "member.acceptInvite": "Accepted a workspace invite",
69
+ // Flashcards
70
+ "flashcards.listSets": "Listed flashcard sets",
71
+ "flashcards.listCards": "Listed flashcards",
72
+ "flashcards.isGenerating": "Checked flashcard generation status",
73
+ "flashcards.createCard": "Created a flashcard",
74
+ "flashcards.updateCard": "Updated a flashcard",
75
+ "flashcards.gradeTypedAnswer": "Graded a flashcard answer",
76
+ "flashcards.deleteCard": "Deleted a flashcard",
77
+ "flashcards.deleteSet": "Deleted a flashcard set",
78
+ "flashcards.generateFromPrompt": "Generated flashcards from a prompt",
79
+ "flashcards.recordStudyAttempt": "Recorded a flashcard review",
80
+ "flashcards.getSetProgress": "Viewed flashcard progress",
81
+ "flashcards.getDueFlashcards": "Loaded due flashcards",
82
+ "flashcards.getSetStatistics": "Viewed flashcard statistics",
83
+ "flashcards.resetProgress": "Reset flashcard progress",
84
+ "flashcards.recordStudySession": "Recorded a study session",
85
+ // Worksheets
86
+ "worksheets.list": "Listed worksheets",
87
+ "worksheets.listPresets": "Listed worksheet presets",
88
+ "worksheets.createPreset": "Created a worksheet preset",
89
+ "worksheets.updatePreset": "Updated a worksheet preset",
90
+ "worksheets.deletePreset": "Deleted a worksheet preset",
91
+ "worksheets.create": "Created a worksheet",
92
+ "worksheets.get": "Opened a worksheet",
93
+ "worksheets.createWorksheetQuestion": "Added a worksheet question",
94
+ "worksheets.updateWorksheetQuestion": "Updated a worksheet question",
95
+ "worksheets.deleteWorksheetQuestion": "Deleted a worksheet question",
96
+ "worksheets.updateProblemStatus": "Updated worksheet problem status",
97
+ "worksheets.getProgress": "Viewed worksheet progress",
98
+ "worksheets.update": "Updated a worksheet",
99
+ "worksheets.delete": "Deleted a worksheet",
100
+ "worksheets.generateFromPrompt": "Generated a worksheet from a prompt",
101
+ "worksheets.checkAnswer": "Checked a worksheet answer",
102
+ // Podcast
103
+ "podcast.listEpisodes": "Listed podcast episodes",
104
+ "podcast.getEpisode": "Opened a podcast episode",
105
+ "podcast.generateEpisode": "Generated a podcast episode",
106
+ "podcast.deleteSegment": "Deleted a podcast segment",
107
+ "podcast.getEpisodeSchema": "Loaded podcast episode schema",
108
+ "podcast.updateEpisode": "Updated a podcast episode",
109
+ "podcast.deleteEpisode": "Deleted a podcast episode",
110
+ "podcast.getSegment": "Opened a podcast segment",
111
+ "podcast.getAvailableVoices": "Listed podcast voices",
112
+ // Study guide
113
+ "studyguide.get": "Opened or loaded a study guide",
114
+ // Chat
115
+ "chat.getChannels": "Listed chat channels",
116
+ "chat.getChannel": "Opened a chat channel",
117
+ "chat.removeChannel": "Removed a chat channel",
118
+ "chat.editChannel": "Renamed a chat channel",
119
+ "chat.createChannel": "Created a chat channel",
120
+ "chat.postMessage": "Sent a chat message",
121
+ "chat.editMessage": "Edited a chat message",
122
+ "chat.deleteMessage": "Deleted a chat message",
123
+ // Annotations
124
+ "annotations.listHighlights": "Listed study guide highlights",
125
+ "annotations.createHighlight": "Created a highlight",
126
+ "annotations.deleteHighlight": "Deleted a highlight",
127
+ "annotations.addComment": "Added a comment",
128
+ "annotations.updateComment": "Updated a comment",
129
+ "annotations.deleteComment": "Deleted a comment",
130
+ // Copilot
131
+ "copilot.listConversations": "Listed Copilot chats",
132
+ "copilot.getConversation": "Opened a Copilot chat",
133
+ "copilot.createConversation": "Started a Copilot chat",
134
+ "copilot.deleteConversation": "Deleted a Copilot chat",
135
+ "copilot.ask": "Sent a Copilot message",
136
+ "copilot.explainSelection": "Asked Copilot to explain a selection",
137
+ "copilot.suggestHighlights": "Asked Copilot for highlight suggestions",
138
+ "copilot.generateFlashcards": "Generated flashcards with Copilot",
139
+ // Auth (if ever logged)
140
+ "auth.updateProfile": "Updated profile",
141
+ "auth.uploadProfilePicture": "Uploaded a profile picture",
142
+ "auth.confirmProfileUpdate": "Confirmed profile update",
143
+ "auth.signup": "Created an account",
144
+ "auth.verifyEmail": "Verified email",
145
+ "auth.resendVerification": "Resent verification email",
146
+ "auth.login": "Signed in",
147
+ "auth.getSession": "Loaded session",
148
+ "auth.requestAccountDeletion": "Requested account deletion",
149
+ "auth.restoreAccount": "Restored account",
150
+ "auth.logout": "Signed out",
151
+ };
152
+ function splitCamelCase(s) {
153
+ const spaced = s.replace(/([a-z])([A-Z])/g, "$1 $2");
154
+ return spaced.replace(/^\w/, (c) => c.toUpperCase());
155
+ }
156
+ const ROUTER_PREFIX = {
157
+ workspace: "Workspace",
158
+ payment: "Billing",
159
+ notifications: "Notifications",
160
+ admin: "Admin",
161
+ auth: "Account",
162
+ flashcards: "Flashcards",
163
+ worksheets: "Worksheets",
164
+ podcast: "Podcast",
165
+ studyguide: "Study guide",
166
+ chat: "Chat",
167
+ annotations: "Annotations",
168
+ copilot: "Copilot",
169
+ members: "Members",
170
+ };
171
+ function titleCaseRouter(router) {
172
+ return ROUTER_PREFIX[router] ?? splitCamelCase(router);
173
+ }
174
+ /**
175
+ * Paths whose curated description contains `query` (case-insensitive).
176
+ * Used so activity log search matches human-readable text, not only raw paths.
177
+ */
178
+ export function getTrpcPathsMatchingDescriptionSearch(query) {
179
+ const q = query.trim().toLowerCase();
180
+ if (!q)
181
+ return [];
182
+ const paths = [];
183
+ for (const [path, label] of Object.entries(PATH_LABELS)) {
184
+ if (label.toLowerCase().includes(q)) {
185
+ paths.push(path);
186
+ }
187
+ }
188
+ return paths;
189
+ }
190
+ /**
191
+ * Stable, human-readable line for UI and CSV exports.
192
+ */
193
+ export function getActivityHumanDescription(trpcPath, actionFallback) {
194
+ const path = (trpcPath ?? "").trim();
195
+ if (path) {
196
+ const exact = PATH_LABELS[path];
197
+ if (exact)
198
+ return exact;
199
+ const parts = path.split(".").filter(Boolean);
200
+ if (parts.length >= 2) {
201
+ const proc = parts[parts.length - 1];
202
+ const ns = parts[0];
203
+ const rest = parts.slice(1, -1);
204
+ const action = splitCamelCase(proc);
205
+ const area = titleCaseRouter(ns);
206
+ if (rest.length) {
207
+ return `${area} (${rest.join(" › ")}): ${action}`;
208
+ }
209
+ return `${area}: ${action}`;
210
+ }
211
+ return path;
212
+ }
213
+ const act = (actionFallback ?? "").replace(/^trpc\./, "");
214
+ if (act) {
215
+ const exact = PATH_LABELS[act];
216
+ if (exact)
217
+ return exact;
218
+ return act.replace(/\./g, " › ");
219
+ }
220
+ return "Activity";
221
+ }
@@ -0,0 +1,16 @@
1
+ import { test } from "node:test";
2
+ import assert from "node:assert/strict";
3
+ import { getActivityHumanDescription, getTrpcPathsMatchingDescriptionSearch, } from "./activity-human-description.service.js";
4
+ test("description search resolves paths by label substring", () => {
5
+ const paths = getTrpcPathsMatchingDescriptionSearch("study streak");
6
+ assert.ok(paths.includes("workspace.getStudyAnalytics"));
7
+ });
8
+ test("known path returns curated label", () => {
9
+ assert.equal(getActivityHumanDescription("workspace.getStudyAnalytics"), "Loaded study streak and analytics");
10
+ assert.equal(getActivityHumanDescription("payment.getUsageOverview"), "Viewed plan usage and limits");
11
+ });
12
+ test("unknown path gets formatted fallback", () => {
13
+ const d = getActivityHumanDescription("someRouter.unknownProcedureName");
14
+ assert.ok(d.includes("Some Router"));
15
+ assert.ok(d.includes("Unknown"));
16
+ });