@goscribe/server 1.0.11 → 1.1.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 (83) hide show
  1. package/ANALYSIS_PROGRESS_SPEC.md +463 -0
  2. package/PROGRESS_QUICK_REFERENCE.md +239 -0
  3. package/dist/lib/ai-session.d.ts +20 -9
  4. package/dist/lib/ai-session.js +316 -80
  5. package/dist/lib/auth.d.ts +35 -2
  6. package/dist/lib/auth.js +88 -15
  7. package/dist/lib/env.d.ts +32 -0
  8. package/dist/lib/env.js +46 -0
  9. package/dist/lib/errors.d.ts +33 -0
  10. package/dist/lib/errors.js +78 -0
  11. package/dist/lib/inference.d.ts +4 -1
  12. package/dist/lib/inference.js +9 -11
  13. package/dist/lib/logger.d.ts +62 -0
  14. package/dist/lib/logger.js +342 -0
  15. package/dist/lib/podcast-prompts.d.ts +43 -0
  16. package/dist/lib/podcast-prompts.js +135 -0
  17. package/dist/lib/pusher.d.ts +1 -0
  18. package/dist/lib/pusher.js +14 -2
  19. package/dist/lib/storage.d.ts +3 -3
  20. package/dist/lib/storage.js +51 -47
  21. package/dist/lib/validation.d.ts +51 -0
  22. package/dist/lib/validation.js +64 -0
  23. package/dist/routers/_app.d.ts +697 -111
  24. package/dist/routers/_app.js +5 -0
  25. package/dist/routers/auth.d.ts +11 -1
  26. package/dist/routers/chat.d.ts +11 -1
  27. package/dist/routers/flashcards.d.ts +205 -6
  28. package/dist/routers/flashcards.js +144 -66
  29. package/dist/routers/members.d.ts +165 -0
  30. package/dist/routers/members.js +531 -0
  31. package/dist/routers/podcast.d.ts +78 -63
  32. package/dist/routers/podcast.js +330 -393
  33. package/dist/routers/studyguide.d.ts +11 -1
  34. package/dist/routers/worksheets.d.ts +124 -13
  35. package/dist/routers/worksheets.js +123 -50
  36. package/dist/routers/workspace.d.ts +213 -26
  37. package/dist/routers/workspace.js +303 -181
  38. package/dist/server.js +12 -4
  39. package/dist/services/flashcard-progress.service.d.ts +183 -0
  40. package/dist/services/flashcard-progress.service.js +383 -0
  41. package/dist/services/flashcard.service.d.ts +183 -0
  42. package/dist/services/flashcard.service.js +224 -0
  43. package/dist/services/podcast-segment-reorder.d.ts +0 -0
  44. package/dist/services/podcast-segment-reorder.js +107 -0
  45. package/dist/services/podcast.service.d.ts +0 -0
  46. package/dist/services/podcast.service.js +326 -0
  47. package/dist/services/worksheet.service.d.ts +0 -0
  48. package/dist/services/worksheet.service.js +295 -0
  49. package/dist/trpc.d.ts +13 -2
  50. package/dist/trpc.js +55 -6
  51. package/dist/types/index.d.ts +126 -0
  52. package/dist/types/index.js +1 -0
  53. package/package.json +3 -2
  54. package/prisma/schema.prisma +142 -4
  55. package/src/lib/ai-session.ts +356 -85
  56. package/src/lib/auth.ts +113 -19
  57. package/src/lib/env.ts +59 -0
  58. package/src/lib/errors.ts +92 -0
  59. package/src/lib/inference.ts +11 -11
  60. package/src/lib/logger.ts +405 -0
  61. package/src/lib/pusher.ts +15 -3
  62. package/src/lib/storage.ts +56 -51
  63. package/src/lib/validation.ts +75 -0
  64. package/src/routers/_app.ts +5 -0
  65. package/src/routers/chat.ts +2 -23
  66. package/src/routers/flashcards.ts +108 -24
  67. package/src/routers/members.ts +586 -0
  68. package/src/routers/podcast.ts +385 -420
  69. package/src/routers/worksheets.ts +117 -35
  70. package/src/routers/workspace.ts +328 -195
  71. package/src/server.ts +13 -4
  72. package/src/services/flashcard-progress.service.ts +541 -0
  73. package/src/trpc.ts +59 -6
  74. package/src/types/index.ts +165 -0
  75. package/AUTH_FRONTEND_SPEC.md +0 -21
  76. package/CHAT_FRONTEND_SPEC.md +0 -474
  77. package/DATABASE_SETUP.md +0 -165
  78. package/MEETINGSUMMARY_FRONTEND_SPEC.md +0 -28
  79. package/PODCAST_FRONTEND_SPEC.md +0 -595
  80. package/STUDYGUIDE_FRONTEND_SPEC.md +0 -18
  81. package/WORKSHEETS_FRONTEND_SPEC.md +0 -26
  82. package/WORKSPACE_FRONTEND_SPEC.md +0 -47
  83. package/test-ai-integration.js +0 -134
