@studious-lms/server 1.1.18 → 1.1.20
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 +155 -11
- package/dist/routers/_app.d.ts +48 -0
- package/dist/routers/_app.d.ts.map +1 -1
- package/dist/routers/assignment.d.ts +24 -0
- package/dist/routers/assignment.d.ts.map +1 -1
- package/dist/routers/assignment.js +2 -0
- package/dist/seedDatabase.d.ts.map +1 -1
- package/dist/seedDatabase.js +2 -1
- package/dist/utils/logger.d.ts +1 -0
- package/dist/utils/logger.d.ts.map +1 -1
- package/dist/utils/logger.js +27 -2
- package/package.json +2 -2
- package/src/index.ts +169 -13
- package/src/routers/assignment.ts +2 -0
- package/src/seedDatabase.ts +2 -1
- package/src/utils/logger.ts +29 -2
- package/tests/setup.ts +3 -9
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/:
|
|
115
|
+
app.get('/api/files/:fileId', async (req, res) => {
|
|
115
116
|
try {
|
|
116
|
-
const
|
|
117
|
-
console.log('File request:', {
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
351
|
+
logger.error('Error handling file upload:', { error });
|
|
208
352
|
res.status(500).json({ error: 'Internal server error' });
|
|
209
353
|
}
|
|
210
354
|
}
|
package/dist/routers/_app.d.ts
CHANGED
|
@@ -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;
|
|
@@ -1402,6 +1414,18 @@ export declare const appRouter: import("@trpc/server").TRPCBuiltRouter<{
|
|
|
1402
1414
|
student: {
|
|
1403
1415
|
id: string;
|
|
1404
1416
|
username: string;
|
|
1417
|
+
profile: {
|
|
1418
|
+
id: string;
|
|
1419
|
+
location: string | null;
|
|
1420
|
+
userId: string;
|
|
1421
|
+
createdAt: Date;
|
|
1422
|
+
displayName: string | null;
|
|
1423
|
+
bio: string | null;
|
|
1424
|
+
website: string | null;
|
|
1425
|
+
profilePicture: string | null;
|
|
1426
|
+
profilePictureThumbnail: string | null;
|
|
1427
|
+
updatedAt: Date;
|
|
1428
|
+
} | null;
|
|
1405
1429
|
};
|
|
1406
1430
|
attachments: {
|
|
1407
1431
|
path: string;
|
|
@@ -5312,6 +5336,18 @@ export declare const createCaller: import("@trpc/server").TRPCRouterCaller<{
|
|
|
5312
5336
|
student: {
|
|
5313
5337
|
id: string;
|
|
5314
5338
|
username: string;
|
|
5339
|
+
profile: {
|
|
5340
|
+
id: string;
|
|
5341
|
+
location: string | null;
|
|
5342
|
+
userId: string;
|
|
5343
|
+
createdAt: Date;
|
|
5344
|
+
displayName: string | null;
|
|
5345
|
+
bio: string | null;
|
|
5346
|
+
website: string | null;
|
|
5347
|
+
profilePicture: string | null;
|
|
5348
|
+
profilePictureThumbnail: string | null;
|
|
5349
|
+
updatedAt: Date;
|
|
5350
|
+
} | null;
|
|
5315
5351
|
};
|
|
5316
5352
|
attachments: {
|
|
5317
5353
|
path: string;
|
|
@@ -5413,6 +5449,18 @@ export declare const createCaller: import("@trpc/server").TRPCRouterCaller<{
|
|
|
5413
5449
|
student: {
|
|
5414
5450
|
id: string;
|
|
5415
5451
|
username: string;
|
|
5452
|
+
profile: {
|
|
5453
|
+
id: string;
|
|
5454
|
+
location: string | null;
|
|
5455
|
+
userId: string;
|
|
5456
|
+
createdAt: Date;
|
|
5457
|
+
displayName: string | null;
|
|
5458
|
+
bio: string | null;
|
|
5459
|
+
website: string | null;
|
|
5460
|
+
profilePicture: string | null;
|
|
5461
|
+
profilePictureThumbnail: string | null;
|
|
5462
|
+
updatedAt: Date;
|
|
5463
|
+
} | null;
|
|
5416
5464
|
};
|
|
5417
5465
|
attachments: {
|
|
5418
5466
|
path: string;
|
|
@@ -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
|
|
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;
|
|
@@ -549,6 +561,18 @@ export declare const assignmentRouter: import("@trpc/server").TRPCBuiltRouter<{
|
|
|
549
561
|
student: {
|
|
550
562
|
id: string;
|
|
551
563
|
username: string;
|
|
564
|
+
profile: {
|
|
565
|
+
id: string;
|
|
566
|
+
location: string | null;
|
|
567
|
+
userId: string;
|
|
568
|
+
createdAt: Date;
|
|
569
|
+
displayName: string | null;
|
|
570
|
+
bio: string | null;
|
|
571
|
+
website: string | null;
|
|
572
|
+
profilePicture: string | null;
|
|
573
|
+
profilePictureThumbnail: string | null;
|
|
574
|
+
updatedAt: Date;
|
|
575
|
+
} | null;
|
|
552
576
|
};
|
|
553
577
|
attachments: {
|
|
554
578
|
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
|
|
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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAisD3B,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: {
|
|
@@ -739,6 +740,7 @@ export const assignmentRouter = createTRPCRouter({
|
|
|
739
740
|
select: {
|
|
740
741
|
id: true,
|
|
741
742
|
username: true,
|
|
743
|
+
profile: true,
|
|
742
744
|
},
|
|
743
745
|
},
|
|
744
746
|
assignment: {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"seedDatabase.d.ts","sourceRoot":"","sources":["../src/seedDatabase.ts"],"names":[],"mappings":"AAIA,wBAAsB,aAAa,
|
|
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"}
|
package/dist/seedDatabase.js
CHANGED
|
@@ -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.
|
|
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'),
|
package/dist/utils/logger.d.ts
CHANGED
|
@@ -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;
|
|
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"}
|
package/dist/utils/logger.js
CHANGED
|
@@ -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
|
-
|
|
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.
|
|
3
|
+
"version": "1.1.20",
|
|
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": "
|
|
21
|
+
"seed": "tsx src/seedDatabase.ts"
|
|
22
22
|
},
|
|
23
23
|
"dependencies": {
|
|
24
24
|
"@google-cloud/storage": "^7.16.0",
|
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/:
|
|
129
|
+
app.get('/api/files/:fileId', async (req, res) => {
|
|
129
130
|
try {
|
|
130
|
-
const
|
|
131
|
-
console.log('File request:', {
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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: {
|
|
@@ -811,6 +812,7 @@ export const assignmentRouter = createTRPCRouter({
|
|
|
811
812
|
select: {
|
|
812
813
|
id: true,
|
|
813
814
|
username: true,
|
|
815
|
+
profile: true,
|
|
814
816
|
},
|
|
815
817
|
},
|
|
816
818
|
assignment: {
|
package/src/seedDatabase.ts
CHANGED
|
@@ -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.
|
|
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'),
|
package/src/utils/logger.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
23
|
-
|
|
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
|
|