@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.
- package/LICENSE.txt +8 -0
- package/README.md +143 -0
- package/dist/exportType.d.ts +9 -0
- package/dist/exportType.d.ts.map +1 -0
- package/dist/exportType.js +7 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +82 -0
- package/dist/lib/fileUpload.d.ts +38 -0
- package/dist/lib/fileUpload.d.ts.map +1 -0
- package/dist/lib/fileUpload.js +136 -0
- package/dist/lib/googleCloudStorage.d.ts +20 -0
- package/dist/lib/googleCloudStorage.d.ts.map +1 -0
- package/dist/lib/googleCloudStorage.js +88 -0
- package/dist/lib/prisma.d.ts +8 -0
- package/dist/lib/prisma.d.ts.map +1 -0
- package/dist/lib/prisma.js +11 -0
- package/dist/lib/thumbnailGenerator.d.ts +23 -0
- package/dist/lib/thumbnailGenerator.d.ts.map +1 -0
- package/dist/lib/thumbnailGenerator.js +180 -0
- package/dist/logger.d.ts +26 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +135 -0
- package/dist/middleware/auth.d.ts +7 -0
- package/dist/middleware/auth.d.ts.map +1 -0
- package/dist/middleware/auth.js +170 -0
- package/dist/middleware/logging.d.ts +2 -0
- package/dist/middleware/logging.d.ts.map +1 -0
- package/dist/middleware/logging.js +51 -0
- package/dist/routers/_app.d.ts +4369 -0
- package/dist/routers/_app.d.ts.map +1 -0
- package/dist/routers/_app.js +29 -0
- package/dist/routers/agenda.d.ts +66 -0
- package/dist/routers/agenda.d.ts.map +1 -0
- package/dist/routers/agenda.js +78 -0
- package/dist/routers/announcement.d.ts +79 -0
- package/dist/routers/announcement.d.ts.map +1 -0
- package/dist/routers/announcement.js +122 -0
- package/dist/routers/assignment.d.ts +1051 -0
- package/dist/routers/assignment.d.ts.map +1 -0
- package/dist/routers/assignment.js +1497 -0
- package/dist/routers/attendance.d.ts +99 -0
- package/dist/routers/attendance.d.ts.map +1 -0
- package/dist/routers/attendance.js +269 -0
- package/dist/routers/auth.d.ts +87 -0
- package/dist/routers/auth.d.ts.map +1 -0
- package/dist/routers/auth.js +255 -0
- package/dist/routers/class.d.ts +427 -0
- package/dist/routers/class.d.ts.map +1 -0
- package/dist/routers/class.js +693 -0
- package/dist/routers/event.d.ts +249 -0
- package/dist/routers/event.d.ts.map +1 -0
- package/dist/routers/event.js +467 -0
- package/dist/routers/file.d.ts +27 -0
- package/dist/routers/file.d.ts.map +1 -0
- package/dist/routers/file.js +88 -0
- package/dist/routers/section.d.ts +51 -0
- package/dist/routers/section.d.ts.map +1 -0
- package/dist/routers/section.js +123 -0
- package/dist/routers/user.d.ts +49 -0
- package/dist/routers/user.d.ts.map +1 -0
- package/dist/routers/user.js +74 -0
- package/dist/socket/handlers.d.ts +3 -0
- package/dist/socket/handlers.d.ts.map +1 -0
- package/dist/socket/handlers.js +130 -0
- package/dist/trpc.d.ts +147 -0
- package/dist/trpc.d.ts.map +1 -0
- package/dist/trpc.js +67 -0
- package/dist/types/trpc.d.ts +16 -0
- package/dist/types/trpc.d.ts.map +1 -0
- package/dist/types/trpc.js +2 -0
- package/dist/utils/email.d.ts +3 -0
- package/dist/utils/email.d.ts.map +1 -0
- package/dist/utils/email.js +16 -0
- package/dist/utils/generateInviteCode.d.ts +6 -0
- package/dist/utils/generateInviteCode.d.ts.map +1 -0
- package/dist/utils/generateInviteCode.js +11 -0
- package/dist/utils/logger.d.ts +27 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +124 -0
- package/generated/prisma/client.d.ts +1 -0
- package/generated/prisma/client.js +4 -0
- package/generated/prisma/default.d.ts +1 -0
- package/generated/prisma/default.js +4 -0
- package/generated/prisma/edge.d.ts +1 -0
- package/generated/prisma/edge.js +389 -0
- package/generated/prisma/index-browser.js +375 -0
- package/generated/prisma/index.d.ts +34865 -0
- package/generated/prisma/index.js +410 -0
- package/generated/prisma/libquery_engine-darwin-arm64.dylib.node +0 -0
- package/generated/prisma/package.json +140 -0
- package/generated/prisma/runtime/edge-esm.js +34 -0
- package/generated/prisma/runtime/edge.js +34 -0
- package/generated/prisma/runtime/index-browser.d.ts +370 -0
- package/generated/prisma/runtime/index-browser.js +16 -0
- package/generated/prisma/runtime/library.d.ts +3647 -0
- package/generated/prisma/runtime/library.js +146 -0
- package/generated/prisma/runtime/react-native.js +83 -0
- package/generated/prisma/runtime/wasm.js +35 -0
- package/generated/prisma/schema.prisma +304 -0
- package/generated/prisma/wasm.d.ts +1 -0
- package/generated/prisma/wasm.js +375 -0
- 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
|
+
}
|
package/dist/logger.d.ts
ADDED
|
@@ -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 @@
|
|
|
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 @@
|
|
|
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;
|