@studious-lms/server 1.4.1 → 1.4.3

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 (248) hide show
  1. package/.coderabbit.yaml +4 -1
  2. package/.env.example +6 -0
  3. package/.env.test.example +2 -0
  4. package/dist/index.d.ts.map +1 -1
  5. package/dist/index.js +101 -76
  6. package/dist/index.js.map +1 -1
  7. package/dist/lib/config/cors.d.ts +16 -0
  8. package/dist/lib/config/cors.d.ts.map +1 -0
  9. package/dist/lib/config/cors.js +75 -0
  10. package/dist/lib/config/cors.js.map +1 -0
  11. package/dist/lib/config/env.d.ts +14 -0
  12. package/dist/lib/config/env.d.ts.map +1 -1
  13. package/dist/lib/config/env.js +9 -2
  14. package/dist/lib/config/env.js.map +1 -1
  15. package/dist/lib/fileUpload.d.ts +10 -2
  16. package/dist/lib/fileUpload.d.ts.map +1 -1
  17. package/dist/lib/fileUpload.js +38 -6
  18. package/dist/lib/fileUpload.js.map +1 -1
  19. package/dist/lib/prisma.d.ts +14 -2
  20. package/dist/lib/prisma.d.ts.map +1 -1
  21. package/dist/lib/prisma.js +27 -8
  22. package/dist/lib/prisma.js.map +1 -1
  23. package/dist/middleware/auth.d.ts.map +1 -1
  24. package/dist/middleware/auth.js +18 -5
  25. package/dist/middleware/auth.js.map +1 -1
  26. package/dist/middleware/security.d.ts.map +1 -1
  27. package/dist/middleware/security.js +3 -3
  28. package/dist/middleware/security.js.map +1 -1
  29. package/dist/models/agenda.d.ts +16 -16
  30. package/dist/models/announcement.d.ts +59 -23
  31. package/dist/models/announcement.d.ts.map +1 -1
  32. package/dist/models/assignment.d.ts +363 -276
  33. package/dist/models/assignment.d.ts.map +1 -1
  34. package/dist/models/attendance.d.ts +63 -21
  35. package/dist/models/attendance.d.ts.map +1 -1
  36. package/dist/models/auth.d.ts +115 -26
  37. package/dist/models/auth.d.ts.map +1 -1
  38. package/dist/models/auth.js +53 -30
  39. package/dist/models/auth.js.map +1 -1
  40. package/dist/models/class.d.ts +115 -65
  41. package/dist/models/class.d.ts.map +1 -1
  42. package/dist/models/class.js +16 -4
  43. package/dist/models/class.js.map +1 -1
  44. package/dist/models/comment.d.ts +52 -16
  45. package/dist/models/comment.d.ts.map +1 -1
  46. package/dist/models/conversation.d.ts +46 -16
  47. package/dist/models/conversation.d.ts.map +1 -1
  48. package/dist/models/event.d.ts +107 -53
  49. package/dist/models/event.d.ts.map +1 -1
  50. package/dist/models/file.d.ts +213 -165
  51. package/dist/models/file.d.ts.map +1 -1
  52. package/dist/models/folder.d.ts +161 -77
  53. package/dist/models/folder.d.ts.map +1 -1
  54. package/dist/models/labChat.d.ts +73 -31
  55. package/dist/models/labChat.d.ts.map +1 -1
  56. package/dist/models/marketing.d.ts +25 -7
  57. package/dist/models/marketing.d.ts.map +1 -1
  58. package/dist/models/message.d.ts +31 -13
  59. package/dist/models/message.d.ts.map +1 -1
  60. package/dist/models/newtonChat.d.ts +34 -10
  61. package/dist/models/newtonChat.d.ts.map +1 -1
  62. package/dist/models/notification.d.ts +29 -10
  63. package/dist/models/notification.d.ts.map +1 -1
  64. package/dist/models/notification.js +13 -9
  65. package/dist/models/notification.js.map +1 -1
  66. package/dist/models/section.d.ts +71 -23
  67. package/dist/models/section.d.ts.map +1 -1
  68. package/dist/models/user.d.ts +27 -9
  69. package/dist/models/user.d.ts.map +1 -1
  70. package/dist/models/worksheet.d.ts +606 -106
  71. package/dist/models/worksheet.d.ts.map +1 -1
  72. package/dist/models/worksheet.js +168 -2
  73. package/dist/models/worksheet.js.map +1 -1
  74. package/dist/pipelines/aiLabChat.d.ts +22 -2
  75. package/dist/pipelines/aiLabChat.d.ts.map +1 -1
  76. package/dist/pipelines/aiLabChat.js +133 -95
  77. package/dist/pipelines/aiLabChat.js.map +1 -1
  78. package/dist/pipelines/aiLabChatContract.d.ts +25 -25
  79. package/dist/pipelines/aiNewtonChat.d.ts.map +1 -1
  80. package/dist/pipelines/aiNewtonChat.js +25 -14
  81. package/dist/pipelines/aiNewtonChat.js.map +1 -1
  82. package/dist/pipelines/gradeWorksheet.d.ts +8 -8
  83. package/dist/pipelines/gradeWorksheet.d.ts.map +1 -1
  84. package/dist/pipelines/gradeWorksheet.js +12 -5
  85. package/dist/pipelines/gradeWorksheet.js.map +1 -1
  86. package/dist/pipelines/labChatPrompt.d.ts +27 -0
  87. package/dist/pipelines/labChatPrompt.d.ts.map +1 -1
  88. package/dist/pipelines/labChatPrompt.js +143 -69
  89. package/dist/pipelines/labChatPrompt.js.map +1 -1
  90. package/dist/routers/_app.d.ts +1579 -1257
  91. package/dist/routers/_app.d.ts.map +1 -1
  92. package/dist/routers/agenda.d.ts +16 -16
  93. package/dist/routers/announcement.d.ts +19 -19
  94. package/dist/routers/assignment.d.ts +307 -291
  95. package/dist/routers/assignment.d.ts.map +1 -1
  96. package/dist/routers/assignment.js +3 -2
  97. package/dist/routers/assignment.js.map +1 -1
  98. package/dist/routers/attendance.d.ts +7 -7
  99. package/dist/routers/auth.d.ts +1 -1
  100. package/dist/routers/class.d.ts +79 -72
  101. package/dist/routers/class.d.ts.map +1 -1
  102. package/dist/routers/class.js +3 -3
  103. package/dist/routers/class.js.map +1 -1
  104. package/dist/routers/comment.d.ts +6 -6
  105. package/dist/routers/conversation.d.ts +11 -11
  106. package/dist/routers/event.d.ts +35 -35
  107. package/dist/routers/file.d.ts +12 -12
  108. package/dist/routers/folder.d.ts +54 -54
  109. package/dist/routers/labChat.d.ts +18 -12
  110. package/dist/routers/labChat.d.ts.map +1 -1
  111. package/dist/routers/labChat.js +5 -3
  112. package/dist/routers/labChat.js.map +1 -1
  113. package/dist/routers/marketing.d.ts +2 -2
  114. package/dist/routers/message.d.ts +33 -2
  115. package/dist/routers/message.d.ts.map +1 -1
  116. package/dist/routers/message.js +27 -4
  117. package/dist/routers/message.js.map +1 -1
  118. package/dist/routers/newtonChat.d.ts +7 -1
  119. package/dist/routers/newtonChat.d.ts.map +1 -1
  120. package/dist/routers/newtonChat.js +5 -4
  121. package/dist/routers/newtonChat.js.map +1 -1
  122. package/dist/routers/notifications.d.ts +13 -6
  123. package/dist/routers/notifications.d.ts.map +1 -1
  124. package/dist/routers/notifications.js +4 -4
  125. package/dist/routers/notifications.js.map +1 -1
  126. package/dist/routers/section.d.ts +7 -7
  127. package/dist/routers/studentProgress.d.ts +86 -0
  128. package/dist/routers/studentProgress.d.ts.map +1 -1
  129. package/dist/routers/studentProgress.js +14 -4
  130. package/dist/routers/studentProgress.js.map +1 -1
  131. package/dist/routers/user.d.ts +1 -1
  132. package/dist/routers/worksheet.d.ts +64 -62
  133. package/dist/routers/worksheet.d.ts.map +1 -1
  134. package/dist/routers/worksheet.js +18 -16
  135. package/dist/routers/worksheet.js.map +1 -1
  136. package/dist/seedDatabase.d.ts +1 -1
  137. package/dist/services/agenda.d.ts +16 -16
  138. package/dist/services/announcement.d.ts +8 -8
  139. package/dist/services/assignment.d.ts +299 -283
  140. package/dist/services/assignment.d.ts.map +1 -1
  141. package/dist/services/assignment.js +24 -5
  142. package/dist/services/assignment.js.map +1 -1
  143. package/dist/services/attendance.d.ts +7 -7
  144. package/dist/services/auth.d.ts +2 -2
  145. package/dist/services/auth.d.ts.map +1 -1
  146. package/dist/services/auth.js +19 -12
  147. package/dist/services/auth.js.map +1 -1
  148. package/dist/services/class.d.ts +74 -68
  149. package/dist/services/class.d.ts.map +1 -1
  150. package/dist/services/class.js +6 -3
  151. package/dist/services/class.js.map +1 -1
  152. package/dist/services/comment.d.ts +6 -6
  153. package/dist/services/conversation.d.ts +11 -11
  154. package/dist/services/event.d.ts +31 -31
  155. package/dist/services/file.d.ts +12 -12
  156. package/dist/services/file.d.ts.map +1 -1
  157. package/dist/services/file.js +7 -2
  158. package/dist/services/file.js.map +1 -1
  159. package/dist/services/folder.d.ts +52 -52
  160. package/dist/services/labChat.d.ts +18 -12
  161. package/dist/services/labChat.d.ts.map +1 -1
  162. package/dist/services/labChat.js +30 -18
  163. package/dist/services/labChat.js.map +1 -1
  164. package/dist/services/marketing.d.ts +2 -2
  165. package/dist/services/message.d.ts +30 -0
  166. package/dist/services/message.d.ts.map +1 -1
  167. package/dist/services/message.js +97 -21
  168. package/dist/services/message.js.map +1 -1
  169. package/dist/services/newtonChat.d.ts +6 -1
  170. package/dist/services/newtonChat.d.ts.map +1 -1
  171. package/dist/services/newtonChat.js +36 -18
  172. package/dist/services/newtonChat.js.map +1 -1
  173. package/dist/services/notification.d.ts +16 -9
  174. package/dist/services/notification.d.ts.map +1 -1
  175. package/dist/services/notification.js +24 -9
  176. package/dist/services/notification.js.map +1 -1
  177. package/dist/services/section.d.ts +6 -6
  178. package/dist/services/studentProgress.d.ts +75 -0
  179. package/dist/services/studentProgress.d.ts.map +1 -1
  180. package/dist/services/studentProgress.js +300 -107
  181. package/dist/services/studentProgress.js.map +1 -1
  182. package/dist/services/worksheet.d.ts +64 -64
  183. package/dist/services/worksheet.d.ts.map +1 -1
  184. package/dist/services/worksheet.js +138 -54
  185. package/dist/services/worksheet.js.map +1 -1
  186. package/dist/trpc.d.ts.map +1 -1
  187. package/dist/trpc.js +11 -17
  188. package/dist/trpc.js.map +1 -1
  189. package/dist/utils/inference.d.ts +9 -12
  190. package/dist/utils/inference.d.ts.map +1 -1
  191. package/dist/utils/inference.js +135 -95
  192. package/dist/utils/inference.js.map +1 -1
  193. package/package.json +1 -1
  194. package/prisma/migrations/20260410124000_add_submission_recommendation_state/migration.sql +14 -0
  195. package/prisma/migrations/20260415103500_add_session_type/migration.sql +33 -0
  196. package/prisma/migrations/20260415133000_add_ai_usage_logs/migration.sql +19 -0
  197. package/prisma/schema.prisma +41 -1
  198. package/scripts/test-pre-push.ts +6 -2
  199. package/src/index.ts +119 -79
  200. package/src/lib/config/cors.ts +96 -0
  201. package/src/lib/config/env.ts +12 -1
  202. package/src/lib/fileUpload.ts +55 -5
  203. package/src/lib/prisma.ts +25 -6
  204. package/src/middleware/auth.ts +19 -4
  205. package/src/middleware/security.ts +1 -1
  206. package/src/models/auth.ts +59 -29
  207. package/src/models/class.ts +15 -4
  208. package/src/models/notification.ts +13 -7
  209. package/src/models/worksheet.ts +202 -0
  210. package/src/pipelines/aiLabChat.ts +183 -104
  211. package/src/pipelines/aiNewtonChat.ts +24 -14
  212. package/src/pipelines/gradeWorksheet.ts +10 -2
  213. package/src/pipelines/labChatPrompt.ts +196 -68
  214. package/src/routers/assignment.ts +1 -0
  215. package/src/routers/class.ts +2 -2
  216. package/src/routers/labChat.ts +3 -1
  217. package/src/routers/message.ts +38 -1
  218. package/src/routers/newtonChat.ts +4 -2
  219. package/src/routers/notifications.ts +2 -2
  220. package/src/routers/studentProgress.ts +25 -1
  221. package/src/routers/worksheet.ts +31 -19
  222. package/src/services/assignment.ts +30 -2
  223. package/src/services/auth.ts +26 -12
  224. package/src/services/class.ts +4 -1
  225. package/src/services/file.ts +6 -0
  226. package/src/services/labChat.ts +42 -18
  227. package/src/services/message.ts +163 -18
  228. package/src/services/newtonChat.ts +49 -18
  229. package/src/services/notification.ts +27 -8
  230. package/src/services/studentProgress.ts +425 -121
  231. package/src/services/worksheet.ts +191 -51
  232. package/src/trpc.ts +12 -18
  233. package/src/utils/inference.ts +205 -112
  234. package/tests/globalSetup.ts +13 -4
  235. package/tests/lib/cors.test.ts +103 -0
  236. package/tests/pipelines/aiLabChat.test.ts +64 -84
  237. package/tests/routers/auth.test.ts +120 -17
  238. package/tests/routers/class.test.ts +7 -1
  239. package/tests/routers/message.test.ts +236 -9
  240. package/tests/routers/newtonChat.test.ts +81 -0
  241. package/tests/routers/notification.test.ts +12 -0
  242. package/tests/routers/studentProgress.test.ts +2 -31
  243. package/tests/routers/worksheet.test.ts +194 -0
  244. package/tests/setup.ts +22 -5
  245. package/tests/utils/aiLabChatPrompt.test.ts +114 -6
  246. package/tests/utils/inference.test.ts +218 -0
  247. package/tests/utils/studentProgress.test.ts +361 -0
  248. package/vitest.unit.config.ts +1 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cors.js","sources":["lib/config/cors.ts"],"sourceRoot":"/","sourcesContent":["type CorsEnv = {\n NODE_ENV?: 'development' | 'production' | 'test';\n NEXT_PUBLIC_APP_URL?: string;\n CORS_ALLOWED_ORIGINS?: string;\n CORS_ALLOWED_ORIGIN_PATTERNS?: string;\n};\n\nconst productionOrigins = [\n 'https://www.studious.sh',\n 'https://studious.sh',\n 'https://dev.studious.sh',\n 'https://www.dev.studious.sh',\n];\n\nconst nonProductionOrigins = [\n 'http://localhost:3000',\n 'http://localhost:3001',\n 'http://127.0.0.1:3000',\n 'http://127.0.0.1:3001',\n];\nconst MAX_PATTERN_LENGTH = 200;\n// This heuristic only targets common nested-quantifier cases like `(a+)+`.\n// It does not catch every risky construct (for example `.*.*` or deep alternation nesting),\n// so `parseCorsOriginPatterns` should still be limited to operator-controlled, anchored patterns.\nconst SUSPICIOUS_QUANTIFIER_PATTERN = /(\\([^)]*[+*{][^)]*\\)|\\[[^\\]]+[+*{][^\\]]*\\])[+*{]/;\n\nconst parseList = (value?: string) =>\n value\n ?.split(',')\n .map((item) => item.trim())\n .filter(Boolean) ?? [];\n\nconst isDefined = <T>(value: T | undefined | null): value is T => Boolean(value);\n\nconst toCorsOrigin = (value: string) => {\n try {\n return new URL(value).origin;\n } catch {\n return value.replace(/\\/+$/, '');\n }\n};\n\nexport const parseCorsOrigins = (origins?: string) =>\n parseList(origins).map(toCorsOrigin);\n\nconst validateCorsOriginPattern = (pattern: string) => {\n if (pattern.length > MAX_PATTERN_LENGTH) {\n throw new Error(`CORS pattern exceeds maximum length of ${MAX_PATTERN_LENGTH}`);\n }\n\n // Prefer short, anchored patterns and avoid other complex regex features even if they pass this check.\n if (SUSPICIOUS_QUANTIFIER_PATTERN.test(pattern)) {\n throw new Error(\"CORS pattern contains nested or ambiguous quantifiers\");\n }\n\n return pattern;\n};\n\n// Patterns are operator-controlled and matched against bounded URL origins, so ReDoS risk is low.\n// Syntax validation does not guarantee safety; prefer anchored patterns and avoid nested quantifiers.\nexport const parseCorsOriginPatterns = (patterns?: string) =>\n parseList(patterns).map((pattern) => new RegExp(validateCorsOriginPattern(pattern)));\n\nexport const isValidCorsOriginPatternList = (patterns?: string) => {\n try {\n parseCorsOriginPatterns(patterns);\n return true;\n } catch {\n return false;\n }\n};\n\nexport const createCorsOriginMatcher = (env: CorsEnv) => {\n const allowedOrigins = Array.from(new Set([\n ...(env.NODE_ENV === 'production' ? productionOrigins : nonProductionOrigins),\n env.NEXT_PUBLIC_APP_URL ? toCorsOrigin(env.NEXT_PUBLIC_APP_URL) : undefined,\n ...parseCorsOrigins(env.CORS_ALLOWED_ORIGINS),\n ].filter(isDefined)));\n\n const allowedOriginPatterns = parseCorsOriginPatterns(env.CORS_ALLOWED_ORIGIN_PATTERNS);\n\n const isAllowedOrigin = (origin?: string) => {\n const corsOrigin = origin ? toCorsOrigin(origin) : undefined;\n\n return Boolean(corsOrigin && (\n allowedOrigins.includes(corsOrigin) ||\n allowedOriginPatterns.some((pattern) => pattern.test(corsOrigin))\n ));\n };\n\n return {\n allowedOrigins,\n allowedOriginPatterns,\n isAllowedOrigin,\n };\n};\n"],"names":[],"mappings":";;AAOA,MAAM,iBAAiB,GAAG;IACxB,yBAAyB;IACzB,qBAAqB;IACrB,yBAAyB;IACzB,6BAA6B;CAC9B,CAAC;AAEF,MAAM,oBAAoB,GAAG;IAC3B,uBAAuB;IACvB,uBAAuB;IACvB,uBAAuB;IACvB,uBAAuB;CACxB,CAAC;AACF,MAAM,kBAAkB,GAAG,GAAG,CAAC;AAC/B,2EAA2E;AAC3E,4FAA4F;AAC5F,kGAAkG;AAClG,MAAM,6BAA6B,GAAG,kDAAkD,CAAC;AAEzF,MAAM,SAAS,GAAG,CAAC,KAAc,EAAE,EAAE,CACnC,KAAK;IACH,EAAE,KAAK,CAAC,GAAG,CAAC;KACX,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC;KAC1B,MAAM,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC;AAE3B,MAAM,SAAS,GAAG,CAAI,KAA2B,EAAc,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;AAEjF,MAAM,YAAY,GAAG,CAAC,KAAa,EAAE,EAAE;IACrC,IAAI,CAAC;QACH,OAAO,IAAI,GAAG,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC;IAC/B,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;IACnC,CAAC;AACH,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,OAAgB,EAAE,EAAE,CACnD,SAAS,CAAC,OAAO,CAAC,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC;AAEvC,MAAM,yBAAyB,GAAG,CAAC,OAAe,EAAE,EAAE;IACpD,IAAI,OAAO,CAAC,MAAM,GAAG,kBAAkB,EAAE,CAAC;QACxC,MAAM,IAAI,KAAK,CAAC,0CAA0C,kBAAkB,EAAE,CAAC,CAAC;IAClF,CAAC;IAED,uGAAuG;IACvG,IAAI,6BAA6B,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC;QAChD,MAAM,IAAI,KAAK,CAAC,uDAAuD,CAAC,CAAC;IAC3E,CAAC;IAED,OAAO,OAAO,CAAC;AACjB,CAAC,CAAC;AAEF,kGAAkG;AAClG,sGAAsG;AACtG,MAAM,CAAC,MAAM,uBAAuB,GAAG,CAAC,QAAiB,EAAE,EAAE,CAC3D,SAAS,CAAC,QAAQ,CAAC,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,IAAI,MAAM,CAAC,yBAAyB,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;AAEvF,MAAM,CAAC,MAAM,4BAA4B,GAAG,CAAC,QAAiB,EAAE,EAAE;IAChE,IAAI,CAAC;QACH,uBAAuB,CAAC,QAAQ,CAAC,CAAC;QAClC,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,uBAAuB,GAAG,CAAC,GAAY,EAAE,EAAE;IACtD,MAAM,cAAc,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,GAAG,CAAC;QACxC,GAAG,CAAC,GAAG,CAAC,QAAQ,KAAK,YAAY,CAAC,CAAC,CAAC,iBAAiB,CAAC,CAAC,CAAC,oBAAoB,CAAC;QAC7E,GAAG,CAAC,mBAAmB,CAAC,CAAC,CAAC,YAAY,CAAC,GAAG,CAAC,mBAAmB,CAAC,CAAC,CAAC,CAAC,SAAS;QAC3E,GAAG,gBAAgB,CAAC,GAAG,CAAC,oBAAoB,CAAC;KAC9C,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC;IAEtB,MAAM,qBAAqB,GAAG,uBAAuB,CAAC,GAAG,CAAC,4BAA4B,CAAC,CAAC;IAExF,MAAM,eAAe,GAAG,CAAC,MAAe,EAAE,EAAE;QAC1C,MAAM,UAAU,GAAG,MAAM,CAAC,CAAC,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;QAE7D,OAAO,OAAO,CAAC,UAAU,IAAI,CAC3B,cAAc,CAAC,QAAQ,CAAC,UAAU,CAAC;YACnC,qBAAqB,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,CAClE,CAAC,CAAC;IACL,CAAC,CAAC;IAEF,OAAO;QACL,cAAc;QACd,qBAAqB;QACrB,eAAe;KAChB,CAAC;AACJ,CAAC,CAAC","debug_id":"2499986f-b37e-5e23-a023-556de6317b0f"}
