@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,401 @@
1
+ import { TRPCError } from '@trpc/server';
2
+ import { BaseService } from '../base.service.js';
3
+ import { supabaseClient } from '../../lib/storage.js';
4
+ import PusherService from '../../lib/pusher.js';
5
+ import { ai } from '../../lib/ai/index.js';
6
+ import { workspaceKbService } from './workspace-kb.service.js';
7
+ import { getAccountSummary, invalidateUserBillingCache, } from '../billing/usage.service.js';
8
+ import { getUserStorageLimit } from '../billing/subscription.service.js';
9
+ import { notifyWorkspaceDeleted } from '../notifications/notification.service.js';
10
+ export class WorkspaceService extends BaseService {
11
+ constructor(db) {
12
+ super(db);
13
+ }
14
+ async list(userId, parentId) {
15
+ const workspaces = await this.db.workspace.findMany({
16
+ where: { ownerId: userId, folderId: parentId },
17
+ orderBy: { updatedAt: 'desc' },
18
+ });
19
+ const folders = await this.db.folder.findMany({
20
+ where: { ownerId: userId, parentId },
21
+ });
22
+ return { workspaces, folders };
23
+ }
24
+ async getTree(userId) {
25
+ const [allFolders, allWorkspaces] = await Promise.all([
26
+ this.db.folder.findMany({
27
+ where: { ownerId: userId },
28
+ select: {
29
+ id: true,
30
+ name: true,
31
+ parentId: true,
32
+ color: true,
33
+ markerColor: true,
34
+ updatedAt: true,
35
+ },
36
+ orderBy: { updatedAt: 'desc' },
37
+ }),
38
+ this.db.workspace.findMany({
39
+ where: { ownerId: userId },
40
+ select: {
41
+ id: true,
42
+ title: true,
43
+ folderId: true,
44
+ icon: true,
45
+ color: true,
46
+ markerColor: true,
47
+ updatedAt: true,
48
+ uploads: {
49
+ select: {
50
+ id: true,
51
+ name: true,
52
+ mimeType: true,
53
+ createdAt: true,
54
+ },
55
+ },
56
+ },
57
+ orderBy: { updatedAt: 'desc' },
58
+ }),
59
+ ]);
60
+ return { folders: allFolders, workspaces: allWorkspaces };
61
+ }
62
+ async create(userId, input) {
63
+ const ws = await this.db.workspace.create({
64
+ data: {
65
+ title: input.name,
66
+ description: input.description,
67
+ ownerId: userId,
68
+ folderId: input.parentId ?? null,
69
+ ...(input.markerColor !== undefined ? { markerColor: input.markerColor } : {}),
70
+ },
71
+ });
72
+ void ai.backend.initSession(ws.id, userId).catch((err) => {
73
+ this.logger.error('Failed to init AI session on workspace creation:', err);
74
+ });
75
+ void workspaceKbService.ensureWorkspaceKb(ws.id, userId, input.name).catch((err) => {
76
+ this.logger.error('Failed to create workspace knowledge base:', err);
77
+ });
78
+ invalidateUserBillingCache(userId);
79
+ void PusherService.emitLibraryUpdate(userId);
80
+ return ws;
81
+ }
82
+ async createFolder(userId, input) {
83
+ const folder = await this.db.folder.create({
84
+ data: {
85
+ name: input.name,
86
+ ownerId: userId,
87
+ color: input.color ?? '#9D00FF',
88
+ parentId: input.parentId ?? null,
89
+ },
90
+ });
91
+ await PusherService.emitLibraryUpdate(userId);
92
+ return folder;
93
+ }
94
+ async updateFolder(userId, input) {
95
+ const folder = await this.db.folder.update({
96
+ where: { id: input.id },
97
+ data: { name: input.name, markerColor: input.markerColor },
98
+ });
99
+ await PusherService.emitLibraryUpdate(userId);
100
+ return folder;
101
+ }
102
+ async deleteFolder(userId, id) {
103
+ const folder = await this.db.folder.delete({ where: { id } });
104
+ await PusherService.emitLibraryUpdate(userId);
105
+ return folder;
106
+ }
107
+ async get(userId, id) {
108
+ const ws = await this.db.workspace.findFirst({
109
+ where: { id, ownerId: userId },
110
+ select: {
111
+ id: true,
112
+ title: true,
113
+ description: true,
114
+ icon: true,
115
+ color: true,
116
+ markerColor: true,
117
+ ownerId: true,
118
+ folderId: true,
119
+ fileBeingAnalyzed: true,
120
+ analysisProgress: true,
121
+ needsAnalysis: true,
122
+ createdAt: true,
123
+ updatedAt: true,
124
+ folder: { select: { id: true, name: true, color: true } },
125
+ uploads: {
126
+ select: {
127
+ id: true,
128
+ name: true,
129
+ mimeType: true,
130
+ size: true,
131
+ createdAt: true,
132
+ objectKey: true,
133
+ },
134
+ orderBy: { createdAt: 'desc' },
135
+ },
136
+ },
137
+ });
138
+ if (!ws)
139
+ throw new TRPCError({ code: 'NOT_FOUND' });
140
+ return ws;
141
+ }
142
+ async getAccountSummary(userId) {
143
+ return getAccountSummary(userId);
144
+ }
145
+ async getStats(userId) {
146
+ const summary = await getAccountSummary(userId);
147
+ return summary.stats;
148
+ }
149
+ async update(userId, input) {
150
+ const existed = await this.db.workspace.findFirst({
151
+ where: { id: input.id, ownerId: userId },
152
+ });
153
+ if (!existed)
154
+ throw new TRPCError({ code: 'NOT_FOUND' });
155
+ const updated = await this.db.workspace.update({
156
+ where: { id: input.id },
157
+ data: {
158
+ title: input.name ?? existed.title,
159
+ description: input.description,
160
+ markerColor: input.markerColor !== undefined ? input.markerColor : existed.markerColor,
161
+ icon: input.icon ?? existed.icon,
162
+ },
163
+ });
164
+ await PusherService.emitLibraryUpdate(userId);
165
+ return updated;
166
+ }
167
+ async delete(userId, id) {
168
+ const workspaceToDelete = await this.db.workspace.findFirst({
169
+ where: { id, ownerId: userId },
170
+ select: {
171
+ id: true,
172
+ title: true,
173
+ ownerId: true,
174
+ members: { select: { userId: true } },
175
+ },
176
+ });
177
+ if (!workspaceToDelete)
178
+ throw new TRPCError({ code: 'NOT_FOUND' });
179
+ const actor = await this.db.user.findUnique({
180
+ where: { id: userId },
181
+ select: { name: true, email: true },
182
+ });
183
+ const actorName = actor?.name || actor?.email || 'A user';
184
+ await notifyWorkspaceDeleted(this.db, {
185
+ recipientUserIds: workspaceToDelete.members.map((m) => m.userId),
186
+ actorUserId: userId,
187
+ actorName,
188
+ workspaceId: workspaceToDelete.id,
189
+ workspaceTitle: workspaceToDelete.title,
190
+ });
191
+ const deleted = await this.db.workspace.deleteMany({
192
+ where: { id, ownerId: userId },
193
+ });
194
+ if (deleted.count === 0)
195
+ throw new TRPCError({ code: 'NOT_FOUND' });
196
+ await PusherService.emitLibraryUpdate(userId);
197
+ return true;
198
+ }
199
+ async getFolderInformation(userId, id) {
200
+ const folder = await this.db.folder.findFirst({
201
+ where: { id, ownerId: userId },
202
+ });
203
+ if (!folder)
204
+ throw new TRPCError({ code: 'NOT_FOUND' });
205
+ const parents = [];
206
+ let current = folder;
207
+ while (current.parentId) {
208
+ const parent = await this.db.folder.findFirst({
209
+ where: { id: current.parentId, ownerId: userId },
210
+ });
211
+ if (!parent)
212
+ break;
213
+ parents.push(parent);
214
+ current = parent;
215
+ }
216
+ return { folder, parents };
217
+ }
218
+ async getSharedWith(userId, id) {
219
+ const user = await this.db.user.findFirst({ where: { id: userId } });
220
+ if (!user || !user.email)
221
+ throw new TRPCError({ code: 'NOT_FOUND' });
222
+ const sharedWith = await this.db.workspace.findMany({
223
+ where: { members: { some: { userId } } },
224
+ });
225
+ const invitations = await this.db.workspaceInvitation.findMany({
226
+ where: { email: user.email, acceptedAt: null },
227
+ include: { workspace: true },
228
+ });
229
+ return { shared: sharedWith, invitations };
230
+ }
231
+ async uploadFiles(userId, input) {
232
+ const ws = await this.db.workspace.findFirst({
233
+ where: { id: input.id, ownerId: userId },
234
+ });
235
+ if (!ws)
236
+ throw new TRPCError({ code: 'NOT_FOUND' });
237
+ const workspaces = await this.db.workspace.findMany({
238
+ where: {
239
+ OR: [{ ownerId: userId }, { sharedWith: { some: { id: userId } } }],
240
+ },
241
+ });
242
+ const spaceUsed = await this.db.fileAsset.aggregate({
243
+ where: { workspaceId: { in: workspaces.map((w) => w.id) }, userId },
244
+ _sum: { size: true },
245
+ });
246
+ const storageLimit = await getUserStorageLimit(userId);
247
+ const totalSize = input.files.reduce((acc, file) => acc + file.size, 0);
248
+ if ((spaceUsed._sum?.size ?? 0) + totalSize > storageLimit) {
249
+ this.logger.warn(`Storage limit exceeded for user ${userId}. Used: ${spaceUsed._sum?.size}, Tried to upload: ${totalSize}, Limit: ${storageLimit}`);
250
+ throw new TRPCError({
251
+ code: 'FORBIDDEN',
252
+ message: `Storage limit exceeded. Maximum allowed storage is ${(storageLimit / (1024 * 1024 * 1024)).toFixed(1)}GB.`,
253
+ });
254
+ }
255
+ const results = [];
256
+ for (const file of input.files) {
257
+ const record = await this.db.fileAsset.create({
258
+ data: {
259
+ userId,
260
+ name: file.filename,
261
+ mimeType: file.contentType,
262
+ size: file.size,
263
+ workspaceId: input.id,
264
+ },
265
+ });
266
+ const objectKey = `${userId}/${record.id}-${file.filename}`;
267
+ const { data: signedUrlData, error: signedUrlError } = await supabaseClient.storage
268
+ .from('media')
269
+ .createSignedUploadUrl(objectKey);
270
+ if (signedUrlError) {
271
+ throw new TRPCError({
272
+ code: 'INTERNAL_SERVER_ERROR',
273
+ message: `Failed to upload file`,
274
+ });
275
+ }
276
+ await this.db.fileAsset.update({
277
+ where: { id: record.id },
278
+ data: {
279
+ bucket: 'media',
280
+ objectKey,
281
+ },
282
+ });
283
+ results.push({
284
+ fileId: record.id,
285
+ uploadUrl: signedUrlData.signedUrl,
286
+ });
287
+ }
288
+ return results;
289
+ }
290
+ async deleteFiles(userId, input) {
291
+ const files = await this.db.fileAsset.findMany({
292
+ where: {
293
+ id: { in: input.fileId },
294
+ workspaceId: input.id,
295
+ userId,
296
+ },
297
+ });
298
+ for (const file of files) {
299
+ if (file.bucket && file.objectKey) {
300
+ supabaseClient.storage
301
+ .from(file.bucket)
302
+ .remove([file.objectKey])
303
+ .catch((err) => {
304
+ this.logger.error(`Error deleting file ${file.objectKey} from bucket ${file.bucket}:`, err);
305
+ });
306
+ }
307
+ }
308
+ await this.db.fileAsset.deleteMany({
309
+ where: {
310
+ id: { in: input.fileId },
311
+ workspaceId: input.id,
312
+ userId,
313
+ },
314
+ });
315
+ return true;
316
+ }
317
+ async getFileUploadUrl(userId, input) {
318
+ const workspaces = await this.db.workspace.findMany({
319
+ where: {
320
+ OR: [{ ownerId: userId }, { sharedWith: { some: { id: userId } } }],
321
+ },
322
+ });
323
+ const spaceUsed = await this.db.fileAsset.aggregate({
324
+ where: { workspaceId: { in: workspaces.map((w) => w.id) }, userId },
325
+ _sum: { size: true },
326
+ });
327
+ const storageLimit = await getUserStorageLimit(userId);
328
+ if ((spaceUsed._sum?.size ?? 0) + input.size > storageLimit) {
329
+ this.logger.warn(`Storage limit exceeded for user ${userId}. Used: ${spaceUsed._sum?.size}, Tried to upload: ${input.size}, Limit: ${storageLimit}`);
330
+ throw new TRPCError({
331
+ code: 'FORBIDDEN',
332
+ message: `Storage limit exceeded. Maximum allowed storage is ${(storageLimit / (1024 * 1024 * 1024)).toFixed(1)}GB.`,
333
+ });
334
+ }
335
+ const objectKey = `workspace_${userId}/${input.workspaceId}-file_${input.filename}`;
336
+ const fileAsset = await this.db.fileAsset.create({
337
+ data: {
338
+ workspaceId: input.workspaceId,
339
+ name: input.filename,
340
+ mimeType: input.contentType,
341
+ size: input.size,
342
+ userId,
343
+ bucket: 'media',
344
+ objectKey,
345
+ },
346
+ });
347
+ const { data: signedUrlData, error: signedUrlError } = await supabaseClient.storage
348
+ .from('media')
349
+ .createSignedUploadUrl(objectKey, { upsert: true });
350
+ if (signedUrlError) {
351
+ this.logger.error('Signed upload URL error:', signedUrlError);
352
+ throw new TRPCError({
353
+ code: 'INTERNAL_SERVER_ERROR',
354
+ message: `Failed to create upload URL: ${signedUrlError.message}`,
355
+ });
356
+ }
357
+ await this.db.workspace.update({
358
+ where: { id: input.workspaceId },
359
+ data: { needsAnalysis: true },
360
+ });
361
+ return {
362
+ fileId: fileAsset.id,
363
+ uploadUrl: signedUrlData.signedUrl,
364
+ };
365
+ }
366
+ async search(userId, input) {
367
+ const { query, color } = input;
368
+ const workspaces = await this.db.workspace.findMany({
369
+ where: {
370
+ ownerId: userId,
371
+ markerColor: color || undefined,
372
+ ...(query
373
+ ? {
374
+ OR: [
375
+ { title: { contains: query, mode: 'insensitive' } },
376
+ { description: { contains: query, mode: 'insensitive' } },
377
+ ],
378
+ }
379
+ : {}),
380
+ },
381
+ orderBy: { updatedAt: 'desc' },
382
+ take: input.limit,
383
+ });
384
+ const folders = await this.db.folder.findMany({
385
+ where: {
386
+ ownerId: userId,
387
+ markerColor: color || undefined,
388
+ ...(query ? { name: { contains: query, mode: 'insensitive' } } : {}),
389
+ },
390
+ orderBy: { updatedAt: 'desc' },
391
+ take: input.limit,
392
+ });
393
+ const results = [
394
+ ...workspaces.map((w) => ({ ...w, type: 'workspace' })),
395
+ ...folders.map((f) => ({ ...f, type: 'folder', title: f.name })),
396
+ ]
397
+ .sort((a, b) => b.updatedAt.getTime() - a.updatedAt.getTime())
398
+ .slice(0, input.limit);
399
+ return results;
400
+ }
401
+ }
@@ -0,0 +1,60 @@
1
+ import type { Context } from "./context.js";
2
+ export declare const router: import("@trpc/server").TRPCRouterBuilder<{
3
+ ctx: Context;
4
+ meta: object;
5
+ errorShape: {
6
+ data: {
7
+ zodError: string | null;
8
+ code: import("@trpc/server").TRPC_ERROR_CODE_KEY;
9
+ httpStatus: number;
10
+ path?: string;
11
+ stack?: string;
12
+ };
13
+ message: string;
14
+ code: import("@trpc/server").TRPC_ERROR_CODE_NUMBER;
15
+ };
16
+ transformer: true;
17
+ }>;
18
+ export declare const middleware: <$ContextOverrides>(fn: import("@trpc/server").TRPCMiddlewareFunction<Context, object, object, $ContextOverrides, unknown>) => import("@trpc/server").TRPCMiddlewareBuilder<Context, object, $ContextOverrides, unknown>;
19
+ export declare const publicProcedure: import("@trpc/server").TRPCProcedureBuilder<Context, object, object, import("@trpc/server").TRPCUnsetMarker, import("@trpc/server").TRPCUnsetMarker, import("@trpc/server").TRPCUnsetMarker, import("@trpc/server").TRPCUnsetMarker, false>;
20
+ /** Exported procedures with middleware */
21
+ export declare const authedProcedure: import("@trpc/server").TRPCProcedureBuilder<Context, object, {
22
+ userId: any;
23
+ req: import("express").Request<import("express-serve-static-core").ParamsDictionary, any, any, import("qs").ParsedQs, Record<string, any>>;
24
+ res: import("express").Response<any, Record<string, any>>;
25
+ session: {
26
+ user: import("./context.js").SessionUser;
27
+ };
28
+ db: import("@prisma/client").PrismaClient;
29
+ cookies: import("cookie").Cookies;
30
+ }, import("@trpc/server").TRPCUnsetMarker, import("@trpc/server").TRPCUnsetMarker, import("@trpc/server").TRPCUnsetMarker, import("@trpc/server").TRPCUnsetMarker, false>;
31
+ export declare const verifiedProcedure: import("@trpc/server").TRPCProcedureBuilder<Context, object, {
32
+ userId: any;
33
+ req: import("express").Request<import("express-serve-static-core").ParamsDictionary, any, any, import("qs").ParsedQs, Record<string, any>>;
34
+ res: import("express").Response<any, Record<string, any>>;
35
+ session: {
36
+ user: import("./context.js").SessionUser;
37
+ };
38
+ db: import("@prisma/client").PrismaClient;
39
+ cookies: import("cookie").Cookies;
40
+ }, import("@trpc/server").TRPCUnsetMarker, import("@trpc/server").TRPCUnsetMarker, import("@trpc/server").TRPCUnsetMarker, import("@trpc/server").TRPCUnsetMarker, false>;
41
+ export declare const adminProcedure: import("@trpc/server").TRPCProcedureBuilder<Context, object, {
42
+ userId: any;
43
+ req: import("express").Request<import("express-serve-static-core").ParamsDictionary, any, any, import("qs").ParsedQs, Record<string, any>>;
44
+ res: import("express").Response<any, Record<string, any>>;
45
+ session: {
46
+ user: import("./context.js").SessionUser;
47
+ };
48
+ db: import("@prisma/client").PrismaClient;
49
+ cookies: import("cookie").Cookies;
50
+ }, import("@trpc/server").TRPCUnsetMarker, import("@trpc/server").TRPCUnsetMarker, import("@trpc/server").TRPCUnsetMarker, import("@trpc/server").TRPCUnsetMarker, false>;
51
+ export declare const limitedProcedure: import("@trpc/server").TRPCProcedureBuilder<Context, object, {
52
+ userId: any;
53
+ req: import("express").Request<import("express-serve-static-core").ParamsDictionary, any, any, import("qs").ParsedQs, Record<string, any>>;
54
+ res: import("express").Response<any, Record<string, any>>;
55
+ session: {
56
+ user: import("./context.js").SessionUser;
57
+ };
58
+ db: import("@prisma/client").PrismaClient;
59
+ cookies: import("cookie").Cookies;
60
+ }, import("@trpc/server").TRPCUnsetMarker, import("@trpc/server").TRPCUnsetMarker, import("@trpc/server").TRPCUnsetMarker, import("@trpc/server").TRPCUnsetMarker, false>;
@@ -0,0 +1,217 @@
1
+ import { initTRPC, TRPCError } from "@trpc/server";
2
+ import superjson from "superjson";
3
+ import { ActivityLogStatus } from "@prisma/client";
4
+ import { logger } from "./lib/logger.js";
5
+ import { toTRPCError } from "./lib/errors.js";
6
+ import { getUserUsage, getUserPlanLimits } from "./services/billing/usage.service.js";
7
+ import { getClientIp, isActivityLogEnabled, scheduleRecordActivity, truncateUserAgent, } from "./services/activity/activity-log.service.js";
8
+ /** Avoid logging the log viewers themselves (noise when browsing activity). */
9
+ const SKIP_ACTIVITY_TRPC_PATHS = new Set([
10
+ "admin.activityList",
11
+ "admin.activityExportCsv",
12
+ ]);
13
+ const t = initTRPC.context().create({
14
+ transformer: superjson,
15
+ errorFormatter({ shape, error }) {
16
+ // Log errors in development
17
+ if (process.env.NODE_ENV === 'development') {
18
+ logger.error('TRPC Error', 'TRPC', {
19
+ code: error.code,
20
+ message: error.message,
21
+ cause: error.cause,
22
+ });
23
+ }
24
+ return {
25
+ ...shape,
26
+ data: {
27
+ ...shape.data,
28
+ zodError: error.cause instanceof Error ? error.cause.message : null,
29
+ },
30
+ };
31
+ },
32
+ });
33
+ export const router = t.router;
34
+ export const middleware = t.middleware;
35
+ export const publicProcedure = t.procedure;
36
+ /**
37
+ * Logging middleware
38
+ */
39
+ const loggingMiddleware = middleware(async ({ ctx, next, path, type }) => {
40
+ const start = Date.now();
41
+ const result = await next();
42
+ const duration = Date.now() - start;
43
+ logger.info(`TRPC ${type} ${path}`, 'TRPC', {
44
+ duration: `${duration}ms`,
45
+ userId: ctx.session?.user?.id,
46
+ });
47
+ return result;
48
+ });
49
+ /**
50
+ * Middleware that enforces authentication
51
+ */
52
+ const isAuthed = middleware(({ ctx, next }) => {
53
+ const hasUser = Boolean(ctx.session?.user?.id);
54
+ if (!ctx.session || !hasUser) {
55
+ throw new TRPCError({
56
+ code: "UNAUTHORIZED",
57
+ message: "You must be logged in to access this resource"
58
+ });
59
+ }
60
+ return next({
61
+ ctx: {
62
+ ...ctx,
63
+ session: ctx.session,
64
+ userId: ctx.session.user.id,
65
+ },
66
+ });
67
+ });
68
+ /**
69
+ * Error handling middleware
70
+ */
71
+ const errorHandler = middleware(async ({ next }) => {
72
+ try {
73
+ return await next();
74
+ }
75
+ catch (error) {
76
+ throw toTRPCError(error);
77
+ }
78
+ });
79
+ /**
80
+ * Middleware that enforces email verification
81
+ */
82
+ const isVerified = middleware(({ ctx, next }) => {
83
+ const user = ctx.session?.user;
84
+ if (!user?.emailVerified) {
85
+ throw new TRPCError({
86
+ code: "FORBIDDEN",
87
+ message: "Please verify your email to access this feature",
88
+ });
89
+ }
90
+ return next();
91
+ });
92
+ /**
93
+ * Middleware that enforces resource limits based on the user's plan.
94
+ * Note: This matches the 'path' to decide which limit to check.
95
+ */
96
+ const checkUsageLimit = middleware(async ({ ctx, next, path }) => {
97
+ const userId = ctx.session.user.id;
98
+ // 1. Get current usage and limits
99
+ const [usage, limits] = await Promise.all([
100
+ getUserUsage(userId),
101
+ getUserPlanLimits(userId)
102
+ ]);
103
+ // If no limits found (no active plan), we block any premium actions
104
+ if (!limits) {
105
+ throw new TRPCError({
106
+ code: "FORBIDDEN",
107
+ message: "No active plan found. Please subscribe to a plan to continue.",
108
+ });
109
+ }
110
+ // 2. Check limits based on the tRPC path
111
+ // Flashcards (matches: flashcards.createCard, flashcards.generateFromPrompt)
112
+ if (path.startsWith('flashcards.') && (path.includes('create') || path.includes('generate')) && usage.flashcards >= limits.maxFlashcards) {
113
+ throw new TRPCError({ code: "FORBIDDEN", message: "Flashcard limit reached for your plan." });
114
+ }
115
+ // Worksheets (matches: worksheets.create, worksheets.generateFromPrompt)
116
+ if (path.startsWith('worksheets.') && (path.includes('create') || path.includes('generate')) && usage.worksheets >= limits.maxWorksheets) {
117
+ throw new TRPCError({ code: "FORBIDDEN", message: "Worksheet limit reached for your plan." });
118
+ }
119
+ // Podcasts (matches: podcast.generateEpisode)
120
+ if (path.startsWith('podcast.') && path.includes('generate') && usage.podcasts >= limits.maxPodcasts) {
121
+ throw new TRPCError({ code: "FORBIDDEN", message: "Podcast limit reached for your plan." });
122
+ }
123
+ // Study Guides (matches: studyguide.get - because it lazily creates)
124
+ if (path.startsWith('studyguide.') && path.includes('get') && usage.studyGuides >= limits.maxStudyGuides) {
125
+ // Note: We only block if the artifact doesn't exist yet (handled inside the query or by checking usage)
126
+ // For simplicity, if they hit the limit, we block creation of NEW study guides.
127
+ // However, usage_service counts existing ones.
128
+ // If usage is already at/over limit, it means any NEW workspace's 'get' will fail creation.
129
+ }
130
+ // Storage check (for uploads)
131
+ if (path.includes('upload') && usage.storageBytes >= Number(limits.maxStorageBytes)) {
132
+ throw new TRPCError({ code: "FORBIDDEN", message: "Storage limit reached for your plan." });
133
+ }
134
+ return next();
135
+ });
136
+ /**
137
+ * Middleware that enforces system admin role
138
+ */
139
+ const isAdmin = middleware(({ ctx, next }) => {
140
+ const user = ctx.session?.user;
141
+ if (!user?.isSystemAdmin) {
142
+ throw new TRPCError({
143
+ code: "FORBIDDEN",
144
+ message: "You do not have permission to access this resource",
145
+ });
146
+ }
147
+ return next();
148
+ });
149
+ /**
150
+ * Persists ActivityLog rows for authenticated tRPC calls (async, non-blocking).
151
+ */
152
+ const activityLogMiddleware = middleware(async (opts) => {
153
+ const { ctx, next, path, type, getRawInput } = opts;
154
+ if (!isActivityLogEnabled() || SKIP_ACTIVITY_TRPC_PATHS.has(path)) {
155
+ return next();
156
+ }
157
+ const userId = ctx.userId;
158
+ if (!userId) {
159
+ return next();
160
+ }
161
+ let rawInput;
162
+ try {
163
+ rawInput = await getRawInput();
164
+ }
165
+ catch {
166
+ rawInput = undefined;
167
+ }
168
+ const req = ctx.req;
169
+ const ipAddress = req ? getClientIp(req) : undefined;
170
+ const userAgent = req.headers["user-agent"];
171
+ const httpMethod = req.method;
172
+ const start = Date.now();
173
+ const result = await next();
174
+ const durationMs = Date.now() - start;
175
+ if (result.ok) {
176
+ scheduleRecordActivity({
177
+ db: ctx.db,
178
+ actorUserId: userId,
179
+ path,
180
+ type,
181
+ status: ActivityLogStatus.SUCCESS,
182
+ durationMs,
183
+ rawInput,
184
+ ipAddress,
185
+ userAgent: truncateUserAgent(typeof userAgent === "string" ? userAgent : undefined),
186
+ httpMethod,
187
+ });
188
+ }
189
+ else {
190
+ scheduleRecordActivity({
191
+ db: ctx.db,
192
+ actorUserId: userId,
193
+ path,
194
+ type,
195
+ status: ActivityLogStatus.FAILURE,
196
+ durationMs,
197
+ rawInput,
198
+ ipAddress,
199
+ userAgent: truncateUserAgent(typeof userAgent === "string" ? userAgent : undefined),
200
+ httpMethod,
201
+ errorCode: result.error.code,
202
+ });
203
+ }
204
+ return result;
205
+ });
206
+ /** Exported procedures with middleware */
207
+ export const authedProcedure = publicProcedure
208
+ .use(loggingMiddleware)
209
+ .use(errorHandler)
210
+ .use(isAuthed)
211
+ .use(activityLogMiddleware);
212
+ export const verifiedProcedure = authedProcedure
213
+ .use(isVerified);
214
+ export const adminProcedure = authedProcedure
215
+ .use(isAdmin);
216
+ export const limitedProcedure = verifiedProcedure
217
+ .use(checkUsageLimit);