google cloud storage

This commit is contained in:
cupcakearmy 2021-11-27 23:37:58 +01:00
parent c05ee888eb
commit 4daf9db207
No known key found for this signature in database
GPG Key ID: 3235314B4D31232F
13 changed files with 557 additions and 60 deletions

5
.gitignore vendored
View File

@ -2,5 +2,8 @@ node_modules
dist
assets
*.tsbuildinfo
morphus.*
data
# Configs
morphus.*
keyfile.json

View File

@ -21,11 +21,13 @@
"typescript": "^4.4.4"
},
"dependencies": {
"@google-cloud/storage": "^5.16.0",
"caniuse-db": "^1.0.30001280",
"class-validator": "^0.13.1",
"convict": "^6.2.1",
"convict-format-with-validator": "^6.2.0",
"device-detector-js": "^3.0.0",
"fast-crc32c": "^2.0.0",
"fastify": "^3.23.1",
"fastify-caching": "^6.1.0",
"fastify-compress": "^3.6.1",

469
pnpm-lock.yaml generated
View File

@ -1,6 +1,7 @@
lockfileVersion: 5.3
specifiers:
'@google-cloud/storage': ^5.16.0
'@types/convict': ^6.1.1
'@types/flat': ^5.0.2
'@types/js-yaml': ^4.0.4
@ -13,6 +14,7 @@ specifiers:
convict: ^6.2.1
convict-format-with-validator: ^6.2.0
device-detector-js: ^3.0.0
fast-crc32c: ^2.0.0
fastify: ^3.23.1
fastify-caching: ^6.1.0
fastify-compress: ^3.6.1
@ -28,11 +30,13 @@ specifiers:
under-pressure: ^5.8.0
dependencies:
'@google-cloud/storage': 5.16.0
caniuse-db: 1.0.30001280
class-validator: 0.13.1
convict: 6.2.1
convict-format-with-validator: 6.2.0
device-detector-js: 3.0.0
fast-crc32c: 2.0.0
fastify: 3.23.1
fastify-caching: 6.1.0
fastify-compress: 3.6.1
@ -64,6 +68,73 @@ packages:
ajv: 6.12.6
dev: false
/@google-cloud/common/3.8.1:
resolution: {integrity: sha512-FOs3NFU6bDt5mXE7IFpwIeqzLwRZNu9lJYl+bHVNkwmxX/w4VyDZAiGjQHhpV1Ek+muNKlX8HPchxaIxNTuOhw==}
engines: {node: '>=10'}
dependencies:
'@google-cloud/projectify': 2.1.1
'@google-cloud/promisify': 2.0.4
arrify: 2.0.1
duplexify: 4.1.2
ent: 2.2.0
extend: 3.0.2
google-auth-library: 7.10.2
retry-request: 4.2.2
teeny-request: 7.1.3
transitivePeerDependencies:
- supports-color
dev: false
/@google-cloud/paginator/3.0.6:
resolution: {integrity: sha512-XCTm/GfQIlc1ZxpNtTSs/mnZxC2cePNhxU3X8EzHXKIJ2JFncmJj2Fcd2IP+gbmZaSZnY0juFxbUCkIeuu/2eQ==}
engines: {node: '>=10'}
dependencies:
arrify: 2.0.1
extend: 3.0.2
dev: false
/@google-cloud/projectify/2.1.1:
resolution: {integrity: sha512-+rssMZHnlh0twl122gXY4/aCrk0G1acBqkHFfYddtsqpYXGxA29nj9V5V9SfC+GyOG00l650f6lG9KL+EpFEWQ==}
engines: {node: '>=10'}
dev: false
/@google-cloud/promisify/2.0.4:
resolution: {integrity: sha512-j8yRSSqswWi1QqUGKVEKOG03Q7qOoZP6/h2zN2YO+F5h2+DHU0bSrHCK9Y7lo2DI9fBd8qGAw795sf+3Jva4yA==}
engines: {node: '>=10'}
dev: false
/@google-cloud/storage/5.16.0:
resolution: {integrity: sha512-I/1lA78v9c5EbOM/KfcYsjzA7YlHQmhpzHYdKLKdYC8X5fFaQrw5nK+FU8GbEwdPxmREAF2qPbN7Ccq+A/ndWA==}
engines: {node: '>=10'}
dependencies:
'@google-cloud/common': 3.8.1
'@google-cloud/paginator': 3.0.6
'@google-cloud/promisify': 2.0.4
arrify: 2.0.1
async-retry: 1.3.3
compressible: 2.0.18
date-and-time: 2.0.1
duplexify: 4.1.2
extend: 3.0.2
gcs-resumable-upload: 3.6.0
get-stream: 6.0.1
hash-stream-validation: 0.2.4
mime: 3.0.0
mime-types: 2.1.34
p-limit: 3.1.0
pumpify: 2.0.1
snakeize: 0.1.0
stream-events: 1.0.5
xdg-basedir: 4.0.0
transitivePeerDependencies:
- supports-color
dev: false
/@tootallnate/once/2.0.0:
resolution: {integrity: sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==}
engines: {node: '>= 10'}
dev: false
/@types/convict/6.1.1:
resolution: {integrity: sha512-R+JLaTvhsD06p4jyjUDtbd5xMtZTRE3c0iI+lrFWZogSVEjgTWPYwvJPVf+t92E+yrlbXa4X4Eg9ro6gPdUt4w==}
dependencies:
@ -116,6 +187,13 @@ packages:
dev: false
optional: true
/abort-controller/3.0.0:
resolution: {integrity: sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==}
engines: {node: '>=6.5'}
dependencies:
event-target-shim: 5.0.1
dev: false
/abstract-cache/1.0.1:
resolution: {integrity: sha512-EfUeMhRUbG5bVVbrSY/ogLlFXoyfMAPxMlSP7wrEqH53d+59r2foVy9a5KjmprLKFLOfPQCNKEfpBN/nQ76chw==}
dependencies:
@ -128,6 +206,15 @@ packages:
resolution: {integrity: sha512-2BjRTZxTPvheOvGbBslFSYOUkr+SjPtOnrLP33f+VIWLzezQpZcqVg7ja3L4dBXmzzgwT+a029jRx5PCi3JuiA==}
dev: false
/agent-base/6.0.2:
resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==}
engines: {node: '>= 6.0.0'}
dependencies:
debug: 4.3.2
transitivePeerDependencies:
- supports-color
dev: false
/ajv/6.12.6:
resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==}
dependencies:
@ -199,6 +286,17 @@ packages:
mri: 1.1.4
dev: false
/arrify/2.0.1:
resolution: {integrity: sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==}
engines: {node: '>=8'}
dev: false
/async-retry/1.3.3:
resolution: {integrity: sha512-wfr/jstw9xNi/0teMHrRW7dsz3Lt5ARhYNZ2ewpadnhaIp5mbALhOAP+EAdsC7t4Z6wqsDVv9+W6gm1Dk9mEyw==}
dependencies:
retry: 0.13.1
dev: false
/async/3.2.2:
resolution: {integrity: sha512-H0E+qZaDEfx/FY4t7iLRv1W2fFI6+pyCeTw1uN20AQPiwqwM6ojPxHxdLv4z8hi2DtnW9BOckSspLucW7pIE5g==}
dev: false
@ -236,6 +334,10 @@ packages:
engines: {node: '>=0.6'}
dev: false
/bignumber.js/9.0.1:
resolution: {integrity: sha512-IdZR9mh6ahOBv/hYGiXyVuyCetmGJhtYkqLBpTStdhEGjegpPlUawydyaF3pbIOFynJTpllEs+NP+CS9jKFLjA==}
dev: false
/binary-extensions/2.2.0:
resolution: {integrity: sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==}
engines: {node: '>=8'}
@ -248,6 +350,13 @@ packages:
chainsaw: 0.1.0
dev: false
/bindings/1.5.0:
resolution: {integrity: sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==}
dependencies:
file-uri-to-path: 1.0.0
dev: false
optional: true
/bl/4.1.0:
resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==}
dependencies:
@ -279,6 +388,10 @@ packages:
fill-range: 7.0.1
dev: true
/buffer-equal-constant-time/1.0.1:
resolution: {integrity: sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk=}
dev: false
/buffer-from/1.1.2:
resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==}
@ -406,9 +519,28 @@ packages:
resolution: {integrity: sha512-hUewv7oMjCp+wkBv5Rm0v87eJhq4woh5rSR+42YSQJKecCqgIqNkZ6lAlQms/BwHPJA5NKMRlpxPRv0n8HQW6g==}
dev: false
/compressible/2.0.18:
resolution: {integrity: sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==}
engines: {node: '>= 0.6'}
dependencies:
mime-db: 1.51.0
dev: false
/concat-map/0.0.1:
resolution: {integrity: sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=}
/configstore/5.0.1:
resolution: {integrity: sha512-aMKprgk5YhBNyH25hj8wGt2+D52Sw1DRRIzqBwLp2Ya9mFmY8KPvvtvmna8SxVR9JMZ4kzMD68N22vlaRpkeFA==}
engines: {node: '>=8'}
dependencies:
dot-prop: 5.3.0
graceful-fs: 4.2.8
make-dir: 3.1.0
unique-string: 2.0.0
write-file-atomic: 3.0.3
xdg-basedir: 4.0.0
dev: false
/console-control-strings/1.1.0:
resolution: {integrity: sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=}
dev: false
@ -441,6 +573,15 @@ packages:
resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==}
dev: true
/crypto-random-string/2.0.0:
resolution: {integrity: sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==}
engines: {node: '>=8'}
dev: false
/date-and-time/2.0.1:
resolution: {integrity: sha512-O7Xe5dLaqvY/aF/MFWArsAM1J4j7w1CSZlPCX9uHgmb+6SbkPd8Q4YOvfvH/cZGvFlJFfHOZKxQtmMUOoZhc/w==}
dev: false
/dateformat/4.6.3:
resolution: {integrity: sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA==}
dev: false
@ -501,6 +642,13 @@ packages:
engines: {node: '>=0.3.1'}
dev: true
/dot-prop/5.3.0:
resolution: {integrity: sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==}
engines: {node: '>=8'}
dependencies:
is-obj: 2.0.0
dev: false
/duplexer2/0.1.4:
resolution: {integrity: sha1-ixLauHjA1p4+eJEFFmKjL8a93ME=}
dependencies:
@ -531,6 +679,12 @@ packages:
xtend: 4.0.2
dev: true
/ecdsa-sig-formatter/1.0.11:
resolution: {integrity: sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==}
dependencies:
safe-buffer: 5.2.1
dev: false
/encoding-negotiator/2.0.1:
resolution: {integrity: sha512-GSK7qphNR4iPcejfAlZxKDoz3xMhnspwImK+Af5WhePS9jUpK/Oh7rUdyENWu+9rgDflOCTmAojBsgsvM8neAQ==}
engines: {node: '>=10.13.0'}
@ -542,6 +696,10 @@ packages:
once: 1.4.0
dev: false
/ent/2.2.0:
resolution: {integrity: sha1-6WQhkyWiHQX0RGai9obtbOX13R0=}
dev: false
/es-abstract/1.19.1:
resolution: {integrity: sha512-2vJ6tjA/UfqLm2MPs7jxVybLoB8i1t1Jd9R3kISld20sIxPcTbLuggQOUxeWeAvIUkduv/CfMjuh4WmiXr2v9w==}
engines: {node: '>= 0.4'}
@ -586,11 +744,26 @@ packages:
engines: {node: '>=0.8.0'}
dev: false
/event-target-shim/5.0.1:
resolution: {integrity: sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==}
engines: {node: '>=6'}
dev: false
/expand-template/2.0.3:
resolution: {integrity: sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==}
engines: {node: '>=6'}
dev: false
/extend/3.0.2:
resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==}
dev: false
/fast-crc32c/2.0.0:
resolution: {integrity: sha512-LIREwygxtxzHF11oLJ4xIVKu/ZWNgrj/QaGvaSD8ZggIsgCyCtSYevlrpWVqNau57ZwezV8K1HFBSjQ7FcRbTQ==}
optionalDependencies:
sse4_crc32: 6.0.1
dev: false
/fast-decode-uri-component/1.0.1:
resolution: {integrity: sha512-WKgKWg5eUxvRZGwW8FvfbaH7AXSh2cL+3j5fMGzUMCxWBJ3dV3a7Wz8y2f/uQ0e3B6WmodD3oS54jTQ9HVTIIg==}
dev: false
@ -622,6 +795,10 @@ packages:
resolution: {integrity: sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==}
dev: false
/fast-text-encoding/1.0.3:
resolution: {integrity: sha512-dtm4QZH9nZtcDt8qJiOH9fcQd1NAgi+K1O2DbE6GG1PPCK/BWfOH3idCTRQ4ImXRUOyopDEgDEnVEE7Y/2Wrig==}
dev: false
/fast-xml-parser/3.19.0:
resolution: {integrity: sha512-4pXwmBplsCPv8FOY1WRakF970TjNGnGnfbOnLqjlYvMiF1SR3yOHyxMR/YCXpPTOspNF5gwudqktIP4VsWkvBg==}
hasBin: true
@ -702,6 +879,11 @@ packages:
reusify: 1.0.4
dev: false
/file-uri-to-path/1.0.0:
resolution: {integrity: sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==}
dev: false
optional: true
/fill-range/7.0.1:
resolution: {integrity: sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==}
engines: {node: '>=8'}
@ -785,6 +967,46 @@ packages:
wide-align: 1.1.5
dev: false
/gaxios/4.3.2:
resolution: {integrity: sha512-T+ap6GM6UZ0c4E6yb1y/hy2UB6hTrqhglp3XfmU9qbLCGRYhLVV5aRPpC4EmoG8N8zOnkYCgoBz+ScvGAARY6Q==}
engines: {node: '>=10'}
dependencies:
abort-controller: 3.0.0
extend: 3.0.2
https-proxy-agent: 5.0.0
is-stream: 2.0.1
node-fetch: 2.6.6
transitivePeerDependencies:
- supports-color
dev: false
/gcp-metadata/4.3.1:
resolution: {integrity: sha512-x850LS5N7V1F3UcV7PoupzGsyD6iVwTVvsh3tbXfkctZnBnjW5yu5z1/3k3SehF7TyoTIe78rJs02GMMy+LF+A==}
engines: {node: '>=10'}
dependencies:
gaxios: 4.3.2
json-bigint: 1.0.0
transitivePeerDependencies:
- supports-color
dev: false
/gcs-resumable-upload/3.6.0:
resolution: {integrity: sha512-IyaNs4tx3Mp2UKn0CltRUiW/ZXYFlBNuK/V+ixs80chzVD+BJq3+8bfiganATFfCoMluAjokF9EswNJdVuOs8A==}
engines: {node: '>=10'}
hasBin: true
dependencies:
abort-controller: 3.0.0
async-retry: 1.3.3
configstore: 5.0.1
extend: 3.0.2
gaxios: 4.3.2
google-auth-library: 7.10.2
pumpify: 2.0.1
stream-events: 1.0.5
transitivePeerDependencies:
- supports-color
dev: false
/get-intrinsic/1.1.1:
resolution: {integrity: sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==}
dependencies:
@ -793,6 +1015,11 @@ packages:
has-symbols: 1.0.2
dev: false
/get-stream/6.0.1:
resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==}
engines: {node: '>=10'}
dev: false
/get-symbol-description/1.0.0:
resolution: {integrity: sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==}
engines: {node: '>= 0.4'}
@ -822,10 +1049,46 @@ packages:
once: 1.4.0
path-is-absolute: 1.0.1
/google-auth-library/7.10.2:
resolution: {integrity: sha512-M37o9Kxa/TLvOLgF71SXvLeVEP5sbSTmKl1zlIgl72SFy5PtsU3pOdu8G8MIHHpQ3/NZabDI8rQkA9DvQVKkPA==}
engines: {node: '>=10'}
dependencies:
arrify: 2.0.1
base64-js: 1.5.1
ecdsa-sig-formatter: 1.0.11
fast-text-encoding: 1.0.3
gaxios: 4.3.2
gcp-metadata: 4.3.1
gtoken: 5.3.1
jws: 4.0.0
lru-cache: 6.0.0
transitivePeerDependencies:
- supports-color
dev: false
/google-p12-pem/3.1.2:
resolution: {integrity: sha512-tjf3IQIt7tWCDsa0ofDQ1qqSCNzahXDxdAGJDbruWqu3eCg5CKLYKN+hi0s6lfvzYZ1GDVr+oDF9OOWlDSdf0A==}
engines: {node: '>=10'}
hasBin: true
dependencies:
node-forge: 0.10.0
dev: false
/graceful-fs/4.2.8:
resolution: {integrity: sha512-qkIilPUYcNhJpd33n0GBXTB1MMPp14TxEsEs0pTrsSVucApsYzW5V+Q8Qxhik6KU3evy+qkAAowTByymK0avdg==}
dev: false
/gtoken/5.3.1:
resolution: {integrity: sha512-yqOREjzLHcbzz1UrQoxhBtpk8KjrVhuqPE7od1K2uhyxG2BHjKZetlbLw/SPZak/QqTIQW+addS+EcjqQsZbwQ==}
engines: {node: '>=10'}
dependencies:
gaxios: 4.3.2
google-p12-pem: 3.1.2
jws: 4.0.0
transitivePeerDependencies:
- supports-color
dev: false
/has-bigints/1.0.1:
resolution: {integrity: sha512-LSBS2LjbNBTf6287JEbEzvJgftkF5qFkmCo9hDRpAzKhUOlJ+hx8dd4USs00SgsUNwc4617J9ki5YtEClM2ffA==}
dev: false
@ -857,10 +1120,40 @@ packages:
dependencies:
function-bind: 1.1.1
/hash-stream-validation/0.2.4:
resolution: {integrity: sha512-Gjzu0Xn7IagXVkSu9cSFuK1fqzwtLwFhNhVL8IFJijRNMgUttFbBSIAzKuSIrsFMO1+g1RlsoN49zPIbwPDMGQ==}
dev: false
/http-proxy-agent/5.0.0:
resolution: {integrity: sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==}
engines: {node: '>= 6'}
dependencies:
'@tootallnate/once': 2.0.0
agent-base: 6.0.2
debug: 4.3.2
transitivePeerDependencies:
- supports-color
dev: false
/https-proxy-agent/5.0.0:
resolution: {integrity: sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==}
engines: {node: '>= 6'}
dependencies:
agent-base: 6.0.2
debug: 4.3.2
transitivePeerDependencies:
- supports-color
dev: false
/ieee754/1.2.1:
resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==}
dev: false
/imurmurhash/0.1.4:
resolution: {integrity: sha1-khi5srkoojixPcT7a21XbyMUU+o=}
engines: {node: '>=0.8.19'}
dev: false
/inflight/1.0.6:
resolution: {integrity: sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=}
dependencies:
@ -999,6 +1292,11 @@ packages:
engines: {node: '>=0.12.0'}
dev: true
/is-obj/2.0.0:
resolution: {integrity: sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==}
engines: {node: '>=8'}
dev: false
/is-plain-obj/1.1.0:
resolution: {integrity: sha1-caUMhCnfync8kqOQpKA7OfzVHT4=}
engines: {node: '>=0.10.0'}
@ -1046,6 +1344,10 @@ packages:
has-tostringtag: 1.0.0
dev: false
/is-typedarray/1.0.0:
resolution: {integrity: sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=}
dev: false
/is-weakref/1.0.1:
resolution: {integrity: sha512-b2jKc2pQZjaeFYWEf7ScFj+Be1I+PXmlu572Q8coTXZ+LD/QQZ7ShPMst8h16riVgyXTQwUsFEl74mDvc/3MHQ==}
dependencies:
@ -1073,6 +1375,12 @@ packages:
argparse: 2.0.1
dev: false
/json-bigint/1.0.0:
resolution: {integrity: sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==}
dependencies:
bignumber.js: 9.0.1
dev: false
/json-schema-traverse/0.4.1:
resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==}
dev: false
@ -1085,6 +1393,21 @@ packages:
resolution: {integrity: sha1-GjhU4o0rvuqzHMfd9oPS3cVlJwg=}
dev: false
/jwa/2.0.0:
resolution: {integrity: sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==}
dependencies:
buffer-equal-constant-time: 1.0.1
ecdsa-sig-formatter: 1.0.11
safe-buffer: 5.2.1
dev: false
/jws/4.0.0:
resolution: {integrity: sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==}
dependencies:
jwa: 2.0.0
safe-buffer: 5.2.1
dev: false
/leven/2.1.0:
resolution: {integrity: sha1-wuep93IJTe6dNCAq6KzORoeHVYA=}
engines: {node: '>=0.10.0'}
@ -1126,6 +1449,13 @@ packages:
resolution: {integrity: sha1-tcg1G5Rky9dQM1p5ZQoOwOVhGN0=}
dev: false
/make-dir/3.1.0:
resolution: {integrity: sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==}
engines: {node: '>=8'}
dependencies:
semver: 6.3.0
dev: false
/make-error/1.3.6:
resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==}
dev: true
@ -1149,6 +1479,12 @@ packages:
mime-db: 1.51.0
dev: false
/mime/3.0.0:
resolution: {integrity: sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==}
engines: {node: '>=10.0.0'}
hasBin: true
dev: false
/mimic-response/3.1.0:
resolution: {integrity: sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==}
engines: {node: '>=10'}
@ -1229,10 +1565,27 @@ packages:
semver: 7.3.5
dev: false
/node-addon-api/1.7.2:
resolution: {integrity: sha512-ibPK3iA+vaY1eEjESkQkM0BbCqFOaZMiXRTtdB0u7b4djtY6JnsjvPdUHVMg6xQt3B8fpTTWHI9A+ADjM9frzg==}
dev: false
optional: true
/node-addon-api/4.2.0:
resolution: {integrity: sha512-eazsqzwG2lskuzBqCGPi7Ac2UgOoMz8JVOXVhTvvPDYhthvNpefx8jWD8Np7Gv+2Sz0FlPWZk0nJV0z598Wn8Q==}
dev: false
/node-fetch/2.6.6:
resolution: {integrity: sha512-Z8/6vRlTUChSdIgMa51jxQ4lrw/Jy5SOW10ObaA47/RElsAN2c5Pn8bTgFGWn/ibwzXTE8qwr1Yzx28vsecXEA==}
engines: {node: 4.x || >=6.0.0}
dependencies:
whatwg-url: 5.0.0
dev: false
/node-forge/0.10.0:
resolution: {integrity: sha512-PPmu8eEeG9saEUvI97fm4OYxXVB6bFvyNTyiUOBichBpFG8A1Ljw3bY62+5oOjDEMHRnd0Y7HQ+x7uzxOzC6JA==}
engines: {node: '>= 6.0.0'}
dev: false
/normalize-path/3.0.0:
resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==}
engines: {node: '>=0.10.0'}
@ -1286,6 +1639,13 @@ packages:
engines: {node: '>=8'}
dev: false
/p-limit/3.1.0:
resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==}
engines: {node: '>=10'}
dependencies:
yocto-queue: 0.1.0
dev: false
/path-is-absolute/1.0.1:
resolution: {integrity: sha1-F0uSaHNVNP+8es5r9TpanhtcX18=}
engines: {node: '>=0.10.0'}
@ -1475,6 +1835,21 @@ packages:
engines: {node: '>=4'}
dev: false
/retry-request/4.2.2:
resolution: {integrity: sha512-xA93uxUD/rogV7BV59agW/JHPGXeREMWiZc9jhcwY4YdZ7QOtC7qbomYg0n4wyk2lJhggjvKvhNX8wln/Aldhg==}
engines: {node: '>=8.10.0'}
dependencies:
debug: 4.3.2
extend: 3.0.2
transitivePeerDependencies:
- supports-color
dev: false
/retry/0.13.1:
resolution: {integrity: sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==}
engines: {node: '>= 4'}
dev: false
/reusify/1.0.4:
resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==}
engines: {iojs: '>=1.0.0', node: '>=0.10.0'}
@ -1516,6 +1891,11 @@ packages:
resolution: {integrity: sha512-TcZvGMMy9vodEFSse30lWinkj+JgOBvPn8wRItpQRSayhc+4ssDs335uklkfvQQJgL/WvmHLVj4Ycv2s7QCQMg==}
dev: false
/semver/6.3.0:
resolution: {integrity: sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==}
hasBin: true
dev: false
/semver/7.3.5:
resolution: {integrity: sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==}
engines: {node: '>=10'}
@ -1581,6 +1961,10 @@ packages:
is-arrayish: 0.3.2
dev: false
/snakeize/0.1.0:
resolution: {integrity: sha1-EMCI2LWOsHazIpu1oE4jLOEmQi0=}
dev: false
/sonic-boom/1.4.1:
resolution: {integrity: sha512-LRHh/A8tpW7ru89lrlkU4AszXt1dbwSjVWguGrmlxE7tawVmDBlI1PILMkXAxJTwqhgsEeTHzj36D5CmHgQmNg==}
dependencies:
@ -1611,6 +1995,22 @@ packages:
engines: {node: '>= 10.x'}
dev: false
/sse4_crc32/6.0.1:
resolution: {integrity: sha512-FUTYXpLroqytNKWIfHzlDWoy9E4tmBB/RklNMy6w3VJs+/XEYAHgbiylg4SS43iOk/9bM0BlJ2EDpFAGT66IoQ==}
engines: {node: '>=4'}
requiresBuild: true
dependencies:
bindings: 1.5.0
node-addon-api: 1.7.2
dev: false
optional: true
/stream-events/1.0.5:
resolution: {integrity: sha512-E1GUzBSgvct8Jsb3v2X15pjzN1tYebtbLaMg+eBOUOAxgbLoSbT2NS91ckc5lJD1KfLjId+jXJRgo0qnV5Nerg==}
dependencies:
stubs: 3.0.0
dev: false
/stream-shift/1.0.1:
resolution: {integrity: sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ==}
dev: false
@ -1681,6 +2081,10 @@ packages:
engines: {node: '>=8'}
dev: false
/stubs/3.0.0:
resolution: {integrity: sha1-6NK6H6nJBXAwPAMLaQD31fiavls=}
dev: false
/supports-color/5.5.0:
resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==}
engines: {node: '>=4'}
@ -1708,6 +2112,19 @@ packages:
readable-stream: 3.6.0
dev: false
/teeny-request/7.1.3:
resolution: {integrity: sha512-Ew3aoFzgQEatLA5OBIjdr1DWJUaC1xardG+qbPPo5k/y/3fMwXLxpjh5UB5dVfElktLaQbbMs80chkz53ByvSg==}
engines: {node: '>=10'}
dependencies:
http-proxy-agent: 5.0.0
https-proxy-agent: 5.0.0
node-fetch: 2.6.6
stream-events: 1.0.5
uuid: 8.3.2
transitivePeerDependencies:
- supports-color
dev: false
/through2/2.0.5:
resolution: {integrity: sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==}
dependencies:
@ -1734,6 +2151,10 @@ packages:
is-number: 7.0.0
dev: true
/tr46/0.0.3:
resolution: {integrity: sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=}
dev: false
/traverse/0.3.9:
resolution: {integrity: sha1-cXuPIgzAu3tE5AUUwisui7xw2Lk=}
dev: false
@ -1798,6 +2219,12 @@ packages:
safe-buffer: 5.2.1
dev: false
/typedarray-to-buffer/3.1.5:
resolution: {integrity: sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==}
dependencies:
is-typedarray: 1.0.0
dev: false
/typescript/4.4.4:
resolution: {integrity: sha512-DqGhF5IKoBl8WNf8C1gu8q0xZSInh9j1kJJMqT3a94w1JzVaBU4EXOSMrz9yDqMT0xt3selp83fuFMQ0uzv6qA==}
engines: {node: '>=4.2.0'}
@ -1828,6 +2255,13 @@ packages:
fastify-plugin: 3.0.0
dev: false
/unique-string/2.0.0:
resolution: {integrity: sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg==}
engines: {node: '>=8'}
dependencies:
crypto-random-string: 2.0.0
dev: false
/unzipper/0.10.11:
resolution: {integrity: sha512-+BrAq2oFqWod5IESRjL3S8baohbevGcVA+teAIOYWM3pDVdseogqbzhhvvmiyQrUNKFUnDMtELW3X8ykbyDCJw==}
dependencies:
@ -1864,6 +2298,11 @@ packages:
which-typed-array: 1.1.7
dev: false
/uuid/8.3.2:
resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==}
hasBin: true
dev: false
/validator/13.7.0:
resolution: {integrity: sha512-nYXQLCBkpJ8X6ltALua9dRrZDHVYxjJ1wgskNt1lH9fzGjs3tgojGSCBjmEPwkWS1y29+DrizMTW19Pr9uB2nw==}
engines: {node: '>= 0.10'}
@ -1882,6 +2321,17 @@ packages:
'@zxing/text-encoding': 0.9.0
dev: false
/webidl-conversions/3.0.1:
resolution: {integrity: sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE=}
dev: false
/whatwg-url/5.0.0:
resolution: {integrity: sha1-lmRU6HZUYuN2RNNib2dCzotwll0=}
dependencies:
tr46: 0.0.3
webidl-conversions: 3.0.1
dev: false
/which-boxed-primitive/1.0.2:
resolution: {integrity: sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==}
dependencies:
@ -1913,6 +2363,20 @@ packages:
/wrappy/1.0.2:
resolution: {integrity: sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=}
/write-file-atomic/3.0.3:
resolution: {integrity: sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==}
dependencies:
imurmurhash: 0.1.4
is-typedarray: 1.0.0
signal-exit: 3.0.5
typedarray-to-buffer: 3.1.5
dev: false
/xdg-basedir/4.0.0:
resolution: {integrity: sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q==}
engines: {node: '>=8'}
dev: false
/xml/1.0.1:
resolution: {integrity: sha1-eLpyAgApxbyHuKgaPPzXS0ovweU=}
dev: false
@ -1947,3 +2411,8 @@ packages:
resolution: {integrity: sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==}
engines: {node: '>=6'}
dev: true
/yocto-queue/0.1.0:
resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==}
engines: {node: '>=10'}
dev: false

