From a4ca7c33f558454fb5d44d2d8ab904e814a88983 Mon Sep 17 00:00:00 2001 From: Kan-A-Pesh Date: Mon, 4 Nov 2024 11:28:57 +0100 Subject: [PATCH 01/10] chore(deps): add minio --- package-lock.json | 363 +++++++++++++++++++++++++++++++++++++++++++++- package.json | 1 + 2 files changed, 363 insertions(+), 1 deletion(-) diff --git a/package-lock.json b/package-lock.json index 0eabe19..5493631 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,6 +15,7 @@ "drizzle-orm": "^0.35.3", "express": "^4.19.2", "jsonwebtoken": "^9.0.2", + "minio": "^8.0.2", "pg": "^8.13.0", "qr-base64": "^0.3.0", "redis": "^4.7.0", @@ -3421,6 +3422,13 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/@zxing/text-encoding": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@zxing/text-encoding/-/text-encoding-0.9.0.tgz", + "integrity": "sha512-U/4aVJ2mxI0aDNI8Uq0wEhMgY+u4CNtEb0om3+y3+niDAsoTCOB33UF0sxpzqzdqXLqmvc+vZyAt4O8pPdfkwA==", + "license": "(Unlicense OR Apache-2.0)", + "optional": true + }, "node_modules/abbrev": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", @@ -3643,7 +3651,6 @@ "version": "3.2.6", "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", - "dev": true, "license": "MIT" }, "node_modules/asynckit": { @@ -3653,6 +3660,21 @@ "dev": true, "license": "MIT" }, + "node_modules/available-typed-arrays": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", + "license": "MIT", + "dependencies": { + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/babel-jest": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz", @@ -3832,6 +3854,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/block-stream2": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/block-stream2/-/block-stream2-2.1.0.tgz", + "integrity": "sha512-suhjmLI57Ewpmq00qaygS8UgEq2ly2PCItenIyhMqVjo4t4pGzqMvfgJuX8iWTeSDdfSSqS6j38fL4ToNL7Pfg==", + "license": "MIT", + "dependencies": { + "readable-stream": "^3.4.0" + } + }, "node_modules/body-parser": { "version": "1.20.2", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz", @@ -3878,6 +3909,12 @@ "node": ">=8" } }, + "node_modules/browser-or-node": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/browser-or-node/-/browser-or-node-2.1.1.tgz", + "integrity": "sha512-8CVjaLJGuSKMVTxJ2DpBl5XnlNDiT4cQFeuCJJrvJmts9YrTZDizTX7PjC2s6W4x+MBGZeEY6dGMrF04/6Hgqg==", + "license": "MIT" + }, "node_modules/browserslist": { "version": "4.24.2", "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.2.tgz", @@ -3958,6 +3995,15 @@ "ieee754": "^1.2.1" } }, + "node_modules/buffer-crc32": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-1.0.0.tgz", + "integrity": "sha512-Db1SbgBS/fg/392AblrMJk97KggmvYhr4pB5ZIMTWtaivCPMWLkmb7m21cJvpvgK+J3nsU2CmmixNBZx4vFj/w==", + "license": "MIT", + "engines": { + "node": ">=8.0.0" + } + }, "node_modules/buffer-equal-constant-time": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", @@ -4405,6 +4451,15 @@ "ms": "2.0.0" } }, + "node_modules/decode-uri-component": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.2.tgz", + "integrity": "sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==", + "license": "MIT", + "engines": { + "node": ">=0.10" + } + }, "node_modules/dedent": { "version": "1.5.3", "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.5.3.tgz", @@ -5156,6 +5211,12 @@ "node": ">= 0.6" } }, + "node_modules/eventemitter3": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", + "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==", + "license": "MIT" + }, "node_modules/execa": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", @@ -5299,6 +5360,28 @@ "dev": true, "license": "MIT" }, + "node_modules/fast-xml-parser": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.5.0.tgz", + "integrity": "sha512-/PlTQCI96+fZMAOLMZK4CWG1ItCbfZ/0jx7UIJFChPNrx7tcEgerUgWbeieCM9MfHInUDyK8DWYZ+YrywDJuTg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + }, + { + "type": "paypal", + "url": "https://paypal.me/naturalintelligence" + } + ], + "license": "MIT", + "dependencies": { + "strnum": "^1.0.5" + }, + "bin": { + "fxparser": "src/cli/cli.js" + } + }, "node_modules/fastq": { "version": "1.17.1", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz", @@ -5378,6 +5461,15 @@ "node": ">=8" } }, + "node_modules/filter-obj": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/filter-obj/-/filter-obj-1.1.0.tgz", + "integrity": "sha512-8rXg1ZnX7xzy2NGDVkBVaAy+lSlPNwad13BtgSlLuxfIslyt5Vg64U7tFcCt4WS1R0hvtnQybT/IyCkGZ3DpXQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/finalhandler": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", @@ -5433,6 +5525,15 @@ "dev": true, "license": "ISC" }, + "node_modules/for-each": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", + "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", + "license": "MIT", + "dependencies": { + "is-callable": "^1.1.3" + } + }, "node_modules/foreground-child": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.0.tgz", @@ -5811,6 +5912,21 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/has-unicode": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", @@ -6041,6 +6157,22 @@ "node": ">= 0.10" } }, + "node_modules/is-arguments": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz", + "integrity": "sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-arrayish": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", @@ -6061,6 +6193,18 @@ "node": ">=8" } }, + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-core-module": { "version": "2.15.1", "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.15.1.tgz", @@ -6106,6 +6250,21 @@ "node": ">=6" } }, + "node_modules/is-generator-function": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.0.10.tgz", + "integrity": "sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==", + "license": "MIT", + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-glob": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", @@ -6142,6 +6301,21 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/is-typed-array": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.13.tgz", + "integrity": "sha512-uZ25/bUAlUY5fR4OKT4rZQEBrzQWYV9ZJYGGsUmEJ6thodVJ1HX64ePQ6Z0qPWP+m+Uq6e9UugrE38jeYsDSMw==", + "license": "MIT", + "dependencies": { + "which-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", @@ -7102,6 +7276,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "license": "MIT" + }, "node_modules/lodash.includes": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", @@ -7314,6 +7494,40 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/minio": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/minio/-/minio-8.0.2.tgz", + "integrity": "sha512-7ipWbtgzzboctf+McK+2cXwCrNOhuboTA/O1g9iWa0gH8R4GkeyFWwk12aVDEHdzjPiG8wxnjwfHS7pgraKuHw==", + "license": "Apache-2.0", + "dependencies": { + "async": "^3.2.4", + "block-stream2": "^2.1.0", + "browser-or-node": "^2.1.1", + "buffer-crc32": "^1.0.0", + "eventemitter3": "^5.0.1", + "fast-xml-parser": "^4.4.1", + "ipaddr.js": "^2.0.1", + "lodash": "^4.17.21", + "mime-types": "^2.1.35", + "query-string": "^7.1.3", + "stream-json": "^1.8.0", + "through2": "^4.0.2", + "web-encoding": "^1.1.5", + "xml2js": "^0.5.0 || ^0.6.2" + }, + "engines": { + "node": "^16 || ^18 || >=20" + } + }, + "node_modules/minio/node_modules/ipaddr.js": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.2.0.tgz", + "integrity": "sha512-Ag3wB2o37wslZS19hZqorUnrnzSkpOVy+IiiDEiTqNubEYpYuHWIf6K4psgN2ZWKExS4xhVCrRVfb/wfW8fWJA==", + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, "node_modules/minipass": { "version": "7.1.2", "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", @@ -8015,6 +8229,15 @@ "node": ">=8" } }, + "node_modules/possible-typed-array-names": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz", + "integrity": "sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, "node_modules/postgres-array": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz", @@ -8207,6 +8430,24 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/query-string": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/query-string/-/query-string-7.1.3.tgz", + "integrity": "sha512-hh2WYhq4fi8+b+/2Kg9CEge4fDPvHS534aOOvOZeQ3+Vf2mCFsaFBYj0i+iXcAq6I9Vzp5fjMFBlONvayDC1qg==", + "license": "MIT", + "dependencies": { + "decode-uri-component": "^0.2.2", + "filter-obj": "^1.1.0", + "split-on-first": "^1.0.0", + "strict-uri-encode": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", @@ -8460,6 +8701,12 @@ "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, + "node_modules/sax": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.4.1.tgz", + "integrity": "sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==", + "license": "ISC" + }, "node_modules/semver": { "version": "7.6.3", "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", @@ -8649,6 +8896,15 @@ "source-map": "^0.6.0" } }, + "node_modules/split-on-first": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/split-on-first/-/split-on-first-1.1.0.tgz", + "integrity": "sha512-43ZssAJaMusuKWL8sKUBQXHWOpq8d6CfN/u1p4gUzfJkM05C8rxTmYrkIPTXapZpORA6LkkzcUulJ8FqA7Uudw==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/split2": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", @@ -8696,6 +8952,30 @@ "node": ">= 0.8" } }, + "node_modules/stream-chain": { + "version": "2.2.5", + "resolved": "https://registry.npmjs.org/stream-chain/-/stream-chain-2.2.5.tgz", + "integrity": "sha512-1TJmBx6aSWqZ4tx7aTpBDXK0/e2hhcNSTV8+CbFJtDjbb+I1mZ8lHit0Grw9GRT+6JbIrrDd8esncgBi8aBXGA==", + "license": "BSD-3-Clause" + }, + "node_modules/stream-json": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/stream-json/-/stream-json-1.9.0.tgz", + "integrity": "sha512-TqnfW7hRTKje7UobBzXZJ2qOEDJvdcSVgVIK/fopC03xINFuFqQs8RVjyDT4ry7TmOo2ueAXwpXXXG4tNgtvoQ==", + "license": "BSD-3-Clause", + "dependencies": { + "stream-chain": "^2.2.5" + } + }, + "node_modules/strict-uri-encode": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz", + "integrity": "sha512-QwiXZgpRcKkhTj2Scnn++4PKtWsH0kpzZ62L2R6c/LUVYv7hVnZqcg2+sMuT6R7Jusu1vviK/MFsu6kNJfWlEQ==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, "node_modules/string_decoder": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", @@ -8803,6 +9083,12 @@ "node": ">=0.10.0" } }, + "node_modules/strnum": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/strnum/-/strnum-1.0.5.tgz", + "integrity": "sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA==", + "license": "MIT" + }, "node_modules/superagent": { "version": "9.0.2", "resolved": "https://registry.npmjs.org/superagent/-/superagent-9.0.2.tgz", @@ -8993,6 +9279,15 @@ "node": ">=0.8" } }, + "node_modules/through2": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/through2/-/through2-4.0.2.tgz", + "integrity": "sha512-iOqSav00cVxEEICeD7TjLB1sueEL+81Wpzp2bY17uZjZN0pWZPuo4suZ/61VujxmqSGFfgOcNuTZ85QJwNZQpw==", + "license": "MIT", + "dependencies": { + "readable-stream": "3" + } + }, "node_modules/tmpl": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", @@ -9625,6 +9920,19 @@ "punycode": "^2.1.0" } }, + "node_modules/util": { + "version": "0.12.5", + "resolved": "https://registry.npmjs.org/util/-/util-0.12.5.tgz", + "integrity": "sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "is-arguments": "^1.0.4", + "is-generator-function": "^1.0.7", + "is-typed-array": "^1.1.3", + "which-typed-array": "^1.1.2" + } + }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -9702,6 +10010,18 @@ "makeerror": "1.0.12" } }, + "node_modules/web-encoding": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/web-encoding/-/web-encoding-1.1.5.tgz", + "integrity": "sha512-HYLeVCdJ0+lBYV2FvNZmv3HJ2Nt0QYXqZojk3d9FJOLkwnuhzM9tmamh8d7HPM8QqjKH8DeHkFTx+CFlWpZZDA==", + "license": "MIT", + "dependencies": { + "util": "^0.12.3" + }, + "optionalDependencies": { + "@zxing/text-encoding": "0.9.0" + } + }, "node_modules/webidl-conversions": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", @@ -9733,6 +10053,25 @@ "node": ">= 8" } }, + "node_modules/which-typed-array": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.15.tgz", + "integrity": "sha512-oV0jmFtUky6CXfkqehVvBP/LSWJ2sy4vWMioiENyJLePrBO/yKyV9OyJySfAKosh+RYkIl5zJCNZ8/4JncrpdA==", + "license": "MIT", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/wide-align": { "version": "1.1.5", "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", @@ -9807,6 +10146,28 @@ "node": "^12.13.0 || ^14.15.0 || >=16.0.0" } }, + "node_modules/xml2js": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.6.2.tgz", + "integrity": "sha512-T4rieHaC1EXcES0Kxxj4JWgaUQHDk+qwHcYOCFHfiwKz7tOVPLq7Hjq9dM1WCMhylqMEfP7hMcOIChvotiZegA==", + "license": "MIT", + "dependencies": { + "sax": ">=0.6.0", + "xmlbuilder": "~11.0.0" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/xmlbuilder": { + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz", + "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==", + "license": "MIT", + "engines": { + "node": ">=4.0" + } + }, "node_modules/xtend": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", diff --git a/package.json b/package.json index befc3c7..6ce9953 100644 --- a/package.json +++ b/package.json @@ -28,6 +28,7 @@ "drizzle-orm": "^0.35.3", "express": "^4.19.2", "jsonwebtoken": "^9.0.2", + "minio": "^8.0.2", "pg": "^8.13.0", "qr-base64": "^0.3.0", "redis": "^4.7.0", From 607ab4b6f602714dbd6f4edc59a5a57894de7c28 Mon Sep 17 00:00:00 2001 From: Kan-A-Pesh Date: Mon, 4 Nov 2024 11:34:10 +0100 Subject: [PATCH 02/10] chore(deps): add compressorjs --- package-lock.json | 29 +++++++++++++++++++++++++++++ package.json | 1 + 2 files changed, 30 insertions(+) diff --git a/package-lock.json b/package-lock.json index 5493631..c690c8d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,6 +10,7 @@ "license": "MIT", "dependencies": { "bcrypt": "^5.1.1", + "compressorjs": "^1.2.1", "cookie-parser": "^1.4.6", "dotenv": "^16.4.5", "drizzle-orm": "^0.35.3", @@ -3863,6 +3864,12 @@ "readable-stream": "^3.4.0" } }, + "node_modules/blueimp-canvas-to-blob": { + "version": "3.29.0", + "resolved": "https://registry.npmjs.org/blueimp-canvas-to-blob/-/blueimp-canvas-to-blob-3.29.0.tgz", + "integrity": "sha512-0pcSSGxC0QxT+yVkivxIqW0Y4VlO2XSDPofBAqoJ1qJxgH9eiUDLv50Rixij2cDuEfx4M6DpD9UGZpRhT5Q8qg==", + "license": "MIT" + }, "node_modules/body-parser": { "version": "1.20.2", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz", @@ -4317,6 +4324,16 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/compressorjs": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/compressorjs/-/compressorjs-1.2.1.tgz", + "integrity": "sha512-+geIjeRnPhQ+LLvvA7wxBQE5ddeLU7pJ3FsKFWirDw6veY3s9iLxAQEw7lXGHnhCJvBujEQWuNnGzZcvCvdkLQ==", + "license": "MIT", + "dependencies": { + "blueimp-canvas-to-blob": "^3.29.0", + "is-blob": "^2.1.0" + } + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -6193,6 +6210,18 @@ "node": ">=8" } }, + "node_modules/is-blob": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-blob/-/is-blob-2.1.0.tgz", + "integrity": "sha512-SZ/fTft5eUhQM6oF/ZaASFDEdbFVe89Imltn9uZr03wdKMcWNVYSMjQPFtg05QuNkt5l5c135ElvXEQG0rk4tw==", + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-callable": { "version": "1.2.7", "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", diff --git a/package.json b/package.json index 6ce9953..7dca782 100644 --- a/package.json +++ b/package.json @@ -23,6 +23,7 @@ "license": "MIT", "dependencies": { "bcrypt": "^5.1.1", + "compressorjs": "^1.2.1", "cookie-parser": "^1.4.6", "dotenv": "^16.4.5", "drizzle-orm": "^0.35.3", From fb1d028fe39a9e402515c6d73c8ab430b6139d75 Mon Sep 17 00:00:00 2001 From: Kan-A-Pesh Date: Mon, 4 Nov 2024 13:29:00 +0100 Subject: [PATCH 03/10] chore: remove domain from profile redirect uri --- .env | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.env b/.env index 0cab5ab..c528244 100644 --- a/.env +++ b/.env @@ -49,4 +49,4 @@ MAIL_REDIRECT_URL="http://localhost:1337/auth/confirm#{token}" # Profile redirect URL # The {uuid} placeholder will be replaced by the user's UUID -PROFILE_REDIRECT_URL="http://localhost:1337/profile/{uuid}" +PROFILE_REDIRECT_URL="/profile/{uuid}" From c4458fbe2ffa9390300ada49acaa1caa683a020e Mon Sep 17 00:00:00 2001 From: Kan-A-Pesh Date: Mon, 4 Nov 2024 14:50:53 +0100 Subject: [PATCH 04/10] feat: added minio arch & implemented it to users --- .env | 2 +- controllers/users.ts | 2 +- database/init.ts | 2 + database/s3.ts | 87 ++++++++++++++++++++++++++++++++++++++ docker/compose.ci.test.yml | 8 ++-- docker/compose.dev.yml | 17 +++++--- docker/compose.test.yml | 8 ++-- docker/compose.yml | 4 +- env/schema.ts | 2 +- i18n/en/errors.json | 1 + routes/blob.ts | 32 ++++++++++++++ routes/router.ts | 3 ++ routes/users/update.ts | 24 ++++++++++- utils/compress.ts | 30 +++++++++++++ 14 files changed, 204 insertions(+), 18 deletions(-) create mode 100644 routes/blob.ts create mode 100644 utils/compress.ts diff --git a/.env b/.env index c528244..b02e664 100644 --- a/.env +++ b/.env @@ -21,7 +21,7 @@ POSTGRES_HOST=postgres REDIS_HOST=redis MINIO_ROOT_USER=minio -MINIO_ROOT_PASSWORD=minio +MINIO_ROOT_PASSWORD=password MINIO_HOST=minio MINIO_DEFAULT_BUCKETS=george diff --git a/controllers/users.ts b/controllers/users.ts index 6ce7c20..f3b84fa 100644 --- a/controllers/users.ts +++ b/controllers/users.ts @@ -71,7 +71,7 @@ export default abstract class UserController { } } - public static async updateUser(uuid: string, username?: string, avatarUrl?: string, quote?: string) { + public static async updateUser(uuid: string, username?: string, avatarUrl?: string | null, quote?: string) { try { const user = await DB.instance .update(users) diff --git a/database/init.ts b/database/init.ts index 9033cb5..7288242 100644 --- a/database/init.ts +++ b/database/init.ts @@ -1,9 +1,11 @@ import { initDrizzle } from "./config"; import Redis from "./redis"; +import S3 from "./s3"; export function initDatabase() { initDrizzle(); Redis.init(); + S3.init(); return () => { Redis.close(); diff --git a/database/s3.ts b/database/s3.ts index e69de29..e3e449d 100644 --- a/database/s3.ts +++ b/database/s3.ts @@ -0,0 +1,87 @@ +import globals from "@/env/env"; +import Logger from "@/log/logger"; +import compress from "@/utils/compress"; +import { randomUUID } from "crypto"; +import { Client } from "minio"; +import internal from "stream"; + +export default abstract class S3 { + public static client: Client; + + public static init() { + S3.client = new Client({ + endPoint: globals.env.MINIO_HOST, + port: globals.env.MINIO_PORT, + useSSL: false, + accessKey: globals.env.MINIO_ROOT_USER, + secretKey: globals.env.MINIO_ROOT_PASSWORD, + pathStyle: true + }); + + S3.client + .bucketExists(globals.env.MINIO_DEFAULT_BUCKETS) + .then( + (exists) => + !exists && + Logger.error(`s3.ts::init | Default bucket ${globals.env.MINIO_DEFAULT_BUCKETS} does not exist`) + ); + } + + /** + * Uploads a compressed image to Minio as a base64 string. + * @param {Blob} data The image data. + * @param {string} path The path to upload the image to. + */ + public static async putImage(data: Blob, options?: { path?: string; creator?: string }): Promise { + try { + const imagePath = options?.path ?? randomUUID(); + + const compressedData = await compress(data); + const buffer = Buffer.from(compressedData, "base64"); + await S3.client.putObject(globals.env.MINIO_DEFAULT_BUCKETS, imagePath, buffer, compressedData.length, { + "Content-Type": "application/octet-stream", + "Last-Modified": new Date().toUTCString(), + "x-amz-acl": "public-read", + "x-amz-meta-creator": options?.creator + }); + + return imagePath; + } catch (err) { + Logger.error("s3.ts::putImage | Error compressing image", err); + return null; + } + } + + /** + * Gets a base64 image. + * @param {string} path + * @returns {Promise} + */ + public static async getImage(path: string): Promise { + const data: internal.Readable = await S3.client.getObject(globals.env.MINIO_DEFAULT_BUCKETS, path); + if (!data) return null; + + const buffer = await new Promise((resolve, reject) => { + const chunks: Buffer[] = []; + data.on("data", (chunk: Buffer) => { + chunks.push(chunk); + }) + .on("end", () => { + resolve(Buffer.concat(chunks)); + }) + .on("error", (err) => { + reject(err); + }); + }); + + return buffer.toString("base64"); + } + + /** + * @description Deletes an image from the S3 bucket. + * @param {string} path The path of the image to delete. + */ + public static async deleteImage(path: string) { + await S3.client.removeObject(globals.env.MINIO_DEFAULT_BUCKETS, path); + } +} diff --git a/docker/compose.ci.test.yml b/docker/compose.ci.test.yml index ceec444..f2997f8 100644 --- a/docker/compose.ci.test.yml +++ b/docker/compose.ci.test.yml @@ -15,7 +15,7 @@ services: REDIS_HOST: redis MINIO_ROOT_USER: minio - MINIO_ROOT_PASSWORD: minio + MINIO_ROOT_PASSWORD: password MINIO_HOST: minio MINIO_DEFAULT_BUCKETS: george @@ -33,6 +33,8 @@ services: PROFILE_REDIRECT_URL: http://localhost:1337/profile/{uuid} depends_on: - postgres + - redis + - minio postgres: image: postgres:17-alpine @@ -46,8 +48,8 @@ services: image: redis:7.4 minio: - image: "minio/minio:latest" + image: "bitnami/minio:latest" environment: MINIO_ROOT_USER: minio - MINIO_ROOT_PASSWORD: minio + MINIO_ROOT_PASSWORD: password MINIO_DEFAULT_BUCKETS: george diff --git a/docker/compose.dev.yml b/docker/compose.dev.yml index f9880d0..76062d5 100644 --- a/docker/compose.dev.yml +++ b/docker/compose.dev.yml @@ -17,27 +17,32 @@ services: REDIS_HOST: redis MINIO_ROOT_USER: minio - MINIO_ROOT_PASSWORD: minio + MINIO_ROOT_PASSWORD: password MINIO_HOST: minio MINIO_DEFAULT_BUCKETS: george stdin_open: true tty: true depends_on: - postgres + - redis + - minio redis: image: redis:7.4 ports: - - "0.0.0.0:6379:6379" + - "0.0.0.0:3001:6379" minio: - image: minio/minio + image: bitnami/minio environment: MINIO_ROOT_USER: minio - MINIO_ROOT_PASSWORD: minio + MINIO_ROOT_PASSWORD: password MINIO_DEFAULT_BUCKETS: george ports: - - "0.0.0.0:8900:9000" + - "0.0.0.0:3002:9000" + # # Uncomment the next line to have a persistent volume for the Minio server + # volumes: + # - minio:/bitnami/minio/data postgres: image: postgres:17-alpine @@ -47,7 +52,7 @@ services: POSTGRES_PASSWORD: postgres POSTGRES_HOST: postgres ports: - - "0.0.0.0:3030:5432" # Use port 3030 for external debugging + - "0.0.0.0:3003:5432" # Use port 3030 for external debugging # # Uncomment the next line to have a persistent volume for the database # volumes: # - postgres:/var/lib/postgresql/data diff --git a/docker/compose.test.yml b/docker/compose.test.yml index 65f8fb1..4b53265 100644 --- a/docker/compose.test.yml +++ b/docker/compose.test.yml @@ -21,7 +21,7 @@ services: REDIS_HOST: redis MINIO_ROOT_USER: minio - MINIO_ROOT_PASSWORD: minio + MINIO_ROOT_PASSWORD: password MINIO_HOST: minio MINIO_DEFAULT_BUCKETS: george @@ -39,6 +39,8 @@ services: PROFILE_REDIRECT_URL: http://localhost:1337/profile/{uuid} depends_on: - postgres + - redis + - minio postgres: image: postgres:17-alpine @@ -52,8 +54,8 @@ services: image: redis:7.4 minio: - image: "minio/minio:latest" + image: "bitnami/minio:latest" environment: MINIO_ROOT_USER: minio - MINIO_ROOT_PASSWORD: minio + MINIO_ROOT_PASSWORD: password MINIO_DEFAULT_BUCKETS: george diff --git a/docker/compose.yml b/docker/compose.yml index 9cac37f..d3e71b6 100644 --- a/docker/compose.yml +++ b/docker/compose.yml @@ -14,6 +14,8 @@ services: - ../logs:/logs depends_on: - postgres + - redis + - minio postgres: image: postgres:17-alpine @@ -29,7 +31,7 @@ services: image: redis:7.4 minio: - image: "minio/minio:latest" + image: "bitnami/minio:latest" env_file: - path: ../.env required: true diff --git a/env/schema.ts b/env/schema.ts index 9f3b0db..3494bb9 100644 --- a/env/schema.ts +++ b/env/schema.ts @@ -17,7 +17,7 @@ export const envSchema = z.object({ REDIS_PORT: znumber().default("6379"), MINIO_ROOT_USER: z.string().default("minio"), - MINIO_ROOT_PASSWORD: z.string().default("minio"), + MINIO_ROOT_PASSWORD: z.string().default("password"), MINIO_HOST: z.string().default("minio"), MINIO_PORT: znumber().default("9000"), MINIO_DEFAULT_BUCKETS: z.string().default("george"), diff --git a/i18n/en/errors.json b/i18n/en/errors.json index a591e4b..4f6c608 100644 --- a/i18n/en/errors.json +++ b/i18n/en/errors.json @@ -2,6 +2,7 @@ "template": "This is a template error message", "validation": "Validation error", "database": "Database error", + "image": "Invalid image", "auth": { "admin": "Invalid X-ADMIN-KEY header", "toomany": "Too many requests", diff --git a/routes/blob.ts b/routes/blob.ts new file mode 100644 index 0000000..cdb4121 --- /dev/null +++ b/routes/blob.ts @@ -0,0 +1,32 @@ +import s3 from "@/database/s3"; +import Status from "@/models/status"; +import { NextFunction, Request, Response } from "express"; +import { z } from "zod"; + +const params = z.object({ + hash: z.string() +}); + +export default async function Route_Blob_Read(req: Request, res: Response, next: NextFunction) { + const payload = params.safeParse(req.params); + if (!payload.success) { + return Status.send(req, next, { + status: 400, + error: "errors.validation" + }); + } + + const data = await s3.getImage(payload.data.hash); + + if (!data) { + return Status.send(req, next, { + status: 404, + error: "errors.notFound" + }); + } + + return Status.send(req, next, { + status: 200, + data: data + }); +} diff --git a/routes/router.ts b/routes/router.ts index 8ba8a23..c0e5614 100644 --- a/routes/router.ts +++ b/routes/router.ts @@ -5,10 +5,13 @@ import clubsRouter from "./clubs/router"; import dailyRouter from "./daily/router"; import Route_Index_Read from "./index"; import usersRouter from "./users/router"; +import Route_Blob_Read from "./blob"; const router = Router(); router.get("/", Route_Index_Read); +router.get("/blob/:hash", Route_Blob_Read); + router.use("/daily", dailyRouter); router.use("/clubs", clubsRouter); diff --git a/routes/users/update.ts b/routes/users/update.ts index f8ea78f..5564e0f 100644 --- a/routes/users/update.ts +++ b/routes/users/update.ts @@ -1,4 +1,5 @@ import UserController from "@/controllers/users"; +import s3 from "@/database/s3"; import Status from "@/models/status"; import { NextFunction, Request, Response } from "express"; import { string, z } from "zod"; @@ -9,7 +10,7 @@ const params = z.object({ const body = z.object({ username: z.string().optional(), - avatarUrl: z.string().optional(), + avatar: z.string().optional(), quote: z.string().optional() }); @@ -45,10 +46,29 @@ export default async function Route_Users_Update(req: Request, res: Response, ne }); } + let avatarUrl: string | null | undefined = user.avatarUrl || undefined; + + // If the avatar is the default avatar, remove it + if (bodyPayload.data.avatar === "default" && user.avatarUrl) { + avatarUrl = null; + } + // If the body contains an avatar, upload it and update the user's avatarUrl + else if (bodyPayload.data.avatar) { + // Upload the avatar to S3 + avatarUrl = await s3.putImage(new Blob([bodyPayload.data.avatar]), { creator: user.email }); + + if (!avatarUrl) { + return Status.send(req, next, { + status: 500, + error: "errors.image" + }); + } + } + const updatedUser = await UserController.updateUser( user.uuid, bodyPayload.data.username || user.username, - bodyPayload.data.avatarUrl || user.avatarUrl || undefined, + avatarUrl, bodyPayload.data.quote || undefined ); diff --git a/utils/compress.ts b/utils/compress.ts new file mode 100644 index 0000000..c810118 --- /dev/null +++ b/utils/compress.ts @@ -0,0 +1,30 @@ +import Compressor from "compressorjs"; + +const compress: (data: Blob) => Promise = async (data: Blob) => { + return new Promise((resolve, reject) => { + new Compressor(data, { + quality: 0.6, + maxHeight: 512, + maxWidth: 512, + height: 512, + width: 512, + resize: "cover", + convertTypes: "image/webp", + success(result) { + result + .text() + .then((text) => { + resolve(text); + }) + .catch((err) => { + reject(err); + }); + }, + error(err) { + reject(err); + } + }); + }); +}; + +export default compress; From 3af97d2e0e9b2936ded31f746d78c78570d575ba Mon Sep 17 00:00:00 2001 From: Kan-A-Pesh Date: Mon, 4 Nov 2024 14:55:08 +0100 Subject: [PATCH 05/10] fix: dev container mounting node_modules --- docker/compose.dev.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/docker/compose.dev.yml b/docker/compose.dev.yml index 76062d5..aaa9f4d 100644 --- a/docker/compose.dev.yml +++ b/docker/compose.dev.yml @@ -6,6 +6,7 @@ services: ports: - "0.0.0.0:3000:3000" volumes: + - /data/app/node_modules - ..:/data/app environment: NODE_ENV: development From 7c57ce264aabdf3c526ffe937629fc0734699bb8 Mon Sep 17 00:00:00 2001 From: Kan-A-Pesh Date: Mon, 4 Nov 2024 15:06:50 +0100 Subject: [PATCH 06/10] docs: fix typo on putImage JSDoc --- database/s3.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/database/s3.ts b/database/s3.ts index e3e449d..2e5816a 100644 --- a/database/s3.ts +++ b/database/s3.ts @@ -30,7 +30,9 @@ export default abstract class S3 { /** * Uploads a compressed image to Minio as a base64 string. * @param {Blob} data The image data. - * @param {string} path The path to upload the image to. + * @param {string} options.path The path to upload the image to. + * @param {string} options.creator The creator of the image. + * @returns {Promise} The path of the uploaded image. */ public static async putImage(data: Blob, options?: { path?: string; creator?: string }): Promise { try { From 7ea80c4881acf8127aa414cd9e4b14ae9d7a328e Mon Sep 17 00:00:00 2001 From: Kan-A-Pesh Date: Mon, 4 Nov 2024 15:15:20 +0100 Subject: [PATCH 07/10] chore: disabled automatic coverage report opening --- scripts/test.sh | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/scripts/test.sh b/scripts/test.sh index 1dd1dba..927492e 100755 --- a/scripts/test.sh +++ b/scripts/test.sh @@ -41,10 +41,10 @@ out echo "Cleaning up tests..." out docker compose -f $file rm -s -f -v # Open coverage report -if [ "$1" != "--ci" ]; then - echo "Opening coverage report..." - open ./coverage/cov/lcov-report/index.html -fi +# if [ "$1" != "--ci" ]; then +# echo "Opening coverage report..." +# open ./coverage/cov/lcov-report/index.html +# fi # Return result exit $result From 21bd75ab9288ff8f453c305be452d1d77b855d7c Mon Sep 17 00:00:00 2001 From: Kan-A-Pesh Date: Mon, 4 Nov 2024 16:05:38 +0100 Subject: [PATCH 08/10] chore: using "sharp" instead of "compressorjs" as it is not for server-side --- package-lock.json | 449 +++++++++++++++++++++++++++++++++++++++++ package.json | 1 + routes/users/update.ts | 2 +- 3 files changed, 451 insertions(+), 1 deletion(-) diff --git a/package-lock.json b/package-lock.json index c690c8d..54b1c40 100644 --- a/package-lock.json +++ b/package-lock.json @@ -21,6 +21,7 @@ "qr-base64": "^0.3.0", "redis": "^4.7.0", "reflect-metadata": "^0.2.2", + "sharp": "^0.33.5", "typeorm": "^0.3.20", "zod": "^3.23.8" }, @@ -783,6 +784,16 @@ "dev": true, "license": "Apache-2.0" }, + "node_modules/@emnapi/runtime": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.3.1.tgz", + "integrity": "sha512-kEBmG8KyqtxJZv+ygbEim+KCGtIq1fC22Ms3S4ziXmYKm8uyoLX0MHONVKwp+9opg390VaKRNt4a7A9NwmpNhw==", + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, "node_modules/@esbuild-kit/core-utils": { "version": "3.3.2", "resolved": "https://registry.npmjs.org/@esbuild-kit/core-utils/-/core-utils-3.3.2.tgz", @@ -1859,6 +1870,367 @@ "url": "https://github.com/sponsors/nzakas" } }, + "node_modules/@img/sharp-darwin-arm64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.33.5.tgz", + "integrity": "sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-arm64": "1.0.4" + } + }, + "node_modules/@img/sharp-darwin-x64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.33.5.tgz", + "integrity": "sha512-fyHac4jIc1ANYGRDxtiqelIbdWkIuQaI84Mv45KvGRRxSAa7o7d1ZKAOBaYbnepLC1WqxfpimdeWfvqqSGwR2Q==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-x64": "1.0.4" + } + }, + "node_modules/@img/sharp-libvips-darwin-arm64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.0.4.tgz", + "integrity": "sha512-XblONe153h0O2zuFfTAbQYAX2JhYmDHeWikp1LM9Hul9gVPjFY427k6dFEcOL72O01QxQsWi761svJ/ev9xEDg==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-darwin-x64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.0.4.tgz", + "integrity": "sha512-xnGR8YuZYfJGmWPvmlunFaWJsb9T/AO2ykoP3Fz/0X5XV2aoYBPkX6xqCQvUTKKiLddarLaxpzNe+b1hjeWHAQ==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.0.5.tgz", + "integrity": "sha512-gvcC4ACAOPRNATg/ov8/MnbxFDJqf/pDePbBnuBDcjsI8PssmjoKMAz4LtLaVi+OnSb5FK/yIOamqDwGmXW32g==", + "cpu": [ + "arm" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.0.4.tgz", + "integrity": "sha512-9B+taZ8DlyyqzZQnoeIvDVR/2F4EbMepXMc/NdVbkzsJbzkUjhXv/70GQJ7tdLA4YJgNP25zukcxpX2/SueNrA==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-s390x": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.0.4.tgz", + "integrity": "sha512-u7Wz6ntiSSgGSGcjZ55im6uvTrOxSIS8/dgoVMoiGE9I6JAfU50yH5BoDlYA1tcuGS7g/QNtetJnxA6QEsCVTA==", + "cpu": [ + "s390x" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-x64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.0.4.tgz", + "integrity": "sha512-MmWmQ3iPFZr0Iev+BAgVMb3ZyC4KeFc3jFxnNbEPas60e1cIfevbtuyf9nDGIzOaW9PdnDciJm+wFFaTlj5xYw==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-arm64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.0.4.tgz", + "integrity": "sha512-9Ti+BbTYDcsbp4wfYib8Ctm1ilkugkA/uscUn6UXK1ldpC1JjiXbLfFZtRlBhjPZ5o1NCLiDbg8fhUPKStHoTA==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-x64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.0.4.tgz", + "integrity": "sha512-viYN1KX9m+/hGkJtvYYp+CCLgnJXwiQB39damAO7WMdKWlIhmYTfHjwSbQeUK/20vY154mwezd9HflVFM1wVSw==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-linux-arm": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.33.5.tgz", + "integrity": "sha512-JTS1eldqZbJxjvKaAkxhZmBqPRGmxgu+qFKSInv8moZ2AmT5Yib3EQ1c6gp493HvrvV8QgdOXdyaIBrhvFhBMQ==", + "cpu": [ + "arm" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm": "1.0.5" + } + }, + "node_modules/@img/sharp-linux-arm64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.33.5.tgz", + "integrity": "sha512-JMVv+AMRyGOHtO1RFBiJy/MBsgz0x4AWrT6QoEVVTyh1E39TrCUpTRI7mx9VksGX4awWASxqCYLCV4wBZHAYxA==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm64": "1.0.4" + } + }, + "node_modules/@img/sharp-linux-s390x": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.33.5.tgz", + "integrity": "sha512-y/5PCd+mP4CA/sPDKl2961b+C9d+vPAveS33s6Z3zfASk2j5upL6fXVPZi7ztePZ5CuH+1kW8JtvxgbuXHRa4Q==", + "cpu": [ + "s390x" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-s390x": "1.0.4" + } + }, + "node_modules/@img/sharp-linux-x64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.33.5.tgz", + "integrity": "sha512-opC+Ok5pRNAzuvq1AG0ar+1owsu842/Ab+4qvU879ippJBHvyY5n2mxF1izXqkPYlGuP/M556uh53jRLJmzTWA==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-x64": "1.0.4" + } + }, + "node_modules/@img/sharp-linuxmusl-arm64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.33.5.tgz", + "integrity": "sha512-XrHMZwGQGvJg2V/oRSUfSAfjfPxO+4DkiRh6p2AFjLQztWUuY/o8Mq0eMQVIY7HJ1CDQUJlxGGZRw1a5bqmd1g==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-arm64": "1.0.4" + } + }, + "node_modules/@img/sharp-linuxmusl-x64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.33.5.tgz", + "integrity": "sha512-WT+d/cgqKkkKySYmqoZ8y3pxx7lx9vVejxW/W4DOFMYVSkErR+w7mf2u8m/y4+xHe7yY9DAXQMWQhpnMuFfScw==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-x64": "1.0.4" + } + }, + "node_modules/@img/sharp-wasm32": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.33.5.tgz", + "integrity": "sha512-ykUW4LVGaMcU9lu9thv85CbRMAwfeadCJHRsg2GmeRa/cJxsVY9Rbd57JcMxBkKHag5U/x7TSBpScF4U8ElVzg==", + "cpu": [ + "wasm32" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", + "optional": true, + "dependencies": { + "@emnapi/runtime": "^1.2.0" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-ia32": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.33.5.tgz", + "integrity": "sha512-T36PblLaTwuVJ/zw/LaH0PdZkRz5rd3SmMHX8GSmR7vtNSP5Z6bQkExdSK7xGWyxLw4sUknBuugTelgw2faBbQ==", + "cpu": [ + "ia32" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-x64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.33.5.tgz", + "integrity": "sha512-MpY/o8/8kj+EcnxwvrP4aTJSWw/aZ7JIGR4aBeZkZw5B7/Jn+tY9/VNwtcoGmdT7GfggGIU4kygOMSbYnOrAbg==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, "node_modules/@isaacs/cliui": { "version": "8.0.2", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", @@ -4274,6 +4646,19 @@ "dev": true, "license": "MIT" }, + "node_modules/color": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz", + "integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1", + "color-string": "^1.9.0" + }, + "engines": { + "node": ">=12.5.0" + } + }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -4292,6 +4677,16 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "license": "MIT" }, + "node_modules/color-string": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz", + "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==", + "license": "MIT", + "dependencies": { + "color-name": "^1.0.0", + "simple-swizzle": "^0.2.2" + } + }, "node_modules/color-support": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", @@ -8830,6 +9225,45 @@ "sha.js": "bin.js" } }, + "node_modules/sharp": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.33.5.tgz", + "integrity": "sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw==", + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "color": "^4.2.3", + "detect-libc": "^2.0.3", + "semver": "^7.6.3" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "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" + } + }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -8874,6 +9308,21 @@ "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", "license": "ISC" }, + "node_modules/simple-swizzle": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", + "integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==", + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.3.1" + } + }, + "node_modules/simple-swizzle/node_modules/is-arrayish": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", + "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==", + "license": "MIT" + }, "node_modules/simple-update-notifier": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz", diff --git a/package.json b/package.json index 7dca782..c0d6d8b 100644 --- a/package.json +++ b/package.json @@ -34,6 +34,7 @@ "qr-base64": "^0.3.0", "redis": "^4.7.0", "reflect-metadata": "^0.2.2", + "sharp": "^0.33.5", "typeorm": "^0.3.20", "zod": "^3.23.8" }, diff --git a/routes/users/update.ts b/routes/users/update.ts index 5564e0f..9389aaf 100644 --- a/routes/users/update.ts +++ b/routes/users/update.ts @@ -55,7 +55,7 @@ export default async function Route_Users_Update(req: Request, res: Response, ne // If the body contains an avatar, upload it and update the user's avatarUrl else if (bodyPayload.data.avatar) { // Upload the avatar to S3 - avatarUrl = await s3.putImage(new Blob([bodyPayload.data.avatar]), { creator: user.email }); + avatarUrl = await s3.putImage(bodyPayload.data.avatar, { creator: user.email }); if (!avatarUrl) { return Status.send(req, next, { From 8a78b404ffff23fa7808135f9840928e6a39dc7c Mon Sep 17 00:00:00 2001 From: Kan-A-Pesh Date: Mon, 4 Nov 2024 16:06:13 +0100 Subject: [PATCH 09/10] chore: add sharp compression --- utils/compress.ts | 40 ++++++++++++++-------------------------- 1 file changed, 14 insertions(+), 26 deletions(-) diff --git a/utils/compress.ts b/utils/compress.ts index c810118..0ac0037 100644 --- a/utils/compress.ts +++ b/utils/compress.ts @@ -1,30 +1,18 @@ -import Compressor from "compressorjs"; +import sharp from "sharp"; -const compress: (data: Blob) => Promise = async (data: Blob) => { - return new Promise((resolve, reject) => { - new Compressor(data, { - quality: 0.6, - maxHeight: 512, - maxWidth: 512, - height: 512, - width: 512, - resize: "cover", - convertTypes: "image/webp", - success(result) { - result - .text() - .then((text) => { - resolve(text); - }) - .catch((err) => { - reject(err); - }); - }, - error(err) { - reject(err); - } - }); - }); +const compress: (data: string) => Promise = async (data) => { + const inputBuffer = Buffer.from(data, "base64"); + + const outputBuffer = await sharp(inputBuffer) + .resize(512, 512, { + fit: "cover", + position: "center" + }) + .jpeg({ quality: 60, force: true }) + .toFormat("jpeg") + .toBuffer(); + + return outputBuffer.toString("base64"); }; export default compress; From f538cafa95286d154117d6c8b9f527c6f2518b8e Mon Sep 17 00:00:00 2001 From: Kan-A-Pesh Date: Mon, 4 Nov 2024 16:24:57 +0100 Subject: [PATCH 10/10] feat: add avatar upload to users --- database/s3.ts | 42 ++++++++++++++++++++++------------------ i18n/en/errors.json | 5 ++++- routes/blob.ts | 2 +- routes/users/update.ts | 2 +- tests/assets/avatar.png | Bin 0 -> 3996 bytes tests/e2e/users.test.ts | 34 +++++++++++++++++++++++++++++--- tests/unit/blob.test.ts | 23 ++++++++++++++++++++++ 7 files changed, 83 insertions(+), 25 deletions(-) create mode 100644 tests/assets/avatar.png create mode 100644 tests/unit/blob.test.ts diff --git a/database/s3.ts b/database/s3.ts index 2e5816a..2c9d820 100644 --- a/database/s3.ts +++ b/database/s3.ts @@ -29,18 +29,17 @@ export default abstract class S3 { /** * Uploads a compressed image to Minio as a base64 string. - * @param {Blob} data The image data. + * @param {string} data The base64 string of the image. * @param {string} options.path The path to upload the image to. * @param {string} options.creator The creator of the image. * @returns {Promise} The path of the uploaded image. */ - public static async putImage(data: Blob, options?: { path?: string; creator?: string }): Promise { + public static async putImage(data: string, options?: { path?: string; creator?: string }): Promise { try { const imagePath = options?.path ?? randomUUID(); + const image = await compress(data); - const compressedData = await compress(data); - const buffer = Buffer.from(compressedData, "base64"); - await S3.client.putObject(globals.env.MINIO_DEFAULT_BUCKETS, imagePath, buffer, compressedData.length, { + await S3.client.putObject(globals.env.MINIO_DEFAULT_BUCKETS, imagePath, image, image.length, { "Content-Type": "application/octet-stream", "Last-Modified": new Date().toUTCString(), "x-amz-acl": "public-read", @@ -60,23 +59,28 @@ export default abstract class S3 { * @returns {Promise} */ public static async getImage(path: string): Promise { - const data: internal.Readable = await S3.client.getObject(globals.env.MINIO_DEFAULT_BUCKETS, path); - if (!data) return null; + try { + const data: internal.Readable = await S3.client.getObject(globals.env.MINIO_DEFAULT_BUCKETS, path); + if (!data) return null; - const buffer = await new Promise((resolve, reject) => { - const chunks: Buffer[] = []; - data.on("data", (chunk: Buffer) => { - chunks.push(chunk); - }) - .on("end", () => { - resolve(Buffer.concat(chunks)); + const buffer = await new Promise((resolve, reject) => { + const chunks: Buffer[] = []; + data.on("data", (chunk: Buffer) => { + chunks.push(chunk); }) - .on("error", (err) => { - reject(err); - }); - }); + .on("end", () => { + resolve(Buffer.concat(chunks)); + }) + .on("error", (err) => { + reject(err); + }); + }); - return buffer.toString("base64"); + return buffer.toString("base64"); + } catch (err) { + Logger.error("s3.ts::getImage | Error getting image", err); + return null; + } } /** diff --git a/i18n/en/errors.json b/i18n/en/errors.json index 4f6c608..798b8c1 100644 --- a/i18n/en/errors.json +++ b/i18n/en/errors.json @@ -2,7 +2,10 @@ "template": "This is a template error message", "validation": "Validation error", "database": "Database error", - "image": "Invalid image", + "image": { + "invalid": "Invalid image", + "notFound": "Image not found" + }, "auth": { "admin": "Invalid X-ADMIN-KEY header", "toomany": "Too many requests", diff --git a/routes/blob.ts b/routes/blob.ts index cdb4121..d2fc08e 100644 --- a/routes/blob.ts +++ b/routes/blob.ts @@ -21,7 +21,7 @@ export default async function Route_Blob_Read(req: Request, res: Response, next: if (!data) { return Status.send(req, next, { status: 404, - error: "errors.notFound" + error: "errors.image.notFound" }); } diff --git a/routes/users/update.ts b/routes/users/update.ts index 9389aaf..0cd7ecb 100644 --- a/routes/users/update.ts +++ b/routes/users/update.ts @@ -60,7 +60,7 @@ export default async function Route_Users_Update(req: Request, res: Response, ne if (!avatarUrl) { return Status.send(req, next, { status: 500, - error: "errors.image" + error: "errors.image.invalid" }); } } diff --git a/tests/assets/avatar.png b/tests/assets/avatar.png new file mode 100644 index 0000000000000000000000000000000000000000..e5025b9f723dc95df7563d5934711e05524c8702 GIT binary patch literal 3996 zcmc&%X;4#H7QO)yZBTRu1rZZ^x)mHT^dJHqLRb`ma#&PkQ4~-xVGVl#39Ew2;4+MZ zAe(^f5S6eKmZpJ5J7G=GU6#y}?t8cDz5C90zVn^$ z+_ty1-0_v#R{($=)>bFa0w4q3%7ATKp@knYB!rgjfmSXd04V%+m5H*l_KMyPIC zmDm%+Hsx1E-9Ta=qPTazU&GAIE6x9WPrb6Xik9<4GrLM8sezGAKr$uP-Ujv*4d}` zO2}?fDyy6%@%Dz^w3I$#)S`!}-TOJ5825xy?o{iG+P9~VSU0P#%-g40w{PaxfbU=C zBrgb-9TJHzZ}ia(X91!H$V)6rI|K9k^IzmQi{1vlo_m4HXwMpX20^~?w5!155;I46 zJ(#J@86R(^qf-9%LgN7#`@uKU>wrGbR><`*lF5txYYM$3a45hn&H)aSiY>YdV+lqm zOyz2GY0D#n6qFM6${8g%bl`xt;?i`%s`vW{7Jvn|?~7gtWys#xnPop_ZfJaIsJ-;P zpdu2Y=&wW5uCT6f9UEV44d0^LnUM|AqrB?!o2qF~2VzjD9%()CJ(DcZzlb*ydSURK z8L@{y6Q@_=>qIECo2jfF>G2VT^?$6rfQ_o69{9J+{U>f`uYRXth_9ZmUy9P#x>0;A z<4+uB2rr4RM}60?p>FjJsGm#fFiUk*sHkD9yxn#%%^0zn3i))73-EiJ%DpalS*emU zRin9BwDX=_dFr&M^GLO|pl9Ct9F*%uoGzRZF_i0@EV$Mp8C$u1SyUhyB`srCNC}u- z&5bHC1!U`ll9; zzhy^@Qkmra!^<4>+MCA`dqfOBjajJkw)qTO*u}LdO2=c|*di{8L{msqyJtXH=d)TR z%fjN=RkQ5k?9+oxpS^U8++}okuO@(BeTM3*t2;z@a%&No3p#}zl3cc=-u)ayMV1sZ z-N_`^N#;#B%{H0^Wdd_W7ddIDIQ26Ymi6=FvSiIlhKmyYkxlAB5V!c^0f_ zZ3Dp2M2b>c6C)|}Hy<1L3{5l$KoGM6;W!Dl^WZJ%wPNLQ`tH8nWh`Oq$PKK)u3gX?K9hQQ=8&*SC0ia4;w0$u6vJ{3afzjHar$GdB;HtjF-zIhYSKvhzAmNt>9RPIzI;uvK;_nW>xf@I%)gEy2D&1MYu5hE=+U9z zh6<$G-K692uvw3iSd8M-cz^Rwr_=kZ-_$RKUy}>mtw&wxZ!4;; zGOjzW3yqnfBr7vEX_R4Lah==rW0vv7JyM6WIV1QP0`Fx|SUkZI za)nSs3u4A$0>3>WPc7PM<6bRgtNI|{9Y2cO`wE51th<1H{>wz)!Pj~)!0*a;8EWrH zlY#ecu8cmUgC{pPI#oG|;y|2VGGgm!47xY)PrSa*F&JHej5)*^76hGJZQY@;NP;6V zSVS{3_MHKy&uSAx8Uu_KZ;8D#EoPVK$X#HUpJc2)k8A5l2xll@U!?<>G@ki0sJ@79 zUwxeOaL!06CL=eCA#M-)hwUkMLY~k4g>6!?I-TE$gCuNprb76E#*!CQYzMgUf_o;V z2JEOheIB<}(kbLQ#a8P3>!~~BYM+`UoU6u<1bjC&Bf-`)8N`v%OOR{5X@^iB79qs- z`aHq#<=@BIEJ$IVa%HwgT}8tg^dlosYel9D21S-~-djN5er~+}U{47hMZMy3|EJ9* z>C>oj4Q498K4xiB2^k;A>p}j{$M?FgYg$kfdqlp`5}3+URvAs%2hJYLR+Iz$xc^{^ zkWy_nBvVa=|06qQ*^R3yS@iZFXirQ_z+_Upj;n<&W(Mj8RW@#yi5p!R>p%>P%H?pg z1B^mxr>P7%FsD4RSj5cy<0-jIUjuN#C)10?GO4J|X|283w?|5i7n;ztRL=yr-4H{= OG_ba?Jy~V$p73|hp7y-} literal 0 HcmV?d00001 diff --git a/tests/e2e/users.test.ts b/tests/e2e/users.test.ts index 47591eb..9660cc4 100644 --- a/tests/e2e/users.test.ts +++ b/tests/e2e/users.test.ts @@ -1,9 +1,15 @@ import createApp from "@/app"; import AuthController from "@/controllers/auth"; import { get, post, put } from "../utils"; +import { readFileSync } from "fs"; +import path from "path"; const app = createApp("e2e-users"); +const testGlobals = { + userAvatarUrl: null +}; + describe("Test users", () => { const email = "test-users@no-reply.local"; const token = AuthController.generateCreationToken(email); @@ -112,11 +118,13 @@ describe("Test users", () => { const authToken = AuthController.generateAuthToken(email); test("should edit the user", async () => { + const avatar = readFileSync(path.join(__dirname, "..", "assets", "avatar.png"), "base64"); + const res = await put( app, "/users/:uuid", { uuid: userUuid }, - { quote: "test" }, + { quote: "test", avatar: avatar }, { Authorization: `Bearer ${authToken}` } ); @@ -131,13 +139,33 @@ describe("Test users", () => { uuid: userUuid, email: email, username: "test-users", - avatarUrl: null, + avatarUrl: expect.stringMatching( + /([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})/ + ), // UUID v4 regex clubId: null, quote: "test" } } ] }); + + testGlobals.userAvatarUrl = res.body.response[0].data.avatarUrl; + }); + + test("should get the user avatar", async () => { + const res = await get(app, "/blob/:uuid", { uuid: testGlobals.userAvatarUrl ?? "invalid" }); + + expect(res.body).toStrictEqual({ + masterStatus: 200, + sentAt: expect.any(Number), + response: [ + { + status: 200, + success: true, + data: expect.any(String) + } + ] + }); }); test("should get 'invalid token' error", async () => { @@ -179,7 +207,7 @@ describe("Test users", () => { uuid: userUuid, email: email, username: "test-users", - avatarUrl: null, + avatarUrl: testGlobals.userAvatarUrl, clubId: null, quote: "test" } diff --git a/tests/unit/blob.test.ts b/tests/unit/blob.test.ts new file mode 100644 index 0000000..f816772 --- /dev/null +++ b/tests/unit/blob.test.ts @@ -0,0 +1,23 @@ +import createApp from "@/app"; +import { get } from "../utils"; + +const app = createApp("unit-blob"); + +describe("Test status page", () => { + test("should get a 'image not found' error", async () => { + const res = await get(app, "/blob/:uuid", { uuid: "definitely-not-a-valid-uuid" }); + + expect(res.body).toStrictEqual({ + masterStatus: 404, + sentAt: expect.any(Number), + response: [ + { + status: 404, + success: false, + error: "errors.image.notFound", + translatedError: "Image not found" + } + ] + }); + }); +});