@studious-lms/server 1.0.6 → 1.0.8
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 +1461 -0
- package/dist/exportType.d.ts +3 -3
- package/dist/exportType.d.ts.map +1 -1
- 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 +3619 -1937
- package/dist/routers/_app.d.ts.map +1 -1
- package/dist/routers/_app.js +28 -27
- package/dist/routers/agenda.d.ts +14 -9
- package/dist/routers/agenda.d.ts.map +1 -1
- package/dist/routers/agenda.js +14 -17
- package/dist/routers/announcement.d.ts +5 -4
- package/dist/routers/announcement.d.ts.map +1 -1
- package/dist/routers/announcement.js +28 -31
- package/dist/routers/assignment.d.ts +283 -197
- package/dist/routers/assignment.d.ts.map +1 -1
- package/dist/routers/assignment.js +256 -202
- package/dist/routers/attendance.d.ts +6 -5
- package/dist/routers/attendance.d.ts.map +1 -1
- package/dist/routers/attendance.js +31 -34
- package/dist/routers/auth.d.ts +2 -1
- package/dist/routers/auth.d.ts.map +1 -1
- package/dist/routers/auth.js +80 -75
- package/dist/routers/class.d.ts +285 -15
- package/dist/routers/class.d.ts.map +1 -1
- package/dist/routers/class.js +440 -164
- package/dist/routers/event.d.ts +48 -39
- package/dist/routers/event.d.ts.map +1 -1
- package/dist/routers/event.js +76 -79
- package/dist/routers/file.d.ts +72 -2
- package/dist/routers/file.d.ts.map +1 -1
- package/dist/routers/file.js +260 -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 +2 -1
- package/dist/routers/section.d.ts.map +1 -1
- package/dist/routers/section.js +30 -33
- package/dist/routers/user.d.ts +3 -2
- 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 +75 -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.d.ts +1 -1
- package/dist/types/trpc.d.ts.map +1 -1
- 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 +12 -4
- package/prisma/migrations/20250807062924_init/migration.sql +436 -0
- package/prisma/migrations/migration_lock.toml +3 -0
- package/prisma/schema.prisma +68 -1
- package/src/exportType.ts +3 -3
- package/src/index.ts +6 -6
- package/src/lib/fileUpload.ts +19 -10
- package/src/lib/thumbnailGenerator.ts +2 -2
- package/src/middleware/auth.ts +2 -4
- package/src/middleware/logging.ts +2 -2
- package/src/routers/_app.ts +17 -13
- package/src/routers/agenda.ts +2 -2
- package/src/routers/announcement.ts +2 -2
- package/src/routers/assignment.ts +86 -26
- package/src/routers/attendance.ts +2 -2
- package/src/routers/auth.ts +83 -57
- package/src/routers/class.ts +339 -39
- package/src/routers/event.ts +2 -2
- package/src/routers/file.ts +276 -21
- package/src/routers/folder.ts +755 -0
- package/src/routers/notifications.ts +93 -0
- package/src/routers/section.ts +2 -2
- package/src/routers/user.ts +3 -3
- package/src/seedDatabase.ts +88 -0
- package/src/socket/handlers.ts +5 -5
- package/src/trpc.ts +17 -4
- package/src/types/trpc.ts +1 -1
- 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/exportType.d.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* This is used to export the types for the server
|
|
4
4
|
* to the client via npmjs
|
|
5
5
|
*/
|
|
6
|
-
export type { AppRouter } from "./routers/_app";
|
|
7
|
-
export type { RouterInputs } from "./routers/_app";
|
|
8
|
-
export type { RouterOutputs } from "./routers/_app";
|
|
6
|
+
export type { AppRouter } from "./routers/_app.js";
|
|
7
|
+
export type { RouterInputs } from "./routers/_app.js";
|
|
8
|
+
export type { RouterOutputs } from "./routers/_app.js";
|
|
9
9
|
//# sourceMappingURL=exportType.d.ts.map
|
package/dist/exportType.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"exportType.d.ts","sourceRoot":"","sources":["../src/exportType.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,YAAY,EAAE,SAAS,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"exportType.d.ts","sourceRoot":"","sources":["../src/exportType.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,YAAY,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AACnD,YAAY,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACtD,YAAY,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC"}
|
package/dist/exportType.js
CHANGED
package/dist/index.js
CHANGED
|
@@ -1,23 +1,18 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
const
|
|
13
|
-
const trpc_1 = require("./trpc");
|
|
14
|
-
const logger_1 = require("./utils/logger");
|
|
15
|
-
const handlers_1 = require("./socket/handlers");
|
|
16
|
-
dotenv_1.default.config();
|
|
17
|
-
const app = (0, express_1.default)();
|
|
1
|
+
import express from 'express';
|
|
2
|
+
import { createServer } from 'http';
|
|
3
|
+
import { Server } from 'socket.io';
|
|
4
|
+
import cors from 'cors';
|
|
5
|
+
import dotenv from 'dotenv';
|
|
6
|
+
import { createExpressMiddleware } from '@trpc/server/adapters/express';
|
|
7
|
+
import { appRouter } from './routers/_app.js';
|
|
8
|
+
import { createTRPCContext, createCallerFactory } from './trpc.js';
|
|
9
|
+
import { logger } from './utils/logger.js';
|
|
10
|
+
import { setupSocketHandlers } from './socket/handlers.js';
|
|
11
|
+
dotenv.config();
|
|
12
|
+
const app = express();
|
|
18
13
|
// CORS middleware
|
|
19
|
-
app.use((
|
|
20
|
-
origin: process.env.NEXT_PUBLIC_APP_URL || 'http://localhost:3000',
|
|
14
|
+
app.use(cors({
|
|
15
|
+
origin: [process.env.NEXT_PUBLIC_APP_URL || 'http://localhost:3000', 'http://localhost:3000'],
|
|
21
16
|
credentials: true,
|
|
22
17
|
}));
|
|
23
18
|
// Response time logging middleware
|
|
@@ -25,7 +20,7 @@ app.use((req, res, next) => {
|
|
|
25
20
|
const start = Date.now();
|
|
26
21
|
res.on('finish', () => {
|
|
27
22
|
const duration = Date.now() - start;
|
|
28
|
-
|
|
23
|
+
logger.info('Request completed', {
|
|
29
24
|
method: req.method,
|
|
30
25
|
path: req.path,
|
|
31
26
|
statusCode: res.statusCode,
|
|
@@ -35,9 +30,9 @@ app.use((req, res, next) => {
|
|
|
35
30
|
next();
|
|
36
31
|
});
|
|
37
32
|
// Create HTTP server
|
|
38
|
-
const httpServer =
|
|
33
|
+
const httpServer = createServer(app);
|
|
39
34
|
// Setup Socket.IO
|
|
40
|
-
const io = new
|
|
35
|
+
const io = new Server(httpServer, {
|
|
41
36
|
cors: {
|
|
42
37
|
origin: process.env.NEXT_PUBLIC_APP_URL || 'http://localhost:3000',
|
|
43
38
|
methods: ['GET', 'POST'],
|
|
@@ -53,28 +48,28 @@ const io = new socket_io_1.Server(httpServer, {
|
|
|
53
48
|
});
|
|
54
49
|
// Add server-level logging
|
|
55
50
|
io.engine.on('connection_error', (err) => {
|
|
56
|
-
|
|
51
|
+
logger.error('Socket connection error', { error: err.message });
|
|
57
52
|
});
|
|
58
53
|
// Setup socket handlers
|
|
59
|
-
|
|
54
|
+
setupSocketHandlers(io);
|
|
60
55
|
// Create caller
|
|
61
|
-
const createCaller =
|
|
56
|
+
const createCaller = createCallerFactory(appRouter);
|
|
62
57
|
// Setup tRPC middleware
|
|
63
|
-
app.use('/trpc',
|
|
64
|
-
router:
|
|
58
|
+
app.use('/trpc', createExpressMiddleware({
|
|
59
|
+
router: appRouter,
|
|
65
60
|
createContext: async ({ req, res }) => {
|
|
66
|
-
return
|
|
61
|
+
return createTRPCContext({ req, res });
|
|
67
62
|
},
|
|
68
63
|
}));
|
|
69
64
|
const PORT = process.env.PORT || 3001;
|
|
70
65
|
httpServer.listen(PORT, () => {
|
|
71
|
-
|
|
66
|
+
logger.info(`Server running on port ${PORT}`, {
|
|
72
67
|
port: PORT,
|
|
73
68
|
services: ['tRPC', 'Socket.IO']
|
|
74
69
|
});
|
|
75
70
|
});
|
|
76
71
|
// log all env variables
|
|
77
|
-
|
|
72
|
+
logger.info('Configurations', {
|
|
78
73
|
NODE_ENV: process.env.NODE_ENV,
|
|
79
74
|
PORT: process.env.PORT,
|
|
80
75
|
NEXT_PUBLIC_APP_URL: process.env.NEXT_PUBLIC_APP_URL,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fileUpload.d.ts","sourceRoot":"","sources":["../../src/lib/fileUpload.ts"],"names":[],"mappings":"AAMA,MAAM,WAAW,QAAQ;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED;;;;;;;GAOG;AACH,wBAAsB,UAAU,CAC9B,IAAI,EAAE,QAAQ,EACd,MAAM,EAAE,MAAM,EACd,SAAS,CAAC,EAAE,MAAM,EAClB,YAAY,CAAC,EAAE,MAAM,GACpB,OAAO,CAAC,YAAY,CAAC,
|
|
1
|
+
{"version":3,"file":"fileUpload.d.ts","sourceRoot":"","sources":["../../src/lib/fileUpload.ts"],"names":[],"mappings":"AAMA,MAAM,WAAW,QAAQ;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED;;;;;;;GAOG;AACH,wBAAsB,UAAU,CAC9B,IAAI,EAAE,QAAQ,EACd,MAAM,EAAE,MAAM,EACd,SAAS,CAAC,EAAE,MAAM,EAClB,YAAY,CAAC,EAAE,MAAM,GACpB,OAAO,CAAC,YAAY,CAAC,CA+FvB;AAED;;;;;;GAMG;AACH,wBAAsB,WAAW,CAC/B,KAAK,EAAE,QAAQ,EAAE,EACjB,MAAM,EAAE,MAAM,EACd,SAAS,CAAC,EAAE,MAAM,GACjB,OAAO,CAAC,YAAY,EAAE,CAAC,CAWzB;AAED;;;;GAIG;AACH,wBAAsB,UAAU,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAUlE"}
|
package/dist/lib/fileUpload.js
CHANGED
|
@@ -1,13 +1,8 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
const server_1 = require("@trpc/server");
|
|
7
|
-
const uuid_1 = require("uuid");
|
|
8
|
-
const googleCloudStorage_1 = require("./googleCloudStorage");
|
|
9
|
-
const thumbnailGenerator_1 = require("./thumbnailGenerator");
|
|
10
|
-
const prisma_1 = require("./prisma");
|
|
1
|
+
import { TRPCError } from "@trpc/server";
|
|
2
|
+
import { v4 as uuidv4 } from "uuid";
|
|
3
|
+
import { uploadFile as uploadToGCS, getSignedUrl } from "./googleCloudStorage.js";
|
|
4
|
+
import { generateMediaThumbnail } from "./thumbnailGenerator.js";
|
|
5
|
+
import { prisma } from "./prisma.js";
|
|
11
6
|
/**
|
|
12
7
|
* Uploads a single file to Google Cloud Storage and creates a file record
|
|
13
8
|
* @param file The file data to upload
|
|
@@ -16,36 +11,37 @@ const prisma_1 = require("./prisma");
|
|
|
16
11
|
* @param assignmentId Optional assignment ID to associate the file with
|
|
17
12
|
* @returns The uploaded file record
|
|
18
13
|
*/
|
|
19
|
-
async function uploadFile(file, userId, directory, assignmentId) {
|
|
14
|
+
export async function uploadFile(file, userId, directory, assignmentId) {
|
|
20
15
|
try {
|
|
21
16
|
// Create a unique filename
|
|
22
17
|
const fileExtension = file.name.split('.').pop();
|
|
23
|
-
const uniqueFilename = `${(
|
|
24
|
-
// Construct the full path
|
|
18
|
+
const uniqueFilename = `${uuidv4()}.${fileExtension}`;
|
|
19
|
+
// // Construct the full path
|
|
25
20
|
const filePath = directory
|
|
26
21
|
? `${directory}/${uniqueFilename}`
|
|
27
22
|
: uniqueFilename;
|
|
28
|
-
// Upload to Google Cloud Storage
|
|
29
|
-
const uploadedPath = await (
|
|
30
|
-
// Generate and store thumbnail if supported
|
|
23
|
+
// // Upload to Google Cloud Storage
|
|
24
|
+
const uploadedPath = await uploadToGCS(file.data, filePath, file.type);
|
|
25
|
+
// // Generate and store thumbnail if supported
|
|
31
26
|
let thumbnailId;
|
|
32
27
|
try {
|
|
33
|
-
// Convert base64 to buffer for thumbnail generation
|
|
28
|
+
// // Convert base64 to buffer for thumbnail generation
|
|
34
29
|
const base64Data = file.data.split(',')[1];
|
|
35
30
|
const fileBuffer = Buffer.from(base64Data, 'base64');
|
|
36
|
-
// Generate thumbnail directly from buffer
|
|
37
|
-
const thumbnailBuffer = await
|
|
31
|
+
// // Generate thumbnail directly from buffer
|
|
32
|
+
const thumbnailBuffer = await generateMediaThumbnail(fileBuffer, file.type);
|
|
38
33
|
if (thumbnailBuffer) {
|
|
39
34
|
// Store thumbnail in a thumbnails directory
|
|
40
35
|
const thumbnailPath = `thumbnails/${filePath}`;
|
|
41
36
|
const thumbnailBase64 = `data:image/jpeg;base64,${thumbnailBuffer.toString('base64')}`;
|
|
42
|
-
await (
|
|
37
|
+
await uploadToGCS(thumbnailBase64, thumbnailPath, 'image/jpeg');
|
|
43
38
|
// Create thumbnail file record
|
|
44
|
-
const thumbnailFile = await
|
|
39
|
+
const thumbnailFile = await prisma.file.create({
|
|
45
40
|
data: {
|
|
46
|
-
name: `${file.name}_thumb.jpg`,
|
|
41
|
+
name: `${file.name}_thumb.jpg${Math.random()}`,
|
|
47
42
|
type: 'image/jpeg',
|
|
48
43
|
path: thumbnailPath,
|
|
44
|
+
// path: '/dummyPath' + Math.random().toString(36).substring(2, 15),
|
|
49
45
|
user: {
|
|
50
46
|
connect: { id: userId }
|
|
51
47
|
}
|
|
@@ -58,7 +54,8 @@ async function uploadFile(file, userId, directory, assignmentId) {
|
|
|
58
54
|
console.warn('Failed to generate thumbnail:', error);
|
|
59
55
|
}
|
|
60
56
|
// Create file record in database
|
|
61
|
-
const
|
|
57
|
+
// const uploadedPath = '/dummyPath' + Math.random().toString(36).substring(2, 15);
|
|
58
|
+
const fileRecord = await prisma.file.create({
|
|
62
59
|
data: {
|
|
63
60
|
name: file.name,
|
|
64
61
|
type: file.type,
|
|
@@ -67,6 +64,11 @@ async function uploadFile(file, userId, directory, assignmentId) {
|
|
|
67
64
|
user: {
|
|
68
65
|
connect: { id: userId }
|
|
69
66
|
},
|
|
67
|
+
...(directory && {
|
|
68
|
+
folder: {
|
|
69
|
+
connect: { id: directory },
|
|
70
|
+
},
|
|
71
|
+
}),
|
|
70
72
|
...(thumbnailId && {
|
|
71
73
|
thumbnail: {
|
|
72
74
|
connect: { id: thumbnailId }
|
|
@@ -91,7 +93,7 @@ async function uploadFile(file, userId, directory, assignmentId) {
|
|
|
91
93
|
}
|
|
92
94
|
catch (error) {
|
|
93
95
|
console.error('Error uploading file:', error);
|
|
94
|
-
throw new
|
|
96
|
+
throw new TRPCError({
|
|
95
97
|
code: 'INTERNAL_SERVER_ERROR',
|
|
96
98
|
message: 'Failed to upload file',
|
|
97
99
|
});
|
|
@@ -104,14 +106,14 @@ async function uploadFile(file, userId, directory, assignmentId) {
|
|
|
104
106
|
* @param directory Optional subdirectory to store the files in
|
|
105
107
|
* @returns Array of uploaded file information
|
|
106
108
|
*/
|
|
107
|
-
async function uploadFiles(files, userId, directory) {
|
|
109
|
+
export async function uploadFiles(files, userId, directory) {
|
|
108
110
|
try {
|
|
109
111
|
const uploadPromises = files.map(file => uploadFile(file, userId, directory));
|
|
110
112
|
return await Promise.all(uploadPromises);
|
|
111
113
|
}
|
|
112
114
|
catch (error) {
|
|
113
115
|
console.error('Error uploading files:', error);
|
|
114
|
-
throw new
|
|
116
|
+
throw new TRPCError({
|
|
115
117
|
code: 'INTERNAL_SERVER_ERROR',
|
|
116
118
|
message: 'Failed to upload files',
|
|
117
119
|
});
|
|
@@ -122,13 +124,13 @@ async function uploadFiles(files, userId, directory) {
|
|
|
122
124
|
* @param filePath The path of the file in Google Cloud Storage
|
|
123
125
|
* @returns The signed URL
|
|
124
126
|
*/
|
|
125
|
-
async function getFileUrl(filePath) {
|
|
127
|
+
export async function getFileUrl(filePath) {
|
|
126
128
|
try {
|
|
127
|
-
return await
|
|
129
|
+
return await getSignedUrl(filePath);
|
|
128
130
|
}
|
|
129
131
|
catch (error) {
|
|
130
132
|
console.error('Error getting signed URL:', error);
|
|
131
|
-
throw new
|
|
133
|
+
throw new TRPCError({
|
|
132
134
|
code: 'INTERNAL_SERVER_ERROR',
|
|
133
135
|
message: 'Failed to get file URL',
|
|
134
136
|
});
|
|
@@ -1,11 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
exports.getSignedUrl = getSignedUrl;
|
|
5
|
-
exports.deleteFile = deleteFile;
|
|
6
|
-
const storage_1 = require("@google-cloud/storage");
|
|
7
|
-
const server_1 = require("@trpc/server");
|
|
8
|
-
const storage = new storage_1.Storage({
|
|
1
|
+
import { Storage } from '@google-cloud/storage';
|
|
2
|
+
import { TRPCError } from '@trpc/server';
|
|
3
|
+
const storage = new Storage({
|
|
9
4
|
projectId: process.env.GOOGLE_CLOUD_PROJECT_ID,
|
|
10
5
|
credentials: {
|
|
11
6
|
client_email: process.env.GOOGLE_CLOUD_CLIENT_EMAIL,
|
|
@@ -22,7 +17,7 @@ const SIGNED_URL_EXPIRATION = 5 * 60 * 1000;
|
|
|
22
17
|
* @param contentType The MIME type of the file
|
|
23
18
|
* @returns The path of the uploaded file
|
|
24
19
|
*/
|
|
25
|
-
async function uploadFile(base64Data, filePath, contentType) {
|
|
20
|
+
export async function uploadFile(base64Data, filePath, contentType) {
|
|
26
21
|
try {
|
|
27
22
|
// Remove the data URL prefix if present
|
|
28
23
|
const base64Content = base64Data.includes('base64,')
|
|
@@ -42,7 +37,7 @@ async function uploadFile(base64Data, filePath, contentType) {
|
|
|
42
37
|
}
|
|
43
38
|
catch (error) {
|
|
44
39
|
console.error('Error uploading to Google Cloud Storage:', error);
|
|
45
|
-
throw new
|
|
40
|
+
throw new TRPCError({
|
|
46
41
|
code: 'INTERNAL_SERVER_ERROR',
|
|
47
42
|
message: 'Failed to upload file to storage',
|
|
48
43
|
});
|
|
@@ -53,7 +48,7 @@ async function uploadFile(base64Data, filePath, contentType) {
|
|
|
53
48
|
* @param filePath The path of the file in the bucket
|
|
54
49
|
* @returns The signed URL
|
|
55
50
|
*/
|
|
56
|
-
async function getSignedUrl(filePath) {
|
|
51
|
+
export async function getSignedUrl(filePath) {
|
|
57
52
|
try {
|
|
58
53
|
const [url] = await bucket.file(filePath).getSignedUrl({
|
|
59
54
|
version: 'v4',
|
|
@@ -64,7 +59,7 @@ async function getSignedUrl(filePath) {
|
|
|
64
59
|
}
|
|
65
60
|
catch (error) {
|
|
66
61
|
console.error('Error getting signed URL:', error);
|
|
67
|
-
throw new
|
|
62
|
+
throw new TRPCError({
|
|
68
63
|
code: 'INTERNAL_SERVER_ERROR',
|
|
69
64
|
message: 'Failed to get signed URL',
|
|
70
65
|
});
|
|
@@ -74,13 +69,13 @@ async function getSignedUrl(filePath) {
|
|
|
74
69
|
* Deletes a file from Google Cloud Storage
|
|
75
70
|
* @param filePath The path of the file to delete
|
|
76
71
|
*/
|
|
77
|
-
async function deleteFile(filePath) {
|
|
72
|
+
export async function deleteFile(filePath) {
|
|
78
73
|
try {
|
|
79
74
|
await bucket.file(filePath).delete();
|
|
80
75
|
}
|
|
81
76
|
catch (error) {
|
|
82
77
|
console.error('Error deleting file from Google Cloud Storage:', error);
|
|
83
|
-
throw new
|
|
78
|
+
throw new TRPCError({
|
|
84
79
|
code: 'INTERNAL_SERVER_ERROR',
|
|
85
80
|
message: 'Failed to delete file from storage',
|
|
86
81
|
});
|
package/dist/lib/prisma.js
CHANGED
|
@@ -1,11 +1,8 @@
|
|
|
1
|
-
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.prisma = void 0;
|
|
4
|
-
const client_1 = require("@prisma/client");
|
|
1
|
+
import { PrismaClient } from '@prisma/client';
|
|
5
2
|
const prismaClientSingleton = () => {
|
|
6
|
-
return new
|
|
3
|
+
return new PrismaClient();
|
|
7
4
|
};
|
|
8
|
-
|
|
5
|
+
export const prisma = globalThis.prisma ?? prismaClientSingleton();
|
|
9
6
|
if (process.env.NODE_ENV !== 'production') {
|
|
10
|
-
globalThis.prisma =
|
|
7
|
+
globalThis.prisma = prisma;
|
|
11
8
|
}
|
|
@@ -1,14 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
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");
|
|
1
|
+
import sharp from 'sharp';
|
|
2
|
+
import { prisma } from './prisma.js';
|
|
3
|
+
import { uploadFile, getSignedUrl } from './googleCloudStorage.js';
|
|
12
4
|
// Thumbnail size configuration
|
|
13
5
|
const THUMBNAIL_WIDTH = 200;
|
|
14
6
|
const THUMBNAIL_HEIGHT = 200;
|
|
@@ -55,11 +47,11 @@ const AUDIO_TYPES = [
|
|
|
55
47
|
* @param fileType The MIME type of the file
|
|
56
48
|
* @returns Thumbnail buffer
|
|
57
49
|
*/
|
|
58
|
-
async function generateMediaThumbnail(fileBuffer, fileType) {
|
|
50
|
+
export async function generateMediaThumbnail(fileBuffer, fileType) {
|
|
59
51
|
if (fileType === 'application/pdf') {
|
|
60
52
|
// For PDFs, we need to use a different approach
|
|
61
53
|
try {
|
|
62
|
-
return await (
|
|
54
|
+
return await sharp(fileBuffer, {
|
|
63
55
|
density: 300, // Higher density for better quality
|
|
64
56
|
page: 0 // First page only
|
|
65
57
|
})
|
|
@@ -76,7 +68,7 @@ async function generateMediaThumbnail(fileBuffer, fileType) {
|
|
|
76
68
|
}
|
|
77
69
|
}
|
|
78
70
|
// For regular images
|
|
79
|
-
return (
|
|
71
|
+
return sharp(fileBuffer)
|
|
80
72
|
.resize(THUMBNAIL_WIDTH, THUMBNAIL_HEIGHT, {
|
|
81
73
|
fit: 'inside',
|
|
82
74
|
withoutEnlargement: true,
|
|
@@ -91,7 +83,7 @@ async function generateMediaThumbnail(fileBuffer, fileType) {
|
|
|
91
83
|
*/
|
|
92
84
|
async function generateGenericThumbnail(fileType) {
|
|
93
85
|
// Create a blank canvas with a colored background based on file type
|
|
94
|
-
const canvas = (
|
|
86
|
+
const canvas = sharp({
|
|
95
87
|
create: {
|
|
96
88
|
width: THUMBNAIL_WIDTH,
|
|
97
89
|
height: THUMBNAIL_HEIGHT,
|
|
@@ -130,9 +122,9 @@ async function generateGenericThumbnail(fileType) {
|
|
|
130
122
|
* @param fileType The MIME type of the file
|
|
131
123
|
* @returns The thumbnail buffer or null if thumbnail generation is not supported
|
|
132
124
|
*/
|
|
133
|
-
async function generateThumbnail(fileName, fileType) {
|
|
125
|
+
export async function generateThumbnail(fileName, fileType) {
|
|
134
126
|
try {
|
|
135
|
-
const signedUrl = await
|
|
127
|
+
const signedUrl = await getSignedUrl(fileName);
|
|
136
128
|
const response = await fetch(signedUrl);
|
|
137
129
|
if (!response.ok) {
|
|
138
130
|
throw new Error(`Failed to download file from storage: ${response.status} ${response.statusText}`);
|
|
@@ -163,12 +155,12 @@ async function generateThumbnail(fileName, fileType) {
|
|
|
163
155
|
* @param userId The user ID who owns the file
|
|
164
156
|
* @returns The ID of the created thumbnail File
|
|
165
157
|
*/
|
|
166
|
-
async function storeThumbnail(thumbnailBuffer, originalFileName, userId) {
|
|
158
|
+
export async function storeThumbnail(thumbnailBuffer, originalFileName, userId) {
|
|
167
159
|
// Convert buffer to base64 for uploadFile function
|
|
168
160
|
const base64Data = `data:image/jpeg;base64,${thumbnailBuffer.toString('base64')}`;
|
|
169
|
-
const thumbnailFileName = await
|
|
161
|
+
const thumbnailFileName = await uploadFile(base64Data, `thumbnails/${originalFileName}_thumb`, 'image/jpeg');
|
|
170
162
|
// Create a new File entry for the thumbnail
|
|
171
|
-
const newThumbnail = await
|
|
163
|
+
const newThumbnail = await prisma.file.create({
|
|
172
164
|
data: {
|
|
173
165
|
name: `${originalFileName}_thumb.jpg`,
|
|
174
166
|
path: thumbnailFileName,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../src/middleware/auth.ts"],"names":[],"mappings":"AAIA,eAAO,MAAM,oBAAoB,GAAI,GAAG,GAAG;;;;;
|
|
1
|
+
{"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../src/middleware/auth.ts"],"names":[],"mappings":"AAIA,eAAO,MAAM,oBAAoB,GAAI,GAAG,GAAG;;;;;CAwL1C,CAAC"}
|
package/dist/middleware/auth.js
CHANGED
|
@@ -1,16 +1,13 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
const server_1 = require("@trpc/server");
|
|
5
|
-
const prisma_1 = require("../lib/prisma");
|
|
6
|
-
const createAuthMiddleware = (t) => {
|
|
1
|
+
import { TRPCError } from '@trpc/server';
|
|
2
|
+
import { prisma } from '../lib/prisma.js';
|
|
3
|
+
export const createAuthMiddleware = (t) => {
|
|
7
4
|
// Auth middleware
|
|
8
5
|
const isAuthed = t.middleware(async ({ next, ctx }) => {
|
|
9
6
|
const startTime = Date.now();
|
|
10
7
|
// Get user from request headers
|
|
11
8
|
const userHeader = ctx.req.headers['x-user'];
|
|
12
9
|
if (!userHeader) {
|
|
13
|
-
throw new
|
|
10
|
+
throw new TRPCError({
|
|
14
11
|
code: 'UNAUTHORIZED',
|
|
15
12
|
message: 'Not authenticated - no token found',
|
|
16
13
|
});
|
|
@@ -18,7 +15,7 @@ const createAuthMiddleware = (t) => {
|
|
|
18
15
|
try {
|
|
19
16
|
const token = typeof userHeader === 'string' ? userHeader : userHeader[0];
|
|
20
17
|
// Find user by session token
|
|
21
|
-
const user = await
|
|
18
|
+
const user = await prisma.user.findFirst({
|
|
22
19
|
where: {
|
|
23
20
|
sessions: {
|
|
24
21
|
some: {
|
|
@@ -33,12 +30,11 @@ const createAuthMiddleware = (t) => {
|
|
|
33
30
|
}
|
|
34
31
|
});
|
|
35
32
|
if (!user) {
|
|
36
|
-
throw new
|
|
33
|
+
throw new TRPCError({
|
|
37
34
|
code: 'UNAUTHORIZED',
|
|
38
35
|
message: 'Invalid or expired session',
|
|
39
36
|
});
|
|
40
37
|
}
|
|
41
|
-
console.log(user);
|
|
42
38
|
return next({
|
|
43
39
|
ctx: {
|
|
44
40
|
...ctx,
|
|
@@ -48,7 +44,7 @@ const createAuthMiddleware = (t) => {
|
|
|
48
44
|
}
|
|
49
45
|
catch (error) {
|
|
50
46
|
console.log(error);
|
|
51
|
-
throw new
|
|
47
|
+
throw new TRPCError({
|
|
52
48
|
code: 'UNAUTHORIZED',
|
|
53
49
|
message: 'Invalid user data',
|
|
54
50
|
});
|
|
@@ -57,13 +53,13 @@ const createAuthMiddleware = (t) => {
|
|
|
57
53
|
// Add computed flags middleware
|
|
58
54
|
const addComputedFlags = t.middleware(async ({ next, ctx }) => {
|
|
59
55
|
if (!ctx.user) {
|
|
60
|
-
throw new
|
|
56
|
+
throw new TRPCError({
|
|
61
57
|
code: 'UNAUTHORIZED',
|
|
62
58
|
message: 'Not authenticated',
|
|
63
59
|
});
|
|
64
60
|
}
|
|
65
61
|
// Get all classes where user is a teacher
|
|
66
|
-
const teacherClasses = await
|
|
62
|
+
const teacherClasses = await prisma.class.findMany({
|
|
67
63
|
where: {
|
|
68
64
|
teachers: {
|
|
69
65
|
some: {
|
|
@@ -86,19 +82,19 @@ const createAuthMiddleware = (t) => {
|
|
|
86
82
|
// Student middleware
|
|
87
83
|
const isMemberInClass = t.middleware(async ({ next, ctx, input }) => {
|
|
88
84
|
if (!ctx.user) {
|
|
89
|
-
throw new
|
|
85
|
+
throw new TRPCError({
|
|
90
86
|
code: 'UNAUTHORIZED',
|
|
91
87
|
message: 'Not authenticated',
|
|
92
88
|
});
|
|
93
89
|
}
|
|
94
90
|
const classId = input?.classId;
|
|
95
91
|
if (!classId) {
|
|
96
|
-
throw new
|
|
92
|
+
throw new TRPCError({
|
|
97
93
|
code: 'BAD_REQUEST',
|
|
98
94
|
message: 'classId is required',
|
|
99
95
|
});
|
|
100
96
|
}
|
|
101
|
-
const isMember = await
|
|
97
|
+
const isMember = await prisma.class.findFirst({
|
|
102
98
|
where: {
|
|
103
99
|
id: classId,
|
|
104
100
|
OR: [
|
|
@@ -120,7 +116,7 @@ const createAuthMiddleware = (t) => {
|
|
|
120
116
|
}
|
|
121
117
|
});
|
|
122
118
|
if (!isMember) {
|
|
123
|
-
throw new
|
|
119
|
+
throw new TRPCError({
|
|
124
120
|
code: 'FORBIDDEN',
|
|
125
121
|
message: 'Not a member in this class',
|
|
126
122
|
});
|
|
@@ -130,19 +126,19 @@ const createAuthMiddleware = (t) => {
|
|
|
130
126
|
// Teacher middleware
|
|
131
127
|
const isTeacherInClass = t.middleware(async ({ next, ctx, input }) => {
|
|
132
128
|
if (!ctx.user) {
|
|
133
|
-
throw new
|
|
129
|
+
throw new TRPCError({
|
|
134
130
|
code: 'UNAUTHORIZED',
|
|
135
131
|
message: 'Not authenticated',
|
|
136
132
|
});
|
|
137
133
|
}
|
|
138
134
|
const classId = input.classId;
|
|
139
135
|
if (!classId) {
|
|
140
|
-
throw new
|
|
136
|
+
throw new TRPCError({
|
|
141
137
|
code: 'BAD_REQUEST',
|
|
142
138
|
message: 'classId is required',
|
|
143
139
|
});
|
|
144
140
|
}
|
|
145
|
-
const isTeacher = await
|
|
141
|
+
const isTeacher = await prisma.class.findFirst({
|
|
146
142
|
where: {
|
|
147
143
|
id: classId,
|
|
148
144
|
teachers: {
|
|
@@ -153,7 +149,7 @@ const createAuthMiddleware = (t) => {
|
|
|
153
149
|
}
|
|
154
150
|
});
|
|
155
151
|
if (!isTeacher) {
|
|
156
|
-
throw new
|
|
152
|
+
throw new TRPCError({
|
|
157
153
|
code: 'FORBIDDEN',
|
|
158
154
|
message: 'Not a teacher in this class',
|
|
159
155
|
});
|
|
@@ -167,4 +163,3 @@ const createAuthMiddleware = (t) => {
|
|
|
167
163
|
isTeacherInClass,
|
|
168
164
|
};
|
|
169
165
|
};
|
|
170
|
-
exports.createAuthMiddleware = createAuthMiddleware;
|
|
@@ -1,13 +1,10 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
exports.createLoggingMiddleware = void 0;
|
|
4
|
-
const logger_1 = require("../utils/logger");
|
|
5
|
-
const createLoggingMiddleware = (t) => {
|
|
1
|
+
import { logger } from '../utils/logger.js';
|
|
2
|
+
export const createLoggingMiddleware = (t) => {
|
|
6
3
|
return t.middleware(async ({ path, type, next, ctx }) => {
|
|
7
4
|
const start = Date.now();
|
|
8
5
|
const requestId = crypto.randomUUID();
|
|
9
6
|
// Log request
|
|
10
|
-
|
|
7
|
+
logger.info('tRPC Request', {
|
|
11
8
|
requestId,
|
|
12
9
|
path,
|
|
13
10
|
type,
|
|
@@ -18,7 +15,7 @@ const createLoggingMiddleware = (t) => {
|
|
|
18
15
|
const result = await next();
|
|
19
16
|
const durationMs = Date.now() - start;
|
|
20
17
|
// Log successful response
|
|
21
|
-
|
|
18
|
+
logger.info('tRPC Response', {
|
|
22
19
|
requestId,
|
|
23
20
|
path,
|
|
24
21
|
type,
|
|
@@ -31,7 +28,7 @@ const createLoggingMiddleware = (t) => {
|
|
|
31
28
|
catch (error) {
|
|
32
29
|
const durationMs = Date.now() - start;
|
|
33
30
|
// Log error response
|
|
34
|
-
|
|
31
|
+
logger.error('tRPC Error' + path, {
|
|
35
32
|
requestId,
|
|
36
33
|
path,
|
|
37
34
|
type,
|
|
@@ -48,4 +45,3 @@ const createLoggingMiddleware = (t) => {
|
|
|
48
45
|
}
|
|
49
46
|
});
|
|
50
47
|
};
|
|
51
|
-
exports.createLoggingMiddleware = createLoggingMiddleware;
|