@goscribe/server 1.1.2 → 1.1.4

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 (36) hide show
  1. package/dist/lib/ai-session.d.ts +13 -3
  2. package/dist/lib/ai-session.js +66 -146
  3. package/dist/lib/pusher.js +1 -1
  4. package/dist/routers/_app.d.ts +114 -7
  5. package/dist/routers/chat.js +2 -23
  6. package/dist/routers/flashcards.d.ts +25 -1
  7. package/dist/routers/flashcards.js +0 -14
  8. package/dist/routers/members.d.ts +18 -0
  9. package/dist/routers/members.js +14 -1
  10. package/dist/routers/worksheets.js +5 -4
  11. package/dist/routers/workspace.d.ts +89 -6
  12. package/dist/routers/workspace.js +389 -259
  13. package/dist/services/flashcard-progress.service.d.ts +25 -1
  14. package/dist/services/flashcard-progress.service.js +70 -31
  15. package/package.json +3 -2
  16. package/prisma/schema.prisma +196 -184
  17. package/src/lib/ai-session.ts +3 -21
  18. package/src/routers/auth.ts +50 -2
  19. package/src/routers/flashcards.ts +0 -16
  20. package/src/routers/members.ts +27 -6
  21. package/src/routers/workspace.ts +468 -439
  22. package/src/server.ts +13 -0
  23. package/ANALYSIS_PROGRESS_SPEC.md +0 -463
  24. package/PROGRESS_QUICK_REFERENCE.md +0 -239
  25. package/dist/lib/podcast-prompts.d.ts +0 -43
  26. package/dist/lib/podcast-prompts.js +0 -135
  27. package/dist/routers/ai-session.d.ts +0 -0
  28. package/dist/routers/ai-session.js +0 -1
  29. package/dist/services/flashcard.service.d.ts +0 -183
  30. package/dist/services/flashcard.service.js +0 -224
  31. package/dist/services/podcast-segment-reorder.d.ts +0 -0
  32. package/dist/services/podcast-segment-reorder.js +0 -107
  33. package/dist/services/podcast.service.d.ts +0 -0
  34. package/dist/services/podcast.service.js +0 -326
  35. package/dist/services/worksheet.service.d.ts +0 -0
  36. 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
- // }