diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml index d5fa3ce7..4c9a5401 100644 --- a/.github/FUNDING.yml +++ b/.github/FUNDING.yml @@ -2,3 +2,4 @@ github: phalcode ko_fi: phalcode liberapay: phalcode +custom: "https://paypal.me/phalcode" diff --git a/.gitignore b/.gitignore index 20c6700b..e8174b07 100644 --- a/.gitignore +++ b/.gitignore @@ -1,13 +1,12 @@ + # compiled output /dist /node_modules -/images -/files -/logs -/db + +.local/*/* +!.gitkeep # Logs -logs *.log npm-debug.log* pnpm-debug.log* diff --git a/.local.env b/.local.env index 0c687dac..bf78de0f 100644 --- a/.local.env +++ b/.local.env @@ -1,7 +1,9 @@ +VOLUMES_SQLITEDB=./.local/db +VOLUMES_IMAGES=./.local/images +VOLUMES_FILES=./.local/files +VOLUMES_LOGS=./.local/logs + DB_SYSTEM=SQLITE TESTING_GOOGLE_API_DISABLED=true SERVER_ACCOUNT_ACTIVATION_DISABLED=true -SERVER_ADMIN_USERNAME=admin -VOLUMES_SQLITEDB=. -VOLUMES_FILES=. -VOLUMES_IMAGES=. \ No newline at end of file +SERVER_ADMIN_USERNAME=admin \ No newline at end of file diff --git a/.local/db/.gitkeep b/.local/db/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/.local/files/.gitkeep b/.local/files/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/.local/images/.gitkeep b/.local/images/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/.local/logs/.gitkeep b/.local/logs/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/.testing.env b/.testing.env index bf69fd4e..63a8a5e0 100644 --- a/.testing.env +++ b/.testing.env @@ -1,3 +1,8 @@ +VOLUMES_SQLITEDB=./.local/db +VOLUMES_IMAGES=./.local/images +VOLUMES_FILES=./.local/files +VOLUMES_LOGS=./.local/logs + SERVER_LOG_LEVEL=debug SERVER_ADMIN_USERNAME=admin SERVER_ADMIN_PASSWORD=12345678 diff --git a/CHANGELOG.md b/CHANGELOG.md index 3f050441..c0f134d1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,21 @@ # GameVault Backend Server Changelog +## 10.3.0 + +### Changes + +- Fixed a bug where not all frames of uploaded .gif images would be saved to the filesystem. +- Fixed deleted sub-entities being returned in /api/games/{id} +- You can now mark bot users for API access, that are hidden from the public user list, by prefixing usernames with `gvbot_`. +- The first registered user is now granted automatically granted admin privileges +- Enhanced logs within the RAWG GameMatcher +- Adjusted the Image Garbage Collector to be less aggressive, now exclusively deleting files with filenames starting with a valid UUID4 +- Huge Performance Improvement of /game/:id up to 1000% faster for slow-loading games. (they loaded up to 4s, now 40ms) + +### Thanks + +- @lordfransie + ## 10.2.0 ### Changes @@ -87,6 +103,8 @@ Recommended Gamevault App Version: `v1.8.0` - Fixed a bug where the `SERVER_MAX_DOWNLOAD_BANDWIDTH_IN_KBPS` config would not be used. +- Default `SERVER_MAX_DOWNLOAD_BANDWIDTH_IN_KBPS` is now unlimited. + - `SERVER_REGISTRATION_DISABLED` no longer blocks registration calls by administrators. [#221](https://github.com/Phalcode/gamevault-backend/issues/221) - Fixed initial folder generation not occurring. diff --git a/migrations.docker-compose.yml b/migrations.docker-compose.yml index d62b0797..dd1681cc 100644 --- a/migrations.docker-compose.yml +++ b/migrations.docker-compose.yml @@ -1,7 +1,7 @@ version: "3.8" services: db: - image: postgres:15 + image: postgres:16 restart: unless-stopped environment: POSTGRES_USER: gamevault diff --git a/package.json b/package.json index 4af01238..299cbd09 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "gamevault-backend", - "version": "10.2.0", + "version": "10.3.0", "description": "the self-hosted gaming platform for drm-free games", "author": "Alkan Alper, Schäfer Philip GbR / Phalcode", "private": true, @@ -48,17 +48,17 @@ "class-validator": "0.14.1", "compression": "1.7.4", "cookie-parser": "1.4.6", - "dotenv": "16.4.3", - "express": "4.18.2", - "fastify": "4.26.1", - "file-type-checker": "1.0.8", + "dotenv": "16.4.5", + "express": "4.18.3", + "fastify": "4.26.2", + "file-type-checker": "1.0.9", "helmet": "7.1.0", "lodash": "^4.17.21", "mime": "3.0.0", "morgan": "1.10.0", "nest-winston": "1.9.4", "nestjs-asyncapi": "1.3.0", - "nestjs-paginate": "8.6.0", + "nestjs-paginate": "8.6.2", "node-7z": "3.0.0", "passport": "0.7.0", "passport-http": "0.3.0", @@ -74,7 +74,7 @@ "typeorm": "0.3.20", "typeorm-naming-strategies": "4.1.0", "unidecode": "^0.1.8", - "winston": "3.11.0", + "winston": "3.12.0", "winston-console-format": "1.0.8", "winston-daily-rotate-file": "5.0.0" }, @@ -84,30 +84,30 @@ "@nestjs/testing": "^10.3.3", "@types/bcrypt": "5.0.2", "@types/compression": "1.7.5", - "@types/cookie-parser": "1.4.6", + "@types/cookie-parser": "1.4.7", "@types/express": "4.17.21", "@types/jest": "^29.5.12", "@types/mime": "3.0.4", "@types/morgan": "1.9.9", "@types/multer": "^1.4.11", - "@types/node": "20.11.17", + "@types/node": "20.11.26", "@types/node-7z": "2.1.8", "@types/passport-http": "0.3.11", "@types/string-similarity": "4.0.2", "@types/unidecode": "^0.1.3", - "@typescript-eslint/eslint-plugin": "7.0.1", - "@typescript-eslint/parser": "7.0.1", - "eslint": "8.56.0", + "@typescript-eslint/eslint-plugin": "7.2.0", + "@typescript-eslint/parser": "7.2.0", + "eslint": "8.57.0", "eslint-config-prettier": "9.1.0", "eslint-plugin-import": "2.29.1", "eslint-plugin-prettier": "5.1.3", "jest": "^29.7.0", "prettier": "3.2.5", "prettier-plugin-jsdoc": "1.3.0", - "simple-git-hooks": "2.9.0", + "simple-git-hooks": "2.10.0", "ts-jest": "^29.1.2", "ts-node": "10.9.2", - "typescript": "5.3.3" + "typescript": "5.4.2" }, "jest": { "moduleFileExtensions": [ diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5dc6a05e..7c76966f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -75,17 +75,17 @@ dependencies: specifier: 1.4.6 version: 1.4.6 dotenv: - specifier: 16.4.3 - version: 16.4.3 + specifier: 16.4.5 + version: 16.4.5 express: - specifier: 4.18.2 - version: 4.18.2 + specifier: 4.18.3 + version: 4.18.3 fastify: - specifier: 4.26.1 - version: 4.26.1 + specifier: 4.26.2 + version: 4.26.2 file-type-checker: - specifier: 1.0.8 - version: 1.0.8 + specifier: 1.0.9 + version: 1.0.9 helmet: specifier: 7.1.0 version: 7.1.0 @@ -100,13 +100,13 @@ dependencies: version: 1.10.0 nest-winston: specifier: 1.9.4 - version: 1.9.4(@nestjs/common@10.3.3)(winston@3.11.0) + version: 1.9.4(@nestjs/common@10.3.3)(winston@3.12.0) nestjs-asyncapi: specifier: 1.3.0 - version: 1.3.0(@nestjs/common@10.3.3)(@nestjs/core@10.3.3)(@nestjs/swagger@7.3.0)(@nestjs/websockets@10.3.3)(@types/node@20.11.17) + version: 1.3.0(@nestjs/common@10.3.3)(@nestjs/core@10.3.3)(@nestjs/swagger@7.3.0)(@nestjs/websockets@10.3.3)(@types/node@20.11.26) nestjs-paginate: - specifier: 8.6.0 - version: 8.6.0(@nestjs/common@10.3.3)(@nestjs/swagger@7.3.0)(express@4.18.2)(fastify@4.26.1)(typeorm@0.3.20) + specifier: 8.6.2 + version: 8.6.2(@nestjs/common@10.3.3)(@nestjs/swagger@7.3.0)(express@4.18.3)(fastify@4.26.2)(typeorm@0.3.20) node-7z: specifier: 3.0.0 version: 3.0.0 @@ -153,14 +153,14 @@ dependencies: specifier: ^0.1.8 version: 0.1.8 winston: - specifier: 3.11.0 - version: 3.11.0 + specifier: 3.12.0 + version: 3.12.0 winston-console-format: specifier: 1.0.8 version: 1.0.8 winston-daily-rotate-file: specifier: 5.0.0 - version: 5.0.0(winston@3.11.0) + version: 5.0.0(winston@3.12.0) devDependencies: '@nestjs/cli': @@ -168,7 +168,7 @@ devDependencies: version: 10.3.2 '@nestjs/schematics': specifier: 10.1.1 - version: 10.1.1(chokidar@3.6.0)(typescript@5.3.3) + version: 10.1.1(chokidar@3.6.0)(typescript@5.4.2) '@nestjs/testing': specifier: ^10.3.3 version: 10.3.3(@nestjs/common@10.3.3)(@nestjs/core@10.3.3)(@nestjs/platform-express@10.3.3) @@ -179,8 +179,8 @@ devDependencies: specifier: 1.7.5 version: 1.7.5 '@types/cookie-parser': - specifier: 1.4.6 - version: 1.4.6 + specifier: 1.4.7 + version: 1.4.7 '@types/express': specifier: 4.17.21 version: 4.17.21 @@ -197,8 +197,8 @@ devDependencies: specifier: ^1.4.11 version: 1.4.11 '@types/node': - specifier: 20.11.17 - version: 20.11.17 + specifier: 20.11.26 + version: 20.11.26 '@types/node-7z': specifier: 2.1.8 version: 2.1.8 @@ -212,26 +212,26 @@ devDependencies: specifier: ^0.1.3 version: 0.1.3 '@typescript-eslint/eslint-plugin': - specifier: 7.0.1 - version: 7.0.1(@typescript-eslint/parser@7.0.1)(eslint@8.56.0)(typescript@5.3.3) + specifier: 7.2.0 + version: 7.2.0(@typescript-eslint/parser@7.2.0)(eslint@8.57.0)(typescript@5.4.2) '@typescript-eslint/parser': - specifier: 7.0.1 - version: 7.0.1(eslint@8.56.0)(typescript@5.3.3) + specifier: 7.2.0 + version: 7.2.0(eslint@8.57.0)(typescript@5.4.2) eslint: - specifier: 8.56.0 - version: 8.56.0 + specifier: 8.57.0 + version: 8.57.0 eslint-config-prettier: specifier: 9.1.0 - version: 9.1.0(eslint@8.56.0) + version: 9.1.0(eslint@8.57.0) eslint-plugin-import: specifier: 2.29.1 - version: 2.29.1(@typescript-eslint/parser@7.0.1)(eslint@8.56.0) + version: 2.29.1(@typescript-eslint/parser@7.2.0)(eslint@8.57.0) eslint-plugin-prettier: specifier: 5.1.3 - version: 5.1.3(eslint-config-prettier@9.1.0)(eslint@8.56.0)(prettier@3.2.5) + version: 5.1.3(eslint-config-prettier@9.1.0)(eslint@8.57.0)(prettier@3.2.5) jest: specifier: ^29.7.0 - version: 29.7.0(@types/node@20.11.17)(ts-node@10.9.2) + version: 29.7.0(@types/node@20.11.26)(ts-node@10.9.2) prettier: specifier: 3.2.5 version: 3.2.5 @@ -239,17 +239,17 @@ devDependencies: specifier: 1.3.0 version: 1.3.0(prettier@3.2.5) simple-git-hooks: - specifier: 2.9.0 - version: 2.9.0 + specifier: 2.10.0 + version: 2.10.0 ts-jest: specifier: ^29.1.2 - version: 29.1.2(@babel/core@7.12.9)(jest@29.7.0)(typescript@5.3.3) + version: 29.1.2(@babel/core@7.12.9)(jest@29.7.0)(typescript@5.4.2) ts-node: specifier: 10.9.2 - version: 10.9.2(@types/node@20.11.17)(typescript@5.3.3) + version: 10.9.2(@types/node@20.11.26)(typescript@5.4.2) typescript: - specifier: 5.3.3 - version: 5.3.3 + specifier: 5.4.2 + version: 5.4.2 packages: @@ -356,7 +356,7 @@ packages: - supports-color dev: false - /@asyncapi/generator@1.13.1(@types/node@20.11.17): + /@asyncapi/generator@1.13.1(@types/node@20.11.26): resolution: {integrity: sha512-+6pQE9OlXue79AO0hMJwlzwH48vED/rPGU1NAQlDyuTvo8599JTd3zUPFDafExRn/arQltJPDE+Z5jtNBifXjw==} engines: {node: '>12.16', npm: '>6.13.7'} hasBin: true @@ -384,7 +384,7 @@ packages: semver: 7.5.4 simple-git: 3.20.0 source-map-support: 0.5.21 - ts-node: 10.9.2(@types/node@20.11.17)(typescript@4.9.5) + ts-node: 10.9.2(@types/node@20.11.26)(typescript@4.9.5) typescript: 4.9.5 transitivePeerDependencies: - '@swc/core' @@ -2107,13 +2107,13 @@ packages: dev: false optional: true - /@eslint-community/eslint-utils@4.4.0(eslint@8.56.0): + /@eslint-community/eslint-utils@4.4.0(eslint@8.57.0): resolution: {integrity: sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 dependencies: - eslint: 8.56.0 + eslint: 8.57.0 eslint-visitor-keys: 3.4.3 dev: true @@ -2139,8 +2139,8 @@ packages: - supports-color dev: true - /@eslint/js@8.56.0: - resolution: {integrity: sha512-gMsVel9D7f2HLkBma9VbtzZRehRogVRfbr++f06nL2vnCGCNlzOD+/MUov/F4p8myyAHspEhVobgjpX64q5m6A==} + /@eslint/js@8.57.0: + resolution: {integrity: sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dev: true @@ -2176,11 +2176,11 @@ packages: resolution: {integrity: sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw==} dev: false - /@humanwhocodes/config-array@0.11.13: - resolution: {integrity: sha512-JSBDMiDKSzQVngfRjOdFXgFfklaXI4K9nLF49Auh21lmBWRLIK3+xTErTWD4KU54pb6coM6ESE7Awz/FNU3zgQ==} + /@humanwhocodes/config-array@0.11.14: + resolution: {integrity: sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==} engines: {node: '>=10.10.0'} dependencies: - '@humanwhocodes/object-schema': 2.0.1 + '@humanwhocodes/object-schema': 2.0.2 debug: 4.3.4 minimatch: 3.1.2 transitivePeerDependencies: @@ -2192,8 +2192,8 @@ packages: engines: {node: '>=12.22'} dev: true - /@humanwhocodes/object-schema@2.0.1: - resolution: {integrity: sha512-dvuCeX5fC9dXgJn9t+X5atfmgQAzUOWqS1254Gh0m6i8wKd10ebXkfNKiRK+1GWi/yTvvLDHpoxLr0xxxeslWw==} + /@humanwhocodes/object-schema@2.0.2: + resolution: {integrity: sha512-6EwiSjwWYP7pTckG6I5eyFANjPhmPjUX9JRLUSfNPC7FX7zK9gyZAfUEaECL6ALTpGX5AjnBq3C9XmVWPitNpw==} dev: true /@img/sharp-darwin-arm64@0.33.2: @@ -2418,7 +2418,7 @@ packages: engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: '@jest/types': 29.6.3 - '@types/node': 20.11.17 + '@types/node': 20.11.26 chalk: 4.1.2 jest-message-util: 29.7.0 jest-util: 29.7.0 @@ -2439,14 +2439,14 @@ packages: '@jest/test-result': 29.7.0 '@jest/transform': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 20.11.17 + '@types/node': 20.11.26 ansi-escapes: 4.3.2 chalk: 4.1.2 ci-info: 3.9.0 exit: 0.1.2 graceful-fs: 4.2.10 jest-changed-files: 29.7.0 - jest-config: 29.7.0(@types/node@20.11.17)(ts-node@10.9.2) + jest-config: 29.7.0(@types/node@20.11.26)(ts-node@10.9.2) jest-haste-map: 29.7.0 jest-message-util: 29.7.0 jest-regex-util: 29.6.3 @@ -2474,7 +2474,7 @@ packages: dependencies: '@jest/fake-timers': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 20.11.17 + '@types/node': 20.11.26 jest-mock: 29.7.0 dev: true @@ -2501,7 +2501,7 @@ packages: dependencies: '@jest/types': 29.6.3 '@sinonjs/fake-timers': 10.3.0 - '@types/node': 20.11.17 + '@types/node': 20.11.26 jest-message-util: 29.7.0 jest-mock: 29.7.0 jest-util: 29.7.0 @@ -2534,7 +2534,7 @@ packages: '@jest/transform': 29.7.0 '@jest/types': 29.6.3 '@jridgewell/trace-mapping': 0.3.18 - '@types/node': 20.11.17 + '@types/node': 20.11.26 chalk: 4.1.2 collect-v8-coverage: 1.0.2 exit: 0.1.2 @@ -2622,7 +2622,7 @@ packages: '@jest/schemas': 29.6.3 '@types/istanbul-lib-coverage': 2.0.5 '@types/istanbul-reports': 3.0.3 - '@types/node': 20.11.17 + '@types/node': 20.11.26 '@types/yargs': 17.0.29 chalk: 4.1.2 dev: true @@ -2952,6 +2952,21 @@ packages: - chokidar dev: true + /@nestjs/schematics@10.1.1(chokidar@3.6.0)(typescript@5.4.2): + resolution: {integrity: sha512-o4lfCnEeIkfJhGBbLZxTuVWcGuqDCFwg5OrvpgRUBM7vI/vONvKKiB5riVNpO+JqXoH0I42NNeDb0m4V5RREig==} + peerDependencies: + typescript: '>=4.8.2' + dependencies: + '@angular-devkit/core': 17.1.2(chokidar@3.6.0) + '@angular-devkit/schematics': 17.1.2(chokidar@3.6.0) + comment-json: 4.2.3 + jsonc-parser: 3.2.1 + pluralize: 8.0.0 + typescript: 5.4.2 + transitivePeerDependencies: + - chokidar + dev: true + /@nestjs/swagger@7.3.0(@nestjs/common@10.3.3)(@nestjs/core@10.3.3)(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.1.14): resolution: {integrity: sha512-zLkfKZ+ioYsIZ3dfv7Bj8YHnZMNAGWFUmx2ZDuLp/fBE4P8BSjB7hldzDueFXsmwaPL90v7lgyd82P+s7KME1Q==} peerDependencies: @@ -3537,14 +3552,14 @@ packages: /@types/bcrypt@5.0.2: resolution: {integrity: sha512-6atioO8Y75fNcbmj0G7UjI9lXN2pQ/IGJ2FWT4a/btd0Lk9lQalHLKhkgKVZ3r+spnmWUKfbMi1GEe9wyHQfNQ==} dependencies: - '@types/node': 20.11.17 + '@types/node': 20.11.26 dev: true /@types/body-parser@1.19.2: resolution: {integrity: sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g==} dependencies: '@types/connect': 3.4.35 - '@types/node': 20.11.17 + '@types/node': 20.11.26 dev: true /@types/compression@1.7.5: @@ -3556,11 +3571,11 @@ packages: /@types/connect@3.4.35: resolution: {integrity: sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==} dependencies: - '@types/node': 20.11.17 + '@types/node': 20.11.26 dev: true - /@types/cookie-parser@1.4.6: - resolution: {integrity: sha512-KoooCrD56qlLskXPLGUiJxOMnv5l/8m7cQD2OxJ73NPMhuSz9PmvwRD6EpjDyKBVrdJDdQ4bQK7JFNHnNmax0w==} + /@types/cookie-parser@1.4.7: + resolution: {integrity: sha512-Fvuyi354Z+uayxzIGCwYTayFKocfV7TuDYZClCdIP9ckhvAu/ixDtCB6qx2TT0FKjPLf1f3P/J1rgf6lPs64mw==} dependencies: '@types/express': 4.17.21 dev: true @@ -3571,7 +3586,7 @@ packages: /@types/cors@2.8.15: resolution: {integrity: sha512-n91JxbNLD8eQIuXDIChAN1tCKNWCEgpceU9b7ZMbFA+P+Q4yIeh80jizFLEvolRPc1ES0VdwFlGv+kJTSirogw==} dependencies: - '@types/node': 20.11.17 + '@types/node': 20.11.26 /@types/debug@4.1.7: resolution: {integrity: sha512-9AonUzyTjXXhEOa0DnqpzZi6VHlqKMswga9EXjpXnnqxwLtdvPPtlO8evrI5D9S6asFRCQ6v+wpiUKbw+vKqyg==} @@ -3588,7 +3603,7 @@ packages: /@types/es-aggregate-error@1.0.4: resolution: {integrity: sha512-95tL6tLR8P3Utx4SxXUEc0e+k2B9VhtBozhgxKGpv30ylIuxGxf080d7mYZ08sH5UjpDv/Nd6F80tH1p+KuPIg==} dependencies: - '@types/node': 20.11.17 + '@types/node': 20.11.26 dev: false /@types/eslint-scope@3.7.4: @@ -3616,7 +3631,7 @@ packages: /@types/express-serve-static-core@4.17.33: resolution: {integrity: sha512-TPBqmR/HRYI3eC2E5hmiivIzv+bidAfXofM+sbonAGvyDhySGw9/PQZFt2BLOrjUUR++4eJVpx6KnLQK1Fk9tA==} dependencies: - '@types/node': 20.11.17 + '@types/node': 20.11.26 '@types/qs': 6.9.7 '@types/range-parser': 1.2.4 dev: true @@ -3633,7 +3648,7 @@ packages: /@types/graceful-fs@4.1.8: resolution: {integrity: sha512-NhRH7YzWq8WiNKVavKPBmtLYZHxNY19Hh+az28O/phfp68CF45pMFud+ZzJ8ewnxnC5smIdF3dqFeiSUQ5I+pw==} dependencies: - '@types/node': 20.11.17 + '@types/node': 20.11.26 dev: true /@types/istanbul-lib-coverage@2.0.5: @@ -3683,7 +3698,7 @@ packages: /@types/morgan@1.9.9: resolution: {integrity: sha512-iRYSDKVaC6FkGSpEVVIvrRGw0DfJMiQzIn3qr2G5B3C//AWkulhXgaBd7tS9/J79GWSYMTHGs7PfI5b3Y8m+RQ==} dependencies: - '@types/node': 20.11.17 + '@types/node': 20.11.26 dev: true /@types/ms@0.7.31: @@ -3699,11 +3714,11 @@ packages: /@types/node-7z@2.1.8: resolution: {integrity: sha512-VjiU7yEbczNc3EFKN4GJcAUqAMkn92P/92r6ARjMSXEdixunMD9lC79mTX81vKxTlNYXuvCJ7zvnzlDbFTt2Vw==} dependencies: - '@types/node': 20.11.17 + '@types/node': 20.11.26 dev: true - /@types/node@20.11.17: - resolution: {integrity: sha512-QmgQZGWu1Yw9TDyAP9ZzpFJKynYNeOvwMJmaxABfieQoVoiVOS6MN1WSpqpRcbeA5+RW82kraAVxCCJg+780Qw==} + /@types/node@20.11.26: + resolution: {integrity: sha512-YwOMmyhNnAWijOBQweOJnQPl068Oqd4K3OFbTc6AHJwzweUwwWG3GIFY74OKks2PJUDkQPeddOQES9mLn1CTEQ==} dependencies: undici-types: 5.26.5 @@ -3723,7 +3738,7 @@ packages: /@types/protocol-buffers-schema@3.4.2: resolution: {integrity: sha512-GaQpfsfFk4wGU3//d7uCGy9zy6B8QBEyWYd6+maZH+S6m861QrFvLWS5RyHj4UfIiON9tmqCz9C+oNpebDgGIw==} dependencies: - '@types/node': 20.11.17 + '@types/node': 20.11.26 dev: false /@types/qs@6.9.7: @@ -3742,7 +3757,7 @@ packages: resolution: {integrity: sha512-z5xyF6uh8CbjAu9760KDKsH2FcDxZ2tFCsA4HIMWE6IkiYMXfVoa+4f9KX+FN0ZLsaMw1WNG2ETLA6N+/YA+cg==} dependencies: '@types/mime': 3.0.4 - '@types/node': 20.11.17 + '@types/node': 20.11.26 dev: true /@types/stack-utils@2.0.2: @@ -3752,7 +3767,7 @@ packages: /@types/stream-throttle@0.1.4: resolution: {integrity: sha512-VxXIHGjVuK8tYsVm60rIQMmF/0xguCeen5OmK5S4Y6K64A+z+y4/GI6anRnVzaUZaJB9Ah9IfbDcO0o1gZCc/w==} dependencies: - '@types/node': 20.11.17 + '@types/node': 20.11.26 dev: false /@types/string-similarity@4.0.2: @@ -3792,12 +3807,12 @@ packages: resolution: {integrity: sha512-Km7XAtUIduROw7QPgvcft0lIupeG8a8rdKL8RiSyKvlE7dYY31fEn41HVuQsRFDuROA8tA4K2UVL+WdfFmErBA==} requiresBuild: true dependencies: - '@types/node': 20.11.17 + '@types/node': 20.11.26 dev: false optional: true - /@typescript-eslint/eslint-plugin@7.0.1(@typescript-eslint/parser@7.0.1)(eslint@8.56.0)(typescript@5.3.3): - resolution: {integrity: sha512-OLvgeBv3vXlnnJGIAgCLYKjgMEU+wBGj07MQ/nxAaON+3mLzX7mJbhRYrVGiVvFiXtwFlkcBa/TtmglHy0UbzQ==} + /@typescript-eslint/eslint-plugin@7.2.0(@typescript-eslint/parser@7.2.0)(eslint@8.57.0)(typescript@5.4.2): + resolution: {integrity: sha512-mdekAHOqS9UjlmyF/LSs6AIEvfceV749GFxoBAjwAv0nkevfKHWQFDMcBZWUiIC5ft6ePWivXoS36aKQ0Cy3sw==} engines: {node: ^16.0.0 || >=18.0.0} peerDependencies: '@typescript-eslint/parser': ^7.0.0 @@ -3808,25 +3823,25 @@ packages: optional: true dependencies: '@eslint-community/regexpp': 4.6.2 - '@typescript-eslint/parser': 7.0.1(eslint@8.56.0)(typescript@5.3.3) - '@typescript-eslint/scope-manager': 7.0.1 - '@typescript-eslint/type-utils': 7.0.1(eslint@8.56.0)(typescript@5.3.3) - '@typescript-eslint/utils': 7.0.1(eslint@8.56.0)(typescript@5.3.3) - '@typescript-eslint/visitor-keys': 7.0.1 + '@typescript-eslint/parser': 7.2.0(eslint@8.57.0)(typescript@5.4.2) + '@typescript-eslint/scope-manager': 7.2.0 + '@typescript-eslint/type-utils': 7.2.0(eslint@8.57.0)(typescript@5.4.2) + '@typescript-eslint/utils': 7.2.0(eslint@8.57.0)(typescript@5.4.2) + '@typescript-eslint/visitor-keys': 7.2.0 debug: 4.3.4 - eslint: 8.56.0 + eslint: 8.57.0 graphemer: 1.4.0 ignore: 5.2.4 natural-compare: 1.4.0 semver: 7.5.4 - ts-api-utils: 1.0.1(typescript@5.3.3) - typescript: 5.3.3 + ts-api-utils: 1.0.1(typescript@5.4.2) + typescript: 5.4.2 transitivePeerDependencies: - supports-color dev: true - /@typescript-eslint/parser@7.0.1(eslint@8.56.0)(typescript@5.3.3): - resolution: {integrity: sha512-8GcRRZNzaHxKzBPU3tKtFNing571/GwPBeCvmAUw0yBtfE2XVd0zFKJIMSWkHJcPQi0ekxjIts6L/rrZq5cxGQ==} + /@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.4.2): + resolution: {integrity: sha512-5FKsVcHTk6TafQKQbuIVkXq58Fnbkd2wDL4LB7AURN7RUOu1utVP+G8+6u3ZhEroW3DF6hyo3ZEXxgKgp4KeCg==} engines: {node: ^16.0.0 || >=18.0.0} peerDependencies: eslint: ^8.56.0 @@ -3835,27 +3850,27 @@ packages: typescript: optional: true dependencies: - '@typescript-eslint/scope-manager': 7.0.1 - '@typescript-eslint/types': 7.0.1 - '@typescript-eslint/typescript-estree': 7.0.1(typescript@5.3.3) - '@typescript-eslint/visitor-keys': 7.0.1 + '@typescript-eslint/scope-manager': 7.2.0 + '@typescript-eslint/types': 7.2.0 + '@typescript-eslint/typescript-estree': 7.2.0(typescript@5.4.2) + '@typescript-eslint/visitor-keys': 7.2.0 debug: 4.3.4 - eslint: 8.56.0 - typescript: 5.3.3 + eslint: 8.57.0 + typescript: 5.4.2 transitivePeerDependencies: - supports-color dev: true - /@typescript-eslint/scope-manager@7.0.1: - resolution: {integrity: sha512-v7/T7As10g3bcWOOPAcbnMDuvctHzCFYCG/8R4bK4iYzdFqsZTbXGln0cZNVcwQcwewsYU2BJLay8j0/4zOk4w==} + /@typescript-eslint/scope-manager@7.2.0: + resolution: {integrity: sha512-Qh976RbQM/fYtjx9hs4XkayYujB/aPwglw2choHmf3zBjB4qOywWSdt9+KLRdHubGcoSwBnXUH2sR3hkyaERRg==} engines: {node: ^16.0.0 || >=18.0.0} dependencies: - '@typescript-eslint/types': 7.0.1 - '@typescript-eslint/visitor-keys': 7.0.1 + '@typescript-eslint/types': 7.2.0 + '@typescript-eslint/visitor-keys': 7.2.0 dev: true - /@typescript-eslint/type-utils@7.0.1(eslint@8.56.0)(typescript@5.3.3): - resolution: {integrity: sha512-YtT9UcstTG5Yqy4xtLiClm1ZpM/pWVGFnkAa90UfdkkZsR1eP2mR/1jbHeYp8Ay1l1JHPyGvoUYR6o3On5Nhmw==} + /@typescript-eslint/type-utils@7.2.0(eslint@8.57.0)(typescript@5.4.2): + resolution: {integrity: sha512-xHi51adBHo9O9330J8GQYQwrKBqbIPJGZZVQTHHmy200hvkLZFWJIFtAG/7IYTWUyun6DE6w5InDReePJYJlJA==} engines: {node: ^16.0.0 || >=18.0.0} peerDependencies: eslint: ^8.56.0 @@ -3864,23 +3879,23 @@ packages: typescript: optional: true dependencies: - '@typescript-eslint/typescript-estree': 7.0.1(typescript@5.3.3) - '@typescript-eslint/utils': 7.0.1(eslint@8.56.0)(typescript@5.3.3) + '@typescript-eslint/typescript-estree': 7.2.0(typescript@5.4.2) + '@typescript-eslint/utils': 7.2.0(eslint@8.57.0)(typescript@5.4.2) debug: 4.3.4 - eslint: 8.56.0 - ts-api-utils: 1.0.1(typescript@5.3.3) - typescript: 5.3.3 + eslint: 8.57.0 + ts-api-utils: 1.0.1(typescript@5.4.2) + typescript: 5.4.2 transitivePeerDependencies: - supports-color dev: true - /@typescript-eslint/types@7.0.1: - resolution: {integrity: sha512-uJDfmirz4FHib6ENju/7cz9SdMSkeVvJDK3VcMFvf/hAShg8C74FW+06MaQPODHfDJp/z/zHfgawIJRjlu0RLg==} + /@typescript-eslint/types@7.2.0: + resolution: {integrity: sha512-XFtUHPI/abFhm4cbCDc5Ykc8npOKBSJePY3a3s+lwumt7XWJuzP5cZcfZ610MIPHjQjNsOLlYK8ASPaNG8UiyA==} engines: {node: ^16.0.0 || >=18.0.0} dev: true - /@typescript-eslint/typescript-estree@7.0.1(typescript@5.3.3): - resolution: {integrity: sha512-SO9wHb6ph0/FN5OJxH4MiPscGah5wjOd0RRpaLvuBv9g8565Fgu0uMySFEPqwPHiQU90yzJ2FjRYKGrAhS1xig==} + /@typescript-eslint/typescript-estree@7.2.0(typescript@5.4.2): + resolution: {integrity: sha512-cyxS5WQQCoBwSakpMrvMXuMDEbhOo9bNHHrNcEWis6XHx6KF518tkF1wBvKIn/tpq5ZpUYK7Bdklu8qY0MsFIA==} engines: {node: ^16.0.0 || >=18.0.0} peerDependencies: typescript: '*' @@ -3888,43 +3903,43 @@ packages: typescript: optional: true dependencies: - '@typescript-eslint/types': 7.0.1 - '@typescript-eslint/visitor-keys': 7.0.1 + '@typescript-eslint/types': 7.2.0 + '@typescript-eslint/visitor-keys': 7.2.0 debug: 4.3.4 globby: 11.1.0 is-glob: 4.0.3 minimatch: 9.0.3 semver: 7.5.4 - ts-api-utils: 1.0.1(typescript@5.3.3) - typescript: 5.3.3 + ts-api-utils: 1.0.1(typescript@5.4.2) + typescript: 5.4.2 transitivePeerDependencies: - supports-color dev: true - /@typescript-eslint/utils@7.0.1(eslint@8.56.0)(typescript@5.3.3): - resolution: {integrity: sha512-oe4his30JgPbnv+9Vef1h48jm0S6ft4mNwi9wj7bX10joGn07QRfqIqFHoMiajrtoU88cIhXf8ahwgrcbNLgPA==} + /@typescript-eslint/utils@7.2.0(eslint@8.57.0)(typescript@5.4.2): + resolution: {integrity: sha512-YfHpnMAGb1Eekpm3XRK8hcMwGLGsnT6L+7b2XyRv6ouDuJU1tZir1GS2i0+VXRatMwSI1/UfcyPe53ADkU+IuA==} engines: {node: ^16.0.0 || >=18.0.0} peerDependencies: eslint: ^8.56.0 dependencies: - '@eslint-community/eslint-utils': 4.4.0(eslint@8.56.0) + '@eslint-community/eslint-utils': 4.4.0(eslint@8.57.0) '@types/json-schema': 7.0.12 '@types/semver': 7.5.0 - '@typescript-eslint/scope-manager': 7.0.1 - '@typescript-eslint/types': 7.0.1 - '@typescript-eslint/typescript-estree': 7.0.1(typescript@5.3.3) - eslint: 8.56.0 + '@typescript-eslint/scope-manager': 7.2.0 + '@typescript-eslint/types': 7.2.0 + '@typescript-eslint/typescript-estree': 7.2.0(typescript@5.4.2) + eslint: 8.57.0 semver: 7.5.4 transitivePeerDependencies: - supports-color - typescript dev: true - /@typescript-eslint/visitor-keys@7.0.1: - resolution: {integrity: sha512-hwAgrOyk++RTXrP4KzCg7zB2U0xt7RUU0ZdMSCsqF3eKUwkdXUMyTb0qdCuji7VIbcpG62kKTU9M1J1c9UpFBw==} + /@typescript-eslint/visitor-keys@7.2.0: + resolution: {integrity: sha512-c6EIQRHhcpl6+tO8EMR+kjkkV+ugUNXOmeASA1rlzkd8EPIriavpWoiEz1HR/VLhbVIdhqnV6E7JZm00cBDx2A==} engines: {node: ^16.0.0 || >=18.0.0} dependencies: - '@typescript-eslint/types': 7.0.1 + '@typescript-eslint/types': 7.2.0 eslint-visitor-keys: 3.4.3 dev: true @@ -5289,7 +5304,7 @@ packages: typescript: 5.3.3 dev: true - /create-jest@29.7.0(@types/node@20.11.17)(ts-node@10.9.2): + /create-jest@29.7.0(@types/node@20.11.26)(ts-node@10.9.2): resolution: {integrity: sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} hasBin: true @@ -5298,7 +5313,7 @@ packages: chalk: 4.1.2 exit: 0.1.2 graceful-fs: 4.2.10 - jest-config: 29.7.0(@types/node@20.11.17)(ts-node@10.9.2) + jest-config: 29.7.0(@types/node@20.11.26)(ts-node@10.9.2) jest-util: 29.7.0 prompts: 2.4.2 transitivePeerDependencies: @@ -5594,8 +5609,8 @@ packages: is-obj: 2.0.0 dev: false - /dotenv@16.4.3: - resolution: {integrity: sha512-II98GFrje5psQTSve0E7bnwMFybNLqT8Vu8JIFWRjsE3khyNUm/loZupuy5DVzG2IXf/ysxvrixYOQnM6mjD3A==} + /dotenv@16.4.5: + resolution: {integrity: sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==} engines: {node: '>=12'} dev: false @@ -5662,7 +5677,7 @@ packages: dependencies: '@types/cookie': 0.4.1 '@types/cors': 2.8.15 - '@types/node': 20.11.17 + '@types/node': 20.11.26 accepts: 1.3.8 base64id: 2.0.0 cookie: 0.4.1 @@ -5824,13 +5839,13 @@ packages: source-map: 0.6.1 dev: false - /eslint-config-prettier@9.1.0(eslint@8.56.0): + /eslint-config-prettier@9.1.0(eslint@8.57.0): resolution: {integrity: sha512-NSWl5BFQWEPi1j4TjVNItzYV7dZXZ+wP6I6ZhrBGpChQhZRUaElihE9uRRkcbRnNb76UMKDF3r+WTmNcGPKsqw==} hasBin: true peerDependencies: eslint: '>=7.0.0' dependencies: - eslint: 8.56.0 + eslint: 8.57.0 dev: true /eslint-import-resolver-node@0.3.9: @@ -5843,7 +5858,7 @@ packages: - supports-color dev: true - /eslint-module-utils@2.8.0(@typescript-eslint/parser@7.0.1)(eslint-import-resolver-node@0.3.9)(eslint@8.56.0): + /eslint-module-utils@2.8.0(@typescript-eslint/parser@7.2.0)(eslint-import-resolver-node@0.3.9)(eslint@8.57.0): resolution: {integrity: sha512-aWajIYfsqCKRDgUfjEXNN/JlrzauMuSEy5sbd7WXbtW3EH6A6MpwEh42c7qD+MqQo9QMJ6fWLAeIJynx0g6OAw==} engines: {node: '>=4'} peerDependencies: @@ -5864,15 +5879,15 @@ packages: eslint-import-resolver-webpack: optional: true dependencies: - '@typescript-eslint/parser': 7.0.1(eslint@8.56.0)(typescript@5.3.3) + '@typescript-eslint/parser': 7.2.0(eslint@8.57.0)(typescript@5.4.2) debug: 3.2.7 - eslint: 8.56.0 + eslint: 8.57.0 eslint-import-resolver-node: 0.3.9 transitivePeerDependencies: - supports-color dev: true - /eslint-plugin-import@2.29.1(@typescript-eslint/parser@7.0.1)(eslint@8.56.0): + /eslint-plugin-import@2.29.1(@typescript-eslint/parser@7.2.0)(eslint@8.57.0): resolution: {integrity: sha512-BbPC0cuExzhiMo4Ff1BTVwHpjjv28C5R+btTOGaCRC7UEz801up0JadwkeSk5Ued6TG34uaczuVuH6qyy5YUxw==} engines: {node: '>=4'} peerDependencies: @@ -5882,16 +5897,16 @@ packages: '@typescript-eslint/parser': optional: true dependencies: - '@typescript-eslint/parser': 7.0.1(eslint@8.56.0)(typescript@5.3.3) + '@typescript-eslint/parser': 7.2.0(eslint@8.57.0)(typescript@5.4.2) array-includes: 3.1.7 array.prototype.findlastindex: 1.2.3 array.prototype.flat: 1.3.2 array.prototype.flatmap: 1.3.2 debug: 3.2.7 doctrine: 2.1.0 - eslint: 8.56.0 + eslint: 8.57.0 eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.8.0(@typescript-eslint/parser@7.0.1)(eslint-import-resolver-node@0.3.9)(eslint@8.56.0) + eslint-module-utils: 2.8.0(@typescript-eslint/parser@7.2.0)(eslint-import-resolver-node@0.3.9)(eslint@8.57.0) hasown: 2.0.0 is-core-module: 2.13.1 is-glob: 4.0.3 @@ -5907,7 +5922,7 @@ packages: - supports-color dev: true - /eslint-plugin-prettier@5.1.3(eslint-config-prettier@9.1.0)(eslint@8.56.0)(prettier@3.2.5): + /eslint-plugin-prettier@5.1.3(eslint-config-prettier@9.1.0)(eslint@8.57.0)(prettier@3.2.5): resolution: {integrity: sha512-C9GCVAs4Eq7ZC/XFQHITLiHJxQngdtraXaM+LoUFoFp/lHNl2Zn8f3WQbe9HvTBBQ9YnKFB0/2Ajdqwo5D1EAw==} engines: {node: ^14.18.0 || >=16.0.0} peerDependencies: @@ -5921,8 +5936,8 @@ packages: eslint-config-prettier: optional: true dependencies: - eslint: 8.56.0 - eslint-config-prettier: 9.1.0(eslint@8.56.0) + eslint: 8.57.0 + eslint-config-prettier: 9.1.0(eslint@8.57.0) prettier: 3.2.5 prettier-linter-helpers: 1.0.0 synckit: 0.8.6 @@ -5949,16 +5964,16 @@ packages: engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dev: true - /eslint@8.56.0: - resolution: {integrity: sha512-Go19xM6T9puCOWntie1/P997aXxFsOi37JIHRWI514Hc6ZnaHGKY9xFhrU65RT6CcBEzZoGG1e6Nq+DT04ZtZQ==} + /eslint@8.57.0: + resolution: {integrity: sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} hasBin: true dependencies: - '@eslint-community/eslint-utils': 4.4.0(eslint@8.56.0) + '@eslint-community/eslint-utils': 4.4.0(eslint@8.57.0) '@eslint-community/regexpp': 4.6.2 '@eslint/eslintrc': 2.1.4 - '@eslint/js': 8.56.0 - '@humanwhocodes/config-array': 0.11.13 + '@eslint/js': 8.57.0 + '@humanwhocodes/config-array': 0.11.14 '@humanwhocodes/module-importer': 1.0.1 '@nodelib/fs.walk': 1.2.8 '@ungap/structured-clone': 1.2.0 @@ -6147,6 +6162,45 @@ packages: transitivePeerDependencies: - supports-color + /express@4.18.3: + resolution: {integrity: sha512-6VyCijWQ+9O7WuVMTRBTl+cjNNIzD5cY5mQ1WM8r/LEkI2u8EYpOotESNwzNlyCn3g+dmjKYI6BmNneSr/FSRw==} + engines: {node: '>= 0.10.0'} + dependencies: + accepts: 1.3.8 + array-flatten: 1.1.1 + body-parser: 1.20.2 + content-disposition: 0.5.4 + content-type: 1.0.5 + cookie: 0.5.0 + cookie-signature: 1.0.6 + debug: 2.6.9 + depd: 2.0.0 + encodeurl: 1.0.2 + escape-html: 1.0.3 + etag: 1.8.1 + finalhandler: 1.2.0 + fresh: 0.5.2 + http-errors: 2.0.0 + merge-descriptors: 1.0.1 + methods: 1.1.2 + on-finished: 2.4.1 + parseurl: 1.3.3 + path-to-regexp: 0.1.7 + proxy-addr: 2.0.7 + qs: 6.11.0 + range-parser: 1.2.1 + safe-buffer: 5.2.1 + send: 0.18.0 + serve-static: 1.15.0 + setprototypeof: 1.2.0 + statuses: 2.0.1 + type-is: 1.6.18 + utils-merge: 1.0.1 + vary: 1.1.2 + transitivePeerDependencies: + - supports-color + dev: false + /extend@3.0.2: resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==} dev: false @@ -6253,8 +6307,8 @@ packages: resolution: {integrity: sha512-cIusKBIt/R/oI6z/1nyfe2FvGKVTohVRfvkOhvx0nCEW+xf5NoCXjAHcWp93uOUBchzYcsvPlrapAdX1uW+YGg==} dev: false - /fastify@4.26.1: - resolution: {integrity: sha512-tznA/G55dsxzM5XChBfcvVSloG2ejeeotfPPJSFaWmHyCDVGMpvf3nRNbsCb/JTBF9RmQFBfuujWt3Nphjesng==} + /fastify@4.26.2: + resolution: {integrity: sha512-90pjTuPGrfVKtdpLeLzND5nyC4woXZN5VadiNQCicj/iJU4viNHKhsAnb7jmv1vu2IzkLXyBiCzdWuzeXgQ5Ug==} dependencies: '@fastify/ajv-compiler': 3.5.0 '@fastify/error': 3.4.0 @@ -6332,8 +6386,8 @@ packages: moment: 2.29.4 dev: false - /file-type-checker@1.0.8: - resolution: {integrity: sha512-aNs0byeXzbY+LD+DVsadicRLd3hRoum0ZJhtO5P4gPuRJGXM4ZmZKzCHuxPcGEaUGZ2r0LKXkgydKROswu7NaQ==} + /file-type-checker@1.0.9: + resolution: {integrity: sha512-fF8UIya3QlakdS0O0TC0Au2LnttP0MDPYexlHv2wI7scaNP8DDhH/KgdhkA1pUMc43uTbC+6z2KLe16ntGNJ9A==} dev: false /file-uri-to-path@1.0.0: @@ -7360,7 +7414,7 @@ packages: '@jest/expect': 29.7.0 '@jest/test-result': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 20.11.17 + '@types/node': 20.11.26 chalk: 4.1.2 co: 4.6.0 dedent: 1.5.1 @@ -7381,7 +7435,7 @@ packages: - supports-color dev: true - /jest-cli@29.7.0(@types/node@20.11.17)(ts-node@10.9.2): + /jest-cli@29.7.0(@types/node@20.11.26)(ts-node@10.9.2): resolution: {integrity: sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} hasBin: true @@ -7395,10 +7449,10 @@ packages: '@jest/test-result': 29.7.0 '@jest/types': 29.6.3 chalk: 4.1.2 - create-jest: 29.7.0(@types/node@20.11.17)(ts-node@10.9.2) + create-jest: 29.7.0(@types/node@20.11.26)(ts-node@10.9.2) exit: 0.1.2 import-local: 3.1.0 - jest-config: 29.7.0(@types/node@20.11.17)(ts-node@10.9.2) + jest-config: 29.7.0(@types/node@20.11.26)(ts-node@10.9.2) jest-util: 29.7.0 jest-validate: 29.7.0 yargs: 17.6.2 @@ -7409,7 +7463,7 @@ packages: - ts-node dev: true - /jest-config@29.7.0(@types/node@20.11.17)(ts-node@10.9.2): + /jest-config@29.7.0(@types/node@20.11.26)(ts-node@10.9.2): resolution: {integrity: sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} peerDependencies: @@ -7424,7 +7478,7 @@ packages: '@babel/core': 7.23.9 '@jest/test-sequencer': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 20.11.17 + '@types/node': 20.11.26 babel-jest: 29.7.0(@babel/core@7.23.9) chalk: 4.1.2 ci-info: 3.9.0 @@ -7444,7 +7498,7 @@ packages: pretty-format: 29.7.0 slash: 3.0.0 strip-json-comments: 3.1.1 - ts-node: 10.9.2(@types/node@20.11.17)(typescript@5.3.3) + ts-node: 10.9.2(@types/node@20.11.26)(typescript@5.4.2) transitivePeerDependencies: - babel-plugin-macros - supports-color @@ -7485,7 +7539,7 @@ packages: '@jest/environment': 29.7.0 '@jest/fake-timers': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 20.11.17 + '@types/node': 20.11.26 jest-mock: 29.7.0 jest-util: 29.7.0 dev: true @@ -7501,7 +7555,7 @@ packages: dependencies: '@jest/types': 29.6.3 '@types/graceful-fs': 4.1.8 - '@types/node': 20.11.17 + '@types/node': 20.11.26 anymatch: 3.1.2 fb-watchman: 2.0.2 graceful-fs: 4.2.10 @@ -7552,7 +7606,7 @@ packages: engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: '@jest/types': 29.6.3 - '@types/node': 20.11.17 + '@types/node': 20.11.26 jest-util: 29.7.0 dev: true @@ -7607,7 +7661,7 @@ packages: '@jest/test-result': 29.7.0 '@jest/transform': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 20.11.17 + '@types/node': 20.11.26 chalk: 4.1.2 emittery: 0.13.1 graceful-fs: 4.2.10 @@ -7638,7 +7692,7 @@ packages: '@jest/test-result': 29.7.0 '@jest/transform': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 20.11.17 + '@types/node': 20.11.26 chalk: 4.1.2 cjs-module-lexer: 1.2.3 collect-v8-coverage: 1.0.2 @@ -7690,7 +7744,7 @@ packages: engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: '@jest/types': 29.6.3 - '@types/node': 20.11.17 + '@types/node': 20.11.26 chalk: 4.1.2 ci-info: 3.9.0 graceful-fs: 4.2.10 @@ -7715,7 +7769,7 @@ packages: dependencies: '@jest/test-result': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 20.11.17 + '@types/node': 20.11.26 ansi-escapes: 4.3.2 chalk: 4.1.2 emittery: 0.13.1 @@ -7727,7 +7781,7 @@ packages: resolution: {integrity: sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==} engines: {node: '>= 10.13.0'} dependencies: - '@types/node': 20.11.17 + '@types/node': 20.11.26 merge-stream: 2.0.0 supports-color: 8.1.1 dev: true @@ -7736,13 +7790,13 @@ packages: resolution: {integrity: sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: - '@types/node': 20.11.17 + '@types/node': 20.11.26 jest-util: 29.7.0 merge-stream: 2.0.0 supports-color: 8.1.1 dev: true - /jest@29.7.0(@types/node@20.11.17)(ts-node@10.9.2): + /jest@29.7.0(@types/node@20.11.26)(ts-node@10.9.2): resolution: {integrity: sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} hasBin: true @@ -7755,7 +7809,7 @@ packages: '@jest/core': 29.7.0(ts-node@10.9.2) '@jest/types': 29.6.3 import-local: 3.1.0 - jest-cli: 29.7.0(@types/node@20.11.17)(ts-node@10.9.2) + jest-cli: 29.7.0(@types/node@20.11.26)(ts-node@10.9.2) transitivePeerDependencies: - '@types/node' - babel-plugin-macros @@ -8687,7 +8741,7 @@ packages: resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==} dev: true - /nest-winston@1.9.4(@nestjs/common@10.3.3)(winston@3.11.0): + /nest-winston@1.9.4(@nestjs/common@10.3.3)(winston@3.12.0): resolution: {integrity: sha512-ilEmHuuYSAI6aMNR120fLBl42EdY13QI9WRggHdEizt9M7qZlmXJwpbemVWKW/tqRmULjSx/otKNQ3GMQbfoUQ==} peerDependencies: '@nestjs/common': ^5.0.0 || ^6.6.0 || ^7.0.0 || ^8.0.0 || ^9.0.0 || ^10.0.0 @@ -8695,10 +8749,10 @@ packages: dependencies: '@nestjs/common': 10.3.3(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.1.14)(rxjs@7.8.1) fast-safe-stringify: 2.1.1 - winston: 3.11.0 + winston: 3.12.0 dev: false - /nestjs-asyncapi@1.3.0(@nestjs/common@10.3.3)(@nestjs/core@10.3.3)(@nestjs/swagger@7.3.0)(@nestjs/websockets@10.3.3)(@types/node@20.11.17): + /nestjs-asyncapi@1.3.0(@nestjs/common@10.3.3)(@nestjs/core@10.3.3)(@nestjs/swagger@7.3.0)(@nestjs/websockets@10.3.3)(@types/node@20.11.26): resolution: {integrity: sha512-ZtCrPdGGPBIRupI+Zt/9EmYuO6gU9O7snLB81on1nLIoir0pPcWCzIjiq5FafVwiHrV4ideUWklWdqh/xZU5PQ==} peerDependencies: '@nestjs/common': ^10.0.0 || ^9.0.0 @@ -8709,7 +8763,7 @@ packages: '@nestjs/websockets': optional: true dependencies: - '@asyncapi/generator': 1.13.1(@types/node@20.11.17) + '@asyncapi/generator': 1.13.1(@types/node@20.11.26) '@asyncapi/html-template': 0.28.4 '@nestjs/common': 10.3.3(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.1.14)(rxjs@7.8.1) '@nestjs/core': 10.3.3(@nestjs/common@10.3.3)(@nestjs/platform-express@10.3.3)(@nestjs/websockets@10.3.3)(reflect-metadata@0.1.14)(rxjs@7.8.1) @@ -8730,19 +8784,19 @@ packages: - utf-8-validate dev: false - /nestjs-paginate@8.6.0(@nestjs/common@10.3.3)(@nestjs/swagger@7.3.0)(express@4.18.2)(fastify@4.26.1)(typeorm@0.3.20): - resolution: {integrity: sha512-RlkNM9OsjNergrERoN95pBevpXLC4rGOwsvNg/7IeiYZcct+upCkSf3rM9R/dzWwMJJg6pjVbGYg3CU2RkFgxw==} + /nestjs-paginate@8.6.2(@nestjs/common@10.3.3)(@nestjs/swagger@7.3.0)(express@4.18.3)(fastify@4.26.2)(typeorm@0.3.20): + resolution: {integrity: sha512-VJN8mdi/TKpB3wQFoGFhxqZtejr2XJDqc9v1zZQkLQilIrBWfpGxzwyOkoThm2QCBhsF65yH/32drwjSzOm7kA==} peerDependencies: - '@nestjs/common': ^10.2.10 - '@nestjs/swagger': ^7.1.16 + '@nestjs/common': ^10.3.3 + '@nestjs/swagger': ^7.3.0 express: ^4.18.2 - fastify: ^4.24.3 + fastify: ^4.26.1 typeorm: ^0.3.17 dependencies: '@nestjs/common': 10.3.3(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.1.14)(rxjs@7.8.1) '@nestjs/swagger': 7.3.0(@nestjs/common@10.3.3)(@nestjs/core@10.3.3)(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.1.14) - express: 4.18.2 - fastify: 4.26.1 + express: 4.18.3 + fastify: 4.26.2 lodash: 4.17.21 typeorm: 0.3.20(better-sqlite3@8.7.0)(pg@8.11.3)(ts-node@10.9.2) dev: false @@ -10276,8 +10330,8 @@ packages: simple-concat: 1.0.1 dev: false - /simple-git-hooks@2.9.0: - resolution: {integrity: sha512-waSQ5paUQtyGC0ZxlHmcMmD9I1rRXauikBwX31bX58l5vTOhCEcBC5Bi+ZDkPXTjDnZAF8TbCqKBY+9+sVPScw==} + /simple-git-hooks@2.10.0: + resolution: {integrity: sha512-TtCytVYfV77pILCkzVxpOSgYKHQyaO7fBI/iwG5bLGb0dIo/v/K1Y1IZ5DN40RQu6WNNJiN0gkuRvSYjxOhFog==} hasBin: true requiresBuild: true dev: true @@ -10833,16 +10887,16 @@ packages: utf8-byte-length: 1.0.4 dev: false - /ts-api-utils@1.0.1(typescript@5.3.3): + /ts-api-utils@1.0.1(typescript@5.4.2): resolution: {integrity: sha512-lC/RGlPmwdrIBFTX59wwNzqh7aR2otPNPR/5brHZm/XKFYKsfqxihXUe9pU3JI+3vGkl+vyCoNNnPhJn3aLK1A==} engines: {node: '>=16.13.0'} peerDependencies: typescript: '>=4.2.0' dependencies: - typescript: 5.3.3 + typescript: 5.4.2 dev: true - /ts-jest@29.1.2(@babel/core@7.12.9)(jest@29.7.0)(typescript@5.3.3): + /ts-jest@29.1.2(@babel/core@7.12.9)(jest@29.7.0)(typescript@5.4.2): resolution: {integrity: sha512-br6GJoH/WUX4pu7FbZXuWGKGNDuU7b8Uj77g/Sp7puZV6EXzuByl6JrECvm0MzVzSTkSHWTihsXt+5XYER5b+g==} engines: {node: ^16.10.0 || ^18.0.0 || >=20.0.0} hasBin: true @@ -10866,17 +10920,17 @@ packages: '@babel/core': 7.12.9 bs-logger: 0.2.6 fast-json-stable-stringify: 2.1.0 - jest: 29.7.0(@types/node@20.11.17)(ts-node@10.9.2) + jest: 29.7.0(@types/node@20.11.26)(ts-node@10.9.2) jest-util: 29.7.0 json5: 2.2.3 lodash.memoize: 4.1.2 make-error: 1.3.6 semver: 7.5.4 - typescript: 5.3.3 + typescript: 5.4.2 yargs-parser: 21.1.1 dev: true - /ts-node@10.9.2(@types/node@20.11.17)(typescript@4.9.5): + /ts-node@10.9.2(@types/node@20.11.26)(typescript@4.9.5): resolution: {integrity: sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==} hasBin: true peerDependencies: @@ -10895,7 +10949,7 @@ packages: '@tsconfig/node12': 1.0.11 '@tsconfig/node14': 1.0.3 '@tsconfig/node16': 1.0.3 - '@types/node': 20.11.17 + '@types/node': 20.11.26 acorn: 8.10.0 acorn-walk: 8.2.0 arg: 4.1.3 @@ -10907,7 +10961,7 @@ packages: yn: 3.1.1 dev: false - /ts-node@10.9.2(@types/node@20.11.17)(typescript@5.3.3): + /ts-node@10.9.2(@types/node@20.11.26)(typescript@5.4.2): resolution: {integrity: sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==} hasBin: true peerDependencies: @@ -10926,14 +10980,14 @@ packages: '@tsconfig/node12': 1.0.11 '@tsconfig/node14': 1.0.3 '@tsconfig/node16': 1.0.3 - '@types/node': 20.11.17 + '@types/node': 20.11.26 acorn: 8.10.0 acorn-walk: 8.2.0 arg: 4.1.3 create-require: 1.1.1 diff: 4.0.2 make-error: 1.3.6 - typescript: 5.3.3 + typescript: 5.4.2 v8-compile-cache-lib: 3.0.1 yn: 3.1.1 @@ -11130,13 +11184,13 @@ packages: cli-highlight: 2.1.11 dayjs: 1.11.10 debug: 4.3.4 - dotenv: 16.4.3 + dotenv: 16.4.5 glob: 10.3.10 mkdirp: 2.1.6 pg: 8.11.3 reflect-metadata: 0.2.1 sha.js: 2.4.11 - ts-node: 10.9.2(@types/node@20.11.17)(typescript@5.3.3) + ts-node: 10.9.2(@types/node@20.11.26)(typescript@5.4.2) tslib: 2.6.2 uuid: 9.0.1 yargs: 17.6.2 @@ -11154,6 +11208,12 @@ packages: resolution: {integrity: sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==} engines: {node: '>=14.17'} hasBin: true + dev: true + + /typescript@5.4.2: + resolution: {integrity: sha512-+2/g0Fds1ERlP6JsakQQDXjZdZMM+rqpamFZJEKh4kwTIn3iDkgKtby0CeNd5ATNZ4Ry1ax15TMx0W2V+miizQ==} + engines: {node: '>=14.17'} + hasBin: true /uc.micro@1.0.6: resolution: {integrity: sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==} @@ -11536,7 +11596,7 @@ packages: triple-beam: 1.3.0 dev: false - /winston-daily-rotate-file@5.0.0(winston@3.11.0): + /winston-daily-rotate-file@5.0.0(winston@3.12.0): resolution: {integrity: sha512-JDjiXXkM5qvwY06733vf09I2wnMXpZEhxEVOSPenZMii+g7pcDcTBt2MRugnoi8BwVSuCT2jfRXBUy+n1Zz/Yw==} engines: {node: '>=8'} peerDependencies: @@ -11545,19 +11605,10 @@ packages: file-stream-rotator: 0.6.1 object-hash: 3.0.0 triple-beam: 1.4.1 - winston: 3.11.0 + winston: 3.12.0 winston-transport: 4.7.0 dev: false - /winston-transport@4.5.0: - resolution: {integrity: sha512-YpZzcUzBedhlTAfJg6vJDlyEai/IFMIVcaEZZyl3UXIl4gmqRpU7AE89AHLkbzLUsv0NVmw7ts+iztqKxxPW1Q==} - engines: {node: '>= 6.4.0'} - dependencies: - logform: 2.4.2 - readable-stream: 3.6.0 - triple-beam: 1.3.0 - dev: false - /winston-transport@4.7.0: resolution: {integrity: sha512-ajBj65K5I7denzer2IYW6+2bNIVqLGDHqDw3Ow8Ohh+vdW+rv4MZ6eiDvHoKhfJFZ2auyN8byXieDDJ96ViONg==} engines: {node: '>= 12.0.0'} @@ -11567,8 +11618,8 @@ packages: triple-beam: 1.4.1 dev: false - /winston@3.11.0: - resolution: {integrity: sha512-L3yR6/MzZAOl0DsysUXHVjOwv8mKZ71TrA/41EIduGpOOV5LQVodqN+QdQ6BS6PJ/RdIshZhq84P/fStEZkk7g==} + /winston@3.12.0: + resolution: {integrity: sha512-OwbxKaOlESDi01mC9rkM0dQqQt2I8DAUMRLZ/HpbwvDXm85IryEHgoogy5fziQy38PntgZsLlhAYHz//UPHZ5w==} engines: {node: '>= 12.0.0'} dependencies: '@colors/colors': 1.6.0 @@ -11580,8 +11631,8 @@ packages: readable-stream: 3.6.0 safe-stable-stringify: 2.3.1 stack-trace: 0.0.10 - triple-beam: 1.3.0 - winston-transport: 4.5.0 + triple-beam: 1.4.1 + winston-transport: 4.7.0 dev: false /wrap-ansi@6.2.0: diff --git a/src/decorators/donothing.decorator.ts b/src/decorators/donothing.decorator.ts deleted file mode 100644 index bce84a57..00000000 --- a/src/decorators/donothing.decorator.ts +++ /dev/null @@ -1,3 +0,0 @@ -export const DoNothing = () => { - return; -}; diff --git a/src/modules/pagination/minimum-role.decorator.ts b/src/decorators/minimum-role.decorator.ts similarity index 68% rename from src/modules/pagination/minimum-role.decorator.ts rename to src/decorators/minimum-role.decorator.ts index 22391037..c8ee14b7 100644 --- a/src/modules/pagination/minimum-role.decorator.ts +++ b/src/decorators/minimum-role.decorator.ts @@ -1,4 +1,4 @@ import { SetMetadata } from "@nestjs/common"; -import { Role } from "../users/models/role.enum"; +import { Role } from "../modules/users/models/role.enum"; export const MinimumRole = (role: Role) => SetMetadata("minimumRole", role); diff --git a/src/modules/pagination/pagination.decorator.ts b/src/decorators/pagination.decorator.ts similarity index 100% rename from src/modules/pagination/pagination.decorator.ts rename to src/decorators/pagination.decorator.ts diff --git a/src/globals.ts b/src/globals.ts index 482f57fa..a27237fd 100644 --- a/src/globals.ts +++ b/src/globals.ts @@ -66,7 +66,8 @@ export default { export interface FindOptions { /** - * Indicates whether deleted (sub)entities should be loaded. + * Indicates whether deleted (sub)entities should be loaded. Subentities may + * be deleted by app-logic afterwards. * * @default false */ diff --git a/src/modules/admin/admin.controller.ts b/src/modules/admin/admin.controller.ts index 0108b734..cdd9b354 100644 --- a/src/modules/admin/admin.controller.ts +++ b/src/modules/admin/admin.controller.ts @@ -15,7 +15,7 @@ import { ApiTags, } from "@nestjs/swagger"; import { Health } from "../health/models/health.model"; -import { MinimumRole } from "../pagination/minimum-role.decorator"; +import { MinimumRole } from "../../decorators/minimum-role.decorator"; import { Role } from "../users/models/role.enum"; import { DatabaseService } from "../database/database.service"; import { FileInterceptor } from "@nestjs/platform-express"; diff --git a/src/modules/database/database.controller.ts b/src/modules/database/database.controller.ts index b55de5ec..313693b0 100644 --- a/src/modules/database/database.controller.ts +++ b/src/modules/database/database.controller.ts @@ -12,7 +12,7 @@ import { ApiBasicAuth, ApiHeader, } from "@nestjs/swagger"; -import { MinimumRole } from "../pagination/minimum-role.decorator"; +import { MinimumRole } from "../../decorators/minimum-role.decorator"; import { Role } from "../users/models/role.enum"; import { DatabaseService } from "./database.service"; import { FileInterceptor } from "@nestjs/platform-express"; diff --git a/src/modules/database/migrations/postgres/1710514948442-images-one-to-one.ts b/src/modules/database/migrations/postgres/1710514948442-images-one-to-one.ts new file mode 100644 index 00000000..99f176cb --- /dev/null +++ b/src/modules/database/migrations/postgres/1710514948442-images-one-to-one.ts @@ -0,0 +1,95 @@ +import { MigrationInterface, QueryRunner } from "typeorm"; + +export class ImagesOneToOne1710514948442 implements MigrationInterface { + name = "ImagesOneToOne1710514948442"; + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + ALTER TABLE "game" DROP CONSTRAINT "FK_52b4bb990c5a5fe76c6d675c002" + `); + await queryRunner.query(` + ALTER TABLE "game" DROP CONSTRAINT "FK_0e88ada3f37f7cabfb6d59ed0d0" + `); + await queryRunner.query(` + ALTER TABLE "game" + ADD CONSTRAINT "UQ_52b4bb990c5a5fe76c6d675c002" UNIQUE ("box_image_id") + `); + await queryRunner.query(` + ALTER TABLE "game" + ADD CONSTRAINT "UQ_0e88ada3f37f7cabfb6d59ed0d0" UNIQUE ("background_image_id") + `); + await queryRunner.query(` + ALTER TABLE "gamevault_user" DROP CONSTRAINT "FK_c1779b9b22212754248aa404bad" + `); + await queryRunner.query(` + ALTER TABLE "gamevault_user" DROP CONSTRAINT "FK_4b83e27ed50c1e183a69fceef68" + `); + await queryRunner.query(` + ALTER TABLE "gamevault_user" + ADD CONSTRAINT "UQ_c1779b9b22212754248aa404bad" UNIQUE ("profile_picture_id") + `); + await queryRunner.query(` + ALTER TABLE "gamevault_user" + ADD CONSTRAINT "UQ_4b83e27ed50c1e183a69fceef68" UNIQUE ("background_image_id") + `); + await queryRunner.query(` + ALTER TABLE "game" + ADD CONSTRAINT "FK_52b4bb990c5a5fe76c6d675c002" FOREIGN KEY ("box_image_id") REFERENCES "image"("id") ON DELETE CASCADE ON UPDATE NO ACTION + `); + await queryRunner.query(` + ALTER TABLE "game" + ADD CONSTRAINT "FK_0e88ada3f37f7cabfb6d59ed0d0" FOREIGN KEY ("background_image_id") REFERENCES "image"("id") ON DELETE CASCADE ON UPDATE NO ACTION + `); + await queryRunner.query(` + ALTER TABLE "gamevault_user" + ADD CONSTRAINT "FK_c1779b9b22212754248aa404bad" FOREIGN KEY ("profile_picture_id") REFERENCES "image"("id") ON DELETE CASCADE ON UPDATE NO ACTION + `); + await queryRunner.query(` + ALTER TABLE "gamevault_user" + ADD CONSTRAINT "FK_4b83e27ed50c1e183a69fceef68" FOREIGN KEY ("background_image_id") REFERENCES "image"("id") ON DELETE CASCADE ON UPDATE NO ACTION + `); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + ALTER TABLE "gamevault_user" DROP CONSTRAINT "FK_4b83e27ed50c1e183a69fceef68" + `); + await queryRunner.query(` + ALTER TABLE "gamevault_user" DROP CONSTRAINT "FK_c1779b9b22212754248aa404bad" + `); + await queryRunner.query(` + ALTER TABLE "game" DROP CONSTRAINT "FK_0e88ada3f37f7cabfb6d59ed0d0" + `); + await queryRunner.query(` + ALTER TABLE "game" DROP CONSTRAINT "FK_52b4bb990c5a5fe76c6d675c002" + `); + await queryRunner.query(` + ALTER TABLE "gamevault_user" DROP CONSTRAINT "UQ_4b83e27ed50c1e183a69fceef68" + `); + await queryRunner.query(` + ALTER TABLE "gamevault_user" DROP CONSTRAINT "UQ_c1779b9b22212754248aa404bad" + `); + await queryRunner.query(` + ALTER TABLE "gamevault_user" + ADD CONSTRAINT "FK_4b83e27ed50c1e183a69fceef68" FOREIGN KEY ("background_image_id") REFERENCES "image"("id") ON DELETE CASCADE ON UPDATE NO ACTION + `); + await queryRunner.query(` + ALTER TABLE "gamevault_user" + ADD CONSTRAINT "FK_c1779b9b22212754248aa404bad" FOREIGN KEY ("profile_picture_id") REFERENCES "image"("id") ON DELETE CASCADE ON UPDATE NO ACTION + `); + await queryRunner.query(` + ALTER TABLE "game" DROP CONSTRAINT "UQ_0e88ada3f37f7cabfb6d59ed0d0" + `); + await queryRunner.query(` + ALTER TABLE "game" DROP CONSTRAINT "UQ_52b4bb990c5a5fe76c6d675c002" + `); + await queryRunner.query(` + ALTER TABLE "game" + ADD CONSTRAINT "FK_0e88ada3f37f7cabfb6d59ed0d0" FOREIGN KEY ("background_image_id") REFERENCES "image"("id") ON DELETE CASCADE ON UPDATE NO ACTION + `); + await queryRunner.query(` + ALTER TABLE "game" + ADD CONSTRAINT "FK_52b4bb990c5a5fe76c6d675c002" FOREIGN KEY ("box_image_id") REFERENCES "image"("id") ON DELETE CASCADE ON UPDATE NO ACTION + `); + } +} diff --git a/src/modules/database/migrations/sqlite.migration-config.ts b/src/modules/database/migrations/sqlite.migration-config.ts index 31efe4d8..97ad4166 100644 --- a/src/modules/database/migrations/sqlite.migration-config.ts +++ b/src/modules/database/migrations/sqlite.migration-config.ts @@ -4,7 +4,7 @@ import { SnakeNamingStrategy } from "typeorm-naming-strategies"; export const dataSource = new DataSource({ name: "sqlite", type: "better-sqlite3", - database: "database.sqlite", + database: "../../../.local/db/database.sqlite", entities: ["dist/**/*.entity.js"], migrations: ["dist/src/modules/database/migrations/sqlite/*.js"], namingStrategy: new SnakeNamingStrategy(), diff --git a/src/modules/database/migrations/sqlite/1710515113986-images-one-to-one.ts b/src/modules/database/migrations/sqlite/1710515113986-images-one-to-one.ts new file mode 100644 index 00000000..b5d7cef5 --- /dev/null +++ b/src/modules/database/migrations/sqlite/1710515113986-images-one-to-one.ts @@ -0,0 +1,1019 @@ +import { MigrationInterface, QueryRunner } from "typeorm"; + +export class ImagesOneToOne1710515113986 implements MigrationInterface { + name = "ImagesOneToOne1710515113986"; + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + DROP INDEX "IDX_352a30652cd352f552fef73dec" + `); + await queryRunner.query(` + DROP INDEX "IDX_0152ed47a9e8963b5aaceb51e7" + `); + await queryRunner.query(` + CREATE TABLE "temporary_game" ( + "id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, + "created_at" datetime NOT NULL DEFAULT (datetime('now')), + "updated_at" datetime NOT NULL DEFAULT (datetime('now')), + "deleted_at" datetime, + "entity_version" integer NOT NULL, + "rawg_id" integer, + "title" varchar NOT NULL, + "rawg_title" varchar, + "version" varchar, + "release_date" datetime, + "rawg_release_date" datetime, + "cache_date" datetime, + "file_path" varchar NOT NULL, + "size" bigint NOT NULL DEFAULT (0), + "description" varchar, + "website_url" varchar, + "metacritic_rating" integer, + "average_playtime" integer, + "early_access" boolean NOT NULL, + "box_image_id" integer, + "background_image_id" integer, + "type" varchar NOT NULL DEFAULT ('UNDETECTABLE'), + CONSTRAINT "UQ_7770cb331bdc54951bb9046fa9d" UNIQUE ("file_path") + ) + `); + await queryRunner.query(` + INSERT INTO "temporary_game"( + "id", + "created_at", + "updated_at", + "deleted_at", + "entity_version", + "rawg_id", + "title", + "rawg_title", + "version", + "release_date", + "rawg_release_date", + "cache_date", + "file_path", + "size", + "description", + "website_url", + "metacritic_rating", + "average_playtime", + "early_access", + "box_image_id", + "background_image_id", + "type" + ) + SELECT "id", + "created_at", + "updated_at", + "deleted_at", + "entity_version", + "rawg_id", + "title", + "rawg_title", + "version", + "release_date", + "rawg_release_date", + "cache_date", + "file_path", + "size", + "description", + "website_url", + "metacritic_rating", + "average_playtime", + "early_access", + "box_image_id", + "background_image_id", + "type" + FROM "game" + `); + await queryRunner.query(` + DROP TABLE "game" + `); + await queryRunner.query(` + ALTER TABLE "temporary_game" + RENAME TO "game" + `); + await queryRunner.query(` + CREATE INDEX "IDX_352a30652cd352f552fef73dec" ON "game" ("id") + `); + await queryRunner.query(` + CREATE INDEX "IDX_0152ed47a9e8963b5aaceb51e7" ON "game" ("title") + `); + await queryRunner.query(` + DROP INDEX "IDX_352a30652cd352f552fef73dec" + `); + await queryRunner.query(` + DROP INDEX "IDX_0152ed47a9e8963b5aaceb51e7" + `); + await queryRunner.query(` + CREATE TABLE "temporary_game" ( + "id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, + "created_at" datetime NOT NULL DEFAULT (datetime('now')), + "updated_at" datetime NOT NULL DEFAULT (datetime('now')), + "deleted_at" datetime, + "entity_version" integer NOT NULL, + "rawg_id" integer, + "title" varchar NOT NULL, + "rawg_title" varchar, + "version" varchar, + "release_date" datetime, + "rawg_release_date" datetime, + "cache_date" datetime, + "file_path" varchar NOT NULL, + "size" bigint NOT NULL DEFAULT (0), + "description" varchar, + "website_url" varchar, + "metacritic_rating" integer, + "average_playtime" integer, + "early_access" boolean NOT NULL, + "box_image_id" integer, + "background_image_id" integer, + "type" varchar NOT NULL DEFAULT ('UNDETECTABLE'), + CONSTRAINT "UQ_7770cb331bdc54951bb9046fa9d" UNIQUE ("file_path"), + CONSTRAINT "UQ_d155b40abc872a8f5994b2db16b" UNIQUE ("box_image_id"), + CONSTRAINT "UQ_ae67c9253e4c3d5ebedeb723b75" UNIQUE ("background_image_id") + ) + `); + await queryRunner.query(` + INSERT INTO "temporary_game"( + "id", + "created_at", + "updated_at", + "deleted_at", + "entity_version", + "rawg_id", + "title", + "rawg_title", + "version", + "release_date", + "rawg_release_date", + "cache_date", + "file_path", + "size", + "description", + "website_url", + "metacritic_rating", + "average_playtime", + "early_access", + "box_image_id", + "background_image_id", + "type" + ) + SELECT "id", + "created_at", + "updated_at", + "deleted_at", + "entity_version", + "rawg_id", + "title", + "rawg_title", + "version", + "release_date", + "rawg_release_date", + "cache_date", + "file_path", + "size", + "description", + "website_url", + "metacritic_rating", + "average_playtime", + "early_access", + "box_image_id", + "background_image_id", + "type" + FROM "game" + `); + await queryRunner.query(` + DROP TABLE "game" + `); + await queryRunner.query(` + ALTER TABLE "temporary_game" + RENAME TO "game" + `); + await queryRunner.query(` + CREATE INDEX "IDX_352a30652cd352f552fef73dec" ON "game" ("id") + `); + await queryRunner.query(` + CREATE INDEX "IDX_0152ed47a9e8963b5aaceb51e7" ON "game" ("title") + `); + await queryRunner.query(` + DROP INDEX "IDX_c2a3f8b06558be9508161af22e" + `); + await queryRunner.query(` + CREATE TABLE "temporary_gamevault_user" ( + "id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, + "created_at" datetime NOT NULL DEFAULT (datetime('now')), + "updated_at" datetime NOT NULL DEFAULT (datetime('now')), + "deleted_at" datetime, + "entity_version" integer NOT NULL, + "username" varchar NOT NULL, + "password" varchar NOT NULL, + "email" varchar, + "first_name" varchar, + "last_name" varchar, + "activated" boolean NOT NULL DEFAULT (0), + "role" varchar CHECK("role" IN ('0', '1', '2', '3')) NOT NULL DEFAULT (1), + "profile_picture_id" integer, + "background_image_id" integer, + "socket_secret" varchar(64) NOT NULL, + CONSTRAINT "UQ_87a411128cdc9520aede81ba929" UNIQUE ("socket_secret"), + CONSTRAINT "UQ_d0e7d50057240e5752a2c303ffb" UNIQUE ("email"), + CONSTRAINT "UQ_ad2fda40ce941655c838fb1435f" UNIQUE ("username") + ) + `); + await queryRunner.query(` + INSERT INTO "temporary_gamevault_user"( + "id", + "created_at", + "updated_at", + "deleted_at", + "entity_version", + "username", + "password", + "email", + "first_name", + "last_name", + "activated", + "role", + "profile_picture_id", + "background_image_id", + "socket_secret" + ) + SELECT "id", + "created_at", + "updated_at", + "deleted_at", + "entity_version", + "username", + "password", + "email", + "first_name", + "last_name", + "activated", + "role", + "profile_picture_id", + "background_image_id", + "socket_secret" + FROM "gamevault_user" + `); + await queryRunner.query(` + DROP TABLE "gamevault_user" + `); + await queryRunner.query(` + ALTER TABLE "temporary_gamevault_user" + RENAME TO "gamevault_user" + `); + await queryRunner.query(` + CREATE INDEX "IDX_c2a3f8b06558be9508161af22e" ON "gamevault_user" ("id") + `); + await queryRunner.query(` + DROP INDEX "IDX_c2a3f8b06558be9508161af22e" + `); + await queryRunner.query(` + CREATE TABLE "temporary_gamevault_user" ( + "id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, + "created_at" datetime NOT NULL DEFAULT (datetime('now')), + "updated_at" datetime NOT NULL DEFAULT (datetime('now')), + "deleted_at" datetime, + "entity_version" integer NOT NULL, + "username" varchar NOT NULL, + "password" varchar NOT NULL, + "email" varchar, + "first_name" varchar, + "last_name" varchar, + "activated" boolean NOT NULL DEFAULT (0), + "role" varchar CHECK("role" IN ('0', '1', '2', '3')) NOT NULL DEFAULT (1), + "profile_picture_id" integer, + "background_image_id" integer, + "socket_secret" varchar(64) NOT NULL, + CONSTRAINT "UQ_87a411128cdc9520aede81ba929" UNIQUE ("socket_secret"), + CONSTRAINT "UQ_d0e7d50057240e5752a2c303ffb" UNIQUE ("email"), + CONSTRAINT "UQ_ad2fda40ce941655c838fb1435f" UNIQUE ("username"), + CONSTRAINT "UQ_7f6b22ed7503f205ee09002a822" UNIQUE ("profile_picture_id"), + CONSTRAINT "UQ_af25d313b2845d841e18ed3237f" UNIQUE ("background_image_id") + ) + `); + await queryRunner.query(` + INSERT INTO "temporary_gamevault_user"( + "id", + "created_at", + "updated_at", + "deleted_at", + "entity_version", + "username", + "password", + "email", + "first_name", + "last_name", + "activated", + "role", + "profile_picture_id", + "background_image_id", + "socket_secret" + ) + SELECT "id", + "created_at", + "updated_at", + "deleted_at", + "entity_version", + "username", + "password", + "email", + "first_name", + "last_name", + "activated", + "role", + "profile_picture_id", + "background_image_id", + "socket_secret" + FROM "gamevault_user" + `); + await queryRunner.query(` + DROP TABLE "gamevault_user" + `); + await queryRunner.query(` + ALTER TABLE "temporary_gamevault_user" + RENAME TO "gamevault_user" + `); + await queryRunner.query(` + CREATE INDEX "IDX_c2a3f8b06558be9508161af22e" ON "gamevault_user" ("id") + `); + await queryRunner.query(` + DROP INDEX "IDX_352a30652cd352f552fef73dec" + `); + await queryRunner.query(` + DROP INDEX "IDX_0152ed47a9e8963b5aaceb51e7" + `); + await queryRunner.query(` + CREATE TABLE "temporary_game" ( + "id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, + "created_at" datetime NOT NULL DEFAULT (datetime('now')), + "updated_at" datetime NOT NULL DEFAULT (datetime('now')), + "deleted_at" datetime, + "entity_version" integer NOT NULL, + "rawg_id" integer, + "title" varchar NOT NULL, + "rawg_title" varchar, + "version" varchar, + "release_date" datetime, + "rawg_release_date" datetime, + "cache_date" datetime, + "file_path" varchar NOT NULL, + "size" bigint NOT NULL DEFAULT (0), + "description" varchar, + "website_url" varchar, + "metacritic_rating" integer, + "average_playtime" integer, + "early_access" boolean NOT NULL, + "box_image_id" integer, + "background_image_id" integer, + "type" varchar NOT NULL DEFAULT ('UNDETECTABLE'), + CONSTRAINT "UQ_7770cb331bdc54951bb9046fa9d" UNIQUE ("file_path"), + CONSTRAINT "UQ_d155b40abc872a8f5994b2db16b" UNIQUE ("box_image_id"), + CONSTRAINT "UQ_ae67c9253e4c3d5ebedeb723b75" UNIQUE ("background_image_id"), + CONSTRAINT "FK_52b4bb990c5a5fe76c6d675c002" FOREIGN KEY ("box_image_id") REFERENCES "image" ("id") ON DELETE CASCADE ON UPDATE NO ACTION, + CONSTRAINT "FK_0e88ada3f37f7cabfb6d59ed0d0" FOREIGN KEY ("background_image_id") REFERENCES "image" ("id") ON DELETE CASCADE ON UPDATE NO ACTION + ) + `); + await queryRunner.query(` + INSERT INTO "temporary_game"( + "id", + "created_at", + "updated_at", + "deleted_at", + "entity_version", + "rawg_id", + "title", + "rawg_title", + "version", + "release_date", + "rawg_release_date", + "cache_date", + "file_path", + "size", + "description", + "website_url", + "metacritic_rating", + "average_playtime", + "early_access", + "box_image_id", + "background_image_id", + "type" + ) + SELECT "id", + "created_at", + "updated_at", + "deleted_at", + "entity_version", + "rawg_id", + "title", + "rawg_title", + "version", + "release_date", + "rawg_release_date", + "cache_date", + "file_path", + "size", + "description", + "website_url", + "metacritic_rating", + "average_playtime", + "early_access", + "box_image_id", + "background_image_id", + "type" + FROM "game" + `); + await queryRunner.query(` + DROP TABLE "game" + `); + await queryRunner.query(` + ALTER TABLE "temporary_game" + RENAME TO "game" + `); + await queryRunner.query(` + CREATE INDEX "IDX_352a30652cd352f552fef73dec" ON "game" ("id") + `); + await queryRunner.query(` + CREATE INDEX "IDX_0152ed47a9e8963b5aaceb51e7" ON "game" ("title") + `); + await queryRunner.query(` + DROP INDEX "IDX_c2a3f8b06558be9508161af22e" + `); + await queryRunner.query(` + CREATE TABLE "temporary_gamevault_user" ( + "id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, + "created_at" datetime NOT NULL DEFAULT (datetime('now')), + "updated_at" datetime NOT NULL DEFAULT (datetime('now')), + "deleted_at" datetime, + "entity_version" integer NOT NULL, + "username" varchar NOT NULL, + "password" varchar NOT NULL, + "email" varchar, + "first_name" varchar, + "last_name" varchar, + "activated" boolean NOT NULL DEFAULT (0), + "role" varchar CHECK("role" IN ('0', '1', '2', '3')) NOT NULL DEFAULT (1), + "profile_picture_id" integer, + "background_image_id" integer, + "socket_secret" varchar(64) NOT NULL, + CONSTRAINT "UQ_87a411128cdc9520aede81ba929" UNIQUE ("socket_secret"), + CONSTRAINT "UQ_d0e7d50057240e5752a2c303ffb" UNIQUE ("email"), + CONSTRAINT "UQ_ad2fda40ce941655c838fb1435f" UNIQUE ("username"), + CONSTRAINT "UQ_7f6b22ed7503f205ee09002a822" UNIQUE ("profile_picture_id"), + CONSTRAINT "UQ_af25d313b2845d841e18ed3237f" UNIQUE ("background_image_id"), + CONSTRAINT "FK_c1779b9b22212754248aa404bad" FOREIGN KEY ("profile_picture_id") REFERENCES "image" ("id") ON DELETE CASCADE ON UPDATE NO ACTION, + CONSTRAINT "FK_4b83e27ed50c1e183a69fceef68" FOREIGN KEY ("background_image_id") REFERENCES "image" ("id") ON DELETE CASCADE ON UPDATE NO ACTION + ) + `); + await queryRunner.query(` + INSERT INTO "temporary_gamevault_user"( + "id", + "created_at", + "updated_at", + "deleted_at", + "entity_version", + "username", + "password", + "email", + "first_name", + "last_name", + "activated", + "role", + "profile_picture_id", + "background_image_id", + "socket_secret" + ) + SELECT "id", + "created_at", + "updated_at", + "deleted_at", + "entity_version", + "username", + "password", + "email", + "first_name", + "last_name", + "activated", + "role", + "profile_picture_id", + "background_image_id", + "socket_secret" + FROM "gamevault_user" + `); + await queryRunner.query(` + DROP TABLE "gamevault_user" + `); + await queryRunner.query(` + ALTER TABLE "temporary_gamevault_user" + RENAME TO "gamevault_user" + `); + await queryRunner.query(` + CREATE INDEX "IDX_c2a3f8b06558be9508161af22e" ON "gamevault_user" ("id") + `); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + DROP INDEX "IDX_c2a3f8b06558be9508161af22e" + `); + await queryRunner.query(` + ALTER TABLE "gamevault_user" + RENAME TO "temporary_gamevault_user" + `); + await queryRunner.query(` + CREATE TABLE "gamevault_user" ( + "id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, + "created_at" datetime NOT NULL DEFAULT (datetime('now')), + "updated_at" datetime NOT NULL DEFAULT (datetime('now')), + "deleted_at" datetime, + "entity_version" integer NOT NULL, + "username" varchar NOT NULL, + "password" varchar NOT NULL, + "email" varchar, + "first_name" varchar, + "last_name" varchar, + "activated" boolean NOT NULL DEFAULT (0), + "role" varchar CHECK("role" IN ('0', '1', '2', '3')) NOT NULL DEFAULT (1), + "profile_picture_id" integer, + "background_image_id" integer, + "socket_secret" varchar(64) NOT NULL, + CONSTRAINT "UQ_87a411128cdc9520aede81ba929" UNIQUE ("socket_secret"), + CONSTRAINT "UQ_d0e7d50057240e5752a2c303ffb" UNIQUE ("email"), + CONSTRAINT "UQ_ad2fda40ce941655c838fb1435f" UNIQUE ("username"), + CONSTRAINT "UQ_7f6b22ed7503f205ee09002a822" UNIQUE ("profile_picture_id"), + CONSTRAINT "UQ_af25d313b2845d841e18ed3237f" UNIQUE ("background_image_id") + ) + `); + await queryRunner.query(` + INSERT INTO "gamevault_user"( + "id", + "created_at", + "updated_at", + "deleted_at", + "entity_version", + "username", + "password", + "email", + "first_name", + "last_name", + "activated", + "role", + "profile_picture_id", + "background_image_id", + "socket_secret" + ) + SELECT "id", + "created_at", + "updated_at", + "deleted_at", + "entity_version", + "username", + "password", + "email", + "first_name", + "last_name", + "activated", + "role", + "profile_picture_id", + "background_image_id", + "socket_secret" + FROM "temporary_gamevault_user" + `); + await queryRunner.query(` + DROP TABLE "temporary_gamevault_user" + `); + await queryRunner.query(` + CREATE INDEX "IDX_c2a3f8b06558be9508161af22e" ON "gamevault_user" ("id") + `); + await queryRunner.query(` + DROP INDEX "IDX_0152ed47a9e8963b5aaceb51e7" + `); + await queryRunner.query(` + DROP INDEX "IDX_352a30652cd352f552fef73dec" + `); + await queryRunner.query(` + ALTER TABLE "game" + RENAME TO "temporary_game" + `); + await queryRunner.query(` + CREATE TABLE "game" ( + "id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, + "created_at" datetime NOT NULL DEFAULT (datetime('now')), + "updated_at" datetime NOT NULL DEFAULT (datetime('now')), + "deleted_at" datetime, + "entity_version" integer NOT NULL, + "rawg_id" integer, + "title" varchar NOT NULL, + "rawg_title" varchar, + "version" varchar, + "release_date" datetime, + "rawg_release_date" datetime, + "cache_date" datetime, + "file_path" varchar NOT NULL, + "size" bigint NOT NULL DEFAULT (0), + "description" varchar, + "website_url" varchar, + "metacritic_rating" integer, + "average_playtime" integer, + "early_access" boolean NOT NULL, + "box_image_id" integer, + "background_image_id" integer, + "type" varchar NOT NULL DEFAULT ('UNDETECTABLE'), + CONSTRAINT "UQ_7770cb331bdc54951bb9046fa9d" UNIQUE ("file_path"), + CONSTRAINT "UQ_d155b40abc872a8f5994b2db16b" UNIQUE ("box_image_id"), + CONSTRAINT "UQ_ae67c9253e4c3d5ebedeb723b75" UNIQUE ("background_image_id") + ) + `); + await queryRunner.query(` + INSERT INTO "game"( + "id", + "created_at", + "updated_at", + "deleted_at", + "entity_version", + "rawg_id", + "title", + "rawg_title", + "version", + "release_date", + "rawg_release_date", + "cache_date", + "file_path", + "size", + "description", + "website_url", + "metacritic_rating", + "average_playtime", + "early_access", + "box_image_id", + "background_image_id", + "type" + ) + SELECT "id", + "created_at", + "updated_at", + "deleted_at", + "entity_version", + "rawg_id", + "title", + "rawg_title", + "version", + "release_date", + "rawg_release_date", + "cache_date", + "file_path", + "size", + "description", + "website_url", + "metacritic_rating", + "average_playtime", + "early_access", + "box_image_id", + "background_image_id", + "type" + FROM "temporary_game" + `); + await queryRunner.query(` + DROP TABLE "temporary_game" + `); + await queryRunner.query(` + CREATE INDEX "IDX_0152ed47a9e8963b5aaceb51e7" ON "game" ("title") + `); + await queryRunner.query(` + CREATE INDEX "IDX_352a30652cd352f552fef73dec" ON "game" ("id") + `); + await queryRunner.query(` + DROP INDEX "IDX_c2a3f8b06558be9508161af22e" + `); + await queryRunner.query(` + ALTER TABLE "gamevault_user" + RENAME TO "temporary_gamevault_user" + `); + await queryRunner.query(` + CREATE TABLE "gamevault_user" ( + "id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, + "created_at" datetime NOT NULL DEFAULT (datetime('now')), + "updated_at" datetime NOT NULL DEFAULT (datetime('now')), + "deleted_at" datetime, + "entity_version" integer NOT NULL, + "username" varchar NOT NULL, + "password" varchar NOT NULL, + "email" varchar, + "first_name" varchar, + "last_name" varchar, + "activated" boolean NOT NULL DEFAULT (0), + "role" varchar CHECK("role" IN ('0', '1', '2', '3')) NOT NULL DEFAULT (1), + "profile_picture_id" integer, + "background_image_id" integer, + "socket_secret" varchar(64) NOT NULL, + CONSTRAINT "UQ_87a411128cdc9520aede81ba929" UNIQUE ("socket_secret"), + CONSTRAINT "UQ_d0e7d50057240e5752a2c303ffb" UNIQUE ("email"), + CONSTRAINT "UQ_ad2fda40ce941655c838fb1435f" UNIQUE ("username") + ) + `); + await queryRunner.query(` + INSERT INTO "gamevault_user"( + "id", + "created_at", + "updated_at", + "deleted_at", + "entity_version", + "username", + "password", + "email", + "first_name", + "last_name", + "activated", + "role", + "profile_picture_id", + "background_image_id", + "socket_secret" + ) + SELECT "id", + "created_at", + "updated_at", + "deleted_at", + "entity_version", + "username", + "password", + "email", + "first_name", + "last_name", + "activated", + "role", + "profile_picture_id", + "background_image_id", + "socket_secret" + FROM "temporary_gamevault_user" + `); + await queryRunner.query(` + DROP TABLE "temporary_gamevault_user" + `); + await queryRunner.query(` + CREATE INDEX "IDX_c2a3f8b06558be9508161af22e" ON "gamevault_user" ("id") + `); + await queryRunner.query(` + DROP INDEX "IDX_c2a3f8b06558be9508161af22e" + `); + await queryRunner.query(` + ALTER TABLE "gamevault_user" + RENAME TO "temporary_gamevault_user" + `); + await queryRunner.query(` + CREATE TABLE "gamevault_user" ( + "id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, + "created_at" datetime NOT NULL DEFAULT (datetime('now')), + "updated_at" datetime NOT NULL DEFAULT (datetime('now')), + "deleted_at" datetime, + "entity_version" integer NOT NULL, + "username" varchar NOT NULL, + "password" varchar NOT NULL, + "email" varchar, + "first_name" varchar, + "last_name" varchar, + "activated" boolean NOT NULL DEFAULT (0), + "role" varchar CHECK("role" IN ('0', '1', '2', '3')) NOT NULL DEFAULT (1), + "profile_picture_id" integer, + "background_image_id" integer, + "socket_secret" varchar(64) NOT NULL, + CONSTRAINT "UQ_87a411128cdc9520aede81ba929" UNIQUE ("socket_secret"), + CONSTRAINT "UQ_d0e7d50057240e5752a2c303ffb" UNIQUE ("email"), + CONSTRAINT "UQ_ad2fda40ce941655c838fb1435f" UNIQUE ("username"), + CONSTRAINT "FK_c1779b9b22212754248aa404bad" FOREIGN KEY ("profile_picture_id") REFERENCES "image" ("id") ON DELETE CASCADE ON UPDATE NO ACTION + ) + `); + await queryRunner.query(` + INSERT INTO "gamevault_user"( + "id", + "created_at", + "updated_at", + "deleted_at", + "entity_version", + "username", + "password", + "email", + "first_name", + "last_name", + "activated", + "role", + "profile_picture_id", + "background_image_id", + "socket_secret" + ) + SELECT "id", + "created_at", + "updated_at", + "deleted_at", + "entity_version", + "username", + "password", + "email", + "first_name", + "last_name", + "activated", + "role", + "profile_picture_id", + "background_image_id", + "socket_secret" + FROM "temporary_gamevault_user" + `); + await queryRunner.query(` + DROP TABLE "temporary_gamevault_user" + `); + await queryRunner.query(` + CREATE INDEX "IDX_c2a3f8b06558be9508161af22e" ON "gamevault_user" ("id") + `); + await queryRunner.query(` + DROP INDEX "IDX_0152ed47a9e8963b5aaceb51e7" + `); + await queryRunner.query(` + DROP INDEX "IDX_352a30652cd352f552fef73dec" + `); + await queryRunner.query(` + ALTER TABLE "game" + RENAME TO "temporary_game" + `); + await queryRunner.query(` + CREATE TABLE "game" ( + "id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, + "created_at" datetime NOT NULL DEFAULT (datetime('now')), + "updated_at" datetime NOT NULL DEFAULT (datetime('now')), + "deleted_at" datetime, + "entity_version" integer NOT NULL, + "rawg_id" integer, + "title" varchar NOT NULL, + "rawg_title" varchar, + "version" varchar, + "release_date" datetime, + "rawg_release_date" datetime, + "cache_date" datetime, + "file_path" varchar NOT NULL, + "size" bigint NOT NULL DEFAULT (0), + "description" varchar, + "website_url" varchar, + "metacritic_rating" integer, + "average_playtime" integer, + "early_access" boolean NOT NULL, + "box_image_id" integer, + "background_image_id" integer, + "type" varchar NOT NULL DEFAULT ('UNDETECTABLE'), + CONSTRAINT "UQ_7770cb331bdc54951bb9046fa9d" UNIQUE ("file_path") + ) + `); + await queryRunner.query(` + INSERT INTO "game"( + "id", + "created_at", + "updated_at", + "deleted_at", + "entity_version", + "rawg_id", + "title", + "rawg_title", + "version", + "release_date", + "rawg_release_date", + "cache_date", + "file_path", + "size", + "description", + "website_url", + "metacritic_rating", + "average_playtime", + "early_access", + "box_image_id", + "background_image_id", + "type" + ) + SELECT "id", + "created_at", + "updated_at", + "deleted_at", + "entity_version", + "rawg_id", + "title", + "rawg_title", + "version", + "release_date", + "rawg_release_date", + "cache_date", + "file_path", + "size", + "description", + "website_url", + "metacritic_rating", + "average_playtime", + "early_access", + "box_image_id", + "background_image_id", + "type" + FROM "temporary_game" + `); + await queryRunner.query(` + DROP TABLE "temporary_game" + `); + await queryRunner.query(` + CREATE INDEX "IDX_0152ed47a9e8963b5aaceb51e7" ON "game" ("title") + `); + await queryRunner.query(` + CREATE INDEX "IDX_352a30652cd352f552fef73dec" ON "game" ("id") + `); + await queryRunner.query(` + DROP INDEX "IDX_0152ed47a9e8963b5aaceb51e7" + `); + await queryRunner.query(` + DROP INDEX "IDX_352a30652cd352f552fef73dec" + `); + await queryRunner.query(` + ALTER TABLE "game" + RENAME TO "temporary_game" + `); + await queryRunner.query(` + CREATE TABLE "game" ( + "id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, + "created_at" datetime NOT NULL DEFAULT (datetime('now')), + "updated_at" datetime NOT NULL DEFAULT (datetime('now')), + "deleted_at" datetime, + "entity_version" integer NOT NULL, + "rawg_id" integer, + "title" varchar NOT NULL, + "rawg_title" varchar, + "version" varchar, + "release_date" datetime, + "rawg_release_date" datetime, + "cache_date" datetime, + "file_path" varchar NOT NULL, + "size" bigint NOT NULL DEFAULT (0), + "description" varchar, + "website_url" varchar, + "metacritic_rating" integer, + "average_playtime" integer, + "early_access" boolean NOT NULL, + "box_image_id" integer, + "background_image_id" integer, + "type" varchar NOT NULL DEFAULT ('UNDETECTABLE'), + CONSTRAINT "UQ_7770cb331bdc54951bb9046fa9d" UNIQUE ("file_path"), + CONSTRAINT "FK_52b4bb990c5a5fe76c6d675c002" FOREIGN KEY ("box_image_id") REFERENCES "image" ("id") ON DELETE CASCADE ON UPDATE NO ACTION + ) + `); + await queryRunner.query(` + INSERT INTO "game"( + "id", + "created_at", + "updated_at", + "deleted_at", + "entity_version", + "rawg_id", + "title", + "rawg_title", + "version", + "release_date", + "rawg_release_date", + "cache_date", + "file_path", + "size", + "description", + "website_url", + "metacritic_rating", + "average_playtime", + "early_access", + "box_image_id", + "background_image_id", + "type" + ) + SELECT "id", + "created_at", + "updated_at", + "deleted_at", + "entity_version", + "rawg_id", + "title", + "rawg_title", + "version", + "release_date", + "rawg_release_date", + "cache_date", + "file_path", + "size", + "description", + "website_url", + "metacritic_rating", + "average_playtime", + "early_access", + "box_image_id", + "background_image_id", + "type" + FROM "temporary_game" + `); + await queryRunner.query(` + DROP TABLE "temporary_game" + `); + await queryRunner.query(` + CREATE INDEX "IDX_0152ed47a9e8963b5aaceb51e7" ON "game" ("title") + `); + await queryRunner.query(` + CREATE INDEX "IDX_352a30652cd352f552fef73dec" ON "game" ("id") + `); + } +} diff --git a/src/modules/files/files.controller.ts b/src/modules/files/files.controller.ts index 703973de..3ec2fd74 100644 --- a/src/modules/files/files.controller.ts +++ b/src/modules/files/files.controller.ts @@ -6,7 +6,7 @@ import { ApiBasicAuth, } from "@nestjs/swagger"; import { Game } from "../games/game.entity"; -import { MinimumRole } from "../pagination/minimum-role.decorator"; +import { MinimumRole } from "../../decorators/minimum-role.decorator"; import { Role } from "../users/models/role.enum"; import { FilesService } from "./files.service"; diff --git a/src/modules/files/files.service.ts b/src/modules/files/files.service.ts index 0744069d..89a8ad2a 100644 --- a/src/modules/files/files.service.ts +++ b/src/modules/files/files.service.ts @@ -10,7 +10,7 @@ import { IGameVaultFile } from "./models/file.model"; import { Game } from "../games/game.entity"; import { GamesService } from "../games/games.service"; import { createReadStream, existsSync, statSync } from "fs"; -import path, { basename, extname } from "path"; +import path, { basename, extname, join } from "path"; import configuration from "../../configuration"; import mock from "../games/games.mock"; import mime from "mime"; @@ -414,7 +414,7 @@ export class FilesService implements OnApplicationBootstrap { this.logger.log( "Skipping Integrity Check because TESTING_MOCK_FILES is set to true", ); - return; + return gamesInDatabase; } this.logger.log("Started Integrity Check"); const updatedGames: Game[] = []; @@ -459,16 +459,15 @@ export class FilesService implements OnApplicationBootstrap { await readdir(configuration.VOLUMES.FILES, { encoding: "utf8", recursive: configuration.GAMES.SEARCH_RECURSIVE, + withFileTypes: true, }) ) - .filter((file) => this.isValidFilename(file)) + .filter((file) => file.isFile() && this.isValidFilename(file.name)) .map( (file) => ({ - name: file, - size: BigInt( - statSync(`${configuration.VOLUMES.FILES}/${file}`).size, - ), + name: file.name, + size: BigInt(statSync(join(file.path, file.name)).size), }) as IGameVaultFile, ); } catch (error) { diff --git a/src/modules/games/game.entity.ts b/src/modules/games/game.entity.ts index 6ca2ce29..12f2b11a 100644 --- a/src/modules/games/game.entity.ts +++ b/src/modules/games/game.entity.ts @@ -7,7 +7,7 @@ import { Index, JoinTable, JoinColumn, - ManyToOne, + OneToOne, } from "typeorm"; import { Developer } from "../developers/developer.entity"; import { Genre } from "../genres/genre.entity"; @@ -107,7 +107,7 @@ export class Game extends DatabaseEntity { }) description?: string; - @ManyToOne(() => Image, { + @OneToOne(() => Image, { nullable: true, eager: true, onDelete: "CASCADE", @@ -120,7 +120,7 @@ export class Game extends DatabaseEntity { }) box_image?: Image; - @ManyToOne(() => Image, { + @OneToOne(() => Image, { nullable: true, eager: true, onDelete: "CASCADE", diff --git a/src/modules/games/games.controller.ts b/src/modules/games/games.controller.ts index 3fa19703..14a16c30 100644 --- a/src/modules/games/games.controller.ts +++ b/src/modules/games/games.controller.ts @@ -27,12 +27,12 @@ import { } from "nestjs-paginate"; import { Repository } from "typeorm"; import { ApiOkResponsePaginated } from "../pagination/paginated-api-response.model"; -import { PaginateQueryOptions } from "../pagination/pagination.decorator"; +import { PaginateQueryOptions } from "../../decorators/pagination.decorator"; import { IdDto } from "../database/models/id.dto"; import { Game } from "./game.entity"; import { FilesService } from "../files/files.service"; import { GamesService } from "./games.service"; -import { MinimumRole } from "../pagination/minimum-role.decorator"; +import { MinimumRole } from "../../decorators/minimum-role.decorator"; import { Role } from "../users/models/role.enum"; import { UpdateGameDto } from "./models/update-game.dto"; diff --git a/src/modules/games/games.mock.ts b/src/modules/games/games.mock.ts index 02cf5aa6..28ba049c 100644 --- a/src/modules/games/games.mock.ts +++ b/src/modules/games/games.mock.ts @@ -2,1243 +2,23 @@ import { IGameVaultFile } from "../files/models/file.model"; export default [ { - name: "20 Minutes Till Dawn (EA) (2022).zip", + name: "Prototype/Prototype (W_S) (2009).iso", size: 51805370n, }, { - name: "A Tale of Paper - Refolded (2022).zip", + name: "Zombie In My Pocket New/Zombie In My Pocket New (v1.0) (W_P) (2018).exe", size: 2511751354n, }, { - name: "A Total War Saga - Thrones of Britannia (2018).zip", + name: "Space Quest 2 Vohaul's Revenge/Space Quest 2 Vohaul's Revenge (v1.1) (W_S) (1987).exe", size: 5812597402n, }, { - name: "A Total War Saga - Troy (W_P) (2021).zip", + name: "Risk 2/Risk 2 (W_P) (2000).zip", size: 10019848592n, }, { - name: "ALUMNI - Escape Room Adventure (2022).zip", + name: "Police Quest Collection/Police Quest Collection (W_S) (1987).iso", size: 601987045n, }, - { - name: "ASTRONEER (v1.18.68.0) (2019).zip", - size: 2280643319n, - }, - { - name: "Academia - School Simulator (v1.0.1) (2017).zip", - size: 146624938n, - }, - { - name: "Adios (2021).zip", - size: 2292428330n, - }, - { - name: "Airport CEO (2021).zip", - size: 992446348n, - }, - { - name: "Alfred Hitchcock - Vertigo (2021).zip", - size: 21488565057n, - }, - { - name: "Amazing Frog? V3 (2022).zip", - size: 7639742378n, - }, - { - name: "American Theft 80s (2022).zip", - size: 4070239605n, - }, - { - name: "Animal Shelter (2022).zip", - size: 1893628789n, - }, - { - name: "Anno 1404 - History Editon (2020).zip", - size: 3179051185n, - }, - { - name: "Anno 1800 (2019).zip", - size: 23850735295n, - }, - { - name: "Aragami (2016).zip", - size: 2152111685n, - }, - { - name: "Arma Reforger (2022).zip", - size: 9672500938n, - }, - { - name: "As Dusk Falls (2022).zip", - size: 18203229744n, - }, - { - name: "Assassin’s Creed (2008).zip", - size: 6930617237n, - }, - { - name: "Assassin’s Creed - Brotherhood (2010).zip", - size: 8341497653n, - }, - { - name: "Assassin’s Creed - Syndicate (2015).zip", - size: 25390129211n, - }, - { - name: "Assassin’s Creed II (2009).zip", - size: 6772465735n, - }, - { - name: "Assassin’s Creed IV - Black Flag (v1.07) (2013).zip", - size: 5158510225n, - }, - { - name: "Assassin’s Creed Rogue (2014).zip", - size: 9528675398n, - }, - { - name: "Assassin’s Creed Unity (v1.5.0) (2014).zip", - size: 35950394798n, - }, - { - name: "Assassin’s Creed Valhalla (2020).zip", - size: 61765396154n, - }, - { - name: "Automation - The Car Company Tycoon Game (v15.07.2018) (2015).zip", - size: 4870779919n, - }, - { - name: "BARRICADEZ (2020).zip", - size: 362207539n, - }, - { - name: "Bakery Simulator (W_P) (2022).zip", - size: 4445754983n, - }, - { - name: "Bear and Breakfast (2022).zip", - size: 785295163n, - }, - { - name: "Biped (2020).zip", - size: 2406544275n, - }, - { - name: "Black Mesa (2020).zip", - size: 18087693299n, - }, - { - name: "Bloons TD 6 (v27.3.4285) (2018).zip", - size: 1050233325n, - }, - { - name: "Brick Rigs (v1.22) (2016).zip", - size: 830621361n, - }, - { - name: "Brick Rigs (v1.22) (EA) (2016).zip", - size: 830617468n, - }, - { - name: "Brick Rigs (v22.03.2018) (2016).zip", - size: 1829794479n, - }, - { - name: "Broken Pieces (2020).zip", - size: 7773522805n, - }, - { - name: "Bugsnax (2020).zip", - size: 2255158289n, - }, - { - name: "Builder Simulator (2022).zip", - size: 6758381955n, - }, - { - name: "Bum Simulator (2021).zip", - size: 5456232649n, - }, - { - name: "CARRION (2020).zip", - size: 96165505n, - }, - { - name: "Captain of Industry (v0.4.12b) (EA) (2022).zip", - size: 855776690n, - }, - { - name: "Captain of Industry (v0.4.7a) (2022).zip", - size: 841837674n, - }, - { - name: "Car Trader Simulator (2020).zip", - size: 2444973400n, - }, - { - name: "Cartel Tycoon (2022).zip", - size: 1365621854n, - }, - { - name: "Castle Flipper (2021).zip", - size: 6695170315n, - }, - { - name: "Celeste (2018).zip", - size: 799894562n, - }, - { - name: "Chef - A Restaurant Tycoon Game (2018).zip", - size: 1346317115n, - }, - { - name: "City of Gangsters (v1.4.4) (2021).zip", - size: 418058038n, - }, - { - name: "Clanfolk (EA) (2022).zip", - size: 239756497n, - }, - { - name: "Cleo - a pirate's tale (2021).zip", - size: 1289731721n, - }, - { - name: "Cloudpunk (2020).zip", - size: 4734332638n, - }, - { - name: "Coffee Noir - Business Detective Game (2021).zip", - size: 1003218647n, - }, - { - name: "Cruelty Squad (2021).zip", - size: 243904493n, - }, - { - name: "Crypt of the NecroDancer (2015).zip", - size: 3324629879n, - }, - { - name: "Crysis Remastered (2021).zip", - size: 12109953535n, - }, - { - name: "Cult of the Lamb (v1.0.5) (2022).zip", - size: 719532265n, - }, - { - name: "Cultist Simulator (W_P) (v2019.6.a.1) (2018).zip", - size: 221538929n, - }, - { - name: "Cuphead (2017).zip", - size: 2139062710n, - }, - { - name: "Curse of the Dead Gods (2021).zip", - size: 679050528n, - }, - { - name: "DARK SOULS - REMASTERED (2018).zip", - size: 7083439871n, - }, - { - name: "DEATHLOOP (2021).zip", - size: 20580915662n, - }, - { - name: "DOOM Eternal (2020).zip", - size: 40622683340n, - }, - { - name: "DORAEMON STORY OF SEASONS (2019).zip", - size: 456055595n, - }, - { - name: "DYSMANTLE (2020).zip", - size: 972557589n, - }, - { - name: "Darkest Dungeon (2016).zip", - size: 2541361698n, - }, - { - name: "Days Gone (2019).zip", - size: 37818656324n, - }, - { - name: "Death Stranding (2019).zip", - size: 55583252242n, - }, - { - name: "Death Trash (EA) (v0.8.7) (2021).zip", - size: 300284595n, - }, - { - name: "Death and Taxes (v1.2.13) (2019).zip", - size: 704211327n, - }, - { - name: "Desktopia - A Desktop Village Simulator (2022).zip", - size: 239012167n, - }, - { - name: "Destroy All Humans! (2020).zip", - size: 10744574872n, - }, - { - name: "Dinkum (EA) (2022).zip", - size: 499131413n, - }, - { - name: "Dinosaur Fossil Hunter (2022).zip", - size: 6776052585n, - }, - { - name: "Disco Elysium - The Final Cut (2019).zip", - size: 13736655483n, - }, - { - name: "Don't Forget Me (2021).zip", - size: 209392844n, - }, - { - name: "Door Kickers 2 Task Force North (2020).zip", - size: 472630871n, - }, - { - name: "Dr. Livingstone I Presume? (2021).zip", - size: 3436981469n, - }, - { - name: "Driver - San Francisco (2011).zip", - size: 5093369981n, - }, - { - name: "Dungeon Alchemist (v1.1.19) (EA) (2022).zip", - size: 1173285417n, - }, - { - name: "Dying Light (2015).zip", - size: 19273756685n, - }, - { - name: "Dying Light 2 - Stay Human (2022).zip", - size: 31983362155n, - }, - { - name: "Dyson Sphere Program (2021).zip", - size: 1190525940n, - }, - { - name: "Eastward (2021).zip", - size: 1075502072n, - }, - { - name: "Eco (v0.9.2.4) (2018).zip", - size: 1386976787n, - }, - { - name: "Electrician Simulator (v1.0) (2022).zip", - size: 5371942958n, - }, - { - name: "Emily is Away Too (W_P) (2017).zip", - size: 28611063n, - }, - { - name: "Enter the Gungeon (2016).zip", - size: 1514224308n, - }, - { - name: "Escape Game Fort Boyard (2022).zip", - size: 1171159310n, - }, - { - name: "Esse Proxy (2021).zip", - size: 4517001968n, - }, - { - name: "Eternal Threads (2022).zip", - size: 3350805894n, - }, - { - name: "Exit the Gungeon (v2.1.0) (2020).zip", - size: 233742692n, - }, - { - name: "F.I.S.T. Forged In Shadow Torch (2021).zip", - size: 14100180342n, - }, - { - name: "Factory Town (v2.1) (2021).zip", - size: 205319105n, - }, - { - name: "Far Cry 6 (2021).zip", - size: 167097354161n, - }, - { - name: "Farm Manager 2021 (2021).zip", - size: 1884658862n, - }, - { - name: "Farming Simulator 22 (2021).zip", - size: 10446271234n, - }, - { - name: "Farthest Frontier (v0.7.5c) (EA) (2022).zip", - size: 1867713511n, - }, - { - name: "Fights in Tight Spaces (v1.0.6853) (2020).zip", - size: 669011017n, - }, - { - name: "Five Nights at Freddy's - Security Breach (2021).zip", - size: 34152642595n, - }, - { - name: "Forza Horizon 5 (2021).zip", - size: 643020675n, - }, - { - name: "Foundation (v1.6.20.0814) (2019).zip", - size: 1886259614n, - }, - { - name: "GRIME (2021).zip", - size: 2243087389n, - }, - { - name: "GTA - The Original Trilogy (2021).zip", - size: 6829378340n, - }, - { - name: "Galactic Mining Corp (2021).zip", - size: 617182080n, - }, - { - name: "Garbage (2021).zip", - size: 2922567713n, - }, - { - name: "Geneforge 1 Mutagen (2021).zip", - size: 160321189n, - }, - { - name: "Ghostrunner (2020).zip", - size: 4677651369n, - }, - { - name: "Ghosts 'n Goblins Resurrection (W_P) (2021).zip", - size: 4523147960n, - }, - { - name: "Ghostwire - Tokyo (2022).zip", - size: 12886224574n, - }, - { - name: "Gladiator Guild Manager (v0.825) (2021).zip", - size: 589654207n, - }, - { - name: "God of War (2022).zip", - size: 30826747941n, - }, - { - name: "Godlike Burger (2022).zip", - size: 668532077n, - }, - { - name: "Going Medieval (v0.8.36) (EA) (2021).zip", - size: 409732634n, - }, - { - name: "Good Company (v1.0.2) (2020).zip", - size: 706447651n, - }, - { - name: "GreedFall (2019).zip", - size: 20378298007n, - }, - { - name: "Grow - Song of the Evertree (2021).zip", - size: 1374567165n, - }, - { - name: "Growing Up (2021).zip", - size: 520008155n, - }, - { - name: "HITMAN 3 (2022).zip", - size: 45046784335n, - }, - { - name: "HUMANKIND (2021).zip", - size: 15391001057n, - }, - { - name: "Hacker Simulator (2021).zip", - size: 1132718065n, - }, - { - name: "Hand of Fate 2 (2017).zip", - size: 2045986409n, - }, - { - name: "Hardspace - Shipbreaker (2022).zip", - size: 2135061189n, - }, - { - name: "Hatred (2015).zip", - size: 1723613059n, - }, - { - name: "Haven (2020).zip", - size: 2993134492n, - }, - { - name: "Heavenly Bodies (2021).zip", - size: 623876035n, - }, - { - name: "HellSign (2021).zip", - size: 1182302457n, - }, - { - name: "Hidden Through Time (2020).zip", - size: 183570713n, - }, - { - name: "Hobo - Tough Life (2021).zip", - size: 4377602231n, - }, - { - name: "Hokko Life (2021) (W_P).zip", - size: 1068488302n, - }, - { - name: "Hollow Knight (2017).zip", - size: 1063298481n, - }, - { - name: "Home Behind (2016).zip", - size: 427772773n, - }, - { - name: "Home Behind 2 (2022).zip", - size: 972346060n, - }, - { - name: "Hydroneer (2021).zip", - size: 1484306150n, - }, - { - name: "I am an Air Traffic Controller 4 (2020).zip", - size: 1073999650n, - }, - { - name: "Imagine Earth (2021).zip", - size: 845344365n, - }, - { - name: "Immortals Fenyx Rising (2020).zip", - size: 16831551313n, - }, - { - name: "Impostor Factory (2021).zip", - size: 452739579n, - }, - { - name: "Inscryption (2021).zip", - size: 1865310365n, - }, - { - name: "Insurmountable (2021).zip", - size: 932425679n, - }, - { - name: "Into the Breach (v1.0.20) (2018).zip", - size: 158892741n, - }, - { - name: "Journey to the Savage Planet (2020).zip", - size: 4698457743n, - }, - { - name: "Jupiter Hell (2021).zip", - size: 1039483607n, - }, - { - name: "Jurassic World Evolution 2 (2021).zip", - size: 6275005111n, - }, - { - name: "King of Seas (2021).zip", - size: 854671849n, - }, - { - name: "KingSim (v2.03) (2020).zip", - size: 335959502n, - }, - { - name: "Kingdoms and Castles (2017).zip", - size: 334652364n, - }, - { - name: "LEGO Star Wars - The Skywalker Saga (2022).zip", - size: 22356737381n, - }, - { - name: "Last Call BBS (EA) (2022).zip", - size: 103816395n, - }, - { - name: "Last Stop (2021).zip", - size: 3535605747n, - }, - { - name: "Lemon Cake(W_P) (2021).zip", - size: 196412995n, - }, - { - name: "Liberated (2020).zip", - size: 2655789143n, - }, - { - name: "Little Nightmares II (2021).zip", - size: 2965537333n, - }, - { - name: "Little Witch in the Woods (EA)(W_P) (2022).zip", - size: 173854369n, - }, - { - name: "Loop Hero (2021).zip", - size: 110858461n, - }, - { - name: "Lost Ember (2019).zip", - size: 3575906634n, - }, - { - name: "Lumberjack's Dynasty (2021).zip", - size: 7667722441n, - }, - { - name: "Luna's Fishing Garden (2021).zip", - size: 55711083n, - }, - { - name: "MADNESS - Project Nexus (2021).zip", - size: 1570811908n, - }, - { - name: "Mad Games Tycoon 2 (2021).zip", - size: 229864281n, - }, - { - name: "Mars Horizon (2020).zip", - size: 651471509n, - }, - { - name: "Marvel's Spider-Man Remastered (2022).zip", - size: 42157898865n, - }, - { - name: "Marvels Guardians of the Galaxy (2021).zip", - size: 24146167551n, - }, - { - name: "Mech Mechanic Simulator (2021).zip", - size: 1518715037n, - }, - { - name: "Medieval Dynasty (2021).zip", - size: 3560668431n, - }, - { - name: "Midnight Fight Express (2022).zip", - size: 6185578413n, - }, - { - name: "Minute of Islands (2021).zip", - size: 1779069874n, - }, - { - name: "Moncage (2021).zip", - size: 756664359n, - }, - { - name: "Monster Sanctuary (2020).zip", - size: 230520525n, - }, - { - name: "Monster Train (2020).zip", - size: 517414615n, - }, - { - name: "Moonglow Bay (2021).zip", - size: 535954389n, - }, - { - name: "Mr. Prepper (2021).zip", - size: 1263858259n, - }, - { - name: "My Summer Car (v03.08.2022) (W_P) (EA) (2016).zip", - size: 407513745n, - }, - { - name: "My Time At Portia (2019).zip", - size: 2726113681n, - }, - { - name: "NIMBY Rails (2021).zip", - size: 17517410410n, - }, - { - name: "Narita Boy (v1.0.1.24) (2021).zip", - size: 1484264705n, - }, - { - name: "Necromunda - Hired Gun (2021).zip", - size: 13687055308n, - }, - { - name: "Need to Know (2018).zip", - size: 1412510311n, - }, - { - name: "Neon Abyss (2020).zip", - size: 316271141n, - }, - { - name: "Nine Parchments (2017).zip", - size: 4546976534n, - }, - { - name: "No Place Like Home (2022).zip", - size: 3675178311n, - }, - { - name: "No Umbrellas Allowed (2021).zip", - size: 224185240n, - }, - { - name: "ONE PUNCH MAN A HERO NOBODY KNOWS (2020).zip", - size: 7101143752n, - }, - { - name: "One More Island (2021).zip", - size: 128678555n, - }, - { - name: "Ori and the Blind Forest Definitive Edition (2015).zip", - size: 2655098524n, - }, - { - name: "Ori and the Will of the Wisps (2020).zip", - size: 3810697101n, - }, - { - name: "Outer Wilds (2020).zip", - size: 2527272840n, - }, - { - name: "Oxygen Not Included (2019).zip", - size: 1040265804n, - }, - { - name: "POSTAL 4 - No Regerts (2022).zip", - size: 21702899295n, - }, - { - name: "Parkasaurus (2020).zip", - size: 226264447n, - }, - { - name: "Parkitect (v1.7) (2016).zip", - size: 746997505n, - }, - { - name: "Partisans 1941 Extended Edition (2020).zip", - size: 8850352405n, - }, - { - name: "Peglin (2022).zip", - size: 71697639n, - }, - { - name: "PixARK (2018).zip", - size: 2375307997n, - }, - { - name: "Plane Mechanic Simulator (2019).zip", - size: 2214344648n, - }, - { - name: "PlateUp! (2022).zip", - size: 236402552n, - }, - { - name: "Platypus Adventures (2022).zip", - size: 1155804635n, - }, - { - name: "Potion Craft (v0.5) (EA) (2021).zip", - size: 968242464n, - }, - { - name: "PowerWash Simulator (2022).zip", - size: 2642150125n, - }, - { - name: "Prison Simulator (2021).zip", - size: 2869742524n, - }, - { - name: "Project Hospital (2018).zip", - size: 138249512n, - }, - { - name: "Reactor Tech 2 (2021).zip", - size: 90111830n, - }, - { - name: "Realms of Magic (2022).zip", - size: 2023345445n, - }, - { - name: "Rebel Inc- Escalation (2021).zip", - size: 450013831n, - }, - { - name: "Recipe For Disaster (2022).zip", - size: 463653235n, - }, - { - name: "Regular Factory - Escape Room (2022).zip", - size: 2391501274n, - }, - { - name: "Return Of The Obra Dinn (2018).zip", - size: 892459622n, - }, - { - name: "Rise to Ruins (2019).zip", - size: 797343765n, - }, - { - name: "Road 96 (2021).zip", - size: 4795307898n, - }, - { - name: "Roadwarden (v1.0) (2022).zip", - size: 851180713n, - }, - { - name: "Rogue Tower (2022).zip", - size: 68772734n, - }, - { - name: "SIFU (2022).zip", - size: 16348553338n, - }, - { - name: "Saints Row (2022).zip", - size: 25044638693n, - }, - { - name: "Scarface - The World Is Yours (2006).zip", - size: 4785229625n, - }, - { - name: "Scrutinized (2020).zip", - size: 1106182464n, - }, - { - name: "Sekiro Shadows Die Twice (2019).zip", - size: 14783990363n, - }, - { - name: "Session - Skate Sim (v1.0.0.32) (2019).zip", - size: 5796063445n, - }, - { - name: "Severed Steel (2021).zip", - size: 1702390738n, - }, - { - name: "Shadow Tactics - Aiko's Choice (2021).zip", - size: 2123753555n, - }, - { - name: "Shadow Tactics Blades of the Shogun (2016).zip", - size: 4113720985n, - }, - { - name: "Shakedown Hawaii (2020).zip", - size: 120463667n, - }, - { - name: "Sheltered 2 (2020).zip", - size: 2065518940n, - }, - { - name: "Sherlock Holmes - Chapter One (2021).zip", - size: 18948498978n, - }, - { - name: "Ship Graveyard Simulator (2021).zip", - size: 5379550958n, - }, - { - name: "Shotgun King - The Final Checkmate (2022).zip", - size: 32882071n, - }, - { - name: "Shovel Knight - King of Cards (2019).zip", - size: 282710978n, - }, - { - name: "Shovel Knight - Specter of Torment (2017).zip", - size: 325578554n, - }, - { - name: "Shovel Knight Pocket Dungeon (2021).zip", - size: 284965202n, - }, - { - name: "Shovel Knight Treasure Trove (v4.1) (2014).zip", - size: 443471290n, - }, - { - name: "Skatebird (2021).zip", - size: 1475394641n, - }, - { - name: "Slime Rancher (v1.4.3) (2016).zip", - size: 641032747n, - }, - { - name: "Slime Rancher 2 (v0.1.1) (EA) (2022).zip", - size: 1188804537n, - }, - { - name: "Sludge Life (2021).zip", - size: 508251593n, - }, - { - name: "Sniper Ghost Warrior Contracts 2 (2021).zip", - size: 10093200343n, - }, - { - name: "SnowRunner (2021).zip", - size: 7096913857n, - }, - { - name: "Song of Farca (2021).zip", - size: 740095980n, - }, - { - name: "Song of Iron (2021).zip", - size: 5915928228n, - }, - { - name: "Soundfall (2022).zip", - size: 2996731144n, - }, - { - name: "Space Warlord Organ Trading Simulator (2021).zip", - size: 1021986111n, - }, - { - name: "Spelunky 2 (2020).zip", - size: 494952144n, - }, - { - name: "Star Wars Jedi - Fallen Order (2019).zip", - size: 47872198594n, - }, - { - name: "Startup Company (2020).zip", - size: 210247608n, - }, - { - name: "Stories - The Path of Destinies (2016).zip", - size: 1825089889n, - }, - { - name: "Stormworks Build and Rescue (2020).zip", - size: 133989624n, - }, - { - name: "Stray (2022).zip", - size: 4985243757n, - }, - { - name: "Subnautica Below Zero (v45588) (2018).zip", - size: 6357321719n, - }, - { - name: "Subnautica Below Zero (v49203) (2021).zip", - size: 5827127158n, - }, - { - name: "SuchArt Genius Painter Simulator (2021).zip", - size: 918024661n, - }, - { - name: "Suzerain (2020).zip", - size: 478649973n, - }, - { - name: "THIEF - Definitive Edition (2018).zip", - size: 12733300293n, - }, - { - name: "TUNIC (2022).zip", - size: 1001378935n, - }, - { - name: "Technicity (2022).zip", - size: 375203364n, - }, - { - name: "Tennis Manager 2021 (2021).zip", - size: 453301380n, - }, - { - name: "Terraformers (v0.7.94c) (EA) (2022).zip", - size: 1948964184n, - }, - { - name: "Test Drive Unlimited 2 (2011).zip", - size: 6302996815n, - }, - { - name: "The Invisible Hand (2021).zip", - size: 1008222187n, - }, - { - name: "The Last Stand Aftermath (2021).zip", - size: 3160342709n, - }, - { - name: "The Life and Suffering of Sir Brante (2021).zip", - size: 1047410142n, - }, - { - name: "The Medium (2021).zip", - size: 19647057963n, - }, - { - name: "The Pathless (2021).zip", - size: 3906892025n, - }, - { - name: "The Political Process (2019).zip", - size: 108869954n, - }, - { - name: "The Sims 4 (2014).zip", - size: 32967591194n, - }, - { - name: "The Sinking City (2021).zip", - size: 12178488105n, - }, - { - name: "The Stanley Parable Ultra Deluxe (2022).zip", - size: 4193296392n, - }, - { - name: "The Vale - Shadow of the Crown (2021).zip", - size: 403714797n, - }, - { - name: "The Wandering Village (v0.1.32) (EA) (2022).zip", - size: 427604032n, - }, - { - name: "They Are Billions (2019).zip", - size: 4018744085n, - }, - { - name: "This Land Is My Land (v0.0.2.12218) (2021).zip", - size: 3712556310n, - }, - { - name: "Thunder Tier One (2021).zip", - size: 11877333618n, - }, - { - name: "Timberborn (EA) (v0.2.8.1) (2021).zip", - size: 864949518n, - }, - { - name: "Timberborn (v09.10.2021) (2021).zip", - size: 699673278n, - }, - { - name: "Tin Can (2020).zip", - size: 1508519738n, - }, - { - name: "Tiny Tactics (2022).zip", - size: 729733322n, - }, - { - name: "Tom Clancy's - Ghost Recon Wildlands (2017).zip", - size: 41342525737n, - }, - { - name: "Tom Clancy's Splinter Cell - Chaos Theory (2009).zip", - size: 3119352733n, - }, - { - name: "Tom Clancy's Splinter Cell - Conviction (2010).zip", - size: 5588590073n, - }, - { - name: "Tom Clancy's Splinter Cell - Double Agent (2009).zip", - size: 4100935960n, - }, - { - name: "Tom Clancy's Splinter Cell - Pandora Tomorrow (2004).zip", - size: 1054859773n, - }, - { - name: "Tom Clancy's Splinter Cell Blacklist (2015).zip", - size: 13474957188n, - }, - { - name: "Total War Three Kingdoms (2019).zip", - size: 18979707625n, - }, - { - name: "Transport Fever 2 (2019).zip", - size: 10817705369n, - }, - { - name: "Trüberbrook (2019).zip", - size: 2443151713n, - }, - { - name: "Twelve Minutes (2021).zip", - size: 1216801013n, - }, - { - name: "Twin Mirror (2021).zip", - size: 9644975595n, - }, - { - name: "Two Point Campus (2022).zip", - size: 1974052015n, - }, - { - name: "UnMetal (2021).zip", - size: 637054032n, - }, - { - name: "UnderMine (2019).zip", - size: 120014537n, - }, - { - name: "Undertale (2015).zip", - size: 436703478n, - }, - { - name: "Unpacking (2021).zip", - size: 286361192n, - }, - { - name: "Unravel (2020).zip", - size: 2658890025n, - }, - { - name: "Unravel Two (2018).zip", - size: 3576473161n, - }, - { - name: "Until We Die (v2021.06.12) (2021).zip", - size: 242200174n, - }, - { - name: "Urbek City Builder (2022).zip", - size: 159356943n, - }, - { - name: "Volcanoids (v1.25.338.0) (2019).zip", - size: 2651277452n, - }, - { - name: "Voxel Tycoon (EA) (v0.87.2.3) (2021).zip", - size: 313968557n, - }, - { - name: "Voxel Tycoon (v0.86.1) (2021).zip", - size: 320343077n, - }, - { - name: "Wanderlust Travel Stories (2019).zip", - size: 1004005690n, - }, - { - name: "Watch Dogs - Legion (2020).zip", - size: 90169346265n, - }, - { - name: "Way of the Hunter (2022).zip", - size: 6904526687n, - }, - { - name: "Webbed (2021).zip", - size: 130494143n, - }, - { - name: "While True learn (v1.0.61) (2019).zip", - size: 56116548n, - }, - { - name: "Wildermyth (2019).zip", - size: 932247681n, - }, - { - name: "Wobbly Life (v0.7.6) (2020).zip", - size: 689576532n, - }, - { - name: "Yakuza - Like a Dragon (2020).zip", - size: 49863001303n, - }, - { - name: "Yakuza 4 Remastered (2021).zip", - size: 14257608708n, - }, - { - name: "Yakuza 5 Remastered (2021).zip", - size: 14089072854n, - }, - { - name: "Yakuza 6 - The Song of Life (2021).zip", - size: 27773277248n, - }, - { - name: "Yakuza Kiwami (2019).zip", - size: 13470307588n, - }, - { - name: "Ziggurat 2 (2021).zip", - size: 786652880n, - }, - { - name: "art of rally (2020).zip", - size: 933198750n, - }, ] as IGameVaultFile[]; diff --git a/src/modules/games/games.service.ts b/src/modules/games/games.service.ts index 0f5367fc..7115c2c5 100644 --- a/src/modules/games/games.service.ts +++ b/src/modules/games/games.service.ts @@ -35,7 +35,7 @@ export class GamesService { options: FindOptions = { loadDeletedEntities: true, loadRelations: false }, ): Promise { try { - return await this.gamesRepository.findOneOrFail({ + const games = await this.gamesRepository.findOneOrFail({ where: { id }, relations: options.loadRelations ? [ @@ -51,7 +51,9 @@ export class GamesService { ] : [], withDeleted: options.loadDeletedEntities, + relationLoadStrategy: "query", }); + return this.filterDeletedSubEntities(games); } catch (error) { throw new NotFoundException( `Game with id ${id} was not found on the server.`, @@ -239,4 +241,16 @@ export class GamesService { await this.gamesRepository.recover({ id }); return this.findByGameIdOrFail(id); } + + private filterDeletedSubEntities(game?: Game): Game { + return { + ...game, + genres: game?.genres?.filter((entity) => !entity.deleted_at), + tags: game?.tags?.filter((entity) => !entity.deleted_at), + developers: game?.developers?.filter((entity) => !entity.deleted_at), + publishers: game?.publishers?.filter((entity) => !entity.deleted_at), + stores: game?.stores?.filter((entity) => !entity.deleted_at), + progresses: game?.progresses?.filter((entity) => !entity.deleted_at), + }; + } } diff --git a/src/modules/garbage-collection/image-garbage-collection.service.ts b/src/modules/garbage-collection/image-garbage-collection.service.ts index 722eb906..705ba47a 100644 --- a/src/modules/garbage-collection/image-garbage-collection.service.ts +++ b/src/modules/garbage-collection/image-garbage-collection.service.ts @@ -9,6 +9,7 @@ import { Cron } from "@nestjs/schedule"; import { join } from "path"; import { ImagesService } from "../images/images.service"; import { readdir, unlink } from "fs/promises"; +import { isUUID } from "class-validator"; @Injectable() export class ImageGarbageCollectionService { @@ -166,26 +167,30 @@ export class ImageGarbageCollectionService { // Get the directory where the image files are stored const imagesDirectory = configuration.VOLUMES.IMAGES; - // Get a list of all files in the directory - const allFiles = await readdir(imagesDirectory); + // Get a list of all image files in the directory + const allImageFilePaths = ( + await readdir(imagesDirectory, { + encoding: "utf8", + withFileTypes: true, + recursive: false, + }) + ) + .filter((file) => file.isFile() && isUUID(file.name.substring(0, 35), 4)) + .map((file) => join(file.path, file.name)); let removedCount = 0; // Create an array of unlink promises for each file - const unlinkPromises = allFiles.map((fileName) => { - const filePath = join(imagesDirectory, fileName); - + const unlinkPromises = allImageFilePaths.map((path) => { // If the file path is not in the usedImagePaths set, delete the file - if (!usedImagePaths.has(filePath)) { - return unlink(filePath) + if (!usedImagePaths.has(path)) { + return unlink(path) .then(() => { - this.logger.debug(`Garbage collected unused image: ${filePath}`); + this.logger.debug(`Garbage collected unused image: ${path}`); removedCount++; }) .catch((error) => { - this.logger.error( - `Error deleting unused image ${filePath}: ${error}`, - ); + this.logger.error(`Error deleting unused image ${path}: ${error}`); }); } diff --git a/src/modules/genres/genres.controller.ts b/src/modules/genres/genres.controller.ts index a0ea95d6..04acec79 100644 --- a/src/modules/genres/genres.controller.ts +++ b/src/modules/genres/genres.controller.ts @@ -8,7 +8,7 @@ import { import { InjectRepository } from "@nestjs/typeorm"; import { Repository } from "typeorm"; import { Genre } from "./genre.entity"; -import { MinimumRole } from "../pagination/minimum-role.decorator"; +import { MinimumRole } from "../../decorators/minimum-role.decorator"; import { Role } from "../users/models/role.enum"; @Controller("genres") diff --git a/src/modules/health/health.controller.ts b/src/modules/health/health.controller.ts index 092cc5b7..6c31800a 100644 --- a/src/modules/health/health.controller.ts +++ b/src/modules/health/health.controller.ts @@ -3,7 +3,7 @@ import { ApiOkResponse, ApiOperation, ApiTags } from "@nestjs/swagger"; import { Public } from "../../decorators/public.decorator"; import { Health } from "./models/health.model"; import { HealthService } from "./health.service"; -import { MinimumRole } from "../pagination/minimum-role.decorator"; +import { MinimumRole } from "../../decorators/minimum-role.decorator"; import { Role } from "../users/models/role.enum"; @Controller("health") @ApiTags("health") diff --git a/src/modules/images/images.controller.ts b/src/modules/images/images.controller.ts index 9913944f..cbd66e75 100644 --- a/src/modules/images/images.controller.ts +++ b/src/modules/images/images.controller.ts @@ -24,7 +24,7 @@ import { import { ImagesService } from "./images.service"; import fs from "fs"; import { Response } from "express"; -import { MinimumRole } from "../pagination/minimum-role.decorator"; +import { MinimumRole } from "../../decorators/minimum-role.decorator"; import { Role } from "../users/models/role.enum"; import { FileInterceptor } from "@nestjs/platform-express"; import { Image } from "./image.entity"; diff --git a/src/modules/images/images.service.ts b/src/modules/images/images.service.ts index a396395b..656db571 100644 --- a/src/modules/images/images.service.ts +++ b/src/modules/images/images.service.ts @@ -142,7 +142,9 @@ export class ImagesService { return; } this.logger.debug(`Compressing image...`); - const compressedImageBuffer = await sharp(imageBuffer).toBuffer(); + const compressedImageBuffer = await sharp(imageBuffer, { + animated: true, + }).toBuffer(); await writeFile(path, compressedImageBuffer); this.logger.debug(`Saved image to '${path}'`); } @@ -186,20 +188,28 @@ export class ImagesService { } private async validate(imageBuffer: Buffer) { - const fileType = fileTypeChecker.detectFile(imageBuffer); - if (!fileType?.extension || !fileType?.mimeType) { + const type = fileTypeChecker.detectFile(imageBuffer); + const errorContextObject = { + type, + bufferLength: imageBuffer.length, + bufferStart: imageBuffer + .toString("hex", 0, 32) + .match(/.{1,2}/g) + .join(" "), + }; + if (!type?.extension || !type?.mimeType) { throw new BadRequestException( + errorContextObject, "File type could not be detected. Please try another image.", ); } - if ( - !configuration.IMAGE.SUPPORTED_IMAGE_FORMATS.includes(fileType.mimeType) - ) { + if (!configuration.IMAGE.SUPPORTED_IMAGE_FORMATS.includes(type.mimeType)) { throw new BadRequestException( - `This file is a "${fileType.mimeType}", which is not supported.`, + errorContextObject, + `This file is a "${type.mimeType}", which is not supported.`, ); } - return fileType; + return type; } private async createFromUpload( diff --git a/src/modules/progress/progress.controller.ts b/src/modules/progress/progress.controller.ts index 09ac24e7..7e93831c 100644 --- a/src/modules/progress/progress.controller.ts +++ b/src/modules/progress/progress.controller.ts @@ -19,7 +19,7 @@ import { IdDto } from "../database/models/id.dto"; import { IncrementProgressByMinutesDto } from "./models/increment-progress-by-minutes.dto"; import { Progress } from "./progress.entity"; import { ProgressService } from "./progress.service"; -import { MinimumRole } from "../pagination/minimum-role.decorator"; +import { MinimumRole } from "../../decorators/minimum-role.decorator"; import { Role } from "../users/models/role.enum"; import { GamevaultUser } from "../users/gamevault-user.entity"; import { UpdateProgressDto } from "./models/update-progress.dto"; diff --git a/src/modules/providers/rawg/mapper.service.ts b/src/modules/providers/rawg/mapper.service.ts index d88eff68..67a2256f 100644 --- a/src/modules/providers/rawg/mapper.service.ts +++ b/src/modules/providers/rawg/mapper.service.ts @@ -29,7 +29,9 @@ export class RawgMapperService { rawg_game: RawgGame, game: Game, ): Promise { - this.logger.debug(`Mapping game "${rawg_game.name}" to "${game.title}"`); + this.logger.debug( + `Mapping RAWG Game "${rawg_game.name}" to Game "${game.title}"`, + ); game = await this.mapRawgStoresToGame(rawg_game, game); game = await this.mapRawgDevelopersToGame(rawg_game, game); game = await this.mapRawgPublishersToGame(rawg_game, game); diff --git a/src/modules/providers/rawg/rawg.controller.ts b/src/modules/providers/rawg/rawg.controller.ts index 33c184f9..3e9f035f 100644 --- a/src/modules/providers/rawg/rawg.controller.ts +++ b/src/modules/providers/rawg/rawg.controller.ts @@ -8,7 +8,7 @@ import { } from "@nestjs/swagger"; import { RawgService } from "./rawg.service"; import { Game } from "../../games/game.entity"; -import { MinimumRole } from "../../pagination/minimum-role.decorator"; +import { MinimumRole } from "../../../decorators/minimum-role.decorator"; import { Role } from "../../users/models/role.enum"; import { IdDto } from "../../database/models/id.dto"; import { GamesService } from "../../games/games.service"; diff --git a/src/modules/providers/rawg/rawg.service.ts b/src/modules/providers/rawg/rawg.service.ts index 264b20f4..747d755d 100644 --- a/src/modules/providers/rawg/rawg.service.ts +++ b/src/modules/providers/rawg/rawg.service.ts @@ -141,34 +141,23 @@ export class RawgService { return mappedGame; } - /** - * Searches for the best matching game in the RAWG API based on the specified - * title and release year. - */ private async getBestMatch( title: string, releaseYear?: number, ): Promise { const sortedResults = await this.fetchMatching(title, releaseYear); - const bestMatch = sortedResults[0]; - if (bestMatch.probability != 1) { - this.logger.log(`${sortedResults.length} matches found for "${title}"`); - this.logger.debug("-- START OF MATCHES --"); - for (const match of sortedResults) { - if (match.probability === 0) continue; - this.logger.debug( - `➥ Match: "${match.name} (${new Date( - match.released, - ).getFullYear()})" | RAWG-ID: "${match.id}" | Probability: ${ - match.probability - }`, - ); - } - this.logger.debug("-- END OF MATCHES --"); - } + const matches = sortedResults.map((match) => { + return { + probability: match.probability, + rawg_id: match.id, + rawg_title: match.name, + rawg_release_date: match.released, + }; + }); + this.logger.log(`${matches.length} Matches found for "${title}"`, matches); - return this.fetchByRawgId(bestMatch.id); + return this.fetchByRawgId(sortedResults[0].id); } /** @@ -192,11 +181,15 @@ export class RawgService { // If releaseYear is provided, fetch games with the given title and release year if (releaseYear) { + this.logger.debug( + `Fetching games matching "${title}" (${releaseYear})...`, + ); searchResults.push(...(await this.fetch(title, releaseYear)).results); } // If no search results are found with the release year, fetch games with the given title only if (searchResults.length === 0) { + this.logger.debug(`Fetching games matching "${title}"...`); searchResults.push(...(await this.fetch(title)).results); } @@ -212,17 +205,23 @@ export class RawgService { } // Calculate the probability of matching for each game - searchResults.forEach((game) => { - const titleCleaned = title.toLowerCase().replace(/[^\w\s]/g, ""); - const gameNameCleaned = game.name.toLowerCase().replace(/[^\w\s]/g, ""); + searchResults.forEach((searchResult) => { + const cleanedGameTitle = title.toLowerCase().replace(/[^\w\s]/g, ""); + const cleanedSearchResultTitle = searchResult.name + .toLowerCase() + .replace(/[^\w\s]/g, ""); // Calculate string similarity between the title and game name - game.probability = stringSimilarity(titleCleaned, gameNameCleaned); + searchResult.probability = stringSimilarity( + cleanedGameTitle, + cleanedSearchResultTitle, + ); // If releaseYear is provided, adjust the probability based on the difference between the release year of the game and the provided release year if (releaseYear !== undefined) { - const gameReleaseYear = new Date(game.released).getFullYear(); - game.probability -= Math.abs(releaseYear - gameReleaseYear) / 10; + const gameReleaseYear = new Date(searchResult.released).getFullYear(); + searchResult.probability -= + Math.abs(releaseYear - gameReleaseYear) / 10; } }); diff --git a/src/modules/tags/tags.controller.ts b/src/modules/tags/tags.controller.ts index c336f306..8f0edc48 100644 --- a/src/modules/tags/tags.controller.ts +++ b/src/modules/tags/tags.controller.ts @@ -11,10 +11,10 @@ import { } from "nestjs-paginate"; import { Repository } from "typeorm"; import { ApiOkResponsePaginated } from "../pagination/paginated-api-response.model"; -import { PaginateQueryOptions } from "../pagination/pagination.decorator"; +import { PaginateQueryOptions } from "../../decorators/pagination.decorator"; import { Tag } from "./tag.entity"; import { all_filters } from "../pagination/all-filters.filter"; -import { MinimumRole } from "../pagination/minimum-role.decorator"; +import { MinimumRole } from "../../decorators/minimum-role.decorator"; import { Role } from "../users/models/role.enum"; @Controller("tags") diff --git a/src/modules/users/activity.gateway.ts b/src/modules/users/activity.gateway.ts index 616a5311..116f9f4b 100644 --- a/src/modules/users/activity.gateway.ts +++ b/src/modules/users/activity.gateway.ts @@ -18,12 +18,12 @@ import { GamevaultUser } from "./gamevault-user.entity"; import { WebsocketExceptionsFilter } from "../../filters/websocket-exceptions.filter"; import { SocketSecretGuard } from "../guards/socket-secret.guard"; import configuration from "../../configuration"; -import { DoNothing } from "../../decorators/donothing.decorator"; +import { noop } from "rxjs"; // Conditionally decorate the WebSocket gateway class. const ConditionalWebSocketGateway = configuration.SERVER .ONLINE_ACTIVITIES_DISABLED - ? DoNothing + ? noop : WebSocketGateway({ cors: true }); @UseGuards(SocketSecretGuard) diff --git a/src/modules/users/gamevault-user.entity.ts b/src/modules/users/gamevault-user.entity.ts index 03a266f1..244dca68 100644 --- a/src/modules/users/gamevault-user.entity.ts +++ b/src/modules/users/gamevault-user.entity.ts @@ -1,5 +1,5 @@ import { ApiProperty, ApiPropertyOptional } from "@nestjs/swagger"; -import { Entity, Column, OneToMany, JoinColumn, ManyToOne } from "typeorm"; +import { Entity, Column, OneToMany, JoinColumn, OneToOne } from "typeorm"; import { Image } from "../images/image.entity"; import { Progress } from "../progress/progress.entity"; import { DatabaseEntity } from "../database/database.entity"; @@ -26,7 +26,7 @@ export class GamevaultUser extends DatabaseEntity { }) socket_secret: string; - @ManyToOne(() => Image, { + @OneToOne(() => Image, { nullable: true, eager: true, onDelete: "CASCADE", @@ -39,7 +39,7 @@ export class GamevaultUser extends DatabaseEntity { }) profile_picture?: Image; - @ManyToOne(() => Image, { + @OneToOne(() => Image, { nullable: true, eager: true, onDelete: "CASCADE", diff --git a/src/modules/users/users.controller.ts b/src/modules/users/users.controller.ts index 2d91aac3..2979e41f 100644 --- a/src/modules/users/users.controller.ts +++ b/src/modules/users/users.controller.ts @@ -22,15 +22,15 @@ import { RegisterUserDto } from "./models/register-user.dto"; import { GamevaultUser } from "./gamevault-user.entity"; import { UsersService } from "./users.service"; import { UpdateUserDto } from "./models/update-user.dto"; -import { MinimumRole } from "../pagination/minimum-role.decorator"; +import { MinimumRole } from "../../decorators/minimum-role.decorator"; import { Role } from "./models/role.enum"; import { Public } from "../../decorators/public.decorator"; import { SocketSecretService } from "./socket-secret.service"; -import { DoNothing } from "../../decorators/donothing.decorator"; +import { noop } from "rxjs"; const ConditionalRegistrationDecorator = configuration.SERVER .REGISTRATION_DISABLED - ? DoNothing + ? noop : Public(); @ApiBasicAuth() @@ -63,7 +63,7 @@ export class UsersController { }) @ApiOkResponse({ type: () => GamevaultUser, isArray: true }) async getUsersAdmin(): Promise { - return await this.usersService.getAll(true, true); + return await this.usersService.getAll(true); } /** Retrieve user information based on the provided request object. */ diff --git a/src/modules/users/users.service.ts b/src/modules/users/users.service.ts index 0b3d8417..db3888f8 100644 --- a/src/modules/users/users.service.ts +++ b/src/modules/users/users.service.ts @@ -16,6 +16,7 @@ import { FindOperator, ILike, IsNull, + Not, Repository, } from "typeorm"; import configuration from "../../configuration"; @@ -40,18 +41,15 @@ export class UsersService implements OnApplicationBootstrap { async onApplicationBootstrap() { try { - await this.setAdmin(); + await this.recoverAdmin(); } catch (error) { this.logger.error(error, "Error on FilesService Bootstrap"); } } - private async setAdmin() { + private async recoverAdmin() { try { if (!configuration.SERVER.ADMIN_USERNAME) { - this.logger.warn( - "No admin user has been configured. Ensure to set up one as follows: https://gamevau.lt/docs/server-docs/user-management#initial-setup", - ); return; } @@ -71,12 +69,12 @@ export class UsersService implements OnApplicationBootstrap { } catch (error) { if (error instanceof NotFoundException) { this.logger.warn( - `The admin user wasn't configured because the user "${configuration.SERVER.ADMIN_USERNAME}" could not be found in the database. Make sure to register the user.`, + `The admin user wasn't recovered because the user "${configuration.SERVER.ADMIN_USERNAME}" could not be found in the database. Make sure to register the user.`, ); } else { this.logger.error( error, - "An error occurred while configuring the server admin.", + "An error occurred while recovering the server admin.", ); } } @@ -137,15 +135,15 @@ export class UsersService implements OnApplicationBootstrap { return this.filterDeletedProgresses(user); } - /** Get a rough overview of all users */ public async getAll( - includeDeleted = false, - includeDeactivated = false, + includeHidden: boolean = false, ): Promise { const query: FindManyOptions = { order: { id: "ASC" }, - withDeleted: includeDeleted, - where: includeDeactivated ? undefined : { activated: true }, + withDeleted: includeHidden, + where: includeHidden + ? { username: Not(ILike("gvbot_%")) } + : { activated: true }, }; return await this.userRepository.find(query); @@ -154,6 +152,7 @@ export class UsersService implements OnApplicationBootstrap { /** Register a new user */ public async register(dto: RegisterUserDto): Promise { await this.throwIfAlreadyExists(dto.username, dto.email); + const isFirstUser = (await this.userRepository.count()) === 0; const user = new GamevaultUser(); user.username = dto.username; user.password = hashSync(dto.password, 10); @@ -164,12 +163,13 @@ export class UsersService implements OnApplicationBootstrap { if ( configuration.SERVER.ACCOUNT_ACTIVATION_DISABLED || - user.username === configuration.SERVER.ADMIN_USERNAME + user.username === configuration.SERVER.ADMIN_USERNAME || + isFirstUser ) { user.activated = true; } - if (user.username === configuration.SERVER.ADMIN_USERNAME) { + if (user.username === configuration.SERVER.ADMIN_USERNAME || isFirstUser) { user.role = Role.ADMIN; }