@studious-lms/server 1.1.17 → 1.1.19

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/dist/index.js CHANGED
@@ -9,6 +9,7 @@ import { createTRPCContext, createCallerFactory } from './trpc.js';
9
9
  import { logger } from './utils/logger.js';
10
10
  import { setupSocketHandlers } from './socket/handlers.js';
11
11
  import { bucket } from './lib/googleCloudStorage.js';
12
+ import { prisma } from './lib/prisma.js';
12
13
  dotenv.config();
13
14
  const app = express();
14
15
  // CORS middleware
@@ -111,16 +112,161 @@ io.engine.on('connection_error', (err) => {
111
112
  // Setup socket handlers
112
113
  setupSocketHandlers(io);
113
114
  // File serving endpoint for secure file access
114
- app.get('/api/files/:filePath', async (req, res) => {
115
+ app.get('/api/files/:fileId', async (req, res) => {
115
116
  try {
116
- const filePath = decodeURIComponent(req.params.filePath);
117
- console.log('File request:', { filePath, originalPath: req.params.filePath });
117
+ const fileId = decodeURIComponent(req.params.fileId);
118
+ // console.log('File request:', { fileId, originalPath: req.params.fileId });
119
+ // Get user from request headers
120
+ const userHeader = req.headers['x-user'];
121
+ if (!userHeader) {
122
+ return res.status(401).json({ error: 'Authentication required' });
123
+ }
124
+ const token = typeof userHeader === 'string' ? userHeader : userHeader[0];
125
+ // Find user by session token
126
+ const user = await prisma.user.findFirst({
127
+ where: {
128
+ sessions: {
129
+ some: {
130
+ id: token
131
+ }
132
+ }
133
+ },
134
+ select: {
135
+ id: true,
136
+ username: true,
137
+ }
138
+ });
139
+ if (!user) {
140
+ return res.status(401).json({ error: 'Invalid or expired session' });
141
+ }
142
+ // Find file in database by path
143
+ const fileRecord = await prisma.file.findFirst({
144
+ where: { id: fileId },
145
+ include: {
146
+ user: true,
147
+ assignment: {
148
+ include: {
149
+ class: {
150
+ include: {
151
+ students: true,
152
+ teachers: true
153
+ }
154
+ }
155
+ }
156
+ },
157
+ submission: {
158
+ include: {
159
+ student: true,
160
+ assignment: {
161
+ include: {
162
+ class: {
163
+ include: {
164
+ teachers: true
165
+ }
166
+ }
167
+ }
168
+ }
169
+ }
170
+ },
171
+ annotations: {
172
+ include: {
173
+ student: true,
174
+ assignment: {
175
+ include: {
176
+ class: {
177
+ include: {
178
+ teachers: true
179
+ }
180
+ }
181
+ }
182
+ }
183
+ }
184
+ },
185
+ folder: {
186
+ include: {
187
+ class: {
188
+ include: {
189
+ students: true,
190
+ teachers: true
191
+ }
192
+ }
193
+ }
194
+ },
195
+ classDraft: {
196
+ include: {
197
+ students: true,
198
+ teachers: true
199
+ }
200
+ }
201
+ }
202
+ });
203
+ if (!fileRecord) {
204
+ return res.status(404).json({ error: 'File not found in database' });
205
+ }
206
+ // Check if user has permission to access this file
207
+ let hasPermission = false;
208
+ // Check if user created the file
209
+ if (fileRecord.userId === user.id) {
210
+ hasPermission = true;
211
+ }
212
+ // Check if file is related to a class where user is a member
213
+ if (!hasPermission) {
214
+ // Check assignment files
215
+ if (fileRecord.assignment?.class) {
216
+ const classData = fileRecord.assignment.class;
217
+ const isStudent = classData.students.some(student => student.id === user.id);
218
+ const isTeacher = classData.teachers.some(teacher => teacher.id === user.id);
219
+ if (isStudent || isTeacher) {
220
+ hasPermission = true;
221
+ }
222
+ }
223
+ if (!hasPermission && fileRecord.annotations) {
224
+ const annotation = fileRecord.annotations;
225
+ if (annotation.studentId === user.id) {
226
+ hasPermission = true;
227
+ }
228
+ else if (annotation.assignment?.class?.teachers.some(teacher => teacher.id === user.id)) {
229
+ hasPermission = true;
230
+ }
231
+ }
232
+ // Check submission files (student can access their own submissions, teachers can access all submissions in their class)
233
+ if (!hasPermission && fileRecord.submission) {
234
+ const submission = fileRecord.submission;
235
+ if (submission.studentId === user.id) {
236
+ hasPermission = true; // Student accessing their own submission
237
+ }
238
+ else if (submission.assignment?.class?.teachers.some(teacher => teacher.id === user.id)) {
239
+ hasPermission = true; // Teacher accessing submission in their class
240
+ }
241
+ }
242
+ // Check folder files
243
+ if (!hasPermission && fileRecord.folder?.class) {
244
+ const classData = fileRecord.folder.class;
245
+ const isStudent = classData.students.some(student => student.id === user.id);
246
+ const isTeacher = classData.teachers.some(teacher => teacher.id === user.id);
247
+ if (isStudent || isTeacher) {
248
+ hasPermission = true;
249
+ }
250
+ }
251
+ // Check class draft files
252
+ if (!hasPermission && fileRecord.classDraft) {
253
+ const classData = fileRecord.classDraft;
254
+ const isStudent = classData.students.some(student => student.id === user.id);
255
+ const isTeacher = classData.teachers.some(teacher => teacher.id === user.id);
256
+ if (isStudent || isTeacher) {
257
+ hasPermission = true;
258
+ }
259
+ }
260
+ }
261
+ if (!hasPermission) {
262
+ return res.status(403).json({ error: 'Access denied - insufficient permissions' });
263
+ }
264
+ const filePath = fileRecord.path;
118
265
  // Get file from Google Cloud Storage
119
266
  const file = bucket.file(filePath);
120
267
  const [exists] = await file.exists();
121
- console.log('File exists:', exists, 'for path:', filePath);
122
268
  if (!exists) {
123
- return res.status(404).json({ error: 'File not found', filePath });
269
+ return res.status(404).json({ error: 'File not found in storage', filePath });
124
270
  }
125
271
  // Get file metadata
126
272
  const [metadata] = await file.getMetadata();
@@ -135,14 +281,14 @@ app.get('/api/files/:filePath', async (req, res) => {
135
281
  const stream = file.createReadStream();
136
282
  stream.pipe(res);
137
283
  stream.on('error', (error) => {
138
- console.error('Error streaming file:', error);
284
+ logger.error('Error streaming file:', { error });
139
285
  if (!res.headersSent) {
140
286
  res.status(500).json({ error: 'Error streaming file' });
141
287
  }
142
288
  });
143
289
  }
144
290
  catch (error) {
145
- console.error('Error serving file:', error);
291
+ logger.error('Error serving file:', { error });
146
292
  res.status(500).json({ error: 'Internal server error' });
147
293
  }
148
294
  });
@@ -156,7 +302,6 @@ app.put('/api/upload/:filePath', async (req, res) => {
156
302
  function handleFileUpload(req, res) {
157
303
  try {
158
304
  const filePath = decodeURIComponent(req.params.filePath);
159
- console.log('File upload request:', { filePath, originalPath: req.params.filePath, method: req.method });
160
305
  // Set CORS headers for upload endpoint
161
306
  const origin = req.headers.origin;
162
307
  const allowedOrigins = [
@@ -187,13 +332,12 @@ function handleFileUpload(req, res) {
187
332
  });
188
333
  // Handle stream events
189
334
  writeStream.on('error', (error) => {
190
- console.error('Error uploading file:', error);
335
+ logger.error('Error uploading file:', { error });
191
336
  if (!res.headersSent) {
192
337
  res.status(500).json({ error: 'Error uploading file' });
193
338
  }
194
339
  });
195
340
  writeStream.on('finish', () => {
196
- console.log('File uploaded successfully:', filePath);
197
341
  res.status(200).json({
198
342
  success: true,
199
343
  filePath,
@@ -204,7 +348,7 @@ function handleFileUpload(req, res) {
204
348
  req.pipe(writeStream);
205
349
  }
206
350
  catch (error) {
207
- console.error('Error handling file upload:', error);
351
+ logger.error('Error handling file upload:', { error });
208
352
  res.status(500).json({ error: 'Internal server error' });
209
353
  }
210
354
  }
@@ -1301,6 +1301,18 @@ export declare const appRouter: import("@trpc/server").TRPCBuiltRouter<{
1301
1301
  student: {
1302
1302
  id: string;
1303
1303
  username: string;
1304
+ profile: {
1305
+ id: string;
1306
+ location: string | null;
1307
+ userId: string;
1308
+ createdAt: Date;
1309
+ displayName: string | null;
1310
+ bio: string | null;
1311
+ website: string | null;
1312
+ profilePicture: string | null;
1313
+ profilePictureThumbnail: string | null;
1314
+ updatedAt: Date;
1315
+ } | null;
1304
1316
  };
1305
1317
  attachments: {
1306
1318
  path: string;
@@ -3995,6 +4007,16 @@ export declare const appRouter: import("@trpc/server").TRPCBuiltRouter<{
3995
4007
  } | null;
3996
4008
  meta: object;
3997
4009
  }>;
4010
+ earlyAccessRequest: import("@trpc/server").TRPCMutationProcedure<{
4011
+ input: {
4012
+ email: string;
4013
+ institutionSize: string;
4014
+ };
4015
+ output: {
4016
+ id: string;
4017
+ };
4018
+ meta: object;
4019
+ }>;
3998
4020
  }>>;
3999
4021
  }>>;
4000
4022
  export type AppRouter = typeof appRouter;
@@ -5302,6 +5324,18 @@ export declare const createCaller: import("@trpc/server").TRPCRouterCaller<{
5302
5324
  student: {
5303
5325
  id: string;
5304
5326
  username: string;
5327
+ profile: {
5328
+ id: string;
5329
+ location: string | null;
5330
+ userId: string;
5331
+ createdAt: Date;
5332
+ displayName: string | null;
5333
+ bio: string | null;
5334
+ website: string | null;
5335
+ profilePicture: string | null;
5336
+ profilePictureThumbnail: string | null;
5337
+ updatedAt: Date;
5338
+ } | null;
5305
5339
  };
5306
5340
  attachments: {
5307
5341
  path: string;
@@ -7996,6 +8030,16 @@ export declare const createCaller: import("@trpc/server").TRPCRouterCaller<{
7996
8030
  } | null;
7997
8031
  meta: object;
7998
8032
  }>;
8033
+ earlyAccessRequest: import("@trpc/server").TRPCMutationProcedure<{
8034
+ input: {
8035
+ email: string;
8036
+ institutionSize: string;
8037
+ };
8038
+ output: {
8039
+ id: string;
8040
+ };
8041
+ meta: object;
8042
+ }>;
7999
8043
  }>>;
8000
8044
  }>>;
8001
8045
  //# sourceMappingURL=_app.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"_app.d.ts","sourceRoot":"","sources":["../../src/routers/_app.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAc1E,eAAO,MAAM,SAAS;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiBpB,CAAC;AAGH,MAAM,MAAM,SAAS,GAAG,OAAO,SAAS,CAAC;AACzC,MAAM,MAAM,YAAY,GAAG,iBAAiB,CAAC,SAAS,CAAC,CAAC;AACxD,MAAM,MAAM,aAAa,GAAG,kBAAkB,CAAC,SAAS,CAAC,CAAC;AAG1D,eAAO,MAAM,YAAY;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAAiC,CAAC"}
1
+ {"version":3,"file":"_app.d.ts","sourceRoot":"","sources":["../../src/routers/_app.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAc1E,eAAO,MAAM,SAAS;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiBpB,CAAC;AAGH,MAAM,MAAM,SAAS,GAAG,OAAO,SAAS,CAAC;AACzC,MAAM,MAAM,YAAY,GAAG,iBAAiB,CAAC,SAAS,CAAC,CAAC;AACxD,MAAM,MAAM,aAAa,GAAG,kBAAkB,CAAC,SAAS,CAAC,CAAC;AAG1D,eAAO,MAAM,YAAY;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAAiC,CAAC"}
@@ -448,6 +448,18 @@ export declare const assignmentRouter: import("@trpc/server").TRPCBuiltRouter<{
448
448
  student: {
449
449
  id: string;
450
450
  username: string;
451
+ profile: {
452
+ id: string;
453
+ location: string | null;
454
+ userId: string;
455
+ createdAt: Date;
456
+ displayName: string | null;
457
+ bio: string | null;
458
+ website: string | null;
459
+ profilePicture: string | null;
460
+ profilePictureThumbnail: string | null;
461
+ updatedAt: Date;
462
+ } | null;
451
463
  };
452
464
  attachments: {
453
465
  path: string;
@@ -1 +1 @@
1
- {"version":3,"file":"assignment.d.ts","sourceRoot":"","sources":["../../src/routers/assignment.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAsFxB,eAAO,MAAM,gBAAgB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+rD3B,CAAC"}
1
+ {"version":3,"file":"assignment.d.ts","sourceRoot":"","sources":["../../src/routers/assignment.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAsFxB,eAAO,MAAM,gBAAgB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgsD3B,CAAC"}
@@ -638,6 +638,7 @@ export const assignmentRouter = createTRPCRouter({
638
638
  select: {
639
639
  id: true,
640
640
  username: true,
641
+ profile: true,
641
642
  },
642
643
  },
643
644
  assignment: {
@@ -66,5 +66,15 @@ export declare const marketingRouter: import("@trpc/server").TRPCBuiltRouter<{
66
66
  } | null;
67
67
  meta: object;
68
68
  }>;
69
+ earlyAccessRequest: import("@trpc/server").TRPCMutationProcedure<{
70
+ input: {
71
+ email: string;
72
+ institutionSize: string;
73
+ };
74
+ output: {
75
+ id: string;
76
+ };
77
+ meta: object;
78
+ }>;
69
79
  }>>;
70
80
  //# sourceMappingURL=marketing.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"marketing.d.ts","sourceRoot":"","sources":["../../src/routers/marketing.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAKxB,eAAO,MAAM,eAAe;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+D1B,CAAC"}
1
+ {"version":3,"file":"marketing.d.ts","sourceRoot":"","sources":["../../src/routers/marketing.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAKxB,eAAO,MAAM,eAAe;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgF1B,CAAC"}
@@ -24,7 +24,7 @@ export const marketingRouter = createTRPCRouter({
24
24
  .mutation(async ({ ctx, input }) => {
25
25
  const { name, type, address, city, country, numberOfStudents, numberOfTeachers, website, contactName, contactRole, contactEmail, contactPhone, eligibilityInformation, whyHelp, additionalInformation } = input;
26
26
  const date = new Date();
27
- const id = name.slice(0, 3).toUpperCase() + date.getFullYear() + '-' + v4();
27
+ const id = name.slice(0, 3).toUpperCase() + '-' + date.getFullYear() + '-' + v4().slice(0, 4).toUpperCase();
28
28
  const schoolDevelopementProgram = await prisma.schoolDevelopementProgram.create({
29
29
  data: {
30
30
  id,
@@ -62,4 +62,21 @@ export const marketingRouter = createTRPCRouter({
62
62
  });
63
63
  return schoolDevelopementProgram;
64
64
  }),
65
+ earlyAccessRequest: publicProcedure
66
+ .input(z.object({
67
+ email: z.string(),
68
+ institutionSize: z.string(),
69
+ }))
70
+ .mutation(async ({ input }) => {
71
+ const { email, institutionSize } = input;
72
+ const earlyAccessRequest = await prisma.earlyAccessRequest.create({
73
+ data: {
74
+ email,
75
+ institutionSize,
76
+ },
77
+ });
78
+ return {
79
+ id: earlyAccessRequest.id,
80
+ };
81
+ }),
65
82
  });
@@ -1 +1 @@
1
- {"version":3,"file":"seedDatabase.d.ts","sourceRoot":"","sources":["../src/seedDatabase.ts"],"names":[],"mappings":"AAIA,wBAAsB,aAAa,kBAsClC;AAED,wBAAsB,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM;;;;;;;;;GAOjF;AAED,wBAAsB,eAAe,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM;;;;;;;;GAQnF;AAED,eAAO,MAAM,YAAY,qBAq+CxB,CAAC"}
1
+ {"version":3,"file":"seedDatabase.d.ts","sourceRoot":"","sources":["../src/seedDatabase.ts"],"names":[],"mappings":"AAIA,wBAAsB,aAAa,kBAuClC;AAED,wBAAsB,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM;;;;;;;;;GAOjF;AAED,wBAAsB,eAAe,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM;;;;;;;;GAQnF;AAED,eAAO,MAAM,YAAY,qBAq+CxB,CAAC"}
@@ -4,6 +4,7 @@ import { logger } from "./utils/logger.js";
4
4
  export async function clearDatabase() {
5
5
  // Delete in order to respect foreign key constraints
6
6
  // Delete notifications first (they reference users)
7
+ logger.info('Clearing database');
7
8
  await prisma.notification.deleteMany();
8
9
  // Delete chat-related records
9
10
  await prisma.mention.deleteMany();
@@ -77,7 +78,7 @@ export const seedDatabase = async () => {
77
78
  ]);
78
79
  // 3. Create Students (realistic names)
79
80
  const students = await Promise.all([
80
- createUser('alex.martinez@student.riverside.edu', 'student123', 'alex.martinez'),
81
+ createUser('alex.martinez@student.rverside.eidu', 'student123', 'alex.martinez'),
81
82
  createUser('sophia.williams@student.riverside.edu', 'student123', 'sophia.williams'),
82
83
  createUser('james.brown@student.riverside.edu', 'student123', 'james.brown'),
83
84
  createUser('olivia.taylor@student.riverside.edu', 'student123', 'olivia.taylor'),
@@ -10,6 +10,7 @@ declare class Logger {
10
10
  private isDevelopment;
11
11
  private mode;
12
12
  private levelColors;
13
+ private levelBgColors;
13
14
  private levelEmojis;
14
15
  private constructor();
15
16
  static getInstance(): Logger;
@@ -1 +1 @@
1
- {"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../../src/utils/logger.ts"],"names":[],"mappings":"AAAA,oBAAY,QAAQ;IAClB,IAAI,SAAS;IACb,IAAI,SAAS;IACb,KAAK,UAAU;IACf,KAAK,UAAU;CAChB;AAED,KAAK,OAAO,GAAG,QAAQ,GAAG,SAAS,GAAG,QAAQ,GAAG,SAAS,CAAC;AAwB3D,cAAM,MAAM;IACV,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAS;IAChC,OAAO,CAAC,aAAa,CAAU;IAC/B,OAAO,CAAC,IAAI,CAAU;IACtB,OAAO,CAAC,WAAW,CAA2B;IAC9C,OAAO,CAAC,WAAW,CAA2B;IAE9C,OAAO;WAuBO,WAAW,IAAI,MAAM;IAO5B,OAAO,CAAC,IAAI,EAAE,OAAO;IAI5B,OAAO,CAAC,SAAS;IAsBjB,OAAO,CAAC,aAAa;IAiBrB,OAAO,CAAC,GAAG;IAqCJ,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC;IAInD,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC;IAInD,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC;IAIpD,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC;CAG5D;AAED,eAAO,MAAM,MAAM,QAAuB,CAAC"}
1
+ {"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../../src/utils/logger.ts"],"names":[],"mappings":"AAAA,oBAAY,QAAQ;IAClB,IAAI,SAAS;IACb,IAAI,SAAS;IACb,KAAK,UAAU;IACf,KAAK,UAAU;CAChB;AAED,KAAK,OAAO,GAAG,QAAQ,GAAG,SAAS,GAAG,QAAQ,GAAG,SAAS,CAAC;AAyC3D,cAAM,MAAM;IACV,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAS;IAChC,OAAO,CAAC,aAAa,CAAU;IAC/B,OAAO,CAAC,IAAI,CAAU;IACtB,OAAO,CAAC,WAAW,CAA2B;IAC9C,OAAO,CAAC,aAAa,CAA2B;IAChD,OAAO,CAAC,WAAW,CAA2B;IAE9C,OAAO;WA8BO,WAAW,IAAI,MAAM;IAO5B,OAAO,CAAC,IAAI,EAAE,OAAO;IAI5B,OAAO,CAAC,SAAS;IAsBjB,OAAO,CAAC,aAAa;IAmBrB,OAAO,CAAC,GAAG;IAqCJ,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC;IAInD,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC;IAInD,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC;IAIpD,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC;CAG5D;AAED,eAAO,MAAM,MAAM,QAAuB,CAAC"}
@@ -17,7 +17,24 @@ const colors = {
17
17
  magenta: '\x1b[35m',
18
18
  cyan: '\x1b[36m',
19
19
  white: '\x1b[37m',
20
- gray: '\x1b[90m'
20
+ gray: '\x1b[90m',
21
+ // Background colors
22
+ bgRed: '\x1b[41m',
23
+ bgGreen: '\x1b[42m',
24
+ bgYellow: '\x1b[43m',
25
+ bgBlue: '\x1b[44m',
26
+ bgMagenta: '\x1b[45m',
27
+ bgCyan: '\x1b[46m',
28
+ bgWhite: '\x1b[47m',
29
+ bgGray: '\x1b[100m',
30
+ // Bright background colors
31
+ bgBrightRed: '\x1b[101m',
32
+ bgBrightGreen: '\x1b[102m',
33
+ bgBrightYellow: '\x1b[103m',
34
+ bgBrightBlue: '\x1b[104m',
35
+ bgBrightMagenta: '\x1b[105m',
36
+ bgBrightCyan: '\x1b[106m',
37
+ bgBrightWhite: '\x1b[107m'
21
38
  };
22
39
  class Logger {
23
40
  constructor() {
@@ -30,6 +47,12 @@ class Logger {
30
47
  [LogLevel.ERROR]: colors.red,
31
48
  [LogLevel.DEBUG]: colors.magenta
32
49
  };
50
+ this.levelBgColors = {
51
+ [LogLevel.INFO]: colors.bgBlue,
52
+ [LogLevel.WARN]: colors.bgYellow,
53
+ [LogLevel.ERROR]: colors.bgRed,
54
+ [LogLevel.DEBUG]: colors.bgMagenta
55
+ };
33
56
  this.levelEmojis = {
34
57
  [LogLevel.INFO]: 'ℹ️',
35
58
  [LogLevel.WARN]: '⚠️',
@@ -70,9 +93,11 @@ class Logger {
70
93
  formatMessage(logMessage) {
71
94
  const { level, message, timestamp, context } = logMessage;
72
95
  const color = this.levelColors[level];
96
+ const bgColor = this.levelBgColors[level];
73
97
  const emoji = this.levelEmojis[level];
74
98
  const timestampStr = colors.gray + `[${timestamp}]` + colors.reset;
75
- const levelStr = color + `[${level.toUpperCase()}]` + colors.reset;
99
+ // Use background color for level badge like Vitest
100
+ const levelStr = colors.white + bgColor + ` ${level.toUpperCase()} ` + colors.reset;
76
101
  const emojiStr = emoji + ' ';
77
102
  const messageStr = colors.bright + message + colors.reset;
78
103
  const contextStr = context
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@studious-lms/server",
3
- "version": "1.1.17",
3
+ "version": "1.1.19",
4
4
  "description": "Backend server for Studious application",
5
5
  "main": "dist/exportType.js",
6
6
  "types": "dist/exportType.d.ts",
@@ -18,7 +18,7 @@
18
18
  "generate": "npx prisma generate",
19
19
  "prepublishOnly": "npm run generate && npm run build",
20
20
  "test": "vitest",
21
- "seed": "node dist/seedDatabase.js"
21
+ "seed": "tsx src/seedDatabase.ts"
22
22
  },
23
23
  "dependencies": {
24
24
  "@google-cloud/storage": "^7.16.0",
@@ -427,4 +427,11 @@ model SchoolDevelopementProgram {
427
427
  status String @default("PENDING") // PENDING, APPROVED, REJECTED, REFERRED
428
428
 
429
429
  supportingDocumentation File[] @relation("SchoolDevelopementProgramSupportingDocumentation")
430
+ }
431
+
432
+ model EarlyAccessRequest {
433
+ id String @id @default(uuid())
434
+ email String
435
+ createdAt DateTime @default(now())
436
+ institutionSize String
430
437
  }
package/src/index.ts CHANGED
@@ -10,6 +10,7 @@ import { createTRPCContext, createCallerFactory } from './trpc.js';
10
10
  import { logger } from './utils/logger.js';
11
11
  import { setupSocketHandlers } from './socket/handlers.js';
12
12
  import { bucket } from './lib/googleCloudStorage.js';
13
+ import { prisma } from './lib/prisma.js';
13
14
 
14
15
  dotenv.config();
15
16
 
@@ -125,19 +126,176 @@ io.engine.on('connection_error', (err: Error) => {
125
126
  setupSocketHandlers(io);
126
127
 
127
128
  // File serving endpoint for secure file access
128
- app.get('/api/files/:filePath', async (req, res) => {
129
+ app.get('/api/files/:fileId', async (req, res) => {
129
130
  try {
130
- const filePath = decodeURIComponent(req.params.filePath);
131
- console.log('File request:', { filePath, originalPath: req.params.filePath });
131
+ const fileId = decodeURIComponent(req.params.fileId);
132
+ // console.log('File request:', { fileId, originalPath: req.params.fileId });
133
+
134
+ // Get user from request headers
135
+ const userHeader = req.headers['x-user'];
136
+ if (!userHeader) {
137
+ return res.status(401).json({ error: 'Authentication required' });
138
+ }
139
+
140
+ const token = typeof userHeader === 'string' ? userHeader : userHeader[0];
141
+
142
+ // Find user by session token
143
+ const user = await prisma.user.findFirst({
144
+ where: {
145
+ sessions: {
146
+ some: {
147
+ id: token
148
+ }
149
+ }
150
+ },
151
+ select: {
152
+ id: true,
153
+ username: true,
154
+ }
155
+ });
156
+
157
+ if (!user) {
158
+ return res.status(401).json({ error: 'Invalid or expired session' });
159
+ }
160
+
161
+ // Find file in database by path
162
+ const fileRecord = await prisma.file.findFirst({
163
+ where: { id: fileId },
164
+ include: {
165
+ user: true,
166
+ assignment: {
167
+ include: {
168
+ class: {
169
+ include: {
170
+ students: true,
171
+ teachers: true
172
+ }
173
+ }
174
+ }
175
+ },
176
+ submission: {
177
+ include: {
178
+ student: true,
179
+ assignment: {
180
+ include: {
181
+ class: {
182
+ include: {
183
+ teachers: true
184
+ }
185
+ }
186
+ }
187
+ }
188
+ }
189
+ },
190
+ annotations: {
191
+ include: {
192
+ student: true,
193
+ assignment: {
194
+ include: {
195
+ class: {
196
+ include: {
197
+ teachers: true
198
+ }
199
+ }
200
+ }
201
+ }
202
+ }
203
+ },
204
+ folder: {
205
+ include: {
206
+ class: {
207
+ include: {
208
+ students: true,
209
+ teachers: true
210
+ }
211
+ }
212
+ }
213
+ },
214
+ classDraft: {
215
+ include: {
216
+ students: true,
217
+ teachers: true
218
+ }
219
+ }
220
+ }
221
+ });
222
+
223
+ if (!fileRecord) {
224
+ return res.status(404).json({ error: 'File not found in database' });
225
+ }
226
+
227
+ // Check if user has permission to access this file
228
+ let hasPermission = false;
229
+
230
+ // Check if user created the file
231
+ if (fileRecord.userId === user.id) {
232
+ hasPermission = true;
233
+ }
234
+
235
+ // Check if file is related to a class where user is a member
236
+ if (!hasPermission) {
237
+ // Check assignment files
238
+ if (fileRecord.assignment?.class) {
239
+ const classData = fileRecord.assignment.class;
240
+ const isStudent = classData.students.some(student => student.id === user.id);
241
+ const isTeacher = classData.teachers.some(teacher => teacher.id === user.id);
242
+ if (isStudent || isTeacher) {
243
+ hasPermission = true;
244
+ }
245
+ }
246
+
247
+ if (!hasPermission && fileRecord.annotations) {
248
+ const annotation = fileRecord.annotations;
249
+ if (annotation.studentId === user.id) {
250
+ hasPermission = true;
251
+ } else if (annotation.assignment?.class?.teachers.some(teacher => teacher.id === user.id)) {
252
+ hasPermission = true;
253
+ }
254
+ }
255
+
256
+ // Check submission files (student can access their own submissions, teachers can access all submissions in their class)
257
+ if (!hasPermission && fileRecord.submission) {
258
+ const submission = fileRecord.submission;
259
+ if (submission.studentId === user.id) {
260
+ hasPermission = true; // Student accessing their own submission
261
+ } else if (submission.assignment?.class?.teachers.some(teacher => teacher.id === user.id)) {
262
+ hasPermission = true; // Teacher accessing submission in their class
263
+ }
264
+ }
265
+
266
+ // Check folder files
267
+ if (!hasPermission && fileRecord.folder?.class) {
268
+ const classData = fileRecord.folder.class;
269
+ const isStudent = classData.students.some(student => student.id === user.id);
270
+ const isTeacher = classData.teachers.some(teacher => teacher.id === user.id);
271
+ if (isStudent || isTeacher) {
272
+ hasPermission = true;
273
+ }
274
+ }
275
+
276
+ // Check class draft files
277
+ if (!hasPermission && fileRecord.classDraft) {
278
+ const classData = fileRecord.classDraft;
279
+ const isStudent = classData.students.some(student => student.id === user.id);
280
+ const isTeacher = classData.teachers.some(teacher => teacher.id === user.id);
281
+ if (isStudent || isTeacher) {
282
+ hasPermission = true;
283
+ }
284
+ }
285
+ }
286
+
287
+ if (!hasPermission) {
288
+ return res.status(403).json({ error: 'Access denied - insufficient permissions' });
289
+ }
290
+
291
+ const filePath = fileRecord.path;
132
292
 
133
293
  // Get file from Google Cloud Storage
134
294
  const file = bucket.file(filePath);
135
295
  const [exists] = await file.exists();
136
-
137
- console.log('File exists:', exists, 'for path:', filePath);
138
-
296
+
139
297
  if (!exists) {
140
- return res.status(404).json({ error: 'File not found', filePath });
298
+ return res.status(404).json({ error: 'File not found in storage', filePath });
141
299
  }
142
300
 
143
301
  // Get file metadata
@@ -156,14 +314,14 @@ app.get('/api/files/:filePath', async (req, res) => {
156
314
  stream.pipe(res);
157
315
 
158
316
  stream.on('error', (error) => {
159
- console.error('Error streaming file:', error);
317
+ logger.error('Error streaming file:', {error});
160
318
  if (!res.headersSent) {
161
319
  res.status(500).json({ error: 'Error streaming file' });
162
320
  }
163
321
  });
164
322
 
165
323
  } catch (error) {
166
- console.error('Error serving file:', error);
324
+ logger.error('Error serving file:', {error});
167
325
  res.status(500).json({ error: 'Internal server error' });
168
326
  }
169
327
  });
@@ -180,7 +338,6 @@ app.put('/api/upload/:filePath', async (req, res) => {
180
338
  function handleFileUpload(req: any, res: any) {
181
339
  try {
182
340
  const filePath = decodeURIComponent(req.params.filePath);
183
- console.log('File upload request:', { filePath, originalPath: req.params.filePath, method: req.method });
184
341
 
185
342
  // Set CORS headers for upload endpoint
186
343
  const origin = req.headers.origin;
@@ -217,14 +374,13 @@ function handleFileUpload(req: any, res: any) {
217
374
 
218
375
  // Handle stream events
219
376
  writeStream.on('error', (error) => {
220
- console.error('Error uploading file:', error);
377
+ logger.error('Error uploading file:', {error});
221
378
  if (!res.headersSent) {
222
379
  res.status(500).json({ error: 'Error uploading file' });
223
380
  }
224
381
  });
225
382
 
226
383
  writeStream.on('finish', () => {
227
- console.log('File uploaded successfully:', filePath);
228
384
  res.status(200).json({
229
385
  success: true,
230
386
  filePath,
@@ -236,7 +392,7 @@ function handleFileUpload(req: any, res: any) {
236
392
  req.pipe(writeStream);
237
393
 
238
394
  } catch (error) {
239
- console.error('Error handling file upload:', error);
395
+ logger.error('Error handling file upload:', {error});
240
396
  res.status(500).json({ error: 'Internal server error' });
241
397
  }
242
398
  }
@@ -705,6 +705,7 @@ export const assignmentRouter = createTRPCRouter({
705
705
  select: {
706
706
  id: true,
707
707
  username: true,
708
+ profile: true,
708
709
  },
709
710
  },
710
711
  assignment: {
@@ -27,7 +27,7 @@ export const marketingRouter = createTRPCRouter({
27
27
 
28
28
  const date = new Date();
29
29
 
30
- const id = name.slice(0, 3).toUpperCase() + date.getFullYear() + '-' + v4();
30
+ const id = name.slice(0, 3).toUpperCase() + '-' + date.getFullYear() + '-' + v4().slice(0, 4).toUpperCase();
31
31
  const schoolDevelopementProgram = await prisma.schoolDevelopementProgram.create({
32
32
  data: {
33
33
  id,
@@ -66,4 +66,21 @@ export const marketingRouter = createTRPCRouter({
66
66
  });
67
67
  return schoolDevelopementProgram;
68
68
  }),
69
+ earlyAccessRequest: publicProcedure
70
+ .input(z.object({
71
+ email: z.string(),
72
+ institutionSize: z.string(),
73
+ }))
74
+ .mutation(async ({ input }) => {
75
+ const { email, institutionSize } = input;
76
+ const earlyAccessRequest = await prisma.earlyAccessRequest.create({
77
+ data: {
78
+ email,
79
+ institutionSize,
80
+ },
81
+ });
82
+ return {
83
+ id: earlyAccessRequest.id,
84
+ };
85
+ }),
69
86
  });
@@ -5,6 +5,7 @@ import { logger } from "./utils/logger.js";
5
5
  export async function clearDatabase() {
6
6
  // Delete in order to respect foreign key constraints
7
7
  // Delete notifications first (they reference users)
8
+ logger.info('Clearing database');
8
9
  await prisma.notification.deleteMany();
9
10
 
10
11
  // Delete chat-related records
@@ -94,7 +95,7 @@ export const seedDatabase = async () => {
94
95
 
95
96
  // 3. Create Students (realistic names)
96
97
  const students = await Promise.all([
97
- createUser('alex.martinez@student.riverside.edu', 'student123', 'alex.martinez'),
98
+ createUser('alex.martinez@student.rverside.eidu', 'student123', 'alex.martinez'),
98
99
  createUser('sophia.williams@student.riverside.edu', 'student123', 'sophia.williams'),
99
100
  createUser('james.brown@student.riverside.edu', 'student123', 'james.brown'),
100
101
  createUser('olivia.taylor@student.riverside.edu', 'student123', 'olivia.taylor'),
@@ -26,7 +26,24 @@ const colors = {
26
26
  magenta: '\x1b[35m',
27
27
  cyan: '\x1b[36m',
28
28
  white: '\x1b[37m',
29
- gray: '\x1b[90m'
29
+ gray: '\x1b[90m',
30
+ // Background colors
31
+ bgRed: '\x1b[41m',
32
+ bgGreen: '\x1b[42m',
33
+ bgYellow: '\x1b[43m',
34
+ bgBlue: '\x1b[44m',
35
+ bgMagenta: '\x1b[45m',
36
+ bgCyan: '\x1b[46m',
37
+ bgWhite: '\x1b[47m',
38
+ bgGray: '\x1b[100m',
39
+ // Bright background colors
40
+ bgBrightRed: '\x1b[101m',
41
+ bgBrightGreen: '\x1b[102m',
42
+ bgBrightYellow: '\x1b[103m',
43
+ bgBrightBlue: '\x1b[104m',
44
+ bgBrightMagenta: '\x1b[105m',
45
+ bgBrightCyan: '\x1b[106m',
46
+ bgBrightWhite: '\x1b[107m'
30
47
  };
31
48
 
32
49
  class Logger {
@@ -34,6 +51,7 @@ class Logger {
34
51
  private isDevelopment: boolean;
35
52
  private mode: LogMode;
36
53
  private levelColors: Record<LogLevel, string>;
54
+ private levelBgColors: Record<LogLevel, string>;
37
55
  private levelEmojis: Record<LogLevel, string>;
38
56
 
39
57
  private constructor() {
@@ -51,6 +69,13 @@ class Logger {
51
69
  [LogLevel.DEBUG]: colors.magenta
52
70
  };
53
71
 
72
+ this.levelBgColors = {
73
+ [LogLevel.INFO]: colors.bgBlue,
74
+ [LogLevel.WARN]: colors.bgYellow,
75
+ [LogLevel.ERROR]: colors.bgRed,
76
+ [LogLevel.DEBUG]: colors.bgMagenta
77
+ };
78
+
54
79
  this.levelEmojis = {
55
80
  [LogLevel.INFO]: 'ℹ️',
56
81
  [LogLevel.WARN]: '⚠️',
@@ -95,10 +120,12 @@ class Logger {
95
120
  private formatMessage(logMessage: LogMessage): string {
96
121
  const { level, message, timestamp, context } = logMessage;
97
122
  const color = this.levelColors[level];
123
+ const bgColor = this.levelBgColors[level];
98
124
  const emoji = this.levelEmojis[level];
99
125
 
100
126
  const timestampStr = colors.gray + `[${timestamp}]` + colors.reset;
101
- const levelStr = color + `[${level.toUpperCase()}]` + colors.reset;
127
+ // Use background color for level badge like Vitest
128
+ const levelStr = colors.white + bgColor + ` ${level.toUpperCase()} ` + colors.reset;
102
129
  const emojiStr = emoji + ' ';
103
130
  const messageStr = colors.bright + message + colors.reset;
104
131
 
package/tests/setup.ts CHANGED
@@ -5,6 +5,7 @@ import { logger } from '../src/utils/logger';
5
5
  import { appRouter } from '../src/routers/_app';
6
6
  import { createTRPCContext } from '../src/trpc';
7
7
  import { Session } from '@prisma/client';
8
+ import { clearDatabase } from '../src/seedDatabase';
8
9
 
9
10
  const getCaller = async (token: string) => {
10
11
  const ctx = await createTRPCContext({
@@ -19,15 +20,8 @@ const getCaller = async (token: string) => {
19
20
 
20
21
  // Before the entire test suite runs
21
22
  beforeAll(async () => {
22
- // // Run migrations so the test DB has the latest schema
23
- // try {
24
- // logger.info('Setting up test database');
25
- // execSync('rm -f prisma/test.db');
26
- // execSync('npx prisma db push --force-reset --schema=prisma/schema.prisma');
27
-
28
- // } catch (error) {
29
- // logger.error('Error initializing test database');
30
- // }
23
+
24
+ await clearDatabase();
31
25
 
32
26
  logger.info('Getting caller');
33
27