@goscribe/server 1.1.7 → 1.3.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 (56) hide show
  1. package/.env.example +43 -0
  2. package/check-difficulty.cjs +14 -0
  3. package/check-questions.cjs +14 -0
  4. package/db-summary.cjs +22 -0
  5. package/dist/routers/auth.js +1 -1
  6. package/mcq-test.cjs +36 -0
  7. package/package.json +10 -2
  8. package/prisma/migrations/20260413143206_init/migration.sql +873 -0
  9. package/prisma/schema.prisma +485 -292
  10. package/src/context.ts +4 -1
  11. package/src/lib/activity_human_description.test.ts +28 -0
  12. package/src/lib/activity_human_description.ts +239 -0
  13. package/src/lib/activity_log_service.test.ts +37 -0
  14. package/src/lib/activity_log_service.ts +353 -0
  15. package/src/lib/ai-session.ts +194 -112
  16. package/src/lib/constants.ts +14 -0
  17. package/src/lib/email.ts +230 -0
  18. package/src/lib/env.ts +23 -6
  19. package/src/lib/inference.ts +3 -3
  20. package/src/lib/logger.ts +26 -9
  21. package/src/lib/notification-service.test.ts +106 -0
  22. package/src/lib/notification-service.ts +677 -0
  23. package/src/lib/prisma.ts +6 -1
  24. package/src/lib/pusher.ts +90 -6
  25. package/src/lib/retry.ts +61 -0
  26. package/src/lib/storage.ts +2 -2
  27. package/src/lib/stripe.ts +39 -0
  28. package/src/lib/subscription_service.ts +722 -0
  29. package/src/lib/usage_service.ts +74 -0
  30. package/src/lib/worksheet-generation.test.ts +31 -0
  31. package/src/lib/worksheet-generation.ts +139 -0
  32. package/src/lib/workspace-access.ts +13 -0
  33. package/src/routers/_app.ts +11 -0
  34. package/src/routers/admin.ts +710 -0
  35. package/src/routers/annotations.ts +227 -0
  36. package/src/routers/auth.ts +432 -33
  37. package/src/routers/copilot.ts +719 -0
  38. package/src/routers/flashcards.ts +207 -80
  39. package/src/routers/members.ts +280 -80
  40. package/src/routers/notifications.ts +142 -0
  41. package/src/routers/payment.ts +448 -0
  42. package/src/routers/podcast.ts +133 -108
  43. package/src/routers/studyguide.ts +80 -74
  44. package/src/routers/worksheets.ts +300 -80
  45. package/src/routers/workspace.ts +538 -328
  46. package/src/scripts/purge-deleted-users.ts +167 -0
  47. package/src/server.ts +140 -12
  48. package/src/services/flashcard-progress.service.ts +52 -43
  49. package/src/trpc.ts +184 -5
  50. package/test-generate.js +30 -0
  51. package/test-ratio.cjs +9 -0
  52. package/zod-test.cjs +22 -0
  53. package/prisma/migrations/20250826124819_add_worksheet_difficulty_and_estimated_time/migration.sql +0 -213
  54. package/prisma/migrations/20250826133236_add_worksheet_question_progress/migration.sql +0 -31
  55. package/prisma/seed.mjs +0 -135
  56. package/src/routers/meetingsummary.ts +0 -416
