@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
package/LICENSE.txt ADDED
@@ -0,0 +1,8 @@
1
+ Copyright 2025 Alan Shen <contact@alanshen.me>
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4
+
5
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6
+
7
+ THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
8
+
package/README.md ADDED
@@ -0,0 +1,143 @@
1
+ # Studious Backend Server
2
+
3
+ This is the backend server for the Studious application. It provides a RESTful and real-time API for managing users, classes, assignments, attendance, and more, supporting both HTTP (via tRPC) and WebSocket (via Socket.IO) communication.
4
+
5
+ ## Features
6
+
7
+ - **Express.js** server with CORS support
8
+ - **tRPC** for type-safe API endpoints
9
+ - **Socket.IO** for real-time features
10
+ - **Prisma ORM** with PostgreSQL for database management
11
+ - **User authentication** and session management
12
+ - **Class, assignment, attendance, and file management**
13
+ - **Structured logging** for requests and server events
14
+
15
+ ## Getting Started
16
+
17
+ ### Prerequisites
18
+
19
+ - Node.js (v18+ recommended)
20
+ - npm (v9+ recommended)
21
+ - PostgreSQL database
22
+
23
+ ### Installation
24
+
25
+ 1. **Clone the repository:**
26
+ ```bash
27
+ git clone <repo-url>
28
+ cd server
29
+ ```
30
+
31
+ 2. **Install dependencies:**
32
+ ```bash
33
+ npm install
34
+ ```
35
+
36
+ 3. **Set up environment variables:**
37
+ Create a `.env` file in the root directory with the following variables:
38
+ ```env
39
+ DATABASE_URL="postgresql://username:password@localhost:5432/easy_lms"
40
+ PORT=3001
41
+ NEXT_PUBLIC_APP_URL=http://localhost:3000
42
+ NODE_ENV=development
43
+ LOG_MODE=info
44
+ ```
45
+
46
+ 4. **Set up the database:**
47
+ - Create a PostgreSQL database named `easy_lms`
48
+ - Run the Prisma migrations:
49
+ ```bash
50
+ npx prisma migrate dev --name init
51
+ ```
52
+ - Generate the Prisma client:
53
+ ```bash
54
+ npx prisma generate
55
+ ```
56
+
57
+ ### Running the Server
58
+
59
+ - **Development mode (with hot reload):**
60
+ ```bash
61
+ npm run dev
62
+ ```
63
+
64
+ - **Production build:**
65
+ ```bash
66
+ npm run build
67
+ npm start
68
+ ```
69
+
70
+ The server will start on the port specified in your `.env` file (default: `3001`).
71
+
72
+ ## API Overview
73
+
74
+ - **tRPC endpoints:** Available at `/trpc`
75
+ - **WebSocket:** Available at `/socket.io/`
76
+ - **CORS:** Configured to allow requests from the frontend app URL via `NEXT_PUBLIC_APP_URL`
77
+
78
+ ## Project Structure
79
+
80
+ ```
81
+ src/
82
+ index.ts # Main server entry point
83
+ routers/ # tRPC routers for API endpoints
84
+ socket/ # Socket.IO event handlers
85
+ middleware/ # Express and tRPC middleware
86
+ utils/ # Utility functions (e.g., logger)
87
+ lib/ # Shared libraries
88
+ types/ # TypeScript types
89
+ prisma/
90
+ schema.prisma # Prisma database schema
91
+ migrations/ # Database migrations
92
+ ```
93
+
94
+ ## Database
95
+
96
+ - **Database:** PostgreSQL
97
+ - **ORM:** Prisma
98
+ - **Schema:** Defined in `prisma/schema.prisma`
99
+ - **Migrations:** Stored in `prisma/migrations/`
100
+
101
+ ### Database Commands
102
+
103
+ ```bash
104
+ # Generate Prisma client
105
+ npx prisma generate
106
+
107
+ # Run migrations
108
+ npx prisma migrate dev
109
+
110
+ # Reset database
111
+ npx prisma migrate reset
112
+
113
+ # Open Prisma Studio (database GUI)
114
+ npx prisma studio
115
+ ```
116
+
117
+ ## Scripts
118
+
119
+ - `npm run dev` — Start in development mode with hot reload
120
+ - `npm run build` — Compile TypeScript to JavaScript
121
+ - `npm start` — Start the compiled server
122
+
123
+ ## Environment Variables
124
+
125
+ | Variable | Description | Default |
126
+ |----------|-------------|---------|
127
+ | `DATABASE_URL` | PostgreSQL connection string | Required |
128
+ | `PORT` | Server port | 3001 |
129
+ | `NEXT_PUBLIC_APP_URL` | Frontend app URL for CORS | http://localhost:3000 |
130
+ | `NODE_ENV` | Environment mode | development |
131
+ | `LOG_MODE` | Logging level | info |
132
+
133
+ ## Development
134
+
135
+ The server uses TypeScript and includes:
136
+ - **tRPC** for type-safe API development
137
+ - **Socket.IO** for real-time communication
138
+ - **Prisma** for database operations
139
+ - **Express** middleware for CORS and logging
140
+
141
+ ## License
142
+
143
+ [MIT](LICENSE.txt)
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Export types for the server
3
+ * This is used to export the types for the server
4
+ * to the client via npmjs
5
+ */
6
+ export type { AppRouter } from "@/routers/_app";
7
+ export type { RouterInputs } from "@/routers/_app";
8
+ export type { RouterOutputs } from "@/routers/_app";
9
+ //# sourceMappingURL=exportType.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"exportType.d.ts","sourceRoot":"","sources":["../src/exportType.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,YAAY,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAChD,YAAY,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AACnD,YAAY,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC"}
@@ -0,0 +1,7 @@
1
+ "use strict";
2
+ /**
3
+ * Export types for the server
4
+ * This is used to export the types for the server
5
+ * to the client via npmjs
6
+ */
7
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":""}
package/dist/index.js ADDED
@@ -0,0 +1,82 @@
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
+ const express_1 = __importDefault(require("express"));
7
+ const http_1 = require("http");
8
+ const socket_io_1 = require("socket.io");
9
+ const cors_1 = __importDefault(require("cors"));
10
+ const dotenv_1 = __importDefault(require("dotenv"));
11
+ const express_2 = require("@trpc/server/adapters/express");
12
+ const _app_1 = require("@routers/_app");
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)();
18
+ // CORS middleware
19
+ app.use((0, cors_1.default)({
20
+ origin: process.env.NEXT_PUBLIC_APP_URL || 'http://localhost:3000',
21
+ credentials: true,
22
+ }));
23
+ // Response time logging middleware
24
+ app.use((req, res, next) => {
25
+ const start = Date.now();
26
+ res.on('finish', () => {
27
+ const duration = Date.now() - start;
28
+ logger_1.logger.info('Request completed', {
29
+ method: req.method,
30
+ path: req.path,
31
+ statusCode: res.statusCode,
32
+ duration: `${duration}ms`
33
+ });
34
+ });
35
+ next();
36
+ });
37
+ // Create HTTP server
38
+ const httpServer = (0, http_1.createServer)(app);
39
+ // Setup Socket.IO
40
+ const io = new socket_io_1.Server(httpServer, {
41
+ cors: {
42
+ origin: process.env.NEXT_PUBLIC_APP_URL || 'http://localhost:3000',
43
+ methods: ['GET', 'POST'],
44
+ credentials: true,
45
+ allowedHeaders: ['Access-Control-Allow-Origin']
46
+ },
47
+ transports: ['websocket', 'polling'],
48
+ pingTimeout: 60000,
49
+ pingInterval: 25000,
50
+ connectTimeout: 45000,
51
+ path: '/socket.io/',
52
+ allowEIO3: true
53
+ });
54
+ // Add server-level logging
55
+ io.engine.on('connection_error', (err) => {
56
+ logger_1.logger.error('Socket connection error', { error: err.message });
57
+ });
58
+ // Setup socket handlers
59
+ (0, handlers_1.setupSocketHandlers)(io);
60
+ // Create caller
61
+ const createCaller = (0, trpc_1.createCallerFactory)(_app_1.appRouter);
62
+ // Setup tRPC middleware
63
+ app.use('/trpc', (0, express_2.createExpressMiddleware)({
64
+ router: _app_1.appRouter,
65
+ createContext: async ({ req, res }) => {
66
+ return (0, trpc_1.createTRPCContext)({ req, res });
67
+ },
68
+ }));
69
+ const PORT = process.env.PORT || 3001;
70
+ httpServer.listen(PORT, () => {
71
+ logger_1.logger.info(`Server running on port ${PORT}`, {
72
+ port: PORT,
73
+ services: ['tRPC', 'Socket.IO']
74
+ });
75
+ });
76
+ // log all env variables
77
+ logger_1.logger.info('Configurations', {
78
+ NODE_ENV: process.env.NODE_ENV,
79
+ PORT: process.env.PORT,
80
+ NEXT_PUBLIC_APP_URL: process.env.NEXT_PUBLIC_APP_URL,
81
+ LOG_MODE: process.env.LOG_MODE,
82
+ });
@@ -0,0 +1,38 @@
1
+ export interface FileData {
2
+ name: string;
3
+ type: string;
4
+ size: number;
5
+ data: string;
6
+ }
7
+ export interface UploadedFile {
8
+ id: string;
9
+ name: string;
10
+ type: string;
11
+ size: number;
12
+ path: string;
13
+ thumbnailId?: string;
14
+ }
15
+ /**
16
+ * Uploads a single file to Google Cloud Storage and creates a file record
17
+ * @param file The file data to upload
18
+ * @param userId The ID of the user uploading the file
19
+ * @param directory Optional directory to store the file in
20
+ * @param assignmentId Optional assignment ID to associate the file with
21
+ * @returns The uploaded file record
22
+ */
23
+ export declare function uploadFile(file: FileData, userId: string, directory?: string, assignmentId?: string): Promise<UploadedFile>;
24
+ /**
25
+ * Uploads multiple files
26
+ * @param files Array of files to upload
27
+ * @param userId The ID of the user uploading the files
28
+ * @param directory Optional subdirectory to store the files in
29
+ * @returns Array of uploaded file information
30
+ */
31
+ export declare function uploadFiles(files: FileData[], userId: string, directory?: string): Promise<UploadedFile[]>;
32
+ /**
33
+ * Gets a signed URL for a file
34
+ * @param filePath The path of the file in Google Cloud Storage
35
+ * @returns The signed URL
36
+ */
37
+ export declare function getFileUrl(filePath: string): Promise<string>;
38
+ //# sourceMappingURL=fileUpload.d.ts.map
@@ -0,0 +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,CAsFvB;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"}
@@ -0,0 +1,136 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.uploadFile = uploadFile;
4
+ exports.uploadFiles = uploadFiles;
5
+ exports.getFileUrl = getFileUrl;
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");
11
+ /**
12
+ * Uploads a single file to Google Cloud Storage and creates a file record
13
+ * @param file The file data to upload
14
+ * @param userId The ID of the user uploading the file
15
+ * @param directory Optional directory to store the file in
16
+ * @param assignmentId Optional assignment ID to associate the file with
17
+ * @returns The uploaded file record
18
+ */
19
+ async function uploadFile(file, userId, directory, assignmentId) {
20
+ try {
21
+ // Create a unique filename
22
+ const fileExtension = file.name.split('.').pop();
23
+ const uniqueFilename = `${(0, uuid_1.v4)()}.${fileExtension}`;
24
+ // Construct the full path
25
+ const filePath = directory
26
+ ? `${directory}/${uniqueFilename}`
27
+ : uniqueFilename;
28
+ // Upload to Google Cloud Storage
29
+ const uploadedPath = await (0, googleCloudStorage_1.uploadFile)(file.data, filePath, file.type);
30
+ // Generate and store thumbnail if supported
31
+ let thumbnailId;
32
+ try {
33
+ // Convert base64 to buffer for thumbnail generation
34
+ const base64Data = file.data.split(',')[1];
35
+ const fileBuffer = Buffer.from(base64Data, 'base64');
36
+ // Generate thumbnail directly from buffer
37
+ const thumbnailBuffer = await (0, thumbnailGenerator_1.generateMediaThumbnail)(fileBuffer, file.type);
38
+ if (thumbnailBuffer) {
39
+ // Store thumbnail in a thumbnails directory
40
+ const thumbnailPath = `thumbnails/${filePath}`;
41
+ const thumbnailBase64 = `data:image/jpeg;base64,${thumbnailBuffer.toString('base64')}`;
42
+ await (0, googleCloudStorage_1.uploadFile)(thumbnailBase64, thumbnailPath, 'image/jpeg');
43
+ // Create thumbnail file record
44
+ const thumbnailFile = await prisma_1.prisma.file.create({
45
+ data: {
46
+ name: `${file.name}_thumb.jpg`,
47
+ type: 'image/jpeg',
48
+ path: thumbnailPath,
49
+ user: {
50
+ connect: { id: userId }
51
+ }
52
+ }
53
+ });
54
+ thumbnailId = thumbnailFile.id;
55
+ }
56
+ }
57
+ catch (error) {
58
+ console.warn('Failed to generate thumbnail:', error);
59
+ }
60
+ // Create file record in database
61
+ const fileRecord = await prisma_1.prisma.file.create({
62
+ data: {
63
+ name: file.name,
64
+ type: file.type,
65
+ size: file.size,
66
+ path: uploadedPath,
67
+ user: {
68
+ connect: { id: userId }
69
+ },
70
+ ...(thumbnailId && {
71
+ thumbnail: {
72
+ connect: { id: thumbnailId }
73
+ }
74
+ }),
75
+ ...(assignmentId && {
76
+ assignment: {
77
+ connect: { id: assignmentId }
78
+ }
79
+ })
80
+ },
81
+ });
82
+ // Return file information
83
+ return {
84
+ id: fileRecord.id,
85
+ name: file.name,
86
+ type: file.type,
87
+ size: file.size,
88
+ path: uploadedPath,
89
+ thumbnailId: thumbnailId
90
+ };
91
+ }
92
+ catch (error) {
93
+ console.error('Error uploading file:', error);
94
+ throw new server_1.TRPCError({
95
+ code: 'INTERNAL_SERVER_ERROR',
96
+ message: 'Failed to upload file',
97
+ });
98
+ }
99
+ }
100
+ /**
101
+ * Uploads multiple files
102
+ * @param files Array of files to upload
103
+ * @param userId The ID of the user uploading the files
104
+ * @param directory Optional subdirectory to store the files in
105
+ * @returns Array of uploaded file information
106
+ */
107
+ async function uploadFiles(files, userId, directory) {
108
+ try {
109
+ const uploadPromises = files.map(file => uploadFile(file, userId, directory));
110
+ return await Promise.all(uploadPromises);
111
+ }
112
+ catch (error) {
113
+ console.error('Error uploading files:', error);
114
+ throw new server_1.TRPCError({
115
+ code: 'INTERNAL_SERVER_ERROR',
116
+ message: 'Failed to upload files',
117
+ });
118
+ }
119
+ }
120
+ /**
121
+ * Gets a signed URL for a file
122
+ * @param filePath The path of the file in Google Cloud Storage
123
+ * @returns The signed URL
124
+ */
125
+ async function getFileUrl(filePath) {
126
+ try {
127
+ return await (0, googleCloudStorage_1.getSignedUrl)(filePath);
128
+ }
129
+ catch (error) {
130
+ console.error('Error getting signed URL:', error);
131
+ throw new server_1.TRPCError({
132
+ code: 'INTERNAL_SERVER_ERROR',
133
+ message: 'Failed to get file URL',
134
+ });
135
+ }
136
+ }
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Uploads a file to Google Cloud Storage
3
+ * @param base64Data Base64 encoded file data
4
+ * @param filePath The path where the file should be stored
5
+ * @param contentType The MIME type of the file
6
+ * @returns The path of the uploaded file
7
+ */
8
+ export declare function uploadFile(base64Data: string, filePath: string, contentType: string): Promise<string>;
9
+ /**
10
+ * Gets a signed URL for a file
11
+ * @param filePath The path of the file in the bucket
12
+ * @returns The signed URL
13
+ */
14
+ export declare function getSignedUrl(filePath: string): Promise<string>;
15
+ /**
16
+ * Deletes a file from Google Cloud Storage
17
+ * @param filePath The path of the file to delete
18
+ */
19
+ export declare function deleteFile(filePath: string): Promise<void>;
20
+ //# sourceMappingURL=googleCloudStorage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"googleCloudStorage.d.ts","sourceRoot":"","sources":["../../src/lib/googleCloudStorage.ts"],"names":[],"mappings":"AAgBA;;;;;;GAMG;AACH,wBAAsB,UAAU,CAC9B,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,MAAM,EAChB,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC,MAAM,CAAC,CA4BjB;AAED;;;;GAIG;AACH,wBAAsB,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAepE;AAED;;;GAGG;AACH,wBAAsB,UAAU,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAUhE"}
@@ -0,0 +1,88 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.uploadFile = uploadFile;
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({
9
+ projectId: process.env.GOOGLE_CLOUD_PROJECT_ID,
10
+ credentials: {
11
+ client_email: process.env.GOOGLE_CLOUD_CLIENT_EMAIL,
12
+ private_key: process.env.GOOGLE_CLOUD_PRIVATE_KEY?.replace(/\\n/g, '\n'),
13
+ },
14
+ });
15
+ const bucket = storage.bucket(process.env.GOOGLE_CLOUD_BUCKET_NAME || '');
16
+ // Short expiration time for signed URLs (5 minutes)
17
+ const SIGNED_URL_EXPIRATION = 5 * 60 * 1000;
18
+ /**
19
+ * Uploads a file to Google Cloud Storage
20
+ * @param base64Data Base64 encoded file data
21
+ * @param filePath The path where the file should be stored
22
+ * @param contentType The MIME type of the file
23
+ * @returns The path of the uploaded file
24
+ */
25
+ async function uploadFile(base64Data, filePath, contentType) {
26
+ try {
27
+ // Remove the data URL prefix if present
28
+ const base64Content = base64Data.includes('base64,')
29
+ ? base64Data.split('base64,')[1]
30
+ : base64Data;
31
+ // Convert base64 to buffer
32
+ const fileBuffer = Buffer.from(base64Content, 'base64');
33
+ // Create a new file in the bucket
34
+ const file = bucket.file(filePath);
35
+ // Upload the file
36
+ await file.save(fileBuffer, {
37
+ metadata: {
38
+ contentType,
39
+ },
40
+ });
41
+ return filePath;
42
+ }
43
+ catch (error) {
44
+ console.error('Error uploading to Google Cloud Storage:', error);
45
+ throw new server_1.TRPCError({
46
+ code: 'INTERNAL_SERVER_ERROR',
47
+ message: 'Failed to upload file to storage',
48
+ });
49
+ }
50
+ }
51
+ /**
52
+ * Gets a signed URL for a file
53
+ * @param filePath The path of the file in the bucket
54
+ * @returns The signed URL
55
+ */
56
+ async function getSignedUrl(filePath) {
57
+ try {
58
+ const [url] = await bucket.file(filePath).getSignedUrl({
59
+ version: 'v4',
60
+ action: 'read',
61
+ expires: Date.now() + SIGNED_URL_EXPIRATION,
62
+ });
63
+ return url;
64
+ }
65
+ catch (error) {
66
+ console.error('Error getting signed URL:', error);
67
+ throw new server_1.TRPCError({
68
+ code: 'INTERNAL_SERVER_ERROR',
69
+ message: 'Failed to get signed URL',
70
+ });
71
+ }
72
+ }
73
+ /**
74
+ * Deletes a file from Google Cloud Storage
75
+ * @param filePath The path of the file to delete
76
+ */
77
+ async function deleteFile(filePath) {
78
+ try {
79
+ await bucket.file(filePath).delete();
80
+ }
81
+ catch (error) {
82
+ console.error('Error deleting file from Google Cloud Storage:', error);
83
+ throw new server_1.TRPCError({
84
+ code: 'INTERNAL_SERVER_ERROR',
85
+ message: 'Failed to delete file from storage',
86
+ });
87
+ }
88
+ }
@@ -0,0 +1,8 @@
1
+ import { PrismaClient } from '@prisma/client';
2
+ declare const prismaClientSingleton: () => PrismaClient<import(".prisma/client").Prisma.PrismaClientOptions, never, import("@prisma/client/runtime/library").DefaultArgs>;
3
+ declare global {
4
+ var prisma: undefined | ReturnType<typeof prismaClientSingleton>;
5
+ }
6
+ export declare const prisma: PrismaClient<import(".prisma/client").Prisma.PrismaClientOptions, never, import("@prisma/client/runtime/library").DefaultArgs>;
7
+ export {};
8
+ //# sourceMappingURL=prisma.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prisma.d.ts","sourceRoot":"","sources":["../../src/lib/prisma.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAE9C,QAAA,MAAM,qBAAqB,sIAE1B,CAAC;AAGF,OAAO,CAAC,MAAM,CAAC;IACb,IAAI,MAAM,EAAE,SAAS,GAAG,UAAU,CAAC,OAAO,qBAAqB,CAAC,CAAC;CAClE;AAED,eAAO,MAAM,MAAM,gIAA+C,CAAC"}
@@ -0,0 +1,11 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.prisma = void 0;
4
+ const client_1 = require("@prisma/client");
5
+ const prismaClientSingleton = () => {
6
+ return new client_1.PrismaClient();
7
+ };
8
+ exports.prisma = globalThis.prisma ?? prismaClientSingleton();
9
+ if (process.env.NODE_ENV !== 'production') {
10
+ globalThis.prisma = exports.prisma;
11
+ }
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Generates a thumbnail for an image or PDF file
3
+ * @param fileBuffer The file buffer
4
+ * @param fileType The MIME type of the file
5
+ * @returns Thumbnail buffer
6
+ */
7
+ export declare function generateMediaThumbnail(fileBuffer: Buffer, fileType: string): Promise<Buffer>;
8
+ /**
9
+ * Generates a thumbnail for a file
10
+ * @param fileName The name of the file in Google Cloud Storage
11
+ * @param fileType The MIME type of the file
12
+ * @returns The thumbnail buffer or null if thumbnail generation is not supported
13
+ */
14
+ export declare function generateThumbnail(fileName: string, fileType: string): Promise<Buffer | null>;
15
+ /**
16
+ * Stores a thumbnail in Google Cloud Storage and creates a File entry
17
+ * @param thumbnailBuffer The thumbnail buffer to store
18
+ * @param originalFileName The original file name
19
+ * @param userId The user ID who owns the file
20
+ * @returns The ID of the created thumbnail File
21
+ */
22
+ export declare function storeThumbnail(thumbnailBuffer: Buffer, originalFileName: string, userId: string): Promise<string>;
23
+ //# sourceMappingURL=thumbnailGenerator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"thumbnailGenerator.d.ts","sourceRoot":"","sources":["../../src/lib/thumbnailGenerator.ts"],"names":[],"mappings":"AAiDA;;;;;GAKG;AACH,wBAAsB,sBAAsB,CAAC,UAAU,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CA4BlG;AA4CD;;;;;GAKG;AACH,wBAAsB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CA0BlG;AAED;;;;;;GAMG;AACH,wBAAsB,cAAc,CAAC,eAAe,EAAE,MAAM,EAAE,gBAAgB,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAgBvH"}