@goscribe/server 1.1.2 → 1.1.3
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/dist/lib/ai-session.d.ts +13 -3
- package/dist/lib/ai-session.js +66 -146
- package/dist/lib/pusher.js +1 -1
- package/dist/routers/_app.d.ts +114 -7
- package/dist/routers/chat.js +2 -23
- package/dist/routers/flashcards.d.ts +25 -1
- package/dist/routers/flashcards.js +0 -14
- package/dist/routers/members.d.ts +18 -0
- package/dist/routers/members.js +14 -1
- package/dist/routers/worksheets.js +5 -4
- package/dist/routers/workspace.d.ts +89 -6
- package/dist/routers/workspace.js +389 -259
- package/dist/services/flashcard-progress.service.d.ts +25 -1
- package/dist/services/flashcard-progress.service.js +70 -31
- package/package.json +2 -2
- package/prisma/schema.prisma +12 -2
- package/src/lib/ai-session.ts +3 -21
- package/src/routers/flashcards.ts +0 -16
- package/src/routers/members.ts +13 -2
- package/src/routers/workspace.ts +468 -439
- package/ANALYSIS_PROGRESS_SPEC.md +0 -463
- package/PROGRESS_QUICK_REFERENCE.md +0 -239
- package/dist/lib/podcast-prompts.d.ts +0 -43
- package/dist/lib/podcast-prompts.js +0 -135
- package/dist/routers/ai-session.d.ts +0 -0
- package/dist/routers/ai-session.js +0 -1
- package/dist/services/flashcard.service.d.ts +0 -183
- package/dist/services/flashcard.service.js +0 -224
- package/dist/services/podcast-segment-reorder.d.ts +0 -0
- package/dist/services/podcast-segment-reorder.js +0 -107
- package/dist/services/podcast.service.d.ts +0 -0
- package/dist/services/podcast.service.js +0 -326
- package/dist/services/worksheet.service.d.ts +0 -0
- package/dist/services/worksheet.service.js +0 -295
|
@@ -1,326 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
// import type { PrismaClient } from '@prisma/client';
|
|
3
|
-
// import { NotFoundError, ValidationError } from '../lib/errors.js';
|
|
4
|
-
// import { v4 as uuidv4 } from 'uuid';
|
|
5
|
-
// export interface PodcastSegmentData {
|
|
6
|
-
// id: string;
|
|
7
|
-
// title: string;
|
|
8
|
-
// content: string;
|
|
9
|
-
// objectKey: string;
|
|
10
|
-
// startTime: number;
|
|
11
|
-
// duration: number;
|
|
12
|
-
// keyPoints: string[];
|
|
13
|
-
// order: number;
|
|
14
|
-
// }
|
|
15
|
-
// export interface PodcastMetadata {
|
|
16
|
-
// title: string;
|
|
17
|
-
// description?: string;
|
|
18
|
-
// totalDuration: number;
|
|
19
|
-
// voice: string;
|
|
20
|
-
// speed: number;
|
|
21
|
-
// summary: {
|
|
22
|
-
// executiveSummary: string;
|
|
23
|
-
// learningObjectives: string[];
|
|
24
|
-
// keyConcepts: string[];
|
|
25
|
-
// followUpActions: string[];
|
|
26
|
-
// targetAudience: string;
|
|
27
|
-
// prerequisites: string[];
|
|
28
|
-
// tags: string[];
|
|
29
|
-
// };
|
|
30
|
-
// generatedAt: string;
|
|
31
|
-
// }
|
|
32
|
-
// export class PodcastService {
|
|
33
|
-
// constructor(private db: PrismaClient) {}
|
|
34
|
-
// /**
|
|
35
|
-
// * Create a podcast artifact in "generating" state
|
|
36
|
-
// */
|
|
37
|
-
// async createGeneratingArtifact(workspaceId: string, userId: string, initialMessage: string) {
|
|
38
|
-
// const workspace = await this.db.workspace.findFirst({
|
|
39
|
-
// where: { id: workspaceId, ownerId: userId },
|
|
40
|
-
// });
|
|
41
|
-
// if (!workspace) {
|
|
42
|
-
// throw new NotFoundError('Workspace');
|
|
43
|
-
// }
|
|
44
|
-
// return this.db.artifact.create({
|
|
45
|
-
// data: {
|
|
46
|
-
// title: '----',
|
|
47
|
-
// type: 'PODCAST_EPISODE',
|
|
48
|
-
// generating: true,
|
|
49
|
-
// generatingMetadata: {
|
|
50
|
-
// message: initialMessage,
|
|
51
|
-
// },
|
|
52
|
-
// workspaceId,
|
|
53
|
-
// createdById: userId,
|
|
54
|
-
// },
|
|
55
|
-
// });
|
|
56
|
-
// }
|
|
57
|
-
// /**
|
|
58
|
-
// * Update generation progress
|
|
59
|
-
// */
|
|
60
|
-
// async updateGenerationProgress(
|
|
61
|
-
// artifactId: string,
|
|
62
|
-
// currentSegment: number,
|
|
63
|
-
// totalSegments: number,
|
|
64
|
-
// segmentTitle: string
|
|
65
|
-
// ) {
|
|
66
|
-
// return this.db.artifact.update({
|
|
67
|
-
// where: { id: artifactId },
|
|
68
|
-
// data: {
|
|
69
|
-
// generatingMetadata: {
|
|
70
|
-
// currentSegment,
|
|
71
|
-
// totalSegments,
|
|
72
|
-
// segmentTitle,
|
|
73
|
-
// message: `Generating segment ${currentSegment} of ${totalSegments}`,
|
|
74
|
-
// },
|
|
75
|
-
// },
|
|
76
|
-
// });
|
|
77
|
-
// }
|
|
78
|
-
// /**
|
|
79
|
-
// * Finalize podcast artifact with segments and metadata
|
|
80
|
-
// */
|
|
81
|
-
// async finalizePodcast(data: {
|
|
82
|
-
// artifactId: string;
|
|
83
|
-
// title: string;
|
|
84
|
-
// description?: string;
|
|
85
|
-
// userId: string;
|
|
86
|
-
// segments: PodcastSegmentData[];
|
|
87
|
-
// metadata: PodcastMetadata;
|
|
88
|
-
// fullTranscript: string;
|
|
89
|
-
// }) {
|
|
90
|
-
// const { artifactId, title, description, userId, segments, metadata, fullTranscript } = data;
|
|
91
|
-
// // Use a transaction for atomic updates
|
|
92
|
-
// return this.db.$transaction(async (tx) => {
|
|
93
|
-
// // Update artifact
|
|
94
|
-
// await tx.artifact.update({
|
|
95
|
-
// where: { id: artifactId },
|
|
96
|
-
// data: {
|
|
97
|
-
// title,
|
|
98
|
-
// description,
|
|
99
|
-
// generating: false,
|
|
100
|
-
// generatingMetadata: {},
|
|
101
|
-
// },
|
|
102
|
-
// });
|
|
103
|
-
// // Create segments
|
|
104
|
-
// await tx.podcastSegment.createMany({
|
|
105
|
-
// data: segments.map((segment) => ({
|
|
106
|
-
// artifactId,
|
|
107
|
-
// title: segment.title,
|
|
108
|
-
// content: segment.content,
|
|
109
|
-
// startTime: segment.startTime,
|
|
110
|
-
// duration: segment.duration,
|
|
111
|
-
// order: segment.order,
|
|
112
|
-
// objectKey: segment.objectKey,
|
|
113
|
-
// keyPoints: segment.keyPoints,
|
|
114
|
-
// meta: {
|
|
115
|
-
// voice: metadata.voice,
|
|
116
|
-
// speed: metadata.speed,
|
|
117
|
-
// },
|
|
118
|
-
// })),
|
|
119
|
-
// });
|
|
120
|
-
// // Create version
|
|
121
|
-
// return tx.artifactVersion.create({
|
|
122
|
-
// data: {
|
|
123
|
-
// artifactId,
|
|
124
|
-
// version: 1,
|
|
125
|
-
// content: fullTranscript.trim(),
|
|
126
|
-
// data: JSON.stringify(metadata),
|
|
127
|
-
// createdById: userId,
|
|
128
|
-
// },
|
|
129
|
-
// });
|
|
130
|
-
// });
|
|
131
|
-
// }
|
|
132
|
-
// /**
|
|
133
|
-
// * Delete artifact and cleanup (for failed generations)
|
|
134
|
-
// */
|
|
135
|
-
// async cleanupFailedGeneration(artifactId: string) {
|
|
136
|
-
// try {
|
|
137
|
-
// await this.db.artifact.delete({
|
|
138
|
-
// where: { id: artifactId },
|
|
139
|
-
// });
|
|
140
|
-
// } catch (error) {
|
|
141
|
-
// console.error('Failed to cleanup artifact:', error);
|
|
142
|
-
// }
|
|
143
|
-
// }
|
|
144
|
-
// /**
|
|
145
|
-
// * Get podcast episode with segments
|
|
146
|
-
// */
|
|
147
|
-
// async getEpisode(episodeId: string, userId: string) {
|
|
148
|
-
// const episode = await this.db.artifact.findFirst({
|
|
149
|
-
// where: {
|
|
150
|
-
// id: episodeId,
|
|
151
|
-
// type: 'PODCAST_EPISODE',
|
|
152
|
-
// workspace: { ownerId: userId },
|
|
153
|
-
// },
|
|
154
|
-
// include: {
|
|
155
|
-
// versions: {
|
|
156
|
-
// orderBy: { version: 'desc' },
|
|
157
|
-
// take: 1,
|
|
158
|
-
// },
|
|
159
|
-
// podcastSegments: {
|
|
160
|
-
// orderBy: { order: 'asc' },
|
|
161
|
-
// },
|
|
162
|
-
// },
|
|
163
|
-
// });
|
|
164
|
-
// if (!episode) {
|
|
165
|
-
// throw new NotFoundError('Podcast episode');
|
|
166
|
-
// }
|
|
167
|
-
// const latestVersion = episode.versions[0];
|
|
168
|
-
// if (!latestVersion) {
|
|
169
|
-
// throw new NotFoundError('Podcast version');
|
|
170
|
-
// }
|
|
171
|
-
// return { episode, latestVersion };
|
|
172
|
-
// }
|
|
173
|
-
// /**
|
|
174
|
-
// * Regenerate a segment with new content
|
|
175
|
-
// */
|
|
176
|
-
// async regenerateSegment(data: {
|
|
177
|
-
// episodeId: string;
|
|
178
|
-
// segmentId: string;
|
|
179
|
-
// newContent: string;
|
|
180
|
-
// newObjectKey: string;
|
|
181
|
-
// newDuration: number;
|
|
182
|
-
// userId: string;
|
|
183
|
-
// }) {
|
|
184
|
-
// const { episodeId, segmentId, newContent, newObjectKey, newDuration, userId } = data;
|
|
185
|
-
// const { episode, latestVersion } = await this.getEpisode(episodeId, userId);
|
|
186
|
-
// return this.db.$transaction(async (tx) => {
|
|
187
|
-
// // Update segment
|
|
188
|
-
// await tx.podcastSegment.update({
|
|
189
|
-
// where: { id: segmentId },
|
|
190
|
-
// data: {
|
|
191
|
-
// content: newContent,
|
|
192
|
-
// objectKey: newObjectKey,
|
|
193
|
-
// duration: newDuration,
|
|
194
|
-
// },
|
|
195
|
-
// });
|
|
196
|
-
// // Recalculate start times
|
|
197
|
-
// const allSegments = await tx.podcastSegment.findMany({
|
|
198
|
-
// where: { artifactId: episodeId },
|
|
199
|
-
// orderBy: { order: 'asc' },
|
|
200
|
-
// });
|
|
201
|
-
// let currentTime = 0;
|
|
202
|
-
// for (const seg of allSegments) {
|
|
203
|
-
// await tx.podcastSegment.update({
|
|
204
|
-
// where: { id: seg.id },
|
|
205
|
-
// data: { startTime: currentTime },
|
|
206
|
-
// });
|
|
207
|
-
// currentTime += seg.id === segmentId ? newDuration : seg.duration;
|
|
208
|
-
// }
|
|
209
|
-
// // Update metadata
|
|
210
|
-
// const metadata = latestVersion.data as unknown as PodcastMetadata;
|
|
211
|
-
// metadata.totalDuration = currentTime;
|
|
212
|
-
// // Rebuild transcript
|
|
213
|
-
// const fullTranscript = allSegments
|
|
214
|
-
// .sort((a, b) => a.order - b.order)
|
|
215
|
-
// .map((s) => `\n\n## ${s.title}\n\n${s.content}`)
|
|
216
|
-
// .join('');
|
|
217
|
-
// // Create new version
|
|
218
|
-
// const nextVersion = (latestVersion.version || 0) + 1;
|
|
219
|
-
// return tx.artifactVersion.create({
|
|
220
|
-
// data: {
|
|
221
|
-
// artifactId: episodeId,
|
|
222
|
-
// version: nextVersion,
|
|
223
|
-
// content: fullTranscript.trim(),
|
|
224
|
-
// data: JSON.stringify(metadata),
|
|
225
|
-
// createdById: userId,
|
|
226
|
-
// },
|
|
227
|
-
// });
|
|
228
|
-
// });
|
|
229
|
-
// }
|
|
230
|
-
// /**
|
|
231
|
-
// * Delete episode and associated resources
|
|
232
|
-
// */
|
|
233
|
-
// async deleteEpisode(episodeId: string, userId: string) {
|
|
234
|
-
// const episode = await this.db.artifact.findFirst({
|
|
235
|
-
// where: {
|
|
236
|
-
// id: episodeId,
|
|
237
|
-
// type: 'PODCAST_EPISODE',
|
|
238
|
-
// workspace: { ownerId: userId },
|
|
239
|
-
// },
|
|
240
|
-
// include: {
|
|
241
|
-
// podcastSegments: true,
|
|
242
|
-
// },
|
|
243
|
-
// });
|
|
244
|
-
// if (!episode) {
|
|
245
|
-
// throw new NotFoundError('Podcast episode');
|
|
246
|
-
// }
|
|
247
|
-
// // Return object keys for deletion from storage
|
|
248
|
-
// const objectKeys = episode.podcastSegments
|
|
249
|
-
// .filter((s) => s.objectKey)
|
|
250
|
-
// .map((s) => s.objectKey!);
|
|
251
|
-
// // Delete in transaction
|
|
252
|
-
// await this.db.$transaction(async (tx) => {
|
|
253
|
-
// await tx.podcastSegment.deleteMany({
|
|
254
|
-
// where: { artifactId: episodeId },
|
|
255
|
-
// });
|
|
256
|
-
// await tx.artifactVersion.deleteMany({
|
|
257
|
-
// where: { artifactId: episodeId },
|
|
258
|
-
// });
|
|
259
|
-
// await tx.artifact.delete({
|
|
260
|
-
// where: { id: episodeId },
|
|
261
|
-
// });
|
|
262
|
-
// });
|
|
263
|
-
// return { objectKeys };
|
|
264
|
-
// }
|
|
265
|
-
// /**
|
|
266
|
-
// * Update episode metadata
|
|
267
|
-
// */
|
|
268
|
-
// async updateEpisodeMetadata(data: {
|
|
269
|
-
// episodeId: string;
|
|
270
|
-
// title?: string;
|
|
271
|
-
// description?: string;
|
|
272
|
-
// userId: string;
|
|
273
|
-
// }) {
|
|
274
|
-
// const { episodeId, title, description, userId } = data;
|
|
275
|
-
// const { episode, latestVersion } = await this.getEpisode(episodeId, userId);
|
|
276
|
-
// const metadata = latestVersion.data as unknown as PodcastMetadata;
|
|
277
|
-
// if (title) metadata.title = title;
|
|
278
|
-
// if (description) metadata.description = description;
|
|
279
|
-
// return this.db.$transaction(async (tx) => {
|
|
280
|
-
// // Create new version
|
|
281
|
-
// const nextVersion = (latestVersion.version || 0) + 1;
|
|
282
|
-
// await tx.artifactVersion.create({
|
|
283
|
-
// data: {
|
|
284
|
-
// artifactId: episodeId,
|
|
285
|
-
// version: nextVersion,
|
|
286
|
-
// content: latestVersion.content,
|
|
287
|
-
// data: JSON.stringify(metadata),
|
|
288
|
-
// createdById: userId,
|
|
289
|
-
// },
|
|
290
|
-
// });
|
|
291
|
-
// // Update artifact
|
|
292
|
-
// return tx.artifact.update({
|
|
293
|
-
// where: { id: episodeId },
|
|
294
|
-
// data: {
|
|
295
|
-
// title: title ?? episode.title,
|
|
296
|
-
// description: description ?? episode.description,
|
|
297
|
-
// updatedAt: new Date(),
|
|
298
|
-
// },
|
|
299
|
-
// });
|
|
300
|
-
// });
|
|
301
|
-
// }
|
|
302
|
-
// /**
|
|
303
|
-
// * Get study guide content for podcast generation
|
|
304
|
-
// */
|
|
305
|
-
// async getStudyGuideContent(workspaceId: string) {
|
|
306
|
-
// const studyGuide = await this.db.artifact.findFirst({
|
|
307
|
-
// where: {
|
|
308
|
-
// workspaceId,
|
|
309
|
-
// type: 'STUDY_GUIDE',
|
|
310
|
-
// },
|
|
311
|
-
// include: {
|
|
312
|
-
// versions: {
|
|
313
|
-
// orderBy: { version: 'desc' },
|
|
314
|
-
// take: 1,
|
|
315
|
-
// },
|
|
316
|
-
// },
|
|
317
|
-
// });
|
|
318
|
-
// return studyGuide?.versions[0]?.content || '';
|
|
319
|
-
// }
|
|
320
|
-
// }
|
|
321
|
-
// /**
|
|
322
|
-
// * Factory function
|
|
323
|
-
// */
|
|
324
|
-
// export function createPodcastService(db: PrismaClient) {
|
|
325
|
-
// return new PodcastService(db);
|
|
326
|
-
// }
|
|
File without changes
|
|
@@ -1,295 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
// import type { PrismaClient } from '@prisma/client';
|
|
3
|
-
// import { NotFoundError, ForbiddenError, ValidationError } from '../lib/errors.js';
|
|
4
|
-
// import type {
|
|
5
|
-
// QuestionTypeEnum,
|
|
6
|
-
// DifficultyEnum,
|
|
7
|
-
// WorksheetQuestionMeta,
|
|
8
|
-
// CreateWorksheetQuestionInput
|
|
9
|
-
// } from '../types/index.js';
|
|
10
|
-
// export class WorksheetService {
|
|
11
|
-
// constructor(private db: PrismaClient) {}
|
|
12
|
-
// /**
|
|
13
|
-
// * List all worksheets for a workspace
|
|
14
|
-
// */
|
|
15
|
-
// async listWorksheets(workspaceId: string, userId: string) {
|
|
16
|
-
// const worksheets = await this.db.artifact.findMany({
|
|
17
|
-
// where: {
|
|
18
|
-
// workspaceId,
|
|
19
|
-
// type: 'WORKSHEET',
|
|
20
|
-
// workspace: { ownerId: userId }
|
|
21
|
-
// },
|
|
22
|
-
// include: {
|
|
23
|
-
// versions: {
|
|
24
|
-
// orderBy: { version: 'desc' },
|
|
25
|
-
// take: 1,
|
|
26
|
-
// },
|
|
27
|
-
// questions: {
|
|
28
|
-
// orderBy: { order: 'asc' }
|
|
29
|
-
// },
|
|
30
|
-
// },
|
|
31
|
-
// orderBy: { updatedAt: 'desc' },
|
|
32
|
-
// });
|
|
33
|
-
// // Merge user progress
|
|
34
|
-
// const allQuestionIds = worksheets.flatMap(w => w.questions.map(q => q.id));
|
|
35
|
-
// if (allQuestionIds.length === 0) {
|
|
36
|
-
// return worksheets;
|
|
37
|
-
// }
|
|
38
|
-
// const progress = await this.db.worksheetQuestionProgress.findMany({
|
|
39
|
-
// where: {
|
|
40
|
-
// userId,
|
|
41
|
-
// worksheetQuestionId: { in: allQuestionIds }
|
|
42
|
-
// },
|
|
43
|
-
// });
|
|
44
|
-
// const progressByQuestionId = new Map(
|
|
45
|
-
// progress.map(p => [p.worksheetQuestionId, p])
|
|
46
|
-
// );
|
|
47
|
-
// return worksheets.map(worksheet => ({
|
|
48
|
-
// ...worksheet,
|
|
49
|
-
// questions: worksheet.questions.map(question => {
|
|
50
|
-
// const userProgress = progressByQuestionId.get(question.id);
|
|
51
|
-
// const existingMeta = this.parseMeta(question.meta);
|
|
52
|
-
// return {
|
|
53
|
-
// ...question,
|
|
54
|
-
// meta: {
|
|
55
|
-
// ...existingMeta,
|
|
56
|
-
// completed: userProgress?.modified || false,
|
|
57
|
-
// userAnswer: userProgress?.userAnswer,
|
|
58
|
-
// completedAt: userProgress?.completedAt,
|
|
59
|
-
// },
|
|
60
|
-
// };
|
|
61
|
-
// }),
|
|
62
|
-
// }));
|
|
63
|
-
// }
|
|
64
|
-
// /**
|
|
65
|
-
// * Get a single worksheet
|
|
66
|
-
// */
|
|
67
|
-
// async getWorksheet(worksheetId: string, userId: string) {
|
|
68
|
-
// const worksheet = await this.db.artifact.findFirst({
|
|
69
|
-
// where: {
|
|
70
|
-
// id: worksheetId,
|
|
71
|
-
// type: 'WORKSHEET',
|
|
72
|
-
// workspace: { ownerId: userId },
|
|
73
|
-
// },
|
|
74
|
-
// include: {
|
|
75
|
-
// questions: {
|
|
76
|
-
// orderBy: { order: 'asc' }
|
|
77
|
-
// },
|
|
78
|
-
// },
|
|
79
|
-
// });
|
|
80
|
-
// if (!worksheet) {
|
|
81
|
-
// throw new NotFoundError('Worksheet');
|
|
82
|
-
// }
|
|
83
|
-
// // Load user progress
|
|
84
|
-
// const progress = await this.db.worksheetQuestionProgress.findMany({
|
|
85
|
-
// where: {
|
|
86
|
-
// userId,
|
|
87
|
-
// worksheetQuestionId: {
|
|
88
|
-
// in: worksheet.questions.map(q => q.id)
|
|
89
|
-
// },
|
|
90
|
-
// },
|
|
91
|
-
// });
|
|
92
|
-
// const progressByQuestionId = new Map(
|
|
93
|
-
// progress.map(p => [p.worksheetQuestionId, p])
|
|
94
|
-
// );
|
|
95
|
-
// return {
|
|
96
|
-
// ...worksheet,
|
|
97
|
-
// questions: worksheet.questions.map(question => {
|
|
98
|
-
// const userProgress = progressByQuestionId.get(question.id);
|
|
99
|
-
// const existingMeta = this.parseMeta(question.meta);
|
|
100
|
-
// return {
|
|
101
|
-
// ...question,
|
|
102
|
-
// meta: {
|
|
103
|
-
// ...existingMeta,
|
|
104
|
-
// completed: userProgress?.modified || false,
|
|
105
|
-
// userAnswer: userProgress?.userAnswer,
|
|
106
|
-
// completedAt: userProgress?.completedAt,
|
|
107
|
-
// },
|
|
108
|
-
// };
|
|
109
|
-
// }),
|
|
110
|
-
// };
|
|
111
|
-
// }
|
|
112
|
-
// /**
|
|
113
|
-
// * Create a new worksheet
|
|
114
|
-
// */
|
|
115
|
-
// async createWorksheet(data: {
|
|
116
|
-
// workspaceId: string;
|
|
117
|
-
// title: string;
|
|
118
|
-
// description?: string;
|
|
119
|
-
// userId: string;
|
|
120
|
-
// problems?: Array<{
|
|
121
|
-
// question: string;
|
|
122
|
-
// answer: string;
|
|
123
|
-
// type: QuestionTypeEnum;
|
|
124
|
-
// options?: string[];
|
|
125
|
-
// }>;
|
|
126
|
-
// }) {
|
|
127
|
-
// // Verify workspace ownership
|
|
128
|
-
// const workspace = await this.db.workspace.findFirst({
|
|
129
|
-
// where: {
|
|
130
|
-
// id: data.workspaceId,
|
|
131
|
-
// ownerId: data.userId
|
|
132
|
-
// },
|
|
133
|
-
// });
|
|
134
|
-
// if (!workspace) {
|
|
135
|
-
// throw new NotFoundError('Workspace');
|
|
136
|
-
// }
|
|
137
|
-
// const { problems, ...worksheetData } = data;
|
|
138
|
-
// return this.db.artifact.create({
|
|
139
|
-
// data: {
|
|
140
|
-
// workspaceId: data.workspaceId,
|
|
141
|
-
// type: 'WORKSHEET',
|
|
142
|
-
// title: data.title,
|
|
143
|
-
// createdById: data.userId,
|
|
144
|
-
// questions: problems ? {
|
|
145
|
-
// create: problems.map((problem, index) => ({
|
|
146
|
-
// prompt: problem.question,
|
|
147
|
-
// answer: problem.answer,
|
|
148
|
-
// type: problem.type,
|
|
149
|
-
// order: index,
|
|
150
|
-
// meta: problem.options ? { options: problem.options } : undefined,
|
|
151
|
-
// })),
|
|
152
|
-
// } : undefined,
|
|
153
|
-
// },
|
|
154
|
-
// include: {
|
|
155
|
-
// questions: {
|
|
156
|
-
// orderBy: { order: 'asc' }
|
|
157
|
-
// },
|
|
158
|
-
// },
|
|
159
|
-
// });
|
|
160
|
-
// }
|
|
161
|
-
// /**
|
|
162
|
-
// * Update a worksheet
|
|
163
|
-
// */
|
|
164
|
-
// async updateWorksheet(data: {
|
|
165
|
-
// id: string;
|
|
166
|
-
// title?: string;
|
|
167
|
-
// description?: string;
|
|
168
|
-
// userId: string;
|
|
169
|
-
// problems?: Array<{
|
|
170
|
-
// id?: string;
|
|
171
|
-
// question: string;
|
|
172
|
-
// answer: string;
|
|
173
|
-
// type: QuestionTypeEnum;
|
|
174
|
-
// options?: string[];
|
|
175
|
-
// }>;
|
|
176
|
-
// }) {
|
|
177
|
-
// const { id, problems, userId, ...updateData } = data;
|
|
178
|
-
// // Verify ownership
|
|
179
|
-
// const existingWorksheet = await this.db.artifact.findFirst({
|
|
180
|
-
// where: {
|
|
181
|
-
// id,
|
|
182
|
-
// type: 'WORKSHEET',
|
|
183
|
-
// workspace: { ownerId: userId },
|
|
184
|
-
// },
|
|
185
|
-
// });
|
|
186
|
-
// if (!existingWorksheet) {
|
|
187
|
-
// throw new NotFoundError('Worksheet');
|
|
188
|
-
// }
|
|
189
|
-
// // Handle questions update if provided
|
|
190
|
-
// if (problems) {
|
|
191
|
-
// // Delete existing questions
|
|
192
|
-
// await this.db.worksheetQuestion.deleteMany({
|
|
193
|
-
// where: { artifactId: id },
|
|
194
|
-
// });
|
|
195
|
-
// // Create new questions
|
|
196
|
-
// await this.db.worksheetQuestion.createMany({
|
|
197
|
-
// data: problems.map((problem, index) => ({
|
|
198
|
-
// artifactId: id,
|
|
199
|
-
// prompt: problem.question,
|
|
200
|
-
// answer: problem.answer,
|
|
201
|
-
// type: problem.type,
|
|
202
|
-
// order: index,
|
|
203
|
-
// meta: problem.options ? { options: problem.options } : undefined,
|
|
204
|
-
// })),
|
|
205
|
-
// });
|
|
206
|
-
// }
|
|
207
|
-
// return this.db.artifact.update({
|
|
208
|
-
// where: { id },
|
|
209
|
-
// data: updateData,
|
|
210
|
-
// include: {
|
|
211
|
-
// questions: {
|
|
212
|
-
// orderBy: { order: 'asc' },
|
|
213
|
-
// },
|
|
214
|
-
// },
|
|
215
|
-
// });
|
|
216
|
-
// }
|
|
217
|
-
// /**
|
|
218
|
-
// * Delete a worksheet
|
|
219
|
-
// */
|
|
220
|
-
// async deleteWorksheet(worksheetId: string, userId: string) {
|
|
221
|
-
// const deleted = await this.db.artifact.deleteMany({
|
|
222
|
-
// where: {
|
|
223
|
-
// id: worksheetId,
|
|
224
|
-
// type: 'WORKSHEET',
|
|
225
|
-
// workspace: { ownerId: userId }
|
|
226
|
-
// },
|
|
227
|
-
// });
|
|
228
|
-
// if (deleted.count === 0) {
|
|
229
|
-
// throw new NotFoundError('Worksheet');
|
|
230
|
-
// }
|
|
231
|
-
// return { success: true };
|
|
232
|
-
// }
|
|
233
|
-
// /**
|
|
234
|
-
// * Update question progress for a user
|
|
235
|
-
// */
|
|
236
|
-
// async updateQuestionProgress(data: {
|
|
237
|
-
// questionId: string;
|
|
238
|
-
// userId: string;
|
|
239
|
-
// completed: boolean;
|
|
240
|
-
// answer?: string;
|
|
241
|
-
// }) {
|
|
242
|
-
// // Verify question ownership
|
|
243
|
-
// const question = await this.db.worksheetQuestion.findFirst({
|
|
244
|
-
// where: {
|
|
245
|
-
// id: data.questionId,
|
|
246
|
-
// artifact: {
|
|
247
|
-
// type: 'WORKSHEET',
|
|
248
|
-
// workspace: { ownerId: data.userId },
|
|
249
|
-
// },
|
|
250
|
-
// },
|
|
251
|
-
// });
|
|
252
|
-
// if (!question) {
|
|
253
|
-
// throw new NotFoundError('Question');
|
|
254
|
-
// }
|
|
255
|
-
// // Upsert progress
|
|
256
|
-
// return this.db.worksheetQuestionProgress.upsert({
|
|
257
|
-
// where: {
|
|
258
|
-
// worksheetQuestionId_userId: {
|
|
259
|
-
// userId: data.userId,
|
|
260
|
-
// worksheetQuestionId: data.questionId,
|
|
261
|
-
// },
|
|
262
|
-
// },
|
|
263
|
-
// update: {
|
|
264
|
-
// modified: data.completed,
|
|
265
|
-
// userAnswer: data.answer,
|
|
266
|
-
// completedAt: data.completed ? new Date() : null,
|
|
267
|
-
// },
|
|
268
|
-
// create: {
|
|
269
|
-
// userId: data.userId,
|
|
270
|
-
// worksheetQuestionId: data.questionId,
|
|
271
|
-
// modified: data.completed,
|
|
272
|
-
// userAnswer: data.answer,
|
|
273
|
-
// completedAt: data.completed ? new Date() : null,
|
|
274
|
-
// },
|
|
275
|
-
// });
|
|
276
|
-
// }
|
|
277
|
-
// /**
|
|
278
|
-
// * Helper to parse meta field safely
|
|
279
|
-
// */
|
|
280
|
-
// private parseMeta(meta: any): WorksheetQuestionMeta {
|
|
281
|
-
// if (!meta) return {};
|
|
282
|
-
// if (typeof meta === 'object') return meta;
|
|
283
|
-
// try {
|
|
284
|
-
// return JSON.parse(meta.toString());
|
|
285
|
-
// } catch {
|
|
286
|
-
// return {};
|
|
287
|
-
// }
|
|
288
|
-
// }
|
|
289
|
-
// }
|
|
290
|
-
// /**
|
|
291
|
-
// * Factory function to create worksheet service
|
|
292
|
-
// */
|
|
293
|
-
// export function createWorksheetService(db: PrismaClient) {
|
|
294
|
-
// return new WorksheetService(db);
|
|
295
|
-
// }
|