@studious-lms/server 1.2.47 → 1.2.48

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/src/index.ts CHANGED
@@ -34,8 +34,6 @@ app.use((req, res, next) => {
34
34
  next();
35
35
  });
36
36
 
37
- app.use(generalLimiter);
38
-
39
37
  const allowedOrigins = env.NODE_ENV === 'production'
40
38
  ? [
41
39
  'https://www.studious.sh',
@@ -59,9 +57,13 @@ app.use(cors({
59
57
  credentials: true,
60
58
  methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
61
59
  allowedHeaders: ['Content-Type', 'Authorization', 'X-Requested-With', 'x-user'],
62
- optionsSuccessStatus: 200
60
+ preflightContinue: false, // Important: stop further handling of OPTIONS
61
+ optionsSuccessStatus: 204, // Recommended for modern browsers
62
+
63
63
  }));
64
64
 
65
+ app.use(generalLimiter);
66
+
65
67
  // CORS debugging middleware
66
68
  app.use((req, res, next) => {
67
69
  if (req.method === 'OPTIONS' || req.path.includes('trpc')) {
@@ -90,25 +92,25 @@ app.use((req, res, next) => {
90
92
  next();
91
93
  });
92
94
 
93
- app.use("/panel", async (_, res) => {
94
- if (env.NODE_ENV !== "development") {
95
- return res.status(404).send("Not Found");
96
- }
97
-
98
- // Dynamically import renderTrpcPanel only in development
99
- const { renderTrpcPanel } = await import("trpc-ui");
100
-
101
- return res.send(
102
- renderTrpcPanel(appRouter, {
103
- url: "/trpc", // Base url of your trpc server
104
- meta: {
105
- title: "Studious Backend",
106
- description:
107
- "This is the backend for the Studious application.",
108
- },
109
- })
110
- );
111
- });
95
+ // app.use("/panel", async (_, res) => {
96
+ // if (env.NODE_ENV !== "development") {
97
+ // return res.status(404).send("Not Found");
98
+ // }
99
+
100
+ // // Dynamically import renderTrpcPanel only in development
101
+ // const { renderTrpcPanel } = await import("trpc-ui");
102
+
103
+ // return res.send(
104
+ // renderTrpcPanel(appRouter, {
105
+ // url: "/trpc", // Base url of your trpc server
106
+ // meta: {
107
+ // title: "Studious Backend",
108
+ // description:
109
+ // "This is the backend for the Studious application.",
110
+ // },
111
+ // })
112
+ // );
113
+ // });
112
114
 
113
115
 
114
116
  // Create HTTP server
@@ -19,7 +19,7 @@ const rateLimitHandler = (req: Request, res: Response) => {
19
19
 
20
20
  // General API rate limiter - applies to all routes
21
21
  export const generalLimiter = rateLimit({
22
- windowMs: 10 * 60 * 1000, // 10 minutes
22
+ windowMs: 10 * 60, // 10 minutes
23
23
  max: 100, // Limit each IP to 100 requests per windowMs
24
24
  message: 'Too many requests from this IP, please try again later.',
25
25
  standardHeaders: true, // Return rate limit info in the `RateLimit-*` headers
@@ -27,7 +27,7 @@ export const generalLimiter = rateLimit({
27
27
  handler: rateLimitHandler,
28
28
  skip: (req) => {
29
29
  // Skip rate limiting for health checks
30
- return req.path === '/health';
30
+ return req.path === '/health' || req.method === 'OPTIONS';
31
31
  },
32
32
  });
33
33
 
@@ -26,12 +26,39 @@ export const classRouter = createTRPCRouter({
26
26
  template: false,
27
27
  },
28
28
  select: {
29
+
29
30
  id: true,
30
31
  title: true,
31
32
  type: true,
32
33
  dueDate: true,
33
34
  },
34
35
  },
36
+ students: {
37
+ select: {
38
+ id: true,
39
+ username: true,
40
+ profile: {
41
+ select: {
42
+ displayName: true,
43
+ profilePicture: true,
44
+ profilePictureThumbnail: true,
45
+ },
46
+ },
47
+ },
48
+ },
49
+ teachers: {
50
+ select: {
51
+ id: true,
52
+ username: true,
53
+ profile: {
54
+ select: {
55
+ displayName: true,
56
+ profilePicture: true,
57
+ profilePictureThumbnail: true,
58
+ },
59
+ },
60
+ },
61
+ },
35
62
  },
36
63
  }),
37
64
  prisma.class.findMany({
@@ -57,6 +84,32 @@ export const classRouter = createTRPCRouter({
57
84
  dueDate: true,
58
85
  },
59
86
  },
87
+ students: {
88
+ select: {
89
+ id: true,
90
+ username: true,
91
+ profile: {
92
+ select: {
93
+ displayName: true,
94
+ profilePicture: true,
95
+ profilePictureThumbnail: true,
96
+ },
97
+ },
98
+ },
99
+ },
100
+ teachers: {
101
+ select: {
102
+ id: true,
103
+ username: true,
104
+ profile: {
105
+ select: {
106
+ displayName: true,
107
+ profilePicture: true,
108
+ profilePictureThumbnail: true,
109
+ },
110
+ },
111
+ },
112
+ },
60
113
  },
61
114
  }),
62
115
  ]);
@@ -69,6 +122,7 @@ export const classRouter = createTRPCRouter({
69
122
  subject: cls.subject,
70
123
  dueToday: cls.assignments,
71
124
  assignments: cls.assignments,
125
+ members: [...cls.students, ...cls.teachers],
72
126
  color: cls.color,
73
127
  })),
