Browse Source

reduce image size in hospital feature

pearlgw 1 month ago
parent
commit
c67f59b42e

+ 547 - 0
package-lock.json

@@ -29,6 +29,7 @@
29 29
         "node-cron": "^4.2.1",
30 30
         "pg": "^8.16.2",
31 31
         "qs": "^6.14.0",
32
+        "sharp": "^0.34.3",
32 33
         "zod": "^4.0.5"
33 34
       },
34 35
       "devDependencies": {
@@ -40,6 +41,7 @@
40 41
         "@types/keycloak-connect": "^4.5.4",
41 42
         "@types/multer": "^2.0.0",
42 43
         "@types/node": "^24.0.14",
44
+        "@types/sharp": "^0.31.1",
43 45
         "nodemon": "^3.1.10",
44 46
         "prisma": "^6.10.1",
45 47
         "ts-node": "^10.9.2",
@@ -59,6 +61,16 @@
59 61
         "node": ">=12"
60 62
       }
61 63
     },
64
+    "node_modules/@emnapi/runtime": {
65
+      "version": "1.4.5",
66
+      "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.4.5.tgz",
67
+      "integrity": "sha512-++LApOtY0pEEz1zrd9vy1/zXVaVJJ/EbAF3u0fXIzPJEDtnITsBGbbK0EkM72amhl/R5b+5xx0Y/QhcVOpuulg==",
68
+      "license": "MIT",
69
+      "optional": true,
70
+      "dependencies": {
71
+        "tslib": "^2.4.0"
72
+      }
73
+    },
62 74
     "node_modules/@faker-js/faker": {
63 75
       "version": "9.8.0",
64 76
       "resolved": "https://registry.npmjs.org/@faker-js/faker/-/faker-9.8.0.tgz",
@@ -90,6 +102,424 @@
90 102
         "@hapi/hoek": "^9.0.0"
91 103
       }
92 104
     },
