@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,592 @@
1
+
2
+ <!doctype html>
3
+ <html lang="en">
4
+
5
+ <head>
6
+ <title>Code coverage report for server/src/lib/thumbnailGenerator.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> thumbnailGenerator.ts</h1>
23
+ <div class='clearfix'>
24
+
25
+ <div class='fl pad1y space-right2'>
26
+ <span class="strong">0% </span>
27
+ <span class="quiet">Statements</span>
28
+ <span class='fraction'>0/120</span>
29
+ </div>
30
+
31
+
32
+ <div class='fl pad1y space-right2'>
33
+ <span class="strong">0% </span>
34
+ <span class="quiet">Branches</span>
35
+ <span class='fraction'>0/1</span>
36
+ </div>
37
+
38
+
39
+ <div class='fl pad1y space-right2'>
40
+ <span class="strong">0% </span>
41
+ <span class="quiet">Functions</span>
42
+ <span class='fraction'>0/1</span>
43
+ </div>
44
+
45
+
46
+ <div class='fl pad1y space-right2'>
47
+ <span class="strong">0% </span>
48
+ <span class="quiet">Lines</span>
49
+ <span class='fraction'>0/120</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></td><td class="line-coverage quiet"><span class="cline-any cline-no">&nbsp;</span>
236
+ <span class="cline-any cline-neutral">&nbsp;</span>
237
+ <span class="cline-any cline-no">&nbsp;</span>
238
+ <span class="cline-any cline-neutral">&nbsp;</span>
239
+ <span class="cline-any cline-neutral">&nbsp;</span>
240
+ <span class="cline-any cline-no">&nbsp;</span>
241
+ <span class="cline-any cline-no">&nbsp;</span>
242
+ <span class="cline-any cline-neutral">&nbsp;</span>
243
+ <span class="cline-any cline-neutral">&nbsp;</span>
244
+ <span class="cline-any cline-no">&nbsp;</span>
245
+ <span class="cline-any cline-no">&nbsp;</span>
246
+ <span class="cline-any cline-no">&nbsp;</span>
247
+ <span class="cline-any cline-no">&nbsp;</span>
248
+ <span class="cline-any cline-no">&nbsp;</span>
249
+ <span class="cline-any cline-no">&nbsp;</span>
250
+ <span class="cline-any cline-no">&nbsp;</span>
251
+ <span class="cline-any cline-no">&nbsp;</span>
252
+ <span class="cline-any cline-no">&nbsp;</span>
253
+ <span class="cline-any cline-neutral">&nbsp;</span>
254
+ <span class="cline-any cline-no">&nbsp;</span>
255
+ <span class="cline-any cline-no">&nbsp;</span>
256
+ <span class="cline-any cline-no">&nbsp;</span>
257
+ <span class="cline-any cline-no">&nbsp;</span>
258
+ <span class="cline-any cline-no">&nbsp;</span>
259
+ <span class="cline-any cline-no">&nbsp;</span>
260
+ <span class="cline-any cline-no">&nbsp;</span>
261
+ <span class="cline-any cline-no">&nbsp;</span>
262
+ <span class="cline-any cline-no">&nbsp;</span>
263
+ <span class="cline-any cline-no">&nbsp;</span>
264
+ <span class="cline-any cline-no">&nbsp;</span>
265
+ <span class="cline-any cline-no">&nbsp;</span>
266
+ <span class="cline-any cline-no">&nbsp;</span>
267
+ <span class="cline-any cline-no">&nbsp;</span>
268
+ <span class="cline-any cline-no">&nbsp;</span>
269
+ <span class="cline-any cline-neutral">&nbsp;</span>
270
+ <span class="cline-any cline-no">&nbsp;</span>
271
+ <span class="cline-any cline-no">&nbsp;</span>
272
+ <span class="cline-any cline-no">&nbsp;</span>
273
+ <span class="cline-any cline-no">&nbsp;</span>
274
+ <span class="cline-any cline-no">&nbsp;</span>
275
+ <span class="cline-any cline-no">&nbsp;</span>
276
+ <span class="cline-any cline-neutral">&nbsp;</span>
277
+ <span class="cline-any cline-no">&nbsp;</span>
278
+ <span class="cline-any cline-no">&nbsp;</span>
279
+ <span class="cline-any cline-no">&nbsp;</span>
280
+ <span class="cline-any cline-no">&nbsp;</span>
281
+ <span class="cline-any cline-no">&nbsp;</span>
282
+ <span class="cline-any cline-no">&nbsp;</span>
283
+ <span class="cline-any cline-neutral">&nbsp;</span>
284
+ <span class="cline-any cline-neutral">&nbsp;</span>
285
+ <span class="cline-any cline-neutral">&nbsp;</span>
286
+ <span class="cline-any cline-neutral">&nbsp;</span>
287
+ <span class="cline-any cline-neutral">&nbsp;</span>
288
+ <span class="cline-any cline-neutral">&nbsp;</span>
289
+ <span class="cline-any cline-neutral">&nbsp;</span>
290
+ <span class="cline-any cline-no">&nbsp;</span>
291
+ <span class="cline-any cline-no">&nbsp;</span>
292
+ <span class="cline-any cline-neutral">&nbsp;</span>
293
+ <span class="cline-any cline-no">&nbsp;</span>
294
+ <span class="cline-any cline-no">&nbsp;</span>
295
+ <span class="cline-any cline-no">&nbsp;</span>
296
+ <span class="cline-any cline-no">&nbsp;</span>
297
+ <span class="cline-any cline-no">&nbsp;</span>
298
+ <span class="cline-any cline-no">&nbsp;</span>
299
+ <span class="cline-any cline-no">&nbsp;</span>
300
+ <span class="cline-any cline-no">&nbsp;</span>
301
+ <span class="cline-any cline-no">&nbsp;</span>
302
+ <span class="cline-any cline-no">&nbsp;</span>
303
+ <span class="cline-any cline-no">&nbsp;</span>
304
+ <span class="cline-any cline-no">&nbsp;</span>
305
+ <span class="cline-any cline-no">&nbsp;</span>
306
+ <span class="cline-any cline-no">&nbsp;</span>
307
+ <span class="cline-any cline-no">&nbsp;</span>
308
+ <span class="cline-any cline-no">&nbsp;</span>
309
+ <span class="cline-any cline-neutral">&nbsp;</span>
310
+ <span class="cline-any cline-neutral">&nbsp;</span>
311
+ <span class="cline-any cline-no">&nbsp;</span>
312
+ <span class="cline-any cline-no">&nbsp;</span>
313
+ <span class="cline-any cline-no">&nbsp;</span>
314
+ <span class="cline-any cline-no">&nbsp;</span>
315
+ <span class="cline-any cline-no">&nbsp;</span>
316
+ <span class="cline-any cline-no">&nbsp;</span>
317
+ <span class="cline-any cline-no">&nbsp;</span>
318
+ <span class="cline-any cline-no">&nbsp;</span>
319
+ <span class="cline-any cline-neutral">&nbsp;</span>
320
+ <span class="cline-any cline-neutral">&nbsp;</span>
321
+ <span class="cline-any cline-neutral">&nbsp;</span>
322
+ <span class="cline-any cline-neutral">&nbsp;</span>
323
+ <span class="cline-any cline-neutral">&nbsp;</span>
324
+ <span class="cline-any cline-neutral">&nbsp;</span>
325
+ <span class="cline-any cline-no">&nbsp;</span>
326
+ <span class="cline-any cline-neutral">&nbsp;</span>
327
+ <span class="cline-any cline-no">&nbsp;</span>
328
+ <span class="cline-any cline-no">&nbsp;</span>
329
+ <span class="cline-any cline-no">&nbsp;</span>
330
+ <span class="cline-any cline-no">&nbsp;</span>
331
+ <span class="cline-any cline-no">&nbsp;</span>
332
+ <span class="cline-any cline-no">&nbsp;</span>
333
+ <span class="cline-any cline-no">&nbsp;</span>
334
+ <span class="cline-any cline-no">&nbsp;</span>
335
+ <span class="cline-any cline-neutral">&nbsp;</span>
336
+ <span class="cline-any cline-neutral">&nbsp;</span>
337
+ <span class="cline-any cline-no">&nbsp;</span>
338
+ <span class="cline-any cline-neutral">&nbsp;</span>
339
+ <span class="cline-any cline-no">&nbsp;</span>
340
+ <span class="cline-any cline-no">&nbsp;</span>
341
+ <span class="cline-any cline-no">&nbsp;</span>
342
+ <span class="cline-any cline-no">&nbsp;</span>
343
+ <span class="cline-any cline-no">&nbsp;</span>
344
+ <span class="cline-any cline-no">&nbsp;</span>
345
+ <span class="cline-any cline-no">&nbsp;</span>
346
+ <span class="cline-any cline-neutral">&nbsp;</span>
347
+ <span class="cline-any cline-no">&nbsp;</span>
348
+ <span class="cline-any cline-no">&nbsp;</span>
349
+ <span class="cline-any cline-no">&nbsp;</span>
350
+ <span class="cline-any cline-no">&nbsp;</span>
351
+ <span class="cline-any cline-no">&nbsp;</span>
352
+ <span class="cline-any cline-no">&nbsp;</span>
353
+ <span class="cline-any cline-no">&nbsp;</span>
354
+ <span class="cline-any cline-no">&nbsp;</span>
355
+ <span class="cline-any cline-no">&nbsp;</span>
356
+ <span class="cline-any cline-no">&nbsp;</span>
357
+ <span class="cline-any cline-no">&nbsp;</span>
358
+ <span class="cline-any cline-no">&nbsp;</span>
359
+ <span class="cline-any cline-no">&nbsp;</span>
360
+ <span class="cline-any cline-no">&nbsp;</span>
361
+ <span class="cline-any cline-neutral">&nbsp;</span>
362
+ <span class="cline-any cline-neutral">&nbsp;</span>
363
+ <span class="cline-any cline-neutral">&nbsp;</span>
364
+ <span class="cline-any cline-neutral">&nbsp;</span>
365
+ <span class="cline-any cline-neutral">&nbsp;</span>
366
+ <span class="cline-any cline-neutral">&nbsp;</span>
367
+ <span class="cline-any cline-neutral">&nbsp;</span>
368
+ <span class="cline-any cline-no">&nbsp;</span>
369
+ <span class="cline-any cline-no">&nbsp;</span>
370
+ <span class="cline-any cline-no">&nbsp;</span>
371
+ <span class="cline-any cline-no">&nbsp;</span>
372
+ <span class="cline-any cline-neutral">&nbsp;</span>
373
+ <span class="cline-any cline-no">&nbsp;</span>
374
+ <span class="cline-any cline-no">&nbsp;</span>
375
+ <span class="cline-any cline-no">&nbsp;</span>
376
+ <span class="cline-any cline-neutral">&nbsp;</span>
377
+ <span class="cline-any cline-no">&nbsp;</span>
378
+ <span class="cline-any cline-neutral">&nbsp;</span>
379
+ <span class="cline-any cline-no">&nbsp;</span>
380
+ <span class="cline-any cline-no">&nbsp;</span>
381
+ <span class="cline-any cline-no">&nbsp;</span>
382
+ <span class="cline-any cline-no">&nbsp;</span>
383
+ <span class="cline-any cline-no">&nbsp;</span>
384
+ <span class="cline-any cline-no">&nbsp;</span>
385
+ <span class="cline-any cline-no">&nbsp;</span>
386
+ <span class="cline-any cline-no">&nbsp;</span>
387
+ <span class="cline-any cline-no">&nbsp;</span>
388
+ <span class="cline-any cline-no">&nbsp;</span>
389
+ <span class="cline-any cline-neutral">&nbsp;</span>
390
+ <span class="cline-any cline-no">&nbsp;</span>
391
+ <span class="cline-any cline-no">&nbsp;</span>
392
+ <span class="cline-any cline-no">&nbsp;</span>
393
+ <span class="cline-any cline-no">&nbsp;</span>
394
+ <span class="cline-any cline-no">&nbsp;</span>
395
+ <span class="cline-any cline-neutral">&nbsp;</span>
396
+ <span class="cline-any cline-neutral">&nbsp;</span>
397
+ <span class="cline-any cline-neutral">&nbsp;</span>
398
+ <span class="cline-any cline-neutral">&nbsp;</span>
399
+ <span class="cline-any cline-neutral">&nbsp;</span>
400
+ <span class="cline-any cline-neutral">&nbsp;</span>
401
+ <span class="cline-any cline-neutral">&nbsp;</span>
402
+ <span class="cline-any cline-neutral">&nbsp;</span>
403
+ <span class="cline-any cline-neutral">&nbsp;</span>
404
+ <span class="cline-any cline-neutral">&nbsp;</span></td><td class="text"><pre class="prettyprint lang-js"><span class="cstat-no" title="statement not covered" >import sharp from 'sharp';<span class="fstat-no" title="function not covered" ><span class="branch-0 cbranch-no" title="branch not covered" ></span></span></span>
405
+ import { prisma } from './prisma.js';
406
+ <span class="cstat-no" title="statement not covered" >import { deleteFile, getSignedUrl } from './googleCloudStorage.js';</span>
407
+ &nbsp;
408
+ // Thumbnail size configuration
409
+ <span class="cstat-no" title="statement not covered" >const THUMBNAIL_WIDTH = 200;</span>
410
+ <span class="cstat-no" title="statement not covered" >const THUMBNAIL_HEIGHT = 200;</span>
411
+ &nbsp;
412
+ // File type configurations
413
+ <span class="cstat-no" title="statement not covered" >const SUPPORTED_IMAGE_TYPES = [</span>
414
+ <span class="cstat-no" title="statement not covered" > 'image/jpeg',</span>
415
+ <span class="cstat-no" title="statement not covered" > 'image/png',</span>
416
+ <span class="cstat-no" title="statement not covered" > 'image/gif',</span>
417
+ <span class="cstat-no" title="statement not covered" > 'image/webp',</span>
418
+ <span class="cstat-no" title="statement not covered" > 'image/tiff',</span>
419
+ <span class="cstat-no" title="statement not covered" > 'image/bmp',</span>
420
+ <span class="cstat-no" title="statement not covered" > 'image/avif'</span>
421
+ <span class="cstat-no" title="statement not covered" >];</span>
422
+ &nbsp;
423
+ <span class="cstat-no" title="statement not covered" >const DOCUMENT_TYPES = [</span>
424
+ <span class="cstat-no" title="statement not covered" > 'application/pdf',</span>
425
+ <span class="cstat-no" title="statement not covered" > 'application/msword',</span>
426
+ <span class="cstat-no" title="statement not covered" > 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', // .docx</span>
427
+ <span class="cstat-no" title="statement not covered" > 'application/vnd.ms-excel',</span>
428
+ <span class="cstat-no" title="statement not covered" > 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', // .xlsx</span>
429
+ <span class="cstat-no" title="statement not covered" > 'application/vnd.ms-powerpoint',</span>
430
+ <span class="cstat-no" title="statement not covered" > 'application/vnd.openxmlformats-officedocument.presentationml.presentation', // .pptx</span>
431
+ <span class="cstat-no" title="statement not covered" > 'text/plain',</span>
432
+ <span class="cstat-no" title="statement not covered" > 'text/csv',</span>
433
+ <span class="cstat-no" title="statement not covered" > 'application/json',</span>
434
+ <span class="cstat-no" title="statement not covered" > 'text/html',</span>
435
+ <span class="cstat-no" title="statement not covered" > 'text/javascript',</span>
436
+ <span class="cstat-no" title="statement not covered" > 'text/css'</span>
437
+ <span class="cstat-no" title="statement not covered" >];</span>
438
+ &nbsp;
439
+ <span class="cstat-no" title="statement not covered" >const VIDEO_TYPES = [</span>
440
+ <span class="cstat-no" title="statement not covered" > 'video/mp4',</span>
441
+ <span class="cstat-no" title="statement not covered" > 'video/webm',</span>
442
+ <span class="cstat-no" title="statement not covered" > 'video/ogg',</span>
443
+ <span class="cstat-no" title="statement not covered" > 'video/quicktime'</span>
444
+ <span class="cstat-no" title="statement not covered" >];</span>
445
+ &nbsp;
446
+ <span class="cstat-no" title="statement not covered" >const AUDIO_TYPES = [</span>
447
+ <span class="cstat-no" title="statement not covered" > 'audio/mpeg',</span>
448
+ <span class="cstat-no" title="statement not covered" > 'audio/ogg',</span>
449
+ <span class="cstat-no" title="statement not covered" > 'audio/wav',</span>
450
+ <span class="cstat-no" title="statement not covered" > 'audio/webm'</span>
451
+ <span class="cstat-no" title="statement not covered" >];</span>
452
+ &nbsp;
453
+ /**
454
+ * Generates a thumbnail for an image or PDF file
455
+ * @param fileBuffer The file buffer
456
+ * @param fileType The MIME type of the file
457
+ * @returns Thumbnail buffer
458
+ */
459
+ <span class="cstat-no" title="statement not covered" >export async function generateMediaThumbnail(fileBuffer: Buffer, fileType: string): Promise&lt;Buffer&gt; {</span>
460
+ <span class="cstat-no" title="statement not covered" > if (fileType === 'application/pdf') {</span>
461
+ // For PDFs, we need to use a different approach
462
+ <span class="cstat-no" title="statement not covered" > try {</span>
463
+ <span class="cstat-no" title="statement not covered" > return await sharp(fileBuffer, { </span>
464
+ <span class="cstat-no" title="statement not covered" > density: 300, // Higher density for better quality</span>
465
+ <span class="cstat-no" title="statement not covered" > page: 0 // First page only</span>
466
+ <span class="cstat-no" title="statement not covered" > })</span>
467
+ <span class="cstat-no" title="statement not covered" > .resize(THUMBNAIL_WIDTH, THUMBNAIL_HEIGHT, {</span>
468
+ <span class="cstat-no" title="statement not covered" > fit: 'inside',</span>
469
+ <span class="cstat-no" title="statement not covered" > withoutEnlargement: true,</span>
470
+ <span class="cstat-no" title="statement not covered" > })</span>
471
+ <span class="cstat-no" title="statement not covered" > .jpeg({ quality: 80 })</span>
472
+ <span class="cstat-no" title="statement not covered" > .toBuffer();</span>
473
+ <span class="cstat-no" title="statement not covered" > } catch (error) {</span>
474
+ <span class="cstat-no" title="statement not covered" > console.warn('Failed to generate PDF thumbnail:', error);</span>
475
+ <span class="cstat-no" title="statement not covered" > return generateGenericThumbnail(fileType);</span>
476
+ <span class="cstat-no" title="statement not covered" > }</span>
477
+ <span class="cstat-no" title="statement not covered" > }</span>
478
+
479
+ // For regular images
480
+ <span class="cstat-no" title="statement not covered" > return sharp(fileBuffer)</span>
481
+ <span class="cstat-no" title="statement not covered" > .resize(THUMBNAIL_WIDTH, THUMBNAIL_HEIGHT, {</span>
482
+ <span class="cstat-no" title="statement not covered" > fit: 'inside',</span>
483
+ <span class="cstat-no" title="statement not covered" > withoutEnlargement: true,</span>
484
+ <span class="cstat-no" title="statement not covered" > })</span>
485
+ <span class="cstat-no" title="statement not covered" > .jpeg({ quality: 80 })</span>
486
+ <span class="cstat-no" title="statement not covered" > .toBuffer();</span>
487
+ <span class="cstat-no" title="statement not covered" >}</span>
488
+ &nbsp;
489
+ /**
490
+ * Generates a generic icon-based thumbnail for a file type
491
+ * @param fileType The MIME type of the file
492
+ * @returns Thumbnail buffer
493
+ */
494
+ <span class="cstat-no" title="statement not covered" >async function generateGenericThumbnail(fileType: string): Promise&lt;Buffer&gt; {</span>
495
+ // Create a blank canvas with a colored background based on file type
496
+ <span class="cstat-no" title="statement not covered" > const canvas = sharp({</span>
497
+ <span class="cstat-no" title="statement not covered" > create: {</span>
498
+ <span class="cstat-no" title="statement not covered" > width: THUMBNAIL_WIDTH,</span>
499
+ <span class="cstat-no" title="statement not covered" > height: THUMBNAIL_HEIGHT,</span>
500
+ <span class="cstat-no" title="statement not covered" > channels: 4,</span>
501
+ <span class="cstat-no" title="statement not covered" > background: { r: 245, g: 245, b: 245, alpha: 1 }</span>
502
+ <span class="cstat-no" title="statement not covered" > }</span>
503
+ <span class="cstat-no" title="statement not covered" > });</span>
504
+ &nbsp;
505
+ // Add a colored overlay based on file type
506
+ <span class="cstat-no" title="statement not covered" > let color = { r: 200, g: 200, b: 200, alpha: 0.5 }; // Default gray</span>
507
+ &nbsp;
508
+ <span class="cstat-no" title="statement not covered" > if (DOCUMENT_TYPES.includes(fileType)) {</span>
509
+ <span class="cstat-no" title="statement not covered" > color = { r: 52, g: 152, b: 219, alpha: 0.5 }; // Blue for documents</span>
510
+ <span class="cstat-no" title="statement not covered" > } else if (VIDEO_TYPES.includes(fileType)) {</span>
511
+ <span class="cstat-no" title="statement not covered" > color = { r: 231, g: 76, b: 60, alpha: 0.5 }; // Red for videos</span>
512
+ <span class="cstat-no" title="statement not covered" > } else if (AUDIO_TYPES.includes(fileType)) {</span>
513
+ <span class="cstat-no" title="statement not covered" > color = { r: 46, g: 204, b: 113, alpha: 0.5 }; // Green for audio</span>
514
+ <span class="cstat-no" title="statement not covered" > }</span>
515
+ &nbsp;
516
+ <span class="cstat-no" title="statement not covered" > return canvas</span>
517
+ <span class="cstat-no" title="statement not covered" > .composite([{</span>
518
+ <span class="cstat-no" title="statement not covered" > input: Buffer.from([color.r, color.g, color.b, Math.floor(color.alpha * 255)]),</span>
519
+ <span class="cstat-no" title="statement not covered" > raw: {</span>
520
+ <span class="cstat-no" title="statement not covered" > width: 1,</span>
521
+ <span class="cstat-no" title="statement not covered" > height: 1,</span>
522
+ <span class="cstat-no" title="statement not covered" > channels: 4</span>
523
+ <span class="cstat-no" title="statement not covered" > },</span>
524
+ <span class="cstat-no" title="statement not covered" > tile: true,</span>
525
+ <span class="cstat-no" title="statement not covered" > blend: 'overlay'</span>
526
+ <span class="cstat-no" title="statement not covered" > }])</span>
527
+ <span class="cstat-no" title="statement not covered" > .jpeg({ quality: 80 })</span>
528
+ <span class="cstat-no" title="statement not covered" > .toBuffer();</span>
529
+ <span class="cstat-no" title="statement not covered" >}</span>
530
+ &nbsp;
531
+ /**
532
+ * Generates a thumbnail for a file
533
+ * @param fileName The name of the file in Google Cloud Storage
534
+ * @param fileType The MIME type of the file
535
+ * @returns The thumbnail buffer or null if thumbnail generation is not supported
536
+ */
537
+ <span class="cstat-no" title="statement not covered" >export async function generateThumbnail(fileName: string, fileType: string): Promise&lt;Buffer | null&gt; {</span>
538
+ <span class="cstat-no" title="statement not covered" > try {</span>
539
+ <span class="cstat-no" title="statement not covered" > const signedUrl = await getSignedUrl(fileName);</span>
540
+ <span class="cstat-no" title="statement not covered" > const response = await fetch(signedUrl);</span>
541
+
542
+ <span class="cstat-no" title="statement not covered" > if (!response.ok) {</span>
543
+ <span class="cstat-no" title="statement not covered" > throw new Error(`Failed to download file from storage: ${response.status} ${response.statusText}`);</span>
544
+ <span class="cstat-no" title="statement not covered" > }</span>
545
+ &nbsp;
546
+ <span class="cstat-no" title="statement not covered" > const fileBuffer = await response.arrayBuffer();</span>
547
+ &nbsp;
548
+ <span class="cstat-no" title="statement not covered" > if (SUPPORTED_IMAGE_TYPES.includes(fileType) || fileType === 'application/pdf') {</span>
549
+ <span class="cstat-no" title="statement not covered" > try {</span>
550
+ <span class="cstat-no" title="statement not covered" > const thumbnail = await generateMediaThumbnail(Buffer.from(fileBuffer), fileType);</span>
551
+ <span class="cstat-no" title="statement not covered" > return thumbnail;</span>
552
+ <span class="cstat-no" title="statement not covered" > } catch (error) {</span>
553
+ <span class="cstat-no" title="statement not covered" > return generateGenericThumbnail(fileType);</span>
554
+ <span class="cstat-no" title="statement not covered" > }</span>
555
+ <span class="cstat-no" title="statement not covered" > } else if ([...DOCUMENT_TYPES, ...VIDEO_TYPES, ...AUDIO_TYPES].includes(fileType)) {</span>
556
+ <span class="cstat-no" title="statement not covered" > return generateGenericThumbnail(fileType);</span>
557
+ <span class="cstat-no" title="statement not covered" > }</span>
558
+ &nbsp;
559
+ <span class="cstat-no" title="statement not covered" > return null; // Unsupported file type</span>
560
+ <span class="cstat-no" title="statement not covered" > } catch (error) {</span>
561
+ <span class="cstat-no" title="statement not covered" > return null;</span>
562
+ <span class="cstat-no" title="statement not covered" > }</span>
563
+ <span class="cstat-no" title="statement not covered" >}</span>
564
+ &nbsp;
565
+ /**
566
+ * Stores a thumbnail in Google Cloud Storage and creates a File entry
567
+ * @param thumbnailBuffer The thumbnail buffer to store
568
+ * @param originalFileName The original file name
569
+ * @param userId The user ID who owns the file
570
+ * @returns The ID of the created thumbnail File
571
+ */
572
+ // DEPRECATED: This function is no longer used - thumbnails are generated during direct uploads
573
+ // Thumbnail generation is now handled in the direct upload flow </pre></td></tr></table></pre>
574
+
575
+ <div class='push'></div><!-- for sticky footer -->
576
+ </div><!-- /wrapper -->
577
+ <div class='footer quiet pad2 space-top1 center small'>
578
+ Code coverage generated by
579
+ <a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
580
+ at 2025-11-22T10:42:40.796Z
581
+ </div>
582
+ <script src="../../../prettify.js"></script>
583
+ <script>
584
+ window.onload = function () {
585
+ prettyPrint();
586
+ };
587
+ </script>
588
+ <script src="../../../sorter.js"></script>
589
+ <script src="../../../block-navigation.js"></script>
590
+ </body>
591
+ </html>
592
+