74
128
  studentInClass: studentClasses.map(cls => ({
@@ -78,6 +132,7 @@ export const classRouter = createTRPCRouter({
78
132
  subject: cls.subject,
79
133
  dueToday: cls.assignments,
80
134
  assignments: cls.assignments,
135
+ members: [...cls.students, ...cls.teachers],
81
136
  color: cls.color,
82
137
  })),
83
138
  };
@@ -120,19 +120,17 @@ export const newtonChatRouter = createTRPCRouter({
120
120
  title: 'Session with Newton Tutor',
121
121
  },
122
122
  });
123
+ generateAndSendNewtonIntroduction(
124
+ newtonChat.id,
125
+ newtonChat.conversationId,
126
+ submission.id
127
+ ).catch(error => {
128
+ logger.error('Failed to generate AI introduction:', { error, newtonChatId: result.id });
129
+ });
123
130
 
124
131
  return newtonChat;
125
132
  });
126
133
 
127
- // Generate AI introduction message in parallel (don't await - fire and forget)
128
- generateAndSendNewtonIntroduction(
129
- result.id,
130
- result.conversationId,
131
- submission.id
132
- ).catch(error => {
133
- logger.error('Failed to generate AI introduction:', { error, newtonChatId: result.id });
134
- });
135
-
136
134
  return {
137
135
  conversationId: result.conversationId,
138
136
  newtonChatId: result.id,
@@ -8,6 +8,21 @@ export async function clearDatabase() {
8
8
  logger.info('Clearing database');
9
9
  await prisma.notification.deleteMany();
10
10
 
11
+ // Delete worksheet-related records
12
+ await prisma.studentQuestionProgress.deleteMany();
13
+ await prisma.studentWorksheetResponse.deleteMany();
14
+ await prisma.worksheetQuestion.deleteMany();
15
+ await prisma.worksheet.deleteMany();
16
+
17
+ // Delete reactions (they reference announcements and comments)
18
+ await prisma.reaction.deleteMany();
19
+
20
+ // Delete comments (they reference announcements and users)
21
+ await prisma.comment.deleteMany();
22
+
23
+ // Delete NewtonChat (they reference submissions and conversations)
24
+ await prisma.newtonChat.deleteMany();
25
+
11
26
  // Delete chat-related records
12
27
  await prisma.mention.deleteMany();
13
28
  await prisma.message.deleteMany();
@@ -39,10 +54,19 @@ export async function clearDatabase() {
39
54
  // Delete schools (which reference files for logos) - this will cascade delete the file references
40
55
  await prisma.school.deleteMany();
41
56
 
57
+ // Delete marketing-related records
58
+ await prisma.schoolDevelopementProgram.deleteMany();
59
+ await prisma.earlyAccessRequest.deleteMany();
60
+
42
61
  // Finally delete all files
43
62
  await prisma.file.deleteMany();
44
63
  }
45
64
 
65
+ // Helper function to generate DiceBear avatar URL
66
+ function getDiceBearAvatar(seed: string): string {
67
+ return `https://api.dicebear.com/7.x/avataaars/svg?seed=${encodeURIComponent(seed)}`;
68
+ }
69
+
46
70
  export async function createUser(email: string, password: string, username: string) {
47
71
  logger.debug("Creating user", { email, username, password });
48
72
 
@@ -109,7 +133,7 @@ export const seedDatabase = async () => {
109
133
  createUser('charlotte.walker@student.riverside.edu', 'student123', 'charlotte.walker'),
110
134
  ]);
111
135
 
112
- // 4. Create User Profiles
136
+ // 4. Create User Profiles with DiceBear avatars
113
137
  await Promise.all([
114
138
  prisma.userProfile.create({
115
139
  data: {
@@ -118,6 +142,7 @@ export const seedDatabase = async () => {
118
142
  bio: 'Biology teacher with 15 years of experience. Passionate about making science accessible to all students.',
119
143
  location: 'Riverside, CA',
120
144
  website: 'https://sarahjohnson-bio.com',
145
+ profilePicture: getDiceBearAvatar(teachers[0].username),
121
146
  }
122
147
  }),
123
148
  prisma.userProfile.create({
@@ -126,6 +151,7 @@ export const seedDatabase = async () => {
126
151
  displayName: 'Mr. Michael Chen',
127
152
  bio: 'Mathematics teacher and department head. Specializes in AP Calculus and Statistics.',
128
153
  location: 'Riverside, CA',
154
+ profilePicture: getDiceBearAvatar(teachers[1].username),
129
155
  }
130
156
  }),
131
157
  prisma.userProfile.create({
@@ -134,18 +160,24 @@ export const seedDatabase = async () => {
134
160
  displayName: 'Ms. Emma Davis',
135
161
  bio: 'English Literature teacher. Loves fostering creative writing and critical thinking.',
136
162
  location: 'Riverside, CA',
163
+ profilePicture: getDiceBearAvatar(teachers[2].username),
137
164
  }
138
165
  }),
139
166
  ]);
140
167
 
141
- // Add profiles for some students
142
- await Promise.all(students.slice(0, 6).map((student, index) => {
143
- const names = ['Alex Martinez', 'Sophia Williams', 'James Brown', 'Olivia Taylor', 'Ethan Anderson', 'Ava Thomas'];
168
+ // Add profiles for all students with DiceBear avatars
169
+ await Promise.all(students.map((student, index) => {
170
+ const names = [
171
+ 'Alex Martinez', 'Sophia Williams', 'James Brown', 'Olivia Taylor',
172
+ 'Ethan Anderson', 'Ava Thomas', 'Noah Jackson', 'Isabella White',
173
+ 'Liam Harris', 'Mia Clark', 'Lucas Lewis', 'Charlotte Walker'
174
+ ];
144
175
  return prisma.userProfile.create({
145
176
  data: {
146
177
  userId: student.id,
147
- displayName: names[index],
148
- bio: `Grade 11 student at Riverside High School.`,
178
+ displayName: names[index] || student.username,
179
+ bio: index < 6 ? `Grade 11 student at Riverside High School.` : undefined,
180
+ profilePicture: getDiceBearAvatar(student.username),
149
181
  }
150
182
  });
151
183
  }));