@goscribe/server 1.0.7 → 1.0.9

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 (74) hide show
  1. package/AUTH_FRONTEND_SPEC.md +21 -0
  2. package/CHAT_FRONTEND_SPEC.md +474 -0
  3. package/MEETINGSUMMARY_FRONTEND_SPEC.md +28 -0
  4. package/PODCAST_FRONTEND_SPEC.md +595 -0
  5. package/STUDYGUIDE_FRONTEND_SPEC.md +18 -0
  6. package/WORKSHEETS_FRONTEND_SPEC.md +26 -0
  7. package/WORKSPACE_FRONTEND_SPEC.md +47 -0
  8. package/dist/context.d.ts +1 -1
  9. package/dist/lib/ai-session.d.ts +26 -0
  10. package/dist/lib/ai-session.js +343 -0
  11. package/dist/lib/auth.js +10 -6
  12. package/dist/lib/inference.d.ts +2 -0
  13. package/dist/lib/inference.js +21 -0
  14. package/dist/lib/pusher.d.ts +14 -0
  15. package/dist/lib/pusher.js +94 -0
  16. package/dist/lib/storage.d.ts +10 -2
  17. package/dist/lib/storage.js +63 -6
  18. package/dist/routers/_app.d.ts +878 -100
  19. package/dist/routers/_app.js +8 -2
  20. package/dist/routers/ai-session.d.ts +0 -0
  21. package/dist/routers/ai-session.js +1 -0
  22. package/dist/routers/auth.d.ts +13 -11
  23. package/dist/routers/auth.js +50 -21
  24. package/dist/routers/chat.d.ts +171 -0
  25. package/dist/routers/chat.js +270 -0
  26. package/dist/routers/flashcards.d.ts +51 -39
  27. package/dist/routers/flashcards.js +143 -31
  28. package/dist/routers/meetingsummary.d.ts +0 -0
  29. package/dist/routers/meetingsummary.js +377 -0
  30. package/dist/routers/podcast.d.ts +277 -0
  31. package/dist/routers/podcast.js +847 -0
  32. package/dist/routers/studyguide.d.ts +54 -0
  33. package/dist/routers/studyguide.js +125 -0
  34. package/dist/routers/worksheets.d.ts +147 -40
  35. package/dist/routers/worksheets.js +348 -33
  36. package/dist/routers/workspace.d.ts +163 -8
  37. package/dist/routers/workspace.js +453 -8
  38. package/dist/server.d.ts +1 -1
  39. package/dist/server.js +7 -2
  40. package/dist/trpc.d.ts +5 -5
  41. package/package.json +11 -3
  42. package/prisma/migrations/20250826124819_add_worksheet_difficulty_and_estimated_time/migration.sql +213 -0
  43. package/prisma/migrations/20250826133236_add_worksheet_question_progress/migration.sql +31 -0
  44. package/prisma/migrations/migration_lock.toml +3 -0
  45. package/prisma/schema.prisma +87 -6
  46. package/prisma/seed.mjs +135 -0
  47. package/src/lib/ai-session.ts +411 -0
  48. package/src/lib/auth.ts +1 -1
  49. package/src/lib/inference.ts +21 -0
  50. package/src/lib/pusher.ts +104 -0
  51. package/src/lib/storage.ts +89 -6
  52. package/src/routers/_app.ts +6 -0
  53. package/src/routers/auth.ts +8 -4
  54. package/src/routers/chat.ts +275 -0
  55. package/src/routers/flashcards.ts +151 -33
  56. package/src/routers/meetingsummary.ts +416 -0
  57. package/src/routers/podcast.ts +934 -0
  58. package/src/routers/studyguide.ts +144 -0
  59. package/src/routers/worksheets.ts +346 -18
  60. package/src/routers/workspace.ts +500 -8
  61. package/src/server.ts +7 -2
  62. package/test-ai-integration.js +134 -0
  63. package/dist/context.d.ts.map +0 -1
  64. package/dist/index.d.ts.map +0 -1
  65. package/dist/lib/auth.d.ts.map +0 -1
  66. package/dist/lib/file.d.ts.map +0 -1
  67. package/dist/lib/prisma.d.ts.map +0 -1
  68. package/dist/lib/storage.d.ts.map +0 -1
  69. package/dist/routers/_app.d.ts.map +0 -1
  70. package/dist/routers/auth.d.ts.map +0 -1
  71. package/dist/routers/sample.js +0 -21
  72. package/dist/routers/workspace.d.ts.map +0 -1
  73. package/dist/server.d.ts.map +0 -1
  74. package/dist/trpc.d.ts.map +0 -1
