@goscribe/server 1.1.7 → 1.2.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.
@@ -6,13 +6,9 @@ import inference from '../lib/inference.js';
6
6
  import { uploadToSupabase, generateSignedUrl, deleteFromSupabase } from '../lib/storage.js';
7
7
  import PusherService from '../lib/pusher.js';
8
8
  import { aiSessionService } from '../lib/ai-session.js';
9
-
10
- // Prisma enum values mapped manually to avoid type import issues in ESM
11
- const ArtifactType = {
12
- PODCAST_EPISODE: 'PODCAST_EPISODE',
13
- STUDY_GUIDE: 'STUDY_GUIDE',
14
- FLASHCARD_SET: 'FLASHCARD_SET',
15
- } as const;
9
+ import { ArtifactType } from '../lib/constants.js';
10
+ import { workspaceAccessFilter } from '../lib/workspace-access.js';
11
+ import { logger } from '../lib/logger.js';
16
12
 
17
13
  // Podcast segment schema
18
14
  const podcastSegmentSchema = z.object({
@@ -94,9 +90,9 @@ export const podcast = router({
94
90
  orderBy: { updatedAt: 'desc' },
95
91
  });
96
92
 
97
- console.log(`📻 Found ${artifacts.length} podcast artifacts`);
93
+ logger.debug(`Found ${artifacts.length} podcast artifacts`);
98
94
  artifacts.forEach((artifact, i) => {
99
- console.log(` Podcast ${i + 1}: "${artifact.title}" - ${artifact.podcastSegments.length} segments`);
95
+ logger.debug(` Podcast ${i + 1}: "${artifact.title}" - ${artifact.podcastSegments.length} segments`);
100
96
  });
101
97
 
102
98
  // Transform to include segments with fresh signed URLs
@@ -126,7 +122,7 @@ export const podcast = router({
126
122
  order: segment.order,
127
123
  };
128
124
  } catch (error) {
129
- console.error(`Failed to generate signed URL for segment ${segment.id}:`, error);
125
+ logger.error(`Failed to generate signed URL for segment ${segment.id}:`, error);
130
126
  return {
131
127
  id: segment.id,
132
128
  title: segment.title,
@@ -154,10 +150,10 @@ export const podcast = router({
154
150
  let metadata = null;
155
151
  if (latestVersion) {
156
152
  try {
157
- console.log(latestVersion.data)
153
+ logger.debug(latestVersion.data)
158
154
  metadata = podcastMetadataSchema.parse(latestVersion.data);
159
155
  } catch (error) {
160
- console.error('Failed to parse podcast metadata:', error);
156
+ logger.error('Failed to parse podcast metadata:', error);
161
157
  }
162
158
  }
163
159
 
@@ -191,7 +187,7 @@ export const podcast = router({
191
187
  where: {
192
188
  id: input.episodeId,
193
189
  type: ArtifactType.PODCAST_EPISODE,
194
- workspace: { ownerId: ctx.session.user.id }
190
+ workspace: workspaceAccessFilter(ctx.session.user.id)
195
191
  },
196
192
  include: {
197
193
  versions: {
@@ -204,18 +200,18 @@ export const podcast = router({
204
200
  },
205
201
  });
206
202
 
207
- console.log(episode)
203
+ logger.debug(episode)
208
204
 
209
205
  if (!episode) throw new TRPCError({ code: 'NOT_FOUND' });
210
206
 
211
207
  const latestVersion = episode.versions[0];
212
208
  if (!latestVersion) throw new TRPCError({ code: 'NOT_FOUND', message: 'No version found' });
213
209
 
214
- console.log(latestVersion)
210
+ logger.debug(latestVersion)
215
211
  try {
216
212
  const metadata = podcastMetadataSchema.parse(latestVersion.data);
217
213
  } catch (error) {
218
- console.error('Failed to parse podcast metadata:', error);
214
+ logger.error('Failed to parse podcast metadata:', error);
219
215
  }
220
216
  const metadata = podcastMetadataSchema.parse(latestVersion.data);
221
217
 
@@ -240,7 +236,7 @@ export const podcast = router({
240
236
  order: segment.order,
241
237
  };
242
238
  } catch (error) {
243
- console.error(`Failed to generate signed URL for segment ${segment.id}:`, error);
239
+ logger.error(`Failed to generate signed URL for segment ${segment.id}:`, error);
244
240
  return {
245
241
  id: segment.id,
246
242
  title: segment.title,
@@ -444,7 +440,7 @@ export const podcast = router({
444
440
 
445
441
  } catch (audioError) {
446
442
  const errorMessage = audioError instanceof Error ? audioError.message : 'Unknown error';
447
- console.error(`❌ Error generating audio for segment ${i + 1}:`, {
443
+ logger.error(`❌ Error generating audio for segment ${i + 1}:`, {
448
444
  title: segment.title,
449
445
  error: errorMessage,
450
446
  stack: audioError instanceof Error ? audioError.stack : undefined,
@@ -471,7 +467,7 @@ export const podcast = router({
471
467
 
472
468
  // Check if any segments were successfully generated
473
469
  if (segments.length === 0) {
474
- console.error('No segments were successfully generated');
470
+ logger.error('No segments were successfully generated');
475
471
  await PusherService.emitError(input.workspaceId,
476
472
  `Failed to generate any segments. ${failedSegments.length} segment(s) failed.`,
477
473
  'podcast'
@@ -539,7 +535,7 @@ export const podcast = router({
539
535
  }
540
536
  episodeSummary = JSON.parse(jsonMatch[0]);
541
537
  } catch (parseError) {
542
- console.error('Failed to parse summary response:', summaryContent);
538
+ logger.error('Failed to parse summary response:', summaryContent);
543
539
  await PusherService.emitTaskComplete(input.workspaceId, 'podcast_summary_error', {
544
540
  error: 'Failed to parse summary response'
545
541
  });
@@ -633,7 +629,7 @@ export const podcast = router({
633
629
 
634
630
  } catch (error) {
635
631
 
636
- console.error('Error generating podcast episode:', error);
632
+ logger.error('Error generating podcast episode:', error);
637
633
 
638
634
  await ctx.db.artifact.delete({
639
635
  where: {
@@ -663,7 +659,7 @@ export const podcast = router({
663
659
  where: {
664
660
  id: input.episodeId,
665
661
  type: ArtifactType.PODCAST_EPISODE,
666
- workspace: { ownerId: ctx.session.user.id }
662
+ workspace: workspaceAccessFilter(ctx.session.user.id)
667
663
  },
668
664
  include: {
669
665
  versions: {
@@ -714,7 +710,7 @@ export const podcast = router({
714
710
  where: {
715
711
  id: input.episodeId,
716
712
  type: ArtifactType.PODCAST_EPISODE,
717
- workspace: { ownerId: ctx.session.user.id }
713
+ workspace: workspaceAccessFilter(ctx.session.user.id)
718
714
  },
719
715
  include: {
720
716
  versions: {
@@ -766,7 +762,7 @@ export const podcast = router({
766
762
  where: {
767
763
  id: input.episodeId,
768
764
  type: ArtifactType.PODCAST_EPISODE,
769
- workspace: { ownerId: ctx.session.user.id }
765
+ workspace: workspaceAccessFilter(ctx.session.user.id)
770
766
  },
771
767
  include: {
772
768
  versions: {
@@ -796,7 +792,7 @@ export const podcast = router({
796
792
  try {
797
793
  await deleteFromSupabase(segment.objectKey);
798
794
  } catch (error) {
799
- console.error(`Failed to delete audio file ${segment.objectKey}:`, error);
795
+ logger.error(`Failed to delete audio file ${segment.objectKey}:`, error);
800
796
  }
801
797
  }
802
798
  }
@@ -825,7 +821,7 @@ export const podcast = router({
825
821
  return true;
826
822
 
827
823
  } catch (error) {
828
- console.error('Error deleting episode:', error);
824
+ logger.error('Error deleting episode:', error);
829
825
  await PusherService.emitError(episode.workspaceId, `Failed to delete episode: ${error instanceof Error ? error.message : 'Unknown error'}`, 'podcast');
830
826
  throw new TRPCError({
831
827
  code: 'INTERNAL_SERVER_ERROR',
@@ -842,7 +838,7 @@ export const podcast = router({
842
838
  where: {
843
839
  id: input.segmentId,
844
840
  artifact: {
845
- workspace: { ownerId: ctx.session.user.id }
841
+ workspace: workspaceAccessFilter(ctx.session.user.id)
846
842
  }
847
843
  },
848
844
  include: {
@@ -858,7 +854,7 @@ export const podcast = router({
858
854
  try {
859
855
  audioUrl = await generateSignedUrl(segment.objectKey, 24); // 24 hours
860
856
  } catch (error) {
861
- console.error(`Failed to generate signed URL for segment ${segment.id}:`, error);
857
+ logger.error(`Failed to generate signed URL for segment ${segment.id}:`, error);
862
858
  }
863
859
  }
864
860
 
@@ -1,12 +1,8 @@
1
1
  import { z } from 'zod';
2
2
  import { TRPCError } from '@trpc/server';
3
3
  import { router, authedProcedure } from '../trpc.js';
4
- import { title } from 'node:process';
5
-
6
- // Mirror Prisma enum to avoid direct type import
7
- const ArtifactType = {
8
- STUDY_GUIDE: 'STUDY_GUIDE',
9
- } as const;
4
+ import { ArtifactType } from '../lib/constants.js';
5
+ import { workspaceAccessFilter } from '../lib/workspace-access.js';
10
6
 
11
7
  const initializeEditorJsEmptyBlock = () => ({
12
8
  time: Date.now(),
@@ -14,7 +10,7 @@ const initializeEditorJsEmptyBlock = () => ({
14
10
  {
15
11
  id: 'initial',
16
12
  type: 'paragraph',
17
- data: { text: 'Start writing your study guide...' },
13
+ data: { text: 'Upload some files to begin creating your revision workspace...' },
18
14
  },
19
15
  ],
20
16
  version: '2.27.0',
@@ -34,14 +30,13 @@ export const studyguide = router({
34
30
  where: {
35
31
  workspaceId: input.workspaceId!,
36
32
  type: ArtifactType.STUDY_GUIDE,
37
- workspace: { ownerId: ctx.session.user.id },
33
+ workspace: workspaceAccessFilter(ctx.session.user.id),
38
34
  },
39
35
  include: {
40
36
  versions: { orderBy: { version: 'desc' }, take: 1 },
41
37
  },
42
38
  });
43
39
 
44
- console.log('artifact', artifact);
45
40
  if (!artifact) {
46
41
  artifact = await ctx.db.artifact.create({
47
42
  data: {
@@ -67,78 +62,77 @@ export const studyguide = router({
67
62
  }),
68
63
 
69
64
  // Edit study guide content by creating a new version, or create if doesn't exist
70
- edit: authedProcedure
71
- .input(
72
- z.object({
73
- workspaceId: z.string(),
74
- studyGuideId: z.string().optional(),
75
- content: z.string().min(1),
76
- data: z.record(z.string(), z.unknown()).optional(),
77
- title: z.string().min(1).optional(),
78
- })
79
- )
80
- .mutation(async ({ ctx, input }) => {
81
- let artifact;
65
+ // edit: authedProcedure
66
+ // .input(
67
+ // z.object({
68
+ // workspaceId: z.string(),
69
+ // studyGuideId: z.string().optional(),
70
+ // content: z.string().min(1),
71
+ // data: z.record(z.string(), z.unknown()).optional(),
72
+ // title: z.string().min(1).optional(),
73
+ // })
74
+ // )
75
+ // .mutation(async ({ ctx, input }) => {
76
+ // let artifact;
82
77
 
83
- if (input.studyGuideId) {
84
- // Try to find existing study guide
85
- artifact = await ctx.db.artifact.findFirst({
86
- where: {
87
- id: input.studyGuideId,
88
- type: ArtifactType.STUDY_GUIDE,
89
- workspace: { ownerId: ctx.session.user.id },
90
- },
91
- });
92
- } else {
93
- // Find by workspace if no specific studyGuideId provided
94
- artifact = await ctx.db.artifact.findFirst({
95
- where: {
96
- workspaceId: input.workspaceId,
97
- type: ArtifactType.STUDY_GUIDE,
98
- workspace: { ownerId: ctx.session.user.id },
99
- },
100
- });
101
- }
78
+ // if (input.studyGuideId) {
79
+ // // Try to find existing study guide
80
+ // artifact = await ctx.db.artifact.findFirst({
81
+ // where: {
82
+ // id: input.studyGuideId,
83
+ // type: ArtifactType.STUDY_GUIDE,
84
+ // workspace: workspaceAccessFilter(ctx.session.user.id),
85
+ // },
86
+ // });
87
+ // } else {
88
+ // // Find by workspace if no specific studyGuideId provided
89
+ // artifact = await ctx.db.artifact.findFirst({
90
+ // where: {
91
+ // workspaceId: input.workspaceId,
92
+ // type: ArtifactType.STUDY_GUIDE,
93
+ // workspace: workspaceAccessFilter(ctx.session.user.id),
94
+ // },
95
+ // });
96
+ // }
102
97
 
103
- // If no study guide found, create a new one
104
- if (!artifact) {
105
- artifact = await ctx.db.artifact.create({
106
- data: {
107
- workspaceId: input.workspaceId,
108
- type: ArtifactType.STUDY_GUIDE,
109
- title: 'Study Guide',
110
- createdById: ctx.session.user.id,
111
- },
112
- });
113
- }
98
+ // // If no study guide found, create a new one
99
+ // if (!artifact) {
100
+ // artifact = await ctx.db.artifact.create({
101
+ // data: {
102
+ // workspaceId: input.workspaceId,
103
+ // type: ArtifactType.STUDY_GUIDE,
104
+ // title: 'Study Guide',
105
+ // createdById: ctx.session.user.id,
106
+ // },
107
+ // });
108
+ // }
114
109
 
115
- const last = await ctx.db.artifactVersion.findFirst({
116
- where: { artifactId: artifact.id },
117
- orderBy: { version: 'desc' },
118
- });
110
+ // const last = await ctx.db.artifactVersion.findFirst({
111
+ // where: { artifactId: artifact.id },
112
+ // orderBy: { version: 'desc' },
113
+ // });
119
114
 
120
- if (input.title && input.title !== artifact.title) {
121
- console.log('rename')
122
- await ctx.db.artifact.update({
123
- where: { id: artifact.id },
124
- data: { title: input.title },
125
- });
126
- }
115
+ // if (input.title && input.title !== artifact.title) {
116
+ // await ctx.db.artifact.update({
117
+ // where: { id: artifact.id },
118
+ // data: { title: input.title },
119
+ // });
120
+ // }
127
121
 
128
- const nextVersion = (last?.version ?? 0) + 1;
122
+ // const nextVersion = (last?.version ?? 0) + 1;
129
123
 
130
- const version = await ctx.db.artifactVersion.create({
131
- data: {
132
- artifactId: artifact.id,
133
- content: input.content,
134
- data: input.data as any,
135
- version: nextVersion,
136
- createdById: ctx.session.user.id,
137
- },
138
- });
124
+ // const version = await ctx.db.artifactVersion.create({
125
+ // data: {
126
+ // artifactId: artifact.id,
127
+ // content: input.content,
128
+ // data: input.data ?? undefined,
129
+ // version: nextVersion,
130
+ // createdById: ctx.session.user.id,
131
+ // },
132
+ // });
139
133
 
140
- return { artifactId: artifact.id, version };
141
- }),
134
+ // return { artifactId: artifact.id, version };
135
+ // }),
142
136
  });
143
137
 
144
138
 
@@ -4,11 +4,8 @@ import { router, authedProcedure } from '../trpc.js';
4
4
  import { aiSessionService } from '../lib/ai-session.js';
5
5
  import PusherService from '../lib/pusher.js';
6
6
  import { logger } from '../lib/logger.js';
7
-
8
- // Avoid importing Prisma enums directly; mirror values as string literals
9
- const ArtifactType = {
10
- WORKSHEET: 'WORKSHEET',
11
- } as const;
7
+ import { ArtifactType } from '../lib/constants.js';
8
+ import { workspaceAccessFilter } from '../lib/workspace-access.js';
12
9
 
13
10
  const Difficulty = {
14
11
  EASY: 'EASY',
@@ -130,7 +127,7 @@ export const worksheets = router({
130
127
  where: {
131
128
  id: input.worksheetId,
132
129
  type: ArtifactType.WORKSHEET,
133
- workspace: { ownerId: ctx.session.user.id },
130
+ workspace: workspaceAccessFilter(ctx.session.user.id),
134
131
  },
135
132
  include: { questions: true },
136
133
  orderBy: { updatedAt: 'desc' },
@@ -181,7 +178,7 @@ export const worksheets = router({
181
178
  }))
182
179
  .mutation(async ({ ctx, input }) => {
183
180
  const worksheet = await ctx.db.artifact.findFirst({
184
- where: { id: input.worksheetId, type: ArtifactType.WORKSHEET, workspace: { ownerId: ctx.session.user.id } },
181
+ where: { id: input.worksheetId, type: ArtifactType.WORKSHEET, workspace: workspaceAccessFilter(ctx.session.user.id) },
185
182
  });
186
183
  if (!worksheet) throw new TRPCError({ code: 'NOT_FOUND' });
187
184
  return ctx.db.worksheetQuestion.create({
@@ -210,7 +207,7 @@ export const worksheets = router({
210
207
  }))
211
208
  .mutation(async ({ ctx, input }) => {
212
209
  const q = await ctx.db.worksheetQuestion.findFirst({
213
- where: { id: input.worksheetQuestionId, artifact: { type: ArtifactType.WORKSHEET, workspace: { ownerId: ctx.session.user.id } } },
210
+ where: { id: input.worksheetQuestionId, artifact: { type: ArtifactType.WORKSHEET, workspace: workspaceAccessFilter(ctx.session.user.id) } },
214
211
  });
215
212
  if (!q) throw new TRPCError({ code: 'NOT_FOUND' });
216
213
  return ctx.db.worksheetQuestion.update({
@@ -231,7 +228,7 @@ export const worksheets = router({
231
228
  .input(z.object({ worksheetQuestionId: z.string() }))
232
229
  .mutation(async ({ ctx, input }) => {
233
230
  const q = await ctx.db.worksheetQuestion.findFirst({
234
- where: { id: input.worksheetQuestionId, artifact: { workspace: { ownerId: ctx.session.user.id } } },
231
+ where: { id: input.worksheetQuestionId, artifact: { workspace: workspaceAccessFilter(ctx.session.user.id) } },
235
232
  });
236
233
  if (!q) throw new TRPCError({ code: 'NOT_FOUND' });
237
234
  await ctx.db.worksheetQuestion.delete({ where: { id: input.worksheetQuestionId } });
@@ -253,7 +250,7 @@ export const worksheets = router({
253
250
  id: input.problemId,
254
251
  artifact: {
255
252
  type: ArtifactType.WORKSHEET,
256
- workspace: { ownerId: ctx.session.user.id },
253
+ workspace: workspaceAccessFilter(ctx.session.user.id),
257
254
  },
258
255
  },
259
256
  });
@@ -297,7 +294,7 @@ export const worksheets = router({
297
294
  where: {
298
295
  id: input.worksheetId,
299
296
  type: ArtifactType.WORKSHEET,
300
- workspace: { ownerId: ctx.session.user.id },
297
+ workspace: workspaceAccessFilter(ctx.session.user.id),
301
298
  },
302
299
  });
303
300
  if (!worksheet) throw new TRPCError({ code: 'NOT_FOUND' });
@@ -342,7 +339,7 @@ export const worksheets = router({
342
339
  where: {
343
340
  id,
344
341
  type: ArtifactType.WORKSHEET,
345
- workspace: { ownerId: ctx.session.user.id },
342
+ workspace: workspaceAccessFilter(ctx.session.user.id),
346
343
  },
347
344
  });
348
345
  if (!existingWorksheet) throw new TRPCError({ code: 'NOT_FOUND' });
@@ -388,7 +385,7 @@ export const worksheets = router({
388
385
  .input(z.object({ id: z.string() }))
389
386
  .mutation(async ({ ctx, input }) => {
390
387
  const deleted = await ctx.db.artifact.deleteMany({
391
- where: { id: input.id, type: ArtifactType.WORKSHEET, workspace: { ownerId: ctx.session.user.id } },
388
+ where: { id: input.id, type: ArtifactType.WORKSHEET, workspace: workspaceAccessFilter(ctx.session.user.id) },
392
389
  });
393
390
  if (deleted.count === 0) throw new TRPCError({ code: 'NOT_FOUND' });
394
391
  return true;
@@ -484,7 +481,7 @@ export const worksheets = router({
484
481
  answer: z.string().min(1),
485
482
  }))
486
483
  .mutation(async ({ ctx, input }) => {
487
- const worksheet = await ctx.db.artifact.findFirst({ where: { id: input.worksheetId, type: ArtifactType.WORKSHEET, workspace: { ownerId: ctx.session.user.id } }, include: { workspace: true } });
484
+ const worksheet = await ctx.db.artifact.findFirst({ where: { id: input.worksheetId, type: ArtifactType.WORKSHEET, workspace: workspaceAccessFilter(ctx.session.user.id) }, include: { workspace: true } });
488
485
  if (!worksheet) throw new TRPCError({ code: 'NOT_FOUND' });
489
486
  const question = await ctx.db.worksheetQuestion.findFirst({ where: { id: input.questionId, artifactId: input.worksheetId } });
490
487
  if (!question) throw new TRPCError({ code: 'NOT_FOUND' });