105
+    "node_modules/@img/sharp-darwin-arm64": {
106
+      "version": "0.34.3",
107
+      "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.34.3.tgz",
108
+      "integrity": "sha512-ryFMfvxxpQRsgZJqBd4wsttYQbCxsJksrv9Lw/v798JcQ8+w84mBWuXwl+TT0WJ/WrYOLaYpwQXi3sA9nTIaIg==",
109
+      "cpu": [
110
+        "arm64"
111
+      ],
112
+      "license": "Apache-2.0",
113
+      "optional": true,
114
+      "os": [
115
+        "darwin"
116
+      ],
117
+      "engines": {
118
+        "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
119
+      },
120
+      "funding": {
121
+        "url": "https://opencollective.com/libvips"
122
+      },
123
+      "optionalDependencies": {
124
+        "@img/sharp-libvips-darwin-arm64": "1.2.0"
125
+      }
126
+    },
127
+    "node_modules/@img/sharp-darwin-x64": {
128
+      "version": "0.34.3",
129
+      "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.34.3.tgz",
130
+      "integrity": "sha512-yHpJYynROAj12TA6qil58hmPmAwxKKC7reUqtGLzsOHfP7/rniNGTL8tjWX6L3CTV4+5P4ypcS7Pp+7OB+8ihA==",
131
+      "cpu": [
132
+        "x64"
133
+      ],
134
+      "license": "Apache-2.0",
135
+      "optional": true,
136
+      "os": [
137
+        "darwin"
138
+      ],
139
+      "engines": {
140
+        "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
141
+      },
142
+      "funding": {
143
+        "url": "https://opencollective.com/libvips"
144
+      },
145
+      "optionalDependencies": {
146
+        "@img/sharp-libvips-darwin-x64": "1.2.0"
147
+      }
148
+    },
149
+    "node_modules/@img/sharp-libvips-darwin-arm64": {
150
+      "version": "1.2.0",
151
+      "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.2.0.tgz",
152
+      "integrity": "sha512-sBZmpwmxqwlqG9ueWFXtockhsxefaV6O84BMOrhtg/YqbTaRdqDE7hxraVE3y6gVM4eExmfzW4a8el9ArLeEiQ==",
153
+      "cpu": [
154
+        "arm64"
155
+      ],
156
+      "license": "LGPL-3.0-or-later",
157
+      "optional": true,
158
+      "os": [
159
+        "darwin"
160
+      ],
161
+      "funding": {
162
+        "url": "https://opencollective.com/libvips"
163
+      }
164
+    },
165
+    "node_modules/@img/sharp-libvips-darwin-x64": {
166
+      "version": "1.2.0",
167
+      "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.2.0.tgz",
168
+      "integrity": "sha512-M64XVuL94OgiNHa5/m2YvEQI5q2cl9d/wk0qFTDVXcYzi43lxuiFTftMR1tOnFQovVXNZJ5TURSDK2pNe9Yzqg==",
169
+      "cpu": [
170
+        "x64"
171
+      ],
172
+      "license": "LGPL-3.0-or-later",
173
+      "optional": true,
174
+      "os": [
175
+        "darwin"
176
+      ],
177
+      "funding": {
178
+        "url": "https://opencollective.com/libvips"
179
+      }
180
+    },
181
+    "node_modules/@img/sharp-libvips-linux-arm": {
182
+      "version": "1.2.0",
183
+      "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.2.0.tgz",
184
+      "integrity": "sha512-mWd2uWvDtL/nvIzThLq3fr2nnGfyr/XMXlq8ZJ9WMR6PXijHlC3ksp0IpuhK6bougvQrchUAfzRLnbsen0Cqvw==",
185
+      "cpu": [
186
+        "arm"
187
+      ],
188
+      "license": "LGPL-3.0-or-later",
189
+      "optional": true,
190
+      "os": [
191
+        "linux"
192
+      ],
193
+      "funding": {
194
+        "url": "https://opencollective.com/libvips"
195
+      }
196
+    },
197
+    "node_modules/@img/sharp-libvips-linux-arm64": {
198
+      "version": "1.2.0",
199
+      "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.2.0.tgz",
200
+      "integrity": "sha512-RXwd0CgG+uPRX5YYrkzKyalt2OJYRiJQ8ED/fi1tq9WQW2jsQIn0tqrlR5l5dr/rjqq6AHAxURhj2DVjyQWSOA==",
201
+      "cpu": [
202
+        "arm64"
203
+      ],
204
+      "license": "LGPL-3.0-or-later",
205
+      "optional": true,
206
+      "os": [
207
+        "linux"
208
+      ],
209
+      "funding": {
210
+        "url": "https://opencollective.com/libvips"
211
+      }
212
+    },
213
+    "node_modules/@img/sharp-libvips-linux-ppc64": {
214
+      "version": "1.2.0",
215
+      "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.2.0.tgz",
216
+      "integrity": "sha512-Xod/7KaDDHkYu2phxxfeEPXfVXFKx70EAFZ0qyUdOjCcxbjqyJOEUpDe6RIyaunGxT34Anf9ue/wuWOqBW2WcQ==",
217
+      "cpu": [
218
+        "ppc64"
219
+      ],
220
+      "license": "LGPL-3.0-or-later",
221
+      "optional": true,
222
+      "os": [
223
+        "linux"
224
+      ],
225
+      "funding": {
226
+        "url": "https://opencollective.com/libvips"
227
+      }
228
+    },
229
+    "node_modules/@img/sharp-libvips-linux-s390x": {
230
+      "version": "1.2.0",
231
+      "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.2.0.tgz",
232
+      "integrity": "sha512-eMKfzDxLGT8mnmPJTNMcjfO33fLiTDsrMlUVcp6b96ETbnJmd4uvZxVJSKPQfS+odwfVaGifhsB07J1LynFehw==",
233
+      "cpu": [
234
+        "s390x"
235
+      ],
236
+      "license": "LGPL-3.0-or-later",
237
+      "optional": true,
238
+      "os": [
239
+        "linux"
240
+      ],
241
+      "funding": {
242
+        "url": "https://opencollective.com/libvips"
243
+      }
244
+    },
245
+    "node_modules/@img/sharp-libvips-linux-x64": {
246
+      "version": "1.2.0",
247
+      "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.2.0.tgz",
248
+      "integrity": "sha512-ZW3FPWIc7K1sH9E3nxIGB3y3dZkpJlMnkk7z5tu1nSkBoCgw2nSRTFHI5pB/3CQaJM0pdzMF3paf9ckKMSE9Tg==",
249
+      "cpu": [
250
+        "x64"
251
+      ],
252
+      "license": "LGPL-3.0-or-later",
253
+      "optional": true,
254
+      "os": [
255
+        "linux"
256
+      ],
257
+      "funding": {
258
+        "url": "https://opencollective.com/libvips"
259
+      }
260
+    },
261
+    "node_modules/@img/sharp-libvips-linuxmusl-arm64": {
262
+      "version": "1.2.0",
263
+      "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.2.0.tgz",
264
+      "integrity": "sha512-UG+LqQJbf5VJ8NWJ5Z3tdIe/HXjuIdo4JeVNADXBFuG7z9zjoegpzzGIyV5zQKi4zaJjnAd2+g2nna8TZvuW9Q==",
265
+      "cpu": [
266
+        "arm64"
267
+      ],
268
+      "license": "LGPL-3.0-or-later",
269
+      "optional": true,
270
+      "os": [
271
+        "linux"
272
+      ],
273
+      "funding": {
274
+        "url": "https://opencollective.com/libvips"
275
+      }
276
+    },
277
+    "node_modules/@img/sharp-libvips-linuxmusl-x64": {
278
+      "version": "1.2.0",
279
+      "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.2.0.tgz",
280
+      "integrity": "sha512-SRYOLR7CXPgNze8akZwjoGBoN1ThNZoqpOgfnOxmWsklTGVfJiGJoC/Lod7aNMGA1jSsKWM1+HRX43OP6p9+6Q==",
281
+      "cpu": [
282
+        "x64"
283
+      ],
284
+      "license": "LGPL-3.0-or-later",
285
+      "optional": true,
286
+      "os": [
287
+        "linux"
288
+      ],
289
+      "funding": {
290
+        "url": "https://opencollective.com/libvips"
291
+      }
292
+    },
293
+    "node_modules/@img/sharp-linux-arm": {
294
+      "version": "0.34.3",
295
+      "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.34.3.tgz",
296
+      "integrity": "sha512-oBK9l+h6KBN0i3dC8rYntLiVfW8D8wH+NPNT3O/WBHeW0OQWCjfWksLUaPidsrDKpJgXp3G3/hkmhptAW0I3+A==",
297
+      "cpu": [
298
+        "arm"
299
+      ],
300
+      "license": "Apache-2.0",
301
+      "optional": true,
302
+      "os": [
303
+        "linux"
304
+      ],
305
+      "engines": {
306
+        "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
307
+      },
308
+      "funding": {
309
+        "url": "https://opencollective.com/libvips"
310
+      },
311
+      "optionalDependencies": {
312
+        "@img/sharp-libvips-linux-arm": "1.2.0"
313
+      }
314
+    },
315
+    "node_modules/@img/sharp-linux-arm64": {
316
+      "version": "0.34.3",
317
+      "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.34.3.tgz",
318
+      "integrity": "sha512-QdrKe3EvQrqwkDrtuTIjI0bu6YEJHTgEeqdzI3uWJOH6G1O8Nl1iEeVYRGdj1h5I21CqxSvQp1Yv7xeU3ZewbA==",
319
+      "cpu": [
320
+        "arm64"
321
+      ],
322
+      "license": "Apache-2.0",
323
+      "optional": true,
324
+      "os": [
325
+        "linux"
326
+      ],
327
+      "engines": {
328
+        "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
329
+      },
330
+      "funding": {
331
+        "url": "https://opencollective.com/libvips"
332
+      },
333
+      "optionalDependencies": {
334
+        "@img/sharp-libvips-linux-arm64": "1.2.0"
335
+      }
336
+    },
337
+    "node_modules/@img/sharp-linux-ppc64": {
338
+      "version": "0.34.3",
339
+      "resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.34.3.tgz",
340
+      "integrity": "sha512-GLtbLQMCNC5nxuImPR2+RgrviwKwVql28FWZIW1zWruy6zLgA5/x2ZXk3mxj58X/tszVF69KK0Is83V8YgWhLA==",
341
+      "cpu": [
342
+        "ppc64"
343
+      ],
344
+      "license": "Apache-2.0",
345
+      "optional": true,
346
+      "os": [
347
+        "linux"
348
+      ],
349
+      "engines": {
350
+        "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
351
+      },
352
+      "funding": {
353
+        "url": "https://opencollective.com/libvips"
354
+      },
355
+      "optionalDependencies": {
356
+        "@img/sharp-libvips-linux-ppc64": "1.2.0"
357
+      }
358
+    },
359
+    "node_modules/@img/sharp-linux-s390x": {
360
+      "version": "0.34.3",
361
+      "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.34.3.tgz",
362
+      "integrity": "sha512-3gahT+A6c4cdc2edhsLHmIOXMb17ltffJlxR0aC2VPZfwKoTGZec6u5GrFgdR7ciJSsHT27BD3TIuGcuRT0KmQ==",
363
+      "cpu": [
364
+        "s390x"
365
+      ],
366
+      "license": "Apache-2.0",
367
+      "optional": true,
368
+      "os": [
369
+        "linux"
370
+      ],
371
+      "engines": {
372
+        "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
373
+      },
374
+      "funding": {
375
+        "url": "https://opencollective.com/libvips"
376
+      },
377
+      "optionalDependencies": {
378
+        "@img/sharp-libvips-linux-s390x": "1.2.0"
379
+      }
380
+    },
381
+    "node_modules/@img/sharp-linux-x64": {
382
+      "version": "0.34.3",
383
+      "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.34.3.tgz",
384
+      "integrity": "sha512-8kYso8d806ypnSq3/Ly0QEw90V5ZoHh10yH0HnrzOCr6DKAPI6QVHvwleqMkVQ0m+fc7EH8ah0BB0QPuWY6zJQ==",
385
+      "cpu": [
386
+        "x64"
387
+      ],
388
+      "license": "Apache-2.0",
389
+      "optional": true,
390
+      "os": [
391
+        "linux"
392
+      ],
393
+      "engines": {
394
+        "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
395
+      },
396
+      "funding": {
397
+        "url": "https://opencollective.com/libvips"
398
+      },
399
+      "optionalDependencies": {
400
+        "@img/sharp-libvips-linux-x64": "1.2.0"
401
+      }
402
+    },
403
+    "node_modules/@img/sharp-linuxmusl-arm64": {
404
+      "version": "0.34.3",
405
+      "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.34.3.tgz",
406
+      "integrity": "sha512-vAjbHDlr4izEiXM1OTggpCcPg9tn4YriK5vAjowJsHwdBIdx0fYRsURkxLG2RLm9gyBq66gwtWI8Gx0/ov+JKQ==",
407
+      "cpu": [
408
+        "arm64"
409
+      ],
410
+      "license": "Apache-2.0",
411
+      "optional": true,
412
+      "os": [
413
+        "linux"
414
+      ],
415
+      "engines": {
416
+        "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
417
+      },
418
+      "funding": {
419
+        "url": "https://opencollective.com/libvips"
420
+      },
421
+      "optionalDependencies": {
422
+        "@img/sharp-libvips-linuxmusl-arm64": "1.2.0"
423
+      }
424
+    },
425
+    "node_modules/@img/sharp-linuxmusl-x64": {
426
+      "version": "0.34.3",
427
+      "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.34.3.tgz",
428
+      "integrity": "sha512-gCWUn9547K5bwvOn9l5XGAEjVTTRji4aPTqLzGXHvIr6bIDZKNTA34seMPgM0WmSf+RYBH411VavCejp3PkOeQ==",
429
+      "cpu": [
430
+        "x64"
431
+      ],
432
+      "license": "Apache-2.0",
433
+      "optional": true,
434
+      "os": [
435
+        "linux"
436
+      ],
437
+      "engines": {
438
+        "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
439
+      },
440
+      "funding": {
441
+        "url": "https://opencollective.com/libvips"
442
+      },
443
+      "optionalDependencies": {
444
+        "@img/sharp-libvips-linuxmusl-x64": "1.2.0"
445
+      }
446
+    },
447
+    "node_modules/@img/sharp-wasm32": {
448
+      "version": "0.34.3",
449
+      "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.34.3.tgz",
450
+      "integrity": "sha512-+CyRcpagHMGteySaWos8IbnXcHgfDn7pO2fiC2slJxvNq9gDipYBN42/RagzctVRKgxATmfqOSulgZv5e1RdMg==",
451
+      "cpu": [
452
+        "wasm32"
453
+      ],
454
+      "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT",
455
+      "optional": true,
456
+      "dependencies": {
457
+        "@emnapi/runtime": "^1.4.4"
458
+      },
459
+      "engines": {
460
+        "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
461
+      },
462
+      "funding": {
463
+        "url": "https://opencollective.com/libvips"
464
+      }
465
+    },
466
+    "node_modules/@img/sharp-win32-arm64": {
467
+      "version": "0.34.3",
468
+      "resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.34.3.tgz",
469
+      "integrity": "sha512-MjnHPnbqMXNC2UgeLJtX4XqoVHHlZNd+nPt1kRPmj63wURegwBhZlApELdtxM2OIZDRv/DFtLcNhVbd1z8GYXQ==",
470
+      "cpu": [
471
+        "arm64"
472
+      ],
473
+      "license": "Apache-2.0 AND LGPL-3.0-or-later",
474
+      "optional": true,
475
+      "os": [
476
+        "win32"
477
+      ],
478
+      "engines": {
479
+        "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
480
+      },
481
+      "funding": {
482
+        "url": "https://opencollective.com/libvips"
483
+      }
484
+    },
485
+    "node_modules/@img/sharp-win32-ia32": {
486
+      "version": "0.34.3",
487
+      "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.34.3.tgz",
488
+      "integrity": "sha512-xuCdhH44WxuXgOM714hn4amodJMZl3OEvf0GVTm0BEyMeA2to+8HEdRPShH0SLYptJY1uBw+SCFP9WVQi1Q/cw==",
489
+      "cpu": [
490
+        "ia32"
491
+      ],
492
+      "license": "Apache-2.0 AND LGPL-3.0-or-later",
493
+      "optional": true,
494
+      "os": [
495
+        "win32"
496
+      ],
497
+      "engines": {
498
+        "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
499
+      },
500
+      "funding": {
501
+        "url": "https://opencollective.com/libvips"
502
+      }
503
+    },
504
+    "node_modules/@img/sharp-win32-x64": {
505
+      "version": "0.34.3",
506
+      "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.34.3.tgz",
507
+      "integrity": "sha512-OWwz05d++TxzLEv4VnsTz5CmZ6mI6S05sfQGEMrNrQcOEERbX46332IvE7pO/EUiw7jUrrS40z/M7kPyjfl04g==",
508
+      "cpu": [
509
+        "x64"
510
+      ],
511
+      "license": "Apache-2.0 AND LGPL-3.0-or-later",
512
+      "optional": true,
513
+      "os": [
514
+        "win32"
515
+      ],
516
+      "engines": {
517
+        "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
518
+      },
519
+      "funding": {
520
+        "url": "https://opencollective.com/libvips"
521
+      }
522
+    },
93 523
     "node_modules/@jridgewell/resolve-uri": {
94 524
       "version": "3.1.2",
95 525
       "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
@@ -438,6 +868,16 @@
438 868
         "@types/send": "*"
439 869
       }