@@ -1,11 +1,17 @@
1
1
  import { router } from '../trpc.js';
2
2
  import { auth } from './auth.js';
3
3
  import { workspace } from './workspace.js';
4
- import { flashcards } from './flashcards';
5
- import { worksheets } from './worksheets';
4
+ import { flashcards } from './flashcards.js';
5
+ import { worksheets } from './worksheets.js';
6
+ import { studyguide } from './studyguide.js';
7
+ import { podcast } from './podcast.js';
8
+ import { chat } from './chat.js';
6
9
  export const appRouter = router({
7
10
  auth,
8
11
  workspace,
9
12
  flashcards,
10
13
  worksheets,
14
+ studyguide,
15
+ podcast,
16
+ chat,
11
17
  });
File without changes
@@ -0,0 +1 @@
1
+ "use strict";
@@ -4,7 +4,7 @@ export declare const auth: import("@trpc/server").TRPCBuiltRouter<{
4
4
  session: any;
5
5
  req: import("express").Request<import("express-serve-static-core").ParamsDictionary, any, any, import("qs").ParsedQs, Record<string, any>>;
6
6
  res: import("express").Response<any, Record<string, any>>;
7
- cookies: any;
7
+ cookies: Record<string, string | undefined>;
8
8
  };
9
9
  meta: object;
10
10
  errorShape: import("@trpc/server").TRPCDefaultErrorShape;
@@ -29,22 +29,17 @@ export declare const auth: import("@trpc/server").TRPCBuiltRouter<{
29
29
  password: string;
30
30
  };
31
31
  output: {
32
- id: any;
33
- session: any;
34
- user: {
35
- id: string;
36
- email: string | null;
37
- name: string | null;
38
- image: string | null;
39
- };
32
+ id: string;
33
+ email: string | null;
34
+ name: string | null;
35
+ image: string | null;
36
+ token: string;
40
37
  };
41
38
  meta: object;
42
39
  }>;
