@studious-lms/server 1.0.4 → 1.0.7

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 (101) hide show
  1. package/API_SPECIFICATION.md +1117 -0
  2. package/dist/exportType.js +1 -2
  3. package/dist/index.js +25 -30
  4. package/dist/lib/fileUpload.d.ts.map +1 -1
  5. package/dist/lib/fileUpload.js +31 -29
  6. package/dist/lib/googleCloudStorage.js +9 -14
  7. package/dist/lib/prisma.js +4 -7
  8. package/dist/lib/thumbnailGenerator.js +12 -20
  9. package/dist/middleware/auth.d.ts.map +1 -1
  10. package/dist/middleware/auth.js +17 -22
  11. package/dist/middleware/logging.js +5 -9
  12. package/dist/routers/_app.d.ts +3483 -1801
  13. package/dist/routers/_app.d.ts.map +1 -1
  14. package/dist/routers/_app.js +28 -27
  15. package/dist/routers/agenda.d.ts +13 -8
  16. package/dist/routers/agenda.d.ts.map +1 -1
  17. package/dist/routers/agenda.js +14 -17
  18. package/dist/routers/announcement.d.ts +4 -3
  19. package/dist/routers/announcement.d.ts.map +1 -1
  20. package/dist/routers/announcement.js +28 -31
  21. package/dist/routers/assignment.d.ts +282 -196
  22. package/dist/routers/assignment.d.ts.map +1 -1
  23. package/dist/routers/assignment.js +256 -202
  24. package/dist/routers/attendance.d.ts +5 -4
  25. package/dist/routers/attendance.d.ts.map +1 -1
  26. package/dist/routers/attendance.js +31 -34
  27. package/dist/routers/auth.d.ts +1 -0
  28. package/dist/routers/auth.d.ts.map +1 -1
  29. package/dist/routers/auth.js +80 -75
  30. package/dist/routers/class.d.ts +284 -14
  31. package/dist/routers/class.d.ts.map +1 -1
  32. package/dist/routers/class.js +435 -164
  33. package/dist/routers/event.d.ts +47 -38
  34. package/dist/routers/event.d.ts.map +1 -1
  35. package/dist/routers/event.js +76 -79
  36. package/dist/routers/file.d.ts +71 -1
  37. package/dist/routers/file.d.ts.map +1 -1
  38. package/dist/routers/file.js +267 -32
  39. package/dist/routers/folder.d.ts +296 -0
  40. package/dist/routers/folder.d.ts.map +1 -0
  41. package/dist/routers/folder.js +693 -0
  42. package/dist/routers/notifications.d.ts +103 -0
  43. package/dist/routers/notifications.d.ts.map +1 -0
  44. package/dist/routers/notifications.js +91 -0
  45. package/dist/routers/school.d.ts +208 -0
  46. package/dist/routers/school.d.ts.map +1 -0
  47. package/dist/routers/school.js +481 -0
  48. package/dist/routers/section.d.ts +1 -0
  49. package/dist/routers/section.d.ts.map +1 -1
  50. package/dist/routers/section.js +30 -33
  51. package/dist/routers/user.d.ts +2 -1
  52. package/dist/routers/user.d.ts.map +1 -1
  53. package/dist/routers/user.js +21 -24
  54. package/dist/seedDatabase.d.ts +22 -0
  55. package/dist/seedDatabase.d.ts.map +1 -0
  56. package/dist/seedDatabase.js +57 -0
  57. package/dist/socket/handlers.js +26 -30
  58. package/dist/trpc.d.ts +5 -0
  59. package/dist/trpc.d.ts.map +1 -1
  60. package/dist/trpc.js +35 -26
  61. package/dist/types/trpc.js +1 -2
  62. package/dist/utils/email.js +2 -8
  63. package/dist/utils/generateInviteCode.js +1 -5
  64. package/dist/utils/logger.d.ts.map +1 -1
  65. package/dist/utils/logger.js +13 -9
  66. package/dist/utils/prismaErrorHandler.d.ts +9 -0
  67. package/dist/utils/prismaErrorHandler.d.ts.map +1 -0
  68. package/dist/utils/prismaErrorHandler.js +234 -0
  69. package/dist/utils/prismaWrapper.d.ts +14 -0
  70. package/dist/utils/prismaWrapper.d.ts.map +1 -0
  71. package/dist/utils/prismaWrapper.js +64 -0
  72. package/package.json +17 -4
  73. package/prisma/migrations/20250807062924_init/migration.sql +436 -0
  74. package/prisma/migrations/migration_lock.toml +3 -0
  75. package/prisma/schema.prisma +67 -0
  76. package/src/index.ts +2 -2
  77. package/src/lib/fileUpload.ts +16 -7
  78. package/src/middleware/auth.ts +0 -2
  79. package/src/routers/_app.ts +5 -1
  80. package/src/routers/assignment.ts +82 -22
  81. package/src/routers/auth.ts +80 -54
  82. package/src/routers/class.ts +330 -36
  83. package/src/routers/file.ts +283 -20
  84. package/src/routers/folder.ts +755 -0
  85. package/src/routers/notifications.ts +93 -0
  86. package/src/seedDatabase.ts +66 -0
  87. package/src/socket/handlers.ts +4 -4
  88. package/src/trpc.ts +13 -0
  89. package/src/utils/logger.ts +14 -4
  90. package/src/utils/prismaErrorHandler.ts +275 -0
  91. package/src/utils/prismaWrapper.ts +91 -0
  92. package/tests/auth.test.ts +25 -0
  93. package/tests/class.test.ts +281 -0
  94. package/tests/setup.ts +98 -0
  95. package/tests/startup.test.ts +5 -0
  96. package/tsconfig.json +2 -1
  97. package/vitest.config.ts +11 -0
  98. package/dist/logger.d.ts +0 -26
  99. package/dist/logger.d.ts.map +0 -1
  100. package/dist/logger.js +0 -135
  101. package/src/logger.ts +0 -163