@@ -5,6 +5,8 @@ declare const envSchema: z.ZodObject<{
5
5
  DATABASE_URL: z.ZodString;
6
6
  } & {
7
7
  NEXT_PUBLIC_APP_URL: z.ZodDefault<z.ZodString>;
8
+ CORS_ALLOWED_ORIGINS: z.ZodOptional<z.ZodString>;
9
+ CORS_ALLOWED_ORIGIN_PATTERNS: z.ZodEffects<z.ZodOptional<z.ZodString>, string | undefined, string | undefined>;
8
10
  BACKEND_URL: z.ZodDefault<z.ZodString>;
9
11
  SENTRY_DSN: z.ZodOptional<z.ZodString>;
10
12
  EMAIL_HOST: z.ZodString;
@@ -46,6 +48,8 @@ declare const envSchema: z.ZodObject<{
46
48
  PUSHER_KEY: string;
47
49
  PUSHER_SECRET: string;
48
50
  PUSHER_CLUSTER: string;
51
+ CORS_ALLOWED_ORIGINS?: string | undefined;
52
+ CORS_ALLOWED_ORIGIN_PATTERNS?: string | undefined;
49
53
  SENTRY_DSN?: string | undefined;
50
54
  PUSHER_AUTH_COOKIE_NAME?: string | undefined;
51
55
  REDIS_URL?: string | undefined;
@@ -69,6 +73,8 @@ declare const envSchema: z.ZodObject<{
69
73
  LOG_MODE?: "normal" | "verbose" | "quiet" | undefined;
70
74
  PORT?: string | undefined;
71
75
  NEXT_PUBLIC_APP_URL?: string | undefined;
76
+ CORS_ALLOWED_ORIGINS?: string | undefined;
77
+ CORS_ALLOWED_ORIGIN_PATTERNS?: string | undefined;
72
78
  BACKEND_URL?: string | undefined;
73
79
  SENTRY_DSN?: string | undefined;
74
80
  EMAIL_PORT?: string | undefined;
@@ -84,6 +90,8 @@ declare const envSchema: z.ZodObject<{
84
90
  DATABASE_URL: z.ZodString;
85
91
  } & {
86
92
  NEXT_PUBLIC_APP_URL: z.ZodDefault<z.ZodOptional<z.ZodString>>;
93
+ CORS_ALLOWED_ORIGINS: z.ZodOptional<z.ZodString>;
94
+ CORS_ALLOWED_ORIGIN_PATTERNS: z.ZodEffects<z.ZodOptional<z.ZodString>, string | undefined, string | undefined>;
87
95
  BACKEND_URL: z.ZodDefault<z.ZodOptional<z.ZodString>>;
88
96
  SENTRY_DSN: z.ZodOptional<z.ZodString>;
89
97
  EMAIL_HOST: z.ZodDefault<z.ZodOptional<z.ZodString>>;
@@ -125,6 +133,8 @@ declare const envSchema: z.ZodObject<{
125
133
  PUSHER_KEY: string;
126
134
  PUSHER_SECRET: string;
127
135
  PUSHER_CLUSTER: string;
136
+ CORS_ALLOWED_ORIGINS?: string | undefined;
137
+ CORS_ALLOWED_ORIGIN_PATTERNS?: string | undefined;
128
138
  SENTRY_DSN?: string | undefined;
129
139
  PUSHER_AUTH_COOKIE_NAME?: string | undefined;
130
140
  REDIS_URL?: string | undefined;
@@ -137,6 +147,8 @@ declare const envSchema: z.ZodObject<{
137
147
  LOG_MODE?: "normal" | "verbose" | "quiet" | undefined;
138
148
  PORT?: string | undefined;
139
149
  NEXT_PUBLIC_APP_URL?: string | undefined;
150
+ CORS_ALLOWED_ORIGINS?: string | undefined;
151
+ CORS_ALLOWED_ORIGIN_PATTERNS?: string | undefined;
140
152
  BACKEND_URL?: string | undefined;
141
153
  SENTRY_DSN?: string | undefined;
142
154
  EMAIL_HOST?: string | undefined;
@@ -178,6 +190,8 @@ export declare const env: {
178
190
  PUSHER_KEY: string;
179
191
  PUSHER_SECRET: string;
180
192
  PUSHER_CLUSTER: string;
193
+ CORS_ALLOWED_ORIGINS?: string | undefined;
194
+ CORS_ALLOWED_ORIGIN_PATTERNS?: string | undefined;
181
195
  SENTRY_DSN?: string | undefined;
182
196
  PUSHER_AUTH_COOKIE_NAME?: string | undefined;
183
197
  REDIS_URL?: string | undefined;
@@ -1 +1 @@
1
- {"version":3,"file":"env.d.ts","sourceRoot":"/","sources":["lib/config/env.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAqFxB,QAAA,MAAM,SAAS;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAAmC,CAAC;AA2CnD,eAAO,MAAM,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;CAAgB,CAAC;AAGjC,MAAM,MAAM,GAAG,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,SAAS,CAAC,CAAC"}
1
+ {"version":3,"file":"env.d.ts","sourceRoot":"/","sources":["lib/config/env.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAgGxB,QAAA,MAAM,SAAS;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAAmC,CAAC;AA2CnD,eAAO,MAAM,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAAgB,CAAC;AAGjC,MAAM,MAAM,GAAG,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,SAAS,CAAC,CAAC"}
@@ -1,9 +1,10 @@
1
1
 
2
- !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="6fe796c8-7781-5540-892a-a320464bfbae")}catch(e){}}();
2
+ !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="2c09f0e4-0311-5ba3-bfea-895c59d8b314")}catch(e){}}();
3
3
  import { z } from 'zod';
4
4
  import dotenv from 'dotenv';
5
5
  import { resolve } from 'path';
6
6
  import { logger } from '../../utils/logger.js';
7
+ import { isValidCorsOriginPatternList } from './cors.js';
7
8
  // Determine which env file to load based on NODE_ENV
8
9
  const nodeEnv = process.env.NODE_ENV || 'development';
9
10
  const envFileMap = {
@@ -20,6 +21,8 @@ dotenv.config(); // Load .env first (base config)
20
21
  dotenv.config({ path: envPath, override: true }); // Override with env-specific
21
22
  const isTest = nodeEnv === 'test';
22
23
  const isProduction = nodeEnv === 'production';
24
+ const corsAllowedOriginsSchema = z.string().optional();
25
+ const corsAllowedOriginPatternsSchema = z.string().optional().refine(isValidCorsOriginPatternList, { message: 'CORS_ALLOWED_ORIGIN_PATTERNS must contain valid regex patterns' });
23
26
  // Base schema with required vars for all environments
24
27
  const baseSchema = z.object({
25
28
  NODE_ENV: z.enum(['development', 'production', 'test']).default('development'),
@@ -29,6 +32,8 @@ const baseSchema = z.object({
29
32
  // Production/development schema with all required vars
30
33
  const fullSchema = baseSchema.extend({
31
34
  NEXT_PUBLIC_APP_URL: z.string().url().default('http://localhost:3000'),
35
+ CORS_ALLOWED_ORIGINS: corsAllowedOriginsSchema,
36
+ CORS_ALLOWED_ORIGIN_PATTERNS: corsAllowedOriginPatternsSchema,
32
37
  BACKEND_URL: z.string().url().default('http://localhost:3001'),
33
38
  SENTRY_DSN: z.string().url().optional(),
34
39
  EMAIL_HOST: z.string().min(1, 'EMAIL_HOST is required'),
@@ -54,6 +59,8 @@ const fullSchema = baseSchema.extend({
54
59
  // Test schema - only require what's needed for tests
55
60
  const testSchema = baseSchema.extend({
56
61
  NEXT_PUBLIC_APP_URL: z.string().url().optional().default('http://localhost:3000'),
62
+ CORS_ALLOWED_ORIGINS: corsAllowedOriginsSchema,
63
+ CORS_ALLOWED_ORIGIN_PATTERNS: corsAllowedOriginPatternsSchema,
57
64
  BACKEND_URL: z.string().url().optional().default('http://localhost:3001'),
58
65
  SENTRY_DSN: z.string().url().optional(),
59
66
  EMAIL_HOST: z.string().optional().default('smtp.test.com'),
@@ -118,4 +125,4 @@ function validateEnv() {
118
125
  // Export validated environment variables
119
126
  export const env = validateEnv();
120
127
  //# sourceMappingURL=env.js.map
121
- //# debugId=6fe796c8-7781-5540-892a-a320464bfbae
128
+ //# debugId=2c09f0e4-0311-5ba3-bfea-895c59d8b314
@@ -1 +1 @@
1
- {"version":3,"file":"env.js","sources":["lib/config/env.ts"],"sourceRoot":"/","sourcesContent":["import { z } from 'zod';\nimport dotenv from 'dotenv';\nimport { resolve } from 'path';\nimport { logger } from '../../utils/logger.js';\n\n// Determine which env file to load based on NODE_ENV\nconst nodeEnv = process.env.NODE_ENV || 'development';\nconst envFileMap: Record<string, string> = {\n test: '.env.test',\n development: '.env.development',\n production: '.env.production',\n};\n\n// Load the appropriate env file\nconst envFile = envFileMap[nodeEnv] || '.env';\nconst envPath = resolve(process.cwd(), envFile);\n\n// Load environment variables from the correct file\n// First load .env (base), then override with environment-specific file\ndotenv.config(); // Load .env first (base config)\ndotenv.config({ path: envPath, override: true }); // Override with env-specific\n\nconst isTest = nodeEnv === 'test';\nconst isProduction = nodeEnv === 'production';\n\n// Base schema with required vars for all environments\nconst baseSchema = z.object({\n NODE_ENV: z.enum(['development', 'production', 'test']).default('development'),\n PORT: z.string().transform(Number).default('3001'),\n DATABASE_URL: z.string().url('DATABASE_URL must be a valid URL'),\n});\n\n// Production/development schema with all required vars\nconst fullSchema = baseSchema.extend({\n NEXT_PUBLIC_APP_URL: z.string().url().default('http://localhost:3000'),\n BACKEND_URL: z.string().url().default('http://localhost:3001'),\n SENTRY_DSN: z.string().url().optional(),\n EMAIL_HOST: z.string().min(1, 'EMAIL_HOST is required'),\n EMAIL_PORT: z.string().transform(Number).default('587'),\n EMAIL_USER: z.string().email('EMAIL_USER must be a valid email'),\n EMAIL_PASS: z.string().min(1, 'EMAIL_PASS is required'),\n EMAIL_DRY_RUN: z.string().optional().default('false'),\n GOOGLE_CLOUD_PROJECT_ID: z.string().min(1, 'GOOGLE_CLOUD_PROJECT_ID is required'),\n GOOGLE_CLOUD_CLIENT_EMAIL: z.string().email('GOOGLE_CLOUD_CLIENT_EMAIL must be a valid email'),\n GOOGLE_CLOUD_PRIVATE_KEY: z.string().min(1, 'GOOGLE_CLOUD_PRIVATE_KEY is required'),\n GOOGLE_CLOUD_BUCKET_NAME: z.string().min(1, 'GOOGLE_CLOUD_BUCKET_NAME is required'),\n PUSHER_APP_ID: z.string().min(1, 'PUSHER_APP_ID is required'),\n PUSHER_KEY: z.string().min(1, 'PUSHER_KEY is required'),\n PUSHER_SECRET: z.string().min(1, 'PUSHER_SECRET is required'),\n PUSHER_CLUSTER: z.string().min(1, 'PUSHER_CLUSTER is required'),\n PUSHER_AUTH_COOKIE_NAME: z.string().optional(), // Cookie name for session token (default: 'token')\n REDIS_URL: z.string().url().optional(), // Redis connection URL (e.g. redis://localhost:6379)\n INFERENCE_API_KEY: z.string().optional(),\n INFERENCE_API_BASE_URL: z.string().url().optional(),\n OPENAI_API_KEY: z.string().optional(),\n LOG_MODE: z.enum(['normal', 'verbose', 'quiet']).default('normal'),\n});\n\n// Test schema - only require what's needed for tests\nconst testSchema = baseSchema.extend({\n NEXT_PUBLIC_APP_URL: z.string().url().optional().default('http://localhost:3000'),\n BACKEND_URL: z.string().url().optional().default('http://localhost:3001'),\n SENTRY_DSN: z.string().url().optional(),\n EMAIL_HOST: z.string().optional().default('smtp.test.com'),\n EMAIL_PORT: z.string().transform(Number).default('587'),\n EMAIL_USER: z.string().email().optional().default('test@test.com'),\n EMAIL_PASS: z.string().optional().default('test'),\n EMAIL_DRY_RUN: z.string().optional().default('false'),\n GOOGLE_CLOUD_PROJECT_ID: z.string().optional().default('test-project'),\n GOOGLE_CLOUD_CLIENT_EMAIL: z.string().email().optional().default('test@test.iam.gserviceaccount.com'),\n GOOGLE_CLOUD_PRIVATE_KEY: z.string().optional().default('test-key'),\n GOOGLE_CLOUD_BUCKET_NAME: z.string().optional().default('test-bucket'),\n PUSHER_APP_ID: z.string().optional().default('test-app-id'),\n PUSHER_KEY: z.string().optional().default('test-key'),\n PUSHER_SECRET: z.string().optional().default('test-secret'),\n PUSHER_CLUSTER: z.string().optional().default('us2'),\n PUSHER_AUTH_COOKIE_NAME: z.string().optional(),\n REDIS_URL: z.string().url().optional(),\n INFERENCE_API_KEY: z.string().optional(),\n OPENAI_API_KEY: z.string().optional(),\n INFERENCE_API_BASE_URL: z.string().url().optional(),\n LOG_MODE: z.enum(['normal', 'verbose', 'quiet']).default('quiet'),\n});\n\n// Use test schema in test mode, full schema otherwise\nconst envSchema = isTest ? testSchema : fullSchema;\n\n// Validate environment variables\nfunction validateEnv() {\n try {\n const parsed = envSchema.parse(process.env);\n \n // Only exit on validation failure in production\n if (isProduction && !parsed.DATABASE_URL) {\n logger.error('DATABASE_URL is required in production');\n process.exit(1);\n }\n \n return parsed;\n } catch (error) {\n if (error instanceof z.ZodError) {\n const missingVars = error.errors.map(err => ({\n path: err.path.join('.'),\n message: err.message,\n }));\n \n logger.error('Environment variable validation failed', {\n envFile,\n missingVars,\n });\n \n // Only exit in production - in test/dev, log warning but continue\n if (isProduction) {\n logger.error(`Please check your ${envFile} file and ensure all required variables are set.`);\n process.exit(1);\n } else {\n logger.warn('Continuing with defaults - some features may not work correctly', {\n envFile,\n });\n // Return parsed with defaults for non-production\n return envSchema.parse({ ...process.env });\n }\n }\n throw error;\n }\n}\n\n// Export validated environment variables\nexport const env = validateEnv();\n\n// Type-safe environment access\nexport type Env = z.infer<typeof envSchema>;"],"names":[],"mappings":"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","debug_id":"6fe796c8-7781-5540-892a-a320464bfbae"}
1
+ {"version":3,"file":"env.js","sources":["lib/config/env.ts"],"sourceRoot":"/","sourcesContent":["import { z } from 'zod';\nimport dotenv from 'dotenv';\nimport { resolve } from 'path';\nimport { logger } from '../../utils/logger.js';\nimport { isValidCorsOriginPatternList } from './cors.js';\n\n// Determine which env file to load based on NODE_ENV\nconst nodeEnv = process.env.NODE_ENV || 'development';\nconst envFileMap: Record<string, string> = {\n test: '.env.test',\n development: '.env.development',\n production: '.env.production',\n};\n\n// Load the appropriate env file\nconst envFile = envFileMap[nodeEnv] || '.env';\nconst envPath = resolve(process.cwd(), envFile);\n\n// Load environment variables from the correct file\n// First load .env (base), then override with environment-specific file\ndotenv.config(); // Load .env first (base config)\ndotenv.config({ path: envPath, override: true }); // Override with env-specific\n\nconst isTest = nodeEnv === 'test';\nconst isProduction = nodeEnv === 'production';\n\nconst corsAllowedOriginsSchema = z.string().optional();\nconst corsAllowedOriginPatternsSchema = z.string().optional().refine(\n isValidCorsOriginPatternList,\n { message: 'CORS_ALLOWED_ORIGIN_PATTERNS must contain valid regex patterns' }\n);\n\n// Base schema with required vars for all environments\nconst baseSchema = z.object({\n NODE_ENV: z.enum(['development', 'production', 'test']).default('development'),\n PORT: z.string().transform(Number).default('3001'),\n DATABASE_URL: z.string().url('DATABASE_URL must be a valid URL'),\n});\n\n// Production/development schema with all required vars\nconst fullSchema = baseSchema.extend({\n NEXT_PUBLIC_APP_URL: z.string().url().default('http://localhost:3000'),\n CORS_ALLOWED_ORIGINS: corsAllowedOriginsSchema,\n CORS_ALLOWED_ORIGIN_PATTERNS: corsAllowedOriginPatternsSchema,\n BACKEND_URL: z.string().url().default('http://localhost:3001'),\n SENTRY_DSN: z.string().url().optional(),\n EMAIL_HOST: z.string().min(1, 'EMAIL_HOST is required'),\n EMAIL_PORT: z.string().transform(Number).default('587'),\n EMAIL_USER: z.string().email('EMAIL_USER must be a valid email'),\n EMAIL_PASS: z.string().min(1, 'EMAIL_PASS is required'),\n EMAIL_DRY_RUN: z.string().optional().default('false'),\n GOOGLE_CLOUD_PROJECT_ID: z.string().min(1, 'GOOGLE_CLOUD_PROJECT_ID is required'),\n GOOGLE_CLOUD_CLIENT_EMAIL: z.string().email('GOOGLE_CLOUD_CLIENT_EMAIL must be a valid email'),\n GOOGLE_CLOUD_PRIVATE_KEY: z.string().min(1, 'GOOGLE_CLOUD_PRIVATE_KEY is required'),\n GOOGLE_CLOUD_BUCKET_NAME: z.string().min(1, 'GOOGLE_CLOUD_BUCKET_NAME is required'),\n PUSHER_APP_ID: z.string().min(1, 'PUSHER_APP_ID is required'),\n PUSHER_KEY: z.string().min(1, 'PUSHER_KEY is required'),\n PUSHER_SECRET: z.string().min(1, 'PUSHER_SECRET is required'),\n PUSHER_CLUSTER: z.string().min(1, 'PUSHER_CLUSTER is required'),\n PUSHER_AUTH_COOKIE_NAME: z.string().optional(), // Cookie name for session token (default: 'token')\n REDIS_URL: z.string().url().optional(), // Redis connection URL (e.g. redis://localhost:6379)\n INFERENCE_API_KEY: z.string().optional(),\n INFERENCE_API_BASE_URL: z.string().url().optional(),\n OPENAI_API_KEY: z.string().optional(),\n LOG_MODE: z.enum(['normal', 'verbose', 'quiet']).default('normal'),\n});\n\n// Test schema - only require what's needed for tests\nconst testSchema = baseSchema.extend({\n NEXT_PUBLIC_APP_URL: z.string().url().optional().default('http://localhost:3000'),\n CORS_ALLOWED_ORIGINS: corsAllowedOriginsSchema,\n CORS_ALLOWED_ORIGIN_PATTERNS: corsAllowedOriginPatternsSchema,\n BACKEND_URL: z.string().url().optional().default('http://localhost:3001'),\n SENTRY_DSN: z.string().url().optional(),\n EMAIL_HOST: z.string().optional().default('smtp.test.com'),\n EMAIL_PORT: z.string().transform(Number).default('587'),\n EMAIL_USER: z.string().email().optional().default('test@test.com'),\n EMAIL_PASS: z.string().optional().default('test'),\n EMAIL_DRY_RUN: z.string().optional().default('false'),\n GOOGLE_CLOUD_PROJECT_ID: z.string().optional().default('test-project'),\n GOOGLE_CLOUD_CLIENT_EMAIL: z.string().email().optional().default('test@test.iam.gserviceaccount.com'),\n GOOGLE_CLOUD_PRIVATE_KEY: z.string().optional().default('test-key'),\n GOOGLE_CLOUD_BUCKET_NAME: z.string().optional().default('test-bucket'),\n PUSHER_APP_ID: z.string().optional().default('test-app-id'),\n PUSHER_KEY: z.string().optional().default('test-key'),\n PUSHER_SECRET: z.string().optional().default('test-secret'),\n PUSHER_CLUSTER: z.string().optional().default('us2'),\n PUSHER_AUTH_COOKIE_NAME: z.string().optional(),\n REDIS_URL: z.string().url().optional(),\n INFERENCE_API_KEY: z.string().optional(),\n OPENAI_API_KEY: z.string().optional(),\n INFERENCE_API_BASE_URL: z.string().url().optional(),\n LOG_MODE: z.enum(['normal', 'verbose', 'quiet']).default('quiet'),\n});\n\n// Use test schema in test mode, full schema otherwise\nconst envSchema = isTest ? testSchema : fullSchema;\n\n// Validate environment variables\nfunction validateEnv() {\n try {\n const parsed = envSchema.parse(process.env);\n \n // Only exit on validation failure in production\n if (isProduction && !parsed.DATABASE_URL) {\n logger.error('DATABASE_URL is required in production');\n process.exit(1);\n }\n \n return parsed;\n } catch (error) {\n if (error instanceof z.ZodError) {\n const missingVars = error.errors.map(err => ({\n path: err.path.join('.'),\n message: err.message,\n }));\n \n logger.error('Environment variable validation failed', {\n envFile,\n missingVars,\n });\n \n // Only exit in production - in test/dev, log warning but continue\n if (isProduction) {\n logger.error(`Please check your ${envFile} file and ensure all required variables are set.`);\n process.exit(1);\n } else {\n logger.warn('Continuing with defaults - some features may not work correctly', {\n envFile,\n });\n // Return parsed with defaults for non-production\n return envSchema.parse({ ...process.env });\n }\n }\n throw error;\n }\n}\n\n// Export validated environment variables\nexport const env = validateEnv();\n\n// Type-safe environment access\nexport type Env = z.infer<typeof envSchema>;\n"],"names":[],"mappings":"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","debug_id":"2c09f0e4-0311-5ba3-bfea-895c59d8b314"}
@@ -26,6 +26,14 @@ export interface DirectUploadFile {
26
26
  uploadExpiresAt: Date;
27
27
  uploadSessionId: string;
28
28
  }
29
+ export declare function validateDirectUploadIntent(input: {
30
+ filePath: string;
31
+ uploadSessionId: string;
32
+ userId: string;
33
+ }): Promise<{
34
+ id: string;
35
+ uploadStatus: import(".prisma/client").$Enums.UploadStatus;
36
+ }>;
29
37
  /**
30
38
  * @deprecated Use createDirectUploadFile instead
31
39
  */
@@ -49,7 +57,7 @@ export declare function getFileUrl(filePath: string): Promise<string>;
49
57
  * @param submissionId Optional submission ID to associate the file with
50
58
  * @returns The direct upload file information with signed URL
51
59
  */
52
- export declare function createDirectUploadFile(file: DirectFileData, userId: string, directory?: string, assignmentId?: string, submissionId?: string, announcementId?: string): Promise<DirectUploadFile>;
60
+ export declare function createDirectUploadFile(file: DirectFileData, userId: string, directory?: string, assignmentId?: string, submissionId?: string, announcementId?: string, conversationId?: string): Promise<DirectUploadFile>;
53
61
  /**
54
62
  * Confirms a direct upload was successful
55
63
  * @param fileId The ID of the file record
@@ -72,5 +80,5 @@ export declare function updateUploadProgress(fileId: string, progress: number):
72
80
  * @param submissionId Optional submission ID to associate files with
73
81
  * @returns Array of direct upload file information
74
82
  */
75
- export declare function createDirectUploadFiles(files: DirectFileData[], userId: string, directory?: string, assignmentId?: string, submissionId?: string, announcementId?: string): Promise<DirectUploadFile[]>;
83
+ export declare function createDirectUploadFiles(files: DirectFileData[], userId: string, directory?: string, assignmentId?: string, submissionId?: string, announcementId?: string, conversationId?: string): Promise<DirectUploadFile[]>;
76
84
  //# sourceMappingURL=fileUpload.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"fileUpload.d.ts","sourceRoot":"/","sources":["lib/fileUpload.ts"],"names":[],"mappings":"AAOA,MAAM,WAAW,QAAQ;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;CAEd;AAED,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;CAEd;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,MAAM,WAAW,gBAAgB;IAC/B,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,SAAS,EAAE,MAAM,CAAC;IAClB,eAAe,EAAE,IAAI,CAAC;IACtB,eAAe,EAAE,MAAM,CAAC;CACzB;AAKD;;GAEG;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,CAKvB;AAED;;GAEG;AACH,wBAAsB,WAAW,CAC/B,KAAK,EAAE,QAAQ,EAAE,EACjB,MAAM,EAAE,MAAM,EACd,SAAS,CAAC,EAAE,MAAM,GACjB,OAAO,CAAC,YAAY,EAAE,CAAC,CAKzB;AAED;;;;GAIG;AACH,wBAAsB,UAAU,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAUlE;AAED;;;;;;;;GAQG;AACH,wBAAsB,sBAAsB,CAC1C,IAAI,EAAE,cAAc,EACpB,MAAM,EAAE,MAAM,EACd,SAAS,CAAC,EAAE,MAAM,EAClB,YAAY,CAAC,EAAE,MAAM,EACrB,YAAY,CAAC,EAAE,MAAM,EACrB,cAAc,CAAC,EAAE,MAAM,GACtB,OAAO,CAAC,gBAAgB,CAAC,CA8F3B;AAED;;;;;GAKG;AACH,wBAAsB,mBAAmB,CACvC,MAAM,EAAE,MAAM,EACd,aAAa,EAAE,OAAO,EACtB,YAAY,CAAC,EAAE,MAAM,GACpB,OAAO,CAAC,IAAI,CAAC,CA+Df;AAED;;;;GAIG;AACH,wBAAsB,oBAAoB,CACxC,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,IAAI,CAAC,CA8Bf;AAED;;;;;;;;GAQG;AACH,wBAAsB,uBAAuB,CAC3C,KAAK,EAAE,cAAc,EAAE,EACvB,MAAM,EAAE,MAAM,EACd,SAAS,CAAC,EAAE,MAAM,EAClB,YAAY,CAAC,EAAE,MAAM,EACrB,YAAY,CAAC,EAAE,MAAM,EACrB,cAAc,CAAC,EAAE,MAAM,GACtB,OAAO,CAAC,gBAAgB,EAAE,CAAC,CAiB7B"}
1
+ {"version":3,"file":"fileUpload.d.ts","sourceRoot":"/","sources":["lib/fileUpload.ts"],"names":[],"mappings":"AAOA,MAAM,WAAW,QAAQ;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;CAEd;AAED,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;CAEd;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,MAAM,WAAW,gBAAgB;IAC/B,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,SAAS,EAAE,MAAM,CAAC;IAClB,eAAe,EAAE,IAAI,CAAC;IACtB,eAAe,EAAE,MAAM,CAAC;CACzB;AAED,wBAAsB,0BAA0B,CAAC,KAAK,EAAE;IACtD,QAAQ,EAAE,MAAM,CAAC;IACjB,eAAe,EAAE,MAAM,CAAC;IACxB,MAAM,EAAE,MAAM,CAAC;CAChB;;;GA+BA;AAKD;;GAEG;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,CAKvB;AAED;;GAEG;AACH,wBAAsB,WAAW,CAC/B,KAAK,EAAE,QAAQ,EAAE,EACjB,MAAM,EAAE,MAAM,EACd,SAAS,CAAC,EAAE,MAAM,GACjB,OAAO,CAAC,YAAY,EAAE,CAAC,CAKzB;AAED;;;;GAIG;AACH,wBAAsB,UAAU,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAUlE;AAED;;;;;;;;GAQG;AACH,wBAAsB,sBAAsB,CAC1C,IAAI,EAAE,cAAc,EACpB,MAAM,EAAE,MAAM,EACd,SAAS,CAAC,EAAE,MAAM,EAClB,YAAY,CAAC,EAAE,MAAM,EACrB,YAAY,CAAC,EAAE,MAAM,EACrB,cAAc,CAAC,EAAE,MAAM,EACvB,cAAc,CAAC,EAAE,MAAM,GACtB,OAAO,CAAC,gBAAgB,CAAC,CAiG3B;AAED;;;;;GAKG;AACH,wBAAsB,mBAAmB,CACvC,MAAM,EAAE,MAAM,EACd,aAAa,EAAE,OAAO,EACtB,YAAY,CAAC,EAAE,MAAM,GACpB,OAAO,CAAC,IAAI,CAAC,CA+Df;AAED;;;;GAIG;AACH,wBAAsB,oBAAoB,CACxC,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,IAAI,CAAC,CA8Bf;AAED;;;;;;;;GAQG;AACH,wBAAsB,uBAAuB,CAC3C,KAAK,EAAE,cAAc,EAAE,EACvB,MAAM,EAAE,MAAM,EACd,SAAS,CAAC,EAAE,MAAM,EAClB,YAAY,CAAC,EAAE,MAAM,EACrB,YAAY,CAAC,EAAE,MAAM,EACrB,cAAc,CAAC,EAAE,MAAM,EACvB,cAAc,CAAC,EAAE,MAAM,GACtB,OAAO,CAAC,gBAAgB,EAAE,CAAC,CAyB7B"}
@@ -1,11 +1,40 @@
1
1
 
2
- !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="4ff4c0f2-8784-50fe-a248-6adcac3fa659")}catch(e){}}();
2
+ !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="01a3ee28-2d14-5542-9301-e6f24e5c7a0d")}catch(e){}}();
3
3
  import { TRPCError } from "@trpc/server";
4
4
  import { v4 as uuidv4 } from "uuid";
5
5
  import { getSignedUrl, objectExists } from "./googleCloudStorage.js";
6
6
  import { prisma } from "./prisma.js";
7
7
  import { logger } from "../utils/logger.js";
8
8
  import { env } from "./config/env.js";
9
+ export async function validateDirectUploadIntent(input) {
10
+ const fileRecord = await prisma.file.findFirst({
11
+ where: {
12
+ path: input.filePath,
13
+ uploadSessionId: input.uploadSessionId,
14
+ userId: input.userId,
15
+ uploadExpiresAt: { gt: new Date() },
16
+ isOrphaned: false,
17
+ messageId: null,
18
+ },
19
+ select: {
20
+ id: true,
21
+ uploadStatus: true,
22
+ },
23
+ });
24
+ if (!fileRecord) {
25
+ throw new TRPCError({
26
+ code: 'FORBIDDEN',
27
+ message: 'Invalid or expired upload intent',
28
+ });
29
+ }
30
+ if (!['PENDING', 'UPLOADING'].includes(fileRecord.uploadStatus)) {
31
+ throw new TRPCError({
32
+ code: 'BAD_REQUEST',
33
+ message: 'Upload is no longer accepting data',
34
+ });
35
+ }
36
+ return fileRecord;
37
+ }
9
38
  // DEPRECATED: These functions are no longer used - files are uploaded directly to GCS
10
39
  // Use createDirectUploadFile() and createDirectUploadFiles() instead
11
40
  /**
@@ -52,7 +81,7 @@ export async function getFileUrl(filePath) {
52
81
  * @param submissionId Optional submission ID to associate the file with
53
82
  * @returns The direct upload file information with signed URL
54
83
  */
55
- export async function createDirectUploadFile(file, userId, directory, assignmentId, submissionId, announcementId) {
84
+ export async function createDirectUploadFile(file, userId, directory, assignmentId, submissionId, announcementId, conversationId) {
56
85
  try {
57
86
  // Validate file extension matches MIME type
58
87
  const fileExtension = file.name.split('.').pop()?.toLowerCase();
@@ -79,7 +108,7 @@ export async function createDirectUploadFile(file, userId, directory, assignment
79
108
  const uploadSessionId = uuidv4();
80
109
  // Generate backend proxy upload URL (not direct GCS)
81
110
  const baseUrl = env.BACKEND_URL || 'http://localhost:3001';
82
- const uploadUrl = `${baseUrl}/api/upload/${encodeURIComponent(filePath)}`;
111
+ const uploadUrl = `${baseUrl}/api/upload/${encodeURIComponent(filePath)}?uploadSessionId=${encodeURIComponent(uploadSessionId)}`;
83
112
  const uploadExpiresAt = new Date(Date.now() + 15 * 60 * 1000); // 15 minutes from now
84
113
  // Create file record in database with PENDING status
85
114
  const fileRecord = await prisma.file.create({
@@ -114,6 +143,9 @@ export async function createDirectUploadFile(file, userId, directory, assignment
114
143
  announcement: {
115
144
  connect: { id: announcementId }
116
145
  }
146
+ }),
147
+ ...(conversationId && {
148
+ conversationId,
117
149
  })
118
150
  },
119
151
  });
@@ -252,9 +284,9 @@ export async function updateUploadProgress(fileId, progress) {
252
284
  * @param submissionId Optional submission ID to associate files with
253
285
  * @returns Array of direct upload file information
254
286
  */
255
- export async function createDirectUploadFiles(files, userId, directory, assignmentId, submissionId, announcementId) {
287
+ export async function createDirectUploadFiles(files, userId, directory, assignmentId, submissionId, announcementId, conversationId) {
256
288
  try {
257
- const uploadPromises = files.map(file => createDirectUploadFile(file, userId, directory, assignmentId, submissionId, announcementId));
289
+ const uploadPromises = files.map(file => createDirectUploadFile(file, userId, directory, assignmentId, submissionId, announcementId, conversationId));
258
290
  return await Promise.all(uploadPromises);
259
291
  }
260
292
  catch (error) {
@@ -270,4 +302,4 @@ export async function createDirectUploadFiles(files, userId, directory, assignme
270
302
  }
271
303
  }
272
304
  //# sourceMappingURL=fileUpload.js.map
273
- //# debugId=4ff4c0f2-8784-50fe-a248-6adcac3fa659
305
+ //# debugId=01a3ee28-2d14-5542-9301-e6f24e5c7a0d
@@ -1 +1 @@
1
- {"version":3,"file":"fileUpload.js","sources":["lib/fileUpload.ts"],"sourceRoot":"/","sourcesContent":["import { TRPCError } from \"@trpc/server\";\nimport { v4 as uuidv4 } from \"uuid\";\nimport { getSignedUrl, objectExists } from \"./googleCloudStorage.js\";\nimport { prisma } from \"./prisma.js\";\nimport { logger } from \"../utils/logger.js\";\nimport { env } from \"./config/env.js\";\n\nexport interface FileData {\n name: string;\n type: string;\n size: number;\n // No data field - for direct file uploads\n}\n\nexport interface DirectFileData {\n name: string;\n type: string;\n size: number;\n // No data field - for direct file uploads\n}\n\nexport interface UploadedFile {\n id: string;\n name: string;\n type: string;\n size: number;\n path: string;\n thumbnailId?: string;\n}\n\nexport interface DirectUploadFile {\n id: string;\n name: string;\n type: string;\n size: number;\n path: string;\n uploadUrl: string;\n uploadExpiresAt: Date;\n uploadSessionId: string;\n}\n\n// DEPRECATED: These functions are no longer used - files are uploaded directly to GCS\n// Use createDirectUploadFile() and createDirectUploadFiles() instead\n\n/**\n * @deprecated Use createDirectUploadFile instead\n */\nexport async function uploadFile(\n file: FileData,\n userId: string,\n directory?: string,\n assignmentId?: string\n): Promise<UploadedFile> {\n throw new TRPCError({\n code: 'NOT_IMPLEMENTED',\n message: 'uploadFile is deprecated. Use createDirectUploadFile instead.',\n });\n}\n\n/**\n * @deprecated Use createDirectUploadFiles instead\n */\nexport async function uploadFiles(\n files: FileData[], \n userId: string,\n directory?: string\n): Promise<UploadedFile[]> {\n throw new TRPCError({\n code: 'NOT_IMPLEMENTED',\n message: 'uploadFiles is deprecated. Use createDirectUploadFiles instead.',\n });\n}\n\n/**\n * Gets a signed URL for a file\n * @param filePath The path of the file in Google Cloud Storage\n * @returns The signed URL\n */\nexport async function getFileUrl(filePath: string): Promise<string> {\n try {\n return await getSignedUrl(filePath);\n } catch (error) {\n console.error('Error getting signed URL:', error);\n throw new TRPCError({\n code: 'INTERNAL_SERVER_ERROR',\n message: 'Failed to get file URL',\n });\n }\n}\n\n/**\n * Creates a file record for direct upload and generates signed URL\n * @param file The file metadata (no base64 data)\n * @param userId The ID of the user uploading the file\n * @param directory Optional directory to store the file in\n * @param assignmentId Optional assignment ID to associate the file with\n * @param submissionId Optional submission ID to associate the file with\n * @returns The direct upload file information with signed URL\n */\nexport async function createDirectUploadFile(\n file: DirectFileData,\n userId: string,\n directory?: string,\n assignmentId?: string,\n submissionId?: string,\n announcementId?: string\n): Promise<DirectUploadFile> {\n try {\n // Validate file extension matches MIME type\n const fileExtension = file.name.split('.').pop()?.toLowerCase();\n const mimeType = file.type.toLowerCase();\n \n const extensionMimeMap: Record<string, string[]> = {\n 'jpg': ['image/jpeg'],\n 'jpeg': ['image/jpeg'],\n 'png': ['image/png'],\n 'gif': ['image/gif'],\n 'webp': ['image/webp']\n };\n \n if (fileExtension && extensionMimeMap[fileExtension]) {\n if (!extensionMimeMap[fileExtension].includes(mimeType)) {\n throw new Error(`File extension .${fileExtension} does not match MIME type ${mimeType}`);\n }\n }\n \n // Create a unique filename\n const uniqueFilename = `${uuidv4()}.${fileExtension}`;\n \n // Construct the full path\n const filePath = directory \n ? `${directory}/${uniqueFilename}`\n : uniqueFilename;\n \n // Generate upload session ID\n const uploadSessionId = uuidv4();\n \n // Generate backend proxy upload URL (not direct GCS)\n const baseUrl = env.BACKEND_URL || 'http://localhost:3001';\n const uploadUrl = `${baseUrl}/api/upload/${encodeURIComponent(filePath)}`;\n const uploadExpiresAt = new Date(Date.now() + 15 * 60 * 1000); // 15 minutes from now\n \n // Create file record in database with PENDING status\n const fileRecord = await prisma.file.create({\n data: {\n name: file.name,\n type: file.type,\n size: file.size,\n path: filePath,\n uploadStatus: 'PENDING',\n uploadUrl,\n uploadExpiresAt,\n uploadSessionId,\n user: {\n connect: { id: userId }\n },\n ...(directory && {\n folder: {\n connect: {id: directory},\n },\n }),\n ...(assignmentId && {\n assignment: {\n connect: { id: assignmentId }\n }\n }),\n ...(submissionId && {\n submission: {\n connect: { id: submissionId }\n }\n }),\n ...(announcementId && {\n announcement: {\n connect: { id: announcementId }\n }\n })\n },\n });\n \n return {\n id: fileRecord.id,\n name: file.name,\n type: file.type,\n size: file.size,\n path: filePath,\n uploadUrl,\n uploadExpiresAt,\n uploadSessionId\n };\n } catch (error) {\n logger.error('Error creating direct upload file:', {error: error instanceof Error ? {\n name: error.name,\n message: error.message,\n stack: error.stack,\n } : error});\n throw new TRPCError({\n code: 'INTERNAL_SERVER_ERROR',\n message: 'Failed to create direct upload file',\n });\n }\n}\n\n/**\n * Confirms a direct upload was successful\n * @param fileId The ID of the file record\n * @param uploadSuccess Whether the upload was successful\n * @param errorMessage Optional error message if upload failed\n */\nexport async function confirmDirectUpload(\n fileId: string,\n uploadSuccess: boolean,\n errorMessage?: string\n): Promise<void> {\n try {\n // First fetch the file record to get the object path\n const fileRecord = await prisma.file.findUnique({\n where: { id: fileId },\n select: { path: true }\n });\n\n if (!fileRecord) {\n throw new TRPCError({\n code: 'NOT_FOUND',\n message: 'File record not found',\n });\n }\n\n let actualUploadSuccess = uploadSuccess;\n let actualErrorMessage = errorMessage;\n\n // If uploadSuccess is true, verify the object actually exists in GCS\n if (uploadSuccess) {\n try {\n const exists = await objectExists(env.GOOGLE_CLOUD_BUCKET_NAME!, fileRecord.path);\n if (!exists) {\n actualUploadSuccess = false;\n actualErrorMessage = 'File upload reported as successful but object not found in Google Cloud Storage';\n logger.error(`File upload verification failed for ${fileId}: object ${fileRecord.path} not found in GCS`);\n }\n } catch (error) {\n logger.error(`Error verifying file existence in GCS for ${fileId}:`, {error: error instanceof Error ? {\n name: error.name,\n message: error.message,\n stack: error.stack,\n } : error});\n actualUploadSuccess = false;\n actualErrorMessage = 'Failed to verify file existence in Google Cloud Storage';\n }\n }\n\n const updateData: any = {\n uploadStatus: actualUploadSuccess ? 'COMPLETED' : 'FAILED',\n uploadProgress: actualUploadSuccess ? 100 : 0,\n };\n \n if (!actualUploadSuccess && actualErrorMessage) {\n updateData.uploadError = actualErrorMessage;\n updateData.uploadRetryCount = { increment: 1 };\n }\n \n if (actualUploadSuccess) {\n updateData.uploadedAt = new Date();\n }\n \n await prisma.file.update({\n where: { id: fileId },\n data: updateData\n });\n } catch (error) {\n logger.error('Error confirming direct upload:', {error});\n throw new TRPCError({\n code: 'INTERNAL_SERVER_ERROR',\n message: 'Failed to confirm upload',\n });\n }\n}\n\n/**\n * Updates upload progress for a direct upload\n * @param fileId The ID of the file record\n * @param progress Progress percentage (0-100)\n */\nexport async function updateUploadProgress(\n fileId: string,\n progress: number\n): Promise<void> {\n try {\n // await prisma.file.update({\n // where: { id: fileId },\n // data: {\n // uploadStatus: 'UPLOADING',\n // uploadProgress: Math.min(100, Math.max(0, progress))\n // }\n // });\n const current = await prisma.file.findUnique({ where: { id: fileId }, select: { uploadStatus: true } });\n if (!current || ['COMPLETED','FAILED','CANCELLED'].includes(current.uploadStatus as string)) return;\n const clamped = Math.min(100, Math.max(0, progress));\n await prisma.file.update({\n where: { id: fileId },\n data: {\n uploadStatus: 'UPLOADING',\n uploadProgress: clamped\n }\n });\n } catch (error) {\n logger.error('Error updating upload progress:', {error: error instanceof Error ? {\n name: error.name,\n message: error.message,\n stack: error.stack,\n } : error}); \n throw new TRPCError({\n code: 'INTERNAL_SERVER_ERROR',\n message: 'Failed to update upload progress',\n });\n }\n}\n\n/**\n * Creates multiple direct upload files\n * @param files Array of file metadata\n * @param userId The ID of the user uploading the files\n * @param directory Optional subdirectory to store the files in\n * @param assignmentId Optional assignment ID to associate files with\n * @param submissionId Optional submission ID to associate files with\n * @returns Array of direct upload file information\n */\nexport async function createDirectUploadFiles(\n files: DirectFileData[], \n userId: string,\n directory?: string,\n assignmentId?: string,\n submissionId?: string,\n announcementId?: string\n): Promise<DirectUploadFile[]> {\n try {\n const uploadPromises = files.map(file => \n createDirectUploadFile(file, userId, directory, assignmentId, submissionId, announcementId)\n );\n return await Promise.all(uploadPromises);\n } catch (error) {\n logger.error('Error creating direct upload files:', {error: error instanceof Error ? {\n name: error.name,\n message: error.message,\n stack: error.stack,\n } : error});\n throw new TRPCError({\n code: 'INTERNAL_SERVER_ERROR',\n message: 'Failed to create direct upload files',\n });\n }\n}"],"names":[],"mappings":"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","debug_id":"4ff4c0f2-8784-50fe-a248-6adcac3fa659"}
1
+ {"version":3,"file":"fileUpload.js","sources":["lib/fileUpload.ts"],"sourceRoot":"/","sourcesContent":["import { TRPCError } from \"@trpc/server\";\nimport { v4 as uuidv4 } from \"uuid\";\nimport { getSignedUrl, objectExists } from \"./googleCloudStorage.js\";\nimport { prisma } from \"./prisma.js\";\nimport { logger } from \"../utils/logger.js\";\nimport { env } from \"./config/env.js\";\n\nexport interface FileData {\n name: string;\n type: string;\n size: number;\n // No data field - for direct file uploads\n}\n\nexport interface DirectFileData {\n name: string;\n type: string;\n size: number;\n // No data field - for direct file uploads\n}\n\nexport interface UploadedFile {\n id: string;\n name: string;\n type: string;\n size: number;\n path: string;\n thumbnailId?: string;\n}\n\nexport interface DirectUploadFile {\n id: string;\n name: string;\n type: string;\n size: number;\n path: string;\n uploadUrl: string;\n uploadExpiresAt: Date;\n uploadSessionId: string;\n}\n\nexport async function validateDirectUploadIntent(input: {\n filePath: string;\n uploadSessionId: string;\n userId: string;\n}) {\n const fileRecord = await prisma.file.findFirst({\n where: {\n path: input.filePath,\n uploadSessionId: input.uploadSessionId,\n userId: input.userId,\n uploadExpiresAt: { gt: new Date() },\n isOrphaned: false,\n messageId: null,\n },\n select: {\n id: true,\n uploadStatus: true,\n },\n });\n\n if (!fileRecord) {\n throw new TRPCError({\n code: 'FORBIDDEN',\n message: 'Invalid or expired upload intent',\n });\n }\n\n if (!['PENDING', 'UPLOADING'].includes(fileRecord.uploadStatus)) {\n throw new TRPCError({\n code: 'BAD_REQUEST',\n message: 'Upload is no longer accepting data',\n });\n }\n\n return fileRecord;\n}\n\n// DEPRECATED: These functions are no longer used - files are uploaded directly to GCS\n// Use createDirectUploadFile() and createDirectUploadFiles() instead\n\n/**\n * @deprecated Use createDirectUploadFile instead\n */\nexport async function uploadFile(\n file: FileData,\n userId: string,\n directory?: string,\n assignmentId?: string\n): Promise<UploadedFile> {\n throw new TRPCError({\n code: 'NOT_IMPLEMENTED',\n message: 'uploadFile is deprecated. Use createDirectUploadFile instead.',\n });\n}\n\n/**\n * @deprecated Use createDirectUploadFiles instead\n */\nexport async function uploadFiles(\n files: FileData[], \n userId: string,\n directory?: string\n): Promise<UploadedFile[]> {\n throw new TRPCError({\n code: 'NOT_IMPLEMENTED',\n message: 'uploadFiles is deprecated. Use createDirectUploadFiles instead.',\n });\n}\n\n/**\n * Gets a signed URL for a file\n * @param filePath The path of the file in Google Cloud Storage\n * @returns The signed URL\n */\nexport async function getFileUrl(filePath: string): Promise<string> {\n try {\n return await getSignedUrl(filePath);\n } catch (error) {\n console.error('Error getting signed URL:', error);\n throw new TRPCError({\n code: 'INTERNAL_SERVER_ERROR',\n message: 'Failed to get file URL',\n });\n }\n}\n\n/**\n * Creates a file record for direct upload and generates signed URL\n * @param file The file metadata (no base64 data)\n * @param userId The ID of the user uploading the file\n * @param directory Optional directory to store the file in\n * @param assignmentId Optional assignment ID to associate the file with\n * @param submissionId Optional submission ID to associate the file with\n * @returns The direct upload file information with signed URL\n */\nexport async function createDirectUploadFile(\n file: DirectFileData,\n userId: string,\n directory?: string,\n assignmentId?: string,\n submissionId?: string,\n announcementId?: string,\n conversationId?: string\n): Promise<DirectUploadFile> {\n try {\n // Validate file extension matches MIME type\n const fileExtension = file.name.split('.').pop()?.toLowerCase();\n const mimeType = file.type.toLowerCase();\n \n const extensionMimeMap: Record<string, string[]> = {\n 'jpg': ['image/jpeg'],\n 'jpeg': ['image/jpeg'],\n 'png': ['image/png'],\n 'gif': ['image/gif'],\n 'webp': ['image/webp']\n };\n \n if (fileExtension && extensionMimeMap[fileExtension]) {\n if (!extensionMimeMap[fileExtension].includes(mimeType)) {\n throw new Error(`File extension .${fileExtension} does not match MIME type ${mimeType}`);\n }\n }\n \n // Create a unique filename\n const uniqueFilename = `${uuidv4()}.${fileExtension}`;\n \n // Construct the full path\n const filePath = directory \n ? `${directory}/${uniqueFilename}`\n : uniqueFilename;\n \n // Generate upload session ID\n const uploadSessionId = uuidv4();\n \n // Generate backend proxy upload URL (not direct GCS)\n const baseUrl = env.BACKEND_URL || 'http://localhost:3001';\n const uploadUrl = `${baseUrl}/api/upload/${encodeURIComponent(filePath)}?uploadSessionId=${encodeURIComponent(uploadSessionId)}`;\n const uploadExpiresAt = new Date(Date.now() + 15 * 60 * 1000); // 15 minutes from now\n \n // Create file record in database with PENDING status\n const fileRecord = await prisma.file.create({\n data: {\n name: file.name,\n type: file.type,\n size: file.size,\n path: filePath,\n uploadStatus: 'PENDING',\n uploadUrl,\n uploadExpiresAt,\n uploadSessionId,\n user: {\n connect: { id: userId }\n },\n ...(directory && {\n folder: {\n connect: {id: directory},\n },\n }),\n ...(assignmentId && {\n assignment: {\n connect: { id: assignmentId }\n }\n }),\n ...(submissionId && {\n submission: {\n connect: { id: submissionId }\n }\n }),\n ...(announcementId && {\n announcement: {\n connect: { id: announcementId }\n }\n }),\n ...(conversationId && {\n conversationId,\n })\n },\n });\n \n return {\n id: fileRecord.id,\n name: file.name,\n type: file.type,\n size: file.size,\n path: filePath,\n uploadUrl,\n uploadExpiresAt,\n uploadSessionId\n };\n } catch (error) {\n logger.error('Error creating direct upload file:', {error: error instanceof Error ? {\n name: error.name,\n message: error.message,\n stack: error.stack,\n } : error});\n throw new TRPCError({\n code: 'INTERNAL_SERVER_ERROR',\n message: 'Failed to create direct upload file',\n });\n }\n}\n\n/**\n * Confirms a direct upload was successful\n * @param fileId The ID of the file record\n * @param uploadSuccess Whether the upload was successful\n * @param errorMessage Optional error message if upload failed\n */\nexport async function confirmDirectUpload(\n fileId: string,\n uploadSuccess: boolean,\n errorMessage?: string\n): Promise<void> {\n try {\n // First fetch the file record to get the object path\n const fileRecord = await prisma.file.findUnique({\n where: { id: fileId },\n select: { path: true }\n });\n\n if (!fileRecord) {\n throw new TRPCError({\n code: 'NOT_FOUND',\n message: 'File record not found',\n });\n }\n\n let actualUploadSuccess = uploadSuccess;\n let actualErrorMessage = errorMessage;\n\n // If uploadSuccess is true, verify the object actually exists in GCS\n if (uploadSuccess) {\n try {\n const exists = await objectExists(env.GOOGLE_CLOUD_BUCKET_NAME!, fileRecord.path);\n if (!exists) {\n actualUploadSuccess = false;\n actualErrorMessage = 'File upload reported as successful but object not found in Google Cloud Storage';\n logger.error(`File upload verification failed for ${fileId}: object ${fileRecord.path} not found in GCS`);\n }\n } catch (error) {\n logger.error(`Error verifying file existence in GCS for ${fileId}:`, {error: error instanceof Error ? {\n name: error.name,\n message: error.message,\n stack: error.stack,\n } : error});\n actualUploadSuccess = false;\n actualErrorMessage = 'Failed to verify file existence in Google Cloud Storage';\n }\n }\n\n const updateData: any = {\n uploadStatus: actualUploadSuccess ? 'COMPLETED' : 'FAILED',\n uploadProgress: actualUploadSuccess ? 100 : 0,\n };\n \n if (!actualUploadSuccess && actualErrorMessage) {\n updateData.uploadError = actualErrorMessage;\n updateData.uploadRetryCount = { increment: 1 };\n }\n \n if (actualUploadSuccess) {\n updateData.uploadedAt = new Date();\n }\n \n await prisma.file.update({\n where: { id: fileId },\n data: updateData\n });\n } catch (error) {\n logger.error('Error confirming direct upload:', {error});\n throw new TRPCError({\n code: 'INTERNAL_SERVER_ERROR',\n message: 'Failed to confirm upload',\n });\n }\n}\n\n/**\n * Updates upload progress for a direct upload\n * @param fileId The ID of the file record\n * @param progress Progress percentage (0-100)\n */\nexport async function updateUploadProgress(\n fileId: string,\n progress: number\n): Promise<void> {\n try {\n // await prisma.file.update({\n // where: { id: fileId },\n // data: {\n // uploadStatus: 'UPLOADING',\n // uploadProgress: Math.min(100, Math.max(0, progress))\n // }\n // });\n const current = await prisma.file.findUnique({ where: { id: fileId }, select: { uploadStatus: true } });\n if (!current || ['COMPLETED','FAILED','CANCELLED'].includes(current.uploadStatus as string)) return;\n const clamped = Math.min(100, Math.max(0, progress));\n await prisma.file.update({\n where: { id: fileId },\n data: {\n uploadStatus: 'UPLOADING',\n uploadProgress: clamped\n }\n });\n } catch (error) {\n logger.error('Error updating upload progress:', {error: error instanceof Error ? {\n name: error.name,\n message: error.message,\n stack: error.stack,\n } : error}); \n throw new TRPCError({\n code: 'INTERNAL_SERVER_ERROR',\n message: 'Failed to update upload progress',\n });\n }\n}\n\n/**\n * Creates multiple direct upload files\n * @param files Array of file metadata\n * @param userId The ID of the user uploading the files\n * @param directory Optional subdirectory to store the files in\n * @param assignmentId Optional assignment ID to associate files with\n * @param submissionId Optional submission ID to associate files with\n * @returns Array of direct upload file information\n */\nexport async function createDirectUploadFiles(\n files: DirectFileData[], \n userId: string,\n directory?: string,\n assignmentId?: string,\n submissionId?: string,\n announcementId?: string,\n conversationId?: string\n): Promise<DirectUploadFile[]> {\n try {\n const uploadPromises = files.map(file => \n createDirectUploadFile(\n file,\n userId,\n directory,\n assignmentId,\n submissionId,\n announcementId,\n conversationId\n )\n );\n return await Promise.all(uploadPromises);\n } catch (error) {\n logger.error('Error creating direct upload files:', {error: error instanceof Error ? {\n name: error.name,\n message: error.message,\n stack: error.stack,\n } : error});\n throw new TRPCError({\n code: 'INTERNAL_SERVER_ERROR',\n message: 'Failed to create direct upload files',\n });\n }\n}\n"],"names":[],"mappings":"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","debug_id":"01a3ee28-2d14-5542-9301-e6f24e5c7a0d"}
@@ -1,8 +1,20 @@
1
1
  import { PrismaClient } from '@prisma/client';
2
- declare const prismaClientSingleton: () => PrismaClient<import(".prisma/client").Prisma.PrismaClientOptions, never, import("@prisma/client/runtime/library.js").DefaultArgs>;
2
+ declare const prismaClientSingleton: () => PrismaClient<{
3
+ datasources: {
4
+ db: {
5
+ url: string;
6
+ };
7
+ };
8
+ }, never, import("@prisma/client/runtime/library.js").DefaultArgs>;
3
9
  declare global {
4
10
  var prisma: undefined | ReturnType<typeof prismaClientSingleton>;
5
11
  }
6
- export declare const prisma: PrismaClient<import(".prisma/client").Prisma.PrismaClientOptions, never, import("@prisma/client/runtime/library.js").DefaultArgs>;
12
+ export declare const prisma: PrismaClient<{
13
+ datasources: {
14
+ db: {
15
+ url: string;
16
+ };
17
+ };
18
+ }, never, import("@prisma/client/runtime/library.js").DefaultArgs>;
7
19
  export {};
8
20
  //# sourceMappingURL=prisma.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"prisma.d.ts","sourceRoot":"/","sources":["lib/prisma.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAc9C,QAAA,MAAM,qBAAqB,yIAO1B,CAAC;AAGF,OAAO,CAAC,MAAM,CAAC;IACb,IAAI,MAAM,EAAE,SAAS,GAAG,UAAU,CAAC,OAAO,qBAAqB,CAAC,CAAC;CAClE;AAED,eAAO,MAAM,MAAM,mIAA+C,CAAC"}
1
+ {"version":3,"file":"prisma.d.ts","sourceRoot":"/","sources":["lib/prisma.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAgC9C,QAAA,MAAM,qBAAqB;;;;;;kEAQ1B,CAAC;AAGF,OAAO,CAAC,MAAM,CAAC;IACb,IAAI,MAAM,EAAE,SAAS,GAAG,UAAU,CAAC,OAAO,qBAAqB,CAAC,CAAC;CAClE;AAED,eAAO,MAAM,MAAM;;;;;;kEAA+C,CAAC"}
@@ -1,5 +1,5 @@
1
1
 
2
- !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="ccd03fae-35bf-58da-b672-e834ceba3cc9")}catch(e){}}();
2
+ !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="fee0859e-cfe1-5496-aa37-3bbf9aa0f3bb")}catch(e){}}();
3
3
  import { PrismaClient } from '@prisma/client';
4
4
  import { env } from './config/env.js';
5
5
  const getLogLevel = () => {
@@ -12,13 +12,32 @@ const getLogLevel = () => {
12
12
  return ['error'];
13
13
  }
14
14
  };
15
+ const getDatabaseUrl = () => {
16
+ try {
17
+ const url = new URL(env.DATABASE_URL);
18
+ // Reduce per-instance DB pressure in hosted/serverless environments.
19
+ if (env.NODE_ENV === 'production') {
20
+ if (!url.searchParams.has('connection_limit')) {
21
+ url.searchParams.set('connection_limit', '1');
22
+ }
23
+ if (!url.searchParams.has('pool_timeout')) {
24
+ url.searchParams.set('pool_timeout', '20');
25
+ }
26
+ }
27
+ return url.toString();
28
+ }
29
+ catch {
30
+ return env.DATABASE_URL;
31
+ }
32
+ };
15
33
  const prismaClientSingleton = () => {
16
- // return new PrismaClient({
17
- // log: env.NODE_ENV === 'development'
18
- // ? ['query', 'error', 'warn']
19
- // : ['error'],
20
- // });
21
- return new PrismaClient();
34
+ return new PrismaClient({
35
+ datasources: {
36
+ db: {
37
+ url: getDatabaseUrl(),
38
+ },
39
+ },
40
+ });
22
41
  };
23
42
  export const prisma = globalThis.prisma ?? prismaClientSingleton();
24
43
  if (env.NODE_ENV !== 'production') {
@@ -28,4 +47,4 @@ process.on('beforeExit', async () => {
28
47
  await prisma.$disconnect();
29
48
  });
30
49
  //# sourceMappingURL=prisma.js.map
31
- //# debugId=ccd03fae-35bf-58da-b672-e834ceba3cc9
50
+ //# debugId=fee0859e-cfe1-5496-aa37-3bbf9aa0f3bb
@@ -1 +1 @@
1
- {"version":3,"file":"prisma.js","sources":["lib/prisma.ts"],"sourceRoot":"/","sourcesContent":["import { PrismaClient } from '@prisma/client';\nimport { env } from './config/env.js';\n\nconst getLogLevel = () => {\n switch (env.NODE_ENV) {\n case 'development':\n return ['query', 'error', 'warn'];\n case 'production':\n return ['error'];\n default:\n return ['error'];\n }\n}\n\nconst prismaClientSingleton = () => {\n // return new PrismaClient({\n // log: env.NODE_ENV === 'development' \n // ? ['query', 'error', 'warn'] \n // : ['error'],\n // });\n return new PrismaClient();\n};\n\n// Prevent multiple instances of Prisma Client in development\ndeclare global {\n var prisma: undefined | ReturnType<typeof prismaClientSingleton>;\n}\n\nexport const prisma = globalThis.prisma ?? prismaClientSingleton();\n\nif (env.NODE_ENV !== 'production') {\n globalThis.prisma = prisma;\n}\n\nprocess.on('beforeExit', async () => {\n await prisma.$disconnect();\n});"],"names":[],"mappings":";;AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,OAAO,EAAE,GAAG,EAAE,MAAM,iBAAiB,CAAC;AAEtC,MAAM,WAAW,GAAG,GAAG,EAAE;IACvB,QAAQ,GAAG,CAAC,QAAQ,EAAE,CAAC;QACrB,KAAK,aAAa;YAChB,OAAO,CAAC,OAAO,EAAE,OAAO,EAAE,MAAM,CAAC,CAAC;QACpC,KAAK,YAAY;YACf,OAAO,CAAC,OAAO,CAAC,CAAC;QACnB;YACE,OAAO,CAAC,OAAO,CAAC,CAAC;IACrB,CAAC;AACH,CAAC,CAAA;AAED,MAAM,qBAAqB,GAAG,GAAG,EAAE;IACjC,4BAA4B;IAC5B,yCAAyC;IACzC,oCAAoC;IACpC,mBAAmB;IACnB,MAAM;IACN,OAAO,IAAI,YAAY,EAAE,CAAC;AAC5B,CAAC,CAAC;AAOF,MAAM,CAAC,MAAM,MAAM,GAAG,UAAU,CAAC,MAAM,IAAI,qBAAqB,EAAE,CAAC;AAEnE,IAAI,GAAG,CAAC,QAAQ,KAAK,YAAY,EAAE,CAAC;IAClC,UAAU,CAAC,MAAM,GAAG,MAAM,CAAC;AAC7B,CAAC;AAED,OAAO,CAAC,EAAE,CAAC,YAAY,EAAE,KAAK,IAAI,EAAE;IAClC,MAAM,MAAM,CAAC,WAAW,EAAE,CAAC;AAC7B,CAAC,CAAC,CAAC","debug_id":"ccd03fae-35bf-58da-b672-e834ceba3cc9"}
1
+ {"version":3,"file":"prisma.js","sources":["lib/prisma.ts"],"sourceRoot":"/","sourcesContent":["import { PrismaClient } from '@prisma/client';\nimport { env } from './config/env.js';\n\nconst getLogLevel = () => {\n switch (env.NODE_ENV) {\n case 'development':\n return ['query', 'error', 'warn'];\n case 'production':\n return ['error'];\n default:\n return ['error'];\n }\n}\n\nconst getDatabaseUrl = () => {\n try {\n const url = new URL(env.DATABASE_URL);\n // Reduce per-instance DB pressure in hosted/serverless environments.\n if (env.NODE_ENV === 'production') {\n if (!url.searchParams.has('connection_limit')) {\n url.searchParams.set('connection_limit', '1');\n }\n if (!url.searchParams.has('pool_timeout')) {\n url.searchParams.set('pool_timeout', '20');\n }\n }\n return url.toString();\n } catch {\n return env.DATABASE_URL;\n }\n};\n\nconst prismaClientSingleton = () => {\n return new PrismaClient({\n datasources: {\n db: {\n url: getDatabaseUrl(),\n },\n },\n });\n};\n\n// Prevent multiple instances of Prisma Client in development\ndeclare global {\n var prisma: undefined | ReturnType<typeof prismaClientSingleton>;\n}\n\nexport const prisma = globalThis.prisma ?? prismaClientSingleton();\n\nif (env.NODE_ENV !== 'production') {\n globalThis.prisma = prisma;\n}\n\nprocess.on('beforeExit', async () => {\n await prisma.$disconnect();\n});"],"names":[],"mappings":";;AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,OAAO,EAAE,GAAG,EAAE,MAAM,iBAAiB,CAAC;AAEtC,MAAM,WAAW,GAAG,GAAG,EAAE;IACvB,QAAQ,GAAG,CAAC,QAAQ,EAAE,CAAC;QACrB,KAAK,aAAa;YAChB,OAAO,CAAC,OAAO,EAAE,OAAO,EAAE,MAAM,CAAC,CAAC;QACpC,KAAK,YAAY;YACf,OAAO,CAAC,OAAO,CAAC,CAAC;QACnB;YACE,OAAO,CAAC,OAAO,CAAC,CAAC;IACrB,CAAC;AACH,CAAC,CAAA;AAED,MAAM,cAAc,GAAG,GAAG,EAAE;IAC1B,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC;QACtC,qEAAqE;QACrE,IAAI,GAAG,CAAC,QAAQ,KAAK,YAAY,EAAE,CAAC;YAClC,IAAI,CAAC,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,kBAAkB,CAAC,EAAE,CAAC;gBAC9C,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,kBAAkB,EAAE,GAAG,CAAC,CAAC;YAChD,CAAC;YACD,IAAI,CAAC,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,cAAc,CAAC,EAAE,CAAC;gBAC1C,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,cAAc,EAAE,IAAI,CAAC,CAAC;YAC7C,CAAC;QACH,CAAC;QACD,OAAO,GAAG,CAAC,QAAQ,EAAE,CAAC;IACxB,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,GAAG,CAAC,YAAY,CAAC;IAC1B,CAAC;AACH,CAAC,CAAC;AAEF,MAAM,qBAAqB,GAAG,GAAG,EAAE;IACjC,OAAO,IAAI,YAAY,CAAC;QACtB,WAAW,EAAE;YACX,EAAE,EAAE;gBACF,GAAG,EAAE,cAAc,EAAE;aACtB;SACF;KACF,CAAC,CAAC;AACL,CAAC,CAAC;AAOF,MAAM,CAAC,MAAM,MAAM,GAAG,UAAU,CAAC,MAAM,IAAI,qBAAqB,EAAE,CAAC;AAEnE,IAAI,GAAG,CAAC,QAAQ,KAAK,YAAY,EAAE,CAAC;IAClC,UAAU,CAAC,MAAM,GAAG,MAAM,CAAC;AAC7B,CAAC;AAED,OAAO,CAAC,EAAE,CAAC,YAAY,EAAE,KAAK,IAAI,EAAE;IAClC,MAAM,MAAM,CAAC,WAAW,EAAE,CAAC;AAC7B,CAAC,CAAC,CAAC","debug_id":"fee0859e-cfe1-5496-aa37-3bbf9aa0f3bb"}
@@ -1 +1 @@
1
- {"version":3,"file":"auth.d.ts","sourceRoot":"/","sources":["middleware/auth.ts"],"names":[],"mappings":"AAUA,eAAO,MAAM,oBAAoB,GAAI,GAAG,GAAG;;;;;CA+H1C,CAAC"}
1
+ {"version":3,"file":"auth.d.ts","sourceRoot":"/","sources":["middleware/auth.ts"],"names":[],"mappings":"AA2BA,eAAO,MAAM,oBAAoB,GAAI,GAAG,GAAG;;;;;CA6H1C,CAAC"}
@@ -1,19 +1,32 @@
1
1
 
2
- !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="f1f0bcb4-fc6b-5b13-92c1-e2fedb706ef2")}catch(e){}}();
2
+ !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="ef261406-0c03-5992-af7a-26665f456c05")}catch(e){}}();
3
3
  import { TRPCError } from "@trpc/server";
4
4
  import * as Sentry from "@sentry/node";
5
5
  import { findUserBySessionToken, findTeacherClassesByUserId, findClassWithMember, findClassWithTeacher, } from "../models/auth.js";
6
+ function getRequestToken(headers) {
7
+ const userHeader = headers["x-user"];
8
+ if (typeof userHeader === "string") {
9
+ return userHeader;
10
+ }
11
+ if (Array.isArray(userHeader) && userHeader.length > 0) {
12
+ return userHeader[0];
13
+ }
14
+ const authorizationHeader = headers.authorization;
15
+ if (typeof authorizationHeader === "string" && authorizationHeader.startsWith("Bearer ")) {
16
+ return authorizationHeader.slice(7);
17
+ }
18
+ return null;
19
+ }
6
20
  export const createAuthMiddleware = (t) => {
7
21
  const isAuthed = t.middleware(async ({ next, ctx }) => {
8
- const userHeader = ctx.req.headers["x-user"];
9
- if (!userHeader) {
22
+ const token = getRequestToken(ctx.req.headers);
23
+ if (!token) {
10
24
  throw new TRPCError({
11
25
  code: "UNAUTHORIZED",
12
26
  message: "Not authenticated - no token found",
13
27
  });
14
28
  }
15
29
  try {
16
- const token = typeof userHeader === "string" ? userHeader : userHeader[0];
17
30
  const user = await findUserBySessionToken(token);
18
31
  if (!user) {
19
32
  throw new TRPCError({
@@ -110,4 +123,4 @@ export const createAuthMiddleware = (t) => {
110
123
  };
111
124
  };
112
125
  //# sourceMappingURL=auth.js.map
113
- //# debugId=f1f0bcb4-fc6b-5b13-92c1-e2fedb706ef2
126
+ //# debugId=ef261406-0c03-5992-af7a-26665f456c05
@@ -1 +1 @@
1
- {"version":3,"file":"auth.js","sources":["middleware/auth.ts"],"sourceRoot":"/","sourcesContent":["import { TRPCError } from \"@trpc/server\";\nimport * as Sentry from \"@sentry/node\";\nimport type { MiddlewareContext } from \"../types/trpc.js\";\nimport {\n findUserBySessionToken,\n findTeacherClassesByUserId,\n findClassWithMember,\n findClassWithTeacher,\n} from \"../models/auth.js\";\n\nexport const createAuthMiddleware = (t: any) => {\n const isAuthed = t.middleware(async ({ next, ctx }: MiddlewareContext) => {\n const userHeader = ctx.req.headers[\"x-user\"];\n\n if (!userHeader) {\n throw new TRPCError({\n code: \"UNAUTHORIZED\",\n message: \"Not authenticated - no token found\",\n });\n }\n\n try {\n const token =\n typeof userHeader === \"string\" ? userHeader : userHeader[0];\n const user = await findUserBySessionToken(token);\n\n if (!user) {\n throw new TRPCError({\n code: \"UNAUTHORIZED\",\n message: \"Invalid or expired session\",\n });\n }\n\n return next({\n ctx: {\n ...ctx,\n user,\n },\n });\n } catch (error) {\n if (error instanceof TRPCError) {\n throw error;\n }\n Sentry.captureException(error);\n console.error(error);\n throw new TRPCError({\n code: \"INTERNAL_SERVER_ERROR\",\n message: \"Internal server error\",\n });\n }\n });\n\n const addComputedFlags = t.middleware(\n async ({ next, ctx }: MiddlewareContext) => {\n if (!ctx.user) {\n throw new TRPCError({\n code: \"UNAUTHORIZED\",\n message: \"Not authenticated\",\n });\n }\n\n const teacherClasses = await findTeacherClassesByUserId(ctx.user.id);\n\n return next({\n ctx: {\n ...ctx,\n isTeacher: teacherClasses.length > 0,\n teacherClassIds: teacherClasses.map((c) => c.id),\n },\n });\n }\n );\n\n const isMemberInClass = t.middleware(\n async ({ next, ctx, input }: MiddlewareContext) => {\n if (!ctx.user) {\n throw new TRPCError({\n code: \"UNAUTHORIZED\",\n message: \"Not authenticated\",\n });\n }\n\n const classId = (input as { classId: string })?.classId;\n if (!classId) {\n throw new TRPCError({\n code: \"BAD_REQUEST\",\n message: \"classId is required\",\n });\n }\n\n const isMember = await findClassWithMember(classId, ctx.user.id);\n if (!isMember) {\n throw new TRPCError({\n code: \"FORBIDDEN\",\n message: \"Not a member in this class\",\n });\n }\n\n return next();\n }\n );\n\n const isTeacherInClass = t.middleware(\n async ({ next, ctx, input }: MiddlewareContext) => {\n if (!ctx.user) {\n throw new TRPCError({\n code: \"UNAUTHORIZED\",\n message: \"Not authenticated\",\n });\n }\n\n const classId = (input as { classId: string })?.classId;\n if (!classId) {\n throw new TRPCError({\n code: \"BAD_REQUEST\",\n message: \"classId is required\",\n });\n }\n\n const isTeacher = await findClassWithTeacher(classId, ctx.user.id);\n if (!isTeacher) {\n throw new TRPCError({\n code: \"FORBIDDEN\",\n message: \"Not a teacher in this class\",\n });\n }\n\n return next();\n }\n );\n\n return {\n isAuthed,\n addComputedFlags,\n isMemberInClass,\n isTeacherInClass,\n };\n};\n"],"names":[],"mappings":"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","debug_id":"f1f0bcb4-fc6b-5b13-92c1-e2fedb706ef2"}
1
+ {"version":3,"file":"auth.js","sources":["middleware/auth.ts"],"sourceRoot":"/","sourcesContent":["import { TRPCError } from \"@trpc/server\";\nimport * as Sentry from \"@sentry/node\";\nimport type { MiddlewareContext } from \"../types/trpc.js\";\nimport {\n findUserBySessionToken,\n findTeacherClassesByUserId,\n findClassWithMember,\n findClassWithTeacher,\n} from \"../models/auth.js\";\n\nfunction getRequestToken(headers: MiddlewareContext[\"ctx\"][\"req\"][\"headers\"]) {\n const userHeader = headers[\"x-user\"];\n if (typeof userHeader === \"string\") {\n return userHeader;\n }\n if (Array.isArray(userHeader) && userHeader.length > 0) {\n return userHeader[0];\n }\n\n const authorizationHeader = headers.authorization;\n if (typeof authorizationHeader === \"string\" && authorizationHeader.startsWith(\"Bearer \")) {\n return authorizationHeader.slice(7);\n }\n\n return null;\n}\n\nexport const createAuthMiddleware = (t: any) => {\n const isAuthed = t.middleware(async ({ next, ctx }: MiddlewareContext) => {\n const token = getRequestToken(ctx.req.headers);\n\n if (!token) {\n throw new TRPCError({\n code: \"UNAUTHORIZED\",\n message: \"Not authenticated - no token found\",\n });\n }\n\n try {\n const user = await findUserBySessionToken(token);\n\n if (!user) {\n throw new TRPCError({\n code: \"UNAUTHORIZED\",\n message: \"Invalid or expired session\",\n });\n }\n\n return next({\n ctx: {\n ...ctx,\n user,\n },\n });\n } catch (error) {\n if (error instanceof TRPCError) {\n throw error;\n }\n Sentry.captureException(error);\n console.error(error);\n throw new TRPCError({\n code: \"INTERNAL_SERVER_ERROR\",\n message: \"Internal server error\",\n });\n }\n });\n\n const addComputedFlags = t.middleware(\n async ({ next, ctx }: MiddlewareContext) => {\n if (!ctx.user) {\n throw new TRPCError({\n code: \"UNAUTHORIZED\",\n message: \"Not authenticated\",\n });\n }\n\n const teacherClasses = await findTeacherClassesByUserId(ctx.user.id);\n\n return next({\n ctx: {\n ...ctx,\n isTeacher: teacherClasses.length > 0,\n teacherClassIds: teacherClasses.map((c) => c.id),\n },\n });\n }\n );\n\n const isMemberInClass = t.middleware(\n async ({ next, ctx, input }: MiddlewareContext) => {\n if (!ctx.user) {\n throw new TRPCError({\n code: \"UNAUTHORIZED\",\n message: \"Not authenticated\",\n });\n }\n\n const classId = (input as { classId: string })?.classId;\n if (!classId) {\n throw new TRPCError({\n code: \"BAD_REQUEST\",\n message: \"classId is required\",\n });\n }\n\n const isMember = await findClassWithMember(classId, ctx.user.id);\n if (!isMember) {\n throw new TRPCError({\n code: \"FORBIDDEN\",\n message: \"Not a member in this class\",\n });\n }\n\n return next();\n }\n );\n\n const isTeacherInClass = t.middleware(\n async ({ next, ctx, input }: MiddlewareContext) => {\n if (!ctx.user) {\n throw new TRPCError({\n code: \"UNAUTHORIZED\",\n message: \"Not authenticated\",\n });\n }\n\n const classId = (input as { classId: string })?.classId;\n if (!classId) {\n throw new TRPCError({\n code: \"BAD_REQUEST\",\n message: \"classId is required\",\n });\n }\n\n const isTeacher = await findClassWithTeacher(classId, ctx.user.id);\n if (!isTeacher) {\n throw new TRPCError({\n code: \"FORBIDDEN\",\n message: \"Not a teacher in this class\",\n });\n }\n\n return next();\n }\n );\n\n return {\n isAuthed,\n addComputedFlags,\n isMemberInClass,\n isTeacherInClass,\n };\n};\n"],"names":[],"mappings":"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","debug_id":"ef261406-0c03-5992-af7a-26665f456c05"}
@@ -1 +1 @@
1
- {"version":3,"file":"security.d.ts","sourceRoot":"/","sources":["middleware/security.ts"],"names":[],"mappings":"AAoBA,eAAO,MAAM,cAAc,sDAWzB,CAAC;AAGH,eAAO,MAAM,WAAW,sDAQtB,CAAC;AAGH,eAAO,MAAM,aAAa,sDAOxB,CAAC;AAGH,eAAO,MAAM,YAAY,sFAwB2mF,CAAC,2BAAnoF,CAAC"}
1
+ {"version":3,"file":"security.d.ts","sourceRoot":"/","sources":["middleware/security.ts"],"names":[],"mappings":"AAoBA,eAAO,MAAM,cAAc,sDAWzB,CAAC;AAGH,eAAO,MAAM,WAAW,sDAQtB,CAAC;AAGH,eAAO,MAAM,aAAa,sDAOxB,CAAC;AAGH,eAAO,MAAM,YAAY,sFAwBomF,CAAC,2BAA5nF,CAAC"}