@sync-in/server 1.7.0 → 1.8.1

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 (275) hide show
  1. package/CHANGELOG.md +173 -58
  2. package/environment/environment.dist.yaml +6 -3
  3. package/migrations/0003_giant_luckman.sql +6 -0
  4. package/migrations/meta/0003_snapshot.json +2463 -0
  5. package/migrations/meta/_journal.json +7 -0
  6. package/package.json +19 -17
  7. package/server/app.bootstrap.js +5 -2
  8. package/server/app.bootstrap.js.map +1 -1
  9. package/server/app.constants.js +0 -4
  10. package/server/app.constants.js.map +1 -1
  11. package/server/app.service.js +7 -6
  12. package/server/app.service.js.map +1 -1
  13. package/server/applications/files/constants/only-office.js +12 -0
  14. package/server/applications/files/constants/only-office.js.map +1 -1
  15. package/server/applications/files/files.config.js +5 -0
  16. package/server/applications/files/files.config.js.map +1 -1
  17. package/server/applications/files/files.controller.js +12 -4
  18. package/server/applications/files/files.controller.js.map +1 -1
  19. package/server/applications/files/files.controller.spec.js +18 -4
  20. package/server/applications/files/files.controller.spec.js.map +1 -1
  21. package/server/applications/files/services/files-content-manager.service.js +6 -6
  22. package/server/applications/files/services/files-content-manager.service.js.map +1 -1
  23. package/server/applications/files/services/files-manager.service.js +4 -4
  24. package/server/applications/files/services/files-manager.service.js.map +1 -1
  25. package/server/applications/files/services/files-methods.service.js +4 -7
  26. package/server/applications/files/services/files-methods.service.js.map +1 -1
  27. package/server/applications/files/services/files-only-office-manager.service.js +2 -2
  28. package/server/applications/files/services/files-only-office-manager.service.js.map +1 -1
  29. package/server/applications/files/services/files-parser.service.js +6 -3
  30. package/server/applications/files/services/files-parser.service.js.map +1 -1
  31. package/server/applications/files/services/files-scheduler.service.js +51 -3
  32. package/server/applications/files/services/files-scheduler.service.js.map +1 -1
  33. package/server/applications/files/services/files-search-manager.service.js +4 -0
  34. package/server/applications/files/services/files-search-manager.service.js.map +1 -1
  35. package/server/applications/files/utils/doc-textify/adapters/pdf.js +6 -8
  36. package/server/applications/files/utils/doc-textify/adapters/pdf.js.map +1 -1
  37. package/server/applications/notifications/i18n/de.js +56 -0
  38. package/server/applications/notifications/i18n/de.js.map +1 -0
  39. package/server/applications/notifications/i18n/es.js +52 -0
  40. package/server/applications/notifications/i18n/es.js.map +1 -0
  41. package/server/applications/notifications/i18n/hi.js +52 -0
  42. package/server/applications/notifications/i18n/hi.js.map +1 -0
  43. package/server/applications/notifications/i18n/index.js +73 -8
  44. package/server/applications/notifications/i18n/index.js.map +1 -1
  45. package/server/applications/notifications/i18n/it.js +52 -0
  46. package/server/applications/notifications/i18n/it.js.map +1 -0
  47. package/server/applications/notifications/i18n/ja.js +52 -0
  48. package/server/applications/notifications/i18n/ja.js.map +1 -0
  49. package/server/applications/notifications/i18n/ko.js +52 -0
  50. package/server/applications/notifications/i18n/ko.js.map +1 -0
  51. package/server/applications/notifications/i18n/pl.js +52 -0
  52. package/server/applications/notifications/i18n/pl.js.map +1 -0
  53. package/server/applications/notifications/i18n/pt.js +52 -0
  54. package/server/applications/notifications/i18n/pt.js.map +1 -0
  55. package/server/applications/notifications/i18n/pt_br.js +52 -0
  56. package/server/applications/notifications/i18n/pt_br.js.map +1 -0
  57. package/server/applications/notifications/i18n/ru.js +52 -0
  58. package/server/applications/notifications/i18n/ru.js.map +1 -0
  59. package/server/applications/notifications/i18n/tr.js +52 -0
  60. package/server/applications/notifications/i18n/tr.js.map +1 -0
  61. package/server/applications/notifications/i18n/zh.js +52 -0
  62. package/server/applications/notifications/i18n/zh.js.map +1 -0
  63. package/server/applications/notifications/mails/models.js +6 -7
  64. package/server/applications/notifications/mails/models.js.map +1 -1
  65. package/server/applications/notifications/services/notifications-manager.service.js.map +1 -1
  66. package/server/applications/shares/dto/create-or-update-share.dto.js +11 -0
  67. package/server/applications/shares/dto/create-or-update-share.dto.js.map +1 -1
  68. package/server/applications/shares/interfaces/share-props.interface.js.map +1 -1
  69. package/server/applications/shares/schemas/share.interface.js.map +1 -1
  70. package/server/applications/shares/schemas/shares.schema.js +9 -0
  71. package/server/applications/shares/schemas/shares.schema.js.map +1 -1
  72. package/server/applications/shares/services/shares-manager.service.js +46 -17
  73. package/server/applications/shares/services/shares-manager.service.js.map +1 -1
  74. package/server/applications/shares/services/shares-queries.service.js +24 -5
  75. package/server/applications/shares/services/shares-queries.service.js.map +1 -1
  76. package/server/applications/spaces/constants/cache.js +4 -0
  77. package/server/applications/spaces/constants/cache.js.map +1 -1
  78. package/server/applications/spaces/dto/create-or-update-space.dto.js +5 -0
  79. package/server/applications/spaces/dto/create-or-update-space.dto.js.map +1 -1
  80. package/server/applications/spaces/guards/space.guard.js +3 -3
  81. package/server/applications/spaces/guards/space.guard.js.map +1 -1
  82. package/server/applications/spaces/models/space-props.model.js.map +1 -1
  83. package/server/applications/spaces/models/space.model.js.map +1 -1
  84. package/server/applications/spaces/schemas/space.interface.js.map +1 -1
  85. package/server/applications/spaces/schemas/spaces.schema.js +1 -0
  86. package/server/applications/spaces/schemas/spaces.schema.js.map +1 -1
  87. package/server/applications/spaces/services/spaces-browser.service.js.map +1 -1
  88. package/server/applications/spaces/services/spaces-manager.service.js +34 -31
  89. package/server/applications/spaces/services/spaces-manager.service.js.map +1 -1
  90. package/server/applications/spaces/services/spaces-queries.service.js +23 -7
  91. package/server/applications/spaces/services/spaces-queries.service.js.map +1 -1
  92. package/server/applications/spaces/services/spaces-scheduler.service.js +21 -20
  93. package/server/applications/spaces/services/spaces-scheduler.service.js.map +1 -1
  94. package/server/applications/spaces/spaces.controller.js +4 -2
  95. package/server/applications/spaces/spaces.controller.js.map +1 -1
  96. package/server/applications/spaces/utils/paths.js +14 -16
  97. package/server/applications/spaces/utils/paths.js.map +1 -1
  98. package/server/applications/sync/services/sync-manager.service.js +4 -3
  99. package/server/applications/sync/services/sync-manager.service.js.map +1 -1
  100. package/server/applications/sync/services/sync-paths-manager.service.js +1 -1
  101. package/server/applications/sync/services/sync-paths-manager.service.js.map +1 -1
  102. package/server/applications/sync/services/sync-paths-manager.service.spec.js +1 -1
  103. package/server/applications/sync/services/sync-paths-manager.service.spec.js.map +1 -1
  104. package/server/applications/sync/sync.controller.js +2 -1
  105. package/server/applications/sync/sync.controller.js.map +1 -1
  106. package/server/applications/users/constants/routes.js +5 -0
  107. package/server/applications/users/constants/routes.js.map +1 -1
  108. package/server/applications/users/constants/user.js +0 -16
  109. package/server/applications/users/constants/user.js.map +1 -1
  110. package/server/applications/users/dto/user-properties.dto.js +10 -0
  111. package/server/applications/users/dto/user-properties.dto.js.map +1 -1
  112. package/server/applications/users/models/user.model.js.map +1 -1
  113. package/server/applications/users/schemas/user.interface.js.map +1 -1
  114. package/server/applications/users/schemas/users.schema.js +3 -2
  115. package/server/applications/users/schemas/users.schema.js.map +1 -1
  116. package/server/applications/users/services/admin-users-manager.service.js +1 -0
  117. package/server/applications/users/services/admin-users-manager.service.js.map +1 -1
  118. package/server/applications/users/services/admin-users-manager.service.spec.js +2 -1
  119. package/server/applications/users/services/admin-users-manager.service.spec.js.map +1 -1
  120. package/server/applications/users/services/users-manager.service.js +7 -2
  121. package/server/applications/users/services/users-manager.service.js.map +1 -1
  122. package/server/applications/users/services/users-manager.service.spec.js +1 -0
  123. package/server/applications/users/services/users-manager.service.spec.js.map +1 -1
  124. package/server/applications/users/services/users-queries.service.js +18 -4
  125. package/server/applications/users/services/users-queries.service.js.map +1 -1
  126. package/server/applications/users/users.controller.js +15 -0
  127. package/server/applications/users/users.controller.js.map +1 -1
  128. package/server/applications/users/users.gateway.js +6 -0
  129. package/server/applications/users/users.gateway.js.map +1 -1
  130. package/server/applications/users/utils/test.js +2 -2
  131. package/server/applications/users/utils/test.js.map +1 -1
  132. package/server/applications/webdav/constants/routes.js +2 -2
  133. package/server/applications/webdav/constants/routes.js.map +1 -1
  134. package/server/applications/webdav/constants/webdav.js +2 -2
  135. package/server/applications/webdav/constants/webdav.js.map +1 -1
  136. package/server/applications/webdav/filters/webdav.filter.js +2 -2
  137. package/server/applications/webdav/filters/webdav.filter.js.map +1 -1
  138. package/server/applications/webdav/filters/webdav.filter.spec.js +2 -2
  139. package/server/applications/webdav/filters/webdav.filter.spec.js.map +1 -1
  140. package/server/applications/webdav/services/webdav-methods.service.js +3 -2
  141. package/server/applications/webdav/services/webdav-methods.service.js.map +1 -1
  142. package/server/applications/webdav/utils/webdav.js +1 -2
  143. package/server/applications/webdav/utils/webdav.js.map +1 -1
  144. package/server/authentication/auth.config.js +2 -2
  145. package/server/authentication/auth.config.js.map +1 -1
  146. package/server/authentication/guards/auth-basic.strategy.js +2 -2
  147. package/server/authentication/guards/auth-basic.strategy.js.map +1 -1
  148. package/server/common/i18n.js +52 -0
  149. package/server/common/i18n.js.map +1 -0
  150. package/server/common/image.js +63 -43
  151. package/server/common/image.js.map +1 -1
  152. package/server/common/interfaces.js.map +1 -1
  153. package/server/common/shared.js +5 -2
  154. package/server/common/shared.js.map +1 -1
  155. package/server/configuration/config.validation.js +3 -3
  156. package/server/configuration/config.validation.js.map +1 -1
  157. package/server/infrastructure/cache/adapters/mysql-cache.adapter.js +8 -6
  158. package/server/infrastructure/cache/adapters/mysql-cache.adapter.js.map +1 -1
  159. package/server/infrastructure/cache/adapters/redis-cache.adapter.js +22 -17
  160. package/server/infrastructure/cache/adapters/redis-cache.adapter.js.map +1 -1
  161. package/server/infrastructure/cache/cache.e2e-spec.js +1 -0
  162. package/server/infrastructure/cache/cache.e2e-spec.js.map +1 -1
  163. package/server/infrastructure/cache/cache.module.js +1 -14
  164. package/server/infrastructure/cache/cache.module.js.map +1 -1
  165. package/server/infrastructure/cache/services/cache.service.js.map +1 -1
  166. package/server/infrastructure/database/database.module.js +20 -1
  167. package/server/infrastructure/database/database.module.js.map +1 -1
  168. package/server/infrastructure/database/utils.js +48 -0
  169. package/server/infrastructure/database/utils.js.map +1 -1
  170. package/server/infrastructure/scheduler/scheduler.module.js +1 -1
  171. package/server/infrastructure/scheduler/scheduler.module.js.map +1 -1
  172. package/server/infrastructure/websocket/adapters/cluster.adapter.js +1 -3
  173. package/server/infrastructure/websocket/adapters/cluster.adapter.js.map +1 -1
  174. package/static/3rdpartylicenses.txt +137 -163
  175. package/static/chunk-2KLC4T2Z.js +1 -0
  176. package/static/chunk-2VMSXRCB.js +12 -0
  177. package/static/chunk-3GMLWAFZ.js +1 -0
  178. package/static/chunk-3OHSRRKH.js +4 -0
  179. package/static/chunk-3R4WKOHQ.js +1 -0
  180. package/static/{chunk-7ITZXYYJ.js → chunk-3R74L4UU.js} +1 -1
  181. package/static/chunk-3XVM35O2.js +1 -0
  182. package/static/chunk-3YVRP3VM.js +2 -0
  183. package/static/chunk-5NMSIIQB.js +1 -0
  184. package/static/chunk-5UKZLU5H.js +1 -0
  185. package/static/chunk-AF24EYXU.js +1 -0
  186. package/static/chunk-AKQVEHO6.js +2 -0
  187. package/static/chunk-BCVX464U.js +2 -0
  188. package/static/chunk-BQV4FRM6.js +1 -0
  189. package/static/{chunk-EVIE5F2U.js → chunk-CETH7UYS.js} +1 -1
  190. package/static/chunk-CHJ64RJM.js +1 -0
  191. package/static/chunk-DIT6W7VM.js +562 -0
  192. package/static/chunk-DKSEQTMX.js +1 -0
  193. package/static/chunk-DM4NXKEP.js +1 -0
  194. package/static/chunk-DPUVSXRB.js +1 -0
  195. package/static/chunk-DSWEWLXJ.js +1 -0
  196. package/static/chunk-FJE6BOFL.js +1 -0
  197. package/static/chunk-FZ3JPGYZ.js +1 -0
  198. package/static/chunk-IQSKQXC3.js +1 -0
  199. package/static/chunk-ITUFI2BJ.js +1 -0
  200. package/static/chunk-JPT5WEAT.js +1 -0
  201. package/static/chunk-LCTZJ537.js +1 -0
  202. package/static/chunk-LK2UCQJ6.js +1 -0
  203. package/static/chunk-LNTUR3GU.js +1 -0
  204. package/static/chunk-LP5TBXEN.js +7 -0
  205. package/static/{chunk-IPAC4VAF.js → chunk-LVSNIS5P.js} +1 -1
  206. package/static/{chunk-SIPE37PA.js → chunk-MTVSJTIW.js} +1 -1
  207. package/static/chunk-N3U6637P.js +1 -0
  208. package/static/chunk-NNV4OXSB.js +1 -0
  209. package/static/chunk-O6FYXVHI.js +1 -0
  210. package/static/chunk-OOGP4WSH.js +2 -0
  211. package/static/chunk-PB4AIT7O.js +1 -0
  212. package/static/chunk-PCWDQPOM.js +2 -0
  213. package/static/chunk-PNR6M34W.js +1 -0
  214. package/static/chunk-PVDHBQRM.js +1 -0
  215. package/static/chunk-Q5KM7LTX.js +1 -0
  216. package/static/chunk-QHC6ZPQ4.js +1 -0
  217. package/static/chunk-QMRBZHE4.js +1 -0
  218. package/static/chunk-QO6BTONN.js +1 -0
  219. package/static/chunk-QSJRY3TF.js +1 -0
  220. package/static/chunk-QUUIRSYT.js +1 -0
  221. package/static/chunk-RFH46UW3.js +1 -0
  222. package/static/{chunk-PTGDOWV3.js → chunk-RSXHRKM5.js} +1 -1
  223. package/static/chunk-RV3VZJPZ.js +1 -0
  224. package/static/{chunk-QNJFQVYI.js → chunk-S7HNXVRB.js} +1 -1
  225. package/static/chunk-SBZ572Q4.js +2 -0
  226. package/static/chunk-SJR5R3Y4.js +1 -0
  227. package/static/chunk-SLHTEGRU.js +1 -0
  228. package/static/{chunk-SH5EVL4E.js → chunk-SSFF27P2.js} +1 -1
  229. package/static/chunk-UNCPXHHT.js +1 -0
  230. package/static/chunk-URHTCJ7G.js +1 -0
  231. package/static/chunk-V3LHHZYN.js +1 -0
  232. package/static/{chunk-DJYJ66UF.js → chunk-VJTXJ43D.js} +1 -1
  233. package/static/chunk-VQQKMY2C.js +1 -0
  234. package/static/{chunk-IQOALFYU.js → chunk-WSSU2HXE.js} +1 -1
  235. package/static/chunk-XDZGW64M.js +3 -0
  236. package/static/chunk-XTRDKGKG.js +1 -0
  237. package/static/chunk-YLWTEC3X.js +1 -0
  238. package/static/chunk-Z5J5F5SX.js +1 -0
  239. package/static/chunk-ZIJQRARU.js +1 -0
  240. package/static/index.html +2 -2
  241. package/static/main-4H5BJY3J.js +9 -0
  242. package/static/scripts-WRDOQIU5.js +24 -0
  243. package/static/{styles-A5VYX3CE.css → styles-2C2UNCNB.css} +1 -1
  244. package/server/applications/spaces/interfaces/space-quota.interface.js +0 -10
  245. package/server/applications/spaces/interfaces/space-quota.interface.js.map +0 -1
  246. package/static/chunk-22EANI6R.js +0 -1
  247. package/static/chunk-3GFGJYMK.js +0 -1
  248. package/static/chunk-4YGJGZZZ.js +0 -1
  249. package/static/chunk-5K7HEX3C.js +0 -27
  250. package/static/chunk-5KLMS6A4.js +0 -1
  251. package/static/chunk-ATP3BFHV.js +0 -562
  252. package/static/chunk-BB4G55KE.js +0 -1
  253. package/static/chunk-EWKSX76T.js +0 -1
  254. package/static/chunk-FHLACA7V.js +0 -1
  255. package/static/chunk-GCATNU55.js +0 -1
  256. package/static/chunk-GYODPCIE.js +0 -1
  257. package/static/chunk-HZTFYLM5.js +0 -1
  258. package/static/chunk-JSUKJT6Z.js +0 -1
  259. package/static/chunk-JXZCNFW7.js +0 -1
  260. package/static/chunk-LTGFCQR7.js +0 -1
  261. package/static/chunk-LV3PYKWO.js +0 -1
  262. package/static/chunk-N2WFNW6M.js +0 -7
  263. package/static/chunk-ORMRCEGT.js +0 -1
  264. package/static/chunk-OUTBJSMW.js +0 -1
  265. package/static/chunk-RS2PX32L.js +0 -1
  266. package/static/chunk-RSSWH3S2.js +0 -1
  267. package/static/chunk-RTRJ3KFH.js +0 -1
  268. package/static/chunk-TKTCBDOG.js +0 -1
  269. package/static/chunk-V6K2N46L.js +0 -1
  270. package/static/chunk-XLCCZSQL.js +0 -4
  271. package/static/chunk-YPEH66GG.js +0 -1
  272. package/static/chunk-YPOIUQ57.js +0 -1
  273. package/static/chunk-ZKCFO2OA.js +0 -4
  274. package/static/main-MZ7HWZXO.js +0 -9
  275. package/static/scripts-VZVAP2P4.js +0 -30