43
40
  getSession: import("@trpc/server").TRPCQueryProcedure<{
44
41
  input: void;
45
42
  output: {
46
- id: any;
47
- userId: any;
48
43
  user: {
49
44
  id: string;
50
45
  email: string | null;
@@ -54,4 +49,11 @@ export declare const auth: import("@trpc/server").TRPCBuiltRouter<{
54
49
  };
55
50
  meta: object;
56
51
  }>;
52
+ logout: import("@trpc/server").TRPCMutationProcedure<{
53
+ input: void;
54
+ output: {
55
+ success: boolean;
56
+ };
57
+ meta: object;
58
+ }>;
57
59
  }>>;
@@ -2,6 +2,19 @@ import { z } from 'zod';
2
2
  import { router, publicProcedure } from '../trpc.js';
3
3
  import bcrypt from 'bcryptjs';
4
4
  import { serialize } from 'cookie';
5
+ import crypto from 'node:crypto';
6
+ // Helper to create custom auth token
7
+ function createCustomAuthToken(userId) {
8
+ const secret = process.env.AUTH_SECRET;
9
+ if (!secret) {
10
+ throw new Error("AUTH_SECRET is not set");
11
+ }
12
+ const base64UserId = Buffer.from(userId, 'utf8').toString('base64url');
13
+ const hmac = crypto.createHmac('sha256', secret);
14
+ hmac.update(base64UserId);
15
+ const signature = hmac.digest('hex');
16
+ return `${base64UserId}.${signature}`;
17
+ }
5
18
  export const auth = router({
6
19
  signup: publicProcedure
7
20
  .input(z.object({
@@ -43,39 +56,55 @@ export const auth = router({
43
56
  if (!valid) {
44
57
  throw new Error("Invalid credentials");
45
58
  }
46
- const session = await ctx.db.session.create({
47
- data: {
48
- userId: user.id,
49
- expires: new Date(Date.now() + 1000 * 60 * 60 * 24 * 30),
50
- },
59
+ // Create custom auth token
60
+ const authToken = createCustomAuthToken(user.id);
61
+ const isProduction = (process.env.NODE_ENV === "production" || process.env.RENDER);
62
+ const cookieValue = serialize("auth_token", authToken, {
63
+ httpOnly: true,
64
+ secure: isProduction, // true for production/HTTPS, false for localhost
65
+ sameSite: isProduction ? "none" : "lax", // none for cross-origin, lax for same-origin
66
+ path: "/",
67
+ domain: isProduction ? "server-w8mz.onrender.com" : undefined,
68
+ maxAge: 60 * 60 * 24 * 30, // 30 days
51
69
  });
52
- return { id: session.id, session: session.id, user: { id: user.id, email: user.email, name: user.name, image: user.image } };
70
+ ctx.res.setHeader("Set-Cookie", cookieValue);
71
+ return {
72
+ id: user.id,
73
+ email: user.email,
74
+ name: user.name,
75
+ image: user.image,
76
+ token: authToken
77
+ };
53
78
  }),
54
79
  getSession: publicProcedure.query(async ({ ctx }) => {
55
- const session = await ctx.db.session.findUnique({
56
- where: {
57
- id: ctx.session?.id,
58
- },
59
- });
60
- if (!session) {
61
- throw new Error("Session not found");
62
- }
63
- if (session.expires < new Date()) {
64
- throw new Error("Session expired");
80
+ // Just return the current session from context
81
+ if (!ctx.session) {
82
+ throw new Error("No session found");
65
83
  }
66
84
  const user = await ctx.db.user.findUnique({
67
- where: { id: session.userId },
85
+ where: { id: ctx.session.user.id },
68
86
  });
69
87
  if (!user) {
70
88
  throw new Error("User not found");
71
89
  }
72
- ctx.res.setHeader("Set-Cookie", serialize("auth_token", session.id, {
90
+ return {
91
+ user: {
92
+ id: user.id,
93
+ email: user.email,
94
+ name: user.name,
95
+ image: user.image
96
+ }
97
+ };
98
+ }),
99
+ logout: publicProcedure.mutation(async ({ ctx }) => {
100
+ // Clear the auth cookie
101
+ ctx.res.setHeader("Set-Cookie", serialize("auth_token", "", {
73
102
  httpOnly: true,
74
103
  secure: process.env.NODE_ENV === "production",
75
- sameSite: "none", // cross-origin XHR needs None
104
+ sameSite: "lax",
76
105
  path: "/",
77
- maxAge: 60 * 60 * 24 * 7,
106
+ maxAge: 0, // Expire immediately
78
107
  }));
79
- return { id: session.id, userId: session.userId, user: { id: user.id, email: user.email, name: user.name, image: user.image } };
108
+ return { success: true };
80
109
  }),
81
110
  });
@@ -0,0 +1,171 @@
1
+ export declare const chat: import("@trpc/server").TRPCBuiltRouter<{
2
+ ctx: {
3
+ db: import("@prisma/client").PrismaClient<import("@prisma/client").Prisma.PrismaClientOptions, never, import("@prisma/client/runtime/library").DefaultArgs>;
4
+ session: any;
5
+ req: import("express").Request<import("express-serve-static-core").ParamsDictionary, any, any, import("qs").ParsedQs, Record<string, any>>;
6
+ res: import("express").Response<any, Record<string, any>>;
7
+ cookies: Record<string, string | undefined>;
8
+ };
9
+ meta: object;
10
+ errorShape: import("@trpc/server").TRPCDefaultErrorShape;
11
+ transformer: true;
12
+ }, import("@trpc/server").TRPCDecorateCreateRouterOptions<{
13
+ getChannels: import("@trpc/server").TRPCQueryProcedure<{
14
+ input: {
15
+ workspaceId: string;
16
+ };
17
+ output: {
18
+ name: string;
19
+ id: string;
20
+ createdAt: Date;
21
+ workspaceId: string;
22
+ }[];
23
+ meta: object;
24
+ }>;
25
+ getChannel: import("@trpc/server").TRPCQueryProcedure<{
26
+ input: {
27
+ workspaceId?: string | undefined;
28
+ channelId?: string | undefined;
29
+ };
30
+ output: {
31
+ chats: ({
32
+ user: {
33
+ name: string | null;
34
+ id: string;
35
+ image: string | null;
36
+ } | null;
37
+ } & {
38
+ id: string;
39
+ createdAt: Date;
40
+ updatedAt: Date;
41
+ userId: string | null;
42
+ channelId: string;
43
+ message: string;
44
+ })[];
45
+ } & {
46
+ name: string;
47
+ id: string;
48
+ createdAt: Date;
49
+ workspaceId: string;
50
+ };
51
+ meta: object;
52
+ }>;
53
+ removeChannel: import("@trpc/server").TRPCMutationProcedure<{
54
+ input: {
55
+ workspaceId: string;
56
+ channelId: string;
57
+ };
58
+ output: {
59
+ success: boolean;
60
+ };
61
+ meta: object;
62
+ }>;
63
+ editChannel: import("@trpc/server").TRPCMutationProcedure<{
64
+ input: {
65
+ workspaceId: string;
66
+ channelId: string;
67
+ name: string;
68
+ };
69
+ output: {
70
+ chats: ({
71
+ user: {
72
+ name: string | null;
73
+ id: string;
74
+ image: string | null;
75
+ } | null;
76
+ } & {
77
+ id: string;
78
+ createdAt: Date;
79
+ updatedAt: Date;
80
+ userId: string | null;
81
+ channelId: string;
82
+ message: string;
83
+ })[];
84
+ } & {
85
+ name: string;
86
+ id: string;
87
+ createdAt: Date;
88
+ workspaceId: string;
89
+ };
90
+ meta: object;
91
+ }>;
92
+ createChannel: import("@trpc/server").TRPCMutationProcedure<{
93
+ input: {
94
+ workspaceId: string;
95
+ name: string;
96
+ };
97
+ output: {
98
+ chats: ({
99
+ user: {
100
+ name: string | null;
101
+ id: string;
102
+ image: string | null;
103
+ } | null;
104
+ } & {
105
+ id: string;
106
+ createdAt: Date;
107
+ updatedAt: Date;
108
+ userId: string | null;
109
+ channelId: string;
110
+ message: string;
111
+ })[];
112
+ } & {
113
+ name: string;
114
+ id: string;
115
+ createdAt: Date;
116
+ workspaceId: string;
117
+ };
118
+ meta: object;
119
+ }>;
120
+ postMessage: import("@trpc/server").TRPCMutationProcedure<{
121
+ input: {
122
+ channelId: string;
123
+ message: string;
124
+ };
125
+ output: {
126
+ user: {
127
+ name: string | null;
128
+ id: string;
129
+ image: string | null;
130
+ } | null;
131
+ } & {
132
+ id: string;
133
+ createdAt: Date;
134
+ updatedAt: Date;
135
+ userId: string | null;
136
+ channelId: string;
137
+ message: string;
138
+ };
139
+ meta: object;
140
+ }>;
141
+ editMessage: import("@trpc/server").TRPCMutationProcedure<{
142
+ input: {
143
+ chatId: string;
144
+ message: string;
145
+ };
146
+ output: {
147
+ user: {
148
+ name: string | null;
149
+ id: string;
150
+ image: string | null;
151
+ } | null;
152
+ } & {
153
+ id: string;
154
+ createdAt: Date;
155
+ updatedAt: Date;
156
+ userId: string | null;
157
+ channelId: string;
158
+ message: string;
159
+ };
160
+ meta: object;
161
+ }>;
162
+ deleteMessage: import("@trpc/server").TRPCMutationProcedure<{
163
+ input: {
164
+ chatId: string;
165
+ };
166
+ output: {
167
+ success: boolean;
168
+ };
169
+ meta: object;
170
+ }>;
171
+ }>>;
@@ -0,0 +1,270 @@
1
+ import { TRPCError } from "@trpc/server";
2
+ import { authedProcedure, router } from "../trpc.js";
3
+ import z from "zod";
4
+ import PusherService from "../lib/pusher.js";
5
+ export const chat = router({
6
+ getChannels: authedProcedure
7
+ .input(z.object({ workspaceId: z.string() }))
8
+ .query(async ({ input, ctx }) => {
9
+ const channels = await ctx.db.channel.findMany({
10
+ where: { workspaceId: input.workspaceId },
11
+ include: { chats: {
12
+ include: {
13
+ user: {
14
+ select: {
15
+ id: true,
16
+ name: true,
17
+ image: true,
18
+ }
19
+ }
20
+ }
21
+ } },
22
+ });
23
+ if (!channels) {
24
+ const defaultChannel = await ctx.db.channel.create({
25
+ data: { workspaceId: input.workspaceId, name: "General" },
26
+ });
27
+ return [defaultChannel];
28
+ }
29
+ return channels;
30
+ }),
31
+ getChannel: authedProcedure
32
+ .input(z.object({ workspaceId: z.string().optional(), channelId: z.string().optional() }))
33
+ .query(async ({ input, ctx }) => {
34
+ if (!input.channelId && input.workspaceId) {
35
+ const defaultChannel = await ctx.db.channel.create({
36
+ data: { workspaceId: input.workspaceId, name: "General" },
37
+ include: { chats: {
38
+ include: {
39
+ user: {
40
+ select: {
41
+ id: true,
42
+ name: true,
43
+ image: true,
44
+ }
45
+ }
46
+ }
47
+ } },
48
+ });
49
+ await PusherService.emitTaskComplete(input.workspaceId, "new_channel", {
50
+ channelId: defaultChannel.id,
51
+ workspaceId: input.workspaceId,
52
+ name: "General",
53
+ createdAt: defaultChannel.createdAt,
54
+ });
55
+ return defaultChannel;
56
+ }
57
+ const channel = await ctx.db.channel.findUnique({
58
+ where: { id: input.channelId },
59
+ include: { chats: {
60
+ include: {
61
+ user: {
62
+ select: {
63
+ id: true,
64
+ name: true,
65
+ image: true,
66
+ }
67
+ }
68
+ }
69
+ } },
70
+ });
71
+ if (!channel) {
72
+ throw new TRPCError({ code: "NOT_FOUND", message: "Channel not found" });
73
+ }
74
+ return channel;
75
+ }),
76
+ removeChannel: authedProcedure
77
+ .input(z.object({ workspaceId: z.string(), channelId: z.string() }))
78
+ .mutation(async ({ input, ctx }) => {
79
+ await ctx.db.channel.delete({ where: { id: input.channelId } });
80
+ await PusherService.emitTaskComplete(input.workspaceId, "remove_channel", {
81
+ channelId: input.channelId,
82
+ deletedAt: new Date().toISOString(),
83
+ });
84
+ return { success: true };
85
+ }),
86
+ editChannel: authedProcedure
87
+ .input(z.object({ workspaceId: z.string(), channelId: z.string(), name: z.string() }))
88
+ .mutation(async ({ input, ctx }) => {
89
+ const channel = await ctx.db.channel.update({
90
+ where: { id: input.channelId },
91
+ data: { name: input.name },
92
+ include: {
93
+ chats: {
94
+ include: {
95
+ user: {
96
+ select: {
97
+ id: true,
98
+ name: true,
99
+ image: true,
100
+ }
101
+ },
102
+ }
103
+ }
104
+ }
105
+ });
106
+ await PusherService.emitTaskComplete(input.workspaceId, "edit_channel", {
107
+ channelId: input.channelId,
108
+ workspaceId: input.workspaceId,
109
+ name: input.name,
110
+ });
111
+ return channel;
112
+ }),
113
+ createChannel: authedProcedure
114
+ .input(z.object({ workspaceId: z.string(), name: z.string() }))
115
+ .mutation(async ({ input, ctx }) => {
116
+ const channel = await ctx.db.channel.create({
117
+ data: { workspaceId: input.workspaceId, name: input.name },
118
+ include: {
119
+ chats: {
120
+ include: {
121
+ user: {
122
+ select: {
123
+ id: true,
124
+ name: true,
125
+ image: true,
126
+ }
127
+ }
128
+ }
129
+ }
130
+ }
131
+ });
132
+ // Notify via Pusher
133
+ await PusherService.emitTaskComplete(input.workspaceId, "new_channel", {
134
+ channelId: channel.id,
135
+ workspaceId: input.workspaceId,
136
+ name: input.name,
137
+ createdAt: channel.createdAt,
138
+ });
139
+ return channel;
140
+ }),
141
+ postMessage: authedProcedure
142
+ .input(z.object({ channelId: z.string(), message: z.string() }))
143
+ .mutation(async ({ input, ctx }) => {
144
+ const channel = await ctx.db.channel.findUnique({
145
+ where: { id: input.channelId },
146
+ include: {
147
+ chats: {
148
+ include: {
149
+ user: {
150
+ select: {
151
+ id: true,
152
+ name: true,
153
+ image: true,
154
+ }
155
+ }
156
+ }
157
+ }
158
+ }
159
+ });
160
+ if (!channel) {
161
+ throw new TRPCError({ code: "NOT_FOUND", message: "Channel not found" });
162
+ }
163
+ const chat = await ctx.db.chat.create({
164
+ data: {
165
+ channelId: input.channelId,
166
+ userId: ctx.session.user.id,
167
+ message: input.message,
168
+ },
169
+ include: {
170
+ user: {
171
+ select: {
172
+ id: true,
173
+ name: true,
174
+ image: true,
175
+ }
176
+ }
177
+ }
178
+ });
179
+ // Notify via Pusher
180
+ await PusherService.emitChannelEvent(input.channelId, "new_message", chat);
181
+ return chat;
182
+ }),
183
+ editMessage: authedProcedure
184
+ .input(z.object({ chatId: z.string(), message: z.string() }))
185
+ .mutation(async ({ input, ctx }) => {
186
+ const chat = await ctx.db.chat.findUnique({
187
+ where: { id: input.chatId },
188
+ include: {
189
+ user: {
190
+ select: {
191
+ id: true,
192
+ name: true,
193
+ image: true,
194
+ }
195
+ }
196
+ }
197
+ });
198
+ if (!chat) {
199
+ throw new TRPCError({ code: "NOT_FOUND", message: "Chat message not found" });
200
+ }
201
+ if (chat.userId !== ctx.session.user.id) {
202
+ throw new TRPCError({ code: "FORBIDDEN", message: "Not your message to edit" });
203
+ }
204
+ const updatedChat = await ctx.db.chat.update({
205
+ where: { id: input.chatId },
206
+ data: { message: input.message },
207
+ include: {
208
+ user: {
209
+ select: {
210
+ id: true,
211
+ name: true,
212
+ image: true,
213
+ }
214
+ }
215
+ }
216
+ });
217
+ // Notify via Pusher
218
+ await PusherService.emitChannelEvent(chat.channelId, "edit_message", {
219
+ chatId: updatedChat.id,
220
+ channelId: updatedChat.channelId,
221
+ userId: updatedChat.userId,
222
+ message: input.message,
223
+ updatedAt: updatedChat.updatedAt,
224
+ user: {
225
+ id: ctx.session.user.id,
226
+ name: updatedChat.user?.name,
227
+ image: updatedChat.user?.image,
228
+ },
229
+ });
230
+ return updatedChat;
231
+ }),
232
+ deleteMessage: authedProcedure
233
+ .input(z.object({ chatId: z.string() }))
234
+ .mutation(async ({ input, ctx }) => {
235
+ const chat = await ctx.db.chat.findUnique({
236
+ where: { id: input.chatId },
237
+ include: {
238
+ user: {
239
+ select: {
240
+ id: true,
241
+ name: true,
242
+ image: true,
243
+ }
244
+ },
245
+ }
246
+ });
247
+ if (!chat) {
248
+ throw new TRPCError({ code: "NOT_FOUND", message: "Chat message not found" });
249
+ }
250
+ if (chat.userId !== ctx.session.user.id) {
251
+ throw new TRPCError({ code: "FORBIDDEN", message: "Not your message to delete" });
252
+ }
253
+ await ctx.db.chat.delete({
254
+ where: { id: input.chatId },
255
+ });
256
+ // Notify via Pusher
257
+ await PusherService.emitChannelEvent(chat.channelId, "delete_message", {
258
+ chatId: chat.id,
259
+ channelId: chat.channelId,
260
+ userId: chat.userId,
261
+ deletedAt: new Date().toISOString(),
262
+ user: {
263
+ id: ctx.session.user.id,
264
+ name: chat.user?.name,
265
+ image: chat.user?.image,
266
+ },
267
+ });
268
+ return { success: true };
269
+ }),
270
+ });