@studious-lms/server 1.2.45 → 1.2.46

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 (231) hide show
  1. package/.env.example +45 -0
  2. package/.env.test.example +37 -0
  3. package/README.md +34 -7
  4. package/coverage/base.css +224 -0
  5. package/coverage/block-navigation.js +87 -0
  6. package/coverage/clover.xml +12110 -0
  7. package/coverage/coverage-final.json +44 -0
  8. package/coverage/favicon.png +0 -0
  9. package/coverage/index.html +221 -0
  10. package/coverage/prettify.css +1 -0
  11. package/coverage/prettify.js +2 -0
  12. package/coverage/server/index.html +116 -0
  13. package/coverage/server/src/exportType.ts.html +109 -0
  14. package/coverage/server/src/index.html +161 -0
  15. package/coverage/server/src/index.ts.html +1702 -0
  16. package/coverage/server/src/instrument.ts.html +130 -0
  17. package/coverage/server/src/lib/config/env.ts.html +448 -0
  18. package/coverage/server/src/lib/config/index.html +116 -0
  19. package/coverage/server/src/lib/fileUpload.ts.html +1138 -0
  20. package/coverage/server/src/lib/googleCloudStorage.ts.html +334 -0
  21. package/coverage/server/src/lib/index.html +206 -0
  22. package/coverage/server/src/lib/jsonConversion.ts.html +2323 -0
  23. package/coverage/server/src/lib/jsonStyles.ts.html +193 -0
  24. package/coverage/server/src/lib/notificationHandler.ts.html +193 -0
  25. package/coverage/server/src/lib/pusher.ts.html +121 -0
  26. package/coverage/server/src/lib/thumbnailGenerator.ts.html +592 -0
  27. package/coverage/server/src/middleware/auth.ts.html +646 -0
  28. package/coverage/server/src/middleware/index.html +146 -0
  29. package/coverage/server/src/middleware/logging.ts.html +244 -0
  30. package/coverage/server/src/middleware/security.ts.html +271 -0
  31. package/coverage/server/src/routers/_app.ts.html +232 -0
  32. package/coverage/server/src/routers/agenda.ts.html +319 -0
  33. package/coverage/server/src/routers/announcement.ts.html +3481 -0
  34. package/coverage/server/src/routers/assignment.ts.html +7633 -0
  35. package/coverage/server/src/routers/attendance.ts.html +1030 -0
  36. package/coverage/server/src/routers/auth.ts.html +1081 -0
  37. package/coverage/server/src/routers/class.ts.html +3535 -0
  38. package/coverage/server/src/routers/comment.ts.html +991 -0
  39. package/coverage/server/src/routers/conversation.ts.html +982 -0
  40. package/coverage/server/src/routers/event.ts.html +1609 -0
  41. package/coverage/server/src/routers/file.ts.html +1144 -0
  42. package/coverage/server/src/routers/folder.ts.html +2797 -0
  43. package/coverage/server/src/routers/index.html +386 -0
  44. package/coverage/server/src/routers/labChat.ts.html +3073 -0
  45. package/coverage/server/src/routers/marketing.ts.html +340 -0
  46. package/coverage/server/src/routers/message.ts.html +1912 -0
  47. package/coverage/server/src/routers/notifications.ts.html +364 -0
  48. package/coverage/server/src/routers/section.ts.html +1120 -0
  49. package/coverage/server/src/routers/user.ts.html +862 -0
  50. package/coverage/server/src/routers/worksheet.ts.html +1729 -0
  51. package/coverage/server/src/trpc.ts.html +397 -0
  52. package/coverage/server/src/types/index.html +116 -0
  53. package/coverage/server/src/types/trpc.ts.html +127 -0
  54. package/coverage/server/src/utils/aiUser.ts.html +280 -0
  55. package/coverage/server/src/utils/email.ts.html +121 -0
  56. package/coverage/server/src/utils/generateInviteCode.ts.html +106 -0
  57. package/coverage/server/src/utils/index.html +206 -0
  58. package/coverage/server/src/utils/inference.ts.html +709 -0
  59. package/coverage/server/src/utils/logger.ts.html +664 -0
  60. package/coverage/server/src/utils/prismaErrorHandler.ts.html +907 -0
  61. package/coverage/server/src/utils/prismaWrapper.ts.html +355 -0
  62. package/coverage/server/vitest.config.ts.html +196 -0
  63. package/coverage/sort-arrow-sprite.png +0 -0
  64. package/coverage/sorter.js +210 -0
  65. package/dist/index.d.ts.map +1 -1
  66. package/dist/index.js +83 -52
  67. package/dist/index.js.map +1 -1
  68. package/dist/instrument.js +15 -8
  69. package/dist/instrument.js.map +1 -1
  70. package/dist/lib/config/env.d.ts +169 -0
  71. package/dist/lib/config/env.d.ts.map +1 -0
  72. package/dist/lib/config/env.js +115 -0
  73. package/dist/lib/config/env.js.map +1 -0
  74. package/dist/lib/fileUpload.d.ts.map +1 -1
  75. package/dist/lib/fileUpload.js +5 -4
  76. package/dist/lib/fileUpload.js.map +1 -1
  77. package/dist/lib/googleCloudStorage.d.ts.map +1 -1
  78. package/dist/lib/googleCloudStorage.js +7 -8
  79. package/dist/lib/googleCloudStorage.js.map +1 -1
  80. package/dist/lib/jsonConversion.d.ts.map +1 -1
  81. package/dist/lib/jsonConversion.js +14 -16
  82. package/dist/lib/jsonConversion.js.map +1 -1
  83. package/dist/lib/notificationHandler.d.ts +2 -2
  84. package/dist/lib/prisma.d.ts +2 -2
  85. package/dist/lib/prisma.d.ts.map +1 -1
  86. package/dist/lib/prisma.js +22 -3
  87. package/dist/lib/prisma.js.map +1 -1
  88. package/dist/lib/pusher.d.ts.map +1 -1
  89. package/dist/lib/pusher.js +8 -7
  90. package/dist/lib/pusher.js.map +1 -1
  91. package/dist/middleware/auth.d.ts.map +1 -1
  92. package/dist/middleware/auth.js +6 -5
  93. package/dist/middleware/auth.js.map +1 -1
  94. package/dist/middleware/security.d.ts +5 -0
  95. package/dist/middleware/security.d.ts.map +1 -0
  96. package/dist/middleware/security.js +77 -0
  97. package/dist/middleware/security.js.map +1 -0
  98. package/dist/routers/_app.d.ts +294 -98
  99. package/dist/routers/_app.d.ts.map +1 -1
  100. package/dist/routers/_app.js +4 -2
  101. package/dist/routers/_app.js.map +1 -1
  102. package/dist/routers/agenda.d.ts.map +1 -1
  103. package/dist/routers/agenda.js +12 -9
  104. package/dist/routers/agenda.js.map +1 -1
  105. package/dist/routers/announcement.d.ts +8 -0
  106. package/dist/routers/announcement.d.ts.map +1 -1
  107. package/dist/routers/announcement.js +6 -4
  108. package/dist/routers/announcement.js.map +1 -1
  109. package/dist/routers/assignment.d.ts +7 -4
  110. package/dist/routers/assignment.d.ts.map +1 -1
  111. package/dist/routers/assignment.js +35 -18
  112. package/dist/routers/assignment.js.map +1 -1
  113. package/dist/routers/attendance.d.ts +1 -0
  114. package/dist/routers/attendance.d.ts.map +1 -1
  115. package/dist/routers/attendance.js +4 -4
  116. package/dist/routers/attendance.js.map +1 -1
  117. package/dist/routers/auth.d.ts +20 -0
  118. package/dist/routers/auth.d.ts.map +1 -1
  119. package/dist/routers/auth.js +132 -15
  120. package/dist/routers/auth.js.map +1 -1
  121. package/dist/routers/class.d.ts +10 -0
  122. package/dist/routers/class.d.ts.map +1 -1
  123. package/dist/routers/class.js +49 -5
  124. package/dist/routers/class.js.map +1 -1
  125. package/dist/routers/comment.d.ts +2 -0
  126. package/dist/routers/comment.d.ts.map +1 -1
  127. package/dist/routers/conversation.d.ts +1 -0
  128. package/dist/routers/conversation.d.ts.map +1 -1
  129. package/dist/routers/conversation.js +46 -31
  130. package/dist/routers/conversation.js.map +1 -1
  131. package/dist/routers/file.d.ts.map +1 -1
  132. package/dist/routers/file.js +30 -7
  133. package/dist/routers/file.js.map +1 -1
  134. package/dist/routers/labChat.d.ts +1 -0
  135. package/dist/routers/labChat.d.ts.map +1 -1
  136. package/dist/routers/labChat.js +2 -3
  137. package/dist/routers/labChat.js.map +1 -1
  138. package/dist/routers/marketing.d.ts +1 -1
  139. package/dist/routers/newtonChat.d.ts +55 -0
  140. package/dist/routers/newtonChat.d.ts.map +1 -0
  141. package/dist/routers/newtonChat.js +438 -0
  142. package/dist/routers/newtonChat.js.map +1 -0
  143. package/dist/routers/notifications.d.ts +4 -4
  144. package/dist/routers/section.d.ts +9 -4
  145. package/dist/routers/section.d.ts.map +1 -1
  146. package/dist/routers/section.js +8 -8
  147. package/dist/routers/section.js.map +1 -1
  148. package/dist/routers/user.d.ts.map +1 -1
  149. package/dist/routers/user.js +5 -4
  150. package/dist/routers/user.js.map +1 -1
  151. package/dist/routers/worksheet.d.ts +30 -36
  152. package/dist/routers/worksheet.d.ts.map +1 -1
  153. package/dist/routers/worksheet.js +11 -33
  154. package/dist/routers/worksheet.js.map +1 -1
  155. package/dist/seedDatabase.d.ts +1 -1
  156. package/dist/seedDatabase.js +275 -284
  157. package/dist/seedDatabase.js.map +1 -1
  158. package/dist/server/pipelines/aiLabChat.d.ts +10 -0
  159. package/dist/server/pipelines/aiLabChat.d.ts.map +1 -0
  160. package/dist/server/pipelines/aiLabChat.js +83 -0
  161. package/dist/server/pipelines/aiLabChat.js.map +1 -0
  162. package/dist/server/pipelines/gradeWorksheet.d.ts +2 -0
  163. package/dist/server/pipelines/gradeWorksheet.d.ts.map +1 -0
  164. package/dist/server/pipelines/gradeWorksheet.js +138 -0
  165. package/dist/server/pipelines/gradeWorksheet.js.map +1 -0
  166. package/dist/trpc.d.ts.map +1 -1
  167. package/dist/trpc.js +2 -2
  168. package/dist/trpc.js.map +1 -1
  169. package/dist/utils/email.d.ts +9 -1
  170. package/dist/utils/email.d.ts.map +1 -1
  171. package/dist/utils/email.js +20 -5
  172. package/dist/utils/email.js.map +1 -1
  173. package/dist/utils/inference.d.ts +3 -0
  174. package/dist/utils/inference.d.ts.map +1 -1
  175. package/dist/utils/inference.js +41 -7
  176. package/dist/utils/inference.js.map +1 -1
  177. package/dist/utils/logger.d.ts.map +1 -1
  178. package/dist/utils/logger.js +3 -3
  179. package/dist/utils/logger.js.map +1 -1
  180. package/docker-compose.yml +14 -0
  181. package/package.json +13 -4
  182. package/prisma/schema.prisma +32 -5
  183. package/scripts/test-pre-push.ts +14 -0
  184. package/src/index.ts +98 -54
  185. package/src/instrument.ts +13 -6
  186. package/src/lib/config/env.ts +126 -0
  187. package/src/lib/fileUpload.ts +3 -2
  188. package/src/lib/googleCloudStorage.ts +6 -6
  189. package/src/lib/jsonConversion.ts +12 -14
  190. package/src/lib/prisma.ts +23 -2
  191. package/src/lib/pusher.ts +6 -5
  192. package/src/middleware/auth.ts +4 -3
  193. package/src/middleware/security.ts +80 -0
  194. package/src/routers/_app.ts +2 -0
  195. package/src/routers/agenda.ts +10 -7
  196. package/src/routers/announcement.ts +4 -2
  197. package/src/routers/assignment.ts +58 -40
  198. package/src/routers/attendance.ts +2 -2
  199. package/src/routers/auth.ts +143 -14
  200. package/src/routers/class.ts +52 -3
  201. package/src/routers/conversation.ts +49 -29
  202. package/src/routers/file.ts +29 -5
  203. package/src/routers/labChat.ts +0 -1
  204. package/src/routers/newtonChat.ts +520 -0
  205. package/src/routers/section.ts +6 -6
  206. package/src/routers/user.ts +3 -2
  207. package/src/routers/worksheet.ts +9 -37
  208. package/src/seedDatabase.ts +290 -283
  209. package/src/server/pipelines/aiLabChat.ts +92 -0
  210. package/src/server/pipelines/gradeWorksheet.ts +152 -0
  211. package/src/trpc.ts +2 -0
  212. package/src/utils/email.ts +30 -3
  213. package/src/utils/inference.ts +50 -5
  214. package/src/utils/logger.ts +2 -1
  215. package/tests/announcement.test.ts +164 -0
  216. package/tests/assignment.test.ts +296 -0
  217. package/tests/attendance.test.ts +168 -0
  218. package/tests/auth.test.ts +33 -10
  219. package/tests/class.test.ts +34 -9
  220. package/tests/event.test.ts +228 -0
  221. package/tests/section.test.ts +216 -0
  222. package/tests/setup.ts +70 -16
  223. package/tests/user.test.ts +158 -0
  224. package/vitest.config.ts +26 -0
  225. package/API_SPECIFICATION.md +0 -1597
  226. package/BASE64_REMOVAL_SUMMARY.md +0 -164
  227. package/CHAT_API_SPEC.md +0 -579
  228. package/LAB_CHAT_API_SPEC.md +0 -518
  229. package/dist/routers/school.d.ts +0 -208
  230. package/dist/routers/school.d.ts.map +0 -1
  231. package/dist/routers/school.js +0 -483