440 870
     },
871
+    "node_modules/@types/sharp": {
872
+      "version": "0.31.1",
873
+      "resolved": "https://registry.npmjs.org/@types/sharp/-/sharp-0.31.1.tgz",
874
+      "integrity": "sha512-5nWwamN9ZFHXaYEincMSuza8nNfOof8nmO+mcI+Agx1uMUk4/pQnNIcix+9rLPXzKrm1pS34+6WRDbDV0Jn7ag==",
875
+      "dev": true,
876
+      "license": "MIT",
877
+      "dependencies": {
878
+        "@types/node": "*"
879
+      }
880
+    },
441 881
     "node_modules/@types/yauzl": {
442 882
       "version": "2.10.3",
443 883
       "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.3.tgz",
@@ -785,6 +1225,47 @@
785 1225
         "node": ">=20"
786 1226
       }
787 1227
     },
1228
+    "node_modules/color": {
1229
+      "version": "4.2.3",
1230
+      "resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz",
1231
+      "integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==",
1232
+      "license": "MIT",
1233
+      "dependencies": {
1234
+        "color-convert": "^2.0.1",
1235
+        "color-string": "^1.9.0"
1236
+      },
1237
+      "engines": {
1238
+        "node": ">=12.5.0"
1239
+      }
1240
+    },
1241
+    "node_modules/color-convert": {
1242
+      "version": "2.0.1",
1243
+      "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
1244
+      "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
1245
+      "license": "MIT",
1246
+      "dependencies": {
1247
+        "color-name": "~1.1.4"
1248
+      },
1249
+      "engines": {
1250
+        "node": ">=7.0.0"
1251
+      }
1252
+    },
1253
+    "node_modules/color-name": {
1254
+      "version": "1.1.4",
1255
+      "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
1256
+      "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
1257
+      "license": "MIT"
1258
+    },
1259
+    "node_modules/color-string": {
1260
+      "version": "1.9.1",
1261
+      "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz",
1262
+      "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==",
1263
+      "license": "MIT",
1264
+      "dependencies": {
1265
+        "color-name": "^1.0.0",
1266
+        "simple-swizzle": "^0.2.2"
1267
+      }
1268
+    },
788 1269
     "node_modules/combined-stream": {
789 1270
       "version": "1.0.8",
790 1271
       "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
@@ -983,6 +1464,15 @@
983 1464
         "node": ">= 0.8"
984 1465
       }