@@ -0,0 +1,227 @@
1
+ import { z } from 'zod';
2
+ import { TRPCError } from '@trpc/server';
3
+ import { router, authedProcedure } from '../trpc.js';
4
+ import { notifyStudyGuideCommentAdded } from '../lib/notification-service.js';
5
+
6
+ export const annotations = router({
7
+ // List all highlights (with nested comments) for an artifact version
8
+ listHighlights: authedProcedure
9
+ .input(
10
+ z.object({
11
+ artifactVersionId: z.string(),
12
+ })
13
+ )
14
+ .query(async ({ ctx, input }) => {
15
+ const highlights = await ctx.db.studyGuideHighlight.findMany({
16
+ where: { artifactVersionId: input.artifactVersionId },
17
+ include: {
18
+ comments: {
19
+ include: {
20
+ user: { select: { id: true, name: true, profilePicture: true } },
21
+ },
22
+ orderBy: { createdAt: 'asc' },
23
+ },
24
+ user: { select: { id: true, name: true, profilePicture: true } },
25
+ },
26
+ orderBy: { startOffset: 'asc' },
27
+ });
28
+
29
+ return highlights;
30
+ }),
31
+
32
+ // Create a new highlight
33
+ createHighlight: authedProcedure
34
+ .input(
35
+ z.object({
36
+ artifactVersionId: z.string(),
37
+ startOffset: z.number().int().min(0),
38
+ endOffset: z.number().int().min(0),
39
+ selectedText: z.string().min(1),
40
+ color: z.string().optional(),
41
+ })
42
+ )
43
+ .mutation(async ({ ctx, input }) => {
44
+ // Verify the artifact version exists
45
+ const version = await ctx.db.artifactVersion.findUnique({
46
+ where: { id: input.artifactVersionId },
47
+ });
48
+
49
+ if (!version) {
50
+ throw new TRPCError({ code: 'NOT_FOUND', message: 'Artifact version not found' });
51
+ }
52
+
53
+ const highlight = await ctx.db.studyGuideHighlight.create({
54
+ data: {
55
+ artifactVersionId: input.artifactVersionId,
56
+ userId: ctx.session.user.id,
57
+ startOffset: input.startOffset,
58
+ endOffset: input.endOffset,
59
+ selectedText: input.selectedText,
60
+ ...(input.color && { color: input.color }),
61
+ },
62
+ include: {
63
+ comments: true,
64
+ user: { select: { id: true, name: true, profilePicture: true } },
65
+ },
66
+ });
67
+
68
+ return highlight;
69
+ }),
70
+
71
+ // Delete a highlight (and its comments via cascade)
72
+ deleteHighlight: authedProcedure
73
+ .input(
74
+ z.object({
75
+ highlightId: z.string(),
76
+ })
77
+ )
78
+ .mutation(async ({ ctx, input }) => {
79
+ const highlight = await ctx.db.studyGuideHighlight.findUnique({
80
+ where: { id: input.highlightId },
81
+ });
82
+
83
+ if (!highlight) {
84
+ throw new TRPCError({ code: 'NOT_FOUND', message: 'Highlight not found' });
85
+ }
86
+
87
+ if (highlight.userId !== ctx.session.user.id) {
88
+ throw new TRPCError({ code: 'FORBIDDEN', message: 'You can only delete your own highlights' });
89
+ }
90
+
91
+ await ctx.db.studyGuideHighlight.delete({
92
+ where: { id: input.highlightId },
93
+ });
94
+
95
+ return { success: true };
96
+ }),
97
+
98
+ // Add a comment to a highlight
99
+ addComment: authedProcedure
100
+ .input(
101
+ z.object({
102
+ highlightId: z.string(),
103
+ content: z.string().min(1),
104
+ })
105
+ )
106
+ .mutation(async ({ ctx, input }) => {
107
+ const highlight = await ctx.db.studyGuideHighlight.findUnique({
108
+ where: { id: input.highlightId },
109
+ });
110
+
111
+ if (!highlight) {
112
+ throw new TRPCError({ code: 'NOT_FOUND', message: 'Highlight not found' });
113
+ }
114
+
115
+ const comment = await ctx.db.studyGuideComment.create({
116
+ data: {
117
+ highlightId: input.highlightId,
118
+ userId: ctx.session.user.id,
119
+ content: input.content,
120
+ },
121
+ include: {
122
+ user: { select: { id: true, name: true, profilePicture: true } },
123
+ },
124
+ });
125
+
126
+ const after = await ctx.db.studyGuideHighlight.findUnique({
127
+ where: { id: input.highlightId },
128
+ include: {
129
+ comments: { select: { userId: true } },
130
+ artifactVersion: {
131
+ include: {
132
+ artifact: {
133
+ select: { id: true, title: true, workspaceId: true, type: true },
134
+ },
135
+ },
136
+ },
137
+ },
138
+ });
139
+
140
+ if (after?.artifactVersion?.artifact) {
141
+ const art = after.artifactVersion.artifact;
142
+ const recipientIds = new Set<string>();
143
+ if (after.userId !== ctx.session.user.id) {
144
+ recipientIds.add(after.userId);
145
+ }
146
+ for (const c of after.comments) {
147
+ if (c.userId !== ctx.session.user.id) {
148
+ recipientIds.add(c.userId);
149
+ }
150
+ }
151
+ const authorName =
152
+ comment.user.name?.trim() || 'Someone';
153
+ await notifyStudyGuideCommentAdded(ctx.db, {
154
+ authorUserId: ctx.session.user.id,
155
+ authorName,
156
+ content: input.content,
157
+ highlightId: input.highlightId,
158
+ commentId: comment.id,
159
+ workspaceId: art.workspaceId,
160
+ artifactId: art.id,
161
+ artifactTitle: art.title,
162
+ recipientUserIds: [...recipientIds],
163
+ }).catch(() => {});
164
+ }
165
+
166
+ return comment;
167
+ }),
168
+
169
+ // Update a comment
170
+ updateComment: authedProcedure
171
+ .input(
172
+ z.object({
173
+ commentId: z.string(),
174
+ content: z.string().min(1),
175
+ })
176
+ )
177
+ .mutation(async ({ ctx, input }) => {
178
+ const comment = await ctx.db.studyGuideComment.findUnique({
179
+ where: { id: input.commentId },
180
+ });
181
+
182
+ if (!comment) {
183
+ throw new TRPCError({ code: 'NOT_FOUND', message: 'Comment not found' });
184
+ }
185
+
186
+ if (comment.userId !== ctx.session.user.id) {
187
+ throw new TRPCError({ code: 'FORBIDDEN', message: 'You can only edit your own comments' });
188
+ }
189
+
190
+ const updated = await ctx.db.studyGuideComment.update({
191
+ where: { id: input.commentId },
192
+ data: { content: input.content },
193
+ include: {
194
+ user: { select: { id: true, name: true, profilePicture: true } },
195
+ },
196
+ });
197
+
198
+ return updated;
199
+ }),
200
+
201
+ // Delete a comment
202
+ deleteComment: authedProcedure
203
+ .input(
204
+ z.object({
205
+ commentId: z.string(),
206
+ })
207
+ )
208
+ .mutation(async ({ ctx, input }) => {
209
+ const comment = await ctx.db.studyGuideComment.findUnique({
210
+ where: { id: input.commentId },
211
+ });
212
+
213
+ if (!comment) {
214
+ throw new TRPCError({ code: 'NOT_FOUND', message: 'Comment not found' });
215
+ }
216
+
217
+ if (comment.userId !== ctx.session.user.id) {
218
+ throw new TRPCError({ code: 'FORBIDDEN', message: 'You can only delete your own comments' });
219
+ }
220
+
221
+ await ctx.db.studyGuideComment.delete({
222
+ where: { id: input.commentId },
223
+ });
224
+
225
+ return { success: true };
226
+ }),
227
+ });