@@ -27,64 +27,67 @@ _export(exports, {
27
27
  },
28
28
  get svgMimeType () {
29
29
  return svgMimeType;
30
+ },
31
+ get webpMimeType () {
32
+ return webpMimeType;
30
33
  }
31
34
  });
32
- const _canvas = require("canvas");
33
35
  const _promises = /*#__PURE__*/ _interop_require_default(require("node:fs/promises"));
36
+ const _nodeos = /*#__PURE__*/ _interop_require_default(require("node:os"));
34
37
  const _nodepath = /*#__PURE__*/ _interop_require_default(require("node:path"));
38
+ const _nodeutil = require("node:util");
39
+ const _sharp = /*#__PURE__*/ _interop_require_default(require("sharp"));
40
+ const _texttosvg = /*#__PURE__*/ _interop_require_default(require("text-to-svg" // Sharp settings
41
+ ));
35
42
  function _interop_require_default(obj) {
36
43
  return obj && obj.__esModule ? obj : {
37
44
  default: obj
38
45
  };
39
46
  }
40
- (0, _canvas.registerFont)(_nodepath.default.join(__dirname, 'fonts', 'avatar.ttf'), {
41
- family: 'Avatar'
42
- });
47
+ // Sharp settings
48
+ _sharp.default.cache(false);
49
+ _sharp.default.concurrency(Math.min(2, _nodeos.default.cpus()?.length || 1));
43
50
  const pngMimeType = 'image/png';
44
51
  const svgMimeType = 'image/svg+xml';