@@ -0,0 +1,234 @@
1
+ import { PrismaClientKnownRequestError, PrismaClientUnknownRequestError, PrismaClientValidationError } from '@prisma/client/runtime/library';
2
+ export function handlePrismaError(error) {
3
+ // PrismaClientKnownRequestError - Database constraint violations, etc.
4
+ if (error instanceof PrismaClientKnownRequestError) {
5
+ return handleKnownRequestError(error);
6
+ }
7
+ // PrismaClientValidationError - Invalid data format, missing required fields, etc.
8
+ if (error instanceof PrismaClientValidationError) {
9
+ return handleValidationError(error);
10
+ }
11
+ // PrismaClientUnknownRequestError - Unknown database errors
12
+ if (error instanceof PrismaClientUnknownRequestError) {
13
+ return handleUnknownRequestError(error);
14
+ }
15
+ // Generic error fallback
16
+ if (error instanceof Error) {
17
+ return {
18
+ message: error.message,
19
+ details: error.stack
20
+ };
21
+ }
22
+ return {
23
+ message: 'An unknown database error occurred',
24
+ details: String(error)
25
+ };
26
+ }
27
+ function handleKnownRequestError(error) {
28
+ const { code, meta, message } = error;
29
+ switch (code) {
30
+ case 'P2002':
31
+ const target = Array.isArray(meta?.target) ? meta.target.join(', ') : meta?.target || 'field';
32
+ return {
33
+ message: `A record with this ${target} already exists`,
34
+ code,
35
+ meta,
36
+ details: `Unique constraint violation on ${target}`
37
+ };
38
+ case 'P2003':
39
+ const fieldName = meta?.field_name || 'related field';
40
+ return {
41
+ message: `Cannot delete this record because it's referenced by other records`,
42
+ code,
43
+ meta,
44
+ details: `Foreign key constraint violation on ${fieldName}`
45
+ };
46
+ case 'P2025':
47
+ return {
48
+ message: 'The record you are trying to update or delete does not exist',
49
+ code,
50
+ meta,
51
+ details: 'Record not found in database'
52
+ };
53
+ case 'P2014':
54
+ return {
55
+ message: 'The change you are trying to make would violate the required relationship',
56
+ code,
57
+ meta,
58
+ details: 'Required relation violation'
59
+ };
60
+ case 'P2011':
61
+ return {
62
+ message: 'A required field is missing or empty',
63
+ code,
64
+ meta,
65
+ details: 'Null constraint violation'
66
+ };
67
+ case 'P2012':
68
+ return {
69
+ message: 'The data you provided is not in the correct format',
70
+ code,
71
+ meta,
72
+ details: 'Data validation error'
73
+ };
74
+ case 'P2013':
75
+ return {
76
+ message: 'The data you provided is too long for this field',
77
+ code,
78
+ meta,
79
+ details: 'String length constraint violation'
80
+ };
81
+ case 'P2015':
82
+ return {
83
+ message: 'The record you are looking for could not be found',
84
+ code,
85
+ meta,
86
+ details: 'Record not found'
87
+ };
88
+ case 'P2016':
89
+ return {
90
+ message: 'The query you are trying to execute is not valid',
91
+ code,
92
+ meta,
93
+ details: 'Query interpretation error'
94
+ };
95
+ case 'P2017':
96
+ return {
97
+ message: 'The relationship between records is not properly connected',
98
+ code,
99
+ meta,
100
+ details: 'Relation connection error'
101
+ };
102
+ case 'P2018':
103
+ return {
104
+ message: 'The connected record you are looking for does not exist',
105
+ code,
106
+ meta,
107
+ details: 'Connected record not found'
108
+ };
109
+ case 'P2019':
110
+ return {
111
+ message: 'The input you provided is not valid',
112
+ code,
113
+ meta,
114
+ details: 'Input error'
115
+ };
116
+ case 'P2020':
117
+ return {
118
+ message: 'The value you provided is outside the allowed range',
119
+ code,
120
+ meta,
121
+ details: 'Value out of range'
122
+ };
123
+ case 'P2021':
124
+ return {
125
+ message: 'The table you are trying to access does not exist',
126
+ code,
127
+ meta,
128
+ details: 'Table does not exist'
129
+ };
130
+ case 'P2022':
131
+ return {
132
+ message: 'The column you are trying to access does not exist',
133
+ code,
134
+ meta,
135
+ details: 'Column does not exist'
136
+ };
137
+ case 'P2023':
138
+ return {
139
+ message: 'The column data is not valid',
140
+ code,
141
+ meta,
142
+ details: 'Column data validation error'
143
+ };
144
+ case 'P2024':
145
+ return {
146
+ message: 'The database connection pool is exhausted',
147
+ code,
148
+ meta,
149
+ details: 'Connection pool timeout'
150
+ };
151
+ case 'P2026':
152
+ return {
153
+ message: 'The current database provider does not support this feature',
154
+ code,
155
+ meta,
156
+ details: 'Feature not supported by database provider'
157
+ };
158
+ case 'P2027':
159
+ return {
160
+ message: 'Multiple errors occurred during the database operation',
161
+ code,
162
+ meta,
163
+ details: 'Multiple errors in query execution'
164
+ };
165
+ default:
166
+ return {
167
+ message: 'A database constraint was violated',
168
+ code,
169
+ meta,
170
+ details: message
171
+ };
172
+ }
173
+ }
174
+ function handleValidationError(error) {
175
+ return {
176
+ message: 'The data you provided is not valid',
177
+ details: error.message,
178
+ meta: {
179
+ type: 'validation_error',
180
+ originalMessage: error.message
181
+ }
182
+ };
183
+ }
184
+ function handleUnknownRequestError(error) {
185
+ return {
186
+ message: 'An unexpected database error occurred',
187
+ details: error.message,
188
+ meta: {
189
+ type: 'unknown_request_error',
190
+ originalMessage: error.message
191
+ }
192
+ };
193
+ }
194
+ // Helper function to get user-friendly field names
195
+ export function getFieldDisplayName(fieldName) {
196
+ const fieldMap = {
197
+ 'username': 'username',
198
+ 'email': 'email address',
199
+ 'password': 'password',
200
+ 'name': 'name',
201
+ 'title': 'title',
202
+ 'content': 'content',
203
+ 'description': 'description',
204
+ 'subject': 'subject',
205
+ 'section': 'section',
206
+ 'color': 'color',
207
+ 'location': 'location',
208
+ 'startTime': 'start time',
209
+ 'endTime': 'end time',
210
+ 'dueDate': 'due date',
211
+ 'maxGrade': 'maximum grade',
212
+ 'grade': 'grade',
213
+ 'feedback': 'feedback',
214
+ 'remarks': 'remarks',
215
+ 'syllabus': 'syllabus',
216
+ 'path': 'file path',
217
+ 'size': 'file size',
218
+ 'type': 'file type',
219
+ 'uploadedAt': 'upload date',
220
+ 'verified': 'verification status',
221
+ 'profileId': 'profile',
222
+ 'schoolId': 'school',
223
+ 'classId': 'class',
224
+ 'assignmentId': 'assignment',
225
+ 'submissionId': 'submission',
226
+ 'userId': 'user',
227
+ 'eventId': 'event',
228
+ 'sessionId': 'session',
229
+ 'thumbnailId': 'thumbnail',
230
+ 'annotationId': 'annotation',
231
+ 'logoId': 'logo'
232
+ };
233
+ return fieldMap[fieldName] || fieldName;
234
+ }
@@ -0,0 +1,14 @@
1
+ export declare function withPrismaErrorHandling<T>(operation: () => Promise<T>, context?: string): Promise<T>;
2
+ export declare const prismaWrapper: {
3
+ findUnique: <T>(operation: () => Promise<T | null>, context?: string) => Promise<T | null>;
4
+ findFirst: <T>(operation: () => Promise<T | null>, context?: string) => Promise<T | null>;
5
+ findMany: <T>(operation: () => Promise<T[]>, context?: string) => Promise<T[]>;
6
+ create: <T>(operation: () => Promise<T>, context?: string) => Promise<T>;
7
+ update: <T>(operation: () => Promise<T>, context?: string) => Promise<T>;
8
+ delete: <T>(operation: () => Promise<T>, context?: string) => Promise<T>;
9
+ deleteMany: <T>(operation: () => Promise<T>, context?: string) => Promise<T>;
10
+ upsert: <T>(operation: () => Promise<T>, context?: string) => Promise<T>;
11
+ count: <T>(operation: () => Promise<T>, context?: string) => Promise<T>;
12
+ aggregate: <T>(operation: () => Promise<T>, context?: string) => Promise<T>;
13
+ };
14
+ //# sourceMappingURL=prismaWrapper.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prismaWrapper.d.ts","sourceRoot":"","sources":["../../src/utils/prismaWrapper.ts"],"names":[],"mappings":"AAGA,wBAAsB,uBAAuB,CAAC,CAAC,EAC7C,SAAS,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,EAC3B,OAAO,GAAE,MAA6B,GACrC,OAAO,CAAC,CAAC,CAAC,CAsBZ;AAgCD,eAAO,MAAM,aAAa;iBACX,CAAC,aAAa,MAAM,OAAO,CAAC,CAAC,GAAG,IAAI,CAAC,YAAY,MAAM;gBAGxD,CAAC,aAAa,MAAM,OAAO,CAAC,CAAC,GAAG,IAAI,CAAC,YAAY,MAAM;eAGxD,CAAC,aAAa,MAAM,OAAO,CAAC,CAAC,EAAE,CAAC,YAAY,MAAM;aAGpD,CAAC,aAAa,MAAM,OAAO,CAAC,CAAC,CAAC,YAAY,MAAM;aAGhD,CAAC,aAAa,MAAM,OAAO,CAAC,CAAC,CAAC,YAAY,MAAM;aAGhD,CAAC,aAAa,MAAM,OAAO,CAAC,CAAC,CAAC,YAAY,MAAM;iBAG5C,CAAC,aAAa,MAAM,OAAO,CAAC,CAAC,CAAC,YAAY,MAAM;aAGpD,CAAC,aAAa,MAAM,OAAO,CAAC,CAAC,CAAC,YAAY,MAAM;YAGjD,CAAC,aAAa,MAAM,OAAO,CAAC,CAAC,CAAC,YAAY,MAAM;gBAG5C,CAAC,aAAa,MAAM,OAAO,CAAC,CAAC,CAAC,YAAY,MAAM;CAE7D,CAAC"}
@@ -0,0 +1,64 @@
1
+ import { TRPCError } from '@trpc/server';
2
+ import { handlePrismaError } from './prismaErrorHandler';
3
+ export async function withPrismaErrorHandling(operation, context = 'database operation') {
4
+ try {
5
+ return await operation();
6
+ }
7
+ catch (error) {
8
+ const prismaErrorInfo = handlePrismaError(error);
9
+ // Log the detailed error for debugging
10
+ console.error(`Prisma error in ${context}:`, {
11
+ userMessage: prismaErrorInfo.message,
12
+ details: prismaErrorInfo.details,
13
+ code: prismaErrorInfo.code,
14
+ meta: prismaErrorInfo.meta,
15
+ originalError: error
16
+ });
17
+ // Throw a tRPC error with the user-friendly message
18
+ throw new TRPCError({
19
+ code: getTRPCCode(prismaErrorInfo.code),
20
+ message: prismaErrorInfo.message,
21
+ cause: error
22
+ });
23
+ }
24
+ }
25
+ function getTRPCCode(prismaCode) {
26
+ if (!prismaCode)
27
+ return 'INTERNAL_SERVER_ERROR';
28
+ // Map Prisma error codes to tRPC error codes
29
+ const codeMap = {
30
+ 'P2002': 'CONFLICT', // Unique constraint violation
31
+ 'P2003': 'BAD_REQUEST', // Foreign key constraint violation
32
+ 'P2025': 'NOT_FOUND', // Record not found
33
+ 'P2014': 'BAD_REQUEST', // Required relation violation
34
+ 'P2011': 'BAD_REQUEST', // Null constraint violation
35
+ 'P2012': 'BAD_REQUEST', // Data validation error
36
+ 'P2013': 'BAD_REQUEST', // String length constraint violation
37
+ 'P2015': 'NOT_FOUND', // Record not found
38
+ 'P2016': 'BAD_REQUEST', // Query interpretation error
39
+ 'P2017': 'BAD_REQUEST', // Relation connection error
40
+ 'P2018': 'NOT_FOUND', // Connected record not found
41
+ 'P2019': 'BAD_REQUEST', // Input error
42
+ 'P2020': 'BAD_REQUEST', // Value out of range
43
+ 'P2021': 'INTERNAL_SERVER_ERROR', // Table does not exist
44
+ 'P2022': 'INTERNAL_SERVER_ERROR', // Column does not exist
45
+ 'P2023': 'BAD_REQUEST', // Column data validation error
46
+ 'P2024': 'INTERNAL_SERVER_ERROR', // Connection pool timeout
47
+ 'P2026': 'INTERNAL_SERVER_ERROR', // Feature not supported
48
+ 'P2027': 'INTERNAL_SERVER_ERROR', // Multiple errors
49
+ };
50
+ return codeMap[prismaCode] || 'INTERNAL_SERVER_ERROR';
51
+ }
52
+ // Convenience functions for common operations
53
+ export const prismaWrapper = {
54
+ findUnique: (operation, context) => withPrismaErrorHandling(operation, context),
55
+ findFirst: (operation, context) => withPrismaErrorHandling(operation, context),
56
+ findMany: (operation, context) => withPrismaErrorHandling(operation, context),
57
+ create: (operation, context) => withPrismaErrorHandling(operation, context),
58
+ update: (operation, context) => withPrismaErrorHandling(operation, context),
59
+ delete: (operation, context) => withPrismaErrorHandling(operation, context),
60
+ deleteMany: (operation, context) => withPrismaErrorHandling(operation, context),
61
+ upsert: (operation, context) => withPrismaErrorHandling(operation, context),
62
+ count: (operation, context) => withPrismaErrorHandling(operation, context),
63
+ aggregate: (operation, context) => withPrismaErrorHandling(operation, context),
64
+ };
package/package.json CHANGED
@@ -1,15 +1,24 @@
1
1
  {
2
2
  "name": "@studious-lms/server",
3
- "version": "1.0.4",
3
+ "version": "1.0.7",
4
4
  "description": "Backend server for Studious application",
5
5
  "main": "dist/exportType.js",
6
6
  "types": "dist/exportType.d.ts",
7
+ "type": "module",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/exportType.d.ts",
11
+ "default": "./dist/exportType.js"
12
+ }
13
+ },
7
14
  "scripts": {
8
- "dev": "ts-node-dev -r tsconfig-paths/register --respawn --transpile-only src/index.ts",
15
+ "dev": "ts-node-dev --respawn --transpile-only --loader ts-node/esm src/index.ts",
9
16
  "build": "tsc",
10
17
  "start": "node dist/index.js",
11
18
  "generate": "npx prisma generate",
12
- "prepublishOnly": "npm run generate && npm run build"
19
+ "prepublishOnly": "npm run generate && npm run build",
20
+ "test": "vitest",
21
+ "seed": "ts-node src/seedDatabase.ts"
13
22
  },
14
23
  "dependencies": {
15
24
  "@google-cloud/storage": "^7.16.0",
@@ -23,6 +32,7 @@
23
32
  "nodemailer": "^7.0.4",
24
33
  "prisma": "^6.7.0",
25
34
  "sharp": "^0.34.2",
35
+ "socket.io": "^4.8.1",
26
36
  "superjson": "^2.2.2",
27
37
  "uuid": "^11.1.0",
28
38
  "zod": "^3.22.4"
@@ -32,10 +42,13 @@
32
42
  "@types/express": "^4.17.21",
33
43
  "@types/node": "^20.11.24",
34
44
  "@types/nodemailer": "^6.4.17",
45
+ "@types/supertest": "^6.0.3",
35
46
  "@types/uuid": "^10.0.0",
47
+ "supertest": "^7.1.4",
36
48
  "ts-node": "^10.9.2",
37
49
  "ts-node-dev": "^2.0.0",
38
50
  "tsconfig-paths": "^4.2.0",
39
- "typescript": "^5.3.3"
51
+ "typescript": "^5.3.3",
52
+ "vitest": "^3.2.4"
40
53
  }
41
54
  }