@studious-lms/server 1.0.1 → 1.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. package/dist/index.js +4 -4
  2. package/dist/middleware/auth.js +1 -1
  3. package/dist/middleware/logging.js +1 -1
  4. package/dist/routers/_app.js +1 -1
  5. package/dist/routers/agenda.js +1 -1
  6. package/dist/routers/announcement.js +1 -1
  7. package/dist/routers/assignment.js +3 -3
  8. package/dist/routers/attendance.js +1 -1
  9. package/dist/routers/auth.js +2 -2
  10. package/dist/routers/class.js +2 -2
  11. package/dist/routers/event.js +1 -1
  12. package/dist/routers/file.js +2 -2
  13. package/dist/routers/section.js +1 -1
  14. package/dist/routers/user.js +2 -2
  15. package/dist/trpc.js +2 -2
  16. package/package.json +1 -6
  17. package/prisma/schema.prisma +228 -0
  18. package/src/exportType.ts +9 -0
  19. package/src/index.ts +94 -0
  20. package/src/lib/fileUpload.ts +163 -0
  21. package/src/lib/googleCloudStorage.ts +94 -0
  22. package/src/lib/prisma.ts +16 -0
  23. package/src/lib/thumbnailGenerator.ts +185 -0
  24. package/src/logger.ts +163 -0
  25. package/src/middleware/auth.ts +191 -0
  26. package/src/middleware/logging.ts +54 -0
  27. package/src/routers/_app.ts +34 -0
  28. package/src/routers/agenda.ts +79 -0
  29. package/src/routers/announcement.ts +134 -0
  30. package/src/routers/assignment.ts +1614 -0
  31. package/src/routers/attendance.ts +284 -0
  32. package/src/routers/auth.ts +286 -0
  33. package/src/routers/class.ts +753 -0
  34. package/src/routers/event.ts +509 -0
  35. package/src/routers/file.ts +96 -0
  36. package/src/routers/section.ts +138 -0
  37. package/src/routers/user.ts +82 -0
  38. package/src/socket/handlers.ts +143 -0
  39. package/src/trpc.ts +90 -0
  40. package/src/types/trpc.ts +15 -0
  41. package/src/utils/email.ts +11 -0
  42. package/src/utils/generateInviteCode.ts +8 -0
  43. package/src/utils/logger.ts +156 -0
  44. package/tsconfig.json +17 -0
  45. package/generated/prisma/client.d.ts +0 -1
  46. package/generated/prisma/client.js +0 -4
  47. package/generated/prisma/default.d.ts +0 -1
  48. package/generated/prisma/default.js +0 -4
  49. package/generated/prisma/edge.d.ts +0 -1
  50. package/generated/prisma/edge.js +0 -389
  51. package/generated/prisma/index-browser.js +0 -375
  52. package/generated/prisma/index.d.ts +0 -34865
  53. package/generated/prisma/index.js +0 -410
  54. package/generated/prisma/libquery_engine-darwin-arm64.dylib.node +0 -0
  55. package/generated/prisma/package.json +0 -140
  56. package/generated/prisma/runtime/edge-esm.js +0 -34
  57. package/generated/prisma/runtime/edge.js +0 -34
  58. package/generated/prisma/runtime/index-browser.d.ts +0 -370
  59. package/generated/prisma/runtime/index-browser.js +0 -16
  60. package/generated/prisma/runtime/library.d.ts +0 -3647
  61. package/generated/prisma/runtime/library.js +0 -146
  62. package/generated/prisma/runtime/react-native.js +0 -83
  63. package/generated/prisma/runtime/wasm.js +0 -35
  64. package/generated/prisma/schema.prisma +0 -304
  65. package/generated/prisma/wasm.d.ts +0 -1
  66. package/generated/prisma/wasm.js +0 -375