52
+ const webpMimeType = 'image/webp';
53
+ const avatarSize = 256;
54
+ const fontPath = _nodepath.default.join(__dirname, 'fonts', 'avatar.ttf');
55
+ const loadTextToSVG = (0, _nodeutil.promisify)(_texttosvg.default.load.bind(_texttosvg.default));
56
+ let textToSvgCache = null;
45
57
  async function generateThumbnail(filePath, size) {
46
- const image = await (0, _canvas.loadImage)(filePath);
47
- let width = image.width;
48
- let height = image.height;
49
- // Calculate the new dimensions, maintaining the aspect ratio
50
- if (width > height) {
51
- if (width > size) {
52
- height *= size / width;
53
- width = size;
54
- }
55
- } else {
56
- if (height > size) {
57
- width *= size / height;
58
- height = size;
59
- }
60
- }
61
- // Set the canvas dimensions to the new dimensions
62
- const canvas = (0, _canvas.createCanvas)(width, height);
63
- // Draw the resized image on the canvas
64
- const ctx = canvas.getContext('2d');
65
- ctx.drawImage(image, 0, 0, width, height);
66
- return canvas.createPNGStream({
67
- compressionLevel: 0
58
+ return (0, _sharp.default)(filePath, {
59
+ sequentialRead: true,
60
+ limitInputPixels: 268e6 // protects against extremely large images
61
+ }).rotate().resize({
62
+ width: size,
63
+ height: size,
64
+ fit: 'inside',
65
+ kernel: 'nearest',
66
+ withoutEnlargement: true,
67
+ fastShrinkOnLoad: true // true by default, added for clarity
68
+ }).webp({
69
+ quality: 80,
70
+ effort: 0,
71
+ alphaQuality: 90
68
72
  });
69
73
  }
70
- function generateAvatar(initials) {
71
- const canvas = (0, _canvas.createCanvas)(256, 256);
72
- const ctx = canvas.getContext('2d');
73
- const text = initials;
74
+ async function generateAvatar(initials) {
75
+ const tts = await getTextToSvg();
74
76
  const { backgroundColor, foregroundColor } = randomColor();
75
- // Properties
76
- ctx.quality = 'best';
77
- // Draw background
78
- ctx.fillStyle = backgroundColor;
79
- ctx.fillRect(0, 0, canvas.width, canvas.height);
80
- // Draw text
81
- const height = 150;
82
- ctx.font = `${height}px "Avatar"`;
83
- ctx.fillStyle = foregroundColor;
84
- ctx.textAlign = 'center';
85
- ctx.textBaseline = 'middle';
86
- ctx.fillText(text, canvas.width / 2, canvas.height / 2.1);
87
- return canvas.createPNGStream();
77
+ const fontSize = fitFontSize(tts, initials, avatarSize * 0.8, 170);
78
+ const d = tts.getD(initials, {
79
+ x: avatarSize / 2,
80
+ y: avatarSize / 2.1,
81
+ fontSize,
82
+ anchor: 'center middle'
83
+ });
84
+ const svg = `
85
+ <svg width="${avatarSize}" height="${avatarSize}" viewBox="0 0 ${avatarSize} ${avatarSize}"
86
+ xmlns="http://www.w3.org/2000/svg">
87
+ <rect width="100%" height="100%" fill="${backgroundColor}"/>
88
+ <path d="${d}" fill="${foregroundColor}" />
89
+ </svg>`.trim();
90
+ return (0, _sharp.default)(Buffer.from(svg, 'utf8')).png();
88
91
  }
89
92
  async function convertImageToBase64(imgPath) {
90
93
  const base64String = await _promises.default.readFile(imgPath, {
@@ -109,5 +112,22 @@ function randomColor() {
109
112
  foregroundColor: brightness > 180 ? '#000000' : '#ffffff'
110
113
  };
111
114
  }
115
+ function fitFontSize(tts, text, box, start = 170) {
116
+ // Heuristic to make the text occupy ~80% of the available width
117
+ let size = start;
118
+ // Lower bound to prevent infinite loops when the font renders very small
119
+ while(size > 20){
120
+ const m = tts.getMetrics(text, {
121
+ fontSize: size,
122
+ anchor: 'center middle'
123
+ });
124
+ if (m.width <= box) break;
125
+ size -= 4;
126
+ }
127
+ return size;
128
+ }
129
+ function getTextToSvg() {
130
+ return textToSvgCache ??= loadTextToSVG(fontPath);
131
+ }
112
132
 
113
133
  //# sourceMappingURL=image.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../backend/src/common/image.ts"],"sourcesContent":["/*\n * Copyright (C) 2012-2025 Johan Legrand <johan.legrand@sync-in.com>\n * This file is part of Sync-in | The open source file sync and share solution\n * See the LICENSE file for licensing details\n */\n\nimport { createCanvas, loadImage, PNGStream, registerFont } from 'canvas'\nimport fs from 'node:fs/promises'\nimport path from 'node:path'\n\nregisterFont(path.join(__dirname, 'fonts', 'avatar.ttf'), { family: 'Avatar' })\n\nexport const pngMimeType = 'image/png'\nexport const svgMimeType = 'image/svg+xml'\n\nexport async function generateThumbnail(filePath: string, size: number) {\n const image = await loadImage(filePath)\n let width = image.width\n let height = image.height\n\n // Calculate the new dimensions, maintaining the aspect ratio\n if (width > height) {\n if (width > size) {\n height *= size / width\n width = size\n }\n } else {\n if (height > size) {\n width *= size / height\n height = size\n }\n }\n // Set the canvas dimensions to the new dimensions\n const canvas = createCanvas(width, height)\n // Draw the resized image on the canvas\n const ctx = canvas.getContext('2d')\n ctx.drawImage(image, 0, 0, width, height)\n\n return canvas.createPNGStream({ compressionLevel: 0 })\n}\n\nexport function generateAvatar(initials: string): PNGStream {\n const canvas = createCanvas(256, 256)\n const ctx = canvas.getContext('2d')\n const text = initials\n const { backgroundColor, foregroundColor } = randomColor()\n\n // Properties\n ctx.quality = 'best'\n\n // Draw background\n ctx.fillStyle = backgroundColor\n ctx.fillRect(0, 0, canvas.width, canvas.height)\n\n // Draw text\n const height = 150\n ctx.font = `${height}px \"Avatar\"`\n ctx.fillStyle = foregroundColor\n ctx.textAlign = 'center'\n ctx.textBaseline = 'middle'\n ctx.fillText(text, canvas.width / 2, canvas.height / 2.1)\n\n return canvas.createPNGStream()\n}\n\nexport async function convertImageToBase64(imgPath: string) {\n const base64String = await fs.readFile(imgPath, { encoding: 'base64' })\n return `data:image/png;base64,${base64String}`\n}\n\nfunction randomColor() {\n let color = ''\n while (color.length < 6) {\n /* sometimes the returned value does not have\n * the 6 digits needed, so we do it again until\n * it does\n */\n color = Math.floor(Math.random() * 16777215).toString(16)\n }\n const red = parseInt(color.substring(0, 2), 16)\n const green = parseInt(color.substring(2, 4), 16)\n const blue = parseInt(color.substring(4, 6), 16)\n const brightness = red * 0.299 + green * 0.587 + blue * 0.114\n\n return {\n backgroundColor: `#${color}`,\n foregroundColor: brightness > 180 ? '#000000' : '#ffffff'\n }\n}\n"],"names":["convertImageToBase64","generateAvatar","generateThumbnail","pngMimeType","svgMimeType","registerFont","path","join","__dirname","family","filePath","size","image","loadImage","width","height","canvas","createCanvas","ctx","getContext","drawImage","createPNGStream","compressionLevel","initials","text","backgroundColor","foregroundColor","randomColor","quality","fillStyle","fillRect","font","textAlign","textBaseline","fillText","imgPath","base64String","fs","readFile","encoding","color","length","Math","floor","random","toString","red","parseInt","substring","green","blue","brightness"],"mappings":"AAAA;;;;CAIC;;;;;;;;;;;QA6DqBA;eAAAA;;QAxBNC;eAAAA;;QA1BMC;eAAAA;;QAHTC;eAAAA;;QACAC;eAAAA;;;wBAPoD;iEAClD;iEACE;;;;;;AAEjBC,IAAAA,oBAAY,EAACC,iBAAI,CAACC,IAAI,CAACC,WAAW,SAAS,eAAe;IAAEC,QAAQ;AAAS;AAEtE,MAAMN,cAAc;AACpB,MAAMC,cAAc;AAEpB,eAAeF,kBAAkBQ,QAAgB,EAAEC,IAAY;IACpE,MAAMC,QAAQ,MAAMC,IAAAA,iBAAS,EAACH;IAC9B,IAAII,QAAQF,MAAME,KAAK;IACvB,IAAIC,SAASH,MAAMG,MAAM;IAEzB,6DAA6D;IAC7D,IAAID,QAAQC,QAAQ;QAClB,IAAID,QAAQH,MAAM;YAChBI,UAAUJ,OAAOG;YACjBA,QAAQH;QACV;IACF,OAAO;QACL,IAAII,SAASJ,MAAM;YACjBG,SAASH,OAAOI;YAChBA,SAASJ;QACX;IACF;IACA,kDAAkD;IAClD,MAAMK,SAASC,IAAAA,oBAAY,EAACH,OAAOC;IACnC,uCAAuC;IACvC,MAAMG,MAAMF,OAAOG,UAAU,CAAC;IAC9BD,IAAIE,SAAS,CAACR,OAAO,GAAG,GAAGE,OAAOC;IAElC,OAAOC,OAAOK,eAAe,CAAC;QAAEC,kBAAkB;IAAE;AACtD;AAEO,SAASrB,eAAesB,QAAgB;IAC7C,MAAMP,SAASC,IAAAA,oBAAY,EAAC,KAAK;IACjC,MAAMC,MAAMF,OAAOG,UAAU,CAAC;IAC9B,MAAMK,OAAOD;IACb,MAAM,EAAEE,eAAe,EAAEC,eAAe,EAAE,GAAGC;IAE7C,aAAa;IACbT,IAAIU,OAAO,GAAG;IAEd,kBAAkB;IAClBV,IAAIW,SAAS,GAAGJ;IAChBP,IAAIY,QAAQ,CAAC,GAAG,GAAGd,OAAOF,KAAK,EAAEE,OAAOD,MAAM;IAE9C,YAAY;IACZ,MAAMA,SAAS;IACfG,IAAIa,IAAI,GAAG,GAAGhB,OAAO,WAAW,CAAC;IACjCG,IAAIW,SAAS,GAAGH;IAChBR,IAAIc,SAAS,GAAG;IAChBd,IAAIe,YAAY,GAAG;IACnBf,IAAIgB,QAAQ,CAACV,MAAMR,OAAOF,KAAK,GAAG,GAAGE,OAAOD,MAAM,GAAG;IAErD,OAAOC,OAAOK,eAAe;AAC/B;AAEO,eAAerB,qBAAqBmC,OAAe;IACxD,MAAMC,eAAe,MAAMC,iBAAE,CAACC,QAAQ,CAACH,SAAS;QAAEI,UAAU;IAAS;IACrE,OAAO,CAAC,sBAAsB,EAAEH,cAAc;AAChD;AAEA,SAAST;IACP,IAAIa,QAAQ;IACZ,MAAOA,MAAMC,MAAM,GAAG,EAAG;QACvB;;;KAGC,GACDD,QAAQE,KAAKC,KAAK,CAACD,KAAKE,MAAM,KAAK,UAAUC,QAAQ,CAAC;IACxD;IACA,MAAMC,MAAMC,SAASP,MAAMQ,SAAS,CAAC,GAAG,IAAI;IAC5C,MAAMC,QAAQF,SAASP,MAAMQ,SAAS,CAAC,GAAG,IAAI;IAC9C,MAAME,OAAOH,SAASP,MAAMQ,SAAS,CAAC,GAAG,IAAI;IAC7C,MAAMG,aAAaL,MAAM,QAAQG,QAAQ,QAAQC,OAAO;IAExD,OAAO;QACLzB,iBAAiB,CAAC,CAAC,EAAEe,OAAO;QAC5Bd,iBAAiByB,aAAa,MAAM,YAAY;IAClD;AACF"}
1
+ {"version":3,"sources":["../../../backend/src/common/image.ts"],"sourcesContent":["/*\n * Copyright (C) 2012-2025 Johan Legrand <johan.legrand@sync-in.com>\n * This file is part of Sync-in | The open source file sync and share solution\n * See the LICENSE file for licensing details\n */\n\nimport fs from 'node:fs/promises'\nimport os from 'node:os'\nimport path from 'node:path'\nimport { Readable } from 'node:stream'\nimport { promisify } from 'node:util'\nimport sharp from 'sharp'\nimport TextToSVG from 'text-to-svg' // Sharp settings\n\n// Sharp settings\nsharp.cache(false)\nsharp.concurrency(Math.min(2, os.cpus()?.length || 1))\n\n// Constants\nexport const pngMimeType = 'image/png'\nexport const svgMimeType = 'image/svg+xml'\nexport const webpMimeType = 'image/webp'\nconst avatarSize = 256\nconst fontPath = path.join(__dirname, 'fonts', 'avatar.ttf')\nconst loadTextToSVG = promisify(TextToSVG.load.bind(TextToSVG))\nlet textToSvgCache: Promise<TextToSVG> | null = null\n\nexport async function generateThumbnail(filePath: string, size: number): Promise<Readable> {\n return sharp(filePath, {\n sequentialRead: true, // sequential read = more efficient I/O\n limitInputPixels: 268e6 // protects against extremely large images\n })\n .rotate()\n .resize({\n width: size,\n height: size,\n fit: 'inside',\n kernel: 'nearest',\n withoutEnlargement: true,\n fastShrinkOnLoad: true // true by default, added for clarity\n })\n .webp({ quality: 80, effort: 0, alphaQuality: 90 })\n}\n\nexport async function generateAvatar(initials: string): Promise<NodeJS.ReadableStream> {\n const tts = await getTextToSvg()\n const { backgroundColor, foregroundColor } = randomColor()\n const fontSize = fitFontSize(tts, initials, avatarSize * 0.8, 170)\n\n const d = tts.getD(initials, {\n x: avatarSize / 2,\n y: avatarSize / 2.1,\n fontSize,\n anchor: 'center middle'\n })\n\n const svg = `\n<svg width=\"${avatarSize}\" height=\"${avatarSize}\" viewBox=\"0 0 ${avatarSize} ${avatarSize}\"\n xmlns=\"http://www.w3.org/2000/svg\">\n <rect width=\"100%\" height=\"100%\" fill=\"${backgroundColor}\"/>\n <path d=\"${d}\" fill=\"${foregroundColor}\" />\n</svg>`.trim()\n\n return sharp(Buffer.from(svg, 'utf8')).png()\n}\n\nexport async function convertImageToBase64(imgPath: string) {\n const base64String = await fs.readFile(imgPath, { encoding: 'base64' })\n return `data:image/png;base64,${base64String}`\n}\n\nfunction randomColor() {\n let color = ''\n while (color.length < 6) {\n /* sometimes the returned value does not have\n * the 6 digits needed, so we do it again until\n * it does\n */\n color = Math.floor(Math.random() * 16777215).toString(16)\n }\n const red = parseInt(color.substring(0, 2), 16)\n const green = parseInt(color.substring(2, 4), 16)\n const blue = parseInt(color.substring(4, 6), 16)\n const brightness = red * 0.299 + green * 0.587 + blue * 0.114\n\n return {\n backgroundColor: `#${color}`,\n foregroundColor: brightness > 180 ? '#000000' : '#ffffff'\n }\n}\n\nfunction fitFontSize(tts: TextToSVG, text: string, box: number, start = 170): number {\n // Heuristic to make the text occupy ~80% of the available width\n let size = start\n // Lower bound to prevent infinite loops when the font renders very small\n while (size > 20) {\n const m = tts.getMetrics(text, { fontSize: size, anchor: 'center middle' })\n if (m.width <= box) break\n size -= 4\n }\n return size\n}\n\nfunction getTextToSvg(): Promise<TextToSVG> {\n return (textToSvgCache ??= loadTextToSVG(fontPath) as Promise<TextToSVG>)\n}\n"],"names":["convertImageToBase64","generateAvatar","generateThumbnail","pngMimeType","svgMimeType","webpMimeType","sharp","cache","concurrency","Math","min","os","cpus","length","avatarSize","fontPath","path","join","__dirname","loadTextToSVG","promisify","TextToSVG","load","bind","textToSvgCache","filePath","size","sequentialRead","limitInputPixels","rotate","resize","width","height","fit","kernel","withoutEnlargement","fastShrinkOnLoad","webp","quality","effort","alphaQuality","initials","tts","getTextToSvg","backgroundColor","foregroundColor","randomColor","fontSize","fitFontSize","d","getD","x","y","anchor","svg","trim","Buffer","from","png","imgPath","base64String","fs","readFile","encoding","color","floor","random","toString","red","parseInt","substring","green","blue","brightness","text","box","start","m","getMetrics"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../backend/src/common/interfaces.ts"],"sourcesContent":["/*\n * Copyright (C) 2012-2025 Johan Legrand <johan.legrand@sync-in.com>\n * This file is part of Sync-in | The open source file sync and share solution\n * See the LICENSE file for licensing details\n */\n\nexport type Entries<T> = { [K in keyof T]: [K, T[K]] }[keyof T][]\n"],"names":[],"mappings":"AAAA;;;;CAIC"}
1
+ {"version":3,"sources":["../../../backend/src/common/interfaces.ts"],"sourcesContent":["/*\n * Copyright (C) 2012-2025 Johan Legrand <johan.legrand@sync-in.com>\n * This file is part of Sync-in | The open source file sync and share solution\n * See the LICENSE file for licensing details\n */\n\nexport type Entries<T> = { [K in keyof T]: [K, T[K]] }[keyof T][]\n\nexport interface StorageQuota {\n storageUsage: number\n storageQuota: number\n}\n"],"names":[],"mappings":"AAAA;;;;CAIC"}
@@ -2,8 +2,7 @@
2
2
  * Copyright (C) 2012-2025 Johan Legrand <johan.legrand@sync-in.com>
3
3
  * This file is part of Sync-in | The open source file sync and share solution
4
4
  * See the LICENSE file for licensing details
5
- */ // eslint-disable-next-line no-control-regex
6
- "use strict";
5
+ */ "use strict";
7
6
  Object.defineProperty(exports, "__esModule", {
8
7
  value: true
9
8
  });
@@ -14,6 +13,9 @@ function _export(target, all) {
14
13
  });
15
14
  }
16
15
  _export(exports, {
16
+ get SERVER_NAME () {
17
+ return SERVER_NAME;
18
+ },
17
19
  get capitalizeString () {
18
20
  return capitalizeString;
19
21
  },
@@ -60,6 +62,7 @@ _export(exports, {
60
62
  return regExpPreventPathTraversal;
61
63
  }
62
64
  });
65
+ const SERVER_NAME = 'Sync-in';
63
66
  const regExpInvalidFileName = /^(?:CON|PRN|AUX|NUL|COM[1-9]|LPT[1-9])$|[<>:"/\\|?*\x00-\x1f\x80-\x9f]/;
64
67
  const regExpPreventPathTraversal = /^(\.\.(\/|\\|$))+/;
65
68
  const regExpNumberSuffix = /-\d+$/;
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../backend/src/common/shared.ts"],"sourcesContent":["/*\n * Copyright (C) 2012-2025 Johan Legrand <johan.legrand@sync-in.com>\n * This file is part of Sync-in | The open source file sync and share solution\n * See the LICENSE file for licensing details\n */\n\n// eslint-disable-next-line no-control-regex\nexport const regExpInvalidFileName = /^(?:CON|PRN|AUX|NUL|COM[1-9]|LPT[1-9])$|[<>:\"/\\\\|?*\\x00-\\x1f\\x80-\\x9f]/\nexport const regExpPreventPathTraversal = /^(\\.\\.(\\/|\\\\|$))+/\nexport const regExpNumberSuffix = /-\\d+$/\nexport const forbiddenChars = '\\\\ / : * ? \" < > |'\n\nexport function isValidFileName(fileName: string) {\n if (regExpInvalidFileName.test(fileName)) {\n throw new Error('Forbidden characters')\n }\n}\n\nexport function currentTimeStamp(date?: Date, ms = false): number {\n return Math.floor((date ? date : new Date()).getTime() / (ms ? 1 : 1000))\n}\n\nexport function currentDate(value?: string): Date {\n return new Date((value ? value : new Date().toISOString()).split('T')[0])\n}\n\nexport function createSlug(input: string, replaceCount = false): string {\n const r = input\n .toLowerCase()\n .trim()\n .replace(/[\\s_-]+/g, '-')\n .replace(/^-+|-+$/g, '')\n .normalize('NFD')\n .replace(/[\\u0300-\\u036f]/g, '')\n if (replaceCount) return r.replace(regExpNumberSuffix, '')\n return r\n}\n\nexport function createLightSlug(input: string) {\n return input\n .toLowerCase()\n .trim()\n .normalize('NFD')\n .replace(/[\\u0300-\\u036f]/g, '')\n}\n\nexport function genPassword(length = 12) {\n const chars = '0123456789abcdefghijklmnopqrstuvwxyz!@#$%^&*()ABCDEFGHIJKLMNOPQRSTUVWXYZ'\n let password = ''\n for (let i = 0; i <= length; i++) {\n const randomNumber = Math.floor(Math.random() * chars.length)\n password += chars.substring(randomNumber, randomNumber + 1)\n }\n return password\n}\n\nexport function popFromObject(key: string, object: any): any {\n const item = object[key]\n delete object[key]\n return item\n}\n\nexport function encodeUrl(url: string): string {\n return url\n .split('/')\n .map((e) => encodeURIComponent(e))\n .join('/')\n}\n\nexport function decodeUrl(url: string): string {\n return url\n .split('/')\n .map((e) => decodeURIComponent(e))\n .join('/')\n}\n\nexport function objectPropertyFromString(obj: any, property: string): any {\n const a = property.split('.')\n let o = obj\n for (let i = 0, n = a.length; i < n; i++) {\n const k = a[i]\n if (k in o) {\n o = o[k]\n } else {\n return null\n }\n }\n return o\n}\n\nexport function capitalizeString(value: string): string {\n return value.charAt(0).toUpperCase() + value.slice(1)\n}\n"],"names":["capitalizeString","createLightSlug","createSlug","currentDate","currentTimeStamp","decodeUrl","encodeUrl","forbiddenChars","genPassword","isValidFileName","objectPropertyFromString","popFromObject","regExpInvalidFileName","regExpNumberSuffix","regExpPreventPathTraversal","fileName","test","Error","date","ms","Math","floor","Date","getTime","value","toISOString","split","input","replaceCount","r","toLowerCase","trim","replace","normalize","length","chars","password","i","randomNumber","random","substring","key","object","item","url","map","e","encodeURIComponent","join","decodeURIComponent","obj","property","a","o","n","k","charAt","toUpperCase","slice"],"mappings":"AAAA;;;;CAIC,GAED,4CAA4C;;;;;;;;;;;;QAoF5BA;eAAAA;;QApDAC;eAAAA;;QAZAC;eAAAA;;QAJAC;eAAAA;;QAJAC;eAAAA;;QAmDAC;eAAAA;;QAPAC;eAAAA;;QApDHC;eAAAA;;QAoCGC;eAAAA;;QAlCAC;eAAAA;;QAgEAC;eAAAA;;QApBAC;eAAAA;;QAjDHC;eAAAA;;QAEAC;eAAAA;;QADAC;eAAAA;;;AADN,MAAMF,wBAAwB;AAC9B,MAAME,6BAA6B;AACnC,MAAMD,qBAAqB;AAC3B,MAAMN,iBAAiB;AAEvB,SAASE,gBAAgBM,QAAgB;IAC9C,IAAIH,sBAAsBI,IAAI,CAACD,WAAW;QACxC,MAAM,IAAIE,MAAM;IAClB;AACF;AAEO,SAASb,iBAAiBc,IAAW,EAAEC,KAAK,KAAK;IACtD,OAAOC,KAAKC,KAAK,CAAC,AAACH,CAAAA,OAAOA,OAAO,IAAII,MAAK,EAAGC,OAAO,KAAMJ,CAAAA,KAAK,IAAI,IAAG;AACxE;AAEO,SAAShB,YAAYqB,KAAc;IACxC,OAAO,IAAIF,KAAK,AAACE,CAAAA,QAAQA,QAAQ,IAAIF,OAAOG,WAAW,EAAC,EAAGC,KAAK,CAAC,IAAI,CAAC,EAAE;AAC1E;AAEO,SAASxB,WAAWyB,KAAa,EAAEC,eAAe,KAAK;IAC5D,MAAMC,IAAIF,MACPG,WAAW,GACXC,IAAI,GACJC,OAAO,CAAC,YAAY,KACpBA,OAAO,CAAC,YAAY,IACpBC,SAAS,CAAC,OACVD,OAAO,CAAC,oBAAoB;IAC/B,IAAIJ,cAAc,OAAOC,EAAEG,OAAO,CAACnB,oBAAoB;IACvD,OAAOgB;AACT;AAEO,SAAS5B,gBAAgB0B,KAAa;IAC3C,OAAOA,MACJG,WAAW,GACXC,IAAI,GACJE,SAAS,CAAC,OACVD,OAAO,CAAC,oBAAoB;AACjC;AAEO,SAASxB,YAAY0B,SAAS,EAAE;IACrC,MAAMC,QAAQ;IACd,IAAIC,WAAW;IACf,IAAK,IAAIC,IAAI,GAAGA,KAAKH,QAAQG,IAAK;QAChC,MAAMC,eAAelB,KAAKC,KAAK,CAACD,KAAKmB,MAAM,KAAKJ,MAAMD,MAAM;QAC5DE,YAAYD,MAAMK,SAAS,CAACF,cAAcA,eAAe;IAC3D;IACA,OAAOF;AACT;AAEO,SAASzB,cAAc8B,GAAW,EAAEC,MAAW;IACpD,MAAMC,OAAOD,MAAM,CAACD,IAAI;IACxB,OAAOC,MAAM,CAACD,IAAI;IAClB,OAAOE;AACT;AAEO,SAASrC,UAAUsC,GAAW;IACnC,OAAOA,IACJlB,KAAK,CAAC,KACNmB,GAAG,CAAC,CAACC,IAAMC,mBAAmBD,IAC9BE,IAAI,CAAC;AACV;AAEO,SAAS3C,UAAUuC,GAAW;IACnC,OAAOA,IACJlB,KAAK,CAAC,KACNmB,GAAG,CAAC,CAACC,IAAMG,mBAAmBH,IAC9BE,IAAI,CAAC;AACV;AAEO,SAAStC,yBAAyBwC,GAAQ,EAAEC,QAAgB;IACjE,MAAMC,IAAID,SAASzB,KAAK,CAAC;IACzB,IAAI2B,IAAIH;IACR,IAAK,IAAIb,IAAI,GAAGiB,IAAIF,EAAElB,MAAM,EAAEG,IAAIiB,GAAGjB,IAAK;QACxC,MAAMkB,IAAIH,CAAC,CAACf,EAAE;QACd,IAAIkB,KAAKF,GAAG;YACVA,IAAIA,CAAC,CAACE,EAAE;QACV,OAAO;YACL,OAAO;QACT;IACF;IACA,OAAOF;AACT;AAEO,SAASrD,iBAAiBwB,KAAa;IAC5C,OAAOA,MAAMgC,MAAM,CAAC,GAAGC,WAAW,KAAKjC,MAAMkC,KAAK,CAAC;AACrD"}
1
+ {"version":3,"sources":["../../../backend/src/common/shared.ts"],"sourcesContent":["/*\n * Copyright (C) 2012-2025 Johan Legrand <johan.legrand@sync-in.com>\n * This file is part of Sync-in | The open source file sync and share solution\n * See the LICENSE file for licensing details\n */\n\nexport const SERVER_NAME = 'Sync-in' as const\n\n// eslint-disable-next-line no-control-regex\nexport const regExpInvalidFileName = /^(?:CON|PRN|AUX|NUL|COM[1-9]|LPT[1-9])$|[<>:\"/\\\\|?*\\x00-\\x1f\\x80-\\x9f]/\nexport const regExpPreventPathTraversal = /^(\\.\\.(\\/|\\\\|$))+/\nexport const regExpNumberSuffix = /-\\d+$/\nexport const forbiddenChars = '\\\\ / : * ? \" < > |'\n\nexport function isValidFileName(fileName: string) {\n if (regExpInvalidFileName.test(fileName)) {\n throw new Error('Forbidden characters')\n }\n}\n\nexport function currentTimeStamp(date?: Date, ms = false): number {\n return Math.floor((date ? date : new Date()).getTime() / (ms ? 1 : 1000))\n}\n\nexport function currentDate(value?: string): Date {\n return new Date((value ? value : new Date().toISOString()).split('T')[0])\n}\n\nexport function createSlug(input: string, replaceCount = false): string {\n const r = input\n .toLowerCase()\n .trim()\n .replace(/[\\s_-]+/g, '-')\n .replace(/^-+|-+$/g, '')\n .normalize('NFD')\n .replace(/[\\u0300-\\u036f]/g, '')\n if (replaceCount) return r.replace(regExpNumberSuffix, '')\n return r\n}\n\nexport function createLightSlug(input: string) {\n return input\n .toLowerCase()\n .trim()\n .normalize('NFD')\n .replace(/[\\u0300-\\u036f]/g, '')\n}\n\nexport function genPassword(length = 12) {\n const chars = '0123456789abcdefghijklmnopqrstuvwxyz!@#$%^&*()ABCDEFGHIJKLMNOPQRSTUVWXYZ'\n let password = ''\n for (let i = 0; i <= length; i++) {\n const randomNumber = Math.floor(Math.random() * chars.length)\n password += chars.substring(randomNumber, randomNumber + 1)\n }\n return password\n}\n\nexport function popFromObject(key: string, object: any): any {\n const item = object[key]\n delete object[key]\n return item\n}\n\nexport function encodeUrl(url: string): string {\n return url\n .split('/')\n .map((e) => encodeURIComponent(e))\n .join('/')\n}\n\nexport function decodeUrl(url: string): string {\n return url\n .split('/')\n .map((e) => decodeURIComponent(e))\n .join('/')\n}\n\nexport function objectPropertyFromString(obj: any, property: string): any {\n const a = property.split('.')\n let o = obj\n for (let i = 0, n = a.length; i < n; i++) {\n const k = a[i]\n if (k in o) {\n o = o[k]\n } else {\n return null\n }\n }\n return o\n}\n\nexport function capitalizeString(value: string): string {\n return value.charAt(0).toUpperCase() + value.slice(1)\n}\n"],"names":["SERVER_NAME","capitalizeString","createLightSlug","createSlug","currentDate","currentTimeStamp","decodeUrl","encodeUrl","forbiddenChars","genPassword","isValidFileName","objectPropertyFromString","popFromObject","regExpInvalidFileName","regExpNumberSuffix","regExpPreventPathTraversal","fileName","test","Error","date","ms","Math","floor","Date","getTime","value","toISOString","split","input","replaceCount","r","toLowerCase","trim","replace","normalize","length","chars","password","i","randomNumber","random","substring","key","object","item","url","map","e","encodeURIComponent","join","decodeURIComponent","obj","property","a","o","n","k","charAt","toUpperCase","slice"],"mappings":"AAAA;;;;CAIC;;;;;;;;;;;QAEYA;eAAAA;;QAsFGC;eAAAA;;QApDAC;eAAAA;;QAZAC;eAAAA;;QAJAC;eAAAA;;QAJAC;eAAAA;;QAmDAC;eAAAA;;QAPAC;eAAAA;;QApDHC;eAAAA;;QAoCGC;eAAAA;;QAlCAC;eAAAA;;QAgEAC;eAAAA;;QApBAC;eAAAA;;QAjDHC;eAAAA;;QAEAC;eAAAA;;QADAC;eAAAA;;;AAJN,MAAMf,cAAc;AAGpB,MAAMa,wBAAwB;AAC9B,MAAME,6BAA6B;AACnC,MAAMD,qBAAqB;AAC3B,MAAMN,iBAAiB;AAEvB,SAASE,gBAAgBM,QAAgB;IAC9C,IAAIH,sBAAsBI,IAAI,CAACD,WAAW;QACxC,MAAM,IAAIE,MAAM;IAClB;AACF;AAEO,SAASb,iBAAiBc,IAAW,EAAEC,KAAK,KAAK;IACtD,OAAOC,KAAKC,KAAK,CAAC,AAACH,CAAAA,OAAOA,OAAO,IAAII,MAAK,EAAGC,OAAO,KAAMJ,CAAAA,KAAK,IAAI,IAAG;AACxE;AAEO,SAAShB,YAAYqB,KAAc;IACxC,OAAO,IAAIF,KAAK,AAACE,CAAAA,QAAQA,QAAQ,IAAIF,OAAOG,WAAW,EAAC,EAAGC,KAAK,CAAC,IAAI,CAAC,EAAE;AAC1E;AAEO,SAASxB,WAAWyB,KAAa,EAAEC,eAAe,KAAK;IAC5D,MAAMC,IAAIF,MACPG,WAAW,GACXC,IAAI,GACJC,OAAO,CAAC,YAAY,KACpBA,OAAO,CAAC,YAAY,IACpBC,SAAS,CAAC,OACVD,OAAO,CAAC,oBAAoB;IAC/B,IAAIJ,cAAc,OAAOC,EAAEG,OAAO,CAACnB,oBAAoB;IACvD,OAAOgB;AACT;AAEO,SAAS5B,gBAAgB0B,KAAa;IAC3C,OAAOA,MACJG,WAAW,GACXC,IAAI,GACJE,SAAS,CAAC,OACVD,OAAO,CAAC,oBAAoB;AACjC;AAEO,SAASxB,YAAY0B,SAAS,EAAE;IACrC,MAAMC,QAAQ;IACd,IAAIC,WAAW;IACf,IAAK,IAAIC,IAAI,GAAGA,KAAKH,QAAQG,IAAK;QAChC,MAAMC,eAAelB,KAAKC,KAAK,CAACD,KAAKmB,MAAM,KAAKJ,MAAMD,MAAM;QAC5DE,YAAYD,MAAMK,SAAS,CAACF,cAAcA,eAAe;IAC3D;IACA,OAAOF;AACT;AAEO,SAASzB,cAAc8B,GAAW,EAAEC,MAAW;IACpD,MAAMC,OAAOD,MAAM,CAACD,IAAI;IACxB,OAAOC,MAAM,CAACD,IAAI;IAClB,OAAOE;AACT;AAEO,SAASrC,UAAUsC,GAAW;IACnC,OAAOA,IACJlB,KAAK,CAAC,KACNmB,GAAG,CAAC,CAACC,IAAMC,mBAAmBD,IAC9BE,IAAI,CAAC;AACV;AAEO,SAAS3C,UAAUuC,GAAW;IACnC,OAAOA,IACJlB,KAAK,CAAC,KACNmB,GAAG,CAAC,CAACC,IAAMG,mBAAmBH,IAC9BE,IAAI,CAAC;AACV;AAEO,SAAStC,yBAAyBwC,GAAQ,EAAEC,QAAgB;IACjE,MAAMC,IAAID,SAASzB,KAAK,CAAC;IACzB,IAAI2B,IAAIH;IACR,IAAK,IAAIb,IAAI,GAAGiB,IAAIF,EAAElB,MAAM,EAAEG,IAAIiB,GAAGjB,IAAK;QACxC,MAAMkB,IAAIH,CAAC,CAACf,EAAE;QACd,IAAIkB,KAAKF,GAAG;YACVA,IAAIA,CAAC,CAACE,EAAE;QACV,OAAO;YACL,OAAO;QACT;IACF;IACA,OAAOF;AACT;AAEO,SAASrD,iBAAiBwB,KAAa;IAC5C,OAAOA,MAAMgC,MAAM,CAAC,GAAGC,WAAW,KAAKjC,MAAMkC,KAAK,CAAC;AACrD"}
@@ -46,7 +46,7 @@ let ServerConfig = class ServerConfig {
46
46
  constructor(){
47
47
  this.host = '0.0.0.0';
48
48
  this.port = 8080;
49
- this.workers = 2;
49
+ this.workers = 1;
50
50
  this.trustProxy = 1;
51
51
  this.restartOnFailure = true;
52
52
  }
@@ -62,9 +62,9 @@ _ts_decorate([
62
62
  _ts_metadata("design:type", Number)
63
63
  ], ServerConfig.prototype, "port", void 0);
64
64
  _ts_decorate([
65
- (0, _classtransformer.Transform)(({ value })=>value === 0 || value === 'auto' ? (0, _nodeos.cpus)().length : Math.max(Number(value), 2)),
65
+ (0, _classtransformer.Transform)(({ value })=>value === 0 || value === 'auto' ? (0, _nodeos.cpus)().length : Math.max(Number(value), 1)),
66
66
  (0, _classvalidator.IsInt)(),
67
- (0, _classvalidator.Min)(2),
67
+ (0, _classvalidator.Min)(1),
68
68
  _ts_metadata("design:type", Number)
69
69
  ], ServerConfig.prototype, "workers", void 0);
70
70
  _ts_decorate([
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../backend/src/configuration/config.validation.ts"],"sourcesContent":["/*\n * Copyright (C) 2012-2025 Johan Legrand <johan.legrand@sync-in.com>\n * This file is part of Sync-in | The open source file sync and share solution\n * See the LICENSE file for licensing details\n */\n\nimport { Transform, Type } from 'class-transformer'\nimport {\n IsBoolean,\n IsDefined,\n IsInt,\n IsIP,\n IsNotEmpty,\n IsNotEmptyObject,\n IsObject,\n IsOptional,\n IsString,\n Max,\n Min,\n ValidateNested\n} from 'class-validator'\nimport { cpus } from 'node:os'\nimport type { Level } from 'pino'\nimport { ApplicationsConfig } from '../applications/applications.config'\nimport { AuthConfig } from '../authentication/auth.config'\nimport { CacheConfig } from '../infrastructure/cache/cache.config'\nimport { MySQLConfig } from '../infrastructure/database/database.config'\nimport { MailerConfig } from '../infrastructure/mailer/mailer.config'\nimport { WebSocketConfig } from '../infrastructure/websocket/web-socket.config'\nimport { DEFAULT_LOG_FILE_PATH } from './config.constants'\n\nexport class ServerConfig {\n @IsIP()\n host: string = '0.0.0.0'\n\n @IsInt()\n @Min(1024)\n @Max(65535)\n port: number = 8080\n\n @Transform(({ value }) => (value === 0 || value === 'auto' ? cpus().length : Math.max(Number(value), 2)))\n @IsInt()\n @Min(2)\n workers: number = 2\n\n @IsOptional()\n trustProxy: boolean | string | number = 1\n\n @IsBoolean()\n restartOnFailure: boolean = true\n}\n\nexport class LoggerConfig {\n @IsString()\n @IsNotEmpty()\n level: Level = 'info'\n\n @IsBoolean()\n stdout: boolean = true\n\n @IsBoolean()\n colorize: boolean = true\n\n @IsOptional()\n @IsString()\n @Transform(({ value }) => value || DEFAULT_LOG_FILE_PATH)\n filePath: string = DEFAULT_LOG_FILE_PATH\n}\n\nexport class GlobalConfig {\n @IsDefined()\n @IsObject()\n @IsNotEmptyObject()\n @ValidateNested()\n @Type(() => ServerConfig)\n server: ServerConfig = new ServerConfig()\n\n @IsDefined()\n @IsObject()\n @IsNotEmptyObject()\n @ValidateNested()\n @Type(() => LoggerConfig)\n logger: LoggerConfig = new LoggerConfig()\n\n @IsDefined()\n @IsObject()\n @IsNotEmptyObject()\n @ValidateNested()\n @Type(() => MySQLConfig)\n mysql: MySQLConfig\n\n @IsDefined()\n @IsObject()\n @IsNotEmptyObject()\n @ValidateNested()\n @Type(() => CacheConfig)\n cache: CacheConfig = new CacheConfig()\n\n @IsOptional()\n @IsObject()\n @IsNotEmptyObject()\n @ValidateNested()\n @Type(() => WebSocketConfig)\n websocket: WebSocketConfig = new WebSocketConfig()\n\n @IsDefined()\n @IsObject()\n @IsNotEmptyObject()\n @ValidateNested()\n @Type(() => AuthConfig)\n auth: AuthConfig\n\n @IsOptional()\n @IsObject()\n @ValidateNested()\n @Type(() => MailerConfig)\n mail?: MailerConfig\n\n @IsDefined()\n @IsObject()\n @IsNotEmptyObject()\n @ValidateNested()\n @Type(() => ApplicationsConfig)\n applications: ApplicationsConfig\n}\n"],"names":["GlobalConfig","LoggerConfig","ServerConfig","host","port","workers","trustProxy","restartOnFailure","value","cpus","length","Math","max","Number","level","stdout","colorize","filePath","DEFAULT_LOG_FILE_PATH","server","logger","cache","CacheConfig","websocket","WebSocketConfig","MySQLConfig","AuthConfig","MailerConfig","ApplicationsConfig"],"mappings":"AAAA;;;;CAIC;;;;;;;;;;;QAiEYA;eAAAA;;QAjBAC;eAAAA;;QArBAC;eAAAA;;;kCAzBmB;gCAczB;wBACc;oCAEc;4BACR;6BACC;gCACA;8BACC;iCACG;iCACM;;;;;;;;;;AAE/B,IAAA,AAAMA,eAAN,MAAMA;;aAEXC,OAAe;aAKfC,OAAe;aAKfC,UAAkB;aAGlBC,aAAwC;aAGxCC,mBAA4B;;AAC9B;;;;;;;;;;;;sCAVc,EAAEC,KAAK,EAAE,GAAMA,UAAU,KAAKA,UAAU,SAASC,IAAAA,YAAI,IAAGC,MAAM,GAAGC,KAAKC,GAAG,CAACC,OAAOL,QAAQ;;;;;;;;;;;;;AAYhG,IAAA,AAAMP,eAAN,MAAMA;;aAGXa,QAAe;aAGfC,SAAkB;aAGlBC,WAAoB;aAKpBC,WAAmBC,sCAAqB;;AAC1C;;;;;;;;;;;;;;;;;sCAFc,EAAEV,KAAK,EAAE,GAAKA,SAASU,sCAAqB;;;AAInD,IAAA,AAAMlB,eAAN,MAAMA;;aAMXmB,SAAuB,IAAIjB;aAO3BkB,SAAuB,IAAInB;aAc3BoB,QAAqB,IAAIC,wBAAW;aAOpCC,YAA6B,IAAIC,gCAAe;;AAqBlD;;;;;;oCAlDctB;;;;;;;;oCAOAD;;;;;;;;oCAOAwB,2BAAW;;;;;;;;oCAOXH,wBAAW;;;;;;;;oCAOXE,gCAAe;;;;;;;;oCAOfE,sBAAU;;;;;;;oCAMVC,0BAAY;;;;;;;;oCAOZC,sCAAkB"}
1
+ {"version":3,"sources":["../../../backend/src/configuration/config.validation.ts"],"sourcesContent":["/*\n * Copyright (C) 2012-2025 Johan Legrand <johan.legrand@sync-in.com>\n * This file is part of Sync-in | The open source file sync and share solution\n * See the LICENSE file for licensing details\n */\n\nimport { Transform, Type } from 'class-transformer'\nimport {\n IsBoolean,\n IsDefined,\n IsInt,\n IsIP,\n IsNotEmpty,\n IsNotEmptyObject,\n IsObject,\n IsOptional,\n IsString,\n Max,\n Min,\n ValidateNested\n} from 'class-validator'\nimport { cpus } from 'node:os'\nimport type { Level } from 'pino'\nimport { ApplicationsConfig } from '../applications/applications.config'\nimport { AuthConfig } from '../authentication/auth.config'\nimport { CacheConfig } from '../infrastructure/cache/cache.config'\nimport { MySQLConfig } from '../infrastructure/database/database.config'\nimport { MailerConfig } from '../infrastructure/mailer/mailer.config'\nimport { WebSocketConfig } from '../infrastructure/websocket/web-socket.config'\nimport { DEFAULT_LOG_FILE_PATH } from './config.constants'\n\nexport class ServerConfig {\n @IsIP()\n host: string = '0.0.0.0'\n\n @IsInt()\n @Min(1024)\n @Max(65535)\n port: number = 8080\n\n @Transform(({ value }) => (value === 0 || value === 'auto' ? cpus().length : Math.max(Number(value), 1)))\n @IsInt()\n @Min(1)\n workers: number = 1\n\n @IsOptional()\n trustProxy: boolean | string | number = 1\n\n @IsBoolean()\n restartOnFailure: boolean = true\n}\n\nexport class LoggerConfig {\n @IsString()\n @IsNotEmpty()\n level: Level = 'info'\n\n @IsBoolean()\n stdout: boolean = true\n\n @IsBoolean()\n colorize: boolean = true\n\n @IsOptional()\n @IsString()\n @Transform(({ value }) => value || DEFAULT_LOG_FILE_PATH)\n filePath: string = DEFAULT_LOG_FILE_PATH\n}\n\nexport class GlobalConfig {\n @IsDefined()\n @IsObject()\n @IsNotEmptyObject()\n @ValidateNested()\n @Type(() => ServerConfig)\n server: ServerConfig = new ServerConfig()\n\n @IsDefined()\n @IsObject()\n @IsNotEmptyObject()\n @ValidateNested()\n @Type(() => LoggerConfig)\n logger: LoggerConfig = new LoggerConfig()\n\n @IsDefined()\n @IsObject()\n @IsNotEmptyObject()\n @ValidateNested()\n @Type(() => MySQLConfig)\n mysql: MySQLConfig\n\n @IsDefined()\n @IsObject()\n @IsNotEmptyObject()\n @ValidateNested()\n @Type(() => CacheConfig)\n cache: CacheConfig = new CacheConfig()\n\n @IsOptional()\n @IsObject()\n @IsNotEmptyObject()\n @ValidateNested()\n @Type(() => WebSocketConfig)\n websocket: WebSocketConfig = new WebSocketConfig()\n\n @IsDefined()\n @IsObject()\n @IsNotEmptyObject()\n @ValidateNested()\n @Type(() => AuthConfig)\n auth: AuthConfig\n\n @IsOptional()\n @IsObject()\n @ValidateNested()\n @Type(() => MailerConfig)\n mail?: MailerConfig\n\n @IsDefined()\n @IsObject()\n @IsNotEmptyObject()\n @ValidateNested()\n @Type(() => ApplicationsConfig)\n applications: ApplicationsConfig\n}\n"],"names":["GlobalConfig","LoggerConfig","ServerConfig","host","port","workers","trustProxy","restartOnFailure","value","cpus","length","Math","max","Number","level","stdout","colorize","filePath","DEFAULT_LOG_FILE_PATH","server","logger","cache","CacheConfig","websocket","WebSocketConfig","MySQLConfig","AuthConfig","MailerConfig","ApplicationsConfig"],"mappings":"AAAA;;;;CAIC;;;;;;;;;;;QAiEYA;eAAAA;;QAjBAC;eAAAA;;QArBAC;eAAAA;;;kCAzBmB;gCAczB;wBACc;oCAEc;4BACR;6BACC;gCACA;8BACC;iCACG;iCACM;;;;;;;;;;AAE/B,IAAA,AAAMA,eAAN,MAAMA;;aAEXC,OAAe;aAKfC,OAAe;aAKfC,UAAkB;aAGlBC,aAAwC;aAGxCC,mBAA4B;;AAC9B;;;;;;;;;;;;sCAVc,EAAEC,KAAK,EAAE,GAAMA,UAAU,KAAKA,UAAU,SAASC,IAAAA,YAAI,IAAGC,MAAM,GAAGC,KAAKC,GAAG,CAACC,OAAOL,QAAQ;;;;;;;;;;;;;AAYhG,IAAA,AAAMP,eAAN,MAAMA;;aAGXa,QAAe;aAGfC,SAAkB;aAGlBC,WAAoB;aAKpBC,WAAmBC,sCAAqB;;AAC1C;;;;;;;;;;;;;;;;;sCAFc,EAAEV,KAAK,EAAE,GAAKA,SAASU,sCAAqB;;;AAInD,IAAA,AAAMlB,eAAN,MAAMA;;aAMXmB,SAAuB,IAAIjB;aAO3BkB,SAAuB,IAAInB;aAc3BoB,QAAqB,IAAIC,wBAAW;aAOpCC,YAA6B,IAAIC,gCAAe;;AAqBlD;;;;;;oCAlDctB;;;;;;;;oCAOAD;;;;;;;;oCAOAwB,2BAAW;;;;;;;;oCAOXH,wBAAW;;;;;;;;oCAOXE,gCAAe;;;;;;;;oCAOfE,sBAAU;;;;;;;oCAMVC,0BAAY;;;;;;;;oCAOZC,sCAAkB"}
@@ -22,6 +22,7 @@ const _configenvironment = require("../../../configuration/config.environment");
22
22
  const _constants = require("../../database/constants");
23
23
  const _databaseinterface = require("../../database/interfaces/database.interface");
24
24
  const _utils = require("../../database/utils");
25
+ const _schedulerconstants = require("../../scheduler/scheduler.constants");
25
26
  const _mysqlcacheschema = require("../schemas/mysql-cache.schema");
26
27
  const _cacheservice = require("../services/cache.service");
27
28
  function _interop_require_default(obj) {
@@ -44,8 +45,8 @@ function _ts_param(paramIndex, decorator) {
44
45
  };
45
46
  }
46
47
  let MysqlCacheAdapter = class MysqlCacheAdapter {
47
- async initScheduler() {
48
- if (!_nodecluster.default.worker || _nodecluster.default.worker.id === 1) {
48
+ async onModuleInit() {
49
+ if (_nodecluster.default.isWorker && process.env[_schedulerconstants.SCHEDULER_ENV] === _schedulerconstants.SCHEDULER_STATE.ENABLED) {
49
50
  try {
50
51
  await this.db.execute(`SET GLOBAL event_scheduler = ON;`);
51
52
  await this.db.execute(`DROP EVENT IF EXISTS ${this.scheduledJobName};`);
@@ -62,6 +63,11 @@ let MysqlCacheAdapter = class MysqlCacheAdapter {
62
63
  }
63
64
  }
64
65
  }
66
+ async onModuleDestroy() {
67
+ if (this.scheduledJob) {
68
+ await this.scheduledJob.stop();
69
+ }
70
+ }
65
71
  async keys(pattern) {
66
72
  const ks = await this.db.select({
67
73
  key: _mysqlcacheschema.cache.key
@@ -117,9 +123,6 @@ let MysqlCacheAdapter = class MysqlCacheAdapter {
117
123
  genSlugKey(...args) {
118
124
  return (0, _shared.createSlug)(args.join(' '));
119
125
  }
120
- async quit() {
121
- this.logger.verbose(`${this.quit.name}`);
122
- }
123
126
  getTTL(ttl) {
124
127
  /* ttl (seconds):
125
128
  - 0 : infinite expiration
@@ -153,7 +156,6 @@ let MysqlCacheAdapter = class MysqlCacheAdapter {
153
156
  this.logger = new _common.Logger(_cacheservice.Cache.name.toUpperCase());
154
157
  this.whereNotExpired = ()=>(0, _drizzleorm.notBetween)(_mysqlcacheschema.cache.expiration, 0, (0, _shared.currentTimeStamp)());
155
158
  this.whereExpired = ()=>(0, _drizzleorm.between)(_mysqlcacheschema.cache.expiration, 0, (0, _shared.currentTimeStamp)());
156
- this.initScheduler().catch((e)=>this.logger.error(e));
157
159
  }
158
160
  };
159
161
  MysqlCacheAdapter = _ts_decorate([
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../backend/src/infrastructure/cache/adapters/mysql-cache.adapter.ts"],"sourcesContent":["/*\n * Copyright (C) 2012-2025 Johan Legrand <johan.legrand@sync-in.com>\n * This file is part of Sync-in | The open source file sync and share solution\n * See the LICENSE file for licensing details\n */\n\nimport { Inject, Injectable, Logger } from '@nestjs/common'\nimport { SchedulerRegistry } from '@nestjs/schedule'\nimport { CronJob } from 'cron'\nimport { and, between, eq, exists, inArray, like, notBetween, SQL } from 'drizzle-orm'\nimport cluster from 'node:cluster'\nimport { createSlug, currentTimeStamp } from '../../../common/shared'\nimport { configuration } from '../../../configuration/config.environment'\nimport { DB_TOKEN_PROVIDER } from '../../database/constants'\nimport { DBSchema } from '../../database/interfaces/database.interface'\nimport { dbCheckAffectedRows } from '../../database/utils'\nimport { MysqlCache } from '../schemas/mysql-cache.interface'\nimport { cache } from '../schemas/mysql-cache.schema'\nimport { Cache } from '../services/cache.service'\n\n@Injectable()\nexport class MysqlCacheAdapter implements Cache {\n /* Useful SQL commands to stats the scheduler\n SHOW VARIABLES LIKE 'event_scheduler';\n SHOW EVENTS;\n */\n defaultTTL: number = configuration.cache.ttl\n infiniteExpiration = -1\n private scheduledJob: CronJob\n private readonly scheduledJobName = 'cache_expired_keys' as const\n private readonly scheduledJobInterval = 5 // minutes\n private readonly logger = new Logger(Cache.name.toUpperCase())\n\n constructor(\n @Inject(DB_TOKEN_PROVIDER) private readonly db: DBSchema,\n private readonly scheduler: SchedulerRegistry\n ) {\n this.initScheduler().catch((e: Error) => this.logger.error(e))\n }\n\n async initScheduler(): Promise<void> {\n if (!cluster.worker || cluster.worker.id === 1) {\n try {\n await this.db.execute(`SET GLOBAL event_scheduler = ON;`)\n await this.db.execute(`DROP EVENT IF EXISTS ${this.scheduledJobName};`)\n await this.db.execute(`CREATE EVENT IF NOT EXISTS ${this.scheduledJobName}\n ON SCHEDULE EVERY ${this.scheduledJobInterval} MINUTE\n DO DELETE FROM cache WHERE cache.expiration BETWEEN 0 AND UNIX_TIMESTAMP();`)\n this.logger.log(`Using MySQL scheduler`)\n } catch (e) {\n this.logger.error(`MySQL scheduler on '${e?.sql || e?.code}' : ${e.message || e}`)\n this.logger.warn(`Fallback to internal scheduler`)\n this.scheduledJob = new CronJob(`0 */${this.scheduledJobInterval} * * * *`, async () => await this.clearExpiredKeys())\n this.scheduler.addCronJob(this.scheduledJobName, this.scheduledJob)\n this.scheduledJob.start()\n }\n }\n }\n\n async keys(pattern: string): Promise<string[]> {\n const ks = await this.db\n .select({ key: cache.key })\n .from(cache)\n .where(and(like(cache.key, pattern.replaceAll('*', '%')), this.whereNotExpired()))\n return ks.map((k: { key: string }) => k.key)\n }\n\n async has(key: string): Promise<boolean> {\n const [r] = await this.db\n .select({ key: cache.key })\n .from(cache)\n .where(\n exists(\n this.db\n .select({ key: cache.key })\n .from(cache)\n .where(and(eq(cache.key, key), this.whereNotExpired()))\n )\n )\n return !!r\n }\n\n async get(key: string): Promise<any> {\n const [v]: { value: any }[] = await this.db\n .select({ value: cache.value })\n .from(cache)\n .where(and(eq(cache.key, key), this.whereNotExpired()))\n .limit(1)\n return v ? v.value : v\n }\n\n async mget(keys: string[]): Promise<any[]> {\n const vs: { value: any }[] = await this.db\n .select({ value: cache.value })\n .from(cache)\n .where(and(inArray(cache.key, keys), this.whereNotExpired()))\n return vs.map((v: { value: any }) => v.value)\n }\n\n async set(key: any, data: any, ttl?: number): Promise<boolean> {\n data = this.serialize(data)\n const exp = this.getTTL(ttl)\n try {\n await this.db\n .insert(cache)\n .values({ key: key, value: data, expiration: exp } as MysqlCache)\n .onDuplicateKeyUpdate({\n set: {\n value: data,\n expiration: exp\n } as Partial<MysqlCache>\n })\n return true\n } catch (e) {\n this.logger.error(`${this.set.name} - ${e}`)\n return false\n }\n }\n\n async del(key: string): Promise<boolean> {\n return dbCheckAffectedRows(await this.db.delete(cache).where(eq(cache.key, key)), 1, false)\n }\n\n async mdel(keys: string[]): Promise<boolean> {\n return dbCheckAffectedRows(await this.db.delete(cache).where(inArray(cache.key, keys)), keys.length, false)\n }\n\n genSlugKey(...args: any[]): string {\n return createSlug(args.join(' '))\n }\n\n async quit(): Promise<void> {\n this.logger.verbose(`${this.quit.name}`)\n }\n\n private readonly whereNotExpired: () => SQL = () => notBetween(cache.expiration, 0, currentTimeStamp())\n\n private readonly whereExpired: () => SQL = () => between(cache.expiration, 0, currentTimeStamp())\n\n private getTTL(ttl: number): number {\n /* ttl (seconds):\n - 0 : infinite expiration\n - undefined : default ttl\n */\n return ttl ? currentTimeStamp() + ttl : ttl === 0 ? this.infiniteExpiration : currentTimeStamp() + this.defaultTTL\n }\n\n private serialize(data: any) {\n if (data === undefined) {\n // undefined values are not handled by JSON serialization\n return null\n }\n return data\n }\n\n private async clearExpiredKeys() {\n try {\n await this.db.delete(cache).where(this.whereExpired())\n } catch (e) {\n this.logger.error(`${this.clearExpiredKeys.name} - ${e?.code || e}`)\n }\n }\n}\n"],"names":["MysqlCacheAdapter","initScheduler","cluster","worker","id","db","execute","scheduledJobName","scheduledJobInterval","logger","log","e","error","sql","code","message","warn","scheduledJob","CronJob","clearExpiredKeys","scheduler","addCronJob","start","keys","pattern","ks","select","key","cache","from","where","and","like","replaceAll","whereNotExpired","map","k","has","r","exists","eq","get","v","value","limit","mget","vs","inArray","set","data","ttl","serialize","exp","getTTL","insert","values","expiration","onDuplicateKeyUpdate","name","del","dbCheckAffectedRows","delete","mdel","length","genSlugKey","args","createSlug","join","quit","verbose","currentTimeStamp","infiniteExpiration","defaultTTL","undefined","whereExpired","configuration","Logger","Cache","toUpperCase","notBetween","between","catch"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../backend/src/infrastructure/cache/adapters/mysql-cache.adapter.ts"],"sourcesContent":["/*\n * Copyright (C) 2012-2025 Johan Legrand <johan.legrand@sync-in.com>\n * This file is part of Sync-in | The open source file sync and share solution\n * See the LICENSE file for licensing details\n */\n\nimport { Inject, Injectable, Logger } from '@nestjs/common'\nimport { SchedulerRegistry } from '@nestjs/schedule'\nimport { CronJob } from 'cron'\nimport { and, between, eq, exists, inArray, like, notBetween, SQL } from 'drizzle-orm'\nimport cluster from 'node:cluster'\nimport { createSlug, currentTimeStamp } from '../../../common/shared'\nimport { configuration } from '../../../configuration/config.environment'\nimport { DB_TOKEN_PROVIDER } from '../../database/constants'\nimport { DBSchema } from '../../database/interfaces/database.interface'\nimport { dbCheckAffectedRows } from '../../database/utils'\nimport { SCHEDULER_ENV, SCHEDULER_STATE } from '../../scheduler/scheduler.constants'\nimport { MysqlCache } from '../schemas/mysql-cache.interface'\nimport { cache } from '../schemas/mysql-cache.schema'\nimport { Cache } from '../services/cache.service'\n\n@Injectable()\nexport class MysqlCacheAdapter implements Cache {\n /* Useful SQL commands to stats the scheduler\n SHOW VARIABLES LIKE 'event_scheduler';\n SHOW EVENTS;\n */\n defaultTTL: number = configuration.cache.ttl\n infiniteExpiration = -1\n private scheduledJob: CronJob\n private readonly scheduledJobName = 'cache_expired_keys' as const\n private readonly scheduledJobInterval = 5 // minutes\n private readonly logger = new Logger(Cache.name.toUpperCase())\n\n constructor(\n @Inject(DB_TOKEN_PROVIDER) private readonly db: DBSchema,\n private readonly scheduler: SchedulerRegistry\n ) {}\n\n async onModuleInit(): Promise<void> {\n if (cluster.isWorker && process.env[SCHEDULER_ENV] === SCHEDULER_STATE.ENABLED) {\n try {\n await this.db.execute(`SET GLOBAL event_scheduler = ON;`)\n await this.db.execute(`DROP EVENT IF EXISTS ${this.scheduledJobName};`)\n await this.db.execute(`CREATE EVENT IF NOT EXISTS ${this.scheduledJobName}\n ON SCHEDULE EVERY ${this.scheduledJobInterval} MINUTE\n DO DELETE FROM cache WHERE cache.expiration BETWEEN 0 AND UNIX_TIMESTAMP();`)\n this.logger.log(`Using MySQL scheduler`)\n } catch (e) {\n this.logger.error(`MySQL scheduler on '${e?.sql || e?.code}' : ${e.message || e}`)\n this.logger.warn(`Fallback to internal scheduler`)\n this.scheduledJob = new CronJob(`0 */${this.scheduledJobInterval} * * * *`, async () => await this.clearExpiredKeys())\n this.scheduler.addCronJob(this.scheduledJobName, this.scheduledJob)\n this.scheduledJob.start()\n }\n }\n }\n\n async onModuleDestroy(): Promise<void> {\n if (this.scheduledJob) {\n await this.scheduledJob.stop()\n }\n }\n\n async keys(pattern: string): Promise<string[]> {\n const ks = await this.db\n .select({ key: cache.key })\n .from(cache)\n .where(and(like(cache.key, pattern.replaceAll('*', '%')), this.whereNotExpired()))\n return ks.map((k: { key: string }) => k.key)\n }\n\n async has(key: string): Promise<boolean> {\n const [r] = await this.db\n .select({ key: cache.key })\n .from(cache)\n .where(\n exists(\n this.db\n .select({ key: cache.key })\n .from(cache)\n .where(and(eq(cache.key, key), this.whereNotExpired()))\n )\n )\n return !!r\n }\n\n async get(key: string): Promise<any> {\n const [v]: { value: any }[] = await this.db\n .select({ value: cache.value })\n .from(cache)\n .where(and(eq(cache.key, key), this.whereNotExpired()))\n .limit(1)\n return v ? v.value : v\n }\n\n async mget(keys: string[]): Promise<any[]> {\n const vs: { value: any }[] = await this.db\n .select({ value: cache.value })\n .from(cache)\n .where(and(inArray(cache.key, keys), this.whereNotExpired()))\n return vs.map((v: { value: any }) => v.value)\n }\n\n async set(key: string, data: unknown, ttl?: number): Promise<boolean> {\n data = this.serialize(data)\n const exp = this.getTTL(ttl)\n try {\n await this.db\n .insert(cache)\n .values({ key: key, value: data, expiration: exp } as MysqlCache)\n .onDuplicateKeyUpdate({\n set: {\n value: data,\n expiration: exp\n } as Partial<MysqlCache>\n })\n return true\n } catch (e) {\n this.logger.error(`${this.set.name} - ${e}`)\n return false\n }\n }\n\n async del(key: string): Promise<boolean> {\n return dbCheckAffectedRows(await this.db.delete(cache).where(eq(cache.key, key)), 1, false)\n }\n\n async mdel(keys: string[]): Promise<boolean> {\n return dbCheckAffectedRows(await this.db.delete(cache).where(inArray(cache.key, keys)), keys.length, false)\n }\n\n genSlugKey(...args: any[]): string {\n return createSlug(args.join(' '))\n }\n\n private readonly whereNotExpired: () => SQL = () => notBetween(cache.expiration, 0, currentTimeStamp())\n\n private readonly whereExpired: () => SQL = () => between(cache.expiration, 0, currentTimeStamp())\n\n private getTTL(ttl: number): number {\n /* ttl (seconds):\n - 0 : infinite expiration\n - undefined : default ttl\n */\n return ttl ? currentTimeStamp() + ttl : ttl === 0 ? this.infiniteExpiration : currentTimeStamp() + this.defaultTTL\n }\n\n private serialize(data: any) {\n if (data === undefined) {\n // undefined values are not handled by JSON serialization\n return null\n }\n return data\n }\n\n private async clearExpiredKeys() {\n try {\n await this.db.delete(cache).where(this.whereExpired())\n } catch (e) {\n this.logger.error(`${this.clearExpiredKeys.name} - ${e?.code || e}`)\n }\n }\n}\n"],"names":["MysqlCacheAdapter","onModuleInit","cluster","isWorker","process","env","SCHEDULER_ENV","SCHEDULER_STATE","ENABLED","db","execute","scheduledJobName","scheduledJobInterval","logger","log","e","error","sql","code","message","warn","scheduledJob","CronJob","clearExpiredKeys","scheduler","addCronJob","start","onModuleDestroy","stop","keys","pattern","ks","select","key","cache","from","where","and","like","replaceAll","whereNotExpired","map","k","has","r","exists","eq","get","v","value","limit","mget","vs","inArray","set","data","ttl","serialize","exp","getTTL","insert","values","expiration","onDuplicateKeyUpdate","name","del","dbCheckAffectedRows","delete","mdel","length","genSlugKey","args","createSlug","join","currentTimeStamp","infiniteExpiration","defaultTTL","undefined","whereExpired","configuration","Logger","Cache","toUpperCase","notBetween","between"],"mappings":"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"}
@@ -27,16 +27,26 @@ function _ts_metadata(k, v) {
27
27
  if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
28
28
  }
29
29
  let RedisCacheAdapter = class RedisCacheAdapter {
30
+ async onModuleInit() {
31
+ this.client.on('error', (e)=>this.logger.error(e.message || e));
32
+ this.client.on('ready', ()=>this.logger.log(`Connected to Redis Server at ${this.client.options.url}`));
33
+ this.client.connect().catch((e)=>this.logger.error(e));
34
+ }
35
+ async onModuleDestroy() {
36
+ if (this.client?.isOpen) {
37
+ await this.client.close();
38
+ }
39
+ }
30
40
  async has(key) {
31
41
  return await this.client.exists(key) === 1;
32
42
  }
33
43
  async keys(pattern) {
34
44
  const matches = [];
35
- for await (const key of this.client.scanIterator({
45
+ for await (const keys of this.client.scanIterator({
36
46
  MATCH: pattern,
37
47
  COUNT: 100
38
48
  })){
39
- matches.push(key);
49
+ matches.push(...keys);
40
50
  }
41
51
  return matches;
42
52
  }
@@ -49,7 +59,10 @@ let RedisCacheAdapter = class RedisCacheAdapter {
49
59
  async set(key, data, ttl) {
50
60
  const exp = this.getTTL(ttl);
51
61
  return await this.client.set(key, this.serialize(data), {
52
- EX: exp === -1 ? undefined : exp
62
+ expiration: {
63
+ type: 'EX',
64
+ value: exp === -1 ? undefined : exp
65
+ }
53
66
  }) === 'OK';
54
67
  }
55
68
  async del(key) {
@@ -60,20 +73,16 @@ let RedisCacheAdapter = class RedisCacheAdapter {
60
73
  for (const key of keys){
61
74
  multi.unlink(key);
62
75
  }
63
- const results = await multi.exec();
64
- return results.indexOf(1) > -1;
76
+ const res = await multi.exec();
77
+ return Array.isArray(res) && res.some((r)=>typeof r === 'number' && r > 0);
65
78
  }
66
79
  genSlugKey(...args) {
67
80
  return (0, _shared.createSlug)(args.join(' '));
68
81
  }
69
- async quit() {
70
- await this.client.disconnect();
71
- this.logger.verbose(`${this.quit.name}`);
72
- }
73
82
  getTTL(ttl) {
74
83
  /* ttl (seconds):
75
- - 0 : infinite expiration
76
- - undefined : default ttl
84
+ - 0: infinite expiration
85
+ - undefined: default ttl
77
86
  */ return ttl ? ttl : ttl === 0 ? this.infiniteExpiration : this.defaultTTL;
78
87
  }
79
88
  serialize(data) {
@@ -94,8 +103,8 @@ let RedisCacheAdapter = class RedisCacheAdapter {
94
103
  this.logger = new _common.Logger(_cacheservice.Cache.name.toUpperCase());
95
104
  this.reconnectOptions = {
96
105
  maxAttempts: 3,
97
- minConnectDelay: 6000,
98
- maxConnectDelay: 30000
106
+ minConnectDelay: 1000,
107
+ maxConnectDelay: 2000
99
108
  };
100
109
  this.reconnectStrategy = (attempts)=>{
101
110
  if (attempts > this.reconnectOptions.maxAttempts) {
@@ -114,10 +123,6 @@ let RedisCacheAdapter = class RedisCacheAdapter {
114
123
  reconnectStrategy: this.reconnectStrategy
115
124
  }
116
125
  });
117
- this.client.connect().then(()=>{
118
- this.client.on('error', (e)=>this.logger.error(e.message || e));
119
- this.client.on('ready', ()=>this.logger.log(`Connected to Redis Server at ${this.client.options.url}`));
120
- });
121
126
  }
122
127
  };
123
128
  RedisCacheAdapter = _ts_decorate([
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../backend/src/infrastructure/cache/adapters/redis-cache.adapter.ts"],"sourcesContent":["/*\n * Copyright (C) 2012-2025 Johan Legrand <johan.legrand@sync-in.com>\n * This file is part of Sync-in | The open source file sync and share solution\n * See the LICENSE file for licensing details\n */\n\nimport { Injectable, Logger } from '@nestjs/common'\nimport { RedisClientOptions } from '@redis/client'\nimport { createClient, RedisClientType } from 'redis'\nimport { createSlug } from '../../../common/shared'\nimport { configuration } from '../../../configuration/config.environment'\nimport { Cache } from '../services/cache.service'\n\n@Injectable()\nexport class RedisCacheAdapter implements Cache {\n defaultTTL: number = configuration.cache.ttl\n infiniteExpiration = -1\n private readonly logger = new Logger(Cache.name.toUpperCase())\n private readonly client: RedisClientType\n private readonly reconnectOptions = { maxAttempts: 3, minConnectDelay: 6000, maxConnectDelay: 30000 }\n private readonly reconnectStrategy = (attempts: number): number => {\n if (attempts > this.reconnectOptions.maxAttempts) {\n this.logger.error('Too many retries on Redis server. Exiting')\n process.exit()\n } else {\n const wait: number = Math.min(this.reconnectOptions.minConnectDelay * Math.pow(2, attempts), this.reconnectOptions.maxConnectDelay)\n this.logger.warn(`Retrying connection to Redis server in ${wait / 1000}s`)\n return wait\n }\n }\n\n constructor() {\n this.client = createClient({\n url: configuration.cache.redis,\n socket: { noDelay: true, reconnectStrategy: this.reconnectStrategy }\n } satisfies RedisClientOptions)\n this.client.connect().then(() => {\n this.client.on('error', (e: Error) => this.logger.error(e.message || e))\n this.client.on('ready', () => this.logger.log(`Connected to Redis Server at ${this.client.options.url}`))\n })\n }\n\n async has(key: string): Promise<boolean> {\n return (await this.client.exists(key)) === 1\n }\n\n async keys(pattern: string): Promise<string[]> {\n const matches: string[] = []\n for await (const key of this.client.scanIterator({ MATCH: pattern, COUNT: 100 })) {\n matches.push(key)\n }\n return matches\n }\n\n async get(key: string): Promise<any> {\n return this.deserialize(await this.client.get(key))\n }\n\n async mget(keys: string[]): Promise<(any | undefined)[]> {\n return (await this.client.mGet(keys)).map((v) => this.deserialize(v))\n }\n\n async set(key: any, data: any, ttl?: number): Promise<boolean> {\n const exp = this.getTTL(ttl)\n return (await this.client.set(key, this.serialize(data), { EX: exp === -1 ? undefined : exp })) === 'OK'\n }\n\n async del(key: any): Promise<boolean> {\n return (await this.client.unlink(key)) > 0\n }\n\n async mdel(keys: string[]): Promise<boolean> {\n const multi = this.client.multi()\n for (const key of keys) {\n multi.unlink(key)\n }\n const results = await multi.exec()\n return results.indexOf(1) > -1\n }\n\n genSlugKey(...args: any[]): string {\n return createSlug(args.join(' '))\n }\n\n async quit(): Promise<void> {\n await this.client.disconnect()\n this.logger.verbose(`${this.quit.name}`)\n }\n\n private getTTL(ttl: number): number {\n /* ttl (seconds):\n - 0 : infinite expiration\n - undefined : default ttl\n */\n return ttl ? ttl : ttl === 0 ? this.infiniteExpiration : this.defaultTTL\n }\n\n private serialize(data: any) {\n if (data === undefined || data === null) {\n return 'null'\n }\n return JSON.stringify(data)\n }\n\n private deserialize(data: any) {\n if (data === null) {\n return undefined\n }\n return JSON.parse(data)\n }\n}\n"],"names":["RedisCacheAdapter","has","key","client","exists","keys","pattern","matches","scanIterator","MATCH","COUNT","push","get","deserialize","mget","mGet","map","v","set","data","ttl","exp","getTTL","serialize","EX","undefined","del","unlink","mdel","multi","results","exec","indexOf","genSlugKey","args","createSlug","join","quit","disconnect","logger","verbose","name","infiniteExpiration","defaultTTL","JSON","stringify","parse","configuration","cache","Logger","Cache","toUpperCase","reconnectOptions","maxAttempts","minConnectDelay","maxConnectDelay","reconnectStrategy","attempts","error","process","exit","wait","Math","min","pow","warn","createClient","url","redis","socket","noDelay","connect","then","on","e","message","log","options"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../backend/src/infrastructure/cache/adapters/redis-cache.adapter.ts"],"sourcesContent":["/*\n * Copyright (C) 2012-2025 Johan Legrand <johan.legrand@sync-in.com>\n * This file is part of Sync-in | The open source file sync and share solution\n * See the LICENSE file for licensing details\n */\n\nimport { Injectable, Logger } from '@nestjs/common'\nimport { RedisClientOptions } from '@redis/client'\nimport { createClient, RedisClientType } from 'redis'\nimport { createSlug } from '../../../common/shared'\nimport { configuration } from '../../../configuration/config.environment'\nimport { Cache } from '../services/cache.service'\n\n@Injectable()\nexport class RedisCacheAdapter implements Cache {\n defaultTTL: number = configuration.cache.ttl\n infiniteExpiration = -1\n private readonly logger = new Logger(Cache.name.toUpperCase())\n private readonly client: RedisClientType\n private readonly reconnectOptions = { maxAttempts: 3, minConnectDelay: 1000, maxConnectDelay: 2000 }\n\n constructor() {\n this.client = createClient({\n url: configuration.cache.redis,\n socket: { noDelay: true, reconnectStrategy: this.reconnectStrategy }\n } satisfies RedisClientOptions)\n }\n\n async onModuleInit() {\n this.client.on('error', (e: Error) => this.logger.error(e.message || e))\n this.client.on('ready', () => this.logger.log(`Connected to Redis Server at ${this.client.options.url}`))\n this.client.connect().catch((e: Error) => this.logger.error(e))\n }\n\n async onModuleDestroy() {\n if (this.client?.isOpen) {\n await this.client.close()\n }\n }\n\n async has(key: string): Promise<boolean> {\n return (await this.client.exists(key)) === 1\n }\n\n async keys(pattern: string): Promise<string[]> {\n const matches: string[] = []\n for await (const keys of this.client.scanIterator({ MATCH: pattern, COUNT: 100 })) {\n matches.push(...keys)\n }\n return matches\n }\n\n async get(key: string): Promise<any> {\n return this.deserialize(await this.client.get(key))\n }\n\n async mget(keys: string[]): Promise<(any | undefined)[]> {\n return (await this.client.mGet(keys)).map((v) => this.deserialize(v))\n }\n\n async set(key: string, data: unknown, ttl?: number): Promise<boolean> {\n const exp = this.getTTL(ttl)\n return (await this.client.set(key, this.serialize(data), { expiration: { type: 'EX', value: exp === -1 ? undefined : exp } })) === 'OK'\n }\n\n async del(key: any): Promise<boolean> {\n return (await this.client.unlink(key)) > 0\n }\n\n async mdel(keys: string[]): Promise<boolean> {\n const multi = this.client.multi()\n for (const key of keys) {\n multi.unlink(key)\n }\n const res = await multi.exec()\n return Array.isArray(res) && res.some((r) => typeof r === 'number' && r > 0)\n }\n\n genSlugKey(...args: any[]): string {\n return createSlug(args.join(' '))\n }\n\n private readonly reconnectStrategy = (attempts: number): number => {\n if (attempts > this.reconnectOptions.maxAttempts) {\n this.logger.error('Too many retries on Redis server. Exiting')\n process.exit()\n } else {\n const wait: number = Math.min(this.reconnectOptions.minConnectDelay * Math.pow(2, attempts), this.reconnectOptions.maxConnectDelay)\n this.logger.warn(`Retrying connection to Redis server in ${wait / 1000}s`)\n return wait\n }\n }\n\n private getTTL(ttl: number): number {\n /* ttl (seconds):\n - 0: infinite expiration\n - undefined: default ttl\n */\n return ttl ? ttl : ttl === 0 ? this.infiniteExpiration : this.defaultTTL\n }\n\n private serialize(data: any) {\n if (data === undefined || data === null) {\n return 'null'\n }\n return JSON.stringify(data)\n }\n\n private deserialize(data: any) {\n if (data === null) {\n return undefined\n }\n return JSON.parse(data)\n }\n}\n"],"names":["RedisCacheAdapter","onModuleInit","client","on","e","logger","error","message","log","options","url","connect","catch","onModuleDestroy","isOpen","close","has","key","exists","keys","pattern","matches","scanIterator","MATCH","COUNT","push","get","deserialize","mget","mGet","map","v","set","data","ttl","exp","getTTL","serialize","expiration","type","value","undefined","del","unlink","mdel","multi","res","exec","Array","isArray","some","r","genSlugKey","args","createSlug","join","infiniteExpiration","defaultTTL","JSON","stringify","parse","configuration","cache","Logger","Cache","name","toUpperCase","reconnectOptions","maxAttempts","minConnectDelay","maxConnectDelay","reconnectStrategy","attempts","process","exit","wait","Math","min","pow","warn","createClient","redis","socket","noDelay"],"mappings":"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"}
@@ -27,6 +27,7 @@ describe(_cacheservice.Cache.name, ()=>{
27
27
  'fatal'
28
28
  ]);
29
29
  cache = module.get(_cacheservice.Cache);
30
+ cache.onModuleInit();
30
31
  });
31
32
  afterAll(async ()=>{
32
33
  await module.close();
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../backend/src/infrastructure/cache/cache.e2e-spec.ts"],"sourcesContent":["/*\n * Copyright (C) 2012-2025 Johan Legrand <johan.legrand@sync-in.com>\n * This file is part of Sync-in | The open source file sync and share solution\n * See the LICENSE file for licensing details\n */\n\nimport { Test, TestingModule } from '@nestjs/testing'\nimport { LoggerModule } from 'nestjs-pino'\nimport { setTimeout } from 'node:timers/promises'\nimport { DatabaseModule } from '../database/database.module'\nimport { CacheModule } from './cache.module'\nimport { Cache } from './services/cache.service'\n\ndescribe(Cache.name, () => {\n let module: TestingModule\n let cache: Cache\n\n beforeAll(async () => {\n module = await Test.createTestingModule({\n imports: [CacheModule, LoggerModule.forRoot(), DatabaseModule]\n }).compile()\n\n module.useLogger(['fatal'])\n cache = module.get<Cache>(Cache)\n })\n\n afterAll(async () => {\n await module.close()\n })\n\n it('should be defined', () => {\n expect(cache).toBeDefined()\n })\n\n it('should create the key & value', async () => {\n expect(await cache.set('foo', 'bar')).toBe(true)\n expect(await cache.set('undefined', undefined)).toBe(true)\n })\n\n it('should get all keys defined', async () => {\n expect(await cache.keys('*')).toEqual(expect.arrayContaining(['foo', 'undefined']))\n })\n\n it('should has (or not) the key', async () => {\n expect(await cache.has('bar')).toBe(false)\n expect(await cache.has('fo')).toBe(false)\n expect(await cache.has('foo')).toBe(true)\n expect(await cache.has('undefined')).toBe(true)\n })\n\n it('should get value from key', async () => {\n expect(await cache.get('foo')).toBe('bar')\n expect(await cache.get('undefined')).toBeNull()\n expect(await cache.get('unknown')).toBeUndefined()\n })\n\n it('should get values from keys', async () => {\n const values = await cache.mget(['foo', 'undefined'])\n expect(values).toHaveLength(2)\n expect(values[0]).toBe('bar')\n expect(values[1]).toBeNull()\n })\n\n it('should delete the key', async () => {\n expect(await cache.del('foo')).toBe(true)\n expect(await cache.has('foo')).toBe(false)\n expect(await cache.del('undefined')).toBe(true)\n expect(await cache.get('foo')).toBeUndefined()\n expect(await cache.del('unknown')).toBe(false)\n })\n\n it('should search & delete multiple keys', async () => {\n expect(await cache.set('foo', 'bar')).toBe(true)\n expect(await cache.set('foo2', 'bar2')).toBe(true)\n expect(await cache.keys('foo*')).toEqual(expect.arrayContaining(['foo', 'foo2']))\n expect(await cache.mdel(['foo', 'foo2'])).toBe(true)\n expect(await cache.keys('foo*')).toHaveLength(0)\n })\n\n it('should create the key & value with a TTL', async () => {\n expect(await cache.set('foo', 'bar', 1)).toBe(true)\n expect(await cache.get('foo')).toBe('bar')\n await setTimeout(2000)\n expect(await cache.has('foo')).toBe(false)\n expect(await cache.get('foo')).toBeUndefined()\n })\n\n it('should create a slug key from parameters', () => {\n expect(cache.genSlugKey('foo', 'BAR', 12341)).toBe('foo-bar-12341')\n })\n\n // it('should exit if maxConnectRetry is reached', () => {\n // const mockExit = jest.spyOn(process, 'exit').mockImplementation(() => {\n // throw new Error('process.exit')\n // })\n // expect(() => RedisCache.redisReconnectStrategy(RedisCache.redisReconnectOptions.maxAttempts + 1)).toThrow()\n // expect(mockExit).toHaveBeenCalledTimes(1)\n // mockExit.mockRestore()\n // })\n //\n // it('should not exit if maxConnectRetry is not reached', () => {\n // expect(RedisCache.redisReconnectStrategy(1)).toBeGreaterThan(0)\n // })\n})\n"],"names":["describe","Cache","name","module","cache","beforeAll","Test","createTestingModule","imports","CacheModule","LoggerModule","forRoot","DatabaseModule","compile","useLogger","get","afterAll","close","it","expect","toBeDefined","set","toBe","undefined","keys","toEqual","arrayContaining","has","toBeNull","toBeUndefined","values","mget","toHaveLength","del","mdel","setTimeout","genSlugKey"],"mappings":"AAAA;;;;CAIC;;;;yBAEmC;4BACP;0BACF;gCACI;6BACH;8BACN;AAEtBA,SAASC,mBAAK,CAACC,IAAI,EAAE;IACnB,IAAIC;IACJ,IAAIC;IAEJC,UAAU;QACRF,SAAS,MAAMG,aAAI,CAACC,mBAAmB,CAAC;YACtCC,SAAS;gBAACC,wBAAW;gBAAEC,wBAAY,CAACC,OAAO;gBAAIC,8BAAc;aAAC;QAChE,GAAGC,OAAO;QAEVV,OAAOW,SAAS,CAAC;YAAC;SAAQ;QAC1BV,QAAQD,OAAOY,GAAG,CAAQd,mBAAK;IACjC;IAEAe,SAAS;QACP,MAAMb,OAAOc,KAAK;IACpB;IAEAC,GAAG,qBAAqB;QACtBC,OAAOf,OAAOgB,WAAW;IAC3B;IAEAF,GAAG,iCAAiC;QAClCC,OAAO,MAAMf,MAAMiB,GAAG,CAAC,OAAO,QAAQC,IAAI,CAAC;QAC3CH,OAAO,MAAMf,MAAMiB,GAAG,CAAC,aAAaE,YAAYD,IAAI,CAAC;IACvD;IAEAJ,GAAG,+BAA+B;QAChCC,OAAO,MAAMf,MAAMoB,IAAI,CAAC,MAAMC,OAAO,CAACN,OAAOO,eAAe,CAAC;YAAC;YAAO;SAAY;IACnF;IAEAR,GAAG,+BAA+B;QAChCC,OAAO,MAAMf,MAAMuB,GAAG,CAAC,QAAQL,IAAI,CAAC;QACpCH,OAAO,MAAMf,MAAMuB,GAAG,CAAC,OAAOL,IAAI,CAAC;QACnCH,OAAO,MAAMf,MAAMuB,GAAG,CAAC,QAAQL,IAAI,CAAC;QACpCH,OAAO,MAAMf,MAAMuB,GAAG,CAAC,cAAcL,IAAI,CAAC;IAC5C;IAEAJ,GAAG,6BAA6B;QAC9BC,OAAO,MAAMf,MAAMW,GAAG,CAAC,QAAQO,IAAI,CAAC;QACpCH,OAAO,MAAMf,MAAMW,GAAG,CAAC,cAAca,QAAQ;QAC7CT,OAAO,MAAMf,MAAMW,GAAG,CAAC,YAAYc,aAAa;IAClD;IAEAX,GAAG,+BAA+B;QAChC,MAAMY,SAAS,MAAM1B,MAAM2B,IAAI,CAAC;YAAC;YAAO;SAAY;QACpDZ,OAAOW,QAAQE,YAAY,CAAC;QAC5Bb,OAAOW,MAAM,CAAC,EAAE,EAAER,IAAI,CAAC;QACvBH,OAAOW,MAAM,CAAC,EAAE,EAAEF,QAAQ;IAC5B;IAEAV,GAAG,yBAAyB;QAC1BC,OAAO,MAAMf,MAAM6B,GAAG,CAAC,QAAQX,IAAI,CAAC;QACpCH,OAAO,MAAMf,MAAMuB,GAAG,CAAC,QAAQL,IAAI,CAAC;QACpCH,OAAO,MAAMf,MAAM6B,GAAG,CAAC,cAAcX,IAAI,CAAC;QAC1CH,OAAO,MAAMf,MAAMW,GAAG,CAAC,QAAQc,aAAa;QAC5CV,OAAO,MAAMf,MAAM6B,GAAG,CAAC,YAAYX,IAAI,CAAC;IAC1C;IAEAJ,GAAG,wCAAwC;QACzCC,OAAO,MAAMf,MAAMiB,GAAG,CAAC,OAAO,QAAQC,IAAI,CAAC;QAC3CH,OAAO,MAAMf,MAAMiB,GAAG,CAAC,QAAQ,SAASC,IAAI,CAAC;QAC7CH,OAAO,MAAMf,MAAMoB,IAAI,CAAC,SAASC,OAAO,CAACN,OAAOO,eAAe,CAAC;YAAC;YAAO;SAAO;QAC/EP,OAAO,MAAMf,MAAM8B,IAAI,CAAC;YAAC;YAAO;SAAO,GAAGZ,IAAI,CAAC;QAC/CH,OAAO,MAAMf,MAAMoB,IAAI,CAAC,SAASQ,YAAY,CAAC;IAChD;IAEAd,GAAG,4CAA4C;QAC7CC,OAAO,MAAMf,MAAMiB,GAAG,CAAC,OAAO,OAAO,IAAIC,IAAI,CAAC;QAC9CH,OAAO,MAAMf,MAAMW,GAAG,CAAC,QAAQO,IAAI,CAAC;QACpC,MAAMa,IAAAA,oBAAU,EAAC;QACjBhB,OAAO,MAAMf,MAAMuB,GAAG,CAAC,QAAQL,IAAI,CAAC;QACpCH,OAAO,MAAMf,MAAMW,GAAG,CAAC,QAAQc,aAAa;IAC9C;IAEAX,GAAG,4CAA4C;QAC7CC,OAAOf,MAAMgC,UAAU,CAAC,OAAO,OAAO,QAAQd,IAAI,CAAC;IACrD;AAEA,0DAA0D;AAC1D,4EAA4E;AAC5E,sCAAsC;AACtC,OAAO;AACP,gHAAgH;AAChH,8CAA8C;AAC9C,2BAA2B;AAC3B,KAAK;AACL,EAAE;AACF,kEAAkE;AAClE,oEAAoE;AACpE,KAAK;AACP"}
1
+ {"version":3,"sources":["../../../../backend/src/infrastructure/cache/cache.e2e-spec.ts"],"sourcesContent":["/*\n * Copyright (C) 2012-2025 Johan Legrand <johan.legrand@sync-in.com>\n * This file is part of Sync-in | The open source file sync and share solution\n * See the LICENSE file for licensing details\n */\n\nimport { Test, TestingModule } from '@nestjs/testing'\nimport { LoggerModule } from 'nestjs-pino'\nimport { setTimeout } from 'node:timers/promises'\nimport { DatabaseModule } from '../database/database.module'\nimport { CacheModule } from './cache.module'\nimport { Cache } from './services/cache.service'\n\ndescribe(Cache.name, () => {\n let module: TestingModule\n let cache: Cache\n\n beforeAll(async () => {\n module = await Test.createTestingModule({\n imports: [CacheModule, LoggerModule.forRoot(), DatabaseModule]\n }).compile()\n\n module.useLogger(['fatal'])\n cache = module.get<Cache>(Cache)\n cache.onModuleInit()\n })\n\n afterAll(async () => {\n await module.close()\n })\n\n it('should be defined', () => {\n expect(cache).toBeDefined()\n })\n\n it('should create the key & value', async () => {\n expect(await cache.set('foo', 'bar')).toBe(true)\n expect(await cache.set('undefined', undefined)).toBe(true)\n })\n\n it('should get all keys defined', async () => {\n expect(await cache.keys('*')).toEqual(expect.arrayContaining(['foo', 'undefined']))\n })\n\n it('should has (or not) the key', async () => {\n expect(await cache.has('bar')).toBe(false)\n expect(await cache.has('fo')).toBe(false)\n expect(await cache.has('foo')).toBe(true)\n expect(await cache.has('undefined')).toBe(true)\n })\n\n it('should get value from key', async () => {\n expect(await cache.get('foo')).toBe('bar')\n expect(await cache.get('undefined')).toBeNull()\n expect(await cache.get('unknown')).toBeUndefined()\n })\n\n it('should get values from keys', async () => {\n const values = await cache.mget(['foo', 'undefined'])\n expect(values).toHaveLength(2)\n expect(values[0]).toBe('bar')\n expect(values[1]).toBeNull()\n })\n\n it('should delete the key', async () => {\n expect(await cache.del('foo')).toBe(true)\n expect(await cache.has('foo')).toBe(false)\n expect(await cache.del('undefined')).toBe(true)\n expect(await cache.get('foo')).toBeUndefined()\n expect(await cache.del('unknown')).toBe(false)\n })\n\n it('should search & delete multiple keys', async () => {\n expect(await cache.set('foo', 'bar')).toBe(true)\n expect(await cache.set('foo2', 'bar2')).toBe(true)\n expect(await cache.keys('foo*')).toEqual(expect.arrayContaining(['foo', 'foo2']))\n expect(await cache.mdel(['foo', 'foo2'])).toBe(true)\n expect(await cache.keys('foo*')).toHaveLength(0)\n })\n\n it('should create the key & value with a TTL', async () => {\n expect(await cache.set('foo', 'bar', 1)).toBe(true)\n expect(await cache.get('foo')).toBe('bar')\n await setTimeout(2000)\n expect(await cache.has('foo')).toBe(false)\n expect(await cache.get('foo')).toBeUndefined()\n })\n\n it('should create a slug key from parameters', () => {\n expect(cache.genSlugKey('foo', 'BAR', 12341)).toBe('foo-bar-12341')\n })\n\n // it('should exit if maxConnectRetry is reached', () => {\n // const mockExit = jest.spyOn(process, 'exit').mockImplementation(() => {\n // throw new Error('process.exit')\n // })\n // expect(() => RedisCache.redisReconnectStrategy(RedisCache.redisReconnectOptions.maxAttempts + 1)).toThrow()\n // expect(mockExit).toHaveBeenCalledTimes(1)\n // mockExit.mockRestore()\n // })\n //\n // it('should not exit if maxConnectRetry is not reached', () => {\n // expect(RedisCache.redisReconnectStrategy(1)).toBeGreaterThan(0)\n // })\n})\n"],"names":["describe","Cache","name","module","cache","beforeAll","Test","createTestingModule","imports","CacheModule","LoggerModule","forRoot","DatabaseModule","compile","useLogger","get","onModuleInit","afterAll","close","it","expect","toBeDefined","set","toBe","undefined","keys","toEqual","arrayContaining","has","toBeNull","toBeUndefined","values","mget","toHaveLength","del","mdel","setTimeout","genSlugKey"],"mappings":"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"}