@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.
- package/.env.example +43 -0
- package/check-difficulty.cjs +14 -0
- package/check-questions.cjs +14 -0
- package/db-summary.cjs +22 -0
- package/dist/routers/auth.js +1 -1
- package/mcq-test.cjs +36 -0
- package/package.json +10 -2
- package/prisma/migrations/20260413143206_init/migration.sql +873 -0
- package/prisma/schema.prisma +485 -292
- package/src/context.ts +4 -1
- package/src/lib/activity_human_description.test.ts +28 -0
- package/src/lib/activity_human_description.ts +239 -0
- package/src/lib/activity_log_service.test.ts +37 -0
- package/src/lib/activity_log_service.ts +353 -0
- package/src/lib/ai-session.ts +194 -112
- package/src/lib/constants.ts +14 -0
- package/src/lib/email.ts +230 -0
- package/src/lib/env.ts +23 -6
- package/src/lib/inference.ts +3 -3
- package/src/lib/logger.ts +26 -9
- package/src/lib/notification-service.test.ts +106 -0
- package/src/lib/notification-service.ts +677 -0
- package/src/lib/prisma.ts +6 -1
- package/src/lib/pusher.ts +90 -6
- package/src/lib/retry.ts +61 -0
- package/src/lib/storage.ts +2 -2
- package/src/lib/stripe.ts +39 -0
- package/src/lib/subscription_service.ts +722 -0
- package/src/lib/usage_service.ts +74 -0
- package/src/lib/worksheet-generation.test.ts +31 -0
- package/src/lib/worksheet-generation.ts +139 -0
- package/src/lib/workspace-access.ts +13 -0
- package/src/routers/_app.ts +11 -0
- package/src/routers/admin.ts +710 -0
- package/src/routers/annotations.ts +227 -0
- package/src/routers/auth.ts +432 -33
- package/src/routers/copilot.ts +719 -0
- package/src/routers/flashcards.ts +207 -80
- package/src/routers/members.ts +280 -80
- package/src/routers/notifications.ts +142 -0
- package/src/routers/payment.ts +448 -0
- package/src/routers/podcast.ts +133 -108
- package/src/routers/studyguide.ts +80 -74
- package/src/routers/worksheets.ts +300 -80
- package/src/routers/workspace.ts +538 -328
- package/src/scripts/purge-deleted-users.ts +167 -0
- package/src/server.ts +140 -12
- package/src/services/flashcard-progress.service.ts +52 -43
- package/src/trpc.ts +184 -5
- package/test-generate.js +30 -0
- package/test-ratio.cjs +9 -0
- package/zod-test.cjs +22 -0
- package/prisma/migrations/20250826124819_add_worksheet_difficulty_and_estimated_time/migration.sql +0 -213
- package/prisma/migrations/20250826133236_add_worksheet_question_progress/migration.sql +0 -31
- package/prisma/seed.mjs +0 -135
- package/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
|
+
});
|