985 1466
     },
1467
+    "node_modules/detect-libc": {
1468
+      "version": "2.0.4",
1469
+      "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.4.tgz",
1470
+      "integrity": "sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==",
1471
+      "license": "Apache-2.0",
1472
+      "engines": {
1473
+        "node": ">=8"
1474
+      }
1475
+    },
986 1476
     "node_modules/diff": {
987 1477
       "version": "4.0.2",
988 1478
       "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz",
@@ -1666,6 +2156,12 @@
1666 2156
         "node": ">= 0.10"
1667 2157
       }
1668 2158
     },
2159
+    "node_modules/is-arrayish": {
2160
+      "version": "0.3.2",
2161
+      "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz",
2162
+      "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==",
2163
+      "license": "MIT"
2164
+    },
1669 2165
     "node_modules/is-binary-path": {
1670 2166
       "version": "2.1.0",
1671 2167
       "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
@@ -2637,6 +3133,48 @@
2637 3133
       "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==",
2638 3134
       "license": "ISC"
2639 3135
     },
3136
+    "node_modules/sharp": {
3137
+      "version": "0.34.3",
3138
+      "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.34.3.tgz",
3139
+      "integrity": "sha512-eX2IQ6nFohW4DbvHIOLRB3MHFpYqaqvXd3Tp5e/T/dSH83fxaNJQRvDMhASmkNTsNTVF2/OOopzRCt7xokgPfg==",
3140
+      "hasInstallScript": true,
3141
+      "license": "Apache-2.0",
3142
+      "dependencies": {
3143
+        "color": "^4.2.3",
3144
+        "detect-libc": "^2.0.4",
3145
+        "semver": "^7.7.2"
3146
+      },
3147
+      "engines": {
3148
+        "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
3149
+      },
3150
+      "funding": {
3151
+        "url": "https://opencollective.com/libvips"
3152
+      },
3153
+      "optionalDependencies": {
3154
+        "@img/sharp-darwin-arm64": "0.34.3",
3155
+        "@img/sharp-darwin-x64": "0.34.3",
3156
+        "@img/sharp-libvips-darwin-arm64": "1.2.0",
3157
+        "@img/sharp-libvips-darwin-x64": "1.2.0",
3158
+        "@img/sharp-libvips-linux-arm": "1.2.0",
3159
+        "@img/sharp-libvips-linux-arm64": "1.2.0",
3160
+        "@img/sharp-libvips-linux-ppc64": "1.2.0",
3161
+        "@img/sharp-libvips-linux-s390x": "1.2.0",
3162
+        "@img/sharp-libvips-linux-x64": "1.2.0",
3163
+        "@img/sharp-libvips-linuxmusl-arm64": "1.2.0",
3164
+        "@img/sharp-libvips-linuxmusl-x64": "1.2.0",
3165
+        "@img/sharp-linux-arm": "0.34.3",
3166
+        "@img/sharp-linux-arm64": "0.34.3",
3167
+        "@img/sharp-linux-ppc64": "0.34.3",
3168
+        "@img/sharp-linux-s390x": "0.34.3",
3169
+        "@img/sharp-linux-x64": "0.34.3",
3170
+        "@img/sharp-linuxmusl-arm64": "0.34.3",
3171
+        "@img/sharp-linuxmusl-x64": "0.34.3",
3172
+        "@img/sharp-wasm32": "0.34.3",
3173
+        "@img/sharp-win32-arm64": "0.34.3",
3174
+        "@img/sharp-win32-ia32": "0.34.3",
3175
+        "@img/sharp-win32-x64": "0.34.3"
3176
+      }
3177
+    },
2640 3178
     "node_modules/side-channel": {
2641 3179
       "version": "1.1.0",
2642 3180
       "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz",
@@ -2709,6 +3247,15 @@
2709 3247
         "url": "https://github.com/sponsors/ljharb"
2710 3248
       }