View File

@ -1,5 +1,5 @@
import type { FastifyInstance } from 'fastify'
import convict from 'convict'
import type { FastifyInstance } from 'fastify'
import yaml from 'js-yaml'
convict.addFormat(require('convict-format-with-validator').ipaddress)
@ -8,7 +8,7 @@ export enum StorageType {
Local = 'local',
Minio = 'minio',
S3 = 's3',
// GCS = 'gcs',
GCS = 'gcs',
// Azure = 'azure',
// B2 = 'b2',
}
@ -173,9 +173,25 @@ const config = convict({
sensitive: true,
},
},
// GCS storage
gcs: {
bucket: {
doc: 'The GCS bucket to use',
format: String,
default: '',
env: 'GCS_BUCKET',
},
keyFilename: {
doc: 'The GCS key file to use',
format: String,
default: '',
env: 'GCS_KEY_FILENAME',
},
},
})
for (const file of ['morphus.yaml', 'morphus.yaml']) {
for (const file of ['morphus.yaml', 'morphus.yml']) {
try {
config.loadFile(file)
break

View File

@ -1,25 +1,26 @@
import {
IsDefined,
IsIn,
IsInt,
IsObject,
IsOptional,
IsPositive,
IsString,
IsUrl,
IsIn,
IsObject,
ValidateNested,
} from 'class-validator'
import { RouteHandlerMethod } from 'fastify'
import sharp, { FitEnum, FormatEnum } from 'sharp'
import { flatten, unflatten } from 'flat'
import ms from 'ms'
import sharp, { FitEnum, FormatEnum } from 'sharp'
import { App } from '..'
import { Config, URLClean } from '../config'
import { storage } from '../storage'
import { transform } from '../transform'
import { sha3, sortObjectByKeys, testForPrefixOrRegexp, validateSyncOrFail } from '../utils/utils'
import { Config, URLClean } from '../config'
import { supportsAvif, supportsWebP } from '../utils/caniuse'
import { ForbiddenError } from '../utils/errors'
import { sha3, sortObjectByKeys, testForPrefixOrRegexp, validateSyncOrFail } from '../utils/utils'
export enum ImageOperations {
resize,
@ -190,7 +191,8 @@ export const image: RouteHandlerMethod = async (request, reply) => {
let stream: NodeJS.ReadableStream
try {
stream = await storage.readStream(q.hash)
} catch (err) {
} catch {
App.log.debug(`Transforming`)
stream = await transform(q)
}
@ -199,7 +201,6 @@ export const image: RouteHandlerMethod = async (request, reply) => {
})
return stream
// .send(stream)
} catch (err) {
reply.code(400).send(err)
return

View File

@ -2,9 +2,9 @@ import fastify from 'fastify'
import { Config, init as initConfig } from './config'
import { init as initRoutes } from './controllers'
import { init as initStorage } from './storage'
import { init as initMiddleware } from './fastify/middleware'
import { init as initHooks } from './fastify/hooks'
import { init as initMiddleware } from './fastify/middleware'
import { init as initStorage } from './storage'
export const App = fastify({ logger: { prettyPrint: true, level: Config.logLevel } })

40
src/storage/gcs.ts Normal file
View File

@ -0,0 +1,40 @@
import { Bucket, Storage as GCStorage } from '@google-cloud/storage'
import { Storage } from '.'
export type GCSConfig = {
bucket: string
keyFilename: string
}
export class GCS implements Storage {
client: GCStorage
bucket: Bucket
constructor(private options: GCSConfig) {
this.client = new GCStorage(options)
this.bucket = this.client.bucket(options.bucket)
}
async init(): Promise<void> {
await this.client.bucket(this.options.bucket).getFiles({ maxResults: 1 })
}
async readStream(path: string): Promise<NodeJS.ReadableStream> {
if (!(await this.exists(path))) throw new Error(`File ${path} does not exist`)
return this.bucket.file(path).createReadStream()
}
async writeStream(path: string): Promise<NodeJS.WritableStream> {
return this.bucket.file(path).createWriteStream()
}
async exists(path: string): Promise<boolean> {
const [exists] = await this.bucket.file(path).exists()
return exists
}
async delete(path: string): Promise<void> {
await this.bucket.file(path).delete()
}
}

View File

@ -1,14 +1,13 @@
import { FastifyInstance } from 'fastify'
import { Config, StorageType } from '../config'
import { GCS } from './gcs'
import { Local } from './local'
import { Minio } from './minio'
export abstract class Storage {
abstract init(): Promise<void>
abstract read(path: string): Promise<Buffer>
abstract write(path: string, data: Buffer): Promise<void>
abstract readStream(path: string): Promise<NodeJS.ReadableStream>
abstract writeStream(path: string): Promise<NodeJS.WritableStream>
@ -26,12 +25,6 @@ export async function init(App: FastifyInstance) {
storage = new Local(Config.localAssets)
break
case StorageType.S3:
// storage = new S3({
// accessKeyId: Config.s3.accessKey,
// secretAccessKey: Config.s3.secretKey,
// bucket: Config.s3.bucket,
// region: Config.s3.region,
// })
storage = new Minio({
accessKey: Config.s3.accessKey,
secretKey: Config.s3.secretKey,
@ -49,6 +42,12 @@ export async function init(App: FastifyInstance) {
bucket: Config.minio.bucket,
})
break
case StorageType.GCS:
storage = new GCS({
bucket: Config.gcs.bucket,
keyFilename: Config.gcs.keyFilename,
})
break
default:
throw new Error(`Unknown storage type: ${Config.storage}`)
}
@ -56,6 +55,7 @@ export async function init(App: FastifyInstance) {
await storage.init()
App.log.debug(`Storage initialized: ${Config.storage}`)
} catch (e) {
App.log.error((e as Error).message)
App.log.error(`Storage initialization failed: ${Config.storage}`)
process.exit(1)
}

View File

@ -1,5 +1,5 @@
import { resolve, join } from 'path'
import fs from 'fs'
import { join, resolve } from 'path'
import { promisify } from 'util'
import { Storage } from './'
@ -13,30 +13,6 @@ export class Local implements Storage {
await promisify(fs.mkdir)(this.root, { recursive: true })
}
read(path: string): Promise<Buffer> {
const file = join(this.root, path)
return new Promise((resolve, reject) => {
fs.readFile(file, (err, data) => {
if (err) {
return reject(err)
}
resolve(data)
})
})
}
write(path: string, data: Buffer): Promise<void> {
const file = join(this.root, path)
return new Promise((resolve, reject) => {
fs.writeFile(file, data, (err) => {
if (err) {
return reject(err)
}
resolve()
})
})
}
exists(path: string): Promise<boolean> {
const file = join(this.root, path)
return new Promise((resolve, reject) => {

View File

@ -2,7 +2,6 @@ import { Client } from 'minio'
import { PassThrough } from 'stream'
import { Storage } from '.'
import { StreamUtils } from '../utils/utils'
export type MinioConfig = {
accessKey: string
@ -30,15 +29,6 @@ export class Minio implements Storage {
await this.client.bucketExists(this.options.bucket)
}
async read(path: string): Promise<Buffer> {
const stream = await this.client.getObject(this.options.bucket, path)
return StreamUtils.toBuffer(stream)
}
async write(path: string, data: Buffer): Promise<void> {
const stream = await StreamUtils.fromBuffer(data)
await this.client.putObject(this.options.bucket, path, stream)
}
async readStream(path: string): Promise<NodeJS.ReadableStream> {
const stream = await this.client.getObject(this.options.bucket, path)
return stream

View File

@ -1,8 +1,8 @@
import { get } from 'https'
import sharp from 'sharp'
import { PassThrough } from 'stream'
import { ComplexParameter, TransformQueryBase } from '../controllers/image'
import { ComplexParameter, TransformQueryBase } from '../controllers/image'
import { storage } from '../storage'
import { sha3, splitter } from '../utils/utils'

View File

@ -1,6 +1,6 @@
import DeviceDetector from 'device-detector-js'
import Avif from 'caniuse-db/features-json/avif.json'
import WebP from 'caniuse-db/features-json/webp.json'
import DeviceDetector from 'device-detector-js'
const detector = new DeviceDetector()

View File

@ -1,5 +1,5 @@
import { validateSync, ValidationError as VE, ValidatorOptions } from 'class-validator'
import { createHash } from 'crypto'
import { validateSync, ValidatorOptions, ValidationError as VE } from 'class-validator'
import { PassThrough, Readable } from 'stream'
export class ValidationError extends Error {