diff --git a/package.json b/package.json index f4e2014a..2a1c8139 100644 --- a/package.json +++ b/package.json @@ -67,10 +67,12 @@ "lru-cache": "^11.0.0", "mime-types": "^2.1.35", "multer": "1.4.5-lts.1", + "node-cache": "^5.1.2", "node-cron": "^3.0.3", "pg": "^8.12.0", "reflect-metadata": "^0.2.2", "rollup": "^4.12.0", + "sharp": "^0.33.5", "swagger-ui-express": "^5.0.0", "tsoa": "^6.2.1", "tsyringe": "^4.8.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2da93c03..08e7eedb 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -131,6 +131,9 @@ importers: multer: specifier: 1.4.5-lts.1 version: 1.4.5-lts.1 + node-cache: + specifier: ^5.1.2 + version: 5.1.2 node-cron: specifier: ^3.0.3 version: 3.0.3 @@ -143,6 +146,9 @@ importers: rollup: specifier: ^4.12.0 version: 4.12.0 + sharp: + specifier: ^0.33.5 + version: 0.33.5 swagger-ui-express: specifier: ^5.0.0 version: 5.0.0(express@4.19.2) @@ -360,6 +366,7 @@ packages: '@ardatan/relay-compiler@12.0.0': resolution: {integrity: sha512-9anThAaj1dQr6IGmzBMcfzOQKTa5artjuPmw8NYK/fiGEMjADbSguBY2FMDykt+QhilR3wc9VA/3yVju7JHg7Q==} + hasBin: true peerDependencies: graphql: '*' @@ -773,6 +780,9 @@ packages: peerDependencies: graphql: ^15.0.0 || ^16.0.0 + '@emnapi/runtime@1.3.1': + resolution: {integrity: sha512-kEBmG8KyqtxJZv+ygbEim+KCGtIq1fC22Ms3S4ziXmYKm8uyoLX0MHONVKwp+9opg390VaKRNt4a7A9NwmpNhw==} + '@envelop/core@5.0.0': resolution: {integrity: sha512-aJdnH/ptv+cvwfvciCBe7TSvccBwo9g0S5f6u35TBVzRVqIGkK03lFlIL+x1cnfZgN9EfR2b1PH2galrT1CdCQ==} engines: {node: '>=18.0.0'} @@ -1083,6 +1093,7 @@ packages: '@graphql-codegen/cli@5.0.2': resolution: {integrity: sha512-MBIaFqDiLKuO4ojN6xxG9/xL9wmfD3ZjZ7RsPjwQnSHBCUXnEkdKvX+JVpx87Pq29Ycn8wTJUguXnTZ7Di0Mlw==} + hasBin: true peerDependencies: '@parcel/watcher': ^2.1.0 graphql: ^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 @@ -1507,6 +1518,111 @@ packages: '@hypercerts-org/sdk@2.5.0-beta.6': resolution: {integrity: sha512-v24hjmCwkL2/lkbQbYxzepLAJOc2SwfHVBoADNcdcT+/s7Fvpq5I+MddlWHYDcBLacPhyF3k+F9O/tkwvofY1g==} + '@img/sharp-darwin-arm64@0.33.5': + resolution: {integrity: sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [darwin] + + '@img/sharp-darwin-x64@0.33.5': + resolution: {integrity: sha512-fyHac4jIc1ANYGRDxtiqelIbdWkIuQaI84Mv45KvGRRxSAa7o7d1ZKAOBaYbnepLC1WqxfpimdeWfvqqSGwR2Q==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [darwin] + + '@img/sharp-libvips-darwin-arm64@1.0.4': + resolution: {integrity: sha512-XblONe153h0O2zuFfTAbQYAX2JhYmDHeWikp1LM9Hul9gVPjFY427k6dFEcOL72O01QxQsWi761svJ/ev9xEDg==} + cpu: [arm64] + os: [darwin] + + '@img/sharp-libvips-darwin-x64@1.0.4': + resolution: {integrity: sha512-xnGR8YuZYfJGmWPvmlunFaWJsb9T/AO2ykoP3Fz/0X5XV2aoYBPkX6xqCQvUTKKiLddarLaxpzNe+b1hjeWHAQ==} + cpu: [x64] + os: [darwin] + + '@img/sharp-libvips-linux-arm64@1.0.4': + resolution: {integrity: sha512-9B+taZ8DlyyqzZQnoeIvDVR/2F4EbMepXMc/NdVbkzsJbzkUjhXv/70GQJ7tdLA4YJgNP25zukcxpX2/SueNrA==} + cpu: [arm64] + os: [linux] + + '@img/sharp-libvips-linux-arm@1.0.5': + resolution: {integrity: sha512-gvcC4ACAOPRNATg/ov8/MnbxFDJqf/pDePbBnuBDcjsI8PssmjoKMAz4LtLaVi+OnSb5FK/yIOamqDwGmXW32g==} + cpu: [arm] + os: [linux] + + '@img/sharp-libvips-linux-s390x@1.0.4': + resolution: {integrity: sha512-u7Wz6ntiSSgGSGcjZ55im6uvTrOxSIS8/dgoVMoiGE9I6JAfU50yH5BoDlYA1tcuGS7g/QNtetJnxA6QEsCVTA==} + cpu: [s390x] + os: [linux] + + '@img/sharp-libvips-linux-x64@1.0.4': + resolution: {integrity: sha512-MmWmQ3iPFZr0Iev+BAgVMb3ZyC4KeFc3jFxnNbEPas60e1cIfevbtuyf9nDGIzOaW9PdnDciJm+wFFaTlj5xYw==} + cpu: [x64] + os: [linux] + + '@img/sharp-libvips-linuxmusl-arm64@1.0.4': + resolution: {integrity: sha512-9Ti+BbTYDcsbp4wfYib8Ctm1ilkugkA/uscUn6UXK1ldpC1JjiXbLfFZtRlBhjPZ5o1NCLiDbg8fhUPKStHoTA==} + cpu: [arm64] + os: [linux] + + '@img/sharp-libvips-linuxmusl-x64@1.0.4': + resolution: {integrity: sha512-viYN1KX9m+/hGkJtvYYp+CCLgnJXwiQB39damAO7WMdKWlIhmYTfHjwSbQeUK/20vY154mwezd9HflVFM1wVSw==} + cpu: [x64] + os: [linux] + + '@img/sharp-linux-arm64@0.33.5': + resolution: {integrity: sha512-JMVv+AMRyGOHtO1RFBiJy/MBsgz0x4AWrT6QoEVVTyh1E39TrCUpTRI7mx9VksGX4awWASxqCYLCV4wBZHAYxA==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [linux] + + '@img/sharp-linux-arm@0.33.5': + resolution: {integrity: sha512-JTS1eldqZbJxjvKaAkxhZmBqPRGmxgu+qFKSInv8moZ2AmT5Yib3EQ1c6gp493HvrvV8QgdOXdyaIBrhvFhBMQ==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm] + os: [linux] + + '@img/sharp-linux-s390x@0.33.5': + resolution: {integrity: sha512-y/5PCd+mP4CA/sPDKl2961b+C9d+vPAveS33s6Z3zfASk2j5upL6fXVPZi7ztePZ5CuH+1kW8JtvxgbuXHRa4Q==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [s390x] + os: [linux] + + '@img/sharp-linux-x64@0.33.5': + resolution: {integrity: sha512-opC+Ok5pRNAzuvq1AG0ar+1owsu842/Ab+4qvU879ippJBHvyY5n2mxF1izXqkPYlGuP/M556uh53jRLJmzTWA==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [linux] + + '@img/sharp-linuxmusl-arm64@0.33.5': + resolution: {integrity: sha512-XrHMZwGQGvJg2V/oRSUfSAfjfPxO+4DkiRh6p2AFjLQztWUuY/o8Mq0eMQVIY7HJ1CDQUJlxGGZRw1a5bqmd1g==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [linux] + + '@img/sharp-linuxmusl-x64@0.33.5': + resolution: {integrity: sha512-WT+d/cgqKkkKySYmqoZ8y3pxx7lx9vVejxW/W4DOFMYVSkErR+w7mf2u8m/y4+xHe7yY9DAXQMWQhpnMuFfScw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [linux] + + '@img/sharp-wasm32@0.33.5': + resolution: {integrity: sha512-ykUW4LVGaMcU9lu9thv85CbRMAwfeadCJHRsg2GmeRa/cJxsVY9Rbd57JcMxBkKHag5U/x7TSBpScF4U8ElVzg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [wasm32] + + '@img/sharp-win32-ia32@0.33.5': + resolution: {integrity: sha512-T36PblLaTwuVJ/zw/LaH0PdZkRz5rd3SmMHX8GSmR7vtNSP5Z6bQkExdSK7xGWyxLw4sUknBuugTelgw2faBbQ==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [ia32] + os: [win32] + + '@img/sharp-win32-x64@0.33.5': + resolution: {integrity: sha512-MpY/o8/8kj+EcnxwvrP4aTJSWw/aZ7JIGR4aBeZkZw5B7/Jn+tY9/VNwtcoGmdT7GfggGIU4kygOMSbYnOrAbg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [win32] + '@inquirer/checkbox@2.3.5': resolution: {integrity: sha512-3V0OSykTkE/38GG1DhxRGLBmqefgzRg2EK5A375zz+XEvIWfAHcac31e+zlBDPypRHxhmXc/Oh6v9eOPbH3nAg==} engines: {node: '>=18'} @@ -2152,6 +2268,7 @@ packages: '@openzeppelin/hardhat-upgrades@3.2.1': resolution: {integrity: sha512-Zy5M3QhkzwGdpzQmk+xbWdYOGJWjoTvwbBKYLhctu9B91DoprlhDRaZUwCtunwTdynkTDGdVfGr0kIkvycyKjw==} + hasBin: true peerDependencies: '@nomicfoundation/hardhat-ethers': ^3.0.0 '@nomicfoundation/hardhat-verify': ^2.0.0 @@ -2516,6 +2633,7 @@ packages: '@snaplet/seed@0.97.20': resolution: {integrity: sha512-+lnqESgwP92O1266vsTyoRgrg4hMCUTybBUxDT1ICMBFcvdjgwcOaUt8Xjj81YvxYkZlu5+TTBIjyNQT4nP4jQ==} engines: {node: '>=18.5.0'} + hasBin: true peerDependencies: '@prisma/client': '>=5' '@snaplet/copycat': '>=2' @@ -2574,6 +2692,7 @@ packages: '@swc/cli@0.3.12': resolution: {integrity: sha512-h7bvxT+4+UDrLWJLFHt6V+vNAcUNii2G4aGSSotKz1ECEk4MyEh5CWxmeSscwuz5K3i+4DWTgm4+4EyMCQKn+g==} engines: {node: '>= 16.14.0'} + hasBin: true peerDependencies: '@swc/core': ^1.2.66 chokidar: ^3.5.1 @@ -3873,6 +3992,10 @@ packages: resolution: {integrity: sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==} engines: {node: '>=0.8'} + clone@2.1.2: + resolution: {integrity: sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==} + engines: {node: '>=0.8'} + cmd-shim@6.0.3: resolution: {integrity: sha512-FMabTRlc5t5zjdenF6mS0MBeFZm0XqHqeOkcskKFb/LYCcRQ5fVgLOHVc4Lq9CqABd9zhjwPjMBCJvMCziSVtA==} engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} @@ -3899,6 +4022,13 @@ packages: color-name@1.1.4: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + color-string@1.9.1: + resolution: {integrity: sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==} + + color@4.2.3: + resolution: {integrity: sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==} + engines: {node: '>=12.5.0'} + colorette@2.0.20: resolution: {integrity: sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==} @@ -4875,6 +5005,7 @@ packages: gql.tada@1.8.3: resolution: {integrity: sha512-0H81I3M54jKTDHbnNWhXDf57Ie2d2raxnFCc93zdYjXHnrXe522jrio9AAFwqBlGx/xtaP3ILSSUw7J9H31LAA==} + hasBin: true peerDependencies: typescript: ^5.0.0 @@ -4956,6 +5087,7 @@ packages: hardhat@2.22.18: resolution: {integrity: sha512-2+kUz39gvMo56s75cfLBhiFedkQf+gXdrwCcz4R/5wW0oBdwiyfj2q9BIkMoaA0WIGYYMU2I1Cc4ucTunhfjzw==} + hasBin: true peerDependencies: ts-node: '*' typescript: '*' @@ -5167,6 +5299,9 @@ packages: is-arrayish@0.2.1: resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} + is-arrayish@0.3.2: + resolution: {integrity: sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==} + is-bigint@1.0.4: resolution: {integrity: sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==} @@ -6014,6 +6149,10 @@ packages: node-addon-api@3.2.1: resolution: {integrity: sha512-mmcei9JghVNDYydghQmeDX8KoAm0FAiYyIcUt/N4nhyAipB17pllZQDOJD2fotxABnt4Mdz+dKTO7eftLg4d0A==} + node-cache@5.1.2: + resolution: {integrity: sha512-t1QzWwnk4sjLWaQAS8CHgOJ+RAfmHpxFWmc36IWTiWHQfs0w5JDMBS1b1ZxQteo0vVVuWJvIUKHDkkeK7vIGCg==} + engines: {node: '>= 8.0.0'} + node-cron@3.0.3: resolution: {integrity: sha512-dOal67//nohNgYWb+nWmg5dkFdIwDm8EpeGYMekPMrngV3637lqnX0lbUcCtgibHTz6SEz7DAIjKvKDFYCnO1A==} engines: {node: '>=6.0.0'} @@ -6803,6 +6942,7 @@ packages: semver@5.7.2: resolution: {integrity: sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==} + hasBin: true semver@6.3.1: resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} @@ -6853,6 +6993,10 @@ packages: sha.js@2.4.11: resolution: {integrity: sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==} + sharp@0.33.5: + resolution: {integrity: sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + shebang-command@1.2.0: resolution: {integrity: sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==} engines: {node: '>=0.10.0'} @@ -6894,6 +7038,9 @@ packages: signedsource@1.0.0: resolution: {integrity: sha512-6+eerH9fEnNmi/hyM1DXcRK3pWdoMQtlkQ+ns0ntzunjKqp5i3sKCc80ym8Fib3iaYhdJUOPdhlJWj1tvge2Ww==} + simple-swizzle@0.2.2: + resolution: {integrity: sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==} + simple-update-notifier@2.0.0: resolution: {integrity: sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==} engines: {node: '>=10'} @@ -7280,6 +7427,7 @@ packages: ts-node@10.9.2: resolution: {integrity: sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==} + hasBin: true peerDependencies: '@swc/core': '>=1.2.50' '@swc/wasm': '>=1.2.50' @@ -7294,6 +7442,7 @@ packages: tsconfck@3.1.4: resolution: {integrity: sha512-kdqWFGVJqe+KGYvlSO9NIaWn9jT1Ny4oKVzAJsKii5eoE9snzTJzL4+MMVOMn+fikWGFmKEylcXL710V/kIPJQ==} engines: {node: ^18 || >=20} + hasBin: true peerDependencies: typescript: ^5.0.0 peerDependenciesMeta: @@ -7407,6 +7556,7 @@ packages: typedoc@0.26.5: resolution: {integrity: sha512-Vn9YKdjKtDZqSk+by7beZ+xzkkr8T8CYoiasqyt4TTRFy5+UHzL/mF/o4wGBjRF+rlWQHDb0t6xCpA3JNL5phg==} engines: {node: '>= 18'} + hasBin: true peerDependencies: typescript: 4.6.x || 4.7.x || 4.8.x || 4.9.x || 5.0.x || 5.1.x || 5.2.x || 5.3.x || 5.4.x || 5.5.x @@ -7509,6 +7659,7 @@ packages: update-browserslist-db@1.0.13: resolution: {integrity: sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==} + hasBin: true peerDependencies: browserslist: '>= 4.21.0' @@ -7613,6 +7764,7 @@ packages: vite@5.0.11: resolution: {integrity: sha512-XBMnDjZcNAw/G1gEiskiM1v6yzM4GE5aMGvhWTlHAYYhxb7S3/V1s3m2LDHa8Vh6yIWYYB0iJwsEaS523c4oYA==} engines: {node: ^18.0.0 || >=20.0.0} + hasBin: true peerDependencies: '@types/node': ^18.0.0 || >=20.0.0 less: '*' @@ -7646,6 +7798,7 @@ packages: vitest@2.1.8: resolution: {integrity: sha512-1vBKTZskHw/aosXqQUlVWWlGUxSJR8YtiyZDJAFeW2kPAeX6S3Sool0mjspO+kXLuxVWlEDDowBAeqeAQefqLQ==} engines: {node: ^18.0.0 || >=20.0.0} + hasBin: true peerDependencies: '@edge-runtime/vm': '*' '@types/node': ^18.0.0 || >=20.0.0 @@ -8420,7 +8573,7 @@ snapshots: '@commitlint/is-ignored@19.2.2': dependencies: '@commitlint/types': 19.0.3 - semver: 7.6.0 + semver: 7.6.3 '@commitlint/lint@19.4.1': dependencies: @@ -8516,6 +8669,11 @@ snapshots: - encoding - supports-color + '@emnapi/runtime@1.3.1': + dependencies: + tslib: 2.6.2 + optional: true + '@envelop/core@5.0.0': dependencies: '@envelop/types': 5.0.0 @@ -9799,6 +9957,81 @@ snapshots: - typescript - utf-8-validate + '@img/sharp-darwin-arm64@0.33.5': + optionalDependencies: + '@img/sharp-libvips-darwin-arm64': 1.0.4 + optional: true + + '@img/sharp-darwin-x64@0.33.5': + optionalDependencies: + '@img/sharp-libvips-darwin-x64': 1.0.4 + optional: true + + '@img/sharp-libvips-darwin-arm64@1.0.4': + optional: true + + '@img/sharp-libvips-darwin-x64@1.0.4': + optional: true + + '@img/sharp-libvips-linux-arm64@1.0.4': + optional: true + + '@img/sharp-libvips-linux-arm@1.0.5': + optional: true + + '@img/sharp-libvips-linux-s390x@1.0.4': + optional: true + + '@img/sharp-libvips-linux-x64@1.0.4': + optional: true + + '@img/sharp-libvips-linuxmusl-arm64@1.0.4': + optional: true + + '@img/sharp-libvips-linuxmusl-x64@1.0.4': + optional: true + + '@img/sharp-linux-arm64@0.33.5': + optionalDependencies: + '@img/sharp-libvips-linux-arm64': 1.0.4 + optional: true + + '@img/sharp-linux-arm@0.33.5': + optionalDependencies: + '@img/sharp-libvips-linux-arm': 1.0.5 + optional: true + + '@img/sharp-linux-s390x@0.33.5': + optionalDependencies: + '@img/sharp-libvips-linux-s390x': 1.0.4 + optional: true + + '@img/sharp-linux-x64@0.33.5': + optionalDependencies: + '@img/sharp-libvips-linux-x64': 1.0.4 + optional: true + + '@img/sharp-linuxmusl-arm64@0.33.5': + optionalDependencies: + '@img/sharp-libvips-linuxmusl-arm64': 1.0.4 + optional: true + + '@img/sharp-linuxmusl-x64@0.33.5': + optionalDependencies: + '@img/sharp-libvips-linuxmusl-x64': 1.0.4 + optional: true + + '@img/sharp-wasm32@0.33.5': + dependencies: + '@emnapi/runtime': 1.3.1 + optional: true + + '@img/sharp-win32-ia32@0.33.5': + optional: true + + '@img/sharp-win32-x64@0.33.5': + optional: true + '@inquirer/checkbox@2.3.5': dependencies: '@inquirer/core': 8.2.2 @@ -10398,7 +10631,7 @@ snapshots: '@opentelemetry/core': 1.24.1(@opentelemetry/api@1.8.0) '@opentelemetry/instrumentation': 0.51.1(@opentelemetry/api@1.8.0) '@opentelemetry/semantic-conventions': 1.24.1 - semver: 7.6.0 + semver: 7.6.3 transitivePeerDependencies: - supports-color @@ -10483,7 +10716,7 @@ snapshots: '@types/shimmer': 1.0.5 import-in-the-middle: 1.4.2 require-in-the-middle: 7.3.0 - semver: 7.6.0 + semver: 7.6.3 shimmer: 1.2.1 transitivePeerDependencies: - supports-color @@ -10496,7 +10729,7 @@ snapshots: '@types/shimmer': 1.0.5 import-in-the-middle: 1.7.1 require-in-the-middle: 7.3.0 - semver: 7.6.0 + semver: 7.6.3 shimmer: 1.2.1 transitivePeerDependencies: - supports-color @@ -10508,7 +10741,7 @@ snapshots: '@types/shimmer': 1.0.5 import-in-the-middle: 1.7.4 require-in-the-middle: 7.3.0 - semver: 7.6.0 + semver: 7.6.3 shimmer: 1.2.1 transitivePeerDependencies: - supports-color @@ -11707,7 +11940,7 @@ snapshots: globby: 11.1.0 is-glob: 4.0.3 minimatch: 9.0.4 - semver: 7.6.0 + semver: 7.6.3 ts-api-utils: 1.3.0(typescript@5.5.3) optionalDependencies: typescript: 5.5.3 @@ -12404,7 +12637,7 @@ snapshots: bin-version-check@5.1.0: dependencies: bin-version: 6.0.0 - semver: 7.5.4 + semver: 7.6.3 semver-truncate: 3.0.0 bin-version@6.0.0: @@ -12804,6 +13037,8 @@ snapshots: clone@1.0.4: {} + clone@2.1.2: {} + cmd-shim@6.0.3: {} code-block-writer@12.0.0: {} @@ -12830,6 +13065,16 @@ snapshots: color-name@1.1.4: {} + color-string@1.9.1: + dependencies: + color-name: 1.1.4 + simple-swizzle: 0.2.2 + + color@4.2.3: + dependencies: + color-convert: 2.0.1 + color-string: 1.9.1 + colorette@2.0.20: {} combined-stream@1.0.8: @@ -12887,7 +13132,7 @@ snapshots: dot-prop: 7.2.0 env-paths: 3.0.0 json-schema-typed: 8.0.1 - semver: 7.6.0 + semver: 7.6.3 confbox@0.1.7: {} @@ -14472,6 +14717,8 @@ snapshots: is-arrayish@0.2.1: {} + is-arrayish@0.3.2: {} + is-bigint@1.0.4: dependencies: has-bigints: 1.0.2 @@ -15277,13 +15524,17 @@ snapshots: node-abi@3.62.0: dependencies: - semver: 7.6.0 + semver: 7.6.3 node-addon-api@2.0.2: {} node-addon-api@3.2.1: optional: true + node-cache@5.1.2: + dependencies: + clone: 2.1.2 + node-cron@3.0.3: dependencies: uuid: 8.3.2 @@ -16137,7 +16388,7 @@ snapshots: semver-truncate@3.0.0: dependencies: - semver: 7.6.0 + semver: 7.6.3 semver@5.7.2: {} @@ -16217,6 +16468,32 @@ snapshots: inherits: 2.0.4 safe-buffer: 5.2.1 + sharp@0.33.5: + dependencies: + color: 4.2.3 + detect-libc: 2.0.3 + semver: 7.6.3 + optionalDependencies: + '@img/sharp-darwin-arm64': 0.33.5 + '@img/sharp-darwin-x64': 0.33.5 + '@img/sharp-libvips-darwin-arm64': 1.0.4 + '@img/sharp-libvips-darwin-x64': 1.0.4 + '@img/sharp-libvips-linux-arm': 1.0.5 + '@img/sharp-libvips-linux-arm64': 1.0.4 + '@img/sharp-libvips-linux-s390x': 1.0.4 + '@img/sharp-libvips-linux-x64': 1.0.4 + '@img/sharp-libvips-linuxmusl-arm64': 1.0.4 + '@img/sharp-libvips-linuxmusl-x64': 1.0.4 + '@img/sharp-linux-arm': 0.33.5 + '@img/sharp-linux-arm64': 0.33.5 + '@img/sharp-linux-s390x': 0.33.5 + '@img/sharp-linux-x64': 0.33.5 + '@img/sharp-linuxmusl-arm64': 0.33.5 + '@img/sharp-linuxmusl-x64': 0.33.5 + '@img/sharp-wasm32': 0.33.5 + '@img/sharp-win32-ia32': 0.33.5 + '@img/sharp-win32-x64': 0.33.5 + shebang-command@1.2.0: dependencies: shebang-regex: 1.0.0 @@ -16252,6 +16529,10 @@ snapshots: signedsource@1.0.0: {} + simple-swizzle@0.2.2: + dependencies: + is-arrayish: 0.3.2 + simple-update-notifier@2.0.0: dependencies: semver: 7.5.4 diff --git a/src/graphql/schemas/resolvers/baseTypes.ts b/src/graphql/schemas/resolvers/baseTypes.ts index 1bafa13b..e02e7d8d 100644 --- a/src/graphql/schemas/resolvers/baseTypes.ts +++ b/src/graphql/schemas/resolvers/baseTypes.ts @@ -38,13 +38,14 @@ export function createBaseResolver( readonly supabaseCachingService = container.resolve(SupabaseCachingService); readonly supabaseDataService = container.resolve(SupabaseDataService); - getMetadata(args: GetMetadataArgs, single: boolean = false) { + getMetadataWithoutImage(args: GetMetadataArgs, single: boolean = false) { console.debug( `[${entityFieldName}Resolver::getMetadata] Fetching metadata`, ); try { - const queries = this.supabaseCachingService.getMetadata(args); + const queries = + this.supabaseCachingService.getMetadataWithoutImage(args); if (single) { return queries.data.executeTakeFirst(); } diff --git a/src/graphql/schemas/resolvers/fractionResolver.ts b/src/graphql/schemas/resolvers/fractionResolver.ts index d3653c1b..b2c1cf02 100644 --- a/src/graphql/schemas/resolvers/fractionResolver.ts +++ b/src/graphql/schemas/resolvers/fractionResolver.ts @@ -29,7 +29,7 @@ class FractionResolver extends FractionBaseResolver { return; } - return await this.getMetadata( + return await this.getMetadataWithoutImage( { where: { hypercerts: { id: { eq: fraction.claims_id } } }, }, diff --git a/src/graphql/schemas/resolvers/hyperboardResolver.ts b/src/graphql/schemas/resolvers/hyperboardResolver.ts index 1ed40869..b29dc858 100644 --- a/src/graphql/schemas/resolvers/hyperboardResolver.ts +++ b/src/graphql/schemas/resolvers/hyperboardResolver.ts @@ -42,7 +42,7 @@ class HyperboardResolver extends HyperboardBaseResolver { }).then((res) => res.data), ]); - const metadata = await this.getMetadata({ + const metadata = await this.getMetadataWithoutImage({ where: { hypercerts: { hypercert_id: { in: hypercertIds } } }, }) .then((res) => res.data) diff --git a/src/graphql/schemas/resolvers/hypercertResolver.ts b/src/graphql/schemas/resolvers/hypercertResolver.ts index a3df1a68..a97ed4c3 100644 --- a/src/graphql/schemas/resolvers/hypercertResolver.ts +++ b/src/graphql/schemas/resolvers/hypercertResolver.ts @@ -38,7 +38,7 @@ class HypercertResolver extends HypercertBaseResolver { return; } - return await this.getMetadata( + return await this.getMetadataWithoutImage( { where: { uri: { eq: hypercert.uri } } }, true, ); diff --git a/src/graphql/schemas/resolvers/metadataResolver.ts b/src/graphql/schemas/resolvers/metadataResolver.ts index 20885b02..1f56ded1 100644 --- a/src/graphql/schemas/resolvers/metadataResolver.ts +++ b/src/graphql/schemas/resolvers/metadataResolver.ts @@ -1,18 +1,43 @@ -import { Args, ObjectType, Query, Resolver } from "type-graphql"; +import { + Args, + FieldResolver, + ObjectType, + Query, + Resolver, + Root, +} from "type-graphql"; +import { inject, singleton } from "tsyringe"; import { Metadata } from "../typeDefs/metadataTypeDefs.js"; import { GetMetadataArgs } from "../args/metadataArgs.js"; import { createBaseResolver, DataResponse } from "./baseTypes.js"; +import { MetadataImageService } from "../../../services/MetadataImageService.js"; @ObjectType() export class GetMetadataResponse extends DataResponse(Metadata) {} const MetadataBaseResolver = createBaseResolver("metadata"); +@singleton() @Resolver(() => Metadata) class MetadataResolver extends MetadataBaseResolver { + constructor( + @inject(MetadataImageService) private imageService: MetadataImageService, + ) { + super(); + } + @Query(() => GetMetadataResponse) async metadata(@Args() args: GetMetadataArgs) { - return await this.getMetadata(args); + return await this.getMetadataWithoutImage(args); + } + + @FieldResolver(() => String, { + nullable: true, + description: "Base64 encoded representation of the image of the hypercert", + }) + async image(@Root() metadata: Metadata) { + if (!metadata.uri) return null; + return await this.imageService.getImageByUri(metadata.uri); } } diff --git a/src/graphql/schemas/resolvers/orderResolver.ts b/src/graphql/schemas/resolvers/orderResolver.ts index e780ac05..0e7947b4 100644 --- a/src/graphql/schemas/resolvers/orderResolver.ts +++ b/src/graphql/schemas/resolvers/orderResolver.ts @@ -107,7 +107,7 @@ class OrderResolver extends OrderBaseResolver { true, ); - const metadata = await this.getMetadata( + const metadata = await this.getMetadataWithoutImage( { where: { hypercerts: { diff --git a/src/graphql/schemas/resolvers/salesResolver.ts b/src/graphql/schemas/resolvers/salesResolver.ts index 76fadd37..760079c5 100644 --- a/src/graphql/schemas/resolvers/salesResolver.ts +++ b/src/graphql/schemas/resolvers/salesResolver.ts @@ -48,7 +48,7 @@ class SalesResolver extends SalesBaseResolver { return null; } - const metadata = await this.getMetadata( + const metadata = await this.getMetadataWithoutImage( { where: { hypercerts: { diff --git a/src/graphql/schemas/typeDefs/metadataTypeDefs.ts b/src/graphql/schemas/typeDefs/metadataTypeDefs.ts index 71a40d78..99c325eb 100644 --- a/src/graphql/schemas/typeDefs/metadataTypeDefs.ts +++ b/src/graphql/schemas/typeDefs/metadataTypeDefs.ts @@ -14,11 +14,6 @@ class Metadata extends BasicTypeDef { name?: string; @Field({ nullable: true, description: "Description of the hypercert" }) description?: string; - @Field({ - nullable: true, - description: "Base64 encoded representation of the image of the hypercert", - }) - image?: string; @Field({ nullable: true, description: "URI of the hypercert metadata" }) uri?: string; @Field({ diff --git a/src/services/MetadataImageService.ts b/src/services/MetadataImageService.ts new file mode 100644 index 00000000..1d2c7315 --- /dev/null +++ b/src/services/MetadataImageService.ts @@ -0,0 +1,69 @@ +import { singleton } from "tsyringe"; +import { kyselyCaching } from "../client/kysely.js"; +import { CachingDatabase } from "../types/kyselySupabaseCaching.js"; +import { BaseSupabaseService } from "./BaseSupabaseService.js"; +import NodeCache from "node-cache"; +import sharp from "sharp"; + +@singleton() +export class MetadataImageService extends BaseSupabaseService { + private cache: NodeCache; + private readonly CACHE_TTL = 60 * 60 * 24; // 24 hours + + constructor() { + super(kyselyCaching); + this.cache = new NodeCache({ stdTTL: this.CACHE_TTL }); + } + + // TODO: remove these when we more refactor the services to improve typing and performance + getDataQuery() { + throw new Error("Method not implemented - not needed for image service"); + } + + getCountQuery() { + throw new Error("Method not implemented - not needed for image service"); + } + + async getImageByUri(uri: string): Promise { + // Check cache first + const cachedImage = this.cache.get(uri); + if (cachedImage) return cachedImage; + + // Fetch from database if not cached + const result = await this.db + .selectFrom("metadata") + .select(["image"]) + .where("uri", "=", uri) + .executeTakeFirst(); + + if (!result?.image) return null; + + console.log("result", result.image.slice(0, 100)); + + // Compress image + const compressedImage = await this.compressImage(result.image); + + // Cache the compressed result + this.cache.set(uri, compressedImage); + + return compressedImage; + } + + private async compressImage(base64Image: string): Promise { + // TODO: if image is an URL because of 3rd party input, we should fetch and compress the image + + // Remove the data URL prefix if present + const base64Data = base64Image.replace(/^data:image\/\w+;base64,/, ""); + + const imageBuffer = Buffer.from(base64Data, "base64"); + const compressedBuffer = await sharp(imageBuffer) + .webp({ + quality: 80, + effort: 4, + }) + .toBuffer(); + + // Add back the appropriate data URL prefix for WebP + return `data:image/webp;base64,${compressedBuffer.toString("base64")}`; + } +} diff --git a/src/services/SupabaseCachingService.ts b/src/services/SupabaseCachingService.ts index 89670b56..f12e238f 100644 --- a/src/services/SupabaseCachingService.ts +++ b/src/services/SupabaseCachingService.ts @@ -56,7 +56,7 @@ export class SupabaseCachingService extends BaseSupabaseService }; } - getMetadata(args: GetMetadataArgs) { + getMetadataWithoutImage(args: GetMetadataArgs) { return { data: this.handleGetData("metadata", args), count: this.handleGetCount("metadata", args), @@ -138,9 +138,44 @@ export class SupabaseCachingService extends BaseSupabaseService case "fractions_view": return this.db.selectFrom("fractions_view").selectAll(); case "metadata": + // Skip the image column + // 1. id + // 2. name + // 3. description + // 4. image + // 5. external_url + // 6. work_scope + // 7. work_timeframe_from + // 8. work_timeframe_to + // 9. impact_scope + // 10. impact_timeframe_from + // 11. impact_timeframe_to + // 12. contributors + // 13. rights + // 14. uri + // 15. properties + // 16. allow_list_uri + // 17. parsed return this.db .selectFrom("metadata") - .selectAll("metadata") + .select([ + "id", + "name", + "description", + "external_url", + "work_scope", + "work_timeframe_from", + "work_timeframe_to", + "impact_scope", + "impact_timeframe_from", + "impact_timeframe_to", + "contributors", + "rights", + "uri", + "properties", + "allow_list_uri", + "parsed", + ]) .$if(args.where?.hypercerts, (qb) => qb.innerJoin("claims", "claims.uri", "metadata.uri"), );