@@ -0,0 +1,183 @@
1
+ import type { PrismaClient } from '@prisma/client';
2
+ import type { CreateFlashcardInput } from '../types/index.js';
3
+ export declare class FlashcardService {
4
+ private db;
5
+ constructor(db: PrismaClient);
6
+ jsonToFlashcards(json: string): Promise<any>;
7
+ /**
8
+ * List all flashcard sets for a workspace
9
+ */
10
+ listFlashcardSets(workspaceId: string, userId: string): Promise<({
11
+ flashcards: {
12
+ id: string;
13
+ createdAt: Date;
14
+ artifactId: string;
15
+ order: number;
16
+ front: string;
17
+ back: string;
18
+ tags: string[];
19
+ }[];
20
+ } & {
21
+ id: string;
22
+ createdAt: Date;
23
+ updatedAt: Date;
24
+ title: string;
25
+ description: string | null;
26
+ workspaceId: string;
27
+ type: import("@prisma/client").$Enums.ArtifactType;
28
+ isArchived: boolean;
29
+ generating: boolean;
30
+ generatingMetadata: import("@prisma/client/runtime/library").JsonValue | null;
31
+ difficulty: import("@prisma/client").$Enums.Difficulty | null;
32
+ estimatedTime: string | null;
33
+ imageObjectKey: string | null;
34
+ createdById: string | null;
35
+ })[]>;
36
+ /**
37
+ * Get a single flashcard set
38
+ */
39
+ getFlashcardSet(setId: string, userId: string): Promise<{
40
+ flashcards: {
41
+ id: string;
42
+ createdAt: Date;
43
+ artifactId: string;
44
+ order: number;
45
+ front: string;
46
+ back: string;
47
+ tags: string[];
48
+ }[];
49
+ } & {
50
+ id: string;
51
+ createdAt: Date;
52
+ updatedAt: Date;
53
+ title: string;
54
+ description: string | null;
55
+ workspaceId: string;
56
+ type: import("@prisma/client").$Enums.ArtifactType;
57
+ isArchived: boolean;
58
+ generating: boolean;
59
+ generatingMetadata: import("@prisma/client/runtime/library").JsonValue | null;
60
+ difficulty: import("@prisma/client").$Enums.Difficulty | null;
61
+ estimatedTime: string | null;
62
+ imageObjectKey: string | null;
63
+ createdById: string | null;
64
+ }>;
65
+ /**
66
+ * Create a new flashcard set
67
+ */
68
+ createFlashcardSet(data: {
69
+ workspaceId: string;
70
+ title: string;
71
+ userId: string;
72
+ flashcards?: CreateFlashcardInput[];
73
+ }): Promise<{
74
+ flashcards: {
75
+ id: string;
76
+ createdAt: Date;
77
+ artifactId: string;
78
+ order: number;
79
+ front: string;
80
+ back: string;
81
+ tags: string[];
82
+ }[];
83
+ } & {
84
+ id: string;
85
+ createdAt: Date;
86
+ updatedAt: Date;
87
+ title: string;
88
+ description: string | null;
89
+ workspaceId: string;
90
+ type: import("@prisma/client").$Enums.ArtifactType;
91
+ isArchived: boolean;
92
+ generating: boolean;
93
+ generatingMetadata: import("@prisma/client/runtime/library").JsonValue | null;
94
+ difficulty: import("@prisma/client").$Enums.Difficulty | null;
95
+ estimatedTime: string | null;
96
+ imageObjectKey: string | null;
97
+ createdById: string | null;
98
+ }>;
99
+ /**
100
+ * Update a flashcard set
101
+ */
102
+ updateFlashcardSet(data: {
103
+ id: string;
104
+ title?: string;
105
+ userId: string;
106
+ flashcards?: (CreateFlashcardInput & {
107
+ id?: string;
108
+ })[];
109
+ }): Promise<{
110
+ flashcards: {
111
+ id: string;
112
+ createdAt: Date;
113
+ artifactId: string;
114
+ order: number;
115
+ front: string;
116
+ back: string;
117
+ tags: string[];
118
+ }[];
119
+ } & {
120
+ id: string;
121
+ createdAt: Date;
122
+ updatedAt: Date;
123
+ title: string;
124
+ description: string | null;
125
+ workspaceId: string;
126
+ type: import("@prisma/client").$Enums.ArtifactType;
127
+ isArchived: boolean;
128
+ generating: boolean;
129
+ generatingMetadata: import("@prisma/client/runtime/library").JsonValue | null;
130
+ difficulty: import("@prisma/client").$Enums.Difficulty | null;
131
+ estimatedTime: string | null;
132
+ imageObjectKey: string | null;
133
+ createdById: string | null;
134
+ }>;
135
+ /**
136
+ * Delete a flashcard set
137
+ */
138
+ deleteFlashcardSet(setId: string, userId: string): Promise<{
139
+ success: boolean;
140
+ }>;
141
+ /**
142
+ * Add a flashcard to a set
143
+ */
144
+ addFlashcard(data: {
145
+ setId: string;
146
+ userId: string;
147
+ flashcard: CreateFlashcardInput;
148
+ }): Promise<{
149
+ id: string;
150
+ createdAt: Date;
151
+ artifactId: string;
152
+ order: number;
153
+ front: string;
154
+ back: string;
155
+ tags: string[];
156
+ }>;
157
+ /**
158
+ * Update a flashcard
159
+ */
160
+ updateFlashcard(data: {
161
+ flashcardId: string;
162
+ userId: string;
163
+ updates: Partial<CreateFlashcardInput>;
164
+ }): Promise<{
165
+ id: string;
166
+ createdAt: Date;
167
+ artifactId: string;
168
+ order: number;
169
+ front: string;
170
+ back: string;
171
+ tags: string[];
172
+ }>;
173
+ /**
174
+ * Delete a flashcard
175
+ */
176
+ deleteFlashcard(flashcardId: string, userId: string): Promise<{
177
+ success: boolean;
178
+ }>;
179
+ }
180
+ /**
181
+ * Factory function to create flashcard service
182
+ */
183
+ export declare function createFlashcardService(db: PrismaClient): FlashcardService;
@@ -0,0 +1,224 @@
1
+ import { NotFoundError } from '../lib/errors.js';
2
+ export class FlashcardService {
3
+ constructor(db) {
4
+ this.db = db;
5
+ }
6
+ async jsonToFlashcards(json) {
7
+ const flashcards = JSON.parse(json);
8
+ return flashcards.map((card) => ({
9
+ front: card.front,
10
+ back: card.back,
11
+ }));
12
+ }
13
+ /**
14
+ * List all flashcard sets for a workspace
15
+ */
16
+ async listFlashcardSets(workspaceId, userId) {
17
+ return this.db.artifact.findMany({
18
+ where: {
19
+ workspaceId,
20
+ type: 'FLASHCARD_SET',
21
+ workspace: { ownerId: userId },
22
+ },
23
+ include: {
24
+ flashcards: {
25
+ orderBy: { order: 'asc' },
26
+ },
27
+ },
28
+ orderBy: { updatedAt: 'desc' },
29
+ });
30
+ }
31
+ /**
32
+ * Get a single flashcard set
33
+ */
34
+ async getFlashcardSet(setId, userId) {
35
+ const flashcardSet = await this.db.artifact.findFirst({
36
+ where: {
37
+ id: setId,
38
+ type: 'FLASHCARD_SET',
39
+ workspace: { ownerId: userId },
40
+ },
41
+ include: {
42
+ flashcards: {
43
+ orderBy: { order: 'asc' },
44
+ },
45
+ },
46
+ });
47
+ if (!flashcardSet) {
48
+ throw new NotFoundError('Flashcard set');
49
+ }
50
+ return flashcardSet;
51
+ }
52
+ /**
53
+ * Create a new flashcard set
54
+ */
55
+ async createFlashcardSet(data) {
56
+ // Verify workspace ownership
57
+ const workspace = await this.db.workspace.findFirst({
58
+ where: {
59
+ id: data.workspaceId,
60
+ ownerId: data.userId,
61
+ },
62
+ });
63
+ if (!workspace) {
64
+ throw new NotFoundError('Workspace');
65
+ }
66
+ const { flashcards, ...setData } = data;
67
+ return this.db.artifact.create({
68
+ data: {
69
+ workspaceId: data.workspaceId,
70
+ type: 'FLASHCARD_SET',
71
+ title: data.title,
72
+ createdById: data.userId,
73
+ flashcards: flashcards
74
+ ? {
75
+ create: flashcards.map((card, index) => ({
76
+ ...card,
77
+ order: card.order ?? index,
78
+ })),
79
+ }
80
+ : undefined,
81
+ },
82
+ include: {
83
+ flashcards: {
84
+ orderBy: { order: 'asc' },
85
+ },
86
+ },
87
+ });
88
+ }
89
+ /**
90
+ * Update a flashcard set
91
+ */
92
+ async updateFlashcardSet(data) {
93
+ const { id, flashcards, userId, ...updateData } = data;
94
+ // Verify ownership
95
+ const existingSet = await this.db.artifact.findFirst({
96
+ where: {
97
+ id,
98
+ type: 'FLASHCARD_SET',
99
+ workspace: { ownerId: userId },
100
+ },
101
+ });
102
+ if (!existingSet) {
103
+ throw new NotFoundError('Flashcard set');
104
+ }
105
+ // Handle flashcards update if provided
106
+ if (flashcards) {
107
+ // Delete existing flashcards
108
+ await this.db.flashcard.deleteMany({
109
+ where: { artifactId: id },
110
+ });
111
+ // Create new flashcards
112
+ await this.db.flashcard.createMany({
113
+ data: flashcards.map((card, index) => ({
114
+ artifactId: id,
115
+ front: card.front,
116
+ back: card.back,
117
+ tags: card.tags || [],
118
+ order: card.order ?? index,
119
+ })),
120
+ });
121
+ }
122
+ return this.db.artifact.update({
123
+ where: { id },
124
+ data: updateData,
125
+ include: {
126
+ flashcards: {
127
+ orderBy: { order: 'asc' },
128
+ },
129
+ },
130
+ });
131
+ }
132
+ /**
133
+ * Delete a flashcard set
134
+ */
135
+ async deleteFlashcardSet(setId, userId) {
136
+ const deleted = await this.db.artifact.deleteMany({
137
+ where: {
138
+ id: setId,
139
+ type: 'FLASHCARD_SET',
140
+ workspace: { ownerId: userId },
141
+ },
142
+ });
143
+ if (deleted.count === 0) {
144
+ throw new NotFoundError('Flashcard set');
145
+ }
146
+ return { success: true };
147
+ }
148
+ /**
149
+ * Add a flashcard to a set
150
+ */
151
+ async addFlashcard(data) {
152
+ // Verify ownership
153
+ const set = await this.db.artifact.findFirst({
154
+ where: {
155
+ id: data.setId,
156
+ type: 'FLASHCARD_SET',
157
+ workspace: { ownerId: data.userId },
158
+ },
159
+ });
160
+ if (!set) {
161
+ throw new NotFoundError('Flashcard set');
162
+ }
163
+ // Get the next order number
164
+ const maxOrder = await this.db.flashcard.aggregate({
165
+ where: { artifactId: data.setId },
166
+ _max: { order: true },
167
+ });
168
+ return this.db.flashcard.create({
169
+ data: {
170
+ artifactId: data.setId,
171
+ front: data.flashcard.front,
172
+ back: data.flashcard.back,
173
+ tags: data.flashcard.tags || [],
174
+ order: data.flashcard.order ?? (maxOrder._max.order ?? 0) + 1,
175
+ },
176
+ });
177
+ }
178
+ /**
179
+ * Update a flashcard
180
+ */
181
+ async updateFlashcard(data) {
182
+ // Verify ownership
183
+ const flashcard = await this.db.flashcard.findFirst({
184
+ where: {
185
+ id: data.flashcardId,
186
+ artifact: {
187
+ type: 'FLASHCARD_SET',
188
+ workspace: { ownerId: data.userId },
189
+ },
190
+ },
191
+ });
192
+ if (!flashcard) {
193
+ throw new NotFoundError('Flashcard');
194
+ }
195
+ return this.db.flashcard.update({
196
+ where: { id: data.flashcardId },
197
+ data: data.updates,
198
+ });
199
+ }
200
+ /**
201
+ * Delete a flashcard
202
+ */
203
+ async deleteFlashcard(flashcardId, userId) {
204
+ const flashcard = await this.db.flashcard.findFirst({
205
+ where: {
206
+ id: flashcardId,
207
+ artifact: { workspace: { ownerId: userId } },
208
+ },
209
+ });
210
+ if (!flashcard) {
211
+ throw new NotFoundError('Flashcard');
212
+ }
213
+ await this.db.flashcard.delete({
214
+ where: { id: flashcardId },
215
+ });
216
+ return { success: true };
217
+ }
218
+ }
219
+ /**
220
+ * Factory function to create flashcard service
221
+ */
222
+ export function createFlashcardService(db) {
223
+ return new FlashcardService(db);
224
+ }
File without changes
@@ -0,0 +1,107 @@
1
+ "use strict";
2
+ // import type { PrismaClient } from '@prisma/client';
3
+ // import { NotFoundError } from '../lib/errors.js';
4
+ // export interface ReorderSegmentData {
5
+ // id: string;
6
+ // newOrder: number;
7
+ // }
8
+ // export class PodcastSegmentReorderService {
9
+ // constructor(private db: PrismaClient) {}
10
+ // /**
11
+ // * Reorder podcast segments and recalculate start times
12
+ // */
13
+ // async reorderSegments(data: {
14
+ // episodeId: string;
15
+ // userId: string;
16
+ // newOrder: ReorderSegmentData[];
17
+ // }) {
18
+ // const { episodeId, userId, newOrder } = data;
19
+ // // Verify ownership
20
+ // const episode = await this.db.artifact.findFirst({
21
+ // where: {
22
+ // id: episodeId,
23
+ // type: 'PODCAST_EPISODE',
24
+ // workspace: { ownerId: userId },
25
+ // },
26
+ // include: {
27
+ // podcastSegments: {
28
+ // orderBy: { order: 'asc' },
29
+ // },
30
+ // },
31
+ // });
32
+ // if (!episode) {
33
+ // throw new NotFoundError('Podcast episode');
34
+ // }
35
+ // // Validate all segment IDs exist
36
+ // const segmentIds = episode.podcastSegments.map((s) => s.id);
37
+ // const invalidIds = newOrder.filter((item) => !segmentIds.includes(item.id));
38
+ // if (invalidIds.length > 0) {
39
+ // throw new Error(`Invalid segment IDs: ${invalidIds.map((i) => i.id).join(', ')}`);
40
+ // }
41
+ // // Validate order values are sequential
42
+ // const orderValues = newOrder.map((item) => item.newOrder).sort((a, b) => a - b);
43
+ // const expectedOrder = Array.from({ length: newOrder.length }, (_, i) => i + 1);
44
+ // if (JSON.stringify(orderValues) !== JSON.stringify(expectedOrder)) {
45
+ // throw new Error('Order values must be sequential starting from 1');
46
+ // }
47
+ // return this.db.$transaction(async (tx) => {
48
+ // // Update each segment's order
49
+ // for (const item of newOrder) {
50
+ // await tx.podcastSegment.update({
51
+ // where: { id: item.id },
52
+ // data: { order: item.newOrder },
53
+ // });
54
+ // }
55
+ // // Get all segments in new order
56
+ // const reorderedSegments = await tx.podcastSegment.findMany({
57
+ // where: { artifactId: episodeId },
58
+ // orderBy: { order: 'asc' },
59
+ // });
60
+ // // Recalculate start times
61
+ // let currentTime = 0;
62
+ // for (const segment of reorderedSegments) {
63
+ // await tx.podcastSegment.update({
64
+ // where: { id: segment.id },
65
+ // data: { startTime: currentTime },
66
+ // });
67
+ // currentTime += segment.duration;
68
+ // }
69
+ // // Update total duration in latest version
70
+ // const latestVersion = await tx.artifactVersion.findFirst({
71
+ // where: { artifactId: episodeId },
72
+ // orderBy: { version: 'desc' },
73
+ // });
74
+ // if (latestVersion) {
75
+ // const metadata = latestVersion.data as any;
76
+ // if (metadata) {
77
+ // metadata.totalDuration = currentTime;
78
+ // // Create new version with updated metadata
79
+ // await tx.artifactVersion.create({
80
+ // data: {
81
+ // artifactId: episodeId,
82
+ // version: latestVersion.version + 1,
83
+ // content: latestVersion.content,
84
+ // data: metadata,
85
+ // createdById: userId,
86
+ // },
87
+ // });
88
+ // }
89
+ // }
90
+ // // Update artifact timestamp
91
+ // await tx.artifact.update({
92
+ // where: { id: episodeId },
93
+ // data: { updatedAt: new Date() },
94
+ // });
95
+ // return {
96
+ // totalDuration: currentTime,
97
+ // segmentsReordered: reorderedSegments.length,
98
+ // };
99
+ // });
100
+ // }
101
+ // }
102
+ // /**
103
+ // * Factory function
104
+ // */
105
+ // export function createPodcastSegmentReorderService(db: PrismaClient) {
106
+ // return new PodcastSegmentReorderService(db);
107
+ // }
File without changes