2711 3249
     },
3250
+    "node_modules/simple-swizzle": {
3251
+      "version": "0.2.2",
3252
+      "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz",
3253
+      "integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==",
3254
+      "license": "MIT",
3255
+      "dependencies": {
3256
+        "is-arrayish": "^0.3.1"
3257
+      }
3258
+    },
2712 3259
     "node_modules/simple-update-notifier": {
2713 3260
       "version": "2.0.0",
2714 3261
       "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz",

+ 2 - 0
package.json

@@ -34,6 +34,7 @@
34 34
     "node-cron": "^4.2.1",
35 35
     "pg": "^8.16.2",
36 36
     "qs": "^6.14.0",
37
+    "sharp": "^0.34.3",
37 38
     "zod": "^4.0.5"
38 39
   },
39 40
   "devDependencies": {
@@ -45,6 +46,7 @@
45 46
     "@types/keycloak-connect": "^4.5.4",
46 47
     "@types/multer": "^2.0.0",
47 48
     "@types/node": "^24.0.14",
49
+    "@types/sharp": "^0.31.1",
48 50
     "nodemon": "^3.1.10",
49 51
     "prisma": "^6.10.1",
50 52
     "ts-node": "^10.9.2",

+ 2 - 2
prisma/seeders/HospitalSeeder.ts

@@ -5,8 +5,8 @@ const prisma = new PrismaClient();
5 5
 
6 6
 export async function seedHospitals(): Promise<void> {
7 7
     try {
8
-        const sales1 = await prisma.userKeycloak.findFirst({ where: { id: '1b50e497-159f-49c2-aa4d-466e72d796f0' } });
9
-        const sales2 = await prisma.userKeycloak.findFirst({ where: { id: '0ec065d8-547c-46e3-9980-df36b9648767' } });
8
+        const sales1 = await prisma.userKeycloak.findFirst({ where: { id: 'd4c7941b-a180-46dd-ab2c-e9e4d4a53e96' } });
9
+        const sales2 = await prisma.userKeycloak.findFirst({ where: { id: '9a9833ec-dd95-4445-b64e-343335815039' } });
10 10
 
11 11
         if (!sales1 || !sales2) {
12 12
             throw new Error('User sales1 or sales2 not found');

+ 2 - 2
prisma/seeders/UserAreaSeeder.ts

@@ -3,8 +3,8 @@ import prisma from '../../src/prisma/PrismaClient';
3 3
 export async function seedUserAreas(): Promise<void> {
4 4
     try {
5 5
         // Ambil user dengan username 'sales1' dan 'sales2'
6
-        const sales1 = await prisma.userKeycloak.findFirst({ where: { id: '1b50e497-159f-49c2-aa4d-466e72d796f0' } });
7
-        const sales2 = await prisma.userKeycloak.findFirst({ where: { id: '0ec065d8-547c-46e3-9980-df36b9648767' } });
6
+        const sales1 = await prisma.userKeycloak.findFirst({ where: { id: 'd4c7941b-a180-46dd-ab2c-e9e4d4a53e96' } });
7
+        const sales2 = await prisma.userKeycloak.findFirst({ where: { id: '9a9833ec-dd95-4445-b64e-343335815039' } });
8 8
 
9 9
         if (!sales1 || !sales2) {
10 10
             throw new Error('User sales1 or sales2 not found');

+ 1 - 1
prisma/seeders/VendorSeeder.ts

@@ -13,7 +13,7 @@ export async function seedVendors(): Promise<void> {
13 13
         // Cari user dengan username admin1
14 14
         const adminUser = await prisma.userKeycloak.findFirst({
15 15
             where: {
16
-                id: 'd3dcbbbd-fc92-45cf-9520-d0a6859358f6',
16
+                id: '9727ba1a-2266-4699-b3b9-9656f16c9785',
17 17
             },
18 18
         });
19 19
 

+ 2 - 22
src/middleware/UploadImage.ts

@@ -1,26 +1,6 @@
1
-import multer, { StorageEngine } from 'multer';
2
-import path from 'path';
3
-import fs from 'fs';
1
+import multer from 'multer';
4 2
 
5
-// Pastikan folder storage/img ada
6
-const storagePath = path.join(__dirname, '../../storage/img');
7
-if (!fs.existsSync(storagePath)) {
8
-    fs.mkdirSync(storagePath, { recursive: true });
9
-}
10
-
11
-// Konfigurasi penyimpanan
12
-const storage: StorageEngine = multer.diskStorage({
13
-    destination: (_req, _file, cb) => {
14
-        cb(null, storagePath);
15
-    },
16
-    filename: (_req, file, cb) => {
17
-        const ext = path.extname(file.originalname);
18
-        const filename = `${Date.now()}-${Math.round(Math.random() * 1e9)}${ext}`;
19
-        cb(null, filename);
20
-    }
21
-});
22
-
23
-// Inisialisasi multer
3
+const storage = multer.memoryStorage();
24 4
 const upload = multer({ storage });
25 5
 
26 6
 export default upload;

+ 72 - 2
src/services/admin/HospitalService.ts

@@ -9,6 +9,9 @@ import prisma from '../../prisma/PrismaClient';
9 9
 import { ProgressStatus } from '@prisma/client';
10 10
 import { CustomRequest } from '../../types/token/CustomRequest';
11 11
 import { HospitalRequestDTO } from '../../types/admin/hospital/HospitalDTO';
12
+import path from 'path';
13
+import fs from 'fs/promises';
14
+import sharp from 'sharp';
12 15
 
13 16
 interface PaginationParams {
14 17
     page: number;
@@ -97,8 +100,42 @@ export const storeHospitalService = async (validateData: HospitalRequestDTO, req
97 100
     // const imagePath = `/storage/img/${req.file.filename}`;
98 101
 
99 102
     let imagePath: string | null = null;
103
+    // if (req.file) {
104
+    //     imagePath = `/storage/img/${req.file.filename}`;
105
+    // }
106
+
107
+    // if (req.file) {
108
+    //     const ext = path.extname(req.file.originalname);
109
+    //     const filename = `${Date.now()}-${Math.round(Math.random() * 1e9)}${ext}`;
110
+    //     const fullPath = path.join(__dirname, '../../../storage/img', filename);
111
+
112
+    //     await fs.promises.writeFile(fullPath, req.file.buffer);
113
+
114
+    //     imagePath = `/storage/img/${filename}`;
115
+    // }
116
+
100 117
     if (req.file) {
101
-        imagePath = `/storage/img/${req.file.filename}`;
118
+        const ext = '.jpg';
119
+        const filename = `${Date.now()}-${Math.round(Math.random() * 1e9)}${ext}`;
120
+        const fullPath = path.join(__dirname, '../../../storage/img', filename);
121
+
122
+        let outputBuffer = await sharp(req.file.buffer)
123
+            .resize({ width: 1280, withoutEnlargement: true })
124
+            .toFormat('jpeg', { quality: 80 })
125
+            .toBuffer();
126
+
127
+        // Pastikan size <= 500 KB
128
+        let quality = 80;
129
+        while (outputBuffer.length > 500 * 1024 && quality > 20) {
130
+            quality -= 10;
131
+            outputBuffer = await sharp(req.file.buffer)
132
+                .resize({ width: 1280, withoutEnlargement: true })
133
+                .toFormat('jpeg', { quality })
134
+                .toBuffer();
135
+        }
136
+
137
+        await fs.writeFile(fullPath, outputBuffer);
138
+        imagePath = `/storage/img/${filename}`;
102 139
     }
103 140
 
104 141
     let { latitude, longitude, gmaps_url: gmapsUrl } = validateData;
@@ -194,7 +231,40 @@ export const updateHospitalService = async (validateData: HospitalRequestDTO, id
194 231
     }
195 232
 
196 233
     let imagePath = hospital.image;
197
-    if (req.file) imagePath = `/storage/img/${req.file.filename}`;
234
+    // if (req.file) imagePath = `/storage/img/${req.file.filename}`;
235
+    // if (req.file) {
236
+    //     const ext = path.extname(req.file.originalname);
237
+    //     const filename = `${Date.now()}-${Math.round(Math.random() * 1e9)}${ext}`;
238
+    //     const fullPath = path.join(__dirname, '../../../storage/img', filename);
239
+
240
+    //     await fs.promises.writeFile(fullPath, req.file.buffer);
241
+
242
+    //     imagePath = `/storage/img/${filename}`;
243
+    // }
244
+
245
+    if (req.file) {
246
+        const ext = '.jpg';
247
+        const filename = `${Date.now()}-${Math.round(Math.random() * 1e9)}${ext}`;
248
+        const fullPath = path.join(__dirname, '../../../storage/img', filename);
249
+
250
+        let outputBuffer = await sharp(req.file.buffer)
251
+            .resize({ width: 1280, withoutEnlargement: true })
252
+            .toFormat('jpeg', { quality: 80 })
253
+            .toBuffer();
254
+
255
+        // Pastikan size <= 500 KB
256
+        let quality = 80;
257
+        while (outputBuffer.length > 500 * 1024 && quality > 20) {
258
+            quality -= 10;
259
+            outputBuffer = await sharp(req.file.buffer)
260
+                .resize({ width: 1280, withoutEnlargement: true })
261
+                .toFormat('jpeg', { quality })
262
+                .toBuffer();
263
+        }
264
+
265
+        await fs.writeFile(fullPath, outputBuffer);
266
+        imagePath = `/storage/img/${filename}`;
267
+    }
198 268
 
199 269
     let latitude = hospital.latitude;
200 270
     let longitude = hospital.longitude;

+ 73 - 2
src/services/sales/HospitalService.ts

@@ -8,6 +8,10 @@ import CityRepository from '../../repository/admin/CityRepository';
8 8
 import HospitalRepository from '../../repository/admin/HospitalRepository';
9 9
 import { CustomRequest } from '../../types/token/CustomRequest';
10 10
 import { HospitalRequestDTO } from '../../types/sales/hospital/HospitalDTO';
11
+import path from 'path';
12
+// import fs from 'fs';
13
+import fs from 'fs/promises';
14
+import sharp from 'sharp';
11 15
 
12 16
 const prisma = new PrismaClient();
13 17
 
@@ -81,8 +85,42 @@ export const storeHospitalService = async (validateData: HospitalRequestDTO, req
81 85
     // const imagePath = `/storage/img/${req.file.filename}`;
82 86
 
83 87
     let imagePath: string | null = null;
88
+    // if (req.file) {
89
+    //     imagePath = `/storage/img/${req.file.filename}`;
90
+    // }
91
+
92
+    // if (req.file) {
93
+    //     const ext = path.extname(req.file.originalname);
94
+    //     const filename = `${Date.now()}-${Math.round(Math.random() * 1e9)}${ext}`;
95
+    //     const fullPath = path.join(__dirname, '../../../storage/img', filename);
96
+
97
+    //     await fs.promises.writeFile(fullPath, req.file.buffer);
98
+
99
+    //     imagePath = `/storage/img/${filename}`;
100
+    // }
101
+
84 102
     if (req.file) {
85
-        imagePath = `/storage/img/${req.file.filename}`;
103
+        const ext = '.jpg';
104
+        const filename = `${Date.now()}-${Math.round(Math.random() * 1e9)}${ext}`;
105
+        const fullPath = path.join(__dirname, '../../../storage/img', filename);
106
+
107
+        let outputBuffer = await sharp(req.file.buffer)
108
+            .resize({ width: 1280, withoutEnlargement: true })
109
+            .toFormat('jpeg', { quality: 80 })
110
+            .toBuffer();
111
+
112
+        // Pastikan size <= 500 KB
113
+        let quality = 80;
114
+        while (outputBuffer.length > 500 * 1024 && quality > 20) {
115
+            quality -= 10;
116
+            outputBuffer = await sharp(req.file.buffer)
117
+                .resize({ width: 1280, withoutEnlargement: true })
118
+                .toFormat('jpeg', { quality })
119
+                .toBuffer();
120
+        }
121
+
122
+        await fs.writeFile(fullPath, outputBuffer);
123
+        imagePath = `/storage/img/${filename}`;
86 124
     }
87 125
 
88 126
     let { latitude = null, longitude = null, gmaps_url: gmapsUrl = null } = validateData;
@@ -186,7 +224,40 @@ export const updateHospitalService = async (validateData: HospitalRequestDTO, id
186 224
     }
187 225
 
188 226
     let imagePath = hospital.image;
189
-    if (req.file) imagePath = `/storage/img/${req.file.filename}`;
227
+    // if (req.file) imagePath = `/storage/img/${req.file.filename}`;
228
+    // if (req.file) {
229
+    //     const ext = path.extname(req.file.originalname);
230
+    //     const filename = `${Date.now()}-${Math.round(Math.random() * 1e9)}${ext}`;
231
+    //     const fullPath = path.join(__dirname, '../../../storage/img', filename);
232
+
233
+    //     await fs.promises.writeFile(fullPath, req.file.buffer);
234
+
235
+    //     imagePath = `/storage/img/${filename}`;
236
+    // }
237
+
238
+    if (req.file) {
239
+        const ext = '.jpg';
240
+        const filename = `${Date.now()}-${Math.round(Math.random() * 1e9)}${ext}`;
241
+        const fullPath = path.join(__dirname, '../../../storage/img', filename);
242
+
243
+        let outputBuffer = await sharp(req.file.buffer)
244
+            .resize({ width: 1280, withoutEnlargement: true })
245
+            .toFormat('jpeg', { quality: 80 })
246
+            .toBuffer();
247
+
248
+        // Pastikan size <= 500 KB
249
+        let quality = 80;
250
+        while (outputBuffer.length > 500 * 1024 && quality > 20) {
251
+            quality -= 10;
252
+            outputBuffer = await sharp(req.file.buffer)
253
+                .resize({ width: 1280, withoutEnlargement: true })
254
+                .toFormat('jpeg', { quality })
255
+                .toBuffer();
256
+        }
257
+
258
+        await fs.writeFile(fullPath, outputBuffer);
259
+        imagePath = `/storage/img/${filename}`;
260
+    }
190 261
 
191 262
     let latitude = hospital.latitude;
192 263
     let longitude = hospital.longitude;