@@ -0,0 +1,1138 @@
1
+
2
+ <!doctype html>
3
+ <html lang="en">
4
+
5
+ <head>
6
+ <title>Code coverage report for server/src/lib/fileUpload.ts</title>
7
+ <meta charset="utf-8" />
8
+ <link rel="stylesheet" href="../../../prettify.css" />
9
+ <link rel="stylesheet" href="../../../base.css" />
10
+ <link rel="shortcut icon" type="image/x-icon" href="../../../favicon.png" />
11
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
12
+ <style type='text/css'>
13
+ .coverage-summary .sorter {
14
+ background-image: url(../../../sort-arrow-sprite.png);
15
+ }
16
+ </style>
17
+ </head>
18
+
19
+ <body>
20
+ <div class='wrapper'>
21
+ <div class='pad1'>
22
+ <h1><a href="../../../index.html">All files</a> / <a href="index.html">server/src/lib</a> fileUpload.ts</h1>
23
+ <div class='clearfix'>
24
+
25
+ <div class='fl pad1y space-right2'>
26
+ <span class="strong">36.28% </span>
27
+ <span class="quiet">Statements</span>
28
+ <span class='fraction'>86/237</span>
29
+ </div>
30
+
31
+
32
+ <div class='fl pad1y space-right2'>
33
+ <span class="strong">41.66% </span>
34
+ <span class="quiet">Branches</span>
35
+ <span class='fraction'>5/12</span>
36
+ </div>
37
+
38
+
39
+ <div class='fl pad1y space-right2'>
40
+ <span class="strong">28.57% </span>
41
+ <span class="quiet">Functions</span>
42
+ <span class='fraction'>2/7</span>
43
+ </div>
44
+
45
+
46
+ <div class='fl pad1y space-right2'>
47
+ <span class="strong">36.28% </span>
48
+ <span class="quiet">Lines</span>
49
+ <span class='fraction'>86/237</span>
50
+ </div>
51
+
52
+
53
+ </div>
54
+ <p class="quiet">
55
+ Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block.
56
+ </p>
57
+ <template id="filterTemplate">
58
+ <div class="quiet">
59
+ Filter:
60
+ <input type="search" id="fileSearch">
61
+ </div>
62
+ </template>
63
+ </div>
64
+ <div class='status-line low'></div>
65
+ <pre><table class="coverage">
66
+ <tr><td class="line-count quiet"><a name='L1'></a><a href='#L1'>1</a>
67
+ <a name='L2'></a><a href='#L2'>2</a>
68
+ <a name='L3'></a><a href='#L3'>3</a>
69
+ <a name='L4'></a><a href='#L4'>4</a>
70
+ <a name='L5'></a><a href='#L5'>5</a>
71
+ <a name='L6'></a><a href='#L6'>6</a>
72
+ <a name='L7'></a><a href='#L7'>7</a>
73
+ <a name='L8'></a><a href='#L8'>8</a>
74
+ <a name='L9'></a><a href='#L9'>9</a>
75
+ <a name='L10'></a><a href='#L10'>10</a>
76
+ <a name='L11'></a><a href='#L11'>11</a>
77
+ <a name='L12'></a><a href='#L12'>12</a>
78
+ <a name='L13'></a><a href='#L13'>13</a>
79
+ <a name='L14'></a><a href='#L14'>14</a>
80
+ <a name='L15'></a><a href='#L15'>15</a>
81
+ <a name='L16'></a><a href='#L16'>16</a>
82
+ <a name='L17'></a><a href='#L17'>17</a>
83
+ <a name='L18'></a><a href='#L18'>18</a>
84
+ <a name='L19'></a><a href='#L19'>19</a>
85
+ <a name='L20'></a><a href='#L20'>20</a>
86
+ <a name='L21'></a><a href='#L21'>21</a>
87
+ <a name='L22'></a><a href='#L22'>22</a>
88
+ <a name='L23'></a><a href='#L23'>23</a>
89
+ <a name='L24'></a><a href='#L24'>24</a>
90
+ <a name='L25'></a><a href='#L25'>25</a>
91
+ <a name='L26'></a><a href='#L26'>26</a>
92
+ <a name='L27'></a><a href='#L27'>27</a>
93
+ <a name='L28'></a><a href='#L28'>28</a>
94
+ <a name='L29'></a><a href='#L29'>29</a>
95
+ <a name='L30'></a><a href='#L30'>30</a>
96
+ <a name='L31'></a><a href='#L31'>31</a>
97
+ <a name='L32'></a><a href='#L32'>32</a>
98
+ <a name='L33'></a><a href='#L33'>33</a>
99
+ <a name='L34'></a><a href='#L34'>34</a>
100
+ <a name='L35'></a><a href='#L35'>35</a>
101
+ <a name='L36'></a><a href='#L36'>36</a>
102
+ <a name='L37'></a><a href='#L37'>37</a>
103
+ <a name='L38'></a><a href='#L38'>38</a>
104
+ <a name='L39'></a><a href='#L39'>39</a>
105
+ <a name='L40'></a><a href='#L40'>40</a>
106
+ <a name='L41'></a><a href='#L41'>41</a>
107
+ <a name='L42'></a><a href='#L42'>42</a>
108
+ <a name='L43'></a><a href='#L43'>43</a>
109
+ <a name='L44'></a><a href='#L44'>44</a>
110
+ <a name='L45'></a><a href='#L45'>45</a>
111
+ <a name='L46'></a><a href='#L46'>46</a>
112
+ <a name='L47'></a><a href='#L47'>47</a>
113
+ <a name='L48'></a><a href='#L48'>48</a>
114
+ <a name='L49'></a><a href='#L49'>49</a>
115
+ <a name='L50'></a><a href='#L50'>50</a>
116
+ <a name='L51'></a><a href='#L51'>51</a>
117
+ <a name='L52'></a><a href='#L52'>52</a>
118
+ <a name='L53'></a><a href='#L53'>53</a>
119
+ <a name='L54'></a><a href='#L54'>54</a>
120
+ <a name='L55'></a><a href='#L55'>55</a>
121
+ <a name='L56'></a><a href='#L56'>56</a>
122
+ <a name='L57'></a><a href='#L57'>57</a>
123
+ <a name='L58'></a><a href='#L58'>58</a>
124
+ <a name='L59'></a><a href='#L59'>59</a>
125
+ <a name='L60'></a><a href='#L60'>60</a>
126
+ <a name='L61'></a><a href='#L61'>61</a>
127
+ <a name='L62'></a><a href='#L62'>62</a>
128
+ <a name='L63'></a><a href='#L63'>63</a>
129
+ <a name='L64'></a><a href='#L64'>64</a>
130
+ <a name='L65'></a><a href='#L65'>65</a>
131
+ <a name='L66'></a><a href='#L66'>66</a>
132
+ <a name='L67'></a><a href='#L67'>67</a>
133
+ <a name='L68'></a><a href='#L68'>68</a>
134
+ <a name='L69'></a><a href='#L69'>69</a>
135
+ <a name='L70'></a><a href='#L70'>70</a>
136
+ <a name='L71'></a><a href='#L71'>71</a>
137
+ <a name='L72'></a><a href='#L72'>72</a>
138
+ <a name='L73'></a><a href='#L73'>73</a>
139
+ <a name='L74'></a><a href='#L74'>74</a>
140
+ <a name='L75'></a><a href='#L75'>75</a>
141
+ <a name='L76'></a><a href='#L76'>76</a>
142
+ <a name='L77'></a><a href='#L77'>77</a>
143
+ <a name='L78'></a><a href='#L78'>78</a>
144
+ <a name='L79'></a><a href='#L79'>79</a>
145
+ <a name='L80'></a><a href='#L80'>80</a>
146
+ <a name='L81'></a><a href='#L81'>81</a>
147
+ <a name='L82'></a><a href='#L82'>82</a>
148
+ <a name='L83'></a><a href='#L83'>83</a>
149
+ <a name='L84'></a><a href='#L84'>84</a>
150
+ <a name='L85'></a><a href='#L85'>85</a>
151
+ <a name='L86'></a><a href='#L86'>86</a>
152
+ <a name='L87'></a><a href='#L87'>87</a>
153
+ <a name='L88'></a><a href='#L88'>88</a>
154
+ <a name='L89'></a><a href='#L89'>89</a>
155
+ <a name='L90'></a><a href='#L90'>90</a>
156
+ <a name='L91'></a><a href='#L91'>91</a>
157
+ <a name='L92'></a><a href='#L92'>92</a>
158
+ <a name='L93'></a><a href='#L93'>93</a>
159
+ <a name='L94'></a><a href='#L94'>94</a>
160
+ <a name='L95'></a><a href='#L95'>95</a>
161
+ <a name='L96'></a><a href='#L96'>96</a>
162
+ <a name='L97'></a><a href='#L97'>97</a>
163
+ <a name='L98'></a><a href='#L98'>98</a>
164
+ <a name='L99'></a><a href='#L99'>99</a>
165
+ <a name='L100'></a><a href='#L100'>100</a>
166
+ <a name='L101'></a><a href='#L101'>101</a>
167
+ <a name='L102'></a><a href='#L102'>102</a>
168
+ <a name='L103'></a><a href='#L103'>103</a>
169
+ <a name='L104'></a><a href='#L104'>104</a>
170
+ <a name='L105'></a><a href='#L105'>105</a>
171
+ <a name='L106'></a><a href='#L106'>106</a>
172
+ <a name='L107'></a><a href='#L107'>107</a>
173
+ <a name='L108'></a><a href='#L108'>108</a>
174
+ <a name='L109'></a><a href='#L109'>109</a>
175
+ <a name='L110'></a><a href='#L110'>110</a>
176
+ <a name='L111'></a><a href='#L111'>111</a>
177
+ <a name='L112'></a><a href='#L112'>112</a>
178
+ <a name='L113'></a><a href='#L113'>113</a>
179
+ <a name='L114'></a><a href='#L114'>114</a>
180
+ <a name='L115'></a><a href='#L115'>115</a>
181
+ <a name='L116'></a><a href='#L116'>116</a>
182
+ <a name='L117'></a><a href='#L117'>117</a>
183
+ <a name='L118'></a><a href='#L118'>118</a>
184
+ <a name='L119'></a><a href='#L119'>119</a>
185
+ <a name='L120'></a><a href='#L120'>120</a>
186
+ <a name='L121'></a><a href='#L121'>121</a>
187
+ <a name='L122'></a><a href='#L122'>122</a>
188
+ <a name='L123'></a><a href='#L123'>123</a>
189
+ <a name='L124'></a><a href='#L124'>124</a>
190
+ <a name='L125'></a><a href='#L125'>125</a>
191
+ <a name='L126'></a><a href='#L126'>126</a>
192
+ <a name='L127'></a><a href='#L127'>127</a>
193
+ <a name='L128'></a><a href='#L128'>128</a>
194
+ <a name='L129'></a><a href='#L129'>129</a>
195
+ <a name='L130'></a><a href='#L130'>130</a>
196
+ <a name='L131'></a><a href='#L131'>131</a>
197
+ <a name='L132'></a><a href='#L132'>132</a>
198
+ <a name='L133'></a><a href='#L133'>133</a>
199
+ <a name='L134'></a><a href='#L134'>134</a>
200
+ <a name='L135'></a><a href='#L135'>135</a>
201
+ <a name='L136'></a><a href='#L136'>136</a>
202
+ <a name='L137'></a><a href='#L137'>137</a>
203
+ <a name='L138'></a><a href='#L138'>138</a>
204
+ <a name='L139'></a><a href='#L139'>139</a>
205
+ <a name='L140'></a><a href='#L140'>140</a>
206
+ <a name='L141'></a><a href='#L141'>141</a>
207
+ <a name='L142'></a><a href='#L142'>142</a>
208
+ <a name='L143'></a><a href='#L143'>143</a>
209
+ <a name='L144'></a><a href='#L144'>144</a>
210
+ <a name='L145'></a><a href='#L145'>145</a>
211
+ <a name='L146'></a><a href='#L146'>146</a>
212
+ <a name='L147'></a><a href='#L147'>147</a>
213
+ <a name='L148'></a><a href='#L148'>148</a>
214
+ <a name='L149'></a><a href='#L149'>149</a>
215
+ <a name='L150'></a><a href='#L150'>150</a>
216
+ <a name='L151'></a><a href='#L151'>151</a>
217
+ <a name='L152'></a><a href='#L152'>152</a>
218
+ <a name='L153'></a><a href='#L153'>153</a>
219
+ <a name='L154'></a><a href='#L154'>154</a>
220
+ <a name='L155'></a><a href='#L155'>155</a>
221
+ <a name='L156'></a><a href='#L156'>156</a>
222
+ <a name='L157'></a><a href='#L157'>157</a>
223
+ <a name='L158'></a><a href='#L158'>158</a>
224
+ <a name='L159'></a><a href='#L159'>159</a>
225
+ <a name='L160'></a><a href='#L160'>160</a>
226
+ <a name='L161'></a><a href='#L161'>161</a>
227
+ <a name='L162'></a><a href='#L162'>162</a>
228
+ <a name='L163'></a><a href='#L163'>163</a>
229
+ <a name='L164'></a><a href='#L164'>164</a>
230
+ <a name='L165'></a><a href='#L165'>165</a>
231
+ <a name='L166'></a><a href='#L166'>166</a>
232
+ <a name='L167'></a><a href='#L167'>167</a>
233
+ <a name='L168'></a><a href='#L168'>168</a>
234
+ <a name='L169'></a><a href='#L169'>169</a>
235
+ <a name='L170'></a><a href='#L170'>170</a>
236
+ <a name='L171'></a><a href='#L171'>171</a>
237
+ <a name='L172'></a><a href='#L172'>172</a>
238
+ <a name='L173'></a><a href='#L173'>173</a>
239
+ <a name='L174'></a><a href='#L174'>174</a>
240
+ <a name='L175'></a><a href='#L175'>175</a>
241
+ <a name='L176'></a><a href='#L176'>176</a>
242
+ <a name='L177'></a><a href='#L177'>177</a>
243
+ <a name='L178'></a><a href='#L178'>178</a>
244
+ <a name='L179'></a><a href='#L179'>179</a>
245
+ <a name='L180'></a><a href='#L180'>180</a>
246
+ <a name='L181'></a><a href='#L181'>181</a>
247
+ <a name='L182'></a><a href='#L182'>182</a>
248
+ <a name='L183'></a><a href='#L183'>183</a>
249
+ <a name='L184'></a><a href='#L184'>184</a>
250
+ <a name='L185'></a><a href='#L185'>185</a>
251
+ <a name='L186'></a><a href='#L186'>186</a>
252
+ <a name='L187'></a><a href='#L187'>187</a>
253
+ <a name='L188'></a><a href='#L188'>188</a>
254
+ <a name='L189'></a><a href='#L189'>189</a>
255
+ <a name='L190'></a><a href='#L190'>190</a>
256
+ <a name='L191'></a><a href='#L191'>191</a>
257
+ <a name='L192'></a><a href='#L192'>192</a>
258
+ <a name='L193'></a><a href='#L193'>193</a>
259
+ <a name='L194'></a><a href='#L194'>194</a>
260
+ <a name='L195'></a><a href='#L195'>195</a>
261
+ <a name='L196'></a><a href='#L196'>196</a>
262
+ <a name='L197'></a><a href='#L197'>197</a>
263
+ <a name='L198'></a><a href='#L198'>198</a>
264
+ <a name='L199'></a><a href='#L199'>199</a>
265
+ <a name='L200'></a><a href='#L200'>200</a>
266
+ <a name='L201'></a><a href='#L201'>201</a>
267
+ <a name='L202'></a><a href='#L202'>202</a>
268
+ <a name='L203'></a><a href='#L203'>203</a>
269
+ <a name='L204'></a><a href='#L204'>204</a>
270
+ <a name='L205'></a><a href='#L205'>205</a>
271
+ <a name='L206'></a><a href='#L206'>206</a>
272
+ <a name='L207'></a><a href='#L207'>207</a>
273
+ <a name='L208'></a><a href='#L208'>208</a>
274
+ <a name='L209'></a><a href='#L209'>209</a>
275
+ <a name='L210'></a><a href='#L210'>210</a>
276
+ <a name='L211'></a><a href='#L211'>211</a>
277
+ <a name='L212'></a><a href='#L212'>212</a>
278
+ <a name='L213'></a><a href='#L213'>213</a>
279
+ <a name='L214'></a><a href='#L214'>214</a>
280
+ <a name='L215'></a><a href='#L215'>215</a>
281
+ <a name='L216'></a><a href='#L216'>216</a>
282
+ <a name='L217'></a><a href='#L217'>217</a>
283
+ <a name='L218'></a><a href='#L218'>218</a>
284
+ <a name='L219'></a><a href='#L219'>219</a>
285
+ <a name='L220'></a><a href='#L220'>220</a>
286
+ <a name='L221'></a><a href='#L221'>221</a>
287
+ <a name='L222'></a><a href='#L222'>222</a>
288
+ <a name='L223'></a><a href='#L223'>223</a>
289
+ <a name='L224'></a><a href='#L224'>224</a>
290
+ <a name='L225'></a><a href='#L225'>225</a>
291
+ <a name='L226'></a><a href='#L226'>226</a>
292
+ <a name='L227'></a><a href='#L227'>227</a>
293
+ <a name='L228'></a><a href='#L228'>228</a>
294
+ <a name='L229'></a><a href='#L229'>229</a>
295
+ <a name='L230'></a><a href='#L230'>230</a>
296
+ <a name='L231'></a><a href='#L231'>231</a>
297
+ <a name='L232'></a><a href='#L232'>232</a>
298
+ <a name='L233'></a><a href='#L233'>233</a>
299
+ <a name='L234'></a><a href='#L234'>234</a>
300
+ <a name='L235'></a><a href='#L235'>235</a>
301
+ <a name='L236'></a><a href='#L236'>236</a>
302
+ <a name='L237'></a><a href='#L237'>237</a>
303
+ <a name='L238'></a><a href='#L238'>238</a>
304
+ <a name='L239'></a><a href='#L239'>239</a>
305
+ <a name='L240'></a><a href='#L240'>240</a>
306
+ <a name='L241'></a><a href='#L241'>241</a>
307
+ <a name='L242'></a><a href='#L242'>242</a>
308
+ <a name='L243'></a><a href='#L243'>243</a>
309
+ <a name='L244'></a><a href='#L244'>244</a>
310
+ <a name='L245'></a><a href='#L245'>245</a>
311
+ <a name='L246'></a><a href='#L246'>246</a>
312
+ <a name='L247'></a><a href='#L247'>247</a>
313
+ <a name='L248'></a><a href='#L248'>248</a>
314
+ <a name='L249'></a><a href='#L249'>249</a>
315
+ <a name='L250'></a><a href='#L250'>250</a>
316
+ <a name='L251'></a><a href='#L251'>251</a>
317
+ <a name='L252'></a><a href='#L252'>252</a>
318
+ <a name='L253'></a><a href='#L253'>253</a>
319
+ <a name='L254'></a><a href='#L254'>254</a>
320
+ <a name='L255'></a><a href='#L255'>255</a>
321
+ <a name='L256'></a><a href='#L256'>256</a>
322
+ <a name='L257'></a><a href='#L257'>257</a>
323
+ <a name='L258'></a><a href='#L258'>258</a>
324
+ <a name='L259'></a><a href='#L259'>259</a>
325
+ <a name='L260'></a><a href='#L260'>260</a>
326
+ <a name='L261'></a><a href='#L261'>261</a>
327
+ <a name='L262'></a><a href='#L262'>262</a>
328
+ <a name='L263'></a><a href='#L263'>263</a>
329
+ <a name='L264'></a><a href='#L264'>264</a>
330
+ <a name='L265'></a><a href='#L265'>265</a>
331
+ <a name='L266'></a><a href='#L266'>266</a>
332
+ <a name='L267'></a><a href='#L267'>267</a>
333
+ <a name='L268'></a><a href='#L268'>268</a>
334
+ <a name='L269'></a><a href='#L269'>269</a>
335
+ <a name='L270'></a><a href='#L270'>270</a>
336
+ <a name='L271'></a><a href='#L271'>271</a>
337
+ <a name='L272'></a><a href='#L272'>272</a>
338
+ <a name='L273'></a><a href='#L273'>273</a>
339
+ <a name='L274'></a><a href='#L274'>274</a>
340
+ <a name='L275'></a><a href='#L275'>275</a>
341
+ <a name='L276'></a><a href='#L276'>276</a>
342
+ <a name='L277'></a><a href='#L277'>277</a>
343
+ <a name='L278'></a><a href='#L278'>278</a>
344
+ <a name='L279'></a><a href='#L279'>279</a>
345
+ <a name='L280'></a><a href='#L280'>280</a>
346
+ <a name='L281'></a><a href='#L281'>281</a>
347
+ <a name='L282'></a><a href='#L282'>282</a>
348
+ <a name='L283'></a><a href='#L283'>283</a>
349
+ <a name='L284'></a><a href='#L284'>284</a>
350
+ <a name='L285'></a><a href='#L285'>285</a>
351
+ <a name='L286'></a><a href='#L286'>286</a>
352
+ <a name='L287'></a><a href='#L287'>287</a>
353
+ <a name='L288'></a><a href='#L288'>288</a>
354
+ <a name='L289'></a><a href='#L289'>289</a>
355
+ <a name='L290'></a><a href='#L290'>290</a>
356
+ <a name='L291'></a><a href='#L291'>291</a>
357
+ <a name='L292'></a><a href='#L292'>292</a>
358
+ <a name='L293'></a><a href='#L293'>293</a>
359
+ <a name='L294'></a><a href='#L294'>294</a>
360
+ <a name='L295'></a><a href='#L295'>295</a>
361
+ <a name='L296'></a><a href='#L296'>296</a>
362
+ <a name='L297'></a><a href='#L297'>297</a>
363
+ <a name='L298'></a><a href='#L298'>298</a>
364
+ <a name='L299'></a><a href='#L299'>299</a>
365
+ <a name='L300'></a><a href='#L300'>300</a>
366
+ <a name='L301'></a><a href='#L301'>301</a>
367
+ <a name='L302'></a><a href='#L302'>302</a>
368
+ <a name='L303'></a><a href='#L303'>303</a>
369
+ <a name='L304'></a><a href='#L304'>304</a>
370
+ <a name='L305'></a><a href='#L305'>305</a>
371
+ <a name='L306'></a><a href='#L306'>306</a>
372
+ <a name='L307'></a><a href='#L307'>307</a>
373
+ <a name='L308'></a><a href='#L308'>308</a>
374
+ <a name='L309'></a><a href='#L309'>309</a>
375
+ <a name='L310'></a><a href='#L310'>310</a>
376
+ <a name='L311'></a><a href='#L311'>311</a>
377
+ <a name='L312'></a><a href='#L312'>312</a>
378
+ <a name='L313'></a><a href='#L313'>313</a>
379
+ <a name='L314'></a><a href='#L314'>314</a>
380
+ <a name='L315'></a><a href='#L315'>315</a>
381
+ <a name='L316'></a><a href='#L316'>316</a>
382
+ <a name='L317'></a><a href='#L317'>317</a>
383
+ <a name='L318'></a><a href='#L318'>318</a>
384
+ <a name='L319'></a><a href='#L319'>319</a>
385
+ <a name='L320'></a><a href='#L320'>320</a>
386
+ <a name='L321'></a><a href='#L321'>321</a>
387
+ <a name='L322'></a><a href='#L322'>322</a>
388
+ <a name='L323'></a><a href='#L323'>323</a>
389
+ <a name='L324'></a><a href='#L324'>324</a>
390
+ <a name='L325'></a><a href='#L325'>325</a>
391
+ <a name='L326'></a><a href='#L326'>326</a>
392
+ <a name='L327'></a><a href='#L327'>327</a>
393
+ <a name='L328'></a><a href='#L328'>328</a>
394
+ <a name='L329'></a><a href='#L329'>329</a>
395
+ <a name='L330'></a><a href='#L330'>330</a>
396
+ <a name='L331'></a><a href='#L331'>331</a>
397
+ <a name='L332'></a><a href='#L332'>332</a>
398
+ <a name='L333'></a><a href='#L333'>333</a>
399
+ <a name='L334'></a><a href='#L334'>334</a>
400
+ <a name='L335'></a><a href='#L335'>335</a>
401
+ <a name='L336'></a><a href='#L336'>336</a>
402
+ <a name='L337'></a><a href='#L337'>337</a>
403
+ <a name='L338'></a><a href='#L338'>338</a>
404
+ <a name='L339'></a><a href='#L339'>339</a>
405
+ <a name='L340'></a><a href='#L340'>340</a>
406
+ <a name='L341'></a><a href='#L341'>341</a>
407
+ <a name='L342'></a><a href='#L342'>342</a>
408
+ <a name='L343'></a><a href='#L343'>343</a>
409
+ <a name='L344'></a><a href='#L344'>344</a>
410
+ <a name='L345'></a><a href='#L345'>345</a>
411
+ <a name='L346'></a><a href='#L346'>346</a>
412
+ <a name='L347'></a><a href='#L347'>347</a>
413
+ <a name='L348'></a><a href='#L348'>348</a>
414
+ <a name='L349'></a><a href='#L349'>349</a>
415
+ <a name='L350'></a><a href='#L350'>350</a>
416
+ <a name='L351'></a><a href='#L351'>351</a>
417
+ <a name='L352'></a><a href='#L352'>352</a></td><td class="line-coverage quiet"><span class="cline-any cline-yes">1x</span>
418
+ <span class="cline-any cline-yes">1x</span>
419
+ <span class="cline-any cline-yes">1x</span>
420
+ <span class="cline-any cline-neutral">&nbsp;</span>
421
+ <span class="cline-any cline-yes">1x</span>
422
+ <span class="cline-any cline-yes">1x</span>
423
+ <span class="cline-any cline-yes">1x</span>
424
+ <span class="cline-any cline-neutral">&nbsp;</span>
425
+ <span class="cline-any cline-neutral">&nbsp;</span>
426
+ <span class="cline-any cline-neutral">&nbsp;</span>
427
+ <span class="cline-any cline-neutral">&nbsp;</span>
428
+ <span class="cline-any cline-neutral">&nbsp;</span>
429
+ <span class="cline-any cline-neutral">&nbsp;</span>
430
+ <span class="cline-any cline-neutral">&nbsp;</span>
431
+ <span class="cline-any cline-neutral">&nbsp;</span>
432
+ <span class="cline-any cline-neutral">&nbsp;</span>
433
+ <span class="cline-any cline-neutral">&nbsp;</span>
434
+ <span class="cline-any cline-neutral">&nbsp;</span>
435
+ <span class="cline-any cline-neutral">&nbsp;</span>
436
+ <span class="cline-any cline-neutral">&nbsp;</span>
437
+ <span class="cline-any cline-neutral">&nbsp;</span>
438
+ <span class="cline-any cline-neutral">&nbsp;</span>
439
+ <span class="cline-any cline-neutral">&nbsp;</span>
440
+ <span class="cline-any cline-neutral">&nbsp;</span>
441
+ <span class="cline-any cline-neutral">&nbsp;</span>
442
+ <span class="cline-any cline-neutral">&nbsp;</span>
443
+ <span class="cline-any cline-neutral">&nbsp;</span>
444
+ <span class="cline-any cline-neutral">&nbsp;</span>
445
+ <span class="cline-any cline-neutral">&nbsp;</span>
446
+ <span class="cline-any cline-neutral">&nbsp;</span>
447
+ <span class="cline-any cline-neutral">&nbsp;</span>
448
+ <span class="cline-any cline-neutral">&nbsp;</span>
449
+ <span class="cline-any cline-neutral">&nbsp;</span>
450
+ <span class="cline-any cline-neutral">&nbsp;</span>
451
+ <span class="cline-any cline-neutral">&nbsp;</span>
452
+ <span class="cline-any cline-neutral">&nbsp;</span>
453
+ <span class="cline-any cline-neutral">&nbsp;</span>
454
+ <span class="cline-any cline-neutral">&nbsp;</span>
455
+ <span class="cline-any cline-neutral">&nbsp;</span>
456
+ <span class="cline-any cline-neutral">&nbsp;</span>
457
+ <span class="cline-any cline-neutral">&nbsp;</span>
458
+ <span class="cline-any cline-neutral">&nbsp;</span>
459
+ <span class="cline-any cline-neutral">&nbsp;</span>
460
+ <span class="cline-any cline-neutral">&nbsp;</span>
461
+ <span class="cline-any cline-neutral">&nbsp;</span>
462
+ <span class="cline-any cline-neutral">&nbsp;</span>
463
+ <span class="cline-any cline-neutral">&nbsp;</span>
464
+ <span class="cline-any cline-neutral">&nbsp;</span>
465
+ <span class="cline-any cline-no">&nbsp;</span>
466
+ <span class="cline-any cline-no">&nbsp;</span>
467
+ <span class="cline-any cline-no">&nbsp;</span>
468
+ <span class="cline-any cline-no">&nbsp;</span>
469
+ <span class="cline-any cline-no">&nbsp;</span>
470
+ <span class="cline-any cline-no">&nbsp;</span>
471
+ <span class="cline-any cline-no">&nbsp;</span>
472
+ <span class="cline-any cline-no">&nbsp;</span>
473
+ <span class="cline-any cline-no">&nbsp;</span>
474
+ <span class="cline-any cline-no">&nbsp;</span>
475
+ <span class="cline-any cline-no">&nbsp;</span>
476
+ <span class="cline-any cline-neutral">&nbsp;</span>
477
+ <span class="cline-any cline-neutral">&nbsp;</span>
478
+ <span class="cline-any cline-neutral">&nbsp;</span>
479
+ <span class="cline-any cline-neutral">&nbsp;</span>
480
+ <span class="cline-any cline-no">&nbsp;</span>
481
+ <span class="cline-any cline-no">&nbsp;</span>
482
+ <span class="cline-any cline-no">&nbsp;</span>
483
+ <span class="cline-any cline-no">&nbsp;</span>
484
+ <span class="cline-any cline-no">&nbsp;</span>
485
+ <span class="cline-any cline-no">&nbsp;</span>
486
+ <span class="cline-any cline-no">&nbsp;</span>
487
+ <span class="cline-any cline-no">&nbsp;</span>
488
+ <span class="cline-any cline-no">&nbsp;</span>
489
+ <span class="cline-any cline-no">&nbsp;</span>
490
+ <span class="cline-any cline-neutral">&nbsp;</span>
491
+ <span class="cline-any cline-neutral">&nbsp;</span>
492
+ <span class="cline-any cline-neutral">&nbsp;</span>
493
+ <span class="cline-any cline-neutral">&nbsp;</span>
494
+ <span class="cline-any cline-neutral">&nbsp;</span>
495
+ <span class="cline-any cline-neutral">&nbsp;</span>
496
+ <span class="cline-any cline-no">&nbsp;</span>
497
+ <span class="cline-any cline-no">&nbsp;</span>
498
+ <span class="cline-any cline-no">&nbsp;</span>
499
+ <span class="cline-any cline-no">&nbsp;</span>
500
+ <span class="cline-any cline-no">&nbsp;</span>
501
+ <span class="cline-any cline-no">&nbsp;</span>
502
+ <span class="cline-any cline-no">&nbsp;</span>
503
+ <span class="cline-any cline-no">&nbsp;</span>
504
+ <span class="cline-any cline-no">&nbsp;</span>
505
+ <span class="cline-any cline-no">&nbsp;</span>
506
+ <span class="cline-any cline-no">&nbsp;</span>
507
+ <span class="cline-any cline-neutral">&nbsp;</span>
508
+ <span class="cline-any cline-neutral">&nbsp;</span>
509
+ <span class="cline-any cline-neutral">&nbsp;</span>
510
+ <span class="cline-any cline-neutral">&nbsp;</span>
511
+ <span class="cline-any cline-neutral">&nbsp;</span>
512
+ <span class="cline-any cline-neutral">&nbsp;</span>
513
+ <span class="cline-any cline-neutral">&nbsp;</span>
514
+ <span class="cline-any cline-neutral">&nbsp;</span>
515
+ <span class="cline-any cline-neutral">&nbsp;</span>
516
+ <span class="cline-any cline-neutral">&nbsp;</span>
517
+ <span class="cline-any cline-yes">2x</span>
518
+ <span class="cline-any cline-yes">2x</span>
519
+ <span class="cline-any cline-yes">2x</span>
520
+ <span class="cline-any cline-yes">2x</span>
521
+ <span class="cline-any cline-yes">2x</span>
522
+ <span class="cline-any cline-yes">2x</span>
523
+ <span class="cline-any cline-yes">2x</span>
524
+ <span class="cline-any cline-yes">2x</span>
525
+ <span class="cline-any cline-yes">2x</span>
526
+ <span class="cline-any cline-neutral">&nbsp;</span>
527
+ <span class="cline-any cline-yes">2x</span>
528
+ <span class="cline-any cline-yes">2x</span>
529
+ <span class="cline-any cline-neutral">&nbsp;</span>
530
+ <span class="cline-any cline-yes">2x</span>
531
+ <span class="cline-any cline-yes">2x</span>
532
+ <span class="cline-any cline-yes">2x</span>
533
+ <span class="cline-any cline-yes">2x</span>
534
+ <span class="cline-any cline-yes">2x</span>
535
+ <span class="cline-any cline-yes">2x</span>
536
+ <span class="cline-any cline-yes">2x</span>
537
+ <span class="cline-any cline-neutral">&nbsp;</span>
538
+ <span class="cline-any cline-yes">2x</span>
539
+ <span class="cline-any cline-no">&nbsp;</span>
540
+ <span class="cline-any cline-no">&nbsp;</span>
541
+ <span class="cline-any cline-no">&nbsp;</span>
542
+ <span class="cline-any cline-no">&nbsp;</span>
543
+ <span class="cline-any cline-neutral">&nbsp;</span>
544
+ <span class="cline-any cline-neutral">&nbsp;</span>
545
+ <span class="cline-any cline-yes">2x</span>
546
+ <span class="cline-any cline-neutral">&nbsp;</span>
547
+ <span class="cline-any cline-neutral">&nbsp;</span>
548
+ <span class="cline-any cline-yes">2x</span>
549
+ <span class="cline-any cline-no">&nbsp;</span>
550
+ <span class="cline-any cline-yes">2x</span>
551
+ <span class="cline-any cline-neutral">&nbsp;</span>
552
+ <span class="cline-any cline-neutral">&nbsp;</span>
553
+ <span class="cline-any cline-yes">2x</span>
554
+ <span class="cline-any cline-neutral">&nbsp;</span>
555
+ <span class="cline-any cline-neutral">&nbsp;</span>
556
+ <span class="cline-any cline-yes">2x</span>
557
+ <span class="cline-any cline-yes">2x</span>
558
+ <span class="cline-any cline-yes">2x</span>
559
+ <span class="cline-any cline-neutral">&nbsp;</span>
560
+ <span class="cline-any cline-neutral">&nbsp;</span>
561
+ <span class="cline-any cline-yes">2x</span>
562
+ <span class="cline-any cline-yes">2x</span>
563
+ <span class="cline-any cline-yes">2x</span>
564
+ <span class="cline-any cline-yes">2x</span>
565
+ <span class="cline-any cline-yes">2x</span>
566
+ <span class="cline-any cline-yes">2x</span>
567
+ <span class="cline-any cline-yes">2x</span>
568
+ <span class="cline-any cline-yes">2x</span>
569
+ <span class="cline-any cline-yes">2x</span>
570
+ <span class="cline-any cline-yes">2x</span>
571
+ <span class="cline-any cline-yes">2x</span>
572
+ <span class="cline-any cline-yes">2x</span>
573
+ <span class="cline-any cline-yes">2x</span>
574
+ <span class="cline-any cline-yes">2x</span>
575
+ <span class="cline-any cline-no">&nbsp;</span>
576
+ <span class="cline-any cline-no">&nbsp;</span>
577
+ <span class="cline-any cline-no">&nbsp;</span>
578
+ <span class="cline-any cline-no">&nbsp;</span>
579
+ <span class="cline-any cline-yes">2x</span>
580
+ <span class="cline-any cline-yes">1x</span>
581
+ <span class="cline-any cline-yes">1x</span>
582
+ <span class="cline-any cline-yes">1x</span>
583
+ <span class="cline-any cline-yes">1x</span>
584
+ <span class="cline-any cline-yes">2x</span>
585
+ <span class="cline-any cline-no">&nbsp;</span>
586
+ <span class="cline-any cline-no">&nbsp;</span>
587
+ <span class="cline-any cline-no">&nbsp;</span>
588
+ <span class="cline-any cline-no">&nbsp;</span>
589
+ <span class="cline-any cline-yes">2x</span>
590
+ <span class="cline-any cline-yes">1x</span>
591
+ <span class="cline-any cline-yes">1x</span>
592
+ <span class="cline-any cline-yes">1x</span>
593
+ <span class="cline-any cline-yes">1x</span>
594
+ <span class="cline-any cline-yes">2x</span>
595
+ <span class="cline-any cline-yes">2x</span>
596
+ <span class="cline-any cline-neutral">&nbsp;</span>
597
+ <span class="cline-any cline-yes">2x</span>
598
+ <span class="cline-any cline-yes">2x</span>
599
+ <span class="cline-any cline-yes">2x</span>
600
+ <span class="cline-any cline-yes">2x</span>
601
+ <span class="cline-any cline-yes">2x</span>
602
+ <span class="cline-any cline-yes">2x</span>
603
+ <span class="cline-any cline-yes">2x</span>
604
+ <span class="cline-any cline-yes">2x</span>
605
+ <span class="cline-any cline-yes">2x</span>
606
+ <span class="cline-any cline-yes">2x</span>
607
+ <span class="cline-any cline-yes">2x</span>
608
+ <span class="cline-any cline-no">&nbsp;</span>
609
+ <span class="cline-any cline-no">&nbsp;</span>
610
+ <span class="cline-any cline-no">&nbsp;</span>
611
+ <span class="cline-any cline-no">&nbsp;</span>
612
+ <span class="cline-any cline-no">&nbsp;</span>
613
+ <span class="cline-any cline-no">&nbsp;</span>
614
+ <span class="cline-any cline-no">&nbsp;</span>
615
+ <span class="cline-any cline-no">&nbsp;</span>
616
+ <span class="cline-any cline-no">&nbsp;</span>
617
+ <span class="cline-any cline-no">&nbsp;</span>
618
+ <span class="cline-any cline-yes">2x</span>
619
+ <span class="cline-any cline-neutral">&nbsp;</span>
620
+ <span class="cline-any cline-neutral">&nbsp;</span>
621
+ <span class="cline-any cline-neutral">&nbsp;</span>
622
+ <span class="cline-any cline-neutral">&nbsp;</span>
623
+ <span class="cline-any cline-neutral">&nbsp;</span>
624
+ <span class="cline-any cline-neutral">&nbsp;</span>
625
+ <span class="cline-any cline-neutral">&nbsp;</span>
626
+ <span class="cline-any cline-no">&nbsp;</span>
627
+ <span class="cline-any cline-no">&nbsp;</span>
628
+ <span class="cline-any cline-no">&nbsp;</span>
629
+ <span class="cline-any cline-no">&nbsp;</span>
630
+ <span class="cline-any cline-no">&nbsp;</span>
631
+ <span class="cline-any cline-no">&nbsp;</span>
632
+ <span class="cline-any cline-neutral">&nbsp;</span>
633
+ <span class="cline-any cline-no">&nbsp;</span>
634
+ <span class="cline-any cline-no">&nbsp;</span>
635
+ <span class="cline-any cline-no">&nbsp;</span>
636
+ <span class="cline-any cline-no">&nbsp;</span>
637
+ <span class="cline-any cline-neutral">&nbsp;</span>
638
+ <span class="cline-any cline-no">&nbsp;</span>
639
+ <span class="cline-any cline-no">&nbsp;</span>
640
+ <span class="cline-any cline-no">&nbsp;</span>
641
+ <span class="cline-any cline-no">&nbsp;</span>
642
+ <span class="cline-any cline-no">&nbsp;</span>
643
+ <span class="cline-any cline-no">&nbsp;</span>
644
+ <span class="cline-any cline-neutral">&nbsp;</span>
645
+ <span class="cline-any cline-no">&nbsp;</span>
646
+ <span class="cline-any cline-no">&nbsp;</span>
647
+ <span class="cline-any cline-neutral">&nbsp;</span>
648
+ <span class="cline-any cline-neutral">&nbsp;</span>
649
+ <span class="cline-any cline-no">&nbsp;</span>
650
+ <span class="cline-any cline-no">&nbsp;</span>
651
+ <span class="cline-any cline-no">&nbsp;</span>
652
+ <span class="cline-any cline-no">&nbsp;</span>
653
+ <span class="cline-any cline-no">&nbsp;</span>
654
+ <span class="cline-any cline-no">&nbsp;</span>
655
+ <span class="cline-any cline-no">&nbsp;</span>
656
+ <span class="cline-any cline-no">&nbsp;</span>
657
+ <span class="cline-any cline-no">&nbsp;</span>
658
+ <span class="cline-any cline-no">&nbsp;</span>
659
+ <span class="cline-any cline-no">&nbsp;</span>
660
+ <span class="cline-any cline-no">&nbsp;</span>
661
+ <span class="cline-any cline-no">&nbsp;</span>
662
+ <span class="cline-any cline-no">&nbsp;</span>
663
+ <span class="cline-any cline-no">&nbsp;</span>
664
+ <span class="cline-any cline-no">&nbsp;</span>
665
+ <span class="cline-any cline-no">&nbsp;</span>
666
+ <span class="cline-any cline-no">&nbsp;</span>
667
+ <span class="cline-any cline-neutral">&nbsp;</span>
668
+ <span class="cline-any cline-no">&nbsp;</span>
669
+ <span class="cline-any cline-no">&nbsp;</span>
670
+ <span class="cline-any cline-no">&nbsp;</span>
671
+ <span class="cline-any cline-no">&nbsp;</span>
672
+ <span class="cline-any cline-neutral">&nbsp;</span>
673
+ <span class="cline-any cline-no">&nbsp;</span>
674
+ <span class="cline-any cline-no">&nbsp;</span>
675
+ <span class="cline-any cline-no">&nbsp;</span>
676
+ <span class="cline-any cline-no">&nbsp;</span>
677
+ <span class="cline-any cline-neutral">&nbsp;</span>
678
+ <span class="cline-any cline-no">&nbsp;</span>
679
+ <span class="cline-any cline-no">&nbsp;</span>
680
+ <span class="cline-any cline-no">&nbsp;</span>
681
+ <span class="cline-any cline-neutral">&nbsp;</span>
682
+ <span class="cline-any cline-no">&nbsp;</span>
683
+ <span class="cline-any cline-no">&nbsp;</span>
684
+ <span class="cline-any cline-no">&nbsp;</span>
685
+ <span class="cline-any cline-no">&nbsp;</span>
686
+ <span class="cline-any cline-no">&nbsp;</span>
687
+ <span class="cline-any cline-no">&nbsp;</span>
688
+ <span class="cline-any cline-no">&nbsp;</span>
689
+ <span class="cline-any cline-no">&nbsp;</span>
690
+ <span class="cline-any cline-no">&nbsp;</span>
691
+ <span class="cline-any cline-no">&nbsp;</span>
692
+ <span class="cline-any cline-no">&nbsp;</span>
693
+ <span class="cline-any cline-no">&nbsp;</span>
694
+ <span class="cline-any cline-neutral">&nbsp;</span>
695
+ <span class="cline-any cline-neutral">&nbsp;</span>
696
+ <span class="cline-any cline-neutral">&nbsp;</span>
697
+ <span class="cline-any cline-neutral">&nbsp;</span>
698
+ <span class="cline-any cline-neutral">&nbsp;</span>
699
+ <span class="cline-any cline-neutral">&nbsp;</span>
700
+ <span class="cline-any cline-no">&nbsp;</span>
701
+ <span class="cline-any cline-no">&nbsp;</span>
702
+ <span class="cline-any cline-no">&nbsp;</span>
703
+ <span class="cline-any cline-no">&nbsp;</span>
704
+ <span class="cline-any cline-no">&nbsp;</span>
705
+ <span class="cline-any cline-neutral">&nbsp;</span>
706
+ <span class="cline-any cline-neutral">&nbsp;</span>
707
+ <span class="cline-any cline-neutral">&nbsp;</span>
708
+ <span class="cline-any cline-neutral">&nbsp;</span>
709
+ <span class="cline-any cline-neutral">&nbsp;</span>
710
+ <span class="cline-any cline-neutral">&nbsp;</span>
711
+ <span class="cline-any cline-neutral">&nbsp;</span>
712
+ <span class="cline-any cline-no">&nbsp;</span>
713
+ <span class="cline-any cline-no">&nbsp;</span>
714
+ <span class="cline-any cline-no">&nbsp;</span>
715
+ <span class="cline-any cline-no">&nbsp;</span>
716
+ <span class="cline-any cline-no">&nbsp;</span>
717
+ <span class="cline-any cline-no">&nbsp;</span>
718
+ <span class="cline-any cline-no">&nbsp;</span>
719
+ <span class="cline-any cline-no">&nbsp;</span>
720
+ <span class="cline-any cline-no">&nbsp;</span>
721
+ <span class="cline-any cline-no">&nbsp;</span>
722
+ <span class="cline-any cline-no">&nbsp;</span>
723
+ <span class="cline-any cline-no">&nbsp;</span>
724
+ <span class="cline-any cline-no">&nbsp;</span>
725
+ <span class="cline-any cline-no">&nbsp;</span>
726
+ <span class="cline-any cline-no">&nbsp;</span>
727
+ <span class="cline-any cline-no">&nbsp;</span>
728
+ <span class="cline-any cline-no">&nbsp;</span>
729
+ <span class="cline-any cline-no">&nbsp;</span>
730
+ <span class="cline-any cline-no">&nbsp;</span>
731
+ <span class="cline-any cline-no">&nbsp;</span>
732
+ <span class="cline-any cline-no">&nbsp;</span>
733
+ <span class="cline-any cline-no">&nbsp;</span>
734
+ <span class="cline-any cline-neutral">&nbsp;</span>
735
+ <span class="cline-any cline-neutral">&nbsp;</span>
736
+ <span class="cline-any cline-neutral">&nbsp;</span>
737
+ <span class="cline-any cline-neutral">&nbsp;</span>
738
+ <span class="cline-any cline-neutral">&nbsp;</span>
739
+ <span class="cline-any cline-neutral">&nbsp;</span>
740
+ <span class="cline-any cline-neutral">&nbsp;</span>
741
+ <span class="cline-any cline-neutral">&nbsp;</span>
742
+ <span class="cline-any cline-neutral">&nbsp;</span>
743
+ <span class="cline-any cline-neutral">&nbsp;</span>
744
+ <span class="cline-any cline-yes">2x</span>
745
+ <span class="cline-any cline-yes">2x</span>
746
+ <span class="cline-any cline-yes">2x</span>
747
+ <span class="cline-any cline-yes">2x</span>
748
+ <span class="cline-any cline-yes">2x</span>
749
+ <span class="cline-any cline-yes">2x</span>
750
+ <span class="cline-any cline-yes">2x</span>
751
+ <span class="cline-any cline-yes">2x</span>
752
+ <span class="cline-any cline-yes">2x</span>
753
+ <span class="cline-any cline-yes">2x</span>
754
+ <span class="cline-any cline-yes">2x</span>
755
+ <span class="cline-any cline-yes">2x</span>
756
+ <span class="cline-any cline-yes">2x</span>
757
+ <span class="cline-any cline-yes">2x</span>
758
+ <span class="cline-any cline-no">&nbsp;</span>
759
+ <span class="cline-any cline-no">&nbsp;</span>
760
+ <span class="cline-any cline-no">&nbsp;</span>
761
+ <span class="cline-any cline-no">&nbsp;</span>
762
+ <span class="cline-any cline-no">&nbsp;</span>
763
+ <span class="cline-any cline-no">&nbsp;</span>
764
+ <span class="cline-any cline-no">&nbsp;</span>
765
+ <span class="cline-any cline-no">&nbsp;</span>
766
+ <span class="cline-any cline-no">&nbsp;</span>
767
+ <span class="cline-any cline-no">&nbsp;</span>
768
+ <span class="cline-any cline-yes">2x</span></td><td class="text"><pre class="prettyprint lang-js">import { TRPCError } from "@trpc/server";
769
+ import { v4 as uuidv4 } from "uuid";
770
+ import { getSignedUrl, objectExists } from "./googleCloudStorage.js";
771
+ import { generateMediaThumbnail } from "./thumbnailGenerator.js";
772
+ import { prisma } from "./prisma.js";
773
+ import { logger } from "../utils/logger.js";
774
+ import { env } from "./config/env.js";
775
+ &nbsp;
776
+ export interface FileData {
777
+ name: string;
778
+ type: string;
779
+ size: number;
780
+ // No data field - for direct file uploads
781
+ }
782
+ &nbsp;
783
+ export interface DirectFileData {
784
+ name: string;
785
+ type: string;
786
+ size: number;
787
+ // No data field - for direct file uploads
788
+ }
789
+ &nbsp;
790
+ export interface UploadedFile {
791
+ id: string;
792
+ name: string;
793
+ type: string;
794
+ size: number;
795
+ path: string;
796
+ thumbnailId?: string;
797
+ }
798
+ &nbsp;
799
+ export interface DirectUploadFile {
800
+ id: string;
801
+ name: string;
802
+ type: string;
803
+ size: number;
804
+ path: string;
805
+ uploadUrl: string;
806
+ uploadExpiresAt: Date;
807
+ uploadSessionId: string;
808
+ }
809
+ &nbsp;
810
+ // DEPRECATED: These functions are no longer used - files are uploaded directly to GCS
811
+ // Use createDirectUploadFile() and createDirectUploadFiles() instead
812
+ &nbsp;
813
+ /**
814
+ * @deprecated Use createDirectUploadFile instead
815
+ */
816
+ <span class="cstat-no" title="statement not covered" ><span class="fstat-no" title="function not covered" >export async function uploadFile(</span></span>
817
+ <span class="cstat-no" title="statement not covered" > file: FileData,</span>
818
+ <span class="cstat-no" title="statement not covered" > userId: string,</span>
819
+ <span class="cstat-no" title="statement not covered" > directory?: string,</span>
820
+ <span class="cstat-no" title="statement not covered" > assignmentId?: string</span>
821
+ <span class="cstat-no" title="statement not covered" >): Promise&lt;UploadedFile&gt; {</span>
822
+ <span class="cstat-no" title="statement not covered" > throw new TRPCError({</span>
823
+ <span class="cstat-no" title="statement not covered" > code: 'NOT_IMPLEMENTED',</span>
824
+ <span class="cstat-no" title="statement not covered" > message: 'uploadFile is deprecated. Use createDirectUploadFile instead.',</span>
825
+ <span class="cstat-no" title="statement not covered" > });</span>
826
+ <span class="cstat-no" title="statement not covered" >}</span>
827
+ &nbsp;
828
+ /**
829
+ * @deprecated Use createDirectUploadFiles instead
830
+ */
831
+ <span class="cstat-no" title="statement not covered" ><span class="fstat-no" title="function not covered" >export async function uploadFiles(</span></span>
832
+ <span class="cstat-no" title="statement not covered" > files: FileData[], </span>
833
+ <span class="cstat-no" title="statement not covered" > userId: string,</span>
834
+ <span class="cstat-no" title="statement not covered" > directory?: string</span>
835
+ <span class="cstat-no" title="statement not covered" >): Promise&lt;UploadedFile[]&gt; {</span>
836
+ <span class="cstat-no" title="statement not covered" > throw new TRPCError({</span>
837
+ <span class="cstat-no" title="statement not covered" > code: 'NOT_IMPLEMENTED',</span>
838
+ <span class="cstat-no" title="statement not covered" > message: 'uploadFiles is deprecated. Use createDirectUploadFiles instead.',</span>
839
+ <span class="cstat-no" title="statement not covered" > });</span>
840
+ <span class="cstat-no" title="statement not covered" >}</span>
841
+ &nbsp;
842
+ /**
843
+ * Gets a signed URL for a file
844
+ * @param filePath The path of the file in Google Cloud Storage
845
+ * @returns The signed URL
846
+ */
847
+ <span class="cstat-no" title="statement not covered" ><span class="fstat-no" title="function not covered" >export async function getFileUrl(filePath: string): Promise&lt;string&gt; {</span></span>
848
+ <span class="cstat-no" title="statement not covered" > try {</span>
849
+ <span class="cstat-no" title="statement not covered" > return await getSignedUrl(filePath);</span>
850
+ <span class="cstat-no" title="statement not covered" > } catch (error) {</span>
851
+ <span class="cstat-no" title="statement not covered" > console.error('Error getting signed URL:', error);</span>
852
+ <span class="cstat-no" title="statement not covered" > throw new TRPCError({</span>
853
+ <span class="cstat-no" title="statement not covered" > code: 'INTERNAL_SERVER_ERROR',</span>
854
+ <span class="cstat-no" title="statement not covered" > message: 'Failed to get file URL',</span>
855
+ <span class="cstat-no" title="statement not covered" > });</span>
856
+ <span class="cstat-no" title="statement not covered" > }</span>
857
+ <span class="cstat-no" title="statement not covered" >}</span>
858
+ &nbsp;
859
+ /**
860
+ * Creates a file record for direct upload and generates signed URL
861
+ * @param file The file metadata (no base64 data)
862
+ * @param userId The ID of the user uploading the file
863
+ * @param directory Optional directory to store the file in
864
+ * @param assignmentId Optional assignment ID to associate the file with
865
+ * @param submissionId Optional submission ID to associate the file with
866
+ * @returns The direct upload file information with signed URL
867
+ */
868
+ export async function createDirectUploadFile(
869
+ file: DirectFileData,
870
+ userId: string,
871
+ directory?: string,
872
+ assignmentId?: string,
873
+ submissionId?: string,
874
+ announcementId?: string
875
+ ): Promise&lt;DirectUploadFile&gt; {
876
+ try {
877
+ // Validate file extension matches MIME type
878
+ const fileExtension = file.name.split('.').pop()?.toLowerCase();
879
+ const mimeType = file.type.toLowerCase();
880
+
881
+ const extensionMimeMap: Record&lt;string, string[]&gt; = {
882
+ 'jpg': ['image/jpeg'],
883
+ 'jpeg': ['image/jpeg'],
884
+ 'png': ['image/png'],
885
+ 'gif': ['image/gif'],
886
+ 'webp': ['image/webp']
887
+ };
888
+
889
+ if (fileExtension &amp;&amp; extensionMimeMap[fileExtension]) <span class="branch-0 cbranch-no" title="branch not covered" >{</span>
890
+ <span class="cstat-no" title="statement not covered" > if (!extensionMimeMap[fileExtension].includes(mimeType)) {</span>
891
+ <span class="cstat-no" title="statement not covered" > throw new Error(`File extension .${fileExtension} does not match MIME type ${mimeType}`);</span>
892
+ <span class="cstat-no" title="statement not covered" > }</span>
893
+ <span class="cstat-no" title="statement not covered" > }</span>
894
+
895
+ // Create a unique filename
896
+ const uniqueFilename = `${uuidv4()}.${fileExtension}`;
897
+
898
+ // Construct the full path
899
+ const filePath = <span class="branch-0 cbranch-no" title="branch not covered" >directory </span>
900
+ <span class="cstat-no" title="statement not covered" > ? `${directory}/${uniqueFilename}`</span>
901
+ : uniqueFilename;
902
+
903
+ // Generate upload session ID
904
+ const uploadSessionId = uuidv4();
905
+
906
+ // Generate backend proxy upload URL (not direct GCS)
907
+ const baseUrl = env.<span class="branch-0 cbranch-no" title="branch not covered" >BACKEND_URL || 'http://localhost:3001';</span>
908
+ const uploadUrl = `${baseUrl}/api/upload/${encodeURIComponent(filePath)}`;
909
+ const uploadExpiresAt = new Date(Date.now() + 15 * 60 * 1000); // 15 minutes from now
910
+
911
+ // Create file record in database with PENDING status
912
+ const fileRecord = await prisma.file.create({
913
+ data: {
914
+ name: file.name,
915
+ type: file.type,
916
+ size: file.size,
917
+ path: filePath,
918
+ uploadStatus: 'PENDING',
919
+ uploadUrl,
920
+ uploadExpiresAt,
921
+ uploadSessionId,
922
+ user: {
923
+ connect: { id: userId }
924
+ },
925
+ ...(<span class="branch-0 cbranch-no" title="branch not covered" >directory &amp;&amp; {</span>
926
+ <span class="cstat-no" title="statement not covered" > folder: {</span>
927
+ <span class="cstat-no" title="statement not covered" > connect: {id: directory},</span>
928
+ <span class="cstat-no" title="statement not covered" > },</span>
929
+ <span class="cstat-no" title="statement not covered" > }),</span>
930
+ ...(assignmentId &amp;&amp; {
931
+ assignment: {
932
+ connect: { id: assignmentId }
933
+ }
934
+ }),
935
+ ...(<span class="branch-0 cbranch-no" title="branch not covered" >submissionId &amp;&amp; {</span>
936
+ <span class="cstat-no" title="statement not covered" > submission: {</span>
937
+ <span class="cstat-no" title="statement not covered" > connect: { id: submissionId }</span>
938
+ <span class="cstat-no" title="statement not covered" > }</span>
939
+ <span class="cstat-no" title="statement not covered" > }),</span>
940
+ ...(announcementId &amp;&amp; {
941
+ announcement: {
942
+ connect: { id: announcementId }
943
+ }
944
+ })
945
+ },
946
+ });
947
+
948
+ return {
949
+ id: fileRecord.id,
950
+ name: file.name,
951
+ type: file.type,
952
+ size: file.size,
953
+ path: filePath,
954
+ uploadUrl,
955
+ uploadExpiresAt,
956
+ uploadSessionId
957
+ };
958
+ <span class="branch-0 cbranch-no" title="branch not covered" > } catch (error) {</span>
959
+ <span class="cstat-no" title="statement not covered" > logger.error('Error creating direct upload file:', {error: error instanceof Error ? {</span>
960
+ <span class="cstat-no" title="statement not covered" > name: error.name,</span>
961
+ <span class="cstat-no" title="statement not covered" > message: error.message,</span>
962
+ <span class="cstat-no" title="statement not covered" > stack: error.stack,</span>
963
+ <span class="cstat-no" title="statement not covered" > } : error});</span>
964
+ <span class="cstat-no" title="statement not covered" > throw new TRPCError({</span>
965
+ <span class="cstat-no" title="statement not covered" > code: 'INTERNAL_SERVER_ERROR',</span>
966
+ <span class="cstat-no" title="statement not covered" > message: 'Failed to create direct upload file',</span>
967
+ <span class="cstat-no" title="statement not covered" > });</span>
968
+ <span class="cstat-no" title="statement not covered" > }</span>
969
+ }
970
+ &nbsp;
971
+ /**
972
+ * Confirms a direct upload was successful
973
+ * @param fileId The ID of the file record
974
+ * @param uploadSuccess Whether the upload was successful
975
+ * @param errorMessage Optional error message if upload failed
976
+ */
977
+ <span class="cstat-no" title="statement not covered" ><span class="fstat-no" title="function not covered" >export async function confirmDirectUpload(</span></span>
978
+ <span class="cstat-no" title="statement not covered" > fileId: string,</span>
979
+ <span class="cstat-no" title="statement not covered" > uploadSuccess: boolean,</span>
980
+ <span class="cstat-no" title="statement not covered" > errorMessage?: string</span>
981
+ <span class="cstat-no" title="statement not covered" >): Promise&lt;void&gt; {</span>
982
+ <span class="cstat-no" title="statement not covered" > try {</span>
983
+ // First fetch the file record to get the object path
984
+ <span class="cstat-no" title="statement not covered" > const fileRecord = await prisma.file.findUnique({</span>
985
+ <span class="cstat-no" title="statement not covered" > where: { id: fileId },</span>
986
+ <span class="cstat-no" title="statement not covered" > select: { path: true }</span>
987
+ <span class="cstat-no" title="statement not covered" > });</span>
988
+ &nbsp;
989
+ <span class="cstat-no" title="statement not covered" > if (!fileRecord) {</span>
990
+ <span class="cstat-no" title="statement not covered" > throw new TRPCError({</span>
991
+ <span class="cstat-no" title="statement not covered" > code: 'NOT_FOUND',</span>
992
+ <span class="cstat-no" title="statement not covered" > message: 'File record not found',</span>
993
+ <span class="cstat-no" title="statement not covered" > });</span>
994
+ <span class="cstat-no" title="statement not covered" > }</span>
995
+ &nbsp;
996
+ <span class="cstat-no" title="statement not covered" > let actualUploadSuccess = uploadSuccess;</span>
997
+ <span class="cstat-no" title="statement not covered" > let actualErrorMessage = errorMessage;</span>
998
+ &nbsp;
999
+ // If uploadSuccess is true, verify the object actually exists in GCS
1000
+ <span class="cstat-no" title="statement not covered" > if (uploadSuccess) {</span>
1001
+ <span class="cstat-no" title="statement not covered" > try {</span>
1002
+ <span class="cstat-no" title="statement not covered" > const exists = await objectExists(env.GOOGLE_CLOUD_BUCKET_NAME!, fileRecord.path);</span>
1003
+ <span class="cstat-no" title="statement not covered" > if (!exists) {</span>
1004
+ <span class="cstat-no" title="statement not covered" > actualUploadSuccess = false;</span>
1005
+ <span class="cstat-no" title="statement not covered" > actualErrorMessage = 'File upload reported as successful but object not found in Google Cloud Storage';</span>
1006
+ <span class="cstat-no" title="statement not covered" > logger.error(`File upload verification failed for ${fileId}: object ${fileRecord.path} not found in GCS`);</span>
1007
+ <span class="cstat-no" title="statement not covered" > }</span>
1008
+ <span class="cstat-no" title="statement not covered" > } catch (error) {</span>
1009
+ <span class="cstat-no" title="statement not covered" > logger.error(`Error verifying file existence in GCS for ${fileId}:`, {error: error instanceof Error ? {</span>
1010
+ <span class="cstat-no" title="statement not covered" > name: error.name,</span>
1011
+ <span class="cstat-no" title="statement not covered" > message: error.message,</span>
1012
+ <span class="cstat-no" title="statement not covered" > stack: error.stack,</span>
1013
+ <span class="cstat-no" title="statement not covered" > } : error});</span>
1014
+ <span class="cstat-no" title="statement not covered" > actualUploadSuccess = false;</span>
1015
+ <span class="cstat-no" title="statement not covered" > actualErrorMessage = 'Failed to verify file existence in Google Cloud Storage';</span>
1016
+ <span class="cstat-no" title="statement not covered" > }</span>
1017
+ <span class="cstat-no" title="statement not covered" > }</span>
1018
+ &nbsp;
1019
+ <span class="cstat-no" title="statement not covered" > const updateData: any = {</span>
1020
+ <span class="cstat-no" title="statement not covered" > uploadStatus: actualUploadSuccess ? 'COMPLETED' : 'FAILED',</span>
1021
+ <span class="cstat-no" title="statement not covered" > uploadProgress: actualUploadSuccess ? 100 : 0,</span>
1022
+ <span class="cstat-no" title="statement not covered" > };</span>
1023
+
1024
+ <span class="cstat-no" title="statement not covered" > if (!actualUploadSuccess &amp;&amp; actualErrorMessage) {</span>
1025
+ <span class="cstat-no" title="statement not covered" > updateData.uploadError = actualErrorMessage;</span>
1026
+ <span class="cstat-no" title="statement not covered" > updateData.uploadRetryCount = { increment: 1 };</span>
1027
+ <span class="cstat-no" title="statement not covered" > }</span>
1028
+
1029
+ <span class="cstat-no" title="statement not covered" > if (actualUploadSuccess) {</span>
1030
+ <span class="cstat-no" title="statement not covered" > updateData.uploadedAt = new Date();</span>
1031
+ <span class="cstat-no" title="statement not covered" > }</span>
1032
+
1033
+ <span class="cstat-no" title="statement not covered" > await prisma.file.update({</span>
1034
+ <span class="cstat-no" title="statement not covered" > where: { id: fileId },</span>
1035
+ <span class="cstat-no" title="statement not covered" > data: updateData</span>
1036
+ <span class="cstat-no" title="statement not covered" > });</span>
1037
+ <span class="cstat-no" title="statement not covered" > } catch (error) {</span>
1038
+ <span class="cstat-no" title="statement not covered" > logger.error('Error confirming direct upload:', {error});</span>
1039
+ <span class="cstat-no" title="statement not covered" > throw new TRPCError({</span>
1040
+ <span class="cstat-no" title="statement not covered" > code: 'INTERNAL_SERVER_ERROR',</span>
1041
+ <span class="cstat-no" title="statement not covered" > message: 'Failed to confirm upload',</span>
1042
+ <span class="cstat-no" title="statement not covered" > });</span>
1043
+ <span class="cstat-no" title="statement not covered" > }</span>
1044
+ <span class="cstat-no" title="statement not covered" >}</span>
1045
+ &nbsp;
1046
+ /**
1047
+ * Updates upload progress for a direct upload
1048
+ * @param fileId The ID of the file record
1049
+ * @param progress Progress percentage (0-100)
1050
+ */
1051
+ <span class="cstat-no" title="statement not covered" ><span class="fstat-no" title="function not covered" >export async function updateUploadProgress(</span></span>
1052
+ <span class="cstat-no" title="statement not covered" > fileId: string,</span>
1053
+ <span class="cstat-no" title="statement not covered" > progress: number</span>
1054
+ <span class="cstat-no" title="statement not covered" >): Promise&lt;void&gt; {</span>
1055
+ <span class="cstat-no" title="statement not covered" > try {</span>
1056
+ // await prisma.file.update({
1057
+ // where: { id: fileId },
1058
+ // data: {
1059
+ // uploadStatus: 'UPLOADING',
1060
+ // uploadProgress: Math.min(100, Math.max(0, progress))
1061
+ // }
1062
+ // });
1063
+ <span class="cstat-no" title="statement not covered" > const current = await prisma.file.findUnique({ where: { id: fileId }, select: { uploadStatus: true } });</span>
1064
+ <span class="cstat-no" title="statement not covered" > if (!current || ['COMPLETED','FAILED','CANCELLED'].includes(current.uploadStatus as string)) return;</span>
1065
+ <span class="cstat-no" title="statement not covered" > const clamped = Math.min(100, Math.max(0, progress));</span>
1066
+ <span class="cstat-no" title="statement not covered" > await prisma.file.update({</span>
1067
+ <span class="cstat-no" title="statement not covered" > where: { id: fileId },</span>
1068
+ <span class="cstat-no" title="statement not covered" > data: {</span>
1069
+ <span class="cstat-no" title="statement not covered" > uploadStatus: 'UPLOADING',</span>
1070
+ <span class="cstat-no" title="statement not covered" > uploadProgress: clamped</span>
1071
+ <span class="cstat-no" title="statement not covered" > }</span>
1072
+ <span class="cstat-no" title="statement not covered" > });</span>
1073
+ <span class="cstat-no" title="statement not covered" > } catch (error) {</span>
1074
+ <span class="cstat-no" title="statement not covered" > logger.error('Error updating upload progress:', {error: error instanceof Error ? {</span>
1075
+ <span class="cstat-no" title="statement not covered" > name: error.name,</span>
1076
+ <span class="cstat-no" title="statement not covered" > message: error.message,</span>
1077
+ <span class="cstat-no" title="statement not covered" > stack: error.stack,</span>
1078
+ <span class="cstat-no" title="statement not covered" > } : error}); </span>
1079
+ <span class="cstat-no" title="statement not covered" > throw new TRPCError({</span>
1080
+ <span class="cstat-no" title="statement not covered" > code: 'INTERNAL_SERVER_ERROR',</span>
1081
+ <span class="cstat-no" title="statement not covered" > message: 'Failed to update upload progress',</span>
1082
+ <span class="cstat-no" title="statement not covered" > });</span>
1083
+ <span class="cstat-no" title="statement not covered" > }</span>
1084
+ <span class="cstat-no" title="statement not covered" >}</span>
1085
+ &nbsp;
1086
+ /**
1087
+ * Creates multiple direct upload files
1088
+ * @param files Array of file metadata
1089
+ * @param userId The ID of the user uploading the files
1090
+ * @param directory Optional subdirectory to store the files in
1091
+ * @param assignmentId Optional assignment ID to associate files with
1092
+ * @param submissionId Optional submission ID to associate files with
1093
+ * @returns Array of direct upload file information
1094
+ */
1095
+ export async function createDirectUploadFiles(
1096
+ files: DirectFileData[],
1097
+ userId: string,
1098
+ directory?: string,
1099
+ assignmentId?: string,
1100
+ submissionId?: string,
1101
+ announcementId?: string
1102
+ ): Promise&lt;DirectUploadFile[]&gt; {
1103
+ try {
1104
+ const uploadPromises = files.map(file =&gt;
1105
+ createDirectUploadFile(file, userId, directory, assignmentId, submissionId, announcementId)
1106
+ );
1107
+ return await Promise.all(uploadPromises);
1108
+ <span class="branch-0 cbranch-no" title="branch not covered" > } catch (error) {</span>
1109
+ <span class="cstat-no" title="statement not covered" > logger.error('Error creating direct upload files:', {error: error instanceof Error ? {</span>
1110
+ <span class="cstat-no" title="statement not covered" > name: error.name,</span>
1111
+ <span class="cstat-no" title="statement not covered" > message: error.message,</span>
1112
+ <span class="cstat-no" title="statement not covered" > stack: error.stack,</span>
1113
+ <span class="cstat-no" title="statement not covered" > } : error});</span>
1114
+ <span class="cstat-no" title="statement not covered" > throw new TRPCError({</span>
1115
+ <span class="cstat-no" title="statement not covered" > code: 'INTERNAL_SERVER_ERROR',</span>
1116
+ <span class="cstat-no" title="statement not covered" > message: 'Failed to create direct upload files',</span>
1117
+ <span class="cstat-no" title="statement not covered" > });</span>
1118
+ <span class="cstat-no" title="statement not covered" > }</span>
1119
+ }</pre></td></tr></table></pre>
1120
+
1121
+ <div class='push'></div><!-- for sticky footer -->
1122
+ </div><!-- /wrapper -->
1123
+ <div class='footer quiet pad2 space-top1 center small'>
1124
+ Code coverage generated by
1125
+ <a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
1126
+ at 2025-11-22T10:42:40.796Z
1127
+ </div>
1128
+ <script src="../../../prettify.js"></script>
1129
+ <script>
1130
+ window.onload = function () {
1131
+ prettyPrint();
1132
+ };
1133
+ </script>
1134
+ <script src="../../../sorter.js"></script>
1135
+ <script src="../../../block-navigation.js"></script>
1136
+ </body>
1137
+ </html>
1138
+