@@ -0,0 +1,191 @@
1
+ import { TRPCError } from '@trpc/server';
2
+ import { prisma } from '../lib/prisma';
3
+ import type { MiddlewareContext } from '../types/trpc';
4
+
5
+ export const createAuthMiddleware = (t: any) => {
6
+
7
+ // Auth middleware
8
+ const isAuthed = t.middleware(async ({ next, ctx }: MiddlewareContext) => {
9
+ const startTime = Date.now();
10
+ // Get user from request headers
11
+ const userHeader = ctx.req.headers['x-user'];
12
+
13
+ if (!userHeader) {
14
+ throw new TRPCError({
15
+ code: 'UNAUTHORIZED',
16
+ message: 'Not authenticated - no token found',
17
+ });
18
+ }
19
+
20
+ try {
21
+ const token = typeof userHeader === 'string' ? userHeader : userHeader[0];
22
+
23
+ // Find user by session token
24
+ const user = await prisma.user.findFirst({
25
+ where: {
26
+ sessions: {
27
+ some: {
28
+ id: token
29
+ }
30
+ }
31
+ },
32
+ select: {
33
+ id: true,
34
+ username: true,
35
+ // institutionId: true,
36
+ }
37
+ });
38
+
39
+ if (!user) {
40
+ throw new TRPCError({
41
+ code: 'UNAUTHORIZED',
42
+ message: 'Invalid or expired session',
43
+ });
44
+ }
45
+
46
+ console.log(user)
47
+
48
+ return next({
49
+ ctx: {
50
+ ...ctx,
51
+ user,
52
+ },
53
+ });
54
+ } catch (error) {
55
+ console.log(error)
56
+ throw new TRPCError({
57
+ code: 'UNAUTHORIZED',
58
+ message: 'Invalid user data',
59
+ });
60
+ }
61
+ });
62
+
63
+ // Add computed flags middleware
64
+ const addComputedFlags = t.middleware(async ({ next, ctx }: MiddlewareContext) => {
65
+ if (!ctx.user) {
66
+ throw new TRPCError({
67
+ code: 'UNAUTHORIZED',
68
+ message: 'Not authenticated',
69
+ });
70
+ }
71
+
72
+ // Get all classes where user is a teacher
73
+ const teacherClasses = await prisma.class.findMany({
74
+ where: {
75
+ teachers: {
76
+ some: {
77
+ id: ctx.user.id
78
+ }
79
+ }
80
+ },
81
+ select: {
82
+ id: true
83
+ }
84
+ });
85
+
86
+ return next({
87
+ ctx: {
88
+ ...ctx,
89
+ isTeacher: teacherClasses.length > 0,
90
+ teacherClassIds: teacherClasses.map((c: { id: string }) => c.id)
91
+ }
92
+ });
93
+ });
94
+
95
+ // Student middleware
96
+ const isMemberInClass = t.middleware(async ({ next, ctx, input }: MiddlewareContext) => {
97
+ if (!ctx.user) {
98
+ throw new TRPCError({
99
+ code: 'UNAUTHORIZED',
100
+ message: 'Not authenticated',
101
+ });
102
+ }
103
+
104
+ const classId = (input as { classId: string })?.classId;
105
+
106
+ if (!classId) {
107
+ throw new TRPCError({
108
+ code: 'BAD_REQUEST',
109
+ message: 'classId is required',
110
+ });
111
+ }
112
+
113
+ const isMember = await prisma.class.findFirst({
114
+ where: {
115
+ id: classId,
116
+ OR: [
117
+ {
118
+ students: {
119
+ some: {
120
+ id: ctx.user.id
121
+ }
122
+ }
123
+ },
124
+ {
125
+ teachers: {
126
+ some: {
127
+ id: ctx.user.id
128
+ }
129
+ }
130
+ }
131
+ ]
132
+ }
133
+ });
134
+
135
+ if (!isMember) {
136
+ throw new TRPCError({
137
+ code: 'FORBIDDEN',
138
+ message: 'Not a member in this class',
139
+ });
140
+ }
141
+
142
+ return next();
143
+ });
144
+
145
+ // Teacher middleware
146
+ const isTeacherInClass = t.middleware(async ({ next, ctx, input }: MiddlewareContext) => {
147
+ if (!ctx.user) {
148
+ throw new TRPCError({
149
+ code: 'UNAUTHORIZED',
150
+ message: 'Not authenticated',
151
+ });
152
+ }
153
+
154
+ const classId = input.classId;
155
+ if (!classId) {
156
+ throw new TRPCError({
157
+ code: 'BAD_REQUEST',
158
+ message: 'classId is required',
159
+ });
160
+ }
161
+
162
+ const isTeacher = await prisma.class.findFirst({
163
+ where: {
164
+ id: classId,
165
+ teachers: {
166
+ some: {
167
+ id: ctx.user.id
168
+ }
169
+ }
170
+ }
171
+ });
172
+
173
+
174
+
175
+ if (!isTeacher) {
176
+ throw new TRPCError({
177
+ code: 'FORBIDDEN',
178
+ message: 'Not a teacher in this class',
179
+ });
180
+ }
181
+
182
+ return next();
183
+ });
184
+
185
+ return {
186
+ isAuthed,
187
+ addComputedFlags,
188
+ isMemberInClass,
189
+ isTeacherInClass,
190
+ };
191
+ };
@@ -0,0 +1,54 @@
1
+ import type { MiddlewareContext } from '../types/trpc';
2
+ import { logger } from '../utils/logger';
3
+
4
+ export const createLoggingMiddleware = (t: any) => {
5
+ return t.middleware(async ({ path, type, next, ctx }: MiddlewareContext) => {
6
+ const start = Date.now();
7
+ const requestId = crypto.randomUUID();
8
+
9
+ // Log request
10
+ logger.info('tRPC Request', {
11
+ requestId,
12
+ path,
13
+ type,
14
+ // input,
15
+ timestamp: new Date().toISOString(),
16
+ });
17
+
18
+ try {
19
+ const result = await next();
20
+ const durationMs = Date.now() - start;
21
+
22
+ // Log successful response
23
+ logger.info('tRPC Response', {
24
+ requestId,
25
+ path,
26
+ type,
27
+ durationMs,
28
+ ok: result.ok,
29
+ timestamp: new Date().toISOString(),
30
+ });
31
+
32
+ return result;
33
+ } catch (error) {
34
+ const durationMs = Date.now() - start;
35
+
36
+ // Log error response
37
+ logger.error('tRPC Error' + path, {
38
+ requestId,
39
+ path,
40
+ type,
41
+ durationMs,
42
+ error: error instanceof Error ? {
43
+ path,
44
+ name: error.name,
45
+ message: error.message,
46
+ stack: error.stack,
47
+ } : error,
48
+ timestamp: new Date().toISOString(),
49
+ });
50
+
51
+ throw error;
52
+ }
53
+ });
54
+ };
@@ -0,0 +1,34 @@
1
+ import { createTRPCRouter } from "../trpc";
2
+ import { classRouter } from "./class";
3
+ import { announcementRouter } from "./announcement";
4
+ import { assignmentRouter } from "./assignment";
5
+ import { userRouter } from "./user";
6
+ import { createCallerFactory } from "../trpc";
7
+ import type { inferRouterInputs, inferRouterOutputs } from "@trpc/server";
8
+ import { sectionRouter } from "./section";
9
+ import { attendanceRouter } from "./attendance";
10
+ import { eventRouter } from "./event";
11
+ import { authRouter } from "./auth";
12
+ import { agendaRouter } from "./agenda";
13
+ import { fileRouter } from "./file";
14
+
15
+ export const appRouter = createTRPCRouter({
16
+ class: classRouter,
17
+ announcement: announcementRouter,
18
+ assignment: assignmentRouter,
19
+ user: userRouter,
20
+ section: sectionRouter,
21
+ attendance: attendanceRouter,
22
+ event: eventRouter,
23
+ auth: authRouter,
24
+ agenda: agendaRouter,
25
+ file: fileRouter,
26
+ });
27
+
28
+ // Export type router type definition
29
+ export type AppRouter = typeof appRouter;
30
+ export type RouterInputs = inferRouterInputs<AppRouter>;
31
+ export type RouterOutputs = inferRouterOutputs<AppRouter>;
32
+
33
+ // Export caller
34
+ export const createCaller = createCallerFactory(appRouter);
@@ -0,0 +1,79 @@
1
+ import { z } from "zod";
2
+ import { createTRPCRouter, protectedProcedure } from "../trpc";
3
+ import { prisma } from "../lib/prisma";
4
+ import { TRPCError } from "@trpc/server";
5
+ import { addDays, startOfDay, endOfDay } from "date-fns";
6
+
7
+ export const agendaRouter = createTRPCRouter({
8
+ get: protectedProcedure
9
+ .input(z.object({
10
+ weekStart: z.string(),
11
+ }))
12
+ .query(async ({ ctx, input }) => {
13
+ if (!ctx.user) {
14
+ throw new TRPCError({
15
+ code: "UNAUTHORIZED",
16
+ message: "You must be logged in to get your agenda",
17
+ });
18
+ }
19
+
20
+ const weekStart = startOfDay(new Date(input.weekStart));
21
+ const weekEnd = endOfDay(addDays(weekStart, 6));
22
+
23
+ const [personalEvents, classEvents] = await Promise.all([
24
+ // Get personal events
25
+ prisma.event.findMany({
26
+ where: {
27
+ userId: ctx.user.id,
28
+ startTime: {
29
+ gte: weekStart,
30
+ lte: weekEnd,
31
+ },
32
+ class: {
33
+ is: null,
34
+ },
35
+ },
36
+ include: {
37
+ class: true,
38
+ },
39
+ }),
40
+ // Get class events
41
+ prisma.event.findMany({
42
+ where: {
43
+ class: {
44
+ OR: [
45
+ {
46
+ teachers: {
47
+ some: {
48
+ id: ctx.user.id,
49
+ },
50
+ },
51
+ },
52
+ {
53
+ students: {
54
+ some: {
55
+ id: ctx.user.id,
56
+ },
57
+ },
58
+ },
59
+ ],
60
+ },
61
+ startTime: {
62
+ gte: weekStart,
63
+ lte: weekEnd,
64
+ },
65
+ },
66
+ include: {
67
+ class: true,
68
+ },
69
+ }),
70
+ ]);
71
+
72
+ return {
73
+ events: {
74
+ personal: personalEvents,
75
+ class: classEvents,
76
+ },
77
+ };
78
+ }),
79
+ });
@@ -0,0 +1,134 @@
1
+ import { z } from "zod";
2
+ import { createTRPCRouter, protectedClassMemberProcedure, protectedTeacherProcedure, protectedProcedure } from "../trpc";
3
+ import { prisma } from "../lib/prisma";
4
+ import { TRPCError } from "@trpc/server";
5
+
6
+ const AnnouncementSelect = {
7
+ id: true,
8
+ teacher: {
9
+ select: {
10
+ id: true,
11
+ username: true,
12
+ },
13
+ },
14
+ remarks: true,
15
+ createdAt: true,
16
+ };
17
+
18
+ export const announcementRouter = createTRPCRouter({
19
+ getAll: protectedClassMemberProcedure
20
+ .input(z.object({
21
+ classId: z.string(),
22
+ }))
23
+ .query(async ({ ctx, input }) => {
24
+ const announcements = await prisma.announcement.findMany({
25
+ where: {
26
+ classId: input.classId,
27
+ },
28
+ select: AnnouncementSelect,
29
+ orderBy: {
30
+ createdAt: 'desc',
31
+ },
32
+ });
33
+
34
+ return {
35
+ announcements,
36
+ };
37
+ }),
38
+
39
+ create: protectedTeacherProcedure
40
+ .input(z.object({
41
+ classId: z.string(),
42
+ remarks: z.string(),
43
+ }))
44
+ .mutation(async ({ ctx, input }) => {
45
+ const announcement = await prisma.announcement.create({
46
+ data: {
47
+ remarks: input.remarks,
48
+ teacher: {
49
+ connect: {
50
+ id: ctx.user?.id,
51
+ },
52
+ },
53
+ class: {
54
+ connect: {
55
+ id: input.classId,
56
+ },
57
+ },
58
+ },
59
+ select: AnnouncementSelect,
60
+ });
61
+
62
+ return {
63
+ announcement,
64
+ };
65
+ }),
66
+
67
+ update: protectedProcedure
68
+ .input(z.object({
69
+ id: z.string(),
70
+ data: z.object({
71
+ content: z.string(),
72
+ }),
73
+ }))
74
+ .mutation(async ({ ctx, input }) => {
75
+
76
+ const announcement = await prisma.announcement.findUnique({
77
+ where: { id: input.id },
78
+ include: {
79
+ class: {
80
+ include: {
81
+ teachers: true,
82
+ },
83
+ },
84
+ },
85
+ });
86
+
87
+ if (!announcement) {
88
+ throw new TRPCError({
89
+ code: "NOT_FOUND",
90
+ message: "Announcement not found",
91
+ });
92
+ }
93
+
94
+ const updatedAnnouncement = await prisma.announcement.update({
95
+ where: { id: input.id },
96
+ data: {
97
+ remarks: input.data.content,
98
+ },
99
+ });
100
+
101
+ return { announcement: updatedAnnouncement };
102
+ }),
103
+
104
+ delete: protectedProcedure
105
+ .input(z.object({
106
+ id: z.string(),
107
+ }))
108
+ .mutation(async ({ ctx, input }) => {
109
+
110
+ const announcement = await prisma.announcement.findUnique({
111
+ where: { id: input.id },
112
+ include: {
113
+ class: {
114
+ include: {
115
+ teachers: true,
116
+ },
117
+ },
118
+ },
119
+ });
120
+
121
+ if (!announcement) {
122
+ throw new TRPCError({
123
+ code: "NOT_FOUND",
124
+ message: "Announcement not found",
125
+ });
126
+ }
127
+
128
+ await prisma.announcement.delete({
129
+ where: { id: input.id },
130
+ });
131
+
132
+ return { success: true };
133
+ }),
134
+ });