@studious-lms/server 1.0.0

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 (103) hide show
  1. package/LICENSE.txt +8 -0
  2. package/README.md +143 -0
  3. package/dist/exportType.d.ts +9 -0
  4. package/dist/exportType.d.ts.map +1 -0
  5. package/dist/exportType.js +7 -0
  6. package/dist/index.d.ts +2 -0
  7. package/dist/index.d.ts.map +1 -0
  8. package/dist/index.js +82 -0
  9. package/dist/lib/fileUpload.d.ts +38 -0
  10. package/dist/lib/fileUpload.d.ts.map +1 -0
  11. package/dist/lib/fileUpload.js +136 -0
  12. package/dist/lib/googleCloudStorage.d.ts +20 -0
  13. package/dist/lib/googleCloudStorage.d.ts.map +1 -0
  14. package/dist/lib/googleCloudStorage.js +88 -0
  15. package/dist/lib/prisma.d.ts +8 -0
  16. package/dist/lib/prisma.d.ts.map +1 -0
  17. package/dist/lib/prisma.js +11 -0
  18. package/dist/lib/thumbnailGenerator.d.ts +23 -0
  19. package/dist/lib/thumbnailGenerator.d.ts.map +1 -0
  20. package/dist/lib/thumbnailGenerator.js +180 -0
  21. package/dist/logger.d.ts +26 -0
  22. package/dist/logger.d.ts.map +1 -0
  23. package/dist/logger.js +135 -0
  24. package/dist/middleware/auth.d.ts +7 -0
  25. package/dist/middleware/auth.d.ts.map +1 -0
  26. package/dist/middleware/auth.js +170 -0
  27. package/dist/middleware/logging.d.ts +2 -0
  28. package/dist/middleware/logging.d.ts.map +1 -0
  29. package/dist/middleware/logging.js +51 -0
  30. package/dist/routers/_app.d.ts +4369 -0
  31. package/dist/routers/_app.d.ts.map +1 -0
  32. package/dist/routers/_app.js +29 -0
  33. package/dist/routers/agenda.d.ts +66 -0
  34. package/dist/routers/agenda.d.ts.map +1 -0
  35. package/dist/routers/agenda.js +78 -0
  36. package/dist/routers/announcement.d.ts +79 -0
  37. package/dist/routers/announcement.d.ts.map +1 -0
  38. package/dist/routers/announcement.js +122 -0
  39. package/dist/routers/assignment.d.ts +1051 -0
  40. package/dist/routers/assignment.d.ts.map +1 -0
  41. package/dist/routers/assignment.js +1497 -0
  42. package/dist/routers/attendance.d.ts +99 -0
  43. package/dist/routers/attendance.d.ts.map +1 -0
  44. package/dist/routers/attendance.js +269 -0
  45. package/dist/routers/auth.d.ts +87 -0
  46. package/dist/routers/auth.d.ts.map +1 -0
  47. package/dist/routers/auth.js +255 -0
  48. package/dist/routers/class.d.ts +427 -0
  49. package/dist/routers/class.d.ts.map +1 -0
  50. package/dist/routers/class.js +693 -0
  51. package/dist/routers/event.d.ts +249 -0
  52. package/dist/routers/event.d.ts.map +1 -0
  53. package/dist/routers/event.js +467 -0
  54. package/dist/routers/file.d.ts +27 -0
  55. package/dist/routers/file.d.ts.map +1 -0
  56. package/dist/routers/file.js +88 -0
  57. package/dist/routers/section.d.ts +51 -0
  58. package/dist/routers/section.d.ts.map +1 -0
  59. package/dist/routers/section.js +123 -0
  60. package/dist/routers/user.d.ts +49 -0
  61. package/dist/routers/user.d.ts.map +1 -0
  62. package/dist/routers/user.js +74 -0
  63. package/dist/socket/handlers.d.ts +3 -0
  64. package/dist/socket/handlers.d.ts.map +1 -0
  65. package/dist/socket/handlers.js +130 -0
  66. package/dist/trpc.d.ts +147 -0
  67. package/dist/trpc.d.ts.map +1 -0
  68. package/dist/trpc.js +67 -0
  69. package/dist/types/trpc.d.ts +16 -0
  70. package/dist/types/trpc.d.ts.map +1 -0
  71. package/dist/types/trpc.js +2 -0
  72. package/dist/utils/email.d.ts +3 -0
  73. package/dist/utils/email.d.ts.map +1 -0
  74. package/dist/utils/email.js +16 -0
  75. package/dist/utils/generateInviteCode.d.ts +6 -0
  76. package/dist/utils/generateInviteCode.d.ts.map +1 -0
  77. package/dist/utils/generateInviteCode.js +11 -0
  78. package/dist/utils/logger.d.ts +27 -0
  79. package/dist/utils/logger.d.ts.map +1 -0
  80. package/dist/utils/logger.js +124 -0
  81. package/generated/prisma/client.d.ts +1 -0
  82. package/generated/prisma/client.js +4 -0
  83. package/generated/prisma/default.d.ts +1 -0
  84. package/generated/prisma/default.js +4 -0
  85. package/generated/prisma/edge.d.ts +1 -0
  86. package/generated/prisma/edge.js +389 -0
  87. package/generated/prisma/index-browser.js +375 -0
  88. package/generated/prisma/index.d.ts +34865 -0
  89. package/generated/prisma/index.js +410 -0
  90. package/generated/prisma/libquery_engine-darwin-arm64.dylib.node +0 -0
  91. package/generated/prisma/package.json +140 -0
  92. package/generated/prisma/runtime/edge-esm.js +34 -0
  93. package/generated/prisma/runtime/edge.js +34 -0
  94. package/generated/prisma/runtime/index-browser.d.ts +370 -0
  95. package/generated/prisma/runtime/index-browser.js +16 -0
  96. package/generated/prisma/runtime/library.d.ts +3647 -0
  97. package/generated/prisma/runtime/library.js +146 -0
  98. package/generated/prisma/runtime/react-native.js +83 -0
  99. package/generated/prisma/runtime/wasm.js +35 -0
  100. package/generated/prisma/schema.prisma +304 -0
  101. package/generated/prisma/wasm.d.ts +1 -0
  102. package/generated/prisma/wasm.js +375 -0
  103. package/package.json +46 -0
@@ -0,0 +1,180 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.generateMediaThumbnail = generateMediaThumbnail;
7
+ exports.generateThumbnail = generateThumbnail;
8
+ exports.storeThumbnail = storeThumbnail;
9
+ const sharp_1 = __importDefault(require("sharp"));
10
+ const prisma_1 = require("./prisma");
11
+ const googleCloudStorage_1 = require("./googleCloudStorage");
12
+ // Thumbnail size configuration
13
+ const THUMBNAIL_WIDTH = 200;
14
+ const THUMBNAIL_HEIGHT = 200;
15
+ // File type configurations
16
+ const SUPPORTED_IMAGE_TYPES = [
17
+ 'image/jpeg',
18
+ 'image/png',
19
+ 'image/gif',
20
+ 'image/webp',
21
+ 'image/tiff',
22
+ 'image/bmp',
23
+ 'image/avif'
24
+ ];
25
+ const DOCUMENT_TYPES = [
26
+ 'application/pdf',
27
+ 'application/msword',
28
+ 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', // .docx
29
+ 'application/vnd.ms-excel',
30
+ 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', // .xlsx
31
+ 'application/vnd.ms-powerpoint',
32
+ 'application/vnd.openxmlformats-officedocument.presentationml.presentation', // .pptx
33
+ 'text/plain',
34
+ 'text/csv',
35
+ 'application/json',
36
+ 'text/html',
37
+ 'text/javascript',
38
+ 'text/css'
39
+ ];
40
+ const VIDEO_TYPES = [
41
+ 'video/mp4',
42
+ 'video/webm',
43
+ 'video/ogg',
44
+ 'video/quicktime'
45
+ ];
46
+ const AUDIO_TYPES = [
47
+ 'audio/mpeg',
48
+ 'audio/ogg',
49
+ 'audio/wav',
50
+ 'audio/webm'
51
+ ];
52
+ /**
53
+ * Generates a thumbnail for an image or PDF file
54
+ * @param fileBuffer The file buffer
55
+ * @param fileType The MIME type of the file
56
+ * @returns Thumbnail buffer
57
+ */
58
+ async function generateMediaThumbnail(fileBuffer, fileType) {
59
+ if (fileType === 'application/pdf') {
60
+ // For PDFs, we need to use a different approach
61
+ try {
62
+ return await (0, sharp_1.default)(fileBuffer, {
63
+ density: 300, // Higher density for better quality
64
+ page: 0 // First page only
65
+ })
66
+ .resize(THUMBNAIL_WIDTH, THUMBNAIL_HEIGHT, {
67
+ fit: 'inside',
68
+ withoutEnlargement: true,
69
+ })
70
+ .jpeg({ quality: 80 })
71
+ .toBuffer();
72
+ }
73
+ catch (error) {
74
+ console.warn('Failed to generate PDF thumbnail:', error);
75
+ return generateGenericThumbnail(fileType);
76
+ }
77
+ }
78
+ // For regular images
79
+ return (0, sharp_1.default)(fileBuffer)
80
+ .resize(THUMBNAIL_WIDTH, THUMBNAIL_HEIGHT, {
81
+ fit: 'inside',
82
+ withoutEnlargement: true,
83
+ })
84
+ .jpeg({ quality: 80 })
85
+ .toBuffer();
86
+ }
87
+ /**
88
+ * Generates a generic icon-based thumbnail for a file type
89
+ * @param fileType The MIME type of the file
90
+ * @returns Thumbnail buffer
91
+ */
92
+ async function generateGenericThumbnail(fileType) {
93
+ // Create a blank canvas with a colored background based on file type
94
+ const canvas = (0, sharp_1.default)({
95
+ create: {
96
+ width: THUMBNAIL_WIDTH,
97
+ height: THUMBNAIL_HEIGHT,
98
+ channels: 4,
99
+ background: { r: 245, g: 245, b: 245, alpha: 1 }
100
+ }
101
+ });
102
+ // Add a colored overlay based on file type
103
+ let color = { r: 200, g: 200, b: 200, alpha: 0.5 }; // Default gray
104
+ if (DOCUMENT_TYPES.includes(fileType)) {
105
+ color = { r: 52, g: 152, b: 219, alpha: 0.5 }; // Blue for documents
106
+ }
107
+ else if (VIDEO_TYPES.includes(fileType)) {
108
+ color = { r: 231, g: 76, b: 60, alpha: 0.5 }; // Red for videos
109
+ }
110
+ else if (AUDIO_TYPES.includes(fileType)) {
111
+ color = { r: 46, g: 204, b: 113, alpha: 0.5 }; // Green for audio
112
+ }
113
+ return canvas
114
+ .composite([{
115
+ input: Buffer.from([color.r, color.g, color.b, Math.floor(color.alpha * 255)]),
116
+ raw: {
117
+ width: 1,
118
+ height: 1,
119
+ channels: 4
120
+ },
121
+ tile: true,
122
+ blend: 'overlay'
123
+ }])
124
+ .jpeg({ quality: 80 })
125
+ .toBuffer();
126
+ }
127
+ /**
128
+ * Generates a thumbnail for a file
129
+ * @param fileName The name of the file in Google Cloud Storage
130
+ * @param fileType The MIME type of the file
131
+ * @returns The thumbnail buffer or null if thumbnail generation is not supported
132
+ */
133
+ async function generateThumbnail(fileName, fileType) {
134
+ try {
135
+ const signedUrl = await (0, googleCloudStorage_1.getSignedUrl)(fileName);
136
+ const response = await fetch(signedUrl);
137
+ if (!response.ok) {
138
+ throw new Error(`Failed to download file from storage: ${response.status} ${response.statusText}`);
139
+ }
140
+ const fileBuffer = await response.arrayBuffer();
141
+ if (SUPPORTED_IMAGE_TYPES.includes(fileType) || fileType === 'application/pdf') {
142
+ try {
143
+ const thumbnail = await generateMediaThumbnail(Buffer.from(fileBuffer), fileType);
144
+ return thumbnail;
145
+ }
146
+ catch (error) {
147
+ return generateGenericThumbnail(fileType);
148
+ }
149
+ }
150
+ else if ([...DOCUMENT_TYPES, ...VIDEO_TYPES, ...AUDIO_TYPES].includes(fileType)) {
151
+ return generateGenericThumbnail(fileType);
152
+ }
153
+ return null; // Unsupported file type
154
+ }
155
+ catch (error) {
156
+ return null;
157
+ }
158
+ }
159
+ /**
160
+ * Stores a thumbnail in Google Cloud Storage and creates a File entry
161
+ * @param thumbnailBuffer The thumbnail buffer to store
162
+ * @param originalFileName The original file name
163
+ * @param userId The user ID who owns the file
164
+ * @returns The ID of the created thumbnail File
165
+ */
166
+ async function storeThumbnail(thumbnailBuffer, originalFileName, userId) {
167
+ // Convert buffer to base64 for uploadFile function
168
+ const base64Data = `data:image/jpeg;base64,${thumbnailBuffer.toString('base64')}`;
169
+ const thumbnailFileName = await (0, googleCloudStorage_1.uploadFile)(base64Data, `thumbnails/${originalFileName}_thumb`, 'image/jpeg');
170
+ // Create a new File entry for the thumbnail
171
+ const newThumbnail = await prisma_1.prisma.file.create({
172
+ data: {
173
+ name: `${originalFileName}_thumb.jpg`,
174
+ path: thumbnailFileName,
175
+ type: 'image/jpeg',
176
+ userId: userId,
177
+ },
178
+ });
179
+ return newThumbnail.id;
180
+ }
@@ -0,0 +1,26 @@
1
+ export declare enum LogLevel {
2
+ INFO = "info",
3
+ WARN = "warn",
4
+ ERROR = "error",
5
+ DEBUG = "debug"
6
+ }
7
+ type LogMode = 'silent' | 'minimal' | 'normal' | 'verbose';
8
+ declare class Logger {
9
+ private static instance;
10
+ private isDevelopment;
11
+ private mode;
12
+ private levelEmojis;
13
+ private constructor();
14
+ static getInstance(): Logger;
15
+ setMode(mode: LogMode): void;
16
+ private shouldLog;
17
+ private formatMessage;
18
+ private log;
19
+ info(message: string, context?: Record<string, any>): void;
20
+ warn(message: string, context?: Record<string, any>): void;
21
+ error(message: string, context?: Record<string, any>): void;
22
+ debug(message: string, context?: Record<string, any>): void;
23
+ }
24
+ export declare const logger: Logger;
25
+ export {};
26
+ //# sourceMappingURL=logger.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../src/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;AAuC3D,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;IAE9C,OAAO;WAYO,WAAW,IAAI,MAAM;IAO5B,OAAO,CAAC,IAAI,EAAE,OAAO;IAI5B,OAAO,CAAC,SAAS;IAWjB,OAAO,CAAC,aAAa;IA8BrB,OAAO,CAAC,GAAG;IA6BJ,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/logger.js ADDED
@@ -0,0 +1,135 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.logger = exports.LogLevel = void 0;
4
+ var LogLevel;
5
+ (function (LogLevel) {
6
+ LogLevel["INFO"] = "info";
7
+ LogLevel["WARN"] = "warn";
8
+ LogLevel["ERROR"] = "error";
9
+ LogLevel["DEBUG"] = "debug";
10
+ })(LogLevel || (exports.LogLevel = LogLevel = {}));
11
+ // Environment detection
12
+ const isNode = typeof process !== 'undefined' && process.versions != null && process.versions.node != null;
13
+ const isBrowser = typeof window !== 'undefined' && typeof window.document !== 'undefined';
14
+ // ANSI color codes for Node.js
15
+ const ansiColors = {
16
+ reset: '\x1b[0m',
17
+ bright: '\x1b[1m',
18
+ dim: '\x1b[2m',
19
+ red: '\x1b[31m',
20
+ green: '\x1b[32m',
21
+ yellow: '\x1b[33m',
22
+ blue: '\x1b[34m',
23
+ magenta: '\x1b[35m',
24
+ cyan: '\x1b[36m',
25
+ white: '\x1b[37m',
26
+ gray: '\x1b[90m'
27
+ };
28
+ // CSS color codes for browser console
29
+ const cssColors = {
30
+ reset: '%c',
31
+ info: 'color: #2196F3; font-weight: bold;',
32
+ warn: 'color: #FFC107; font-weight: bold;',
33
+ error: 'color: #F44336; font-weight: bold;',
34
+ debug: 'color: #9C27B0; font-weight: bold;',
35
+ gray: 'color: #9E9E9E;',
36
+ context: 'color: #757575; font-style: italic;'
37
+ };
38
+ class Logger {
39
+ constructor() {
40
+ this.isDevelopment = process.env.NODE_ENV === 'development';
41
+ this.mode = process.env.LOG_MODE || 'normal';
42
+ this.levelEmojis = {
43
+ [LogLevel.INFO]: 'ℹ️',
44
+ [LogLevel.WARN]: '⚠️',
45
+ [LogLevel.ERROR]: '❌',
46
+ [LogLevel.DEBUG]: '🔍'
47
+ };
48
+ }
49
+ static getInstance() {
50
+ if (!Logger.instance) {
51
+ Logger.instance = new Logger();
52
+ }
53
+ return Logger.instance;
54
+ }
55
+ setMode(mode) {
56
+ this.mode = mode;
57
+ }
58
+ shouldLog(level) {
59
+ const silent = [LogLevel.ERROR];
60
+ const minimal = [LogLevel.ERROR, LogLevel.WARN];
61
+ const normal = [LogLevel.ERROR, LogLevel.WARN, LogLevel.INFO];
62
+ if (this.mode === 'silent')
63
+ return silent.includes(level);
64
+ if (this.mode === 'minimal')
65
+ return minimal.includes(level);
66
+ if (this.mode === 'normal')
67
+ return normal.includes(level);
68
+ return true; // verbose mode
69
+ }
70
+ formatMessage(logMessage) {
71
+ const { level, message, timestamp, context } = logMessage;
72
+ const emoji = this.levelEmojis[level];
73
+ const timestampStr = `[${timestamp}]`;
74
+ const levelStr = `[${level.toUpperCase()}]`;
75
+ const messageStr = `${emoji} ${message}`;
76
+ const contextStr = context
77
+ ? `\nContext: ${JSON.stringify(context, null, 2)}`
78
+ : '';
79
+ if (isNode) {
80
+ const color = ansiColors[level === LogLevel.INFO ? 'blue' :
81
+ level === LogLevel.WARN ? 'yellow' :
82
+ level === LogLevel.ERROR ? 'red' : 'magenta'];
83
+ return `${ansiColors.gray}${timestampStr}${ansiColors.reset} ${color}${levelStr}${ansiColors.reset} ${messageStr}${contextStr}`;
84
+ }
85
+ else {
86
+ const color = cssColors[level];
87
+ return [
88
+ `${timestampStr} ${levelStr} ${messageStr}${contextStr}`,
89
+ cssColors.reset,
90
+ color,
91
+ cssColors.gray,
92
+ cssColors.context
93
+ ];
94
+ }
95
+ }
96
+ log(level, message, context) {
97
+ if (!this.shouldLog(level))
98
+ return;
99
+ const logMessage = {
100
+ level,
101
+ message,
102
+ timestamp: new Date().toISOString(),
103
+ context
104
+ };
105
+ const formattedMessage = this.formatMessage(logMessage);
106
+ switch (level) {
107
+ case LogLevel.ERROR:
108
+ console.error(...(Array.isArray(formattedMessage) ? formattedMessage : [formattedMessage]));
109
+ break;
110
+ case LogLevel.WARN:
111
+ console.warn(...(Array.isArray(formattedMessage) ? formattedMessage : [formattedMessage]));
112
+ break;
113
+ case LogLevel.DEBUG:
114
+ if (this.isDevelopment) {
115
+ console.debug(...(Array.isArray(formattedMessage) ? formattedMessage : [formattedMessage]));
116
+ }
117
+ break;
118
+ default:
119
+ console.log(...(Array.isArray(formattedMessage) ? formattedMessage : [formattedMessage]));
120
+ }
121
+ }
122
+ info(message, context) {
123
+ this.log(LogLevel.INFO, message, context);
124
+ }
125
+ warn(message, context) {
126
+ this.log(LogLevel.WARN, message, context);
127
+ }
128
+ error(message, context) {
129
+ this.log(LogLevel.ERROR, message, context);
130
+ }
131
+ debug(message, context) {
132
+ this.log(LogLevel.DEBUG, message, context);
133
+ }
134
+ }
135
+ exports.logger = Logger.getInstance();
@@ -0,0 +1,7 @@
1
+ export declare const createAuthMiddleware: (t: any) => {
2
+ isAuthed: any;
3
+ addComputedFlags: any;
4
+ isMemberInClass: any;
5
+ isTeacherInClass: any;
6
+ };
7
+ //# sourceMappingURL=auth.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../src/middleware/auth.ts"],"names":[],"mappings":"AAIA,eAAO,MAAM,oBAAoB,GAAI,GAAG,GAAG;;;;;CA0L1C,CAAC"}
@@ -0,0 +1,170 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createAuthMiddleware = void 0;
4
+ const server_1 = require("@trpc/server");
5
+ const prisma_1 = require("@lib/prisma");
6
+ const createAuthMiddleware = (t) => {
7
+ // Auth middleware
8
+ const isAuthed = t.middleware(async ({ next, ctx }) => {
9
+ const startTime = Date.now();
10
+ // Get user from request headers
11
+ const userHeader = ctx.req.headers['x-user'];
12
+ if (!userHeader) {
13
+ throw new server_1.TRPCError({
14
+ code: 'UNAUTHORIZED',
15
+ message: 'Not authenticated - no token found',
16
+ });
17
+ }
18
+ try {
19
+ const token = typeof userHeader === 'string' ? userHeader : userHeader[0];
20
+ // Find user by session token
21
+ const user = await prisma_1.prisma.user.findFirst({
22
+ where: {
23
+ sessions: {
24
+ some: {
25
+ id: token
26
+ }
27
+ }
28
+ },
29
+ select: {
30
+ id: true,
31
+ username: true,
32
+ // institutionId: true,
33
+ }
34
+ });
35
+ if (!user) {
36
+ throw new server_1.TRPCError({
37
+ code: 'UNAUTHORIZED',
38
+ message: 'Invalid or expired session',
39
+ });
40
+ }
41
+ console.log(user);
42
+ return next({
43
+ ctx: {
44
+ ...ctx,
45
+ user,
46
+ },
47
+ });
48
+ }
49
+ catch (error) {
50
+ console.log(error);
51
+ throw new server_1.TRPCError({
52
+ code: 'UNAUTHORIZED',
53
+ message: 'Invalid user data',
54
+ });
55
+ }
56
+ });
57
+ // Add computed flags middleware
58
+ const addComputedFlags = t.middleware(async ({ next, ctx }) => {
59
+ if (!ctx.user) {
60
+ throw new server_1.TRPCError({
61
+ code: 'UNAUTHORIZED',
62
+ message: 'Not authenticated',
63
+ });
64
+ }
65
+ // Get all classes where user is a teacher
66
+ const teacherClasses = await prisma_1.prisma.class.findMany({
67
+ where: {
68
+ teachers: {
69
+ some: {
70
+ id: ctx.user.id
71
+ }
72
+ }
73
+ },
74
+ select: {
75
+ id: true
76
+ }
77
+ });
78
+ return next({
79
+ ctx: {
80
+ ...ctx,
81
+ isTeacher: teacherClasses.length > 0,
82
+ teacherClassIds: teacherClasses.map((c) => c.id)
83
+ }
84
+ });
85
+ });
86
+ // Student middleware
87
+ const isMemberInClass = t.middleware(async ({ next, ctx, input }) => {
88
+ if (!ctx.user) {
89
+ throw new server_1.TRPCError({
90
+ code: 'UNAUTHORIZED',
91
+ message: 'Not authenticated',
92
+ });
93
+ }
94
+ const classId = input?.classId;
95
+ if (!classId) {
96
+ throw new server_1.TRPCError({
97
+ code: 'BAD_REQUEST',
98
+ message: 'classId is required',
99
+ });
100
+ }
101
+ const isMember = await prisma_1.prisma.class.findFirst({
102
+ where: {
103
+ id: classId,
104
+ OR: [
105
+ {
106
+ students: {
107
+ some: {
108
+ id: ctx.user.id
109
+ }
110
+ }
111
+ },
112
+ {
113
+ teachers: {
114
+ some: {
115
+ id: ctx.user.id
116
+ }
117
+ }
118
+ }
119
+ ]
120
+ }
121
+ });
122
+ if (!isMember) {
123
+ throw new server_1.TRPCError({
124
+ code: 'FORBIDDEN',
125
+ message: 'Not a member in this class',
126
+ });
127
+ }
128
+ return next();
129
+ });
130
+ // Teacher middleware
131
+ const isTeacherInClass = t.middleware(async ({ next, ctx, input }) => {
132
+ if (!ctx.user) {
133
+ throw new server_1.TRPCError({
134
+ code: 'UNAUTHORIZED',
135
+ message: 'Not authenticated',
136
+ });
137
+ }
138
+ const classId = input.classId;
139
+ if (!classId) {
140
+ throw new server_1.TRPCError({
141
+ code: 'BAD_REQUEST',
142
+ message: 'classId is required',
143
+ });
144
+ }
145
+ const isTeacher = await prisma_1.prisma.class.findFirst({
146
+ where: {
147
+ id: classId,
148
+ teachers: {
149
+ some: {
150
+ id: ctx.user.id
151
+ }
152
+ }
153
+ }
154
+ });
155
+ if (!isTeacher) {
156
+ throw new server_1.TRPCError({
157
+ code: 'FORBIDDEN',
158
+ message: 'Not a teacher in this class',
159
+ });
160
+ }
161
+ return next();
162
+ });
163
+ return {
164
+ isAuthed,
165
+ addComputedFlags,
166
+ isMemberInClass,
167
+ isTeacherInClass,
168
+ };
169
+ };
170
+ exports.createAuthMiddleware = createAuthMiddleware;
@@ -0,0 +1,2 @@
1
+ export declare const createLoggingMiddleware: (t: any) => any;
2
+ //# sourceMappingURL=logging.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"logging.d.ts","sourceRoot":"","sources":["../../src/middleware/logging.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,uBAAuB,GAAI,GAAG,GAAG,QAkD7C,CAAC"}
@@ -0,0 +1,51 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createLoggingMiddleware = void 0;
4
+ const logger_1 = require("@/logger");
5
+ const createLoggingMiddleware = (t) => {
6
+ return t.middleware(async ({ path, type, next, ctx }) => {
7
+ const start = Date.now();
8
+ const requestId = crypto.randomUUID();
9
+ // Log request
10
+ logger_1.logger.info('tRPC Request', {
11
+ requestId,
12
+ path,
13
+ type,
14
+ // input,
15
+ timestamp: new Date().toISOString(),
16
+ });
17
+ try {
18
+ const result = await next();
19
+ const durationMs = Date.now() - start;
20
+ // Log successful response
21
+ logger_1.logger.info('tRPC Response', {
22
+ requestId,
23
+ path,
24
+ type,
25
+ durationMs,
26
+ ok: result.ok,
27
+ timestamp: new Date().toISOString(),
28
+ });
29
+ return result;
30
+ }
31
+ catch (error) {
32
+ const durationMs = Date.now() - start;
33
+ // Log error response
34
+ logger_1.logger.error('tRPC Error' + path, {
35
+ requestId,
36
+ path,
37
+ type,
38
+ durationMs,
39
+ error: error instanceof Error ? {
40
+ path,
41
+ name: error.name,
42
+ message: error.message,
43
+ stack: error.stack,
44
+ } : error,
45
+ timestamp: new Date().toISOString(),
46
+ });
47
+ throw error;
48
+ }
49
+ });
50
+ };
51
+ exports.createLoggingMiddleware = createLoggingMiddleware;