From 7d8e13beeb0af2a7f5a9187706c1fc0d18fe2220 Mon Sep 17 00:00:00 2001 From: Alper Alkan Date: Tue, 7 May 2024 00:42:07 +0200 Subject: [PATCH] Release 12.1.0 (#287) * Implement Feature/bookmarks #178 (#278) * Feature/bookmarks (#279) * implement bookmarks * impl bookmarks not yet done * further implementation of bookmarks * implement migrations for bookmarks * lint * fix plural * fix logs * fix bookmarked_users not being loaded without filter * format * query result caching * fix idempotency * add db indexes * fix utc dates * fix pnpm * load user relations to add bookmark * super efficient relationship loader * faster and more architecturally clean dupe detection * inverse dupe detection * performance test * remove deprecated apis * disable query result cache * Revert "disable query result cache" This reverts commit 4aed05ac6d61065e42cf9190ce16dddd2b11e90c. * Revert "performance test" This reverts commit 014d256a14fc4c393dad4a0fd71ea39dbbff857b. * refactor rawg recache * refactor logging pt. 1 * refactor logging pt. 2 * refactor logging pt. 3 * No Stack needed if contained in err-obj * update dependencies * linting * disable numseperator * fix deleted games bookmark bug * clean imports * fix bookmarking bug * implement stream-slicing using range-header on downloads * accept ranges header * syntax check * fix stream slicer * increment version * fix range content length * refactor * refactor * document header * fix range header * fix start end logic * fix logic again * fix this damn logic PLEASE * log header * add texts * update deps * fix linter * fix config parser #286 * support tls Add encryption support for PostgreSQL Client #285 * fix namings * support for tls db conns * support for rawg-to-steam-redirect * dont skip cache check without api-key when using rawg2steam * fix logger * Release 12.1.0 --- .eslintrc.js | 7 +- .prettierrc | 3 +- CHANGELOG.md | 19 + package.json | 15 +- pnpm-lock.yaml | 294 ++-- src/app.module.ts | 35 +- src/configuration.ts | 51 +- .../conditional-registration.decorator.ts | 1 + src/decorators/disable-api-if.decorator.ts | 1 + src/decorators/minimum-role.decorator.ts | 1 + src/decorators/public.decorator.ts | 1 + .../all-filters.filter.ts | 0 src/filters/http-exception.filter.ts | 5 +- src/globals.ts | 28 + .../disable-api-if.interceptor.ts | 9 +- src/logging.ts | 5 +- src/main.ts | 11 +- .../remove-api-version.middleware.ts | 2 +- src/modules/admin/admin.controller.ts | 9 +- src/modules/admin/admin.module.ts | 3 +- src/modules/boxarts/boxarts.module.ts | 5 +- src/modules/boxarts/boxarts.service.ts | 3 +- src/modules/database/database.module.ts | 5 +- src/modules/database/database.service.ts | 15 +- src/modules/database/db_configuration.ts | 28 +- .../postgres/1689984000000-add-game-type.ts | 1 + ...1695686400000-check-user-case-conflicts.ts | 1 + .../1696967362000-delete-empty-progresses.ts | 3 +- .../1714999717957-rawg-independence.ts | 137 ++ .../sqlite/1689984000000-add-game-type.ts | 1 + ...1695686400000-check-user-case-conflicts.ts | 1 + .../1696967362000-delete-empty-progresses.ts | 3 +- .../sqlite/1714999654369-rawg-independence.ts | 1313 +++++++++++++++++ .../database/models/paginated-entity.model.ts | 2 +- src/modules/developers/developer.entity.ts | 13 +- src/modules/developers/developers.module.ts | 3 +- src/modules/developers/developers.service.ts | 5 +- src/modules/files/files.controller.ts | 7 +- src/modules/files/files.module.ts | 9 +- src/modules/files/files.service.spec.ts | 6 +- src/modules/files/files.service.ts | 116 +- src/modules/files/models/byte-range-stream.ts | 37 + .../files/models/range-header.model.ts | 5 + src/modules/games/game.entity.ts | 17 +- src/modules/games/games.controller.ts | 35 +- src/modules/games/games.e2e.spec.ts | 14 +- src/modules/games/games.module.ts | 11 +- src/modules/games/games.service.ts | 9 +- src/modules/games/models/minimal-game.ts | 1 + .../garbage-collection.module.ts | 5 +- .../image-garbage-collection.service.ts | 13 +- src/modules/genres/genre.entity.ts | 13 +- src/modules/genres/genres.controller.ts | 3 +- src/modules/genres/genres.e2e.spec.ts | 7 +- src/modules/genres/genres.module.ts | 9 +- src/modules/genres/genres.service.ts | 5 +- src/modules/guards/authentication.guard.ts | 3 +- src/modules/guards/authorization.guard.ts | 7 +- src/modules/guards/basic-auth.strategy.ts | 3 +- src/modules/guards/socket-secret.guard.ts | 3 +- src/modules/health/health.controller.ts | 3 +- src/modules/health/health.e2e.spec.ts | 1 + src/modules/health/health.module.ts | 4 +- src/modules/health/health.service.ts | 3 +- src/modules/health/models/health.model.ts | 1 + src/modules/images/image.entity.ts | 3 +- src/modules/images/images.controller.ts | 13 +- src/modules/images/images.module.ts | 9 +- src/modules/images/images.service.ts | 19 +- .../paginated-api-response.model.ts | 27 - .../{plugin => plugins}/plugin.module.ts | 5 +- .../{plugin => plugins}/plugin.service.ts | 5 +- .../increment-progress-by-minutes.dto.ts | 0 .../models/state.enum.ts | 0 .../models/update-progress.dto.ts | 1 + .../models/user-id-game-id.dto.ts | 0 .../progress.controller.ts | 15 +- .../progress.entity.ts | 7 +- .../progress.module.ts | 9 +- .../progress.service.ts | 9 +- src/modules/providers/rawg/mapper.service.ts | 16 +- .../providers/rawg/models/game.interface.ts | 3 +- src/modules/providers/rawg/rawg.controller.ts | 9 +- src/modules/providers/rawg/rawg.e2e.spec.ts | 16 +- src/modules/providers/rawg/rawg.module.ts | 19 +- src/modules/providers/rawg/rawg.service.ts | 30 +- src/modules/publishers/publisher.entity.ts | 13 +- src/modules/publishers/publishers.module.ts | 3 +- src/modules/publishers/publishers.service.ts | 5 +- src/modules/stores/store.entity.ts | 13 +- src/modules/stores/stores.module.ts | 3 +- src/modules/stores/stores.service.ts | 5 +- src/modules/tags/tag.entity.ts | 13 +- src/modules/tags/tags.controller.ts | 15 +- src/modules/tags/tags.e2e.spec.ts | 7 +- src/modules/tags/tags.module.ts | 5 +- src/modules/tags/tags.service.ts | 5 +- src/modules/users/activity.gateway.ts | 25 +- src/modules/users/gamevault-user.entity.ts | 17 +- src/modules/users/models/activity.dto.ts | 1 + src/modules/users/models/register-user.dto.ts | 1 + src/modules/users/models/update-user.dto.ts | 1 + src/modules/users/socket-secret.service.ts | 1 + src/modules/users/users.controller.ts | 13 +- src/modules/users/users.module.ts | 14 +- src/modules/users/users.service.ts | 37 +- src/testing/setup-jest.ts | 1 + 107 files changed, 2285 insertions(+), 517 deletions(-) rename src/{modules/pagination => filters}/all-filters.filter.ts (100%) create mode 100644 src/modules/database/migrations/postgres/1714999717957-rawg-independence.ts create mode 100644 src/modules/database/migrations/sqlite/1714999654369-rawg-independence.ts create mode 100644 src/modules/files/models/byte-range-stream.ts create mode 100644 src/modules/files/models/range-header.model.ts rename src/modules/{plugin => plugins}/plugin.module.ts (92%) rename src/modules/{plugin => plugins}/plugin.service.ts (96%) rename src/modules/{progress => progresses}/models/increment-progress-by-minutes.dto.ts (100%) rename src/modules/{progress => progresses}/models/state.enum.ts (100%) rename src/modules/{progress => progresses}/models/update-progress.dto.ts (99%) rename src/modules/{progress => progresses}/models/user-id-game-id.dto.ts (100%) rename src/modules/{progress => progresses}/progress.controller.ts (96%) rename src/modules/{progress => progresses}/progress.entity.ts (95%) rename src/modules/{progress => progresses}/progress.module.ts (97%) rename src/modules/{progress => progresses}/progress.service.ts (96%) diff --git a/.eslintrc.js b/.eslintrc.js index 20cfe492..1b29ab0f 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -3,8 +3,9 @@ module.exports = { parserOptions: { project: "tsconfig.json", sourceType: "module", + ecmaVersion: "latest" }, - plugins: ["@typescript-eslint/eslint-plugin"], + plugins: ["@typescript-eslint/eslint-plugin", "simple-import-sort"], extends: [ "plugin:@typescript-eslint/recommended", "plugin:prettier/recommended", @@ -17,4 +18,8 @@ module.exports = { jest: true, }, ignorePatterns: [".eslintrc.js"], + rules: { + "simple-import-sort/imports": "error", + "simple-import-sort/exports": "error" + }, }; diff --git a/.prettierrc b/.prettierrc index da1986b9..feda119f 100644 --- a/.prettierrc +++ b/.prettierrc @@ -1,5 +1,4 @@ { "singleQuote": false, - "endOfLine": "auto", - "plugins": ["prettier-plugin-jsdoc"] + "endOfLine": "auto" } diff --git a/CHANGELOG.md b/CHANGELOG.md index b9365141..09304ef1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,24 @@ # GameVault Backend Server Changelog +## 12.1.0 + +Recommended Gamevault App Version: `v1.10.0.0` + +### Changes + +- Support for Pausing and Resuming Downloads by implementing HTTP `Range` Header [#14](https://github.com/Phalcode/gamevault-backend/issues/14) +- Fixed a Bug where deleted bookmarked games would break bookmarking for users who had them bookmarked. +- Formatted imports in entire codebase and refactored project structure +- Made Rotating File logger handle TESTING_MOCK_FILES environment variable +- Fixed a bug where the Config parser would not accept 0 as a value. [#286](https://github.com/Phalcode/gamevault-backend/issues/286) +- Added support for TLS Encrypted Postgres connections [#285](https://github.com/Phalcode/gamevault-backend/issues/285) +- Support for [Rawg2Steam](https://github.com/Phalcode/rawg-to-steam-redirect) API (Removed Reliance on Rawg IDs and their API KEY, and implemented support for RawgToSteam Box Images) + +### Thanks + +- @wieluk +- @hostmatic + ## 12.0.0 Recommended Gamevault App Version: `v1.9.2.0` diff --git a/package.json b/package.json index e6142fb7..b71e0147 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "gamevault-backend", - "version": "12.0.0", + "version": "12.1.0", "description": "the self-hosted gaming platform for drm-free games", "author": "Alkan Alper, Schäfer Philip GbR / Phalcode", "private": true, @@ -41,7 +41,7 @@ "async-g-i-s": "^1.5.2", "axios": "^1.6.8", "bcrypt": "^5.1.1", - "better-sqlite3": "^9.5.0", + "better-sqlite3": "^9.6.0", "builder-pattern": "^2.2.0", "chokidar": "^3.6.0", "class-transformer": "^0.5.1", @@ -90,18 +90,19 @@ "@types/mime": "^3.0.4", "@types/morgan": "^1.9.9", "@types/multer": "^1.4.11", - "@types/node": "^20.12.7", + "@types/node": "^20.12.8", "@types/node-7z": "^2.1.8", "@types/passport-http": "^0.3.11", - "@types/pg": "^8.11.5", + "@types/pg": "^8.11.6", "@types/string-similarity": "^4.0.2", "@types/unidecode": "^0.1.3", - "@typescript-eslint/eslint-plugin": "^7.7.1", - "@typescript-eslint/parser": "^7.7.1", - "eslint": "^8.57.0", + "@typescript-eslint/eslint-plugin": "^7.8.0", + "@typescript-eslint/parser": "^7.8.0", + "eslint": "^8.56.0", "eslint-config-prettier": "^9.1.0", "eslint-plugin-import": "^2.29.1", "eslint-plugin-prettier": "^5.1.3", + "eslint-plugin-simple-import-sort": "^12.1.0", "jest": "^29.7.0", "prettier": "^3.2.5", "prettier-plugin-jsdoc": "^1.3.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 532a7359..47378d73 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -40,7 +40,7 @@ importers: version: 7.3.1(@nestjs/common@10.3.8(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.3.8(@nestjs/common@10.3.8(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.3.8)(@nestjs/websockets@10.3.8)(encoding@0.1.13)(reflect-metadata@0.2.2)(rxjs@7.8.1))(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2) '@nestjs/typeorm': specifier: ^10.0.2 - version: 10.0.2(@nestjs/common@10.3.8(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.3.8(@nestjs/common@10.3.8(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.3.8)(@nestjs/websockets@10.3.8)(encoding@0.1.13)(reflect-metadata@0.2.2)(rxjs@7.8.1))(reflect-metadata@0.2.2)(rxjs@7.8.1)(typeorm@0.3.20(better-sqlite3@9.5.0)(pg@8.11.5)(ts-node@10.9.2(@types/node@20.12.7)(typescript@5.4.5))) + version: 10.0.2(@nestjs/common@10.3.8(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.3.8(@nestjs/common@10.3.8(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.3.8)(@nestjs/websockets@10.3.8)(encoding@0.1.13)(reflect-metadata@0.2.2)(rxjs@7.8.1))(reflect-metadata@0.2.2)(rxjs@7.8.1)(typeorm@0.3.20(better-sqlite3@9.6.0)(pg@8.11.5)(ts-node@10.9.2(@types/node@20.12.8)(typescript@5.4.5))) '@nestjs/websockets': specifier: ^10.3.8 version: 10.3.8(@nestjs/common@10.3.8(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.3.8)(@nestjs/platform-socket.io@10.3.8)(reflect-metadata@0.2.2)(rxjs@7.8.1) @@ -57,8 +57,8 @@ importers: specifier: ^5.1.1 version: 5.1.1(encoding@0.1.13) better-sqlite3: - specifier: ^9.5.0 - version: 9.5.0 + specifier: ^9.6.0 + version: 9.6.0 builder-pattern: specifier: ^2.2.0 version: 2.2.0 @@ -106,10 +106,10 @@ importers: version: 1.9.4(@nestjs/common@10.3.8(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(winston@3.13.0) nestjs-asyncapi: specifier: ^1.3.0 - version: 1.3.0(@nestjs/common@10.3.8(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.3.8(@nestjs/common@10.3.8(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.3.8)(@nestjs/websockets@10.3.8)(encoding@0.1.13)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/swagger@7.3.1(@nestjs/common@10.3.8(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.3.8(@nestjs/common@10.3.8(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.3.8)(@nestjs/websockets@10.3.8)(encoding@0.1.13)(reflect-metadata@0.2.2)(rxjs@7.8.1))(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2))(@nestjs/websockets@10.3.8(@nestjs/common@10.3.8(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.3.8)(@nestjs/platform-socket.io@10.3.8)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@types/babel__core@7.20.3)(@types/node@20.12.7)(encoding@0.1.13) + version: 1.3.0(@nestjs/common@10.3.8(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.3.8(@nestjs/common@10.3.8(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.3.8)(@nestjs/websockets@10.3.8)(encoding@0.1.13)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/swagger@7.3.1(@nestjs/common@10.3.8(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.3.8(@nestjs/common@10.3.8(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.3.8)(@nestjs/websockets@10.3.8)(encoding@0.1.13)(reflect-metadata@0.2.2)(rxjs@7.8.1))(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2))(@nestjs/websockets@10.3.8(@nestjs/common@10.3.8(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.3.8)(@nestjs/platform-socket.io@10.3.8)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@types/babel__core@7.20.3)(@types/node@20.12.8)(encoding@0.1.13) nestjs-paginate: specifier: ^8.6.2 - version: 8.6.2(@nestjs/common@10.3.8(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/swagger@7.3.1(@nestjs/common@10.3.8(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.3.8(@nestjs/common@10.3.8(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.3.8)(@nestjs/websockets@10.3.8)(encoding@0.1.13)(reflect-metadata@0.2.2)(rxjs@7.8.1))(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2))(express@4.19.2)(fastify@4.26.2)(typeorm@0.3.20(better-sqlite3@9.5.0)(pg@8.11.5)(ts-node@10.9.2(@types/node@20.12.7)(typescript@5.4.5))) + version: 8.6.2(@nestjs/common@10.3.8(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/swagger@7.3.1(@nestjs/common@10.3.8(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.3.8(@nestjs/common@10.3.8(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.3.8)(@nestjs/websockets@10.3.8)(encoding@0.1.13)(reflect-metadata@0.2.2)(rxjs@7.8.1))(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2))(express@4.19.2)(fastify@4.26.2)(typeorm@0.3.20(better-sqlite3@9.6.0)(pg@8.11.5)(ts-node@10.9.2(@types/node@20.12.8)(typescript@5.4.5))) node-7z: specifier: ^3.0.0 version: 3.0.0 @@ -148,10 +148,10 @@ importers: version: 2.1.4 typeorm: specifier: ^0.3.20 - version: 0.3.20(better-sqlite3@9.5.0)(pg@8.11.5)(ts-node@10.9.2(@types/node@20.12.7)(typescript@5.4.5)) + version: 0.3.20(better-sqlite3@9.6.0)(pg@8.11.5)(ts-node@10.9.2(@types/node@20.12.8)(typescript@5.4.5)) typeorm-naming-strategies: specifier: ^4.1.0 - version: 4.1.0(typeorm@0.3.20(better-sqlite3@9.5.0)(pg@8.11.5)(ts-node@10.9.2(@types/node@20.12.7)(typescript@5.4.5))) + version: 4.1.0(typeorm@0.3.20(better-sqlite3@9.6.0)(pg@8.11.5)(ts-node@10.9.2(@types/node@20.12.8)(typescript@5.4.5))) unidecode: specifier: ^1.0.1 version: 1.0.1 @@ -199,8 +199,8 @@ importers: specifier: ^1.4.11 version: 1.4.11 '@types/node': - specifier: ^20.12.7 - version: 20.12.7 + specifier: ^20.12.8 + version: 20.12.8 '@types/node-7z': specifier: ^2.1.8 version: 2.1.8 @@ -208,8 +208,8 @@ importers: specifier: ^0.3.11 version: 0.3.11 '@types/pg': - specifier: ^8.11.5 - version: 8.11.5 + specifier: ^8.11.6 + version: 8.11.6 '@types/string-similarity': specifier: ^4.0.2 version: 4.0.2 @@ -217,26 +217,29 @@ importers: specifier: ^0.1.3 version: 0.1.3 '@typescript-eslint/eslint-plugin': - specifier: ^7.7.1 - version: 7.7.1(@typescript-eslint/parser@7.7.1(eslint@8.57.0)(typescript@5.4.5))(eslint@8.57.0)(typescript@5.4.5) + specifier: ^7.8.0 + version: 7.8.0(@typescript-eslint/parser@7.8.0(eslint@8.57.0)(typescript@5.4.5))(eslint@8.57.0)(typescript@5.4.5) '@typescript-eslint/parser': - specifier: ^7.7.1 - version: 7.7.1(eslint@8.57.0)(typescript@5.4.5) + specifier: ^7.8.0 + version: 7.8.0(eslint@8.57.0)(typescript@5.4.5) eslint: - specifier: ^8.57.0 + specifier: ^8.56.0 version: 8.57.0 eslint-config-prettier: specifier: ^9.1.0 version: 9.1.0(eslint@8.57.0) eslint-plugin-import: specifier: ^2.29.1 - version: 2.29.1(@typescript-eslint/parser@7.7.1(eslint@8.57.0)(typescript@5.4.5))(eslint@8.57.0) + version: 2.29.1(@typescript-eslint/parser@7.8.0(eslint@8.57.0)(typescript@5.4.5))(eslint@8.57.0) eslint-plugin-prettier: specifier: ^5.1.3 version: 5.1.3(@types/eslint@8.4.6)(eslint-config-prettier@9.1.0(eslint@8.57.0))(eslint@8.57.0)(prettier@3.2.5) + eslint-plugin-simple-import-sort: + specifier: ^12.1.0 + version: 12.1.0(eslint@8.57.0) jest: specifier: ^29.7.0 - version: 29.7.0(@types/node@20.12.7)(ts-node@10.9.2(@types/node@20.12.7)(typescript@5.4.5)) + version: 29.7.0(@types/node@20.12.8)(ts-node@10.9.2(@types/node@20.12.8)(typescript@5.4.5)) prettier: specifier: ^3.2.5 version: 3.2.5 @@ -248,10 +251,10 @@ importers: version: 2.11.1 ts-jest: specifier: ^29.1.2 - version: 29.1.2(@babel/core@7.12.9)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.12.9))(jest@29.7.0(@types/node@20.12.7)(ts-node@10.9.2(@types/node@20.12.7)(typescript@5.4.5)))(typescript@5.4.5) + version: 29.1.2(@babel/core@7.12.9)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.12.9))(jest@29.7.0(@types/node@20.12.8)(ts-node@10.9.2(@types/node@20.12.8)(typescript@5.4.5)))(typescript@5.4.5) ts-node: specifier: ^10.9.2 - version: 10.9.2(@types/node@20.12.7)(typescript@5.4.5) + version: 10.9.2(@types/node@20.12.8)(typescript@5.4.5) typescript: specifier: ^5.4.5 version: 5.4.5 @@ -1761,8 +1764,8 @@ packages: '@types/node-7z@2.1.8': resolution: {integrity: sha512-VjiU7yEbczNc3EFKN4GJcAUqAMkn92P/92r6ARjMSXEdixunMD9lC79mTX81vKxTlNYXuvCJ7zvnzlDbFTt2Vw==} - '@types/node@20.12.7': - resolution: {integrity: sha512-wq0cICSkRLVaf3UGLMGItu/PtdY7oaXaI/RVU+xliKVOtRna3PRY57ZDfztpDL0n11vfymMUnXv8QwYCO7L1wg==} + '@types/node@20.12.8': + resolution: {integrity: sha512-NU0rJLJnshZWdE/097cdCBbyW1h4hEg0xpovcoAQYHl8dnEyp/NAOiE45pvc+Bd1Dt+2r94v2eGFpQJ4R7g+2w==} '@types/passport-http@0.3.11': resolution: {integrity: sha512-FO0rDRYtuha9m2ZgRx5+jrgrrkAnUzgzdItFI0dwKBC6k9pArK677Gtan67u6+Qah2nXVP3M1uZ5p90SpBT5Zg==} @@ -1770,8 +1773,8 @@ packages: '@types/passport@1.0.12': resolution: {integrity: sha512-QFdJ2TiAEoXfEQSNDISJR1Tm51I78CymqcBa8imbjo6dNNu+l2huDxxbDEIoFIwOSKMkOfHEikyDuZ38WwWsmw==} - '@types/pg@8.11.5': - resolution: {integrity: sha512-2xMjVviMxneZHDHX5p5S6tsRRs7TpDHeeK7kTTMe/kAC/mRRNjWHjZg0rkiY+e17jXSZV3zJYDxXV8Cy72/Vuw==} + '@types/pg@8.11.6': + resolution: {integrity: sha512-/2WmmBXHLsfRqzfHW7BNZ8SbYzE8OSk7i3WjFYvfgRHj7S1xj+16Je5fUKv3lVdVzk/zn9TXOqf+avFCFIE0yQ==} '@types/protocol-buffers-schema@3.4.2': resolution: {integrity: sha512-GaQpfsfFk4wGU3//d7uCGy9zy6B8QBEyWYd6+maZH+S6m861QrFvLWS5RyHj4UfIiON9tmqCz9C+oNpebDgGIw==} @@ -1821,8 +1824,8 @@ packages: '@types/yauzl@2.10.2': resolution: {integrity: sha512-Km7XAtUIduROw7QPgvcft0lIupeG8a8rdKL8RiSyKvlE7dYY31fEn41HVuQsRFDuROA8tA4K2UVL+WdfFmErBA==} - '@typescript-eslint/eslint-plugin@7.7.1': - resolution: {integrity: sha512-KwfdWXJBOviaBVhxO3p5TJiLpNuh2iyXyjmWN0f1nU87pwyvfS0EmjC6ukQVYVFJd/K1+0NWGPDXiyEyQorn0Q==} + '@typescript-eslint/eslint-plugin@7.8.0': + resolution: {integrity: sha512-gFTT+ezJmkwutUPmB0skOj3GZJtlEGnlssems4AjkVweUPGj7jRwwqg0Hhg7++kPGJqKtTYx+R05Ftww372aIg==} engines: {node: ^18.18.0 || >=20.0.0} peerDependencies: '@typescript-eslint/parser': ^7.0.0 @@ -1832,8 +1835,8 @@ packages: typescript: optional: true - '@typescript-eslint/parser@7.7.1': - resolution: {integrity: sha512-vmPzBOOtz48F6JAGVS/kZYk4EkXao6iGrD838sp1w3NQQC0W8ry/q641KU4PrG7AKNAf56NOcR8GOpH8l9FPCw==} + '@typescript-eslint/parser@7.8.0': + resolution: {integrity: sha512-KgKQly1pv0l4ltcftP59uQZCi4HUYswCLbTqVZEJu7uLX8CTLyswqMLqLN+2QFz4jCptqWVV4SB7vdxcH2+0kQ==} engines: {node: ^18.18.0 || >=20.0.0} peerDependencies: eslint: ^8.56.0 @@ -1842,12 +1845,12 @@ packages: typescript: optional: true - '@typescript-eslint/scope-manager@7.7.1': - resolution: {integrity: sha512-PytBif2SF+9SpEUKynYn5g1RHFddJUcyynGpztX3l/ik7KmZEv19WCMhUBkHXPU9es/VWGD3/zg3wg90+Dh2rA==} + '@typescript-eslint/scope-manager@7.8.0': + resolution: {integrity: sha512-viEmZ1LmwsGcnr85gIq+FCYI7nO90DVbE37/ll51hjv9aG+YZMb4WDE2fyWpUR4O/UrhGRpYXK/XajcGTk2B8g==} engines: {node: ^18.18.0 || >=20.0.0} - '@typescript-eslint/type-utils@7.7.1': - resolution: {integrity: sha512-ZksJLW3WF7o75zaBPScdW1Gbkwhd/lyeXGf1kQCxJaOeITscoSl0MjynVvCzuV5boUz/3fOI06Lz8La55mu29Q==} + '@typescript-eslint/type-utils@7.8.0': + resolution: {integrity: sha512-H70R3AefQDQpz9mGv13Uhi121FNMh+WEaRqcXTX09YEDky21km4dV1ZXJIp8QjXc4ZaVkXVdohvWDzbnbHDS+A==} engines: {node: ^18.18.0 || >=20.0.0} peerDependencies: eslint: ^8.56.0 @@ -1856,12 +1859,12 @@ packages: typescript: optional: true - '@typescript-eslint/types@7.7.1': - resolution: {integrity: sha512-AmPmnGW1ZLTpWa+/2omPrPfR7BcbUU4oha5VIbSbS1a1Tv966bklvLNXxp3mrbc+P2j4MNOTfDffNsk4o0c6/w==} + '@typescript-eslint/types@7.8.0': + resolution: {integrity: sha512-wf0peJ+ZGlcH+2ZS23aJbOv+ztjeeP8uQ9GgwMJGVLx/Nj9CJt17GWgWWoSmoRVKAX2X+7fzEnAjxdvK2gqCLw==} engines: {node: ^18.18.0 || >=20.0.0} - '@typescript-eslint/typescript-estree@7.7.1': - resolution: {integrity: sha512-CXe0JHCXru8Fa36dteXqmH2YxngKJjkQLjxzoj6LYwzZ7qZvgsLSc+eqItCrqIop8Vl2UKoAi0StVWu97FQZIQ==} + '@typescript-eslint/typescript-estree@7.8.0': + resolution: {integrity: sha512-5pfUCOwK5yjPaJQNy44prjCwtr981dO8Qo9J9PwYXZ0MosgAbfEMB008dJ5sNo3+/BN6ytBPuSvXUg9SAqB0dg==} engines: {node: ^18.18.0 || >=20.0.0} peerDependencies: typescript: '*' @@ -1869,14 +1872,14 @@ packages: typescript: optional: true - '@typescript-eslint/utils@7.7.1': - resolution: {integrity: sha512-QUvBxPEaBXf41ZBbaidKICgVL8Hin0p6prQDu6bbetWo39BKbWJxRsErOzMNT1rXvTll+J7ChrbmMCXM9rsvOQ==} + '@typescript-eslint/utils@7.8.0': + resolution: {integrity: sha512-L0yFqOCflVqXxiZyXrDr80lnahQfSOfc9ELAAZ75sqicqp2i36kEZZGuUymHNFoYOqxRT05up760b4iGsl02nQ==} engines: {node: ^18.18.0 || >=20.0.0} peerDependencies: eslint: ^8.56.0 - '@typescript-eslint/visitor-keys@7.7.1': - resolution: {integrity: sha512-gBL3Eq25uADw1LQ9kVpf3hRM+DWzs0uZknHYK3hq4jcTPqVCClHGDnB6UUUV2SFeBeA4KWHWbbLqmbGcZ4FYbw==} + '@typescript-eslint/visitor-keys@7.8.0': + resolution: {integrity: sha512-q4/gibTNBQNA0lGyYQCmWRS5D15n8rXh4QjK3KV+MBPlTYHpfBUT3D3PaPR/HeNiI9W6R7FvlkcGhNyAoP+caA==} engines: {node: ^18.18.0 || >=20.0.0} '@ungap/structured-clone@1.2.0': @@ -2259,8 +2262,8 @@ packages: resolution: {integrity: sha512-AGBHOG5hPYZ5Xl9KXzU5iKq9516yEmvCKDg3ecP5kX2aB6UqTeXZxk2ELnDgDm6BQSMlLt9rDB4LoSMx0rYwww==} engines: {node: '>= 10.0.0'} - better-sqlite3@9.5.0: - resolution: {integrity: sha512-01qVcM4gPNwE+PX7ARNiHINwzVuD6nx0gdldaAAcu+MrzyIAukQ31ZDKEpzRO/CNA9sHpxoTZ8rdjoyAin4dyg==} + better-sqlite3@9.6.0: + resolution: {integrity: sha512-yR5HATnqeYNVnkaUTf4bOP2dJSnyhP4puJN/QPRyx4YkBEEUxib422n2XzPqDEHjQQqazoYoADdAm5vE15+dAQ==} big-integer@1.6.51: resolution: {integrity: sha512-GPEid2Y9QU1Exl1rpO9B2IPJGHPSupF5GnVIP0blYvNOMer2bTvSWs1jGOUg04hTmu67nmLsQ9TBo1puaotBHg==} @@ -2994,6 +2997,11 @@ packages: eslint-config-prettier: optional: true + eslint-plugin-simple-import-sort@12.1.0: + resolution: {integrity: sha512-Y2fqAfC11TcG/WP3TrI1Gi3p3nc8XJyEOJYHyEPEGI/UAgNx6akxxlX74p7SbAQdLcgASKhj8M0GKvH3vq/+ig==} + peerDependencies: + eslint: '>=5.0.0' + eslint-scope@5.1.1: resolution: {integrity: sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==} engines: {node: '>=8.0.0'} @@ -6246,7 +6254,7 @@ snapshots: - encoding - supports-color - '@asyncapi/generator@1.13.1(@types/babel__core@7.20.3)(@types/node@20.12.7)(encoding@0.1.13)': + '@asyncapi/generator@1.13.1(@types/babel__core@7.20.3)(@types/node@20.12.8)(encoding@0.1.13)': dependencies: '@asyncapi/generator-react-sdk': 0.2.25(@types/babel__core@7.20.3)(encoding@0.1.13) '@asyncapi/parser': 2.1.0(encoding@0.1.13) @@ -6271,7 +6279,7 @@ snapshots: semver: 7.6.0 simple-git: 3.20.0 source-map-support: 0.5.21 - ts-node: 10.9.2(@types/node@20.12.7)(typescript@4.9.5) + ts-node: 10.9.2(@types/node@20.12.8)(typescript@4.9.5) typescript: 4.9.5 transitivePeerDependencies: - '@swc/core' @@ -7500,27 +7508,27 @@ snapshots: '@jest/console@29.7.0': dependencies: '@jest/types': 29.6.3 - '@types/node': 20.12.7 + '@types/node': 20.12.8 chalk: 4.1.2 jest-message-util: 29.7.0 jest-util: 29.7.0 slash: 3.0.0 - '@jest/core@29.7.0(ts-node@10.9.2(@types/node@20.12.7)(typescript@5.4.5))': + '@jest/core@29.7.0(ts-node@10.9.2(@types/node@20.12.8)(typescript@5.4.5))': dependencies: '@jest/console': 29.7.0 '@jest/reporters': 29.7.0 '@jest/test-result': 29.7.0 '@jest/transform': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 20.12.7 + '@types/node': 20.12.8 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.12.7)(ts-node@10.9.2(@types/node@20.12.7)(typescript@5.4.5)) + jest-config: 29.7.0(@types/node@20.12.8)(ts-node@10.9.2(@types/node@20.12.8)(typescript@5.4.5)) jest-haste-map: 29.7.0 jest-message-util: 29.7.0 jest-regex-util: 29.6.3 @@ -7545,7 +7553,7 @@ snapshots: dependencies: '@jest/fake-timers': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 20.12.7 + '@types/node': 20.12.8 jest-mock: 29.7.0 '@jest/expect-utils@29.7.0': @@ -7563,7 +7571,7 @@ snapshots: dependencies: '@jest/types': 29.6.3 '@sinonjs/fake-timers': 10.3.0 - '@types/node': 20.12.7 + '@types/node': 20.12.8 jest-message-util: 29.7.0 jest-mock: 29.7.0 jest-util: 29.7.0 @@ -7585,7 +7593,7 @@ snapshots: '@jest/transform': 29.7.0 '@jest/types': 29.6.3 '@jridgewell/trace-mapping': 0.3.18 - '@types/node': 20.12.7 + '@types/node': 20.12.8 chalk: 4.1.2 collect-v8-coverage: 1.0.2 exit: 0.1.2 @@ -7655,7 +7663,7 @@ snapshots: '@jest/schemas': 29.6.3 '@types/istanbul-lib-coverage': 2.0.5 '@types/istanbul-reports': 3.0.3 - '@types/node': 20.12.7 + '@types/node': 20.12.8 '@types/yargs': 17.0.29 chalk: 4.1.2 @@ -7894,13 +7902,13 @@ snapshots: optionalDependencies: '@nestjs/platform-express': 10.3.8(@nestjs/common@10.3.8(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.3.8) - '@nestjs/typeorm@10.0.2(@nestjs/common@10.3.8(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.3.8(@nestjs/common@10.3.8(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.3.8)(@nestjs/websockets@10.3.8)(encoding@0.1.13)(reflect-metadata@0.2.2)(rxjs@7.8.1))(reflect-metadata@0.2.2)(rxjs@7.8.1)(typeorm@0.3.20(better-sqlite3@9.5.0)(pg@8.11.5)(ts-node@10.9.2(@types/node@20.12.7)(typescript@5.4.5)))': + '@nestjs/typeorm@10.0.2(@nestjs/common@10.3.8(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.3.8(@nestjs/common@10.3.8(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.3.8)(@nestjs/websockets@10.3.8)(encoding@0.1.13)(reflect-metadata@0.2.2)(rxjs@7.8.1))(reflect-metadata@0.2.2)(rxjs@7.8.1)(typeorm@0.3.20(better-sqlite3@9.6.0)(pg@8.11.5)(ts-node@10.9.2(@types/node@20.12.8)(typescript@5.4.5)))': dependencies: '@nestjs/common': 10.3.8(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1) '@nestjs/core': 10.3.8(@nestjs/common@10.3.8(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.3.8)(@nestjs/websockets@10.3.8)(encoding@0.1.13)(reflect-metadata@0.2.2)(rxjs@7.8.1) reflect-metadata: 0.2.2 rxjs: 7.8.1 - typeorm: 0.3.20(better-sqlite3@9.5.0)(pg@8.11.5)(ts-node@10.9.2(@types/node@20.12.7)(typescript@5.4.5)) + typeorm: 0.3.20(better-sqlite3@9.6.0)(pg@8.11.5)(ts-node@10.9.2(@types/node@20.12.8)(typescript@5.4.5)) uuid: 9.0.1 '@nestjs/websockets@10.3.8(@nestjs/common@10.3.8(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.3.8)(@nestjs/platform-socket.io@10.3.8)(reflect-metadata@0.2.2)(rxjs@7.8.1)': @@ -8270,12 +8278,12 @@ snapshots: '@types/bcrypt@5.0.2': dependencies: - '@types/node': 20.12.7 + '@types/node': 20.12.8 '@types/body-parser@1.19.2': dependencies: '@types/connect': 3.4.35 - '@types/node': 20.12.7 + '@types/node': 20.12.8 '@types/compression@1.7.5': dependencies: @@ -8283,7 +8291,7 @@ snapshots: '@types/connect@3.4.35': dependencies: - '@types/node': 20.12.7 + '@types/node': 20.12.8 '@types/cookie-parser@1.4.7': dependencies: @@ -8293,7 +8301,7 @@ snapshots: '@types/cors@2.8.15': dependencies: - '@types/node': 20.12.7 + '@types/node': 20.12.8 '@types/debug@4.1.7': dependencies: @@ -8305,7 +8313,7 @@ snapshots: '@types/es-aggregate-error@1.0.4': dependencies: - '@types/node': 20.12.7 + '@types/node': 20.12.8 '@types/eslint-scope@3.7.4': dependencies: @@ -8323,7 +8331,7 @@ snapshots: '@types/express-serve-static-core@4.17.33': dependencies: - '@types/node': 20.12.7 + '@types/node': 20.12.8 '@types/qs': 6.9.7 '@types/range-parser': 1.2.4 @@ -8336,7 +8344,7 @@ snapshots: '@types/graceful-fs@4.1.8': dependencies: - '@types/node': 20.12.7 + '@types/node': 20.12.8 '@types/istanbul-lib-coverage@2.0.5': {} @@ -8367,7 +8375,7 @@ snapshots: '@types/morgan@1.9.9': dependencies: - '@types/node': 20.12.7 + '@types/node': 20.12.8 '@types/ms@0.7.31': {} @@ -8377,9 +8385,9 @@ snapshots: '@types/node-7z@2.1.8': dependencies: - '@types/node': 20.12.7 + '@types/node': 20.12.8 - '@types/node@20.12.7': + '@types/node@20.12.8': dependencies: undici-types: 5.26.5 @@ -8392,15 +8400,15 @@ snapshots: dependencies: '@types/express': 4.17.21 - '@types/pg@8.11.5': + '@types/pg@8.11.6': dependencies: - '@types/node': 20.12.7 + '@types/node': 20.12.8 pg-protocol: 1.6.1 pg-types: 4.0.2 '@types/protocol-buffers-schema@3.4.2': dependencies: - '@types/node': 20.12.7 + '@types/node': 20.12.8 '@types/qs@6.9.7': {} @@ -8411,13 +8419,13 @@ snapshots: '@types/serve-static@1.15.0': dependencies: '@types/mime': 3.0.4 - '@types/node': 20.12.7 + '@types/node': 20.12.8 '@types/stack-utils@2.0.2': {} '@types/stream-throttle@0.1.4': dependencies: - '@types/node': 20.12.7 + '@types/node': 20.12.8 '@types/string-similarity@4.0.2': {} @@ -8439,17 +8447,17 @@ snapshots: '@types/yauzl@2.10.2': dependencies: - '@types/node': 20.12.7 + '@types/node': 20.12.8 optional: true - '@typescript-eslint/eslint-plugin@7.7.1(@typescript-eslint/parser@7.7.1(eslint@8.57.0)(typescript@5.4.5))(eslint@8.57.0)(typescript@5.4.5)': + '@typescript-eslint/eslint-plugin@7.8.0(@typescript-eslint/parser@7.8.0(eslint@8.57.0)(typescript@5.4.5))(eslint@8.57.0)(typescript@5.4.5)': dependencies: '@eslint-community/regexpp': 4.10.0 - '@typescript-eslint/parser': 7.7.1(eslint@8.57.0)(typescript@5.4.5) - '@typescript-eslint/scope-manager': 7.7.1 - '@typescript-eslint/type-utils': 7.7.1(eslint@8.57.0)(typescript@5.4.5) - '@typescript-eslint/utils': 7.7.1(eslint@8.57.0)(typescript@5.4.5) - '@typescript-eslint/visitor-keys': 7.7.1 + '@typescript-eslint/parser': 7.8.0(eslint@8.57.0)(typescript@5.4.5) + '@typescript-eslint/scope-manager': 7.8.0 + '@typescript-eslint/type-utils': 7.8.0(eslint@8.57.0)(typescript@5.4.5) + '@typescript-eslint/utils': 7.8.0(eslint@8.57.0)(typescript@5.4.5) + '@typescript-eslint/visitor-keys': 7.8.0 debug: 4.3.4 eslint: 8.57.0 graphemer: 1.4.0 @@ -8462,12 +8470,12 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@7.7.1(eslint@8.57.0)(typescript@5.4.5)': + '@typescript-eslint/parser@7.8.0(eslint@8.57.0)(typescript@5.4.5)': dependencies: - '@typescript-eslint/scope-manager': 7.7.1 - '@typescript-eslint/types': 7.7.1 - '@typescript-eslint/typescript-estree': 7.7.1(typescript@5.4.5) - '@typescript-eslint/visitor-keys': 7.7.1 + '@typescript-eslint/scope-manager': 7.8.0 + '@typescript-eslint/types': 7.8.0 + '@typescript-eslint/typescript-estree': 7.8.0(typescript@5.4.5) + '@typescript-eslint/visitor-keys': 7.8.0 debug: 4.3.4 eslint: 8.57.0 optionalDependencies: @@ -8475,15 +8483,15 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/scope-manager@7.7.1': + '@typescript-eslint/scope-manager@7.8.0': dependencies: - '@typescript-eslint/types': 7.7.1 - '@typescript-eslint/visitor-keys': 7.7.1 + '@typescript-eslint/types': 7.8.0 + '@typescript-eslint/visitor-keys': 7.8.0 - '@typescript-eslint/type-utils@7.7.1(eslint@8.57.0)(typescript@5.4.5)': + '@typescript-eslint/type-utils@7.8.0(eslint@8.57.0)(typescript@5.4.5)': dependencies: - '@typescript-eslint/typescript-estree': 7.7.1(typescript@5.4.5) - '@typescript-eslint/utils': 7.7.1(eslint@8.57.0)(typescript@5.4.5) + '@typescript-eslint/typescript-estree': 7.8.0(typescript@5.4.5) + '@typescript-eslint/utils': 7.8.0(eslint@8.57.0)(typescript@5.4.5) debug: 4.3.4 eslint: 8.57.0 ts-api-utils: 1.3.0(typescript@5.4.5) @@ -8492,12 +8500,12 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/types@7.7.1': {} + '@typescript-eslint/types@7.8.0': {} - '@typescript-eslint/typescript-estree@7.7.1(typescript@5.4.5)': + '@typescript-eslint/typescript-estree@7.8.0(typescript@5.4.5)': dependencies: - '@typescript-eslint/types': 7.7.1 - '@typescript-eslint/visitor-keys': 7.7.1 + '@typescript-eslint/types': 7.8.0 + '@typescript-eslint/visitor-keys': 7.8.0 debug: 4.3.4 globby: 11.1.0 is-glob: 4.0.3 @@ -8509,23 +8517,23 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@7.7.1(eslint@8.57.0)(typescript@5.4.5)': + '@typescript-eslint/utils@7.8.0(eslint@8.57.0)(typescript@5.4.5)': dependencies: '@eslint-community/eslint-utils': 4.4.0(eslint@8.57.0) '@types/json-schema': 7.0.15 '@types/semver': 7.5.8 - '@typescript-eslint/scope-manager': 7.7.1 - '@typescript-eslint/types': 7.7.1 - '@typescript-eslint/typescript-estree': 7.7.1(typescript@5.4.5) + '@typescript-eslint/scope-manager': 7.8.0 + '@typescript-eslint/types': 7.8.0 + '@typescript-eslint/typescript-estree': 7.8.0(typescript@5.4.5) eslint: 8.57.0 semver: 7.6.0 transitivePeerDependencies: - supports-color - typescript - '@typescript-eslint/visitor-keys@7.7.1': + '@typescript-eslint/visitor-keys@7.8.0': dependencies: - '@typescript-eslint/types': 7.7.1 + '@typescript-eslint/types': 7.8.0 eslint-visitor-keys: 3.4.3 '@ungap/structured-clone@1.2.0': {} @@ -9003,7 +9011,7 @@ snapshots: - encoding - supports-color - better-sqlite3@9.5.0: + better-sqlite3@9.6.0: dependencies: bindings: 1.5.0 prebuild-install: 7.1.1 @@ -9401,13 +9409,13 @@ snapshots: optionalDependencies: typescript: 5.3.3 - create-jest@29.7.0(@types/node@20.12.7)(ts-node@10.9.2(@types/node@20.12.7)(typescript@5.4.5)): + create-jest@29.7.0(@types/node@20.12.8)(ts-node@10.9.2(@types/node@20.12.8)(typescript@5.4.5)): dependencies: '@jest/types': 29.6.3 chalk: 4.1.2 exit: 0.1.2 graceful-fs: 4.2.10 - jest-config: 29.7.0(@types/node@20.12.7)(ts-node@10.9.2(@types/node@20.12.7)(typescript@5.4.5)) + jest-config: 29.7.0(@types/node@20.12.8)(ts-node@10.9.2(@types/node@20.12.8)(typescript@5.4.5)) jest-util: 29.7.0 prompts: 2.4.2 transitivePeerDependencies: @@ -9608,7 +9616,7 @@ snapshots: dependencies: '@types/cookie': 0.4.1 '@types/cors': 2.8.15 - '@types/node': 20.12.7 + '@types/node': 20.12.8 accepts: 1.3.8 base64id: 2.0.0 cookie: 0.4.1 @@ -9739,17 +9747,17 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-module-utils@2.8.0(@typescript-eslint/parser@7.7.1(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint@8.57.0): + eslint-module-utils@2.8.0(@typescript-eslint/parser@7.8.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint@8.57.0): dependencies: debug: 3.2.7 optionalDependencies: - '@typescript-eslint/parser': 7.7.1(eslint@8.57.0)(typescript@5.4.5) + '@typescript-eslint/parser': 7.8.0(eslint@8.57.0)(typescript@5.4.5) eslint: 8.57.0 eslint-import-resolver-node: 0.3.9 transitivePeerDependencies: - supports-color - eslint-plugin-import@2.29.1(@typescript-eslint/parser@7.7.1(eslint@8.57.0)(typescript@5.4.5))(eslint@8.57.0): + eslint-plugin-import@2.29.1(@typescript-eslint/parser@7.8.0(eslint@8.57.0)(typescript@5.4.5))(eslint@8.57.0): dependencies: array-includes: 3.1.7 array.prototype.findlastindex: 1.2.3 @@ -9759,7 +9767,7 @@ snapshots: doctrine: 2.1.0 eslint: 8.57.0 eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.8.0(@typescript-eslint/parser@7.7.1(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint@8.57.0) + eslint-module-utils: 2.8.0(@typescript-eslint/parser@7.8.0(eslint@8.57.0)(typescript@5.4.5))(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 @@ -9770,7 +9778,7 @@ snapshots: semver: 6.3.1 tsconfig-paths: 3.15.0 optionalDependencies: - '@typescript-eslint/parser': 7.7.1(eslint@8.57.0)(typescript@5.4.5) + '@typescript-eslint/parser': 7.8.0(eslint@8.57.0)(typescript@5.4.5) transitivePeerDependencies: - eslint-import-resolver-typescript - eslint-import-resolver-webpack @@ -9786,6 +9794,10 @@ snapshots: '@types/eslint': 8.4.6 eslint-config-prettier: 9.1.0(eslint@8.57.0) + eslint-plugin-simple-import-sort@12.1.0(eslint@8.57.0): + dependencies: + eslint: 8.57.0 + eslint-scope@5.1.1: dependencies: esrecurse: 4.3.0 @@ -10715,7 +10727,7 @@ snapshots: '@jest/expect': 29.7.0 '@jest/test-result': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 20.12.7 + '@types/node': 20.12.8 chalk: 4.1.2 co: 4.6.0 dedent: 1.5.1 @@ -10735,16 +10747,16 @@ snapshots: - babel-plugin-macros - supports-color - jest-cli@29.7.0(@types/node@20.12.7)(ts-node@10.9.2(@types/node@20.12.7)(typescript@5.4.5)): + jest-cli@29.7.0(@types/node@20.12.8)(ts-node@10.9.2(@types/node@20.12.8)(typescript@5.4.5)): dependencies: - '@jest/core': 29.7.0(ts-node@10.9.2(@types/node@20.12.7)(typescript@5.4.5)) + '@jest/core': 29.7.0(ts-node@10.9.2(@types/node@20.12.8)(typescript@5.4.5)) '@jest/test-result': 29.7.0 '@jest/types': 29.6.3 chalk: 4.1.2 - create-jest: 29.7.0(@types/node@20.12.7)(ts-node@10.9.2(@types/node@20.12.7)(typescript@5.4.5)) + create-jest: 29.7.0(@types/node@20.12.8)(ts-node@10.9.2(@types/node@20.12.8)(typescript@5.4.5)) exit: 0.1.2 import-local: 3.1.0 - jest-config: 29.7.0(@types/node@20.12.7)(ts-node@10.9.2(@types/node@20.12.7)(typescript@5.4.5)) + jest-config: 29.7.0(@types/node@20.12.8)(ts-node@10.9.2(@types/node@20.12.8)(typescript@5.4.5)) jest-util: 29.7.0 jest-validate: 29.7.0 yargs: 17.6.2 @@ -10754,7 +10766,7 @@ snapshots: - supports-color - ts-node - jest-config@29.7.0(@types/node@20.12.7)(ts-node@10.9.2(@types/node@20.12.7)(typescript@5.4.5)): + jest-config@29.7.0(@types/node@20.12.8)(ts-node@10.9.2(@types/node@20.12.8)(typescript@5.4.5)): dependencies: '@babel/core': 7.23.9 '@jest/test-sequencer': 29.7.0 @@ -10779,8 +10791,8 @@ snapshots: slash: 3.0.0 strip-json-comments: 3.1.1 optionalDependencies: - '@types/node': 20.12.7 - ts-node: 10.9.2(@types/node@20.12.7)(typescript@5.4.5) + '@types/node': 20.12.8 + ts-node: 10.9.2(@types/node@20.12.8)(typescript@5.4.5) transitivePeerDependencies: - babel-plugin-macros - supports-color @@ -10809,7 +10821,7 @@ snapshots: '@jest/environment': 29.7.0 '@jest/fake-timers': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 20.12.7 + '@types/node': 20.12.8 jest-mock: 29.7.0 jest-util: 29.7.0 @@ -10819,7 +10831,7 @@ snapshots: dependencies: '@jest/types': 29.6.3 '@types/graceful-fs': 4.1.8 - '@types/node': 20.12.7 + '@types/node': 20.12.8 anymatch: 3.1.2 fb-watchman: 2.0.2 graceful-fs: 4.2.10 @@ -10858,7 +10870,7 @@ snapshots: jest-mock@29.7.0: dependencies: '@jest/types': 29.6.3 - '@types/node': 20.12.7 + '@types/node': 20.12.8 jest-util: 29.7.0 jest-pnp-resolver@1.2.3(jest-resolve@29.7.0): @@ -10893,7 +10905,7 @@ snapshots: '@jest/test-result': 29.7.0 '@jest/transform': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 20.12.7 + '@types/node': 20.12.8 chalk: 4.1.2 emittery: 0.13.1 graceful-fs: 4.2.10 @@ -10921,7 +10933,7 @@ snapshots: '@jest/test-result': 29.7.0 '@jest/transform': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 20.12.7 + '@types/node': 20.12.8 chalk: 4.1.2 cjs-module-lexer: 1.2.3 collect-v8-coverage: 1.0.2 @@ -10967,7 +10979,7 @@ snapshots: jest-util@29.7.0: dependencies: '@jest/types': 29.6.3 - '@types/node': 20.12.7 + '@types/node': 20.12.8 chalk: 4.1.2 ci-info: 3.9.0 graceful-fs: 4.2.10 @@ -10986,7 +10998,7 @@ snapshots: dependencies: '@jest/test-result': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 20.12.7 + '@types/node': 20.12.8 ansi-escapes: 4.3.2 chalk: 4.1.2 emittery: 0.13.1 @@ -10995,23 +11007,23 @@ snapshots: jest-worker@27.5.1: dependencies: - '@types/node': 20.12.7 + '@types/node': 20.12.8 merge-stream: 2.0.0 supports-color: 8.1.1 jest-worker@29.7.0: dependencies: - '@types/node': 20.12.7 + '@types/node': 20.12.8 jest-util: 29.7.0 merge-stream: 2.0.0 supports-color: 8.1.1 - jest@29.7.0(@types/node@20.12.7)(ts-node@10.9.2(@types/node@20.12.7)(typescript@5.4.5)): + jest@29.7.0(@types/node@20.12.8)(ts-node@10.9.2(@types/node@20.12.8)(typescript@5.4.5)): dependencies: - '@jest/core': 29.7.0(ts-node@10.9.2(@types/node@20.12.7)(typescript@5.4.5)) + '@jest/core': 29.7.0(ts-node@10.9.2(@types/node@20.12.8)(typescript@5.4.5)) '@jest/types': 29.6.3 import-local: 3.1.0 - jest-cli: 29.7.0(@types/node@20.12.7)(ts-node@10.9.2(@types/node@20.12.7)(typescript@5.4.5)) + jest-cli: 29.7.0(@types/node@20.12.8)(ts-node@10.9.2(@types/node@20.12.8)(typescript@5.4.5)) transitivePeerDependencies: - '@types/node' - babel-plugin-macros @@ -11611,9 +11623,9 @@ snapshots: fast-safe-stringify: 2.1.1 winston: 3.13.0 - ? nestjs-asyncapi@1.3.0(@nestjs/common@10.3.8(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.3.8(@nestjs/common@10.3.8(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.3.8)(@nestjs/websockets@10.3.8)(encoding@0.1.13)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/swagger@7.3.1(@nestjs/common@10.3.8(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.3.8(@nestjs/common@10.3.8(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.3.8)(@nestjs/websockets@10.3.8)(encoding@0.1.13)(reflect-metadata@0.2.2)(rxjs@7.8.1))(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2))(@nestjs/websockets@10.3.8(@nestjs/common@10.3.8(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.3.8)(@nestjs/platform-socket.io@10.3.8)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@types/babel__core@7.20.3)(@types/node@20.12.7)(encoding@0.1.13) + ? nestjs-asyncapi@1.3.0(@nestjs/common@10.3.8(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.3.8(@nestjs/common@10.3.8(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.3.8)(@nestjs/websockets@10.3.8)(encoding@0.1.13)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/swagger@7.3.1(@nestjs/common@10.3.8(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.3.8(@nestjs/common@10.3.8(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.3.8)(@nestjs/websockets@10.3.8)(encoding@0.1.13)(reflect-metadata@0.2.2)(rxjs@7.8.1))(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2))(@nestjs/websockets@10.3.8(@nestjs/common@10.3.8(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.3.8)(@nestjs/platform-socket.io@10.3.8)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@types/babel__core@7.20.3)(@types/node@20.12.8)(encoding@0.1.13) : dependencies: - '@asyncapi/generator': 1.13.1(@types/babel__core@7.20.3)(@types/node@20.12.7)(encoding@0.1.13) + '@asyncapi/generator': 1.13.1(@types/babel__core@7.20.3)(@types/node@20.12.8)(encoding@0.1.13) '@asyncapi/html-template': 0.28.4(encoding@0.1.13) '@nestjs/common': 10.3.8(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1) '@nestjs/core': 10.3.8(@nestjs/common@10.3.8(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.3.8)(@nestjs/websockets@10.3.8)(encoding@0.1.13)(reflect-metadata@0.2.2)(rxjs@7.8.1) @@ -11634,14 +11646,14 @@ snapshots: - supports-color - utf-8-validate - nestjs-paginate@8.6.2(@nestjs/common@10.3.8(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/swagger@7.3.1(@nestjs/common@10.3.8(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.3.8(@nestjs/common@10.3.8(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.3.8)(@nestjs/websockets@10.3.8)(encoding@0.1.13)(reflect-metadata@0.2.2)(rxjs@7.8.1))(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2))(express@4.19.2)(fastify@4.26.2)(typeorm@0.3.20(better-sqlite3@9.5.0)(pg@8.11.5)(ts-node@10.9.2(@types/node@20.12.7)(typescript@5.4.5))): + nestjs-paginate@8.6.2(@nestjs/common@10.3.8(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/swagger@7.3.1(@nestjs/common@10.3.8(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.3.8(@nestjs/common@10.3.8(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.3.8)(@nestjs/websockets@10.3.8)(encoding@0.1.13)(reflect-metadata@0.2.2)(rxjs@7.8.1))(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2))(express@4.19.2)(fastify@4.26.2)(typeorm@0.3.20(better-sqlite3@9.6.0)(pg@8.11.5)(ts-node@10.9.2(@types/node@20.12.8)(typescript@5.4.5))): dependencies: '@nestjs/common': 10.3.8(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1) '@nestjs/swagger': 7.3.1(@nestjs/common@10.3.8(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.3.8(@nestjs/common@10.3.8(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.3.8)(@nestjs/websockets@10.3.8)(encoding@0.1.13)(reflect-metadata@0.2.2)(rxjs@7.8.1))(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2) express: 4.19.2 fastify: 4.26.2 lodash: 4.17.21 - typeorm: 0.3.20(better-sqlite3@9.5.0)(pg@8.11.5)(ts-node@10.9.2(@types/node@20.12.7)(typescript@5.4.5)) + typeorm: 0.3.20(better-sqlite3@9.6.0)(pg@8.11.5)(ts-node@10.9.2(@types/node@20.12.8)(typescript@5.4.5)) nimma@0.2.2: dependencies: @@ -12967,11 +12979,11 @@ snapshots: dependencies: typescript: 5.4.5 - ts-jest@29.1.2(@babel/core@7.12.9)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.12.9))(jest@29.7.0(@types/node@20.12.7)(ts-node@10.9.2(@types/node@20.12.7)(typescript@5.4.5)))(typescript@5.4.5): + ts-jest@29.1.2(@babel/core@7.12.9)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.12.9))(jest@29.7.0(@types/node@20.12.8)(ts-node@10.9.2(@types/node@20.12.8)(typescript@5.4.5)))(typescript@5.4.5): dependencies: bs-logger: 0.2.6 fast-json-stable-stringify: 2.1.0 - jest: 29.7.0(@types/node@20.12.7)(ts-node@10.9.2(@types/node@20.12.7)(typescript@5.4.5)) + jest: 29.7.0(@types/node@20.12.8)(ts-node@10.9.2(@types/node@20.12.8)(typescript@5.4.5)) jest-util: 29.7.0 json5: 2.2.3 lodash.memoize: 4.1.2 @@ -12984,14 +12996,14 @@ snapshots: '@jest/types': 29.6.3 babel-jest: 29.7.0(@babel/core@7.12.9) - ts-node@10.9.2(@types/node@20.12.7)(typescript@4.9.5): + ts-node@10.9.2(@types/node@20.12.8)(typescript@4.9.5): dependencies: '@cspotcode/source-map-support': 0.8.1 '@tsconfig/node10': 1.0.9 '@tsconfig/node12': 1.0.11 '@tsconfig/node14': 1.0.3 '@tsconfig/node16': 1.0.3 - '@types/node': 20.12.7 + '@types/node': 20.12.8 acorn: 8.11.3 acorn-walk: 8.2.0 arg: 4.1.3 @@ -13002,14 +13014,14 @@ snapshots: v8-compile-cache-lib: 3.0.1 yn: 3.1.1 - ts-node@10.9.2(@types/node@20.12.7)(typescript@5.4.5): + ts-node@10.9.2(@types/node@20.12.8)(typescript@5.4.5): dependencies: '@cspotcode/source-map-support': 0.8.1 '@tsconfig/node10': 1.0.9 '@tsconfig/node12': 1.0.11 '@tsconfig/node14': 1.0.3 '@tsconfig/node16': 1.0.3 - '@types/node': 20.12.7 + '@types/node': 20.12.8 acorn: 8.11.3 acorn-walk: 8.2.0 arg: 4.1.3 @@ -13097,11 +13109,11 @@ snapshots: typedarray@0.0.6: {} - typeorm-naming-strategies@4.1.0(typeorm@0.3.20(better-sqlite3@9.5.0)(pg@8.11.5)(ts-node@10.9.2(@types/node@20.12.7)(typescript@5.4.5))): + typeorm-naming-strategies@4.1.0(typeorm@0.3.20(better-sqlite3@9.6.0)(pg@8.11.5)(ts-node@10.9.2(@types/node@20.12.8)(typescript@5.4.5))): dependencies: - typeorm: 0.3.20(better-sqlite3@9.5.0)(pg@8.11.5)(ts-node@10.9.2(@types/node@20.12.7)(typescript@5.4.5)) + typeorm: 0.3.20(better-sqlite3@9.6.0)(pg@8.11.5)(ts-node@10.9.2(@types/node@20.12.8)(typescript@5.4.5)) - typeorm@0.3.20(better-sqlite3@9.5.0)(pg@8.11.5)(ts-node@10.9.2(@types/node@20.12.7)(typescript@5.4.5)): + typeorm@0.3.20(better-sqlite3@9.6.0)(pg@8.11.5)(ts-node@10.9.2(@types/node@20.12.8)(typescript@5.4.5)): dependencies: '@sqltools/formatter': 1.2.5 app-root-path: 3.1.0 @@ -13119,9 +13131,9 @@ snapshots: uuid: 9.0.1 yargs: 17.6.2 optionalDependencies: - better-sqlite3: 9.5.0 + better-sqlite3: 9.6.0 pg: 8.11.5 - ts-node: 10.9.2(@types/node@20.12.7)(typescript@5.4.5) + ts-node: 10.9.2(@types/node@20.12.8)(typescript@5.4.5) transitivePeerDependencies: - supports-color diff --git a/src/app.module.ts b/src/app.module.ts index 792094b4..69313604 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -1,26 +1,27 @@ -import { PluginModule } from "./modules/plugin/plugin.module"; +import { Module } from "@nestjs/common"; +import { APP_INTERCEPTOR } from "@nestjs/core"; +import { EventEmitterModule } from "@nestjs/event-emitter"; +import { ScheduleModule } from "@nestjs/schedule"; + +import { DisableApiIfInterceptor } from "./interceptors/disable-api-if.interceptor"; import { AdminModule } from "./modules/admin/admin.module"; -import { DatabaseModule } from "./modules/database/database.module"; -import { FilesModule } from "./modules/files/files.module"; import { BoxartsModule } from "./modules/boxarts/boxarts.module"; +import { DatabaseModule } from "./modules/database/database.module"; import { DevelopersModule } from "./modules/developers/developers.module"; +import { FilesModule } from "./modules/files/files.module"; +import { GamesModule } from "./modules/games/games.module"; +import { GarbageCollectionModule } from "./modules/garbage-collection/garbage-collection.module"; +import { GenresModule } from "./modules/genres/genres.module"; +import { DefaultStrategy } from "./modules/guards/basic-auth.strategy"; +import { HealthModule } from "./modules/health/health.module"; +import { ImagesModule } from "./modules/images/images.module"; +import { PluginModule } from "./modules/plugins/plugin.module"; +import { ProgressModule } from "./modules/progresses/progress.module"; +import { RawgModule } from "./modules/providers/rawg/rawg.module"; import { PublishersModule } from "./modules/publishers/publishers.module"; import { StoresModule } from "./modules/stores/stores.module"; -import { UsersModule } from "./modules/users/users.module"; import { TagsModule } from "./modules/tags/tags.module"; -import { ProgressModule } from "./modules/progress/progress.module"; -import { ImagesModule } from "./modules/images/images.module"; -import { HealthModule } from "./modules/health/health.module"; -import { GenresModule } from "./modules/genres/genres.module"; -import { GamesModule } from "./modules/games/games.module"; -import { Module } from "@nestjs/common"; -import { ScheduleModule } from "@nestjs/schedule"; -import { RawgModule } from "./modules/providers/rawg/rawg.module"; -import { DefaultStrategy } from "./modules/guards/basic-auth.strategy"; -import { GarbageCollectionModule } from "./modules/garbage-collection/garbage-collection.module"; -import { EventEmitterModule } from "@nestjs/event-emitter"; -import { APP_INTERCEPTOR } from "@nestjs/core"; -import { DisableApiIfInterceptor } from "./interceptors/disable-api-if.interceptor"; +import { UsersModule } from "./modules/users/users.module"; @Module({ imports: [ diff --git a/src/configuration.ts b/src/configuration.ts index 7dc27391..845646ef 100644 --- a/src/configuration.ts +++ b/src/configuration.ts @@ -1,5 +1,5 @@ -import globals from "./globals"; import packageJson from "../package.json"; +import globals from "./globals"; function parseBooleanEnvVariable( environmentVariable: string, @@ -38,6 +38,17 @@ function parseList( : defaultList; } +function parseNumber( + environmentVariable: string, + defaultValue?: number, +): number | undefined { + const number = Number(environmentVariable); + if (isNaN(number) || number < 0 || number > Number.MAX_SAFE_INTEGER) { + return defaultValue ?? undefined; + } + return number; +} + function parseNumberList( environmentVariable: string, defaultList: number[] = [], @@ -63,7 +74,7 @@ function parseKibibytesToBytes( const configuration = { SERVER: { - PORT: Number(process.env.SERVER_PORT) || 8080, + PORT: parseNumber(process.env.SERVER_PORT, 8080), VERSION: process.env.npm_package_version || packageJson.version, DEMO_MODE_ENABLED: parseBooleanEnvVariable( process.env.SERVER_DEMO_MODE_ENABLED, @@ -106,17 +117,29 @@ const configuration = { DB: { SYSTEM: process.env.DB_SYSTEM || "POSTGRESQL", HOST: process.env.DB_HOST || "localhost", - PORT: Number(process.env.DB_PORT) || 5432, + PORT: parseNumber(process.env.DB_PORT, 5432), USERNAME: process.env.DB_USERNAME || "default", PASSWORD: process.env.DB_PASSWORD || "default", DATABASE: process.env.DB_DATABASE || "gamevault", DEBUG: parseBooleanEnvVariable(process.env.DB_DEBUG), SYNCHRONIZE: parseBooleanEnvVariable(process.env.DB_SYNCHRONIZE), + TLS: { + ENABLED: parseBooleanEnvVariable(process.env.DB_TLS_ENABLED), + REJECT_UNAUTHORIZED_ENABLED: parseBooleanEnvVariable( + process.env.DB_TLS_REJECT_UNAUTHORIZED_ENABLED, + ), + KEY_PATH: parsePath(process.env.DB_TLS_KEY_PATH, ""), + CERTIFICATE_PATH: parsePath(process.env.DB_TLS_CERTIFICATE_PATH, ""), + CA_CERTIFICATE_PATH: parsePath( + process.env.DB_TLS_CA_CERTIFICATE_PATH, + "", + ), + }, } as const, RAWG_API: { URL: process.env.RAWG_API_URL || "https://api.rawg.io/api", KEY: process.env.RAWG_API_KEY || "", - CACHE_DAYS: Number(process.env.RAWG_API_CACHE_DAYS) || 30, + CACHE_DAYS: parseNumber(process.env.RAWG_API_CACHE_DAYS, 30), INCLUDED_STORES: parseNumberList( process.env.RAWG_API_INCLUDED_STORES, globals.DEFAULT_INCLUDED_RAWG_STORES, @@ -136,8 +159,10 @@ const configuration = { ), } as const, GAMES: { - INDEX_INTERVAL_IN_MINUTES: - Number(process.env.GAMES_INDEX_INTERVAL_IN_MINUTES) || 60, + INDEX_INTERVAL_IN_MINUTES: parseNumber( + process.env.GAMES_INDEX_INTERVAL_IN_MINUTES, + 60, + ), SUPPORTED_FILE_FORMATS: parseList( process.env.GAMES_SUPPORTED_FILE_FORMATS, globals.SUPPORTED_FILE_FORMATS, @@ -149,16 +174,20 @@ const configuration = { } as const, IMAGE: { MAX_SIZE_IN_KB: - Number(process.env.IMAGE_MAX_SIZE_IN_KB) * 1000 || 10_000_000, - GOOGLE_API_RATE_LIMIT_COOLDOWN_IN_HOURS: - Number(process.env.IMAGE_GOOGLE_API_RATE_LIMIT_COOLDOWN_IN_HOURS) || 24, + parseNumber(process.env.IMAGE_MAX_SIZE_IN_KB, 1000) * 10_000, + GOOGLE_API_RATE_LIMIT_COOLDOWN_IN_HOURS: parseNumber( + process.env.IMAGE_GOOGLE_API_RATE_LIMIT_COOLDOWN_IN_HOURS, + 24, + ), SUPPORTED_IMAGE_FORMATS: parseList( process.env.GAMES_SUPPORTED_IMAGE_FORMATS, globals.SUPPORTED_IMAGE_FORMATS, ), GC_DISABLED: parseBooleanEnvVariable(process.env.IMAGE_GC_DISABLED, false), - GC_INTERVAL_IN_MINUTES: - Number(process.env.IMAGE_GC_INTERVAL_IN_MINUTES) || 60, + GC_INTERVAL_IN_MINUTES: parseNumber( + process.env.IMAGE_GC_INTERVAL_IN_MINUTES, + 24, + ), } as const, TESTING: { AUTHENTICATION_DISABLED: parseBooleanEnvVariable( diff --git a/src/decorators/conditional-registration.decorator.ts b/src/decorators/conditional-registration.decorator.ts index c3754892..6453166f 100644 --- a/src/decorators/conditional-registration.decorator.ts +++ b/src/decorators/conditional-registration.decorator.ts @@ -1,4 +1,5 @@ import { noop } from "rxjs"; + import configuration from "../configuration"; import { Public } from "./public.decorator"; diff --git a/src/decorators/disable-api-if.decorator.ts b/src/decorators/disable-api-if.decorator.ts index 5de70b6b..777d8a8e 100644 --- a/src/decorators/disable-api-if.decorator.ts +++ b/src/decorators/disable-api-if.decorator.ts @@ -1,4 +1,5 @@ import { SetMetadata } from "@nestjs/common"; + export const DISABLE_API_IF_KEY = "disableApiIf"; export const DisableApiIf = (disabled: boolean) => SetMetadata(DISABLE_API_IF_KEY, disabled); diff --git a/src/decorators/minimum-role.decorator.ts b/src/decorators/minimum-role.decorator.ts index 6610dc9d..2dffa03f 100644 --- a/src/decorators/minimum-role.decorator.ts +++ b/src/decorators/minimum-role.decorator.ts @@ -1,4 +1,5 @@ import { SetMetadata } from "@nestjs/common"; + import { Role } from "../modules/users/models/role.enum"; export const MINIMUM_ROLE_KEY = "minimumRole"; diff --git a/src/decorators/public.decorator.ts b/src/decorators/public.decorator.ts index 253c06d1..e71a8574 100644 --- a/src/decorators/public.decorator.ts +++ b/src/decorators/public.decorator.ts @@ -1,2 +1,3 @@ import { SetMetadata } from "@nestjs/common"; + export const Public = () => SetMetadata("public", true); diff --git a/src/modules/pagination/all-filters.filter.ts b/src/filters/all-filters.filter.ts similarity index 100% rename from src/modules/pagination/all-filters.filter.ts rename to src/filters/all-filters.filter.ts diff --git a/src/filters/http-exception.filter.ts b/src/filters/http-exception.filter.ts index 3193a060..b51496e2 100644 --- a/src/filters/http-exception.filter.ts +++ b/src/filters/http-exception.filter.ts @@ -1,11 +1,10 @@ import { - ExceptionFilter, + ArgumentsHost, Catch, + ExceptionFilter, HttpException, - ArgumentsHost, Logger, } from "@nestjs/common"; - import { Request, Response } from "express"; @Catch() diff --git a/src/globals.ts b/src/globals.ts index a5697a3d..984e1406 100644 --- a/src/globals.ts +++ b/src/globals.ts @@ -1,6 +1,34 @@ +import { applyDecorators, Type } from "@nestjs/common"; +import { ApiExtraModels, ApiOkResponse, getSchemaPath } from "@nestjs/swagger"; + +import { PaginatedEntity } from "./modules/database/models/paginated-entity.model"; import { RawgPlatform } from "./modules/providers/rawg/models/platforms"; import { RawgStore } from "./modules/providers/rawg/models/stores"; +export const ApiOkResponsePaginated = >( + dataDto: DataDto, +) => + applyDecorators( + ApiExtraModels(PaginatedEntity, dataDto), + ApiOkResponse({ + schema: { + required: ["data", "meta", "links"], + allOf: [ + { + properties: { + data: { + description: "paginated list of entities", + type: "array", + items: { $ref: getSchemaPath(dataDto) }, + }, + }, + }, + { $ref: getSchemaPath(PaginatedEntity) }, + ], + }, + }), + ); + export default { get SUPPORTED_FILE_FORMATS() { return [...this.ARCHIVE_FORMATS, ...this.EXECUTABLE_FORMATS]; diff --git a/src/interceptors/disable-api-if.interceptor.ts b/src/interceptors/disable-api-if.interceptor.ts index d1d18813..ad3c40e7 100644 --- a/src/interceptors/disable-api-if.interceptor.ts +++ b/src/interceptors/disable-api-if.interceptor.ts @@ -1,13 +1,14 @@ import { - Injectable, - NestInterceptor, - ExecutionContext, CallHandler, + ExecutionContext, + Injectable, MethodNotAllowedException, + NestInterceptor, } from "@nestjs/common"; +import { Reflector } from "@nestjs/core"; import { Observable } from "rxjs"; import { map } from "rxjs/operators"; -import { Reflector } from "@nestjs/core"; + import { DISABLE_API_IF_KEY } from "../decorators/disable-api-if.decorator"; @Injectable() diff --git a/src/logging.ts b/src/logging.ts index c922c00d..501d83dd 100644 --- a/src/logging.ts +++ b/src/logging.ts @@ -26,7 +26,10 @@ if (configuration.SERVER.LOG_LEVEL != "off") { ); } -if (configuration.SERVER.LOG_FILES_ENABLED) { +if ( + configuration.SERVER.LOG_FILES_ENABLED && + !configuration.TESTING.MOCK_FILES +) { transports.push( new DailyRotateFile({ level: "debug", diff --git a/src/main.ts b/src/main.ts index e1aae711..efcf6ef2 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,22 +1,25 @@ +/* eslint-disable */ import * as dotenv from "dotenv"; dotenv.config(); +/* eslint-enable */ import { ValidationPipe } from "@nestjs/common"; import { NestFactory, Reflector } from "@nestjs/core"; import { NestExpressApplication } from "@nestjs/platform-express"; +import { DocumentBuilder, SwaggerModule } from "@nestjs/swagger"; +import compression from "compression"; //import { AsyncApiDocumentBuilder, AsyncApiModule } from "nestjs-asyncapi"; import cookieparser from "cookie-parser"; -import compression from "compression"; import helmet from "helmet"; import morgan from "morgan"; + import { AppModule } from "./app.module"; import configuration, { getCensoredConfiguration } from "./configuration"; +import { LoggingExceptionFilter } from "./filters/http-exception.filter"; import { default as logger, default as winston, stream } from "./logging"; -import { DocumentBuilder, SwaggerModule } from "@nestjs/swagger"; +import { ApiVersionMiddleware } from "./middleware/remove-api-version.middleware"; import { AuthenticationGuard } from "./modules/guards/authentication.guard"; import { AuthorizationGuard } from "./modules/guards/authorization.guard"; -import { LoggingExceptionFilter } from "./filters/http-exception.filter"; -import { ApiVersionMiddleware } from "./middleware/remove-api-version.middleware"; async function bootstrap(): Promise { const app = await NestFactory.create(AppModule, { diff --git a/src/middleware/remove-api-version.middleware.ts b/src/middleware/remove-api-version.middleware.ts index c1e6c4e2..ca9450bc 100644 --- a/src/middleware/remove-api-version.middleware.ts +++ b/src/middleware/remove-api-version.middleware.ts @@ -1,5 +1,5 @@ import { Injectable, NestMiddleware } from "@nestjs/common"; -import { Request, Response, NextFunction } from "express"; +import { NextFunction, Request, Response } from "express"; @Injectable() export class ApiVersionMiddleware implements NestMiddleware { diff --git a/src/modules/admin/admin.controller.ts b/src/modules/admin/admin.controller.ts index cdd9b354..d665ea7e 100644 --- a/src/modules/admin/admin.controller.ts +++ b/src/modules/admin/admin.controller.ts @@ -6,7 +6,7 @@ import { UploadedFile, UseInterceptors, } from "@nestjs/common"; -import { HealthService } from "../health/health.service"; +import { FileInterceptor } from "@nestjs/platform-express"; import { ApiBasicAuth, ApiHeader, @@ -14,11 +14,12 @@ import { ApiOperation, ApiTags, } from "@nestjs/swagger"; -import { Health } from "../health/models/health.model"; + 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"; +import { HealthService } from "../health/health.service"; +import { Health } from "../health/models/health.model"; +import { Role } from "../users/models/role.enum"; @ApiBasicAuth() @Controller("admin") diff --git a/src/modules/admin/admin.module.ts b/src/modules/admin/admin.module.ts index 611abc70..ce481ae8 100644 --- a/src/modules/admin/admin.module.ts +++ b/src/modules/admin/admin.module.ts @@ -1,8 +1,9 @@ +import { Module } from "@nestjs/common"; + import { DatabaseModule } from "../database/database.module"; import { DatabaseService } from "../database/database.service"; import { HealthModule } from "../health/health.module"; import { AdminController } from "./admin.controller"; -import { Module } from "@nestjs/common"; @Module({ imports: [HealthModule, DatabaseModule], diff --git a/src/modules/boxarts/boxarts.module.ts b/src/modules/boxarts/boxarts.module.ts index f9130f9e..16f99523 100644 --- a/src/modules/boxarts/boxarts.module.ts +++ b/src/modules/boxarts/boxarts.module.ts @@ -1,7 +1,8 @@ -import { Module, forwardRef } from "@nestjs/common"; -import { BoxArtsService } from "./boxarts.service"; +import { forwardRef, Module } from "@nestjs/common"; + import { GamesModule } from "../games/games.module"; import { ImagesModule } from "../images/images.module"; +import { BoxArtsService } from "./boxarts.service"; @Module({ imports: [forwardRef(() => GamesModule), ImagesModule], diff --git a/src/modules/boxarts/boxarts.service.ts b/src/modules/boxarts/boxarts.service.ts index c4d46aab..a9f1927f 100644 --- a/src/modules/boxarts/boxarts.service.ts +++ b/src/modules/boxarts/boxarts.service.ts @@ -1,9 +1,10 @@ import { forwardRef, Inject, Injectable, Logger } from "@nestjs/common"; import gis, { Result } from "async-g-i-s"; + import configuration from "../../configuration"; +import { Game } from "../games/game.entity"; import { GamesService } from "../games/games.service"; import { ImagesService } from "../images/images.service"; -import { Game } from "../games/game.entity"; @Injectable() export class BoxArtsService { diff --git a/src/modules/database/database.module.ts b/src/modules/database/database.module.ts index 7f7bb01a..5ffb03f2 100644 --- a/src/modules/database/database.module.ts +++ b/src/modules/database/database.module.ts @@ -1,8 +1,9 @@ -import { DatabaseService } from "./database.service"; import { Module } from "@nestjs/common"; import { TypeOrmModule } from "@nestjs/typeorm"; -import { getDatabaseConfiguration } from "./db_configuration"; + import configuration from "../../configuration"; +import { DatabaseService } from "./database.service"; +import { getDatabaseConfiguration } from "./db_configuration"; @Module({ imports: [ diff --git a/src/modules/database/database.service.ts b/src/modules/database/database.service.ts index e48223af..52e8bcfb 100644 --- a/src/modules/database/database.service.ts +++ b/src/modules/database/database.service.ts @@ -6,16 +6,17 @@ import { StreamableFile, UnauthorizedException, } from "@nestjs/common"; -import configuration from "../../configuration"; +import { exec } from "child_process"; +import { createReadStream, existsSync, statSync } from "fs"; +import { copyFile, writeFile } from "fs/promises"; +import mime from "mime"; +import path from "path"; +import filenameSanitizer from "sanitize-filename"; import { DataSource } from "typeorm"; import unidecode from "unidecode"; -import filenameSanitizer from "sanitize-filename"; -import path from "path"; -import { exec } from "child_process"; import { promisify } from "util"; -import mime from "mime"; -import { copyFile, writeFile } from "fs/promises"; -import { createReadStream, existsSync, statSync } from "fs"; + +import configuration from "../../configuration"; @Injectable() export class DatabaseService { diff --git a/src/modules/database/db_configuration.ts b/src/modules/database/db_configuration.ts index 4ac863b4..00e66254 100644 --- a/src/modules/database/db_configuration.ts +++ b/src/modules/database/db_configuration.ts @@ -1,9 +1,12 @@ import { TypeOrmModuleOptions } from "@nestjs/typeorm"; -import { SnakeNamingStrategy } from "typeorm-naming-strategies"; -import { PostgresConnectionOptions } from "typeorm/driver/postgres/PostgresConnectionOptions"; +import { readFileSync } from "fs"; +import pg from "pg"; +import { TlsOptions } from "tls"; import { BetterSqlite3ConnectionOptions } from "typeorm/driver/better-sqlite3/BetterSqlite3ConnectionOptions"; +import { PostgresConnectionOptions } from "typeorm/driver/postgres/PostgresConnectionOptions"; +import { SnakeNamingStrategy } from "typeorm-naming-strategies"; + import configuration from "../../configuration"; -import pg from "pg"; const baseConfig: TypeOrmModuleOptions = { autoLoadEntities: true, @@ -23,6 +26,7 @@ const postgresConfig: PostgresConnectionOptions = { password: configuration.DB.PASSWORD, database: configuration.DB.DATABASE, migrations: ["dist/src/modules/database/migrations/postgres/*.js"], + ssl: getPostgresTlsOptions(), }; const sqliteConfig: BetterSqlite3ConnectionOptions = { @@ -56,3 +60,21 @@ function preparePostgresConnector() { */ pg.defaults.parseInputDatesAsUTC = true; } + +function getPostgresTlsOptions(): TlsOptions { + if (!configuration.DB.TLS.ENABLED) { + return undefined; + } + return { + rejectUnauthorized: configuration.DB.TLS.REJECT_UNAUTHORIZED_ENABLED, + ca: configuration.DB.TLS.CA_CERTIFICATE_PATH + ? readFileSync(configuration.DB.TLS.CA_CERTIFICATE_PATH).toString() + : undefined, + key: configuration.DB.TLS.KEY_PATH + ? readFileSync(configuration.DB.TLS.KEY_PATH) + : undefined, + cert: configuration.DB.TLS.CERTIFICATE_PATH + ? readFileSync(configuration.DB.TLS.CERTIFICATE_PATH) + : undefined, + }; +} diff --git a/src/modules/database/migrations/postgres/1689984000000-add-game-type.ts b/src/modules/database/migrations/postgres/1689984000000-add-game-type.ts index 19ee6415..994a6b41 100644 --- a/src/modules/database/migrations/postgres/1689984000000-add-game-type.ts +++ b/src/modules/database/migrations/postgres/1689984000000-add-game-type.ts @@ -1,4 +1,5 @@ import { MigrationInterface, QueryRunner } from "typeorm"; + import { AddDirectPlay1689638400000 } from "./1689638400000-add-direct-play"; export class AddGameType1689984000000 implements MigrationInterface { diff --git a/src/modules/database/migrations/postgres/1695686400000-check-user-case-conflicts.ts b/src/modules/database/migrations/postgres/1695686400000-check-user-case-conflicts.ts index b5684b0a..df592c29 100644 --- a/src/modules/database/migrations/postgres/1695686400000-check-user-case-conflicts.ts +++ b/src/modules/database/migrations/postgres/1695686400000-check-user-case-conflicts.ts @@ -1,4 +1,5 @@ import { MigrationInterface, QueryRunner } from "typeorm"; + export class CheckUserCaseConflicts1695686400000 implements MigrationInterface { name?: string; transaction?: boolean; diff --git a/src/modules/database/migrations/postgres/1696967362000-delete-empty-progresses.ts b/src/modules/database/migrations/postgres/1696967362000-delete-empty-progresses.ts index 5d40b6cf..5f758be6 100644 --- a/src/modules/database/migrations/postgres/1696967362000-delete-empty-progresses.ts +++ b/src/modules/database/migrations/postgres/1696967362000-delete-empty-progresses.ts @@ -1,5 +1,6 @@ -import { MigrationInterface, QueryRunner } from "typeorm"; import { Logger } from "@nestjs/common"; +import { MigrationInterface, QueryRunner } from "typeorm"; + export class DeleteEmptyProgresses1696967362000 implements MigrationInterface { private readonly logger = new Logger(DeleteEmptyProgresses1696967362000.name); name?: string; diff --git a/src/modules/database/migrations/postgres/1714999717957-rawg-independence.ts b/src/modules/database/migrations/postgres/1714999717957-rawg-independence.ts new file mode 100644 index 00000000..53a051ff --- /dev/null +++ b/src/modules/database/migrations/postgres/1714999717957-rawg-independence.ts @@ -0,0 +1,137 @@ +import { MigrationInterface, QueryRunner } from "typeorm"; + +export class RawgIndependence1714999717957 implements MigrationInterface { + name = "RawgIndependence1714999717957"; + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + ALTER TABLE "bookmark" DROP CONSTRAINT "FK_90b8e2ed41d03127a4a9dbd8392" + `); + await queryRunner.query(` + ALTER TABLE "bookmark" DROP CONSTRAINT "FK_8990e34bae4dca1bed3c9ab14d7" + `); + await queryRunner.query(` + DROP INDEX "public"."IDX_90b8e2ed41d03127a4a9dbd839" + `); + await queryRunner.query(` + DROP INDEX "public"."IDX_8990e34bae4dca1bed3c9ab14d" + `); + await queryRunner.query(` + ALTER TABLE "developer" + ALTER COLUMN "rawg_id" DROP NOT NULL + `); + await queryRunner.query(` + ALTER TABLE "developer" DROP CONSTRAINT "UQ_5c0cd47a75116720223e43db853" + `); + await queryRunner.query(` + ALTER TABLE "genre" + ALTER COLUMN "rawg_id" DROP NOT NULL + `); + await queryRunner.query(` + ALTER TABLE "genre" DROP CONSTRAINT "UQ_672bce67ec8cb2d7755c158ad65" + `); + await queryRunner.query(` + ALTER TABLE "publisher" + ALTER COLUMN "rawg_id" DROP NOT NULL + `); + await queryRunner.query(` + ALTER TABLE "publisher" DROP CONSTRAINT "UQ_4a0539222ee1307f657f875003b" + `); + await queryRunner.query(` + ALTER TABLE "store" + ALTER COLUMN "rawg_id" DROP NOT NULL + `); + await queryRunner.query(` + ALTER TABLE "store" DROP CONSTRAINT "UQ_45c4541783f264043ec2a5864d6" + `); + await queryRunner.query(` + ALTER TABLE "tag" + ALTER COLUMN "rawg_id" DROP NOT NULL + `); + await queryRunner.query(` + ALTER TABLE "tag" DROP CONSTRAINT "UQ_289102542903593026bd16e4e1b" + `); + await queryRunner.query(` + CREATE INDEX "IDX_6f00464edf85ddfedbd2580842" ON "bookmark" ("gamevault_user_id") + `); + await queryRunner.query(` + ALTER TABLE "bookmark" + ADD CONSTRAINT "FK_6f00464edf85ddfedbd25808428" FOREIGN KEY ("gamevault_user_id") REFERENCES "gamevault_user"("id") ON DELETE CASCADE ON UPDATE CASCADE + `); + await queryRunner.query(` + ALTER TABLE "bookmark" + ADD CONSTRAINT "FK_99bd20f783c41fe05489a0aca5f" FOREIGN KEY ("game_id") REFERENCES "game"("id") ON DELETE NO ACTION ON UPDATE NO ACTION + `); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + ALTER TABLE "bookmark" DROP CONSTRAINT "FK_99bd20f783c41fe05489a0aca5f" + `); + await queryRunner.query(` + ALTER TABLE "bookmark" DROP CONSTRAINT "FK_6f00464edf85ddfedbd25808428" + `); + await queryRunner.query(` + DROP INDEX "public"."IDX_6f00464edf85ddfedbd2580842" + `); + await queryRunner.query(` + ALTER TABLE "tag" + ADD CONSTRAINT "UQ_289102542903593026bd16e4e1b" UNIQUE ("rawg_id") + `); + await queryRunner.query(` + ALTER TABLE "tag" + ALTER COLUMN "rawg_id" + SET NOT NULL + `); + await queryRunner.query(` + ALTER TABLE "store" + ADD CONSTRAINT "UQ_45c4541783f264043ec2a5864d6" UNIQUE ("rawg_id") + `); + await queryRunner.query(` + ALTER TABLE "store" + ALTER COLUMN "rawg_id" + SET NOT NULL + `); + await queryRunner.query(` + ALTER TABLE "publisher" + ADD CONSTRAINT "UQ_4a0539222ee1307f657f875003b" UNIQUE ("rawg_id") + `); + await queryRunner.query(` + ALTER TABLE "publisher" + ALTER COLUMN "rawg_id" + SET NOT NULL + `); + await queryRunner.query(` + ALTER TABLE "genre" + ADD CONSTRAINT "UQ_672bce67ec8cb2d7755c158ad65" UNIQUE ("rawg_id") + `); + await queryRunner.query(` + ALTER TABLE "genre" + ALTER COLUMN "rawg_id" + SET NOT NULL + `); + await queryRunner.query(` + ALTER TABLE "developer" + ADD CONSTRAINT "UQ_5c0cd47a75116720223e43db853" UNIQUE ("rawg_id") + `); + await queryRunner.query(` + ALTER TABLE "developer" + ALTER COLUMN "rawg_id" + SET NOT NULL + `); + await queryRunner.query(` + CREATE INDEX "IDX_8990e34bae4dca1bed3c9ab14d" ON "bookmark" ("game_id") + `); + await queryRunner.query(` + CREATE INDEX "IDX_90b8e2ed41d03127a4a9dbd839" ON "bookmark" ("gamevault_user_id") + `); + await queryRunner.query(` + ALTER TABLE "bookmark" + ADD CONSTRAINT "FK_8990e34bae4dca1bed3c9ab14d7" FOREIGN KEY ("game_id") REFERENCES "game"("id") ON DELETE NO ACTION ON UPDATE NO ACTION + `); + await queryRunner.query(` + ALTER TABLE "bookmark" + ADD CONSTRAINT "FK_90b8e2ed41d03127a4a9dbd8392" FOREIGN KEY ("gamevault_user_id") REFERENCES "gamevault_user"("id") ON DELETE CASCADE ON UPDATE CASCADE + `); + } +} diff --git a/src/modules/database/migrations/sqlite/1689984000000-add-game-type.ts b/src/modules/database/migrations/sqlite/1689984000000-add-game-type.ts index 0a131228..58bdf6eb 100644 --- a/src/modules/database/migrations/sqlite/1689984000000-add-game-type.ts +++ b/src/modules/database/migrations/sqlite/1689984000000-add-game-type.ts @@ -1,4 +1,5 @@ import { MigrationInterface, QueryRunner } from "typeorm"; + import { AddDirectPlay1689638400000 } from "./1689638400000-add-direct-play"; export class AddGameType1689984000000 implements MigrationInterface { diff --git a/src/modules/database/migrations/sqlite/1695686400000-check-user-case-conflicts.ts b/src/modules/database/migrations/sqlite/1695686400000-check-user-case-conflicts.ts index 2f4382c6..cc483bcb 100644 --- a/src/modules/database/migrations/sqlite/1695686400000-check-user-case-conflicts.ts +++ b/src/modules/database/migrations/sqlite/1695686400000-check-user-case-conflicts.ts @@ -1,4 +1,5 @@ import { MigrationInterface, QueryRunner } from "typeorm"; + export class CheckUserCaseConflicts1695686400000 implements MigrationInterface { name?: string; transaction?: boolean; diff --git a/src/modules/database/migrations/sqlite/1696967362000-delete-empty-progresses.ts b/src/modules/database/migrations/sqlite/1696967362000-delete-empty-progresses.ts index 5d40b6cf..5f758be6 100644 --- a/src/modules/database/migrations/sqlite/1696967362000-delete-empty-progresses.ts +++ b/src/modules/database/migrations/sqlite/1696967362000-delete-empty-progresses.ts @@ -1,5 +1,6 @@ -import { MigrationInterface, QueryRunner } from "typeorm"; import { Logger } from "@nestjs/common"; +import { MigrationInterface, QueryRunner } from "typeorm"; + export class DeleteEmptyProgresses1696967362000 implements MigrationInterface { private readonly logger = new Logger(DeleteEmptyProgresses1696967362000.name); name?: string; diff --git a/src/modules/database/migrations/sqlite/1714999654369-rawg-independence.ts b/src/modules/database/migrations/sqlite/1714999654369-rawg-independence.ts new file mode 100644 index 00000000..5b72bae5 --- /dev/null +++ b/src/modules/database/migrations/sqlite/1714999654369-rawg-independence.ts @@ -0,0 +1,1313 @@ +import { MigrationInterface, QueryRunner } from "typeorm"; + +export class RawgIndependence1714999654369 implements MigrationInterface { + name = "RawgIndependence1714999654369"; + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + DROP INDEX "IDX_99bd20f783c41fe05489a0aca5" + `); + await queryRunner.query(` + DROP INDEX "IDX_8990e34bae4dca1bed3c9ab14d" + `); + await queryRunner.query(` + DROP INDEX "IDX_90b8e2ed41d03127a4a9dbd839" + `); + await queryRunner.query(` + CREATE TABLE "temporary_bookmark" ( + "gamevault_user_id" integer NOT NULL, + "game_id" integer NOT NULL, + PRIMARY KEY ("gamevault_user_id", "game_id") + ) + `); + await queryRunner.query(` + INSERT INTO "temporary_bookmark"("gamevault_user_id", "game_id") + SELECT "gamevault_user_id", + "game_id" + FROM "bookmark" + `); + await queryRunner.query(` + DROP TABLE "bookmark" + `); + await queryRunner.query(` + ALTER TABLE "temporary_bookmark" + RENAME TO "bookmark" + `); + await queryRunner.query(` + CREATE INDEX "IDX_99bd20f783c41fe05489a0aca5" ON "bookmark" ("game_id") + `); + await queryRunner.query(` + CREATE INDEX "IDX_8990e34bae4dca1bed3c9ab14d" ON "bookmark" ("game_id") + `); + await queryRunner.query(` + CREATE INDEX "IDX_90b8e2ed41d03127a4a9dbd839" ON "bookmark" ("gamevault_user_id") + `); + await queryRunner.query(` + DROP INDEX "IDX_8990e34bae4dca1bed3c9ab14d" + `); + await queryRunner.query(` + DROP INDEX "IDX_90b8e2ed41d03127a4a9dbd839" + `); + await queryRunner.query(` + DROP INDEX "IDX_5c0cd47a75116720223e43db85" + `); + await queryRunner.query(` + DROP INDEX "IDX_5c2989f7bc37f907cfd937c0fd" + `); + await queryRunner.query(` + DROP INDEX "IDX_71b846918f80786eed6bfb68b7" + `); + await queryRunner.query(` + CREATE TABLE "temporary_developer" ( + "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 NOT NULL, + "name" varchar NOT NULL, + CONSTRAINT "UQ_5c2989f7bc37f907cfd937c0fd0" UNIQUE ("name"), + CONSTRAINT "UQ_5c0cd47a75116720223e43db853" UNIQUE ("rawg_id") + ) + `); + await queryRunner.query(` + INSERT INTO "temporary_developer"( + "id", + "created_at", + "updated_at", + "deleted_at", + "entity_version", + "rawg_id", + "name" + ) + SELECT "id", + "created_at", + "updated_at", + "deleted_at", + "entity_version", + "rawg_id", + "name" + FROM "developer" + `); + await queryRunner.query(` + DROP TABLE "developer" + `); + await queryRunner.query(` + ALTER TABLE "temporary_developer" + RENAME TO "developer" + `); + await queryRunner.query(` + CREATE INDEX "IDX_5c0cd47a75116720223e43db85" ON "developer" ("rawg_id") + `); + await queryRunner.query(` + CREATE INDEX "IDX_5c2989f7bc37f907cfd937c0fd" ON "developer" ("name") + `); + await queryRunner.query(` + CREATE INDEX "IDX_71b846918f80786eed6bfb68b7" ON "developer" ("id") + `); + await queryRunner.query(` + DROP INDEX "IDX_672bce67ec8cb2d7755c158ad6" + `); + await queryRunner.query(` + DROP INDEX "IDX_dd8cd9e50dd049656e4be1f7e8" + `); + await queryRunner.query(` + DROP INDEX "IDX_0285d4f1655d080cfcf7d1ab14" + `); + await queryRunner.query(` + CREATE TABLE "temporary_genre" ( + "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 NOT NULL, + "name" varchar NOT NULL, + CONSTRAINT "UQ_dd8cd9e50dd049656e4be1f7e8c" UNIQUE ("name"), + CONSTRAINT "UQ_672bce67ec8cb2d7755c158ad65" UNIQUE ("rawg_id") + ) + `); + await queryRunner.query(` + INSERT INTO "temporary_genre"( + "id", + "created_at", + "updated_at", + "deleted_at", + "entity_version", + "rawg_id", + "name" + ) + SELECT "id", + "created_at", + "updated_at", + "deleted_at", + "entity_version", + "rawg_id", + "name" + FROM "genre" + `); + await queryRunner.query(` + DROP TABLE "genre" + `); + await queryRunner.query(` + ALTER TABLE "temporary_genre" + RENAME TO "genre" + `); + await queryRunner.query(` + CREATE INDEX "IDX_672bce67ec8cb2d7755c158ad6" ON "genre" ("rawg_id") + `); + await queryRunner.query(` + CREATE INDEX "IDX_dd8cd9e50dd049656e4be1f7e8" ON "genre" ("name") + `); + await queryRunner.query(` + CREATE INDEX "IDX_0285d4f1655d080cfcf7d1ab14" ON "genre" ("id") + `); + await queryRunner.query(` + DROP INDEX "IDX_4a0539222ee1307f657f875003" + `); + await queryRunner.query(` + DROP INDEX "IDX_9dc496f2e5b912da9edd2aa445" + `); + await queryRunner.query(` + DROP INDEX "IDX_70a5936b43177f76161724da3e" + `); + await queryRunner.query(` + CREATE TABLE "temporary_publisher" ( + "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 NOT NULL, + "name" varchar NOT NULL, + CONSTRAINT "UQ_9dc496f2e5b912da9edd2aa4455" UNIQUE ("name"), + CONSTRAINT "UQ_4a0539222ee1307f657f875003b" UNIQUE ("rawg_id") + ) + `); + await queryRunner.query(` + INSERT INTO "temporary_publisher"( + "id", + "created_at", + "updated_at", + "deleted_at", + "entity_version", + "rawg_id", + "name" + ) + SELECT "id", + "created_at", + "updated_at", + "deleted_at", + "entity_version", + "rawg_id", + "name" + FROM "publisher" + `); + await queryRunner.query(` + DROP TABLE "publisher" + `); + await queryRunner.query(` + ALTER TABLE "temporary_publisher" + RENAME TO "publisher" + `); + await queryRunner.query(` + CREATE INDEX "IDX_4a0539222ee1307f657f875003" ON "publisher" ("rawg_id") + `); + await queryRunner.query(` + CREATE INDEX "IDX_9dc496f2e5b912da9edd2aa445" ON "publisher" ("name") + `); + await queryRunner.query(` + CREATE INDEX "IDX_70a5936b43177f76161724da3e" ON "publisher" ("id") + `); + await queryRunner.query(` + DROP INDEX "IDX_45c4541783f264043ec2a5864d" + `); + await queryRunner.query(` + DROP INDEX "IDX_66df34da7fb037e24fc7fee642" + `); + await queryRunner.query(` + DROP INDEX "IDX_f3172007d4de5ae8e7692759d7" + `); + await queryRunner.query(` + CREATE TABLE "temporary_store" ( + "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 NOT NULL, + "name" varchar NOT NULL, + CONSTRAINT "UQ_66df34da7fb037e24fc7fee642b" UNIQUE ("name"), + CONSTRAINT "UQ_45c4541783f264043ec2a5864d6" UNIQUE ("rawg_id") + ) + `); + await queryRunner.query(` + INSERT INTO "temporary_store"( + "id", + "created_at", + "updated_at", + "deleted_at", + "entity_version", + "rawg_id", + "name" + ) + SELECT "id", + "created_at", + "updated_at", + "deleted_at", + "entity_version", + "rawg_id", + "name" + FROM "store" + `); + await queryRunner.query(` + DROP TABLE "store" + `); + await queryRunner.query(` + ALTER TABLE "temporary_store" + RENAME TO "store" + `); + await queryRunner.query(` + CREATE INDEX "IDX_45c4541783f264043ec2a5864d" ON "store" ("rawg_id") + `); + await queryRunner.query(` + CREATE INDEX "IDX_66df34da7fb037e24fc7fee642" ON "store" ("name") + `); + await queryRunner.query(` + CREATE INDEX "IDX_f3172007d4de5ae8e7692759d7" ON "store" ("id") + `); + await queryRunner.query(` + DROP INDEX "IDX_289102542903593026bd16e4e1" + `); + await queryRunner.query(` + DROP INDEX "IDX_6a9775008add570dc3e5a0bab7" + `); + await queryRunner.query(` + DROP INDEX "IDX_8e4052373c579afc1471f52676" + `); + await queryRunner.query(` + CREATE TABLE "temporary_tag" ( + "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 NOT NULL, + "name" varchar NOT NULL, + CONSTRAINT "UQ_6a9775008add570dc3e5a0bab7b" UNIQUE ("name"), + CONSTRAINT "UQ_289102542903593026bd16e4e1b" UNIQUE ("rawg_id") + ) + `); + await queryRunner.query(` + INSERT INTO "temporary_tag"( + "id", + "created_at", + "updated_at", + "deleted_at", + "entity_version", + "rawg_id", + "name" + ) + SELECT "id", + "created_at", + "updated_at", + "deleted_at", + "entity_version", + "rawg_id", + "name" + FROM "tag" + `); + await queryRunner.query(` + DROP TABLE "tag" + `); + await queryRunner.query(` + ALTER TABLE "temporary_tag" + RENAME TO "tag" + `); + await queryRunner.query(` + CREATE INDEX "IDX_289102542903593026bd16e4e1" ON "tag" ("rawg_id") + `); + await queryRunner.query(` + CREATE INDEX "IDX_6a9775008add570dc3e5a0bab7" ON "tag" ("name") + `); + await queryRunner.query(` + CREATE INDEX "IDX_8e4052373c579afc1471f52676" ON "tag" ("id") + `); + await queryRunner.query(` + DROP INDEX "IDX_5c0cd47a75116720223e43db85" + `); + await queryRunner.query(` + DROP INDEX "IDX_5c2989f7bc37f907cfd937c0fd" + `); + await queryRunner.query(` + DROP INDEX "IDX_71b846918f80786eed6bfb68b7" + `); + await queryRunner.query(` + CREATE TABLE "temporary_developer" ( + "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, + "name" varchar NOT NULL, + CONSTRAINT "UQ_5c2989f7bc37f907cfd937c0fd0" UNIQUE ("name"), + CONSTRAINT "UQ_5c0cd47a75116720223e43db853" UNIQUE ("rawg_id") + ) + `); + await queryRunner.query(` + INSERT INTO "temporary_developer"( + "id", + "created_at", + "updated_at", + "deleted_at", + "entity_version", + "rawg_id", + "name" + ) + SELECT "id", + "created_at", + "updated_at", + "deleted_at", + "entity_version", + "rawg_id", + "name" + FROM "developer" + `); + await queryRunner.query(` + DROP TABLE "developer" + `); + await queryRunner.query(` + ALTER TABLE "temporary_developer" + RENAME TO "developer" + `); + await queryRunner.query(` + CREATE INDEX "IDX_5c0cd47a75116720223e43db85" ON "developer" ("rawg_id") + `); + await queryRunner.query(` + CREATE INDEX "IDX_5c2989f7bc37f907cfd937c0fd" ON "developer" ("name") + `); + await queryRunner.query(` + CREATE INDEX "IDX_71b846918f80786eed6bfb68b7" ON "developer" ("id") + `); + await queryRunner.query(` + DROP INDEX "IDX_672bce67ec8cb2d7755c158ad6" + `); + await queryRunner.query(` + DROP INDEX "IDX_dd8cd9e50dd049656e4be1f7e8" + `); + await queryRunner.query(` + DROP INDEX "IDX_0285d4f1655d080cfcf7d1ab14" + `); + await queryRunner.query(` + CREATE TABLE "temporary_genre" ( + "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, + "name" varchar NOT NULL, + CONSTRAINT "UQ_dd8cd9e50dd049656e4be1f7e8c" UNIQUE ("name"), + CONSTRAINT "UQ_672bce67ec8cb2d7755c158ad65" UNIQUE ("rawg_id") + ) + `); + await queryRunner.query(` + INSERT INTO "temporary_genre"( + "id", + "created_at", + "updated_at", + "deleted_at", + "entity_version", + "rawg_id", + "name" + ) + SELECT "id", + "created_at", + "updated_at", + "deleted_at", + "entity_version", + "rawg_id", + "name" + FROM "genre" + `); + await queryRunner.query(` + DROP TABLE "genre" + `); + await queryRunner.query(` + ALTER TABLE "temporary_genre" + RENAME TO "genre" + `); + await queryRunner.query(` + CREATE INDEX "IDX_672bce67ec8cb2d7755c158ad6" ON "genre" ("rawg_id") + `); + await queryRunner.query(` + CREATE INDEX "IDX_dd8cd9e50dd049656e4be1f7e8" ON "genre" ("name") + `); + await queryRunner.query(` + CREATE INDEX "IDX_0285d4f1655d080cfcf7d1ab14" ON "genre" ("id") + `); + await queryRunner.query(` + DROP INDEX "IDX_4a0539222ee1307f657f875003" + `); + await queryRunner.query(` + DROP INDEX "IDX_9dc496f2e5b912da9edd2aa445" + `); + await queryRunner.query(` + DROP INDEX "IDX_70a5936b43177f76161724da3e" + `); + await queryRunner.query(` + CREATE TABLE "temporary_publisher" ( + "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, + "name" varchar NOT NULL, + CONSTRAINT "UQ_9dc496f2e5b912da9edd2aa4455" UNIQUE ("name"), + CONSTRAINT "UQ_4a0539222ee1307f657f875003b" UNIQUE ("rawg_id") + ) + `); + await queryRunner.query(` + INSERT INTO "temporary_publisher"( + "id", + "created_at", + "updated_at", + "deleted_at", + "entity_version", + "rawg_id", + "name" + ) + SELECT "id", + "created_at", + "updated_at", + "deleted_at", + "entity_version", + "rawg_id", + "name" + FROM "publisher" + `); + await queryRunner.query(` + DROP TABLE "publisher" + `); + await queryRunner.query(` + ALTER TABLE "temporary_publisher" + RENAME TO "publisher" + `); + await queryRunner.query(` + CREATE INDEX "IDX_4a0539222ee1307f657f875003" ON "publisher" ("rawg_id") + `); + await queryRunner.query(` + CREATE INDEX "IDX_9dc496f2e5b912da9edd2aa445" ON "publisher" ("name") + `); + await queryRunner.query(` + CREATE INDEX "IDX_70a5936b43177f76161724da3e" ON "publisher" ("id") + `); + await queryRunner.query(` + DROP INDEX "IDX_45c4541783f264043ec2a5864d" + `); + await queryRunner.query(` + DROP INDEX "IDX_66df34da7fb037e24fc7fee642" + `); + await queryRunner.query(` + DROP INDEX "IDX_f3172007d4de5ae8e7692759d7" + `); + await queryRunner.query(` + CREATE TABLE "temporary_store" ( + "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, + "name" varchar NOT NULL, + CONSTRAINT "UQ_66df34da7fb037e24fc7fee642b" UNIQUE ("name"), + CONSTRAINT "UQ_45c4541783f264043ec2a5864d6" UNIQUE ("rawg_id") + ) + `); + await queryRunner.query(` + INSERT INTO "temporary_store"( + "id", + "created_at", + "updated_at", + "deleted_at", + "entity_version", + "rawg_id", + "name" + ) + SELECT "id", + "created_at", + "updated_at", + "deleted_at", + "entity_version", + "rawg_id", + "name" + FROM "store" + `); + await queryRunner.query(` + DROP TABLE "store" + `); + await queryRunner.query(` + ALTER TABLE "temporary_store" + RENAME TO "store" + `); + await queryRunner.query(` + CREATE INDEX "IDX_45c4541783f264043ec2a5864d" ON "store" ("rawg_id") + `); + await queryRunner.query(` + CREATE INDEX "IDX_66df34da7fb037e24fc7fee642" ON "store" ("name") + `); + await queryRunner.query(` + CREATE INDEX "IDX_f3172007d4de5ae8e7692759d7" ON "store" ("id") + `); + await queryRunner.query(` + DROP INDEX "IDX_289102542903593026bd16e4e1" + `); + await queryRunner.query(` + DROP INDEX "IDX_6a9775008add570dc3e5a0bab7" + `); + await queryRunner.query(` + DROP INDEX "IDX_8e4052373c579afc1471f52676" + `); + await queryRunner.query(` + CREATE TABLE "temporary_tag" ( + "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, + "name" varchar NOT NULL, + CONSTRAINT "UQ_6a9775008add570dc3e5a0bab7b" UNIQUE ("name"), + CONSTRAINT "UQ_289102542903593026bd16e4e1b" UNIQUE ("rawg_id") + ) + `); + await queryRunner.query(` + INSERT INTO "temporary_tag"( + "id", + "created_at", + "updated_at", + "deleted_at", + "entity_version", + "rawg_id", + "name" + ) + SELECT "id", + "created_at", + "updated_at", + "deleted_at", + "entity_version", + "rawg_id", + "name" + FROM "tag" + `); + await queryRunner.query(` + DROP TABLE "tag" + `); + await queryRunner.query(` + ALTER TABLE "temporary_tag" + RENAME TO "tag" + `); + await queryRunner.query(` + CREATE INDEX "IDX_289102542903593026bd16e4e1" ON "tag" ("rawg_id") + `); + await queryRunner.query(` + CREATE INDEX "IDX_6a9775008add570dc3e5a0bab7" ON "tag" ("name") + `); + await queryRunner.query(` + CREATE INDEX "IDX_8e4052373c579afc1471f52676" ON "tag" ("id") + `); + await queryRunner.query(` + CREATE INDEX "IDX_6f00464edf85ddfedbd2580842" ON "bookmark" ("gamevault_user_id") + `); + await queryRunner.query(` + DROP INDEX "IDX_99bd20f783c41fe05489a0aca5" + `); + await queryRunner.query(` + DROP INDEX "IDX_6f00464edf85ddfedbd2580842" + `); + await queryRunner.query(` + CREATE TABLE "temporary_bookmark" ( + "gamevault_user_id" integer NOT NULL, + "game_id" integer NOT NULL, + CONSTRAINT "FK_6f00464edf85ddfedbd25808428" FOREIGN KEY ("gamevault_user_id") REFERENCES "gamevault_user" ("id") ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT "FK_99bd20f783c41fe05489a0aca5f" FOREIGN KEY ("game_id") REFERENCES "game" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION, + PRIMARY KEY ("gamevault_user_id", "game_id") + ) + `); + await queryRunner.query(` + INSERT INTO "temporary_bookmark"("gamevault_user_id", "game_id") + SELECT "gamevault_user_id", + "game_id" + FROM "bookmark" + `); + await queryRunner.query(` + DROP TABLE "bookmark" + `); + await queryRunner.query(` + ALTER TABLE "temporary_bookmark" + RENAME TO "bookmark" + `); + await queryRunner.query(` + CREATE INDEX "IDX_99bd20f783c41fe05489a0aca5" ON "bookmark" ("game_id") + `); + await queryRunner.query(` + CREATE INDEX "IDX_6f00464edf85ddfedbd2580842" ON "bookmark" ("gamevault_user_id") + `); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + DROP INDEX "IDX_6f00464edf85ddfedbd2580842" + `); + await queryRunner.query(` + DROP INDEX "IDX_99bd20f783c41fe05489a0aca5" + `); + await queryRunner.query(` + ALTER TABLE "bookmark" + RENAME TO "temporary_bookmark" + `); + await queryRunner.query(` + CREATE TABLE "bookmark" ( + "gamevault_user_id" integer NOT NULL, + "game_id" integer NOT NULL, + PRIMARY KEY ("gamevault_user_id", "game_id") + ) + `); + await queryRunner.query(` + INSERT INTO "bookmark"("gamevault_user_id", "game_id") + SELECT "gamevault_user_id", + "game_id" + FROM "temporary_bookmark" + `); + await queryRunner.query(` + DROP TABLE "temporary_bookmark" + `); + await queryRunner.query(` + CREATE INDEX "IDX_6f00464edf85ddfedbd2580842" ON "bookmark" ("gamevault_user_id") + `); + await queryRunner.query(` + CREATE INDEX "IDX_99bd20f783c41fe05489a0aca5" ON "bookmark" ("game_id") + `); + await queryRunner.query(` + DROP INDEX "IDX_6f00464edf85ddfedbd2580842" + `); + await queryRunner.query(` + DROP INDEX "IDX_8e4052373c579afc1471f52676" + `); + await queryRunner.query(` + DROP INDEX "IDX_6a9775008add570dc3e5a0bab7" + `); + await queryRunner.query(` + DROP INDEX "IDX_289102542903593026bd16e4e1" + `); + await queryRunner.query(` + ALTER TABLE "tag" + RENAME TO "temporary_tag" + `); + await queryRunner.query(` + CREATE TABLE "tag" ( + "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 NOT NULL, + "name" varchar NOT NULL, + CONSTRAINT "UQ_6a9775008add570dc3e5a0bab7b" UNIQUE ("name"), + CONSTRAINT "UQ_289102542903593026bd16e4e1b" UNIQUE ("rawg_id") + ) + `); + await queryRunner.query(` + INSERT INTO "tag"( + "id", + "created_at", + "updated_at", + "deleted_at", + "entity_version", + "rawg_id", + "name" + ) + SELECT "id", + "created_at", + "updated_at", + "deleted_at", + "entity_version", + "rawg_id", + "name" + FROM "temporary_tag" + `); + await queryRunner.query(` + DROP TABLE "temporary_tag" + `); + await queryRunner.query(` + CREATE INDEX "IDX_8e4052373c579afc1471f52676" ON "tag" ("id") + `); + await queryRunner.query(` + CREATE INDEX "IDX_6a9775008add570dc3e5a0bab7" ON "tag" ("name") + `); + await queryRunner.query(` + CREATE INDEX "IDX_289102542903593026bd16e4e1" ON "tag" ("rawg_id") + `); + await queryRunner.query(` + DROP INDEX "IDX_f3172007d4de5ae8e7692759d7" + `); + await queryRunner.query(` + DROP INDEX "IDX_66df34da7fb037e24fc7fee642" + `); + await queryRunner.query(` + DROP INDEX "IDX_45c4541783f264043ec2a5864d" + `); + await queryRunner.query(` + ALTER TABLE "store" + RENAME TO "temporary_store" + `); + await queryRunner.query(` + CREATE TABLE "store" ( + "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 NOT NULL, + "name" varchar NOT NULL, + CONSTRAINT "UQ_66df34da7fb037e24fc7fee642b" UNIQUE ("name"), + CONSTRAINT "UQ_45c4541783f264043ec2a5864d6" UNIQUE ("rawg_id") + ) + `); + await queryRunner.query(` + INSERT INTO "store"( + "id", + "created_at", + "updated_at", + "deleted_at", + "entity_version", + "rawg_id", + "name" + ) + SELECT "id", + "created_at", + "updated_at", + "deleted_at", + "entity_version", + "rawg_id", + "name" + FROM "temporary_store" + `); + await queryRunner.query(` + DROP TABLE "temporary_store" + `); + await queryRunner.query(` + CREATE INDEX "IDX_f3172007d4de5ae8e7692759d7" ON "store" ("id") + `); + await queryRunner.query(` + CREATE INDEX "IDX_66df34da7fb037e24fc7fee642" ON "store" ("name") + `); + await queryRunner.query(` + CREATE INDEX "IDX_45c4541783f264043ec2a5864d" ON "store" ("rawg_id") + `); + await queryRunner.query(` + DROP INDEX "IDX_70a5936b43177f76161724da3e" + `); + await queryRunner.query(` + DROP INDEX "IDX_9dc496f2e5b912da9edd2aa445" + `); + await queryRunner.query(` + DROP INDEX "IDX_4a0539222ee1307f657f875003" + `); + await queryRunner.query(` + ALTER TABLE "publisher" + RENAME TO "temporary_publisher" + `); + await queryRunner.query(` + CREATE TABLE "publisher" ( + "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 NOT NULL, + "name" varchar NOT NULL, + CONSTRAINT "UQ_9dc496f2e5b912da9edd2aa4455" UNIQUE ("name"), + CONSTRAINT "UQ_4a0539222ee1307f657f875003b" UNIQUE ("rawg_id") + ) + `); + await queryRunner.query(` + INSERT INTO "publisher"( + "id", + "created_at", + "updated_at", + "deleted_at", + "entity_version", + "rawg_id", + "name" + ) + SELECT "id", + "created_at", + "updated_at", + "deleted_at", + "entity_version", + "rawg_id", + "name" + FROM "temporary_publisher" + `); + await queryRunner.query(` + DROP TABLE "temporary_publisher" + `); + await queryRunner.query(` + CREATE INDEX "IDX_70a5936b43177f76161724da3e" ON "publisher" ("id") + `); + await queryRunner.query(` + CREATE INDEX "IDX_9dc496f2e5b912da9edd2aa445" ON "publisher" ("name") + `); + await queryRunner.query(` + CREATE INDEX "IDX_4a0539222ee1307f657f875003" ON "publisher" ("rawg_id") + `); + await queryRunner.query(` + DROP INDEX "IDX_0285d4f1655d080cfcf7d1ab14" + `); + await queryRunner.query(` + DROP INDEX "IDX_dd8cd9e50dd049656e4be1f7e8" + `); + await queryRunner.query(` + DROP INDEX "IDX_672bce67ec8cb2d7755c158ad6" + `); + await queryRunner.query(` + ALTER TABLE "genre" + RENAME TO "temporary_genre" + `); + await queryRunner.query(` + CREATE TABLE "genre" ( + "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 NOT NULL, + "name" varchar NOT NULL, + CONSTRAINT "UQ_dd8cd9e50dd049656e4be1f7e8c" UNIQUE ("name"), + CONSTRAINT "UQ_672bce67ec8cb2d7755c158ad65" UNIQUE ("rawg_id") + ) + `); + await queryRunner.query(` + INSERT INTO "genre"( + "id", + "created_at", + "updated_at", + "deleted_at", + "entity_version", + "rawg_id", + "name" + ) + SELECT "id", + "created_at", + "updated_at", + "deleted_at", + "entity_version", + "rawg_id", + "name" + FROM "temporary_genre" + `); + await queryRunner.query(` + DROP TABLE "temporary_genre" + `); + await queryRunner.query(` + CREATE INDEX "IDX_0285d4f1655d080cfcf7d1ab14" ON "genre" ("id") + `); + await queryRunner.query(` + CREATE INDEX "IDX_dd8cd9e50dd049656e4be1f7e8" ON "genre" ("name") + `); + await queryRunner.query(` + CREATE INDEX "IDX_672bce67ec8cb2d7755c158ad6" ON "genre" ("rawg_id") + `); + await queryRunner.query(` + DROP INDEX "IDX_71b846918f80786eed6bfb68b7" + `); + await queryRunner.query(` + DROP INDEX "IDX_5c2989f7bc37f907cfd937c0fd" + `); + await queryRunner.query(` + DROP INDEX "IDX_5c0cd47a75116720223e43db85" + `); + await queryRunner.query(` + ALTER TABLE "developer" + RENAME TO "temporary_developer" + `); + await queryRunner.query(` + CREATE TABLE "developer" ( + "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 NOT NULL, + "name" varchar NOT NULL, + CONSTRAINT "UQ_5c2989f7bc37f907cfd937c0fd0" UNIQUE ("name"), + CONSTRAINT "UQ_5c0cd47a75116720223e43db853" UNIQUE ("rawg_id") + ) + `); + await queryRunner.query(` + INSERT INTO "developer"( + "id", + "created_at", + "updated_at", + "deleted_at", + "entity_version", + "rawg_id", + "name" + ) + SELECT "id", + "created_at", + "updated_at", + "deleted_at", + "entity_version", + "rawg_id", + "name" + FROM "temporary_developer" + `); + await queryRunner.query(` + DROP TABLE "temporary_developer" + `); + await queryRunner.query(` + CREATE INDEX "IDX_71b846918f80786eed6bfb68b7" ON "developer" ("id") + `); + await queryRunner.query(` + CREATE INDEX "IDX_5c2989f7bc37f907cfd937c0fd" ON "developer" ("name") + `); + await queryRunner.query(` + CREATE INDEX "IDX_5c0cd47a75116720223e43db85" ON "developer" ("rawg_id") + `); + await queryRunner.query(` + DROP INDEX "IDX_8e4052373c579afc1471f52676" + `); + await queryRunner.query(` + DROP INDEX "IDX_6a9775008add570dc3e5a0bab7" + `); + await queryRunner.query(` + DROP INDEX "IDX_289102542903593026bd16e4e1" + `); + await queryRunner.query(` + ALTER TABLE "tag" + RENAME TO "temporary_tag" + `); + await queryRunner.query(` + CREATE TABLE "tag" ( + "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 NOT NULL, + "name" varchar NOT NULL, + CONSTRAINT "UQ_6a9775008add570dc3e5a0bab7b" UNIQUE ("name"), + CONSTRAINT "UQ_289102542903593026bd16e4e1b" UNIQUE ("rawg_id") + ) + `); + await queryRunner.query(` + INSERT INTO "tag"( + "id", + "created_at", + "updated_at", + "deleted_at", + "entity_version", + "rawg_id", + "name" + ) + SELECT "id", + "created_at", + "updated_at", + "deleted_at", + "entity_version", + "rawg_id", + "name" + FROM "temporary_tag" + `); + await queryRunner.query(` + DROP TABLE "temporary_tag" + `); + await queryRunner.query(` + CREATE INDEX "IDX_8e4052373c579afc1471f52676" ON "tag" ("id") + `); + await queryRunner.query(` + CREATE INDEX "IDX_6a9775008add570dc3e5a0bab7" ON "tag" ("name") + `); + await queryRunner.query(` + CREATE INDEX "IDX_289102542903593026bd16e4e1" ON "tag" ("rawg_id") + `); + await queryRunner.query(` + DROP INDEX "IDX_f3172007d4de5ae8e7692759d7" + `); + await queryRunner.query(` + DROP INDEX "IDX_66df34da7fb037e24fc7fee642" + `); + await queryRunner.query(` + DROP INDEX "IDX_45c4541783f264043ec2a5864d" + `); + await queryRunner.query(` + ALTER TABLE "store" + RENAME TO "temporary_store" + `); + await queryRunner.query(` + CREATE TABLE "store" ( + "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 NOT NULL, + "name" varchar NOT NULL, + CONSTRAINT "UQ_66df34da7fb037e24fc7fee642b" UNIQUE ("name"), + CONSTRAINT "UQ_45c4541783f264043ec2a5864d6" UNIQUE ("rawg_id") + ) + `); + await queryRunner.query(` + INSERT INTO "store"( + "id", + "created_at", + "updated_at", + "deleted_at", + "entity_version", + "rawg_id", + "name" + ) + SELECT "id", + "created_at", + "updated_at", + "deleted_at", + "entity_version", + "rawg_id", + "name" + FROM "temporary_store" + `); + await queryRunner.query(` + DROP TABLE "temporary_store" + `); + await queryRunner.query(` + CREATE INDEX "IDX_f3172007d4de5ae8e7692759d7" ON "store" ("id") + `); + await queryRunner.query(` + CREATE INDEX "IDX_66df34da7fb037e24fc7fee642" ON "store" ("name") + `); + await queryRunner.query(` + CREATE INDEX "IDX_45c4541783f264043ec2a5864d" ON "store" ("rawg_id") + `); + await queryRunner.query(` + DROP INDEX "IDX_70a5936b43177f76161724da3e" + `); + await queryRunner.query(` + DROP INDEX "IDX_9dc496f2e5b912da9edd2aa445" + `); + await queryRunner.query(` + DROP INDEX "IDX_4a0539222ee1307f657f875003" + `); + await queryRunner.query(` + ALTER TABLE "publisher" + RENAME TO "temporary_publisher" + `); + await queryRunner.query(` + CREATE TABLE "publisher" ( + "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 NOT NULL, + "name" varchar NOT NULL, + CONSTRAINT "UQ_9dc496f2e5b912da9edd2aa4455" UNIQUE ("name"), + CONSTRAINT "UQ_4a0539222ee1307f657f875003b" UNIQUE ("rawg_id") + ) + `); + await queryRunner.query(` + INSERT INTO "publisher"( + "id", + "created_at", + "updated_at", + "deleted_at", + "entity_version", + "rawg_id", + "name" + ) + SELECT "id", + "created_at", + "updated_at", + "deleted_at", + "entity_version", + "rawg_id", + "name" + FROM "temporary_publisher" + `); + await queryRunner.query(` + DROP TABLE "temporary_publisher" + `); + await queryRunner.query(` + CREATE INDEX "IDX_70a5936b43177f76161724da3e" ON "publisher" ("id") + `); + await queryRunner.query(` + CREATE INDEX "IDX_9dc496f2e5b912da9edd2aa445" ON "publisher" ("name") + `); + await queryRunner.query(` + CREATE INDEX "IDX_4a0539222ee1307f657f875003" ON "publisher" ("rawg_id") + `); + await queryRunner.query(` + DROP INDEX "IDX_0285d4f1655d080cfcf7d1ab14" + `); + await queryRunner.query(` + DROP INDEX "IDX_dd8cd9e50dd049656e4be1f7e8" + `); + await queryRunner.query(` + DROP INDEX "IDX_672bce67ec8cb2d7755c158ad6" + `); + await queryRunner.query(` + ALTER TABLE "genre" + RENAME TO "temporary_genre" + `); + await queryRunner.query(` + CREATE TABLE "genre" ( + "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 NOT NULL, + "name" varchar NOT NULL, + CONSTRAINT "UQ_dd8cd9e50dd049656e4be1f7e8c" UNIQUE ("name"), + CONSTRAINT "UQ_672bce67ec8cb2d7755c158ad65" UNIQUE ("rawg_id") + ) + `); + await queryRunner.query(` + INSERT INTO "genre"( + "id", + "created_at", + "updated_at", + "deleted_at", + "entity_version", + "rawg_id", + "name" + ) + SELECT "id", + "created_at", + "updated_at", + "deleted_at", + "entity_version", + "rawg_id", + "name" + FROM "temporary_genre" + `); + await queryRunner.query(` + DROP TABLE "temporary_genre" + `); + await queryRunner.query(` + CREATE INDEX "IDX_0285d4f1655d080cfcf7d1ab14" ON "genre" ("id") + `); + await queryRunner.query(` + CREATE INDEX "IDX_dd8cd9e50dd049656e4be1f7e8" ON "genre" ("name") + `); + await queryRunner.query(` + CREATE INDEX "IDX_672bce67ec8cb2d7755c158ad6" ON "genre" ("rawg_id") + `); + await queryRunner.query(` + DROP INDEX "IDX_71b846918f80786eed6bfb68b7" + `); + await queryRunner.query(` + DROP INDEX "IDX_5c2989f7bc37f907cfd937c0fd" + `); + await queryRunner.query(` + DROP INDEX "IDX_5c0cd47a75116720223e43db85" + `); + await queryRunner.query(` + ALTER TABLE "developer" + RENAME TO "temporary_developer" + `); + await queryRunner.query(` + CREATE TABLE "developer" ( + "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 NOT NULL, + "name" varchar NOT NULL, + CONSTRAINT "UQ_5c2989f7bc37f907cfd937c0fd0" UNIQUE ("name"), + CONSTRAINT "UQ_5c0cd47a75116720223e43db853" UNIQUE ("rawg_id") + ) + `); + await queryRunner.query(` + INSERT INTO "developer"( + "id", + "created_at", + "updated_at", + "deleted_at", + "entity_version", + "rawg_id", + "name" + ) + SELECT "id", + "created_at", + "updated_at", + "deleted_at", + "entity_version", + "rawg_id", + "name" + FROM "temporary_developer" + `); + await queryRunner.query(` + DROP TABLE "temporary_developer" + `); + await queryRunner.query(` + CREATE INDEX "IDX_71b846918f80786eed6bfb68b7" ON "developer" ("id") + `); + await queryRunner.query(` + CREATE INDEX "IDX_5c2989f7bc37f907cfd937c0fd" ON "developer" ("name") + `); + await queryRunner.query(` + CREATE INDEX "IDX_5c0cd47a75116720223e43db85" ON "developer" ("rawg_id") + `); + await queryRunner.query(` + CREATE INDEX "IDX_90b8e2ed41d03127a4a9dbd839" ON "bookmark" ("gamevault_user_id") + `); + await queryRunner.query(` + CREATE INDEX "IDX_8990e34bae4dca1bed3c9ab14d" ON "bookmark" ("game_id") + `); + await queryRunner.query(` + DROP INDEX "IDX_90b8e2ed41d03127a4a9dbd839" + `); + await queryRunner.query(` + DROP INDEX "IDX_8990e34bae4dca1bed3c9ab14d" + `); + await queryRunner.query(` + DROP INDEX "IDX_99bd20f783c41fe05489a0aca5" + `); + await queryRunner.query(` + ALTER TABLE "bookmark" + RENAME TO "temporary_bookmark" + `); + await queryRunner.query(` + CREATE TABLE "bookmark" ( + "gamevault_user_id" integer NOT NULL, + "game_id" integer NOT NULL, + CONSTRAINT "FK_8990e34bae4dca1bed3c9ab14d7" FOREIGN KEY ("game_id") REFERENCES "game" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION, + CONSTRAINT "FK_90b8e2ed41d03127a4a9dbd8392" FOREIGN KEY ("gamevault_user_id") REFERENCES "gamevault_user" ("id") ON DELETE CASCADE ON UPDATE CASCADE, + PRIMARY KEY ("gamevault_user_id", "game_id") + ) + `); + await queryRunner.query(` + INSERT INTO "bookmark"("gamevault_user_id", "game_id") + SELECT "gamevault_user_id", + "game_id" + FROM "temporary_bookmark" + `); + await queryRunner.query(` + DROP TABLE "temporary_bookmark" + `); + await queryRunner.query(` + CREATE INDEX "IDX_90b8e2ed41d03127a4a9dbd839" ON "bookmark" ("gamevault_user_id") + `); + await queryRunner.query(` + CREATE INDEX "IDX_8990e34bae4dca1bed3c9ab14d" ON "bookmark" ("game_id") + `); + await queryRunner.query(` + CREATE INDEX "IDX_99bd20f783c41fe05489a0aca5" ON "bookmark" ("game_id") + `); + } +} diff --git a/src/modules/database/models/paginated-entity.model.ts b/src/modules/database/models/paginated-entity.model.ts index 118df016..cef041f2 100644 --- a/src/modules/database/models/paginated-entity.model.ts +++ b/src/modules/database/models/paginated-entity.model.ts @@ -1,6 +1,6 @@ import { ApiProperty, ApiPropertyOptional } from "@nestjs/swagger"; import { Paginated } from "nestjs-paginate"; -import { SortBy, Column } from "nestjs-paginate/lib/helper"; +import { Column, SortBy } from "nestjs-paginate/lib/helper"; export class Metadata { @ApiProperty({ example: 50, description: "amount of items per page" }) diff --git a/src/modules/developers/developer.entity.ts b/src/modules/developers/developer.entity.ts index 4bbd7348..8ceb82d4 100644 --- a/src/modules/developers/developer.entity.ts +++ b/src/modules/developers/developer.entity.ts @@ -1,17 +1,18 @@ -import { ApiProperty } from "@nestjs/swagger"; -import { Entity, Column, ManyToMany, Index } from "typeorm"; -import { Game } from "../games/game.entity"; +import { ApiProperty, ApiPropertyOptional } from "@nestjs/swagger"; +import { Column, Entity, Index, ManyToMany } from "typeorm"; + import { DatabaseEntity } from "../database/database.entity"; +import { Game } from "../games/game.entity"; @Entity() export class Developer extends DatabaseEntity { @Index() - @Column({ unique: true }) - @ApiProperty({ + @Column({ nullable: true }) + @ApiPropertyOptional({ example: 1000, description: "unique rawg-api-identifier of the developer", }) - rawg_id: number; + rawg_id?: number; @Index() @Column({ unique: true }) diff --git a/src/modules/developers/developers.module.ts b/src/modules/developers/developers.module.ts index f7962ebf..d49955f8 100644 --- a/src/modules/developers/developers.module.ts +++ b/src/modules/developers/developers.module.ts @@ -1,7 +1,8 @@ import { Module } from "@nestjs/common"; -import { DevelopersService } from "./developers.service"; import { TypeOrmModule } from "@nestjs/typeorm"; + import { Developer } from "./developer.entity"; +import { DevelopersService } from "./developers.service"; @Module({ imports: [TypeOrmModule.forFeature([Developer])], diff --git a/src/modules/developers/developers.service.ts b/src/modules/developers/developers.service.ts index fcdc3f6f..8e95b782 100644 --- a/src/modules/developers/developers.service.ts +++ b/src/modules/developers/developers.service.ts @@ -1,8 +1,9 @@ import { Injectable, Logger } from "@nestjs/common"; import { InjectRepository } from "@nestjs/typeorm"; +import { Builder } from "builder-pattern"; import { Repository } from "typeorm"; + import { Developer } from "./developer.entity"; -import { Builder } from "builder-pattern"; @Injectable() export class DevelopersService { @@ -18,7 +19,7 @@ export class DevelopersService { */ async getOrCreate(name: string, rawg_id: number): Promise { const existingDeveloper = await this.developerRepository.findOneBy({ - rawg_id, + name, }); if (existingDeveloper) return existingDeveloper; diff --git a/src/modules/files/files.controller.ts b/src/modules/files/files.controller.ts index 3ec2fd74..f0ef63e4 100644 --- a/src/modules/files/files.controller.ts +++ b/src/modules/files/files.controller.ts @@ -1,12 +1,13 @@ import { Controller, Put } from "@nestjs/common"; import { - ApiOperation, + ApiBasicAuth, ApiOkResponse, + ApiOperation, ApiTags, - ApiBasicAuth, } from "@nestjs/swagger"; -import { Game } from "../games/game.entity"; + import { MinimumRole } from "../../decorators/minimum-role.decorator"; +import { Game } from "../games/game.entity"; import { Role } from "../users/models/role.enum"; import { FilesService } from "./files.service"; diff --git a/src/modules/files/files.module.ts b/src/modules/files/files.module.ts index de40d897..5ce4b07b 100644 --- a/src/modules/files/files.module.ts +++ b/src/modules/files/files.module.ts @@ -1,9 +1,10 @@ -import { FilesController } from "./files.controller"; -import { Module, forwardRef } from "@nestjs/common"; -import { FilesService } from "./files.service"; +import { forwardRef, Module } from "@nestjs/common"; + +import { BoxartsModule } from "../boxarts/boxarts.module"; import { GamesModule } from "../games/games.module"; import { RawgModule } from "../providers/rawg/rawg.module"; -import { BoxartsModule } from "../boxarts/boxarts.module"; +import { FilesController } from "./files.controller"; +import { FilesService } from "./files.service"; @Module({ imports: [ diff --git a/src/modules/files/files.service.spec.ts b/src/modules/files/files.service.spec.ts index 8e0c7b29..b4a9fe5c 100644 --- a/src/modules/files/files.service.spec.ts +++ b/src/modules/files/files.service.spec.ts @@ -1,12 +1,12 @@ /* https://docs.nestjs.com/fundamentals/testing#unit-testing */ - import { Test } from "@nestjs/testing"; -import { FilesService } from "./files.service"; + +import { BoxArtsService } from "../boxarts/boxarts.service"; import { GamesService } from "../games/games.service"; import { RawgService } from "../providers/rawg/rawg.service"; -import { BoxArtsService } from "../boxarts/boxarts.service"; +import { FilesService } from "./files.service"; describe("FilesService", () => { let filesService: FilesService; diff --git a/src/modules/files/files.service.ts b/src/modules/files/files.service.ts index 2b32cfce..f0516a48 100644 --- a/src/modules/files/files.service.ts +++ b/src/modules/files/files.service.ts @@ -6,29 +6,32 @@ import { OnApplicationBootstrap, StreamableFile, } from "@nestjs/common"; -import { IGameVaultFile } from "./models/file.model"; -import { Game } from "../games/game.entity"; -import { GamesService } from "../games/games.service"; +import { Cron } from "@nestjs/schedule"; +import { watch } from "chokidar"; +import { randomBytes } from "crypto"; import { createReadStream, existsSync, statSync } from "fs"; +import { readdir, stat } from "fs/promises"; +import { debounce } from "lodash"; +import mime from "mime"; +import { add, list } from "node-7z"; import path, { basename, extname, join } from "path"; +import filenameSanitizer from "sanitize-filename"; +import { Readable } from "stream"; +import { Throttle } from "stream-throttle"; +import unidecode from "unidecode"; + import configuration from "../../configuration"; +import globals from "../../globals"; +import { BoxArtsService } from "../boxarts/boxarts.service"; +import { Game } from "../games/game.entity"; import mock from "../games/games.mock"; -import mime from "mime"; +import { GamesService } from "../games/games.service"; import { GameExistence } from "../games/models/game-existence.enum"; -import { add, list } from "node-7z"; import { GameType } from "../games/models/game-type.enum"; import { RawgService } from "../providers/rawg/rawg.service"; -import { BoxArtsService } from "../boxarts/boxarts.service"; -import globals from "../../globals"; -import filenameSanitizer from "sanitize-filename"; -import unidecode from "unidecode"; -import { randomBytes } from "crypto"; -import { watch } from "chokidar"; -import { debounce } from "lodash"; -import { Readable } from "stream"; -import { Throttle } from "stream-throttle"; -import { readdir, stat } from "fs/promises"; -import { Cron } from "@nestjs/schedule"; +import ByteRangeStream from "./models/byte-range-stream"; +import { IGameVaultFile } from "./models/file.model"; +import { RangeHeader } from "./models/range-header.model"; @Injectable() export class FilesService implements OnApplicationBootstrap { @@ -577,18 +580,20 @@ export class FilesService implements OnApplicationBootstrap { * Downloads a game file by ID and returns it as a StreamableFile object. * * @param gameId - The ID of the game to download. - * @param speedlimit - The maximum download speed limit in KBps (optional). + * @param speedlimitHeader - The maximum download speed limit in KBps (optional). + * @param rangeHeader - The range header (optional). * @returns A Promise that resolves to a StreamableFile object. * @throws NotFoundException if the game file could not be found. */ public async download( gameId: number, - speedlimit?: number, + speedlimitHeader?: number, + rangeHeader?: string, ): Promise { // Set the download speed limit if provided, otherwise use the default value from configuration. - speedlimit = - speedlimit || configuration.SERVER.MAX_DOWNLOAD_BANDWIDTH_IN_KBPS; - speedlimit *= 1024; + speedlimitHeader = + speedlimitHeader || configuration.SERVER.MAX_DOWNLOAD_BANDWIDTH_IN_KBPS; + speedlimitHeader *= 1024; // Find the game by ID. const game = await this.gamesService.findByGameIdOrFail(gameId); @@ -628,22 +633,67 @@ export class FilesService implements OnApplicationBootstrap { // Read the file and apply speed limit if necessary. let file: Readable = createReadStream(fileDownloadPath); - if (speedlimit) { - file = file.pipe(new Throttle({ rate: speedlimit })); - } - // Get the file length, type, and sanitized filename. - const length = (await stat(fileDownloadPath)).size; - const type = mime.getType(fileDownloadPath); - const filename = filenameSanitizer( - unidecode(path.basename(fileDownloadPath)), + // Apply range header if provided otherwise returns the entire file + const range = this.calculateRange( + rangeHeader, + (await stat(fileDownloadPath)).size, + ); + this.logger.debug({ + message: "Applying download range.", + rangeHeader, + range, + }); + file = file.pipe( + new ByteRangeStream(BigInt(range.start), BigInt(range.end)), ); - // Return a StreamableFile object with the file stream and metadata. + if (speedlimitHeader) { + file = file.pipe(new Throttle({ rate: speedlimitHeader })); + } + return new StreamableFile(file, { - disposition: `attachment; filename="${filename}"`, - length, - type, + disposition: `attachment; filename="${filenameSanitizer( + unidecode(path.basename(fileDownloadPath)), + )}"`, + length: range.size, + type: mime.getType(fileDownloadPath), }); } + + /** + * Parses the range header and returns the start, end, and size of the range. + */ + private calculateRange( + rangeHeader: string | undefined, + fileSize: number, + ): RangeHeader { + let rangeStart: number = 0; + let rangeEnd: number = fileSize; + + if (rangeHeader?.includes("-")) { + const [extractedStart, extractedEnd] = rangeHeader + .replace("bytes=", "") + .split("-") + .map(Number); + + if (!isNaN(extractedStart) && extractedStart < fileSize) { + rangeStart = extractedStart; + } + if ( + !isNaN(extractedEnd) && + extractedEnd > rangeStart && + extractedEnd < fileSize + ) { + rangeEnd = extractedEnd; + } + } + + const rangeSize: number = rangeEnd - rangeStart; + return { + start: rangeStart, + end: rangeEnd, + size: rangeSize, + }; + } } diff --git a/src/modules/files/models/byte-range-stream.ts b/src/modules/files/models/byte-range-stream.ts new file mode 100644 index 00000000..2c7ff194 --- /dev/null +++ b/src/modules/files/models/byte-range-stream.ts @@ -0,0 +1,37 @@ +import { Transform } from "stream"; + +export default class ByteRangeStream extends Transform { + private bytesRead: bigint = BigInt(0); + private readonly startByte: bigint; + private readonly endByte: bigint; + + constructor(startByte: bigint, endByte: bigint) { + super(); + this.startByte = startByte; + this.endByte = endByte; + } + + _transform( + chunk: Buffer, + _encoding: string, + callback: (error?: Error | null, data?: Buffer) => void, + ) { + const chunkSize = BigInt(chunk.length); + if ( + this.bytesRead + chunkSize >= this.startByte && + this.bytesRead < this.endByte + ) { + const start = Number( + this.startByte > this.bytesRead ? this.startByte - this.bytesRead : 0n, + ); + const end = Number( + this.endByte > this.bytesRead + chunkSize + ? chunkSize + : this.endByte - this.bytesRead, + ); + this.push(chunk.subarray(start, end)); + } + this.bytesRead += chunkSize; + callback(); + } +} diff --git a/src/modules/files/models/range-header.model.ts b/src/modules/files/models/range-header.model.ts new file mode 100644 index 00000000..3b1e0f31 --- /dev/null +++ b/src/modules/files/models/range-header.model.ts @@ -0,0 +1,5 @@ +export interface RangeHeader { + start: number; + end: number; + size: number; +} diff --git a/src/modules/games/game.entity.ts b/src/modules/games/game.entity.ts index 87fbc828..8109baa7 100644 --- a/src/modules/games/game.entity.ts +++ b/src/modules/games/game.entity.ts @@ -1,24 +1,25 @@ import { ApiProperty, ApiPropertyOptional } from "@nestjs/swagger"; import { - Entity, Column, - OneToMany, - ManyToMany, + Entity, Index, - JoinTable, JoinColumn, + JoinTable, + ManyToMany, + OneToMany, OneToOne, } from "typeorm"; + +import { DatabaseEntity } from "../database/database.entity"; import { Developer } from "../developers/developer.entity"; import { Genre } from "../genres/genre.entity"; -import { Progress } from "../progress/progress.entity"; +import { Image } from "../images/image.entity"; +import { Progress } from "../progresses/progress.entity"; import { Publisher } from "../publishers/publisher.entity"; import { Store } from "../stores/store.entity"; import { Tag } from "../tags/tag.entity"; -import { Image } from "../images/image.entity"; -import { DatabaseEntity } from "../database/database.entity"; -import { GameType } from "./models/game-type.enum"; import { GamevaultUser } from "../users/gamevault-user.entity"; +import { GameType } from "./models/game-type.enum"; @Entity() export class Game extends DatabaseEntity { diff --git a/src/modules/games/games.controller.ts b/src/modules/games/games.controller.ts index f95c46bf..8ed297e8 100644 --- a/src/modules/games/games.controller.ts +++ b/src/modules/games/games.controller.ts @@ -2,6 +2,7 @@ import { Body, Controller, Get, + Header, Headers, Logger, Param, @@ -26,14 +27,15 @@ import { PaginationType, } from "nestjs-paginate"; import { Repository } from "typeorm"; -import { ApiOkResponsePaginated } from "../pagination/paginated-api-response.model"; + +import { MinimumRole } from "../../decorators/minimum-role.decorator"; import { PaginateQueryOptions } from "../../decorators/pagination.decorator"; +import { ApiOkResponsePaginated } from "../../globals"; 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 "../../decorators/minimum-role.decorator"; import { Role } from "../users/models/role.enum"; +import { Game } from "./game.entity"; +import { GamesService } from "./games.service"; import { UpdateGameDto } from "./models/update-game.dto"; @ApiBasicAuth() @@ -140,22 +142,45 @@ export class GamesController { name: "X-Download-Speed-Limit", required: false, description: - "This header lets you set the maximum download speed limit in kibibytes per second (kiB/s) for your request. (Default unlimited)", + "This header lets you set the maximum download speed limit in kibibytes per second (kiB/s) for your request. If the header is not present the download speed limit will be unlimited.", example: "1024", }) + @ApiHeader({ + name: "Range", + required: false, + description: + "This header lets you control the range of bytes to download. If the header is not present or not valid the entire file will be downloaded.", + examples: { + "bytes=0-1023": { + description: "Download the first 1024 bytes", + value: "bytes=-1023", + }, + "bytes=1024-2047": { + description: "Download the bytes 1024 through 2047", + value: "bytes=1024-2047", + }, + "bytes=1024-": { + description: "Download the bytes 1024 through the end of the file", + value: "bytes=1024-", + }, + }, + }) @ApiOperation({ summary: "download a game", operationId: "getGameDownload", }) @MinimumRole(Role.USER) @ApiOkResponse({ type: () => StreamableFile }) + @Header("Accept-Ranges", "bytes") async getGameDownload( @Param() params: IdDto, @Headers("X-Download-Speed-Limit") speedlimit?: string, + @Headers("Range") range?: string, ): Promise { return await this.filesService.download( Number(params.id), Number(speedlimit), + range, ); } diff --git a/src/modules/games/games.e2e.spec.ts b/src/modules/games/games.e2e.spec.ts index 271da4bd..b6833c2c 100644 --- a/src/modules/games/games.e2e.spec.ts +++ b/src/modules/games/games.e2e.spec.ts @@ -1,14 +1,16 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ -import { Test } from "@nestjs/testing"; import { HttpService } from "@nestjs/axios"; -import { AppModule } from "../../app.module"; -import { Game } from "../games/game.entity"; +import { Test } from "@nestjs/testing"; import { getRepositoryToken } from "@nestjs/typeorm"; -import { Repository } from "typeorm"; -import { Builder } from "builder-pattern"; -import { GamesController } from "./games.controller"; import gis, { Result } from "async-g-i-s"; +import { Builder } from "builder-pattern"; import { of } from "rxjs"; +import { Repository } from "typeorm"; + +import { AppModule } from "../../app.module"; +import { Game } from "../games/game.entity"; +import { GamesController } from "./games.controller"; + jest.mock("async-g-i-s"); describe("/api/games", () => { diff --git a/src/modules/games/games.module.ts b/src/modules/games/games.module.ts index 97111c22..1a48e857 100644 --- a/src/modules/games/games.module.ts +++ b/src/modules/games/games.module.ts @@ -1,12 +1,13 @@ -import { Module, forwardRef } from "@nestjs/common"; -import { GamesController } from "./games.controller"; -import { GamesService } from "./games.service"; -import { Game } from "./game.entity"; +import { forwardRef, Module } from "@nestjs/common"; import { TypeOrmModule } from "@nestjs/typeorm"; -import { RawgModule } from "../providers/rawg/rawg.module"; + import { BoxartsModule } from "../boxarts/boxarts.module"; import { FilesModule } from "../files/files.module"; import { ImagesModule } from "../images/images.module"; +import { RawgModule } from "../providers/rawg/rawg.module"; +import { Game } from "./game.entity"; +import { GamesController } from "./games.controller"; +import { GamesService } from "./games.service"; @Module({ imports: [ diff --git a/src/modules/games/games.service.ts b/src/modules/games/games.service.ts index 135211b5..1c84330e 100644 --- a/src/modules/games/games.service.ts +++ b/src/modules/games/games.service.ts @@ -8,13 +8,14 @@ import { } from "@nestjs/common"; import { InjectRepository } from "@nestjs/typeorm"; import { Repository } from "typeorm"; -import { Game } from "./game.entity"; + +import { FindOptions } from "../../globals"; +import { BoxArtsService } from "../boxarts/boxarts.service"; +import { ImagesService } from "../images/images.service"; import { RawgService } from "../providers/rawg/rawg.service"; +import { Game } from "./game.entity"; import { GameExistence } from "./models/game-existence.enum"; -import { BoxArtsService } from "../boxarts/boxarts.service"; import { UpdateGameDto } from "./models/update-game.dto"; -import { ImagesService } from "../images/images.service"; -import { FindOptions } from "../../globals"; @Injectable() export class GamesService { diff --git a/src/modules/games/models/minimal-game.ts b/src/modules/games/models/minimal-game.ts index 634aebc8..b76a297f 100644 --- a/src/modules/games/models/minimal-game.ts +++ b/src/modules/games/models/minimal-game.ts @@ -1,4 +1,5 @@ import { ApiPropertyOptional } from "@nestjs/swagger"; + import { Image } from "../../images/image.entity"; export class MinimalGame { diff --git a/src/modules/garbage-collection/garbage-collection.module.ts b/src/modules/garbage-collection/garbage-collection.module.ts index 07abfe05..c3604f81 100644 --- a/src/modules/garbage-collection/garbage-collection.module.ts +++ b/src/modules/garbage-collection/garbage-collection.module.ts @@ -1,10 +1,11 @@ import { Module } from "@nestjs/common"; import { TypeOrmModule } from "@nestjs/typeorm"; + import { Game } from "../games/game.entity"; +import { Image } from "../images/image.entity"; +import { ImagesModule } from "../images/images.module"; import { GamevaultUser } from "../users/gamevault-user.entity"; import { ImageGarbageCollectionService } from "./image-garbage-collection.service"; -import { ImagesModule } from "../images/images.module"; -import { Image } from "../images/image.entity"; @Module({ imports: [ diff --git a/src/modules/garbage-collection/image-garbage-collection.service.ts b/src/modules/garbage-collection/image-garbage-collection.service.ts index be69f07e..2c63b3ec 100644 --- a/src/modules/garbage-collection/image-garbage-collection.service.ts +++ b/src/modules/garbage-collection/image-garbage-collection.service.ts @@ -1,15 +1,16 @@ import { Injectable, Logger } from "@nestjs/common"; +import { Cron } from "@nestjs/schedule"; import { InjectRepository } from "@nestjs/typeorm"; +import { isUUID } from "class-validator"; +import { readdir, unlink } from "fs/promises"; +import { join } from "path"; import { Repository } from "typeorm"; + +import configuration from "../../configuration"; import { Game } from "../games/game.entity"; -import { GamevaultUser } from "../users/gamevault-user.entity"; import { Image } from "../images/image.entity"; -import configuration from "../../configuration"; -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"; +import { GamevaultUser } from "../users/gamevault-user.entity"; @Injectable() export class ImageGarbageCollectionService { diff --git a/src/modules/genres/genre.entity.ts b/src/modules/genres/genre.entity.ts index d8abfa56..bc8749c7 100644 --- a/src/modules/genres/genre.entity.ts +++ b/src/modules/genres/genre.entity.ts @@ -1,17 +1,18 @@ -import { ApiProperty } from "@nestjs/swagger"; -import { Entity, Column, ManyToMany, Index } from "typeorm"; -import { Game } from "../games/game.entity"; +import { ApiProperty, ApiPropertyOptional } from "@nestjs/swagger"; +import { Column, Entity, Index, ManyToMany } from "typeorm"; + import { DatabaseEntity } from "../database/database.entity"; +import { Game } from "../games/game.entity"; @Entity() export class Genre extends DatabaseEntity { @Index() - @Column({ unique: true }) - @ApiProperty({ + @Column({ nullable: true }) + @ApiPropertyOptional({ example: 1000, description: "unique rawg-api-identifier of the genre", }) - rawg_id: number; + rawg_id?: number; @Index() @Column({ unique: true }) diff --git a/src/modules/genres/genres.controller.ts b/src/modules/genres/genres.controller.ts index 04acec79..43a477e0 100644 --- a/src/modules/genres/genres.controller.ts +++ b/src/modules/genres/genres.controller.ts @@ -7,9 +7,10 @@ import { } from "@nestjs/swagger"; import { InjectRepository } from "@nestjs/typeorm"; import { Repository } from "typeorm"; -import { Genre } from "./genre.entity"; + import { MinimumRole } from "../../decorators/minimum-role.decorator"; import { Role } from "../users/models/role.enum"; +import { Genre } from "./genre.entity"; @Controller("genres") @ApiTags("genres") diff --git a/src/modules/genres/genres.e2e.spec.ts b/src/modules/genres/genres.e2e.spec.ts index ec3e2d14..2500adfe 100644 --- a/src/modules/genres/genres.e2e.spec.ts +++ b/src/modules/genres/genres.e2e.spec.ts @@ -1,11 +1,12 @@ import { Test } from "@nestjs/testing"; import { getRepositoryToken } from "@nestjs/typeorm"; -import { Genre } from "./genre.entity"; -import { Game } from "../games/game.entity"; import { Builder } from "builder-pattern"; import { Repository } from "typeorm/repository/Repository"; -import { GenresController } from "./genres.controller"; + import { AppModule } from "../../app.module"; +import { Game } from "../games/game.entity"; +import { Genre } from "./genre.entity"; +import { GenresController } from "./genres.controller"; describe("GenresController", () => { let genresController: GenresController; diff --git a/src/modules/genres/genres.module.ts b/src/modules/genres/genres.module.ts index c0c9795e..557605b6 100644 --- a/src/modules/genres/genres.module.ts +++ b/src/modules/genres/genres.module.ts @@ -1,9 +1,10 @@ -import { Module, forwardRef } from "@nestjs/common"; -import { GenresService } from "./genres.service"; -import { GenresController } from "./genres.controller"; +import { forwardRef, Module } from "@nestjs/common"; import { TypeOrmModule } from "@nestjs/typeorm"; -import { Genre } from "./genre.entity"; + import { FilesModule } from "../files/files.module"; +import { Genre } from "./genre.entity"; +import { GenresController } from "./genres.controller"; +import { GenresService } from "./genres.service"; @Module({ imports: [TypeOrmModule.forFeature([Genre]), forwardRef(() => FilesModule)], diff --git a/src/modules/genres/genres.service.ts b/src/modules/genres/genres.service.ts index 17480839..e75c41f6 100644 --- a/src/modules/genres/genres.service.ts +++ b/src/modules/genres/genres.service.ts @@ -1,8 +1,9 @@ import { Injectable, Logger } from "@nestjs/common"; import { InjectRepository } from "@nestjs/typeorm"; +import { Builder } from "builder-pattern"; import { Repository } from "typeorm"; + import { Genre } from "./genre.entity"; -import { Builder } from "builder-pattern"; @Injectable() export class GenresService { @@ -17,7 +18,7 @@ export class GenresService { * does not already exist. */ async getOrCreate(name: string, rawg_id: number): Promise { - const existingGenre = await this.tagRepository.findOneBy({ rawg_id }); + const existingGenre = await this.tagRepository.findOneBy({ name }); if (existingGenre) return existingGenre; diff --git a/src/modules/guards/authentication.guard.ts b/src/modules/guards/authentication.guard.ts index ae4b4643..75c15592 100644 --- a/src/modules/guards/authentication.guard.ts +++ b/src/modules/guards/authentication.guard.ts @@ -1,6 +1,7 @@ -import { Injectable, ExecutionContext, Logger } from "@nestjs/common"; +import { ExecutionContext, Injectable, Logger } from "@nestjs/common"; import { Reflector } from "@nestjs/core"; import { AuthGuard } from "@nestjs/passport"; + import configuration from "../../configuration"; @Injectable() diff --git a/src/modules/guards/authorization.guard.ts b/src/modules/guards/authorization.guard.ts index 1246061e..7ef78856 100644 --- a/src/modules/guards/authorization.guard.ts +++ b/src/modules/guards/authorization.guard.ts @@ -1,14 +1,15 @@ import { - Injectable, CanActivate, ExecutionContext, - Logger, ForbiddenException, + Injectable, + Logger, } from "@nestjs/common"; import { Reflector } from "@nestjs/core"; + import configuration from "../../configuration"; -import { Role } from "../users/models/role.enum"; import { MINIMUM_ROLE_KEY } from "../../decorators/minimum-role.decorator"; +import { Role } from "../users/models/role.enum"; @Injectable() export class AuthorizationGuard implements CanActivate { diff --git a/src/modules/guards/basic-auth.strategy.ts b/src/modules/guards/basic-auth.strategy.ts index b809f80c..1a75239e 100644 --- a/src/modules/guards/basic-auth.strategy.ts +++ b/src/modules/guards/basic-auth.strategy.ts @@ -1,6 +1,7 @@ -import { BasicStrategy } from "passport-http"; import { Injectable, Logger } from "@nestjs/common"; import { PassportStrategy } from "@nestjs/passport"; +import { BasicStrategy } from "passport-http"; + import { GamevaultUser } from "../users/gamevault-user.entity"; import { UsersService } from "../users/users.service"; diff --git a/src/modules/guards/socket-secret.guard.ts b/src/modules/guards/socket-secret.guard.ts index b99e5983..ef7cf006 100644 --- a/src/modules/guards/socket-secret.guard.ts +++ b/src/modules/guards/socket-secret.guard.ts @@ -1,10 +1,11 @@ import { - Injectable, CanActivate, ExecutionContext, + Injectable, Logger, } from "@nestjs/common"; import { Socket } from "socket.io"; + import { SocketSecretService } from "../users/socket-secret.service"; @Injectable() diff --git a/src/modules/health/health.controller.ts b/src/modules/health/health.controller.ts index 581ace73..067c5b00 100644 --- a/src/modules/health/health.controller.ts +++ b/src/modules/health/health.controller.ts @@ -1,8 +1,9 @@ import { Controller, Get } from "@nestjs/common"; 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 { Health } from "./models/health.model"; @Controller("health") @ApiTags("health") diff --git a/src/modules/health/health.e2e.spec.ts b/src/modules/health/health.e2e.spec.ts index 5be0cfec..90dd2659 100644 --- a/src/modules/health/health.e2e.spec.ts +++ b/src/modules/health/health.e2e.spec.ts @@ -1,4 +1,5 @@ import { Test } from "@nestjs/testing"; + import { AppModule } from "../../app.module"; import { HealthController } from "./health.controller"; diff --git a/src/modules/health/health.module.ts b/src/modules/health/health.module.ts index 4516cb23..cc4170c3 100644 --- a/src/modules/health/health.module.ts +++ b/src/modules/health/health.module.ts @@ -1,6 +1,8 @@ -import { HealthService } from "./health.service"; import { Module } from "@nestjs/common"; + import { HealthController } from "./health.controller"; +import { HealthService } from "./health.service"; + @Module({ imports: [], controllers: [HealthController], diff --git a/src/modules/health/health.service.ts b/src/modules/health/health.service.ts index f7edd337..57384158 100644 --- a/src/modules/health/health.service.ts +++ b/src/modules/health/health.service.ts @@ -1,6 +1,7 @@ import { Injectable } from "@nestjs/common"; -import { Health, HealthProtocolEntry } from "./models/health.model"; + import configuration from "../../configuration"; +import { Health, HealthProtocolEntry } from "./models/health.model"; import { HealthStatus } from "./models/health-status.enum"; @Injectable() diff --git a/src/modules/health/models/health.model.ts b/src/modules/health/models/health.model.ts index 2e14bdd8..081a0612 100644 --- a/src/modules/health/models/health.model.ts +++ b/src/modules/health/models/health.model.ts @@ -1,4 +1,5 @@ import { ApiProperty, ApiPropertyOptional } from "@nestjs/swagger"; + import { HealthStatus } from "./health-status.enum"; export class HealthProtocolEntry { diff --git a/src/modules/images/image.entity.ts b/src/modules/images/image.entity.ts index 9c5837e6..cea783e4 100644 --- a/src/modules/images/image.entity.ts +++ b/src/modules/images/image.entity.ts @@ -1,5 +1,6 @@ import { ApiPropertyOptional } from "@nestjs/swagger"; -import { Entity, Column, Index, ManyToOne } from "typeorm"; +import { Column, Entity, Index, ManyToOne } from "typeorm"; + import { DatabaseEntity } from "../database/database.entity"; import { GamevaultUser } from "../users/gamevault-user.entity"; diff --git a/src/modules/images/images.controller.ts b/src/modules/images/images.controller.ts index 14f20542..69ae08fb 100644 --- a/src/modules/images/images.controller.ts +++ b/src/modules/images/images.controller.ts @@ -12,6 +12,7 @@ import { UploadedFile, UseInterceptors, } from "@nestjs/common"; +import { FileInterceptor } from "@nestjs/platform-express"; import { ApiBasicAuth, ApiBody, @@ -21,16 +22,16 @@ import { ApiProduces, ApiTags, } from "@nestjs/swagger"; -import { ImagesService } from "./images.service"; -import fs from "fs"; import { Response } from "express"; +import fs from "fs"; + +import configuration from "../../configuration"; +import { DisableApiIf } from "../../decorators/disable-api-if.decorator"; import { MinimumRole } from "../../decorators/minimum-role.decorator"; +import { GamevaultUser } from "../users/gamevault-user.entity"; import { Role } from "../users/models/role.enum"; -import { FileInterceptor } from "@nestjs/platform-express"; import { Image } from "./image.entity"; -import configuration from "../../configuration"; -import { GamevaultUser } from "../users/gamevault-user.entity"; -import { DisableApiIf } from "../../decorators/disable-api-if.decorator"; +import { ImagesService } from "./images.service"; @ApiTags("images") @Controller("images") diff --git a/src/modules/images/images.module.ts b/src/modules/images/images.module.ts index e10738eb..4400d932 100644 --- a/src/modules/images/images.module.ts +++ b/src/modules/images/images.module.ts @@ -1,10 +1,11 @@ -import { Module, forwardRef } from "@nestjs/common"; -import { ImagesService } from "./images.service"; -import { ImagesController } from "./images.controller"; import { HttpModule } from "@nestjs/axios"; +import { forwardRef, Module } from "@nestjs/common"; import { TypeOrmModule } from "@nestjs/typeorm"; -import { Image } from "./image.entity"; + import { UsersModule } from "../users/users.module"; +import { Image } from "./image.entity"; +import { ImagesController } from "./images.controller"; +import { ImagesService } from "./images.service"; @Module({ imports: [ diff --git a/src/modules/images/images.service.ts b/src/modules/images/images.service.ts index 9aeea8ec..021e505b 100644 --- a/src/modules/images/images.service.ts +++ b/src/modules/images/images.service.ts @@ -1,25 +1,26 @@ +import { HttpService } from "@nestjs/axios"; import { BadRequestException, + forwardRef, Inject, Injectable, InternalServerErrorException, Logger, NotFoundException, - forwardRef, } from "@nestjs/common"; import { InjectRepository } from "@nestjs/typeorm"; -import { Repository } from "typeorm"; -import { Image } from "./image.entity"; -import configuration from "../../configuration"; -import sharp from "sharp"; -import { HttpService } from "@nestjs/axios"; -import { catchError, firstValueFrom } from "rxjs"; import { AxiosError, AxiosResponse } from "axios"; import { randomUUID } from "crypto"; import fileTypeChecker from "file-type-checker"; -import { UsersService } from "../users/users.service"; -import { unlink, writeFile } from "fs/promises"; import { existsSync } from "fs"; +import { unlink, writeFile } from "fs/promises"; +import { catchError, firstValueFrom } from "rxjs"; +import sharp from "sharp"; +import { Repository } from "typeorm"; + +import configuration from "../../configuration"; +import { UsersService } from "../users/users.service"; +import { Image } from "./image.entity"; @Injectable() export class ImagesService { diff --git a/src/modules/pagination/paginated-api-response.model.ts b/src/modules/pagination/paginated-api-response.model.ts index 39ee6e89..e69de29b 100644 --- a/src/modules/pagination/paginated-api-response.model.ts +++ b/src/modules/pagination/paginated-api-response.model.ts @@ -1,27 +0,0 @@ -import { applyDecorators, Type } from "@nestjs/common"; -import { ApiExtraModels, ApiOkResponse, getSchemaPath } from "@nestjs/swagger"; -import { PaginatedEntity } from "../database/models/paginated-entity.model"; - -export const ApiOkResponsePaginated = >( - dataDto: DataDto, -) => - applyDecorators( - ApiExtraModels(PaginatedEntity, dataDto), - ApiOkResponse({ - schema: { - required: ["data", "meta", "links"], - allOf: [ - { - properties: { - data: { - description: "paginated list of entities", - type: "array", - items: { $ref: getSchemaPath(dataDto) }, - }, - }, - }, - { $ref: getSchemaPath(PaginatedEntity) }, - ], - }, - }), - ); diff --git a/src/modules/plugin/plugin.module.ts b/src/modules/plugins/plugin.module.ts similarity index 92% rename from src/modules/plugin/plugin.module.ts rename to src/modules/plugins/plugin.module.ts index f592f1a2..3e1ff5a8 100644 --- a/src/modules/plugin/plugin.module.ts +++ b/src/modules/plugins/plugin.module.ts @@ -1,5 +1,5 @@ import { Module } from "@nestjs/common"; -import { PluginService } from "./plugin.service"; + import { AdminModule } from "../admin/admin.module"; import { BoxartsModule } from "../boxarts/boxarts.module"; import { DatabaseModule } from "../database/database.module"; @@ -10,12 +10,13 @@ import { GarbageCollectionModule } from "../garbage-collection/garbage-collectio import { GenresModule } from "../genres/genres.module"; import { HealthModule } from "../health/health.module"; import { ImagesModule } from "../images/images.module"; -import { ProgressModule } from "../progress/progress.module"; +import { ProgressModule } from "../progresses/progress.module"; import { RawgModule } from "../providers/rawg/rawg.module"; import { PublishersModule } from "../publishers/publishers.module"; import { StoresModule } from "../stores/stores.module"; import { TagsModule } from "../tags/tags.module"; import { UsersModule } from "../users/users.module"; +import { PluginService } from "./plugin.service"; @Module({ providers: [PluginService], diff --git a/src/modules/plugin/plugin.service.ts b/src/modules/plugins/plugin.service.ts similarity index 96% rename from src/modules/plugin/plugin.service.ts rename to src/modules/plugins/plugin.service.ts index ccea8a65..402a3d4c 100644 --- a/src/modules/plugin/plugin.service.ts +++ b/src/modules/plugins/plugin.service.ts @@ -1,8 +1,9 @@ import { Injectable, Logger, OnApplicationBootstrap } from "@nestjs/common"; import axios from "axios"; +import path from "path"; import * as vm from "vm"; + import configuration from "../../configuration"; -import path from "path"; import { BoxArtsService } from "../boxarts/boxarts.service"; import { DatabaseService } from "../database/database.service"; import { DevelopersService } from "../developers/developers.service"; @@ -12,7 +13,7 @@ import { ImageGarbageCollectionService } from "../garbage-collection/image-garba import { GenresService } from "../genres/genres.service"; import { HealthService } from "../health/health.service"; import { ImagesService } from "../images/images.service"; -import { ProgressService } from "../progress/progress.service"; +import { ProgressService } from "../progresses/progress.service"; import { RawgService } from "../providers/rawg/rawg.service"; import { PublishersService } from "../publishers/publishers.service"; import { StoresService } from "../stores/stores.service"; diff --git a/src/modules/progress/models/increment-progress-by-minutes.dto.ts b/src/modules/progresses/models/increment-progress-by-minutes.dto.ts similarity index 100% rename from src/modules/progress/models/increment-progress-by-minutes.dto.ts rename to src/modules/progresses/models/increment-progress-by-minutes.dto.ts diff --git a/src/modules/progress/models/state.enum.ts b/src/modules/progresses/models/state.enum.ts similarity index 100% rename from src/modules/progress/models/state.enum.ts rename to src/modules/progresses/models/state.enum.ts diff --git a/src/modules/progress/models/update-progress.dto.ts b/src/modules/progresses/models/update-progress.dto.ts similarity index 99% rename from src/modules/progress/models/update-progress.dto.ts rename to src/modules/progresses/models/update-progress.dto.ts index f6030332..a0045a76 100644 --- a/src/modules/progress/models/update-progress.dto.ts +++ b/src/modules/progresses/models/update-progress.dto.ts @@ -1,5 +1,6 @@ import { ApiPropertyOptional } from "@nestjs/swagger"; import { IsEnum, IsNotEmpty, IsNumber, IsOptional } from "class-validator"; + import { State } from "./state.enum"; export class UpdateProgressDto { diff --git a/src/modules/progress/models/user-id-game-id.dto.ts b/src/modules/progresses/models/user-id-game-id.dto.ts similarity index 100% rename from src/modules/progress/models/user-id-game-id.dto.ts rename to src/modules/progresses/models/user-id-game-id.dto.ts diff --git a/src/modules/progress/progress.controller.ts b/src/modules/progresses/progress.controller.ts similarity index 96% rename from src/modules/progress/progress.controller.ts rename to src/modules/progresses/progress.controller.ts index 457a258a..5b20c833 100644 --- a/src/modules/progress/progress.controller.ts +++ b/src/modules/progresses/progress.controller.ts @@ -15,17 +15,18 @@ import { ApiOperation, ApiTags, } from "@nestjs/swagger"; -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 configuration from "../../configuration"; +import { DisableApiIf } from "../../decorators/disable-api-if.decorator"; import { MinimumRole } from "../../decorators/minimum-role.decorator"; -import { Role } from "../users/models/role.enum"; +import { IdDto } from "../database/models/id.dto"; import { GamevaultUser } from "../users/gamevault-user.entity"; +import { Role } from "../users/models/role.enum"; +import { IncrementProgressByMinutesDto } from "./models/increment-progress-by-minutes.dto"; import { UpdateProgressDto } from "./models/update-progress.dto"; import { UserIdGameIdDto } from "./models/user-id-game-id.dto"; -import { DisableApiIf } from "../../decorators/disable-api-if.decorator"; -import configuration from "../../configuration"; +import { Progress } from "./progress.entity"; +import { ProgressService } from "./progress.service"; @Controller("progresses") @ApiTags("progress") diff --git a/src/modules/progress/progress.entity.ts b/src/modules/progresses/progress.entity.ts similarity index 95% rename from src/modules/progress/progress.entity.ts rename to src/modules/progresses/progress.entity.ts index 9fed67ac..4d8b5390 100644 --- a/src/modules/progress/progress.entity.ts +++ b/src/modules/progresses/progress.entity.ts @@ -1,9 +1,10 @@ import { ApiProperty, ApiPropertyOptional } from "@nestjs/swagger"; -import { Entity, Column, ManyToOne, Index } from "typeorm"; -import { State } from "./models/state.enum"; +import { Column, Entity, Index, ManyToOne } from "typeorm"; + +import { DatabaseEntity } from "../database/database.entity"; import { Game } from "../games/game.entity"; import { GamevaultUser } from "../users/gamevault-user.entity"; -import { DatabaseEntity } from "../database/database.entity"; +import { State } from "./models/state.enum"; @Entity() export class Progress extends DatabaseEntity { diff --git a/src/modules/progress/progress.module.ts b/src/modules/progresses/progress.module.ts similarity index 97% rename from src/modules/progress/progress.module.ts rename to src/modules/progresses/progress.module.ts index 7da9503b..215c93b7 100644 --- a/src/modules/progress/progress.module.ts +++ b/src/modules/progresses/progress.module.ts @@ -1,10 +1,11 @@ import { Module } from "@nestjs/common"; -import { ProgressService } from "./progress.service"; -import { ProgressController } from "./progress.controller"; import { TypeOrmModule } from "@nestjs/typeorm"; -import { Progress } from "./progress.entity"; -import { UsersModule } from "../users/users.module"; + import { GamesModule } from "../games/games.module"; +import { UsersModule } from "../users/users.module"; +import { ProgressController } from "./progress.controller"; +import { Progress } from "./progress.entity"; +import { ProgressService } from "./progress.service"; @Module({ imports: [TypeOrmModule.forFeature([Progress]), UsersModule, GamesModule], diff --git a/src/modules/progress/progress.service.ts b/src/modules/progresses/progress.service.ts similarity index 96% rename from src/modules/progress/progress.service.ts rename to src/modules/progresses/progress.service.ts index eeb868d0..066e3c6b 100644 --- a/src/modules/progress/progress.service.ts +++ b/src/modules/progresses/progress.service.ts @@ -6,14 +6,15 @@ import { NotFoundException, } from "@nestjs/common"; import { InjectRepository } from "@nestjs/typeorm"; +import { readFile } from "fs/promises"; +import path from "path"; import { IsNull, Repository } from "typeorm"; -import { Progress } from "./progress.entity"; -import { State } from "./models/state.enum"; + import { GamesService } from "../games/games.service"; import { UsersService } from "../users/users.service"; -import path from "path"; +import { State } from "./models/state.enum"; import { UpdateProgressDto } from "./models/update-progress.dto"; -import { readFile } from "fs/promises"; +import { Progress } from "./progress.entity"; @Injectable() export class ProgressService { diff --git a/src/modules/providers/rawg/mapper.service.ts b/src/modules/providers/rawg/mapper.service.ts index 6f4ac9a3..bad25031 100644 --- a/src/modules/providers/rawg/mapper.service.ts +++ b/src/modules/providers/rawg/mapper.service.ts @@ -1,12 +1,13 @@ import { Injectable, Logger } from "@nestjs/common"; -import { Game } from "../../games/game.entity"; -import { RawgGame } from "./models/game.interface"; + import { DevelopersService } from "../../developers/developers.service"; +import { Game } from "../../games/game.entity"; import { GenresService } from "../../genres/genres.service"; import { ImagesService } from "../../images/images.service"; import { PublishersService } from "../../publishers/publishers.service"; import { StoresService } from "../../stores/stores.service"; import { TagsService } from "../../tags/tags.service"; +import { RawgGame } from "./models/game.interface"; @Injectable() export class RawgMapperService { @@ -193,6 +194,17 @@ export class RawgMapperService { game.background_image, ); } + + if ( + game.box_image && + (!entity.box_image?.id || + !(await this.imagesService.isAvailable(entity.box_image.id))) + ) { + entity.box_image = await this.imagesService.downloadByUrl( + game.box_image, + ); + } + entity.rawg_title = game.name ?? entity.rawg_title; entity.rawg_id = game.id ?? entity.rawg_id; entity.description = game.description_raw ?? entity.description; diff --git a/src/modules/providers/rawg/models/game.interface.ts b/src/modules/providers/rawg/models/game.interface.ts index 9129e52b..7f93a303 100644 --- a/src/modules/providers/rawg/models/game.interface.ts +++ b/src/modules/providers/rawg/models/game.interface.ts @@ -122,7 +122,8 @@ export interface RawgGame { released: string; tba: boolean; updated: Date; - background_image: string; + box_image?: string; + background_image?: string; background_image_additional: string; website: string; rating: number; diff --git a/src/modules/providers/rawg/rawg.controller.ts b/src/modules/providers/rawg/rawg.controller.ts index bf48a2e3..b2f9fab4 100644 --- a/src/modules/providers/rawg/rawg.controller.ts +++ b/src/modules/providers/rawg/rawg.controller.ts @@ -6,14 +6,15 @@ import { ApiQuery, ApiTags, } from "@nestjs/swagger"; -import { RawgService } from "./rawg.service"; -import { Game } from "../../games/game.entity"; + import { MinimumRole } from "../../../decorators/minimum-role.decorator"; -import { Role } from "../../users/models/role.enum"; +import { BoxArtsService } from "../../boxarts/boxarts.service"; import { IdDto } from "../../database/models/id.dto"; +import { Game } from "../../games/game.entity"; import { GamesService } from "../../games/games.service"; -import { BoxArtsService } from "../../boxarts/boxarts.service"; import { MinimalGame } from "../../games/models/minimal-game"; +import { Role } from "../../users/models/role.enum"; +import { RawgService } from "./rawg.service"; @ApiTags("rawg") @Controller("rawg") diff --git a/src/modules/providers/rawg/rawg.e2e.spec.ts b/src/modules/providers/rawg/rawg.e2e.spec.ts index 262c0b7d..d242c467 100644 --- a/src/modules/providers/rawg/rawg.e2e.spec.ts +++ b/src/modules/providers/rawg/rawg.e2e.spec.ts @@ -1,15 +1,17 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ -import { Test } from "@nestjs/testing"; -import { RawgController } from "./rawg.controller"; import { HttpService } from "@nestjs/axios"; +import { Test } from "@nestjs/testing"; +import { getRepositoryToken } from "@nestjs/typeorm"; +import gis, { Result } from "async-g-i-s"; +import { Builder } from "builder-pattern"; +import { of } from "rxjs"; +import { Repository } from "typeorm"; + import { AppModule } from "../../../app.module"; import configuration from "../../../configuration"; -import { of } from "rxjs"; import { Game } from "../../games/game.entity"; -import { getRepositoryToken } from "@nestjs/typeorm"; -import { Repository } from "typeorm"; -import { Builder } from "builder-pattern"; -import gis, { Result } from "async-g-i-s"; +import { RawgController } from "./rawg.controller"; + jest.mock("async-g-i-s"); describe("/api/rawg", () => { diff --git a/src/modules/providers/rawg/rawg.module.ts b/src/modules/providers/rawg/rawg.module.ts index 0572b40e..cae6abc8 100644 --- a/src/modules/providers/rawg/rawg.module.ts +++ b/src/modules/providers/rawg/rawg.module.ts @@ -1,16 +1,17 @@ -import { Module, forwardRef } from "@nestjs/common"; -import { RawgService } from "./rawg.service"; -import { RawgController } from "./rawg.controller"; -import { RawgMapperService } from "./mapper.service"; -import { GamesModule } from "../../games/games.module"; -import { BoxartsModule } from "../../boxarts/boxarts.module"; import { HttpModule } from "@nestjs/axios"; -import { TagsModule } from "../../tags/tags.module"; +import { forwardRef, Module } from "@nestjs/common"; + +import { BoxartsModule } from "../../boxarts/boxarts.module"; +import { DevelopersModule } from "../../developers/developers.module"; +import { GamesModule } from "../../games/games.module"; import { GenresModule } from "../../genres/genres.module"; +import { ImagesModule } from "../../images/images.module"; import { PublishersModule } from "../../publishers/publishers.module"; -import { DevelopersModule } from "../../developers/developers.module"; import { StoresModule } from "../../stores/stores.module"; -import { ImagesModule } from "../../images/images.module"; +import { TagsModule } from "../../tags/tags.module"; +import { RawgMapperService } from "./mapper.service"; +import { RawgController } from "./rawg.controller"; +import { RawgService } from "./rawg.service"; @Module({ imports: [ diff --git a/src/modules/providers/rawg/rawg.service.ts b/src/modules/providers/rawg/rawg.service.ts index f64ec200..8d3bab96 100644 --- a/src/modules/providers/rawg/rawg.service.ts +++ b/src/modules/providers/rawg/rawg.service.ts @@ -1,3 +1,4 @@ +import { HttpService } from "@nestjs/axios"; import { forwardRef, Inject, @@ -6,19 +7,19 @@ import { Logger, NotFoundException, } from "@nestjs/common"; +import { AxiosError } from "axios"; +import { ClientRequest } from "http"; +import { catchError, firstValueFrom } from "rxjs"; +import stringSimilarity from "string-similarity-js"; + import configuration from "../../../configuration"; +import { Game } from "../../games/game.entity"; import { GamesService } from "../../games/games.service"; import { RawgMapperService } from "./mapper.service"; import { RawgGame } from "./models/game.interface"; import { Result as RawgResult, SearchResult } from "./models/games.interface"; -import { Game } from "../../games/game.entity"; -import { catchError, firstValueFrom } from "rxjs"; -import { HttpService } from "@nestjs/axios"; -import { AxiosError } from "axios"; -import stringSimilarity from "string-similarity-js"; import { RawgPlatform } from "./models/platforms"; import { RawgStore } from "./models/stores"; -import { ClientRequest } from "http"; @Injectable() export class RawgService { @@ -52,7 +53,10 @@ export class RawgService { } // Skip cache check if RAWG API key is not set - if (!configuration.RAWG_API.KEY) { + if ( + !configuration.RAWG_API.KEY && + configuration.RAWG_API.URL === "https://api.rawg.io/api" + ) { this.logger.warn({ message: "Skipping RAWG Cache Check.", reason: "RAWG_API_KEY is not set.", @@ -159,19 +163,11 @@ export class RawgService { ): Promise { const sortedResults = await this.fetchMatching(title, releaseYear); - 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({ - message: `Found ${matches.length} matches on RAWG.`, + message: `Found ${sortedResults.length} matches on RAWG.`, title, releaseYear, - matches, + sortedResults, }); return this.fetchByRawgId(sortedResults[0].id); diff --git a/src/modules/publishers/publisher.entity.ts b/src/modules/publishers/publisher.entity.ts index aeb37554..b62eef7a 100644 --- a/src/modules/publishers/publisher.entity.ts +++ b/src/modules/publishers/publisher.entity.ts @@ -1,17 +1,18 @@ -import { ApiProperty } from "@nestjs/swagger"; -import { Entity, Column, ManyToMany, Index } from "typeorm"; -import { Game } from "../games/game.entity"; +import { ApiProperty, ApiPropertyOptional } from "@nestjs/swagger"; +import { Column, Entity, Index, ManyToMany } from "typeorm"; + import { DatabaseEntity } from "../database/database.entity"; +import { Game } from "../games/game.entity"; @Entity() export class Publisher extends DatabaseEntity { @Index() - @Column({ unique: true }) - @ApiProperty({ + @Column({ nullable: true }) + @ApiPropertyOptional({ example: 1000, description: "unique rawg-api-identifier of the publisher", }) - rawg_id: number; + rawg_id?: number; @Index() @Column({ unique: true }) diff --git a/src/modules/publishers/publishers.module.ts b/src/modules/publishers/publishers.module.ts index 8db123ed..fcdfcfdf 100644 --- a/src/modules/publishers/publishers.module.ts +++ b/src/modules/publishers/publishers.module.ts @@ -1,7 +1,8 @@ import { Module } from "@nestjs/common"; -import { PublishersService } from "./publishers.service"; import { TypeOrmModule } from "@nestjs/typeorm"; + import { Publisher } from "./publisher.entity"; +import { PublishersService } from "./publishers.service"; @Module({ imports: [TypeOrmModule, TypeOrmModule.forFeature([Publisher])], diff --git a/src/modules/publishers/publishers.service.ts b/src/modules/publishers/publishers.service.ts index 8b4322b3..5c953e81 100644 --- a/src/modules/publishers/publishers.service.ts +++ b/src/modules/publishers/publishers.service.ts @@ -1,8 +1,9 @@ import { Injectable, Logger } from "@nestjs/common"; import { InjectRepository } from "@nestjs/typeorm"; +import { Builder } from "builder-pattern"; import { Repository } from "typeorm"; + import { Publisher } from "./publisher.entity"; -import { Builder } from "builder-pattern"; @Injectable() export class PublishersService { @@ -18,7 +19,7 @@ export class PublishersService { */ async getOrCreate(name: string, rawg_id: number): Promise { const existingPublisher = await this.publisherRepository.findOneBy({ - rawg_id, + name, }); if (existingPublisher) return existingPublisher; diff --git a/src/modules/stores/store.entity.ts b/src/modules/stores/store.entity.ts index 28ebc9b1..8ba86d79 100644 --- a/src/modules/stores/store.entity.ts +++ b/src/modules/stores/store.entity.ts @@ -1,17 +1,18 @@ -import { ApiProperty } from "@nestjs/swagger"; -import { Entity, Column, ManyToMany, Index } from "typeorm"; -import { Game } from "../games/game.entity"; +import { ApiProperty, ApiPropertyOptional } from "@nestjs/swagger"; +import { Column, Entity, Index, ManyToMany } from "typeorm"; + import { DatabaseEntity } from "../database/database.entity"; +import { Game } from "../games/game.entity"; @Entity() export class Store extends DatabaseEntity { @Index() - @Column({ unique: true }) - @ApiProperty({ + @Column({ nullable: true }) + @ApiPropertyOptional({ example: 1000, description: "unique rawg-api-identifier of the store", }) - rawg_id: number; + rawg_id?: number; @Index() @Column({ unique: true }) diff --git a/src/modules/stores/stores.module.ts b/src/modules/stores/stores.module.ts index c2ec70b9..2c751b8c 100644 --- a/src/modules/stores/stores.module.ts +++ b/src/modules/stores/stores.module.ts @@ -1,7 +1,8 @@ import { Module } from "@nestjs/common"; -import { StoresService } from "./stores.service"; import { TypeOrmModule } from "@nestjs/typeorm"; + import { Store } from "./store.entity"; +import { StoresService } from "./stores.service"; @Module({ imports: [TypeOrmModule, TypeOrmModule.forFeature([Store])], diff --git a/src/modules/stores/stores.service.ts b/src/modules/stores/stores.service.ts index dc26ce38..8ca00200 100644 --- a/src/modules/stores/stores.service.ts +++ b/src/modules/stores/stores.service.ts @@ -1,8 +1,9 @@ import { Injectable, Logger } from "@nestjs/common"; import { InjectRepository } from "@nestjs/typeorm"; +import { Builder } from "builder-pattern"; import { Repository } from "typeorm"; + import { Store } from "./store.entity"; -import { Builder } from "builder-pattern"; @Injectable() export class StoresService { @@ -17,7 +18,7 @@ export class StoresService { * does not already exist. */ async getOrCreate(name: string, rawg_id: number): Promise { - const existingStore = await this.storeRepository.findOneBy({ rawg_id }); + const existingStore = await this.storeRepository.findOneBy({ name }); if (existingStore) return existingStore; diff --git a/src/modules/tags/tag.entity.ts b/src/modules/tags/tag.entity.ts index 49a339d3..e6b96910 100644 --- a/src/modules/tags/tag.entity.ts +++ b/src/modules/tags/tag.entity.ts @@ -1,17 +1,18 @@ -import { ApiProperty } from "@nestjs/swagger"; -import { Entity, Column, ManyToMany, Index } from "typeorm"; -import { Game } from "../games/game.entity"; +import { ApiProperty, ApiPropertyOptional } from "@nestjs/swagger"; +import { Column, Entity, Index, ManyToMany } from "typeorm"; + import { DatabaseEntity } from "../database/database.entity"; +import { Game } from "../games/game.entity"; @Entity() export class Tag extends DatabaseEntity { @Index() - @Column({ unique: true }) - @ApiProperty({ + @Column({ nullable: true }) + @ApiPropertyOptional({ example: 1000, description: "unique rawg-api-identifier of the tag", }) - rawg_id: number; + rawg_id?: number; @Index() @Column({ unique: true }) diff --git a/src/modules/tags/tags.controller.ts b/src/modules/tags/tags.controller.ts index 8f0edc48..731751d9 100644 --- a/src/modules/tags/tags.controller.ts +++ b/src/modules/tags/tags.controller.ts @@ -2,20 +2,21 @@ import { Controller, Get } from "@nestjs/common"; import { ApiBasicAuth, ApiOperation, ApiTags } from "@nestjs/swagger"; import { InjectRepository } from "@nestjs/typeorm"; import { + NO_PAGINATION, Paginate, - PaginateQuery, - Paginated, paginate, - NO_PAGINATION, + Paginated, + PaginateQuery, PaginationType, } from "nestjs-paginate"; import { Repository } from "typeorm"; -import { ApiOkResponsePaginated } from "../pagination/paginated-api-response.model"; -import { PaginateQueryOptions } from "../../decorators/pagination.decorator"; -import { Tag } from "./tag.entity"; -import { all_filters } from "../pagination/all-filters.filter"; + import { MinimumRole } from "../../decorators/minimum-role.decorator"; +import { PaginateQueryOptions } from "../../decorators/pagination.decorator"; +import { all_filters } from "../../filters/all-filters.filter"; +import { ApiOkResponsePaginated } from "../../globals"; import { Role } from "../users/models/role.enum"; +import { Tag } from "./tag.entity"; @Controller("tags") @ApiTags("tags") diff --git a/src/modules/tags/tags.e2e.spec.ts b/src/modules/tags/tags.e2e.spec.ts index c1df0c5e..9974354a 100644 --- a/src/modules/tags/tags.e2e.spec.ts +++ b/src/modules/tags/tags.e2e.spec.ts @@ -1,11 +1,12 @@ import { Test } from "@nestjs/testing"; -import { TagsController } from "./tags.controller"; import { getRepositoryToken } from "@nestjs/typeorm"; -import { Tag } from "./tag.entity"; -import { Game } from "../games/game.entity"; import { Builder } from "builder-pattern"; import { Repository } from "typeorm/repository/Repository"; + import { AppModule } from "../../app.module"; +import { Game } from "../games/game.entity"; +import { Tag } from "./tag.entity"; +import { TagsController } from "./tags.controller"; describe("/api/tags", () => { let tagsController: TagsController; diff --git a/src/modules/tags/tags.module.ts b/src/modules/tags/tags.module.ts index c4b1aae1..0c81f5ce 100644 --- a/src/modules/tags/tags.module.ts +++ b/src/modules/tags/tags.module.ts @@ -1,8 +1,9 @@ import { Module } from "@nestjs/common"; -import { TagsController } from "./tags.controller"; -import { TagsService } from "./tags.service"; import { TypeOrmModule } from "@nestjs/typeorm"; + import { Tag } from "./tag.entity"; +import { TagsController } from "./tags.controller"; +import { TagsService } from "./tags.service"; @Module({ imports: [TypeOrmModule.forFeature([Tag])], diff --git a/src/modules/tags/tags.service.ts b/src/modules/tags/tags.service.ts index 18468d21..84cd6ae7 100644 --- a/src/modules/tags/tags.service.ts +++ b/src/modules/tags/tags.service.ts @@ -1,8 +1,9 @@ import { Injectable, Logger } from "@nestjs/common"; import { InjectRepository } from "@nestjs/typeorm"; +import { Builder } from "builder-pattern"; import { Repository } from "typeorm"; + import { Tag } from "./tag.entity"; -import { Builder } from "builder-pattern"; @Injectable() export class TagsService { @@ -18,7 +19,7 @@ export class TagsService { * not already exist. */ async getOrCreate(name: string, rawg_id: number): Promise { - const existingTag = await this.tagRepository.findOneBy({ rawg_id }); + const existingTag = await this.tagRepository.findOneBy({ name }); if (existingTag) return existingTag; diff --git a/src/modules/users/activity.gateway.ts b/src/modules/users/activity.gateway.ts index dc11717c..d5077ed4 100644 --- a/src/modules/users/activity.gateway.ts +++ b/src/modules/users/activity.gateway.ts @@ -1,24 +1,25 @@ import { Logger, UseFilters, UseGuards } from "@nestjs/common"; -import { AsyncApiPub, AsyncApiSub } from "nestjs-asyncapi"; -import { Server, Socket } from "socket.io"; +import { ApiBasicAuth } from "@nestjs/swagger"; import { + ConnectedSocket, MessageBody, + OnGatewayConnection, + OnGatewayDisconnect, SubscribeMessage, WebSocketGateway, WebSocketServer, - OnGatewayConnection, - OnGatewayDisconnect, - ConnectedSocket, } from "@nestjs/websockets"; -import { Activity } from "./models/activity.dto"; -import { ApiBasicAuth } from "@nestjs/swagger"; -import { UsersService } from "./users.service"; -import { ActivityState } from "./models/activity-state.enum"; -import { GamevaultUser } from "./gamevault-user.entity"; +import { AsyncApiPub, AsyncApiSub } from "nestjs-asyncapi"; +import { noop } from "rxjs"; +import { Server, Socket } from "socket.io"; + +import configuration from "../../configuration"; import { WebsocketExceptionsFilter } from "../../filters/websocket-exceptions.filter"; import { SocketSecretGuard } from "../guards/socket-secret.guard"; -import configuration from "../../configuration"; -import { noop } from "rxjs"; +import { GamevaultUser } from "./gamevault-user.entity"; +import { Activity } from "./models/activity.dto"; +import { ActivityState } from "./models/activity-state.enum"; +import { UsersService } from "./users.service"; // Conditionally decorate the WebSocket gateway class. const ConditionalWebSocketGateway = configuration.SERVER diff --git a/src/modules/users/gamevault-user.entity.ts b/src/modules/users/gamevault-user.entity.ts index ed955b54..b107add1 100644 --- a/src/modules/users/gamevault-user.entity.ts +++ b/src/modules/users/gamevault-user.entity.ts @@ -1,19 +1,20 @@ import { ApiProperty, ApiPropertyOptional } from "@nestjs/swagger"; import { - Entity, Column, - OneToMany, + Entity, + Index, JoinColumn, - OneToOne, - ManyToMany, JoinTable, - Index, + ManyToMany, + OneToMany, + OneToOne, } from "typeorm"; -import { Image } from "../images/image.entity"; -import { Progress } from "../progress/progress.entity"; + import { DatabaseEntity } from "../database/database.entity"; -import { Role } from "./models/role.enum"; import { Game } from "../games/game.entity"; +import { Image } from "../images/image.entity"; +import { Progress } from "../progresses/progress.entity"; +import { Role } from "./models/role.enum"; @Entity() export class GamevaultUser extends DatabaseEntity { diff --git a/src/modules/users/models/activity.dto.ts b/src/modules/users/models/activity.dto.ts index 277bf817..0dc33a54 100644 --- a/src/modules/users/models/activity.dto.ts +++ b/src/modules/users/models/activity.dto.ts @@ -1,5 +1,6 @@ import { ApiProperty, ApiPropertyOptional } from "@nestjs/swagger"; import { IsEmpty, IsNotEmpty, IsOptional } from "class-validator"; + import { ActivityState } from "./activity-state.enum"; export class Activity { diff --git a/src/modules/users/models/register-user.dto.ts b/src/modules/users/models/register-user.dto.ts index 4098d0b6..a767e03f 100644 --- a/src/modules/users/models/register-user.dto.ts +++ b/src/modules/users/models/register-user.dto.ts @@ -8,6 +8,7 @@ import { MinLength, ValidateIf, } from "class-validator"; + import configuration from "../../../configuration"; export class RegisterUserDto { diff --git a/src/modules/users/models/update-user.dto.ts b/src/modules/users/models/update-user.dto.ts index b2ff8475..dbe44958 100644 --- a/src/modules/users/models/update-user.dto.ts +++ b/src/modules/users/models/update-user.dto.ts @@ -11,6 +11,7 @@ import { Matches, MinLength, } from "class-validator"; + import { Role } from "./role.enum"; export class UpdateUserDto { diff --git a/src/modules/users/socket-secret.service.ts b/src/modules/users/socket-secret.service.ts index 20a6c91d..176f7d34 100644 --- a/src/modules/users/socket-secret.service.ts +++ b/src/modules/users/socket-secret.service.ts @@ -1,6 +1,7 @@ import { Injectable } from "@nestjs/common"; import { InjectRepository } from "@nestjs/typeorm"; import { Repository } from "typeorm"; + import { GamevaultUser } from "./gamevault-user.entity"; @Injectable() diff --git a/src/modules/users/users.controller.ts b/src/modules/users/users.controller.ts index 68143f41..e4a57d62 100644 --- a/src/modules/users/users.controller.ts +++ b/src/modules/users/users.controller.ts @@ -16,17 +16,18 @@ import { ApiOperation, ApiTags, } from "@nestjs/swagger"; + import configuration from "../../configuration"; +import { ConditionalRegistration } from "../../decorators/conditional-registration.decorator"; +import { DisableApiIf } from "../../decorators/disable-api-if.decorator"; +import { MinimumRole } from "../../decorators/minimum-role.decorator"; import { IdDto } from "../database/models/id.dto"; -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 "../../decorators/minimum-role.decorator"; +import { RegisterUserDto } from "./models/register-user.dto"; import { Role } from "./models/role.enum"; +import { UpdateUserDto } from "./models/update-user.dto"; import { SocketSecretService } from "./socket-secret.service"; -import { ConditionalRegistration } from "../../decorators/conditional-registration.decorator"; -import { DisableApiIf } from "../../decorators/disable-api-if.decorator"; +import { UsersService } from "./users.service"; @ApiBasicAuth() @ApiTags("user") diff --git a/src/modules/users/users.module.ts b/src/modules/users/users.module.ts index 4bc955bc..a5228b59 100644 --- a/src/modules/users/users.module.ts +++ b/src/modules/users/users.module.ts @@ -1,13 +1,15 @@ -import { Module, forwardRef } from "@nestjs/common"; -import { UsersService } from "./users.service"; -import { UsersController } from "./users.controller"; +import { forwardRef, Module } from "@nestjs/common"; import { TypeOrmModule } from "@nestjs/typeorm"; -import { GamevaultUser } from "./gamevault-user.entity"; + +import { GamesModule } from "../games/games.module"; +import { SocketSecretGuard } from "../guards/socket-secret.guard"; import { ImagesModule } from "../images/images.module"; import { ActivityGateway } from "./activity.gateway"; -import { SocketSecretGuard } from "../guards/socket-secret.guard"; +import { GamevaultUser } from "./gamevault-user.entity"; import { SocketSecretService } from "./socket-secret.service"; -import { GamesModule } from "../games/games.module"; +import { UsersController } from "./users.controller"; +import { UsersService } from "./users.service"; + @Module({ imports: [ TypeOrmModule.forFeature([GamevaultUser]), diff --git a/src/modules/users/users.service.ts b/src/modules/users/users.service.ts index cf32e3ec..7818a1e1 100644 --- a/src/modules/users/users.service.ts +++ b/src/modules/users/users.service.ts @@ -1,26 +1,27 @@ import { BadRequestException, ForbiddenException, + forwardRef, Inject, Injectable, Logger, NotFoundException, OnApplicationBootstrap, UnauthorizedException, - forwardRef, } from "@nestjs/common"; import { InjectRepository } from "@nestjs/typeorm"; import { compareSync, hashSync } from "bcrypt"; +import { randomBytes } from "crypto"; import { FindManyOptions, ILike, IsNull, Not, Repository } from "typeorm"; + import configuration from "../../configuration"; -import { RegisterUserDto } from "./models/register-user.dto"; -import { GamevaultUser } from "./gamevault-user.entity"; -import { ImagesService } from "../images/images.service"; -import { UpdateUserDto } from "./models/update-user.dto"; -import { Role } from "./models/role.enum"; import { FindOptions } from "../../globals"; -import { randomBytes } from "crypto"; import { GamesService } from "../games/games.service"; +import { ImagesService } from "../images/images.service"; +import { GamevaultUser } from "./gamevault-user.entity"; +import { RegisterUserDto } from "./models/register-user.dto"; +import { Role } from "./models/role.enum"; +import { UpdateUserDto } from "./models/update-user.dto"; @Injectable() export class UsersService implements OnApplicationBootstrap { @@ -388,7 +389,15 @@ export class UsersService implements OnApplicationBootstrap { loadDeletedEntities: false, loadRelations: false, }); + + await this.userRepository + .createQueryBuilder() + .relation(GamevaultUser, "bookmarked_games") + .of(user) + .add(game); + user.bookmarked_games.push(game); + this.logger.log({ message: "User bookmarked game.", user: user.username, @@ -397,14 +406,14 @@ export class UsersService implements OnApplicationBootstrap { file_path: game.file_path, }, }); - return this.userRepository.save(user); + return user; } /** Unbookmarks a game with the specified ID from the given user. */ public async unbookmarkGame(userId: number, gameId: number) { const user = await this.findByUserIdOrFail(userId, { loadDeletedEntities: false, - loadRelations: true, + loadRelations: ["bookmarked_games"], }); if (!user.bookmarked_games.some((game) => game.id === gameId)) { return user; @@ -414,9 +423,17 @@ export class UsersService implements OnApplicationBootstrap { loadDeletedEntities: false, loadRelations: false, }); + + await this.userRepository + .createQueryBuilder() + .relation(GamevaultUser, "bookmarked_games") + .of(user) + .remove(game); + user.bookmarked_games = user.bookmarked_games.filter((bookmark) => { return bookmark.id !== game.id; }); + this.logger.log({ message: "User unbookmarked game.", user: user.username, @@ -426,7 +443,7 @@ export class UsersService implements OnApplicationBootstrap { }, }); - return this.userRepository.save(user); + return user; } /** diff --git a/src/testing/setup-jest.ts b/src/testing/setup-jest.ts index d54bc02f..d337fc93 100644 --- a/src/testing/setup-jest.ts +++ b/src/testing/setup-jest.ts @@ -1,2 +1,3 @@ import dotenv from "dotenv"; + dotenv.config({ path: ".testing.env" });