@studious-lms/server 1.0.4 → 1.0.7
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/API_SPECIFICATION.md +1117 -0
- package/dist/exportType.js +1 -2
- package/dist/index.js +25 -30
- package/dist/lib/fileUpload.d.ts.map +1 -1
- package/dist/lib/fileUpload.js +31 -29
- package/dist/lib/googleCloudStorage.js +9 -14
- package/dist/lib/prisma.js +4 -7
- package/dist/lib/thumbnailGenerator.js +12 -20
- package/dist/middleware/auth.d.ts.map +1 -1
- package/dist/middleware/auth.js +17 -22
- package/dist/middleware/logging.js +5 -9
- package/dist/routers/_app.d.ts +3483 -1801
- package/dist/routers/_app.d.ts.map +1 -1
- package/dist/routers/_app.js +28 -27
- package/dist/routers/agenda.d.ts +13 -8
- package/dist/routers/agenda.d.ts.map +1 -1
- package/dist/routers/agenda.js +14 -17
- package/dist/routers/announcement.d.ts +4 -3
- package/dist/routers/announcement.d.ts.map +1 -1
- package/dist/routers/announcement.js +28 -31
- package/dist/routers/assignment.d.ts +282 -196
- package/dist/routers/assignment.d.ts.map +1 -1
- package/dist/routers/assignment.js +256 -202
- package/dist/routers/attendance.d.ts +5 -4
- package/dist/routers/attendance.d.ts.map +1 -1
- package/dist/routers/attendance.js +31 -34
- package/dist/routers/auth.d.ts +1 -0
- package/dist/routers/auth.d.ts.map +1 -1
- package/dist/routers/auth.js +80 -75
- package/dist/routers/class.d.ts +284 -14
- package/dist/routers/class.d.ts.map +1 -1
- package/dist/routers/class.js +435 -164
- package/dist/routers/event.d.ts +47 -38
- package/dist/routers/event.d.ts.map +1 -1
- package/dist/routers/event.js +76 -79
- package/dist/routers/file.d.ts +71 -1
- package/dist/routers/file.d.ts.map +1 -1
- package/dist/routers/file.js +267 -32
- package/dist/routers/folder.d.ts +296 -0
- package/dist/routers/folder.d.ts.map +1 -0
- package/dist/routers/folder.js +693 -0
- package/dist/routers/notifications.d.ts +103 -0
- package/dist/routers/notifications.d.ts.map +1 -0
- package/dist/routers/notifications.js +91 -0
- package/dist/routers/school.d.ts +208 -0
- package/dist/routers/school.d.ts.map +1 -0
- package/dist/routers/school.js +481 -0
- package/dist/routers/section.d.ts +1 -0
- package/dist/routers/section.d.ts.map +1 -1
- package/dist/routers/section.js +30 -33
- package/dist/routers/user.d.ts +2 -1
- package/dist/routers/user.d.ts.map +1 -1
- package/dist/routers/user.js +21 -24
- package/dist/seedDatabase.d.ts +22 -0
- package/dist/seedDatabase.d.ts.map +1 -0
- package/dist/seedDatabase.js +57 -0
- package/dist/socket/handlers.js +26 -30
- package/dist/trpc.d.ts +5 -0
- package/dist/trpc.d.ts.map +1 -1
- package/dist/trpc.js +35 -26
- package/dist/types/trpc.js +1 -2
- package/dist/utils/email.js +2 -8
- package/dist/utils/generateInviteCode.js +1 -5
- package/dist/utils/logger.d.ts.map +1 -1
- package/dist/utils/logger.js +13 -9
- package/dist/utils/prismaErrorHandler.d.ts +9 -0
- package/dist/utils/prismaErrorHandler.d.ts.map +1 -0
- package/dist/utils/prismaErrorHandler.js +234 -0
- package/dist/utils/prismaWrapper.d.ts +14 -0
- package/dist/utils/prismaWrapper.d.ts.map +1 -0
- package/dist/utils/prismaWrapper.js +64 -0
- package/package.json +17 -4
- package/prisma/migrations/20250807062924_init/migration.sql +436 -0
- package/prisma/migrations/migration_lock.toml +3 -0
- package/prisma/schema.prisma +67 -0
- package/src/index.ts +2 -2
- package/src/lib/fileUpload.ts +16 -7
- package/src/middleware/auth.ts +0 -2
- package/src/routers/_app.ts +5 -1
- package/src/routers/assignment.ts +82 -22
- package/src/routers/auth.ts +80 -54
- package/src/routers/class.ts +330 -36
- package/src/routers/file.ts +283 -20
- package/src/routers/folder.ts +755 -0
- package/src/routers/notifications.ts +93 -0
- package/src/seedDatabase.ts +66 -0
- package/src/socket/handlers.ts +4 -4
- package/src/trpc.ts +13 -0
- package/src/utils/logger.ts +14 -4
- package/src/utils/prismaErrorHandler.ts +275 -0
- package/src/utils/prismaWrapper.ts +91 -0
- package/tests/auth.test.ts +25 -0
- package/tests/class.test.ts +281 -0
- package/tests/setup.ts +98 -0
- package/tests/startup.test.ts +5 -0
- package/tsconfig.json +2 -1
- package/vitest.config.ts +11 -0
- package/dist/logger.d.ts +0 -26
- package/dist/logger.d.ts.map +0 -1
- package/dist/logger.js +0 -135
- package/src/logger.ts +0 -163
package/dist/routers/user.js
CHANGED
|
@@ -1,31 +1,28 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
const
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
type: zod_1.z.string(),
|
|
12
|
-
size: zod_1.z.number(),
|
|
13
|
-
data: zod_1.z.string(), // base64 encoded file data
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { createTRPCRouter, protectedProcedure } from "../trpc";
|
|
3
|
+
import { TRPCError } from "@trpc/server";
|
|
4
|
+
import { prisma } from "../lib/prisma";
|
|
5
|
+
import { uploadFiles } from "../lib/fileUpload";
|
|
6
|
+
const fileSchema = z.object({
|
|
7
|
+
name: z.string(),
|
|
8
|
+
type: z.string(),
|
|
9
|
+
size: z.number(),
|
|
10
|
+
data: z.string(), // base64 encoded file data
|
|
14
11
|
});
|
|
15
|
-
const updateProfileSchema =
|
|
16
|
-
profile:
|
|
12
|
+
const updateProfileSchema = z.object({
|
|
13
|
+
profile: z.record(z.any()),
|
|
17
14
|
profilePicture: fileSchema.optional(),
|
|
18
15
|
});
|
|
19
|
-
|
|
20
|
-
getProfile:
|
|
16
|
+
export const userRouter = createTRPCRouter({
|
|
17
|
+
getProfile: protectedProcedure
|
|
21
18
|
.query(async ({ ctx }) => {
|
|
22
19
|
if (!ctx.user) {
|
|
23
|
-
throw new
|
|
20
|
+
throw new TRPCError({
|
|
24
21
|
code: "UNAUTHORIZED",
|
|
25
22
|
message: "User must be authenticated",
|
|
26
23
|
});
|
|
27
24
|
}
|
|
28
|
-
const user = await
|
|
25
|
+
const user = await prisma.user.findUnique({
|
|
29
26
|
where: { id: ctx.user.id },
|
|
30
27
|
select: {
|
|
31
28
|
id: true,
|
|
@@ -34,18 +31,18 @@ exports.userRouter = (0, trpc_1.createTRPCRouter)({
|
|
|
34
31
|
},
|
|
35
32
|
});
|
|
36
33
|
if (!user) {
|
|
37
|
-
throw new
|
|
34
|
+
throw new TRPCError({
|
|
38
35
|
code: "NOT_FOUND",
|
|
39
36
|
message: "User not found",
|
|
40
37
|
});
|
|
41
38
|
}
|
|
42
39
|
return user;
|
|
43
40
|
}),
|
|
44
|
-
updateProfile:
|
|
41
|
+
updateProfile: protectedProcedure
|
|
45
42
|
.input(updateProfileSchema)
|
|
46
43
|
.mutation(async ({ ctx, input }) => {
|
|
47
44
|
if (!ctx.user) {
|
|
48
|
-
throw new
|
|
45
|
+
throw new TRPCError({
|
|
49
46
|
code: "UNAUTHORIZED",
|
|
50
47
|
message: "User must be authenticated",
|
|
51
48
|
});
|
|
@@ -53,12 +50,12 @@ exports.userRouter = (0, trpc_1.createTRPCRouter)({
|
|
|
53
50
|
let uploadedFiles = [];
|
|
54
51
|
if (input.profilePicture) {
|
|
55
52
|
// Store profile picture in a user-specific directory
|
|
56
|
-
uploadedFiles = await
|
|
53
|
+
uploadedFiles = await uploadFiles([input.profilePicture], ctx.user.id, `users/${ctx.user.id}/profile`);
|
|
57
54
|
// Add profile picture path to profile data
|
|
58
55
|
input.profile.profilePicture = uploadedFiles[0].path;
|
|
59
56
|
input.profile.profilePictureThumbnail = uploadedFiles[0].thumbnailId;
|
|
60
57
|
}
|
|
61
|
-
const updatedUser = await
|
|
58
|
+
const updatedUser = await prisma.user.update({
|
|
62
59
|
where: { id: ctx.user.id },
|
|
63
60
|
data: {
|
|
64
61
|
profile: input.profile,
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export declare function clearDatabase(): Promise<void>;
|
|
2
|
+
export declare function createUser(email: string, password: string, username: string): Promise<{
|
|
3
|
+
id: string;
|
|
4
|
+
username: string;
|
|
5
|
+
email: string;
|
|
6
|
+
password: string;
|
|
7
|
+
verified: boolean;
|
|
8
|
+
role: import(".prisma/client").$Enums.UserRole;
|
|
9
|
+
profileId: string | null;
|
|
10
|
+
schoolId: string | null;
|
|
11
|
+
}>;
|
|
12
|
+
export declare function addNotification(userId: string, title: string, content: string): Promise<{
|
|
13
|
+
id: string;
|
|
14
|
+
title: string;
|
|
15
|
+
content: string;
|
|
16
|
+
createdAt: Date;
|
|
17
|
+
read: boolean;
|
|
18
|
+
receiverId: string;
|
|
19
|
+
senderId: string | null;
|
|
20
|
+
}>;
|
|
21
|
+
export declare const seedDatabase: () => Promise<void>;
|
|
22
|
+
//# sourceMappingURL=seedDatabase.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"seedDatabase.d.ts","sourceRoot":"","sources":["../src/seedDatabase.ts"],"names":[],"mappings":"AAIA,wBAAsB,aAAa,kBAKlC;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,qBA6BxB,CAAC"}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { prisma } from "./lib/prisma";
|
|
2
|
+
import { hash } from "bcryptjs";
|
|
3
|
+
import { logger } from "./utils/logger";
|
|
4
|
+
export async function clearDatabase() {
|
|
5
|
+
await prisma.class.deleteMany();
|
|
6
|
+
await prisma.userProfile.deleteMany();
|
|
7
|
+
await prisma.session.deleteMany();
|
|
8
|
+
await prisma.user.deleteMany();
|
|
9
|
+
}
|
|
10
|
+
export async function createUser(email, password, username) {
|
|
11
|
+
logger.debug("Creating user", { email, username, password });
|
|
12
|
+
const hashedPassword = await hash(password, 10);
|
|
13
|
+
return await prisma.user.create({
|
|
14
|
+
data: { email, password: hashedPassword, username, verified: true },
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
export async function addNotification(userId, title, content) {
|
|
18
|
+
return await prisma.notification.create({
|
|
19
|
+
data: {
|
|
20
|
+
receiverId: userId,
|
|
21
|
+
title,
|
|
22
|
+
content,
|
|
23
|
+
},
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
export const seedDatabase = async () => {
|
|
27
|
+
await clearDatabase();
|
|
28
|
+
logger.info('Cleared database');
|
|
29
|
+
// create two test users
|
|
30
|
+
const teacher1 = await createUser('teacher1@studious.sh', '123456', 'teacher1');
|
|
31
|
+
const student1 = await createUser('student1@studious.sh', '123456', 'student1');
|
|
32
|
+
// create a class
|
|
33
|
+
const class1 = await prisma.class.create({
|
|
34
|
+
data: {
|
|
35
|
+
name: 'Class 1',
|
|
36
|
+
subject: 'Math',
|
|
37
|
+
section: 'A',
|
|
38
|
+
teachers: {
|
|
39
|
+
connect: {
|
|
40
|
+
id: teacher1.id,
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
students: {
|
|
44
|
+
connect: {
|
|
45
|
+
id: student1.id,
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
});
|
|
50
|
+
await addNotification(teacher1.id, 'Welcome to Studious', 'Welcome to Studious');
|
|
51
|
+
await addNotification(student1.id, 'Welcome to Studious', 'Welcome to Studious');
|
|
52
|
+
};
|
|
53
|
+
(async () => {
|
|
54
|
+
logger.info('Seeding database');
|
|
55
|
+
await seedDatabase();
|
|
56
|
+
logger.info('Database seeded');
|
|
57
|
+
})();
|
package/dist/socket/handlers.js
CHANGED
|
@@ -1,33 +1,30 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
exports.setupSocketHandlers = void 0;
|
|
4
|
-
const logger_1 = require("../utils/logger");
|
|
5
|
-
const setupSocketHandlers = (io) => {
|
|
1
|
+
import { logger } from '../utils/logger';
|
|
2
|
+
export const setupSocketHandlers = (io) => {
|
|
6
3
|
io.on('connection', (socket) => {
|
|
7
|
-
|
|
4
|
+
logger.info('Client connected', { socketId: socket.id });
|
|
8
5
|
socket.on('disconnect', (reason) => {
|
|
9
|
-
|
|
6
|
+
logger.info('Client disconnected', { socketId: socket.id, reason });
|
|
10
7
|
});
|
|
11
8
|
socket.on('error', (error) => {
|
|
12
|
-
|
|
9
|
+
logger.error('Socket error', { socketId: socket.id, error });
|
|
13
10
|
});
|
|
14
11
|
// Class Room Management
|
|
15
12
|
socket.on('create-class', (data) => {
|
|
16
13
|
io.emit('class-created', data.class);
|
|
17
|
-
|
|
14
|
+
logger.info('Class created', { class: data.class });
|
|
18
15
|
});
|
|
19
16
|
socket.on('delete-class', (data) => {
|
|
20
17
|
io.emit('class-deleted', data.classId);
|
|
21
|
-
|
|
18
|
+
logger.info('Class deleted', { classId: data.classId });
|
|
22
19
|
});
|
|
23
20
|
socket.on('update-class', (data) => {
|
|
24
21
|
io.emit('class-updated', data.class);
|
|
25
|
-
|
|
22
|
+
logger.info('Class updated', { class: data.class });
|
|
26
23
|
});
|
|
27
24
|
socket.on('join-class', (classId, callback) => {
|
|
28
25
|
try {
|
|
29
26
|
socket.join(`class-${classId}`);
|
|
30
|
-
|
|
27
|
+
logger.info('Client joined class room', { socketId: socket.id, classId });
|
|
31
28
|
if (callback) {
|
|
32
29
|
callback(classId);
|
|
33
30
|
}
|
|
@@ -36,9 +33,9 @@ const setupSocketHandlers = (io) => {
|
|
|
36
33
|
}
|
|
37
34
|
}
|
|
38
35
|
catch (error) {
|
|
39
|
-
|
|
36
|
+
logger.error('Error joining class room', { socketId: socket.id, classId, error });
|
|
40
37
|
if (callback) {
|
|
41
|
-
callback(
|
|
38
|
+
callback(classId);
|
|
42
39
|
}
|
|
43
40
|
}
|
|
44
41
|
});
|
|
@@ -46,56 +43,56 @@ const setupSocketHandlers = (io) => {
|
|
|
46
43
|
socket.on('assignment-create', (data) => {
|
|
47
44
|
if (data.classId && data.assignment) {
|
|
48
45
|
io.in(`class-${data.classId}`).emit('assignment-created', data.assignment);
|
|
49
|
-
|
|
46
|
+
logger.info('Assignment created', { classId: data.classId, assignmentId: data.assignment.id });
|
|
50
47
|
}
|
|
51
48
|
else {
|
|
52
|
-
|
|
49
|
+
logger.error('Invalid assignment data format', { data });
|
|
53
50
|
}
|
|
54
51
|
});
|
|
55
52
|
socket.on('assignment-update', (data) => {
|
|
56
53
|
io.in(`class-${data.classId}`).emit('assignment-updated', data.assignment);
|
|
57
|
-
|
|
54
|
+
logger.info('Assignment updated', { classId: data.classId, assignmentId: data.assignment.id });
|
|
58
55
|
});
|
|
59
56
|
socket.on('assignment-delete', (data) => {
|
|
60
57
|
io.in(`class-${data.classId}`).emit('assignment-deleted', data.assignmentId);
|
|
61
|
-
|
|
58
|
+
logger.info('Assignment deleted', { classId: data.classId, assignmentId: data.assignmentId });
|
|
62
59
|
});
|
|
63
60
|
// Submission Events
|
|
64
61
|
socket.on('submission-update', (data) => {
|
|
65
62
|
io.in(`class-${data.classId}`).emit('submission-updated', data.submission);
|
|
66
|
-
|
|
63
|
+
logger.info('Submission updated', { classId: data.classId, submissionId: data.submission.id });
|
|
67
64
|
});
|
|
68
65
|
// Announcement Events
|
|
69
66
|
socket.on('new-announcement', (data) => {
|
|
70
67
|
io.in(`class-${data.classId}`).emit('announcement-created', data.announcement);
|
|
71
|
-
|
|
68
|
+
logger.info('New announcement created', { classId: data.classId, announcementId: data.announcement.id });
|
|
72
69
|
});
|
|
73
70
|
// Section Events
|
|
74
71
|
socket.on('section-create', (data) => {
|
|
75
72
|
io.in(`class-${data.classId}`).emit('section-created', data.section);
|
|
76
|
-
|
|
73
|
+
logger.info('Section created', { classId: data.classId, sectionId: data.section.id });
|
|
77
74
|
});
|
|
78
75
|
socket.on('section-update', (data) => {
|
|
79
76
|
io.in(`class-${data.classId}`).emit('section-updated', data.section);
|
|
80
|
-
|
|
77
|
+
logger.info('Section updated', { classId: data.classId, sectionId: data.section.id });
|
|
81
78
|
});
|
|
82
79
|
socket.on('section-delete', (data) => {
|
|
83
80
|
io.in(`class-${data.classId}`).emit('section-deleted', data.sectionId);
|
|
84
|
-
|
|
81
|
+
logger.info('Section deleted', { classId: data.classId, sectionId: data.sectionId });
|
|
85
82
|
});
|
|
86
83
|
// Member Events
|
|
87
84
|
socket.on('member-update', (data) => {
|
|
88
85
|
io.in(`class-${data.classId}`).emit('member-updated', data.member);
|
|
89
|
-
|
|
86
|
+
logger.info('Member updated', { classId: data.classId, memberId: data.member.id });
|
|
90
87
|
});
|
|
91
88
|
socket.on('member-delete', (data) => {
|
|
92
89
|
io.in(`class-${data.classId}`).emit('member-deleted', data.memberId);
|
|
93
|
-
|
|
90
|
+
logger.info('Member deleted', { classId: data.classId, memberId: data.memberId });
|
|
94
91
|
});
|
|
95
92
|
// Attendance Events
|
|
96
93
|
socket.on('attendance-update', (data) => {
|
|
97
94
|
io.in(`class-${data.classId}`).emit('attendance-updated', data.attendance);
|
|
98
|
-
|
|
95
|
+
logger.info('Attendance updated', { classId: data.classId, attendanceId: data.attendance.id });
|
|
99
96
|
});
|
|
100
97
|
// Event Events
|
|
101
98
|
socket.on('event-create', (data) => {
|
|
@@ -105,7 +102,7 @@ const setupSocketHandlers = (io) => {
|
|
|
105
102
|
else {
|
|
106
103
|
io.emit('event-created', data.event);
|
|
107
104
|
}
|
|
108
|
-
|
|
105
|
+
logger.info('Event created', { classId: data.classId, eventId: data.event.id });
|
|
109
106
|
});
|
|
110
107
|
socket.on('event-update', (data) => {
|
|
111
108
|
if (data.classId) {
|
|
@@ -114,7 +111,7 @@ const setupSocketHandlers = (io) => {
|
|
|
114
111
|
else {
|
|
115
112
|
io.emit('event-updated', data.event);
|
|
116
113
|
}
|
|
117
|
-
|
|
114
|
+
logger.info('Event updated', { classId: data.classId, eventId: data.event.id });
|
|
118
115
|
});
|
|
119
116
|
socket.on('event-delete', (data) => {
|
|
120
117
|
if (data.classId) {
|
|
@@ -123,8 +120,7 @@ const setupSocketHandlers = (io) => {
|
|
|
123
120
|
else {
|
|
124
121
|
io.emit('event-deleted', data.eventId);
|
|
125
122
|
}
|
|
126
|
-
|
|
123
|
+
logger.info('Event deleted', { classId: data.classId, eventId: data.eventId });
|
|
127
124
|
});
|
|
128
125
|
});
|
|
129
126
|
};
|
|
130
|
-
exports.setupSocketHandlers = setupSocketHandlers;
|
package/dist/trpc.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { TRPCError } from '@trpc/server';
|
|
2
2
|
import { Request, Response } from 'express';
|
|
3
3
|
import { z } from 'zod';
|
|
4
|
+
import { PrismaErrorInfo } from './utils/prismaErrorHandler';
|
|
4
5
|
interface CreateContextOptions {
|
|
5
6
|
req: Request;
|
|
6
7
|
res: Response;
|
|
@@ -28,6 +29,7 @@ export declare const t: import("@trpc/server").TRPCRootObject<Context, object, {
|
|
|
28
29
|
}): {
|
|
29
30
|
data: {
|
|
30
31
|
zodError: z.typeToFlattenedError<any, string> | null;
|
|
32
|
+
prismaError: PrismaErrorInfo | null;
|
|
31
33
|
code: import("@trpc/server").TRPC_ERROR_CODE_KEY;
|
|
32
34
|
httpStatus: number;
|
|
33
35
|
path?: string;
|
|
@@ -42,6 +44,7 @@ export declare const t: import("@trpc/server").TRPCRootObject<Context, object, {
|
|
|
42
44
|
errorShape: {
|
|
43
45
|
data: {
|
|
44
46
|
zodError: z.typeToFlattenedError<any, string> | null;
|
|
47
|
+
prismaError: PrismaErrorInfo | null;
|
|
45
48
|
code: import("@trpc/server").TRPC_ERROR_CODE_KEY;
|
|
46
49
|
httpStatus: number;
|
|
47
50
|
path?: string;
|
|
@@ -58,6 +61,7 @@ export declare const createTRPCRouter: import("@trpc/server").TRPCRouterBuilder<
|
|
|
58
61
|
errorShape: {
|
|
59
62
|
data: {
|
|
60
63
|
zodError: z.typeToFlattenedError<any, string> | null;
|
|
64
|
+
prismaError: PrismaErrorInfo | null;
|
|
61
65
|
code: import("@trpc/server").TRPC_ERROR_CODE_KEY;
|
|
62
66
|
httpStatus: number;
|
|
63
67
|
path?: string;
|
|
@@ -86,6 +90,7 @@ export declare const createCallerFactory: import("@trpc/server").TRPCRouterCalle
|
|
|
86
90
|
errorShape: {
|
|
87
91
|
data: {
|
|
88
92
|
zodError: z.typeToFlattenedError<any, string> | null;
|
|
93
|
+
prismaError: PrismaErrorInfo | null;
|
|
89
94
|
code: import("@trpc/server").TRPC_ERROR_CODE_KEY;
|
|
90
95
|
httpStatus: number;
|
|
91
96
|
path?: string;
|
package/dist/trpc.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"trpc.d.ts","sourceRoot":"","sources":["../src/trpc.ts"],"names":[],"mappings":"AAAA,OAAO,EAAY,SAAS,EAAE,MAAM,cAAc,CAAC;AAMnD,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAC5C,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;
|
|
1
|
+
{"version":3,"file":"trpc.d.ts","sourceRoot":"","sources":["../src/trpc.ts"],"names":[],"mappings":"AAAA,OAAO,EAAY,SAAS,EAAE,MAAM,cAAc,CAAC;AAMnD,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAC5C,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAqB,eAAe,EAAE,MAAM,4BAA4B,CAAC;AAEhF,UAAU,oBAAoB;IAC5B,GAAG,EAAE,OAAO,CAAC;IACb,GAAG,EAAE,QAAQ,CAAC;CACf;AAED,MAAM,MAAM,OAAO,GAAG;IACpB,GAAG,EAAE,OAAO,CAAC;IACb,GAAG,EAAE,QAAQ,CAAC;IACd,IAAI,EAAE;QAAE,EAAE,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;IAC5B,IAAI,CAAC,EAAE;QACL,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,aAAa,CAAC,EAAE,MAAM,CAAC;KACxB,CAAC;CACH,CAAC;AAEF,eAAO,MAAM,iBAAiB,GAAU,MAAM,oBAAoB,KAAG,OAAO,CAAC,OAAO,CAwBnF,CAAC;AAEF,eAAO,MAAM,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA8BZ,CAAC;AAOH,eAAO,MAAM,gBAAgB;;;;;;;;;;;;;;;;EAAW,CAAC;AACzC,eAAO,MAAM,eAAe,yOAAqC,CAAC;AAGlE,eAAO,MAAM,kBAAkB,yOAAgC,CAAC;AAChE,eAAO,MAAM,6BAA6B;;;;uHAEnB,CAAC;AACxB,eAAO,MAAM,yBAAyB;;;;uHAEd,CAAC;AAIzB,eAAO,MAAM,mBAAmB;;;;;;;;;;;;;;;;EAAwB,CAAC"}
|
package/dist/trpc.js
CHANGED
|
@@ -1,18 +1,16 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
const
|
|
10
|
-
const zod_2 = require("zod");
|
|
11
|
-
const createTRPCContext = async (opts) => {
|
|
1
|
+
import { initTRPC } from '@trpc/server';
|
|
2
|
+
import { ZodError } from 'zod';
|
|
3
|
+
import { logger } from './utils/logger';
|
|
4
|
+
import { prisma } from './lib/prisma';
|
|
5
|
+
import { createLoggingMiddleware } from './middleware/logging';
|
|
6
|
+
import { createAuthMiddleware } from './middleware/auth';
|
|
7
|
+
import { z } from 'zod';
|
|
8
|
+
import { handlePrismaError } from './utils/prismaErrorHandler';
|
|
9
|
+
export const createTRPCContext = async (opts) => {
|
|
12
10
|
const { req, res } = opts;
|
|
13
11
|
// Get user from session/token
|
|
14
12
|
const token = req.headers.authorization?.split(' ')[1];
|
|
15
|
-
const user = token ? await
|
|
13
|
+
const user = token ? await prisma.user.findFirst({
|
|
16
14
|
where: {
|
|
17
15
|
sessions: {
|
|
18
16
|
some: {
|
|
@@ -31,37 +29,48 @@ const createTRPCContext = async (opts) => {
|
|
|
31
29
|
meta: {},
|
|
32
30
|
};
|
|
33
31
|
};
|
|
34
|
-
|
|
35
|
-
exports.t = server_1.initTRPC.context().create({
|
|
32
|
+
export const t = initTRPC.context().create({
|
|
36
33
|
errorFormatter({ shape, error }) {
|
|
37
|
-
|
|
34
|
+
// Handle Prisma errors specifically
|
|
35
|
+
let prismaErrorInfo = null;
|
|
36
|
+
if (error.cause) {
|
|
37
|
+
try {
|
|
38
|
+
prismaErrorInfo = handlePrismaError(error.cause);
|
|
39
|
+
}
|
|
40
|
+
catch (e) {
|
|
41
|
+
// If Prisma error handling fails, continue with normal error handling
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
logger.error('tRPC Error', {
|
|
38
45
|
code: shape.code,
|
|
39
46
|
message: error.message,
|
|
40
47
|
cause: error.cause,
|
|
41
48
|
stack: error.stack,
|
|
49
|
+
prismaError: prismaErrorInfo,
|
|
42
50
|
});
|
|
43
51
|
return {
|
|
44
52
|
...shape,
|
|
45
53
|
data: {
|
|
46
54
|
...shape.data,
|
|
47
|
-
zodError: error.cause instanceof
|
|
55
|
+
zodError: error.cause instanceof ZodError ? error.cause.flatten() : null,
|
|
56
|
+
prismaError: prismaErrorInfo,
|
|
48
57
|
},
|
|
49
58
|
};
|
|
50
59
|
},
|
|
51
60
|
});
|
|
52
61
|
// Create middleware
|
|
53
|
-
const loggingMiddleware =
|
|
54
|
-
const { isAuthed, isMemberInClass, isTeacherInClass } =
|
|
62
|
+
const loggingMiddleware = createLoggingMiddleware(t);
|
|
63
|
+
const { isAuthed, isMemberInClass, isTeacherInClass } = createAuthMiddleware(t);
|
|
55
64
|
// Base procedures
|
|
56
|
-
|
|
57
|
-
|
|
65
|
+
export const createTRPCRouter = t.router;
|
|
66
|
+
export const publicProcedure = t.procedure.use(loggingMiddleware);
|
|
58
67
|
// Protected procedures
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
.input(
|
|
68
|
+
export const protectedProcedure = publicProcedure.use(isAuthed);
|
|
69
|
+
export const protectedClassMemberProcedure = protectedProcedure
|
|
70
|
+
.input(z.object({ classId: z.string() }).passthrough())
|
|
62
71
|
.use(isMemberInClass);
|
|
63
|
-
|
|
64
|
-
.input(
|
|
72
|
+
export const protectedTeacherProcedure = protectedProcedure
|
|
73
|
+
.input(z.object({ classId: z.string() }).passthrough())
|
|
65
74
|
.use(isTeacherInClass);
|
|
66
75
|
// Create caller factory
|
|
67
|
-
|
|
76
|
+
export const createCallerFactory = t.createCallerFactory;
|
package/dist/types/trpc.js
CHANGED
|
@@ -1,2 +1 @@
|
|
|
1
|
-
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
1
|
+
export {};
|
package/dist/utils/email.js
CHANGED
|
@@ -1,11 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
-
};
|
|
5
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.transport = void 0;
|
|
7
|
-
const nodemailer_1 = __importDefault(require("nodemailer"));
|
|
8
|
-
exports.transport = nodemailer_1.default.createTransport({
|
|
1
|
+
import nodemailer from 'nodemailer';
|
|
2
|
+
export const transport = nodemailer.createTransport({
|
|
9
3
|
host: process.env.EMAIL_HOST,
|
|
10
4
|
port: 587,
|
|
11
5
|
secure: false,
|
|
@@ -1,11 +1,7 @@
|
|
|
1
|
-
"use strict";
|
|
2
1
|
/**
|
|
3
2
|
* Generates a random invite code
|
|
4
3
|
* @returns {string} The invite code with length 5
|
|
5
4
|
*/
|
|
6
|
-
|
|
7
|
-
exports.generateInviteCode = void 0;
|
|
8
|
-
const generateInviteCode = () => {
|
|
5
|
+
export const generateInviteCode = () => {
|
|
9
6
|
return (Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15)).slice(0, 5);
|
|
10
7
|
};
|
|
11
|
-
exports.generateInviteCode = generateInviteCode;
|
|
@@ -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;
|
|
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"}
|
package/dist/utils/logger.js
CHANGED
|
@@ -1,13 +1,10 @@
|
|
|
1
|
-
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.logger = exports.LogLevel = void 0;
|
|
4
|
-
var LogLevel;
|
|
1
|
+
export var LogLevel;
|
|
5
2
|
(function (LogLevel) {
|
|
6
3
|
LogLevel["INFO"] = "info";
|
|
7
4
|
LogLevel["WARN"] = "warn";
|
|
8
5
|
LogLevel["ERROR"] = "error";
|
|
9
6
|
LogLevel["DEBUG"] = "debug";
|
|
10
|
-
})(LogLevel || (
|
|
7
|
+
})(LogLevel || (LogLevel = {}));
|
|
11
8
|
// ANSI color codes
|
|
12
9
|
const colors = {
|
|
13
10
|
reset: '\x1b[0m',
|
|
@@ -24,7 +21,8 @@ const colors = {
|
|
|
24
21
|
};
|
|
25
22
|
class Logger {
|
|
26
23
|
constructor() {
|
|
27
|
-
this.isDevelopment = process.env.NODE_ENV === 'development';
|
|
24
|
+
// this.isDevelopment = process.env.NODE_ENV === 'development';
|
|
25
|
+
this.isDevelopment = true;
|
|
28
26
|
this.mode = process.env.LOG_MODE || 'normal';
|
|
29
27
|
this.levelColors = {
|
|
30
28
|
[LogLevel.INFO]: colors.blue,
|
|
@@ -83,8 +81,14 @@ class Logger {
|
|
|
83
81
|
return `${timestampStr} ${levelStr} ${emojiStr}${messageStr}${contextStr}`;
|
|
84
82
|
}
|
|
85
83
|
log(level, message, context) {
|
|
86
|
-
|
|
87
|
-
|
|
84
|
+
if (!this.shouldLog(level))
|
|
85
|
+
return;
|
|
86
|
+
if (level == LogLevel.WARN || level == LogLevel.ERROR) {
|
|
87
|
+
if (level == LogLevel.ERROR) {
|
|
88
|
+
// alert me
|
|
89
|
+
}
|
|
90
|
+
// store in database
|
|
91
|
+
}
|
|
88
92
|
const logMessage = {
|
|
89
93
|
level,
|
|
90
94
|
message,
|
|
@@ -121,4 +125,4 @@ class Logger {
|
|
|
121
125
|
this.log(LogLevel.DEBUG, message, context);
|
|
122
126
|
}
|
|
123
127
|
}
|
|
124
|
-
|
|
128
|
+
export const logger = Logger.getInstance();
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export interface PrismaErrorInfo {
|
|
2
|
+
message: string;
|
|
3
|
+
code?: string;
|
|
4
|
+
meta?: any;
|
|
5
|
+
details?: string;
|
|
6
|
+
}
|
|
7
|
+
export declare function handlePrismaError(error: unknown): PrismaErrorInfo;
|
|
8
|
+
export declare function getFieldDisplayName(fieldName: string): string;
|
|
9
|
+
//# sourceMappingURL=prismaErrorHandler.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"prismaErrorHandler.d.ts","sourceRoot":"","sources":["../../src/utils/prismaErrorHandler.ts"],"names":[],"mappings":"AAMA,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,GAAG,CAAC;IACX,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,OAAO,GAAG,eAAe,CA4BjE;AAiMD,wBAAgB,mBAAmB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAwC7D"}
|