diff --git a/.github/workflows/deployment-master.yml b/.github/workflows/deployment-master.yml index 83dd5f9b..ee49f07f 100644 --- a/.github/workflows/deployment-master.yml +++ b/.github/workflows/deployment-master.yml @@ -14,12 +14,6 @@ jobs: uses: actions/checkout@v3 with: fetch-depth: 0 - - - uses: sonarsource/sonarqube-scan-action@master - continue-on-error: true - env: - SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} - SONAR_HOST_URL: ${{ secrets.SONAR_HOST_URL }} - name: Fetch Current Package Version id: package-version diff --git a/CHANGELOG.md b/CHANGELOG.md index 65195740..4d4889b2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,9 +2,32 @@ ## 6.0.0 +Recommended Gamevault App Version: `v1.6.0` + +### Breaking Changes & Migration + +- We've removed the outdated Utility APIs. Instead, please switch to using the more current replacements. +- We've removed the Tags, Genres, Developers, and Publisher details from entries on the /games API for performace Reasons. To get all details for a game use the /game/:id API +- Fuzzy Search for Tags, Genres, Developers, and Publishers has been eliminated. (Previously for example, searching for a Publisher like "Rockstar" would return "GTA V") This change was made to improve search performance (18x speed) + ### Changes -- TODO: Removed Deprecated Utility APIs +- Fixed the default CORS configuration. +- Significantly enhanced the RAWG Search API, resulting in approximately 5 times faster performance and reduced data consumption. [#187](https://github.com/Phalcode/gamevault-backend/issues/187). Previously, search results inadvertently generated numerous tags, genres, developers, and stores in your databases, as well as images on your filesystem. This is no longer the case. The RAWG Search now provides only essential game information for identification and remapping. +- Improved Search Performance. +- Enhanced Error-Handling during image downloads. +- Transitioned to the Debian `20.6-slim` docker image. +- Rectified "required/nullable" fields in the API Specification. +- Resolved the file title extraction issue. [#209](https://github.com/Phalcode/gamevault-app/issues/209). +- Fixed the Broken Content-Disposition Header for some downloads. [#209](https://github.com/Phalcode/gamevault-app/issues/209). +- Game Type only gets detected once, or when a game file changes and not on every index. [#200](https://github.com/Phalcode/gamevault-backend/issues/200) +- Unified global error handler for 4XX and 5XX messages. The Problem is now directly inside the response without the duplicated status. +- Implemented `(NC)` flag to disable rawg-caching for single games. #194(https://github.com/Phalcode/gamevault-app/issues/194) + +### Thanks + +- @yodatak +- @Ben2303 ## 5.0.2 @@ -101,7 +124,7 @@ Recommended Gamevault App Version: `v1.5.0` - @freitagdavid - @Kairubyte -- @yotadak +- @yodatak ## 4.0.1 @@ -127,13 +150,13 @@ Recommended Gamevault App Version: `v1.5.0` - Fixed `SERVER_CORS_ALLOWED_ORIGINS` not working for multiple origins - Fixed Vague Password Validation Message - Fixed Version "undefined" on Server Startup Log -- Changed project structure as preparatory work for https://github.com/Phalcode/gamevault-backend/issues/140 +- Changed project structure as preparatory work for [#140](https://github.com/Phalcode/gamevault-backend/issues/140) - Implemented Update Game API (currently only supports rawg_id and box_image may come in handy for [#161](https://github.com/Phalcode/gamevault-backend/issues/161) in the future!) - [#146](https://github.com/Phalcode/gamevault-backend/issues/146) Fixed OpenAPI Spec again ### Thanks -- @Yotadak +- @yodatak - @Kairubyte ## 3.0.0 diff --git a/Dockerfile b/Dockerfile index 50c53435..0bb2c273 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM node:20.6 AS base +FROM node:20.6-slim AS base # Default Variables ENV PUID=1000 ENV PGID=1000 diff --git a/src/static/.progressignore b/assets/ignored-executables.txt similarity index 100% rename from src/static/.progressignore rename to assets/ignored-executables.txt diff --git a/nest-cli.json b/nest-cli.json index c7d5cce0..f2077018 100644 --- a/nest-cli.json +++ b/nest-cli.json @@ -3,6 +3,12 @@ "collection": "@nestjs/schematics", "sourceRoot": "src", "compilerOptions": { - "assets": ["**/static/*"] + "assets": [ + { + "include": "../assets", + "outDir":"dist/assets", + "watchAssets": true + } + ] } } diff --git a/package.json b/package.json index 3e395164..3a9cec09 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "gamevault-backend", - "version": "5.0.2", + "version": "6.0.0", "description": "the self-hosted gaming platform for drm-free games", "author": "Alkan Alper, Schäfer Philip GbR / Phalcode", "private": true, @@ -9,26 +9,27 @@ "build": "rimraf dist && nest build", "format": "prettier --write \"src/**/*.ts\"", "start": "rimraf dist && nest start --watch", - "start:debug": "nest start --debug --watch", + "profile": "rimraf dist && nest start --watch -e 'node --prof'", + "start:debug": "rimraf dist && nest start --debug --watch", "start:prod": "node dist/src/main", - "lint": "eslint \"{src,apps,libs}/**/*.ts\" --fix", - "migration:gen:sqlite": "rimraf dist && pnpm run build && typeorm -d dist/src/modules/database/migrations/sqlite.migration-config.js migration:generate -p", - "migration:run:sqlite": "rimraf dist && pnpm run build && typeorm -d dist/src/modules/database/migrations/sqlite.migration-config.js migration:run", - "migration:revert:sqlite": "rimraf dist && pnpm run build && typeorm -d dist/src/modules/database/migrations/sqlite.migration-config.js migration:revert", - "migration:gen:postgres": "rimraf dist && pnpm run build && typeorm -d dist/src/modules/database/migrations/postgres.migration-config.js migration:generate -p", - "migration:run:postgres": "rimraf dist && pnpm run build && typeorm -d dist/src/modules/database/migrations/postgres.migration-config.js migration:run", - "migration:revert:postgres": "rimraf dist && pnpm run build && typeorm -d dist/src/modules/database/migrations/postgres.migration-config.js migration:revert" + "lint": "eslint \"src/**/*.ts\" --fix", + "migration:gen:sqlite": "pnpm run build && typeorm -d dist/src/modules/database/migrations/sqlite.migration-config.js migration:generate -p", + "migration:run:sqlite": "pnpm run build && typeorm -d dist/src/modules/database/migrations/sqlite.migration-config.js migration:run", + "migration:revert:sqlite": "pnpm run build && typeorm -d dist/src/modules/database/migrations/sqlite.migration-config.js migration:revert", + "migration:gen:postgres": "pnpm run build && typeorm -d dist/src/modules/database/migrations/postgres.migration-config.js migration:generate -p", + "migration:run:postgres": "pnpm run build && typeorm -d dist/src/modules/database/migrations/postgres.migration-config.js migration:run", + "migration:revert:postgres": "pnpm run build && typeorm -d dist/src/modules/database/migrations/postgres.migration-config.js migration:revert" }, "dependencies": { "@nestjs/axios": "3.0.0", - "@nestjs/common": "10.2.5", - "@nestjs/core": "10.2.5", + "@nestjs/common": "10.2.6", + "@nestjs/core": "10.2.6", "@nestjs/passport": "10.0.2", - "@nestjs/platform-express": "10.2.5", - "@nestjs/schedule": "3.0.3", - "@nestjs/swagger": "7.1.11", + "@nestjs/platform-express": "10.2.6", + "@nestjs/schedule": "3.0.4", + "@nestjs/swagger": "7.1.12", "@nestjs/typeorm": "10.0.0", - "async-g-i-s": "1.5.0", + "async-g-i-s": "1.5.1", "axios": "1.5.0", "bcrypt": "5.1.1", "better-sqlite3": "8.6.0", @@ -38,7 +39,7 @@ "cookie-parser": "1.4.6", "dotenv": "16.3.1", "express": "4.18.2", - "fastify": "4.23.0", + "fastify": "4.23.2", "file-type-checker": "^1.0.8", "helmet": "7.0.0", "mime": "3.0.0", @@ -52,33 +53,36 @@ "reflect-metadata": "0.1.13", "rimraf": "5.0.1", "rxjs": "7.8.1", - "sharp": "0.32.5", + "sanitize-filename": "^1.6.3", + "sharp": "0.32.6", "string-similarity-js": "2.1.4", "throttle": "^1.0.3", "typeorm": "0.3.17", "typeorm-naming-strategies": "4.1.0", + "unidecode": "^0.1.8", "winston": "3.10.0", "winston-console-format": "1.0.8", "winston-daily-rotate-file": "4.7.1" }, "devDependencies": { - "@nestjs/cli": "10.1.17", + "@nestjs/cli": "10.1.18", "@nestjs/schematics": "10.0.2", "@types/bcrypt": "5.0.0", "@types/compression": "1.7.3", "@types/cookie-parser": "1.4.4", - "@types/express": "4.17.17", + "@types/express": "4.17.18", "@types/mime": "3.0.1", "@types/morgan": "1.9.5", "@types/multer": "^1.4.7", - "@types/node": "20.6.0", - "@types/node-7z": "2.1.5", + "@types/node": "20.6.5", + "@types/node-7z": "2.1.6", "@types/passport-http": "0.3.9", "@types/string-similarity": "4.0.0", "@types/throttle": "^1.0.2", - "@typescript-eslint/eslint-plugin": "6.7.0", - "@typescript-eslint/parser": "6.7.0", - "eslint": "8.49.0", + "@types/unidecode": "^0.1.1", + "@typescript-eslint/eslint-plugin": "6.7.2", + "@typescript-eslint/parser": "6.7.2", + "eslint": "8.50.0", "eslint-config-prettier": "9.0.0", "eslint-plugin-import": "2.28.1", "eslint-plugin-prettier": "5.0.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5cced117..06d2c1aa 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -7,31 +7,31 @@ settings: dependencies: '@nestjs/axios': specifier: 3.0.0 - version: 3.0.0(@nestjs/common@10.2.5)(axios@1.5.0)(reflect-metadata@0.1.13)(rxjs@7.8.1) + version: 3.0.0(@nestjs/common@10.2.6)(axios@1.5.0)(reflect-metadata@0.1.13)(rxjs@7.8.1) '@nestjs/common': - specifier: 10.2.5 - version: 10.2.5(class-transformer@0.5.1)(class-validator@0.14.0)(reflect-metadata@0.1.13)(rxjs@7.8.1) + specifier: 10.2.6 + version: 10.2.6(class-transformer@0.5.1)(class-validator@0.14.0)(reflect-metadata@0.1.13)(rxjs@7.8.1) '@nestjs/core': - specifier: 10.2.5 - version: 10.2.5(@nestjs/common@10.2.5)(@nestjs/platform-express@10.2.5)(reflect-metadata@0.1.13)(rxjs@7.8.1) + specifier: 10.2.6 + version: 10.2.6(@nestjs/common@10.2.6)(@nestjs/platform-express@10.2.6)(reflect-metadata@0.1.13)(rxjs@7.8.1) '@nestjs/passport': specifier: 10.0.2 - version: 10.0.2(@nestjs/common@10.2.5)(passport@0.6.0) + version: 10.0.2(@nestjs/common@10.2.6)(passport@0.6.0) '@nestjs/platform-express': - specifier: 10.2.5 - version: 10.2.5(@nestjs/common@10.2.5)(@nestjs/core@10.2.5) + specifier: 10.2.6 + version: 10.2.6(@nestjs/common@10.2.6)(@nestjs/core@10.2.6) '@nestjs/schedule': - specifier: 3.0.3 - version: 3.0.3(@nestjs/common@10.2.5)(@nestjs/core@10.2.5)(reflect-metadata@0.1.13) + specifier: 3.0.4 + version: 3.0.4(@nestjs/common@10.2.6)(@nestjs/core@10.2.6)(reflect-metadata@0.1.13) '@nestjs/swagger': - specifier: 7.1.11 - version: 7.1.11(@nestjs/common@10.2.5)(@nestjs/core@10.2.5)(class-transformer@0.5.1)(class-validator@0.14.0)(reflect-metadata@0.1.13) + specifier: 7.1.12 + version: 7.1.12(@nestjs/common@10.2.6)(@nestjs/core@10.2.6)(class-transformer@0.5.1)(class-validator@0.14.0)(reflect-metadata@0.1.13) '@nestjs/typeorm': specifier: 10.0.0 - version: 10.0.0(@nestjs/common@10.2.5)(@nestjs/core@10.2.5)(reflect-metadata@0.1.13)(rxjs@7.8.1)(typeorm@0.3.17) + version: 10.0.0(@nestjs/common@10.2.6)(@nestjs/core@10.2.6)(reflect-metadata@0.1.13)(rxjs@7.8.1)(typeorm@0.3.17) async-g-i-s: - specifier: 1.5.0 - version: 1.5.0 + specifier: 1.5.1 + version: 1.5.1(node-fetch@2.7.0) axios: specifier: 1.5.0 version: 1.5.0 @@ -60,8 +60,8 @@ dependencies: specifier: 4.18.2 version: 4.18.2 fastify: - specifier: 4.23.0 - version: 4.23.0 + specifier: 4.23.2 + version: 4.23.2 file-type-checker: specifier: ^1.0.8 version: 1.0.8 @@ -76,10 +76,10 @@ dependencies: version: 1.10.0 nest-winston: specifier: 1.9.4 - version: 1.9.4(@nestjs/common@10.2.5)(winston@3.10.0) + version: 1.9.4(@nestjs/common@10.2.6)(winston@3.10.0) nestjs-paginate: specifier: 8.3.0 - version: 8.3.0(@nestjs/common@10.2.5)(@nestjs/swagger@7.1.11)(express@4.18.2)(fastify@4.23.0)(typeorm@0.3.17) + version: 8.3.0(@nestjs/common@10.2.6)(@nestjs/swagger@7.1.12)(express@4.18.2)(fastify@4.23.2)(typeorm@0.3.17) node-7z: specifier: 3.0.0 version: 3.0.0 @@ -101,9 +101,12 @@ dependencies: rxjs: specifier: 7.8.1 version: 7.8.1 + sanitize-filename: + specifier: ^1.6.3 + version: 1.6.3 sharp: - specifier: 0.32.5 - version: 0.32.5 + specifier: 0.32.6 + version: 0.32.6 string-similarity-js: specifier: 2.1.4 version: 2.1.4 @@ -116,6 +119,9 @@ dependencies: typeorm-naming-strategies: specifier: 4.1.0 version: 4.1.0(typeorm@0.3.17) + unidecode: + specifier: ^0.1.8 + version: 0.1.8 winston: specifier: 3.10.0 version: 3.10.0 @@ -128,11 +134,11 @@ dependencies: devDependencies: '@nestjs/cli': - specifier: 10.1.17 - version: 10.1.17 + specifier: 10.1.18 + version: 10.1.18 '@nestjs/schematics': specifier: 10.0.2 - version: 10.0.2(typescript@5.2.2) + version: 10.0.2(chokidar@3.5.3)(typescript@5.2.2) '@types/bcrypt': specifier: 5.0.0 version: 5.0.0 @@ -143,8 +149,8 @@ devDependencies: specifier: 1.4.4 version: 1.4.4 '@types/express': - specifier: 4.17.17 - version: 4.17.17 + specifier: 4.17.18 + version: 4.17.18 '@types/mime': specifier: 3.0.1 version: 3.0.1 @@ -155,11 +161,11 @@ devDependencies: specifier: ^1.4.7 version: 1.4.7 '@types/node': - specifier: 20.6.0 - version: 20.6.0 + specifier: 20.6.5 + version: 20.6.5 '@types/node-7z': - specifier: 2.1.5 - version: 2.1.5 + specifier: 2.1.6 + version: 2.1.6 '@types/passport-http': specifier: 0.3.9 version: 0.3.9 @@ -169,24 +175,27 @@ devDependencies: '@types/throttle': specifier: ^1.0.2 version: 1.0.2 + '@types/unidecode': + specifier: ^0.1.1 + version: 0.1.1 '@typescript-eslint/eslint-plugin': - specifier: 6.7.0 - version: 6.7.0(@typescript-eslint/parser@6.7.0)(eslint@8.49.0)(typescript@5.2.2) + specifier: 6.7.2 + version: 6.7.2(@typescript-eslint/parser@6.7.2)(eslint@8.50.0)(typescript@5.2.2) '@typescript-eslint/parser': - specifier: 6.7.0 - version: 6.7.0(eslint@8.49.0)(typescript@5.2.2) + specifier: 6.7.2 + version: 6.7.2(eslint@8.50.0)(typescript@5.2.2) eslint: - specifier: 8.49.0 - version: 8.49.0 + specifier: 8.50.0 + version: 8.50.0 eslint-config-prettier: specifier: 9.0.0 - version: 9.0.0(eslint@8.49.0) + version: 9.0.0(eslint@8.50.0) eslint-plugin-import: specifier: 2.28.1 - version: 2.28.1(@typescript-eslint/parser@6.7.0)(eslint@8.49.0) + version: 2.28.1(@typescript-eslint/parser@6.7.2)(eslint@8.50.0) eslint-plugin-prettier: specifier: 5.0.0 - version: 5.0.0(eslint-config-prettier@9.0.0)(eslint@8.49.0)(prettier@3.0.3) + version: 5.0.0(eslint-config-prettier@9.0.0)(eslint@8.50.0)(prettier@3.0.3) prettier: specifier: 3.0.3 version: 3.0.3 @@ -198,7 +207,7 @@ devDependencies: version: 2.9.0 ts-node: specifier: 10.9.1 - version: 10.9.1(@types/node@20.6.0)(typescript@5.2.2) + version: 10.9.1(@types/node@20.6.5)(typescript@5.2.2) typescript: specifier: 5.2.2 version: 5.2.2 @@ -227,8 +236,8 @@ packages: source-map: 0.7.4 dev: true - /@angular-devkit/core@16.2.0(chokidar@3.5.3): - resolution: {integrity: sha512-l1k6Rqm3YM16BEn3CWyQKrk9xfu+2ux7Bw3oS+h1TO4/RoxO2PgHj8LLRh/WNrYVarhaqO7QZ5ePBkXNMkzJ1g==} + /@angular-devkit/core@16.2.3(chokidar@3.5.3): + resolution: {integrity: sha512-oZLdg2XTx7likYAXRj1CU0XmrsCfe5f2grj3iwuI3OB1LXwwpdbHBztruj03y3yHES+TnO+dIbkvRnvMXs7uAA==} engines: {node: ^16.14.0 || >=18.10.0, npm: ^6.11.0 || ^7.5.6 || >=8.0.0, yarn: '>= 1.13.0'} peerDependencies: chokidar: ^3.5.2 @@ -240,17 +249,18 @@ packages: ajv-formats: 2.1.1(ajv@8.12.0) chokidar: 3.5.3 jsonc-parser: 3.2.0 + picomatch: 2.3.1 rxjs: 7.8.1 source-map: 0.7.4 dev: true - /@angular-devkit/schematics-cli@16.2.0(chokidar@3.5.3): - resolution: {integrity: sha512-f3HjrDvSrRMvESogLsqsZXsEg//trIBySCHRXCglPrWLVdBbIRctGOhXqZoclRxXimIKUx14zLsOWzDwZG8+HQ==} + /@angular-devkit/schematics-cli@16.2.3(chokidar@3.5.3): + resolution: {integrity: sha512-5YQCbQmY9Kc03a9Io4XHOrxGXjnzcVveUuUO64R1m5x2aA5I+mVR8NVvxuoGRAeoI1FWusAKRe9hH8nRCLrelA==} engines: {node: ^16.14.0 || >=18.10.0, npm: ^6.11.0 || ^7.5.6 || >=8.0.0, yarn: '>= 1.13.0'} hasBin: true dependencies: - '@angular-devkit/core': 16.2.0(chokidar@3.5.3) - '@angular-devkit/schematics': 16.2.0(chokidar@3.5.3) + '@angular-devkit/core': 16.2.3(chokidar@3.5.3) + '@angular-devkit/schematics': 16.2.3(chokidar@3.5.3) ansi-colors: 4.1.3 inquirer: 8.2.4 symbol-observable: 4.0.0 @@ -272,11 +282,11 @@ packages: - chokidar dev: true - /@angular-devkit/schematics@16.2.0(chokidar@3.5.3): - resolution: {integrity: sha512-QMDJXPE0+YQJ9Ap3MMzb0v7rx6ZbBEokmHgpdIjN3eILYmbAdsSGE8HTV8NjS9nKmcyE9OGzFCMb7PFrDTlTAw==} + /@angular-devkit/schematics@16.2.3(chokidar@3.5.3): + resolution: {integrity: sha512-+lBiHxi/C9HCfiCbtW25DldwvJDXXXv5oWw+Tg4s18BO/lYZLveGUEaZWu9ZJ5VIJ8GliUi2LohxhDxBkh4Oxg==} engines: {node: ^16.14.0 || >=18.10.0, npm: ^6.11.0 || ^7.5.6 || >=8.0.0, yarn: '>= 1.13.0'} dependencies: - '@angular-devkit/core': 16.2.0(chokidar@3.5.3) + '@angular-devkit/core': 16.2.3(chokidar@3.5.3) jsonc-parser: 3.2.0 magic-string: 0.30.1 ora: 5.4.1 @@ -332,13 +342,13 @@ packages: kuler: 2.0.0 dev: false - /@eslint-community/eslint-utils@4.4.0(eslint@8.49.0): + /@eslint-community/eslint-utils@4.4.0(eslint@8.50.0): resolution: {integrity: sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 dependencies: - eslint: 8.49.0 + eslint: 8.50.0 eslint-visitor-keys: 3.4.3 dev: true @@ -364,8 +374,8 @@ packages: - supports-color dev: true - /@eslint/js@8.49.0: - resolution: {integrity: sha512-1S8uAY/MTJqVx0SC4epBq+N2yhuwtNwLbJYNZyhL2pO1ZVKn5HFXav5T41Ryzy9K9V7ZId2JB2oy/W4aCd9/2w==} + /@eslint/js@8.50.0: + resolution: {integrity: sha512-NCC3zz2+nvYd+Ckfh87rA47zfu2QsQpvc6k1yzTk+b9KzRj0wkGa8LSoGOXN6Zv4lRf/EIoZ80biDh9HOI+RNQ==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dev: true @@ -479,7 +489,7 @@ packages: - supports-color dev: false - /@nestjs/axios@3.0.0(@nestjs/common@10.2.5)(axios@1.5.0)(reflect-metadata@0.1.13)(rxjs@7.8.1): + /@nestjs/axios@3.0.0(@nestjs/common@10.2.6)(axios@1.5.0)(reflect-metadata@0.1.13)(rxjs@7.8.1): resolution: {integrity: sha512-ULdH03jDWkS5dy9X69XbUVbhC+0pVnrRcj7bIK/ytTZ76w7CgvTZDJqsIyisg3kNOiljRW/4NIjSf3j6YGvl+g==} peerDependencies: '@nestjs/common': ^7.0.0 || ^8.0.0 || ^9.0.0 || ^10.0.0 @@ -487,14 +497,14 @@ packages: reflect-metadata: ^0.1.12 rxjs: ^6.0.0 || ^7.0.0 dependencies: - '@nestjs/common': 10.2.5(class-transformer@0.5.1)(class-validator@0.14.0)(reflect-metadata@0.1.13)(rxjs@7.8.1) + '@nestjs/common': 10.2.6(class-transformer@0.5.1)(class-validator@0.14.0)(reflect-metadata@0.1.13)(rxjs@7.8.1) axios: 1.5.0 reflect-metadata: 0.1.13 rxjs: 7.8.1 dev: false - /@nestjs/cli@10.1.17: - resolution: {integrity: sha512-jUEnR2DgC15Op+IhcRWb6cyJrhec9CUQO+GtxCF2Dv9MwLcr4sTDq1UOkfs09HAhpuI8otgF2LoWGTlW3qRuqg==} + /@nestjs/cli@10.1.18: + resolution: {integrity: sha512-jQtG47keLsACt7b4YwJbTBYRm90n82gJpMaiR1HGAyQ9pccbctjSYu592eT4bxqkUWxPgBE3mpNynXj7dWAfrw==} engines: {node: '>= 16'} hasBin: true peerDependencies: @@ -506,15 +516,15 @@ packages: '@swc/core': optional: true dependencies: - '@angular-devkit/core': 16.2.0(chokidar@3.5.3) - '@angular-devkit/schematics': 16.2.0(chokidar@3.5.3) - '@angular-devkit/schematics-cli': 16.2.0(chokidar@3.5.3) - '@nestjs/schematics': 10.0.2(chokidar@3.5.3)(typescript@5.1.6) + '@angular-devkit/core': 16.2.3(chokidar@3.5.3) + '@angular-devkit/schematics': 16.2.3(chokidar@3.5.3) + '@angular-devkit/schematics-cli': 16.2.3(chokidar@3.5.3) + '@nestjs/schematics': 10.0.2(chokidar@3.5.3)(typescript@5.2.2) chalk: 4.1.2 chokidar: 3.5.3 cli-table3: 0.6.3 commander: 4.1.1 - fork-ts-checker-webpack-plugin: 8.0.0(typescript@5.1.6)(webpack@5.88.2) + fork-ts-checker-webpack-plugin: 8.0.0(typescript@5.2.2)(webpack@5.88.2) inquirer: 8.2.6 node-emoji: 1.11.0 ora: 5.4.1 @@ -525,7 +535,7 @@ packages: tree-kill: 1.2.2 tsconfig-paths: 4.2.0 tsconfig-paths-webpack-plugin: 4.1.0 - typescript: 5.1.6 + typescript: 5.2.2 webpack: 5.88.2 webpack-node-externals: 3.0.0 transitivePeerDependencies: @@ -534,8 +544,8 @@ packages: - webpack-cli dev: true - /@nestjs/common@10.2.5(class-transformer@0.5.1)(class-validator@0.14.0)(reflect-metadata@0.1.13)(rxjs@7.8.1): - resolution: {integrity: sha512-2BfkPZKmTVxflm8bhmClKKcHwhlyweEfbM25g7ldXIK9+utCPVXqBfZGORj2L8QagiT6bei48FJmGc2S1tiFEQ==} + /@nestjs/common@10.2.6(class-transformer@0.5.1)(class-validator@0.14.0)(reflect-metadata@0.1.13)(rxjs@7.8.1): + resolution: {integrity: sha512-ma8R7n+FXsWM4XF9QXjjrsRceyRzid/xKmNKVOa/sTJntkVG8lL71BHBEfjtFvO6EJUqjs/15LbDc0iaN5nCwA==} peerDependencies: class-transformer: '*' class-validator: '*' @@ -556,8 +566,8 @@ packages: uid: 2.0.2 dev: false - /@nestjs/core@10.2.5(@nestjs/common@10.2.5)(@nestjs/platform-express@10.2.5)(reflect-metadata@0.1.13)(rxjs@7.8.1): - resolution: {integrity: sha512-O9AycZc4MjzIFrvCxcQVqfSNuN9eHZrfyVcYkp9CMPj6lGd9TQCZX2MmaP1CWs4UJBmTKflPdtPJ0sj9iIuvLQ==} + /@nestjs/core@10.2.6(@nestjs/common@10.2.6)(@nestjs/platform-express@10.2.6)(reflect-metadata@0.1.13)(rxjs@7.8.1): + resolution: {integrity: sha512-oGQ2CoBeFRT7egG47MFqS89xlXBTIRZBkRpKRTPMftEfL1RMXhXIcIIaGfzp11wx6qxrBVxBXpVLM09oaqHpaQ==} requiresBuild: true peerDependencies: '@nestjs/common': ^10.0.0 @@ -574,8 +584,8 @@ packages: '@nestjs/websockets': optional: true dependencies: - '@nestjs/common': 10.2.5(class-transformer@0.5.1)(class-validator@0.14.0)(reflect-metadata@0.1.13)(rxjs@7.8.1) - '@nestjs/platform-express': 10.2.5(@nestjs/common@10.2.5)(@nestjs/core@10.2.5) + '@nestjs/common': 10.2.6(class-transformer@0.5.1)(class-validator@0.14.0)(reflect-metadata@0.1.13)(rxjs@7.8.1) + '@nestjs/platform-express': 10.2.6(@nestjs/common@10.2.6)(@nestjs/core@10.2.6) '@nuxtjs/opencollective': 0.3.2 fast-safe-stringify: 2.1.1 iterare: 1.2.1 @@ -588,7 +598,7 @@ packages: - encoding dev: false - /@nestjs/mapped-types@2.0.2(@nestjs/common@10.2.5)(class-transformer@0.5.1)(class-validator@0.14.0)(reflect-metadata@0.1.13): + /@nestjs/mapped-types@2.0.2(@nestjs/common@10.2.6)(class-transformer@0.5.1)(class-validator@0.14.0)(reflect-metadata@0.1.13): resolution: {integrity: sha512-V0izw6tWs6fTp9+KiiPUbGHWALy563Frn8X6Bm87ANLRuE46iuBMD5acKBDP5lKL/75QFvrzSJT7HkCbB0jTpg==} peerDependencies: '@nestjs/common': ^8.0.0 || ^9.0.0 || ^10.0.0 @@ -601,30 +611,30 @@ packages: class-validator: optional: true dependencies: - '@nestjs/common': 10.2.5(class-transformer@0.5.1)(class-validator@0.14.0)(reflect-metadata@0.1.13)(rxjs@7.8.1) + '@nestjs/common': 10.2.6(class-transformer@0.5.1)(class-validator@0.14.0)(reflect-metadata@0.1.13)(rxjs@7.8.1) class-transformer: 0.5.1 class-validator: 0.14.0 reflect-metadata: 0.1.13 dev: false - /@nestjs/passport@10.0.2(@nestjs/common@10.2.5)(passport@0.6.0): + /@nestjs/passport@10.0.2(@nestjs/common@10.2.6)(passport@0.6.0): resolution: {integrity: sha512-od31vfB2z3y05IDB5dWSbCGE2+pAf2k2WCBinNuTTOxN0O0+wtO1L3kawj/aCW3YR9uxsTOVbTDwtwgpNNsnjQ==} peerDependencies: '@nestjs/common': ^8.0.0 || ^9.0.0 || ^10.0.0 passport: ^0.4.0 || ^0.5.0 || ^0.6.0 dependencies: - '@nestjs/common': 10.2.5(class-transformer@0.5.1)(class-validator@0.14.0)(reflect-metadata@0.1.13)(rxjs@7.8.1) + '@nestjs/common': 10.2.6(class-transformer@0.5.1)(class-validator@0.14.0)(reflect-metadata@0.1.13)(rxjs@7.8.1) passport: 0.6.0 dev: false - /@nestjs/platform-express@10.2.5(@nestjs/common@10.2.5)(@nestjs/core@10.2.5): - resolution: {integrity: sha512-BChjJfsVtsvds31rp5V7LwM2XEQaxkFKUE0TGSH91m95FRO65Fii5RH6atasmhZTIW+T8LR5LuI/MO7cgZOiVg==} + /@nestjs/platform-express@10.2.6(@nestjs/common@10.2.6)(@nestjs/core@10.2.6): + resolution: {integrity: sha512-4U16D5ot2570CR8Qm5qu/SBXsA2l5KxN7AVSGvzoWoBxjEoOnnZOapC5Pler3yYa0tT1xLhji61RX1gceKW3dw==} peerDependencies: '@nestjs/common': ^10.0.0 '@nestjs/core': ^10.0.0 dependencies: - '@nestjs/common': 10.2.5(class-transformer@0.5.1)(class-validator@0.14.0)(reflect-metadata@0.1.13)(rxjs@7.8.1) - '@nestjs/core': 10.2.5(@nestjs/common@10.2.5)(@nestjs/platform-express@10.2.5)(reflect-metadata@0.1.13)(rxjs@7.8.1) + '@nestjs/common': 10.2.6(class-transformer@0.5.1)(class-validator@0.14.0)(reflect-metadata@0.1.13)(rxjs@7.8.1) + '@nestjs/core': 10.2.6(@nestjs/common@10.2.6)(@nestjs/platform-express@10.2.6)(reflect-metadata@0.1.13)(rxjs@7.8.1) body-parser: 1.20.2 cors: 2.8.5 express: 4.18.2 @@ -634,36 +644,21 @@ packages: - supports-color dev: false - /@nestjs/schedule@3.0.3(@nestjs/common@10.2.5)(@nestjs/core@10.2.5)(reflect-metadata@0.1.13): - resolution: {integrity: sha512-xsMA4dmP3LcW3rt2iMPfm88bDbCj/hLuDsLrKmJQlbnxyCYtBwLtmu/4cSfZELLM7pTDT+E8QDAqGwhYyUUjxg==} + /@nestjs/schedule@3.0.4(@nestjs/common@10.2.6)(@nestjs/core@10.2.6)(reflect-metadata@0.1.13): + resolution: {integrity: sha512-uFJpuZsXfpvgx2y7/KrIZW9e1L68TLiwRodZ6+Gc8xqQiHSUzAVn+9F4YMxWFlHITZvvkjWziUFgRNCitDcTZQ==} peerDependencies: '@nestjs/common': ^8.0.0 || ^9.0.0 || ^10.0.0 '@nestjs/core': ^8.0.0 || ^9.0.0 || ^10.0.0 reflect-metadata: ^0.1.12 dependencies: - '@nestjs/common': 10.2.5(class-transformer@0.5.1)(class-validator@0.14.0)(reflect-metadata@0.1.13)(rxjs@7.8.1) - '@nestjs/core': 10.2.5(@nestjs/common@10.2.5)(@nestjs/platform-express@10.2.5)(reflect-metadata@0.1.13)(rxjs@7.8.1) - cron: 2.4.1 + '@nestjs/common': 10.2.6(class-transformer@0.5.1)(class-validator@0.14.0)(reflect-metadata@0.1.13)(rxjs@7.8.1) + '@nestjs/core': 10.2.6(@nestjs/common@10.2.6)(@nestjs/platform-express@10.2.6)(reflect-metadata@0.1.13)(rxjs@7.8.1) + cron: 2.4.3 reflect-metadata: 0.1.13 - uuid: 9.0.0 + uuid: 9.0.1 dev: false - /@nestjs/schematics@10.0.2(chokidar@3.5.3)(typescript@5.1.6): - resolution: {integrity: sha512-DaZZjymYoIfRqC5W62lnYXIIods1PDY6CGc8+IpRwyinzffjKxZ3DF3exu+mdyvllzkXo9DTXkoX4zOPSJHCkw==} - peerDependencies: - typescript: '>=4.8.2' - dependencies: - '@angular-devkit/core': 16.1.8(chokidar@3.5.3) - '@angular-devkit/schematics': 16.1.8(chokidar@3.5.3) - comment-json: 4.2.3 - jsonc-parser: 3.2.0 - pluralize: 8.0.0 - typescript: 5.1.6 - transitivePeerDependencies: - - chokidar - dev: true - - /@nestjs/schematics@10.0.2(typescript@5.2.2): + /@nestjs/schematics@10.0.2(chokidar@3.5.3)(typescript@5.2.2): resolution: {integrity: sha512-DaZZjymYoIfRqC5W62lnYXIIods1PDY6CGc8+IpRwyinzffjKxZ3DF3exu+mdyvllzkXo9DTXkoX4zOPSJHCkw==} peerDependencies: typescript: '>=4.8.2' @@ -678,8 +673,8 @@ packages: - chokidar dev: true - /@nestjs/swagger@7.1.11(@nestjs/common@10.2.5)(@nestjs/core@10.2.5)(class-transformer@0.5.1)(class-validator@0.14.0)(reflect-metadata@0.1.13): - resolution: {integrity: sha512-EneFcucWC4a0n/FVd1Hg1MRugt65vL/1RDsQMqhMRfVw6IbYWuiKh51TNI4QwOPrRGiR1ry8qHJCBcTX9cl89Q==} + /@nestjs/swagger@7.1.12(@nestjs/common@10.2.6)(@nestjs/core@10.2.6)(class-transformer@0.5.1)(class-validator@0.14.0)(reflect-metadata@0.1.13): + resolution: {integrity: sha512-Q1P/IE+cws0sJeNtbs+8uDalcVylpmAnaEUFenGOa3KSNnXF/8DOE84mET/uUhFXsiz9PLHK8Hy7o7B6fRpMhg==} peerDependencies: '@fastify/static': ^6.0.0 '@nestjs/common': ^9.0.0 || ^10.0.0 @@ -695,19 +690,19 @@ packages: class-validator: optional: true dependencies: - '@nestjs/common': 10.2.5(class-transformer@0.5.1)(class-validator@0.14.0)(reflect-metadata@0.1.13)(rxjs@7.8.1) - '@nestjs/core': 10.2.5(@nestjs/common@10.2.5)(@nestjs/platform-express@10.2.5)(reflect-metadata@0.1.13)(rxjs@7.8.1) - '@nestjs/mapped-types': 2.0.2(@nestjs/common@10.2.5)(class-transformer@0.5.1)(class-validator@0.14.0)(reflect-metadata@0.1.13) + '@nestjs/common': 10.2.6(class-transformer@0.5.1)(class-validator@0.14.0)(reflect-metadata@0.1.13)(rxjs@7.8.1) + '@nestjs/core': 10.2.6(@nestjs/common@10.2.6)(@nestjs/platform-express@10.2.6)(reflect-metadata@0.1.13)(rxjs@7.8.1) + '@nestjs/mapped-types': 2.0.2(@nestjs/common@10.2.6)(class-transformer@0.5.1)(class-validator@0.14.0)(reflect-metadata@0.1.13) class-transformer: 0.5.1 class-validator: 0.14.0 js-yaml: 4.1.0 lodash: 4.17.21 path-to-regexp: 3.2.0 reflect-metadata: 0.1.13 - swagger-ui-dist: 5.6.2 + swagger-ui-dist: 5.7.2 dev: false - /@nestjs/typeorm@10.0.0(@nestjs/common@10.2.5)(@nestjs/core@10.2.5)(reflect-metadata@0.1.13)(rxjs@7.8.1)(typeorm@0.3.17): + /@nestjs/typeorm@10.0.0(@nestjs/common@10.2.6)(@nestjs/core@10.2.6)(reflect-metadata@0.1.13)(rxjs@7.8.1)(typeorm@0.3.17): resolution: {integrity: sha512-WQU4HCDTz4UavsFzvGUKDHqi0MO5K47yFoPXdmh+Z/hCNO7SHCMmV9jLiLukM8n5nKUqJ3jDqiljkWBcZPdCtA==} peerDependencies: '@nestjs/common': ^8.0.0 || ^9.0.0 || ^10.0.0 @@ -716,8 +711,8 @@ packages: rxjs: ^7.2.0 typeorm: ^0.3.0 dependencies: - '@nestjs/common': 10.2.5(class-transformer@0.5.1)(class-validator@0.14.0)(reflect-metadata@0.1.13)(rxjs@7.8.1) - '@nestjs/core': 10.2.5(@nestjs/common@10.2.5)(@nestjs/platform-express@10.2.5)(reflect-metadata@0.1.13)(rxjs@7.8.1) + '@nestjs/common': 10.2.6(class-transformer@0.5.1)(class-validator@0.14.0)(reflect-metadata@0.1.13)(rxjs@7.8.1) + '@nestjs/core': 10.2.6(@nestjs/common@10.2.6)(@nestjs/platform-express@10.2.6)(reflect-metadata@0.1.13)(rxjs@7.8.1) reflect-metadata: 0.1.13 rxjs: 7.8.1 typeorm: 0.3.17(better-sqlite3@8.6.0)(pg@8.11.3)(ts-node@10.9.1) @@ -752,7 +747,7 @@ packages: dependencies: chalk: 4.1.2 consola: 2.15.3 - node-fetch: 2.6.7 + node-fetch: 2.7.0 transitivePeerDependencies: - encoding dev: false @@ -795,32 +790,32 @@ packages: /@types/bcrypt@5.0.0: resolution: {integrity: sha512-agtcFKaruL8TmcvqbndlqHPSJgsolhf/qPWchFlgnW1gECTN/nKbFcoFnvKAQRFfKbh+BO6A3SWdJu9t+xF3Lw==} dependencies: - '@types/node': 20.6.0 + '@types/node': 20.6.5 dev: true /@types/body-parser@1.19.2: resolution: {integrity: sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g==} dependencies: '@types/connect': 3.4.35 - '@types/node': 20.6.0 + '@types/node': 20.6.5 dev: true /@types/compression@1.7.3: resolution: {integrity: sha512-rKquEGjebqizyHNMOpaE/4FdYR5VQiWFeesqYfvJU0seSEyB4625UGhNOO/qIkH10S3wftiV7oefc8WdLZ/gCQ==} dependencies: - '@types/express': 4.17.17 + '@types/express': 4.17.18 dev: true /@types/connect@3.4.35: resolution: {integrity: sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==} dependencies: - '@types/node': 20.6.0 + '@types/node': 20.6.5 dev: true /@types/cookie-parser@1.4.4: resolution: {integrity: sha512-Var+aj5I6ZgIqsQ05N2V8q5OBrFfZXtIGWWDSrEYLIbMw758obagSwdGcLCjwh1Ga7M7+wj0SDIAaAC/WT7aaA==} dependencies: - '@types/express': 4.17.17 + '@types/express': 4.17.18 dev: true /@types/debug@4.1.7: @@ -850,13 +845,13 @@ packages: /@types/express-serve-static-core@4.17.33: resolution: {integrity: sha512-TPBqmR/HRYI3eC2E5hmiivIzv+bidAfXofM+sbonAGvyDhySGw9/PQZFt2BLOrjUUR++4eJVpx6KnLQK1Fk9tA==} dependencies: - '@types/node': 20.6.0 + '@types/node': 20.6.5 '@types/qs': 6.9.7 '@types/range-parser': 1.2.4 dev: true - /@types/express@4.17.17: - resolution: {integrity: sha512-Q4FmmuLGBG58btUnfS1c1r/NQdlp3DMfGDGig8WhfpA2YRUtEkxAjkZb0yvplJGYdF1fsQ81iMDcH24sSCNC/Q==} + /@types/express@4.17.18: + resolution: {integrity: sha512-Sxv8BSLLgsBYmcnGdGjjEjqET2U+AKAdCRODmMiq02FgjwuV75Ut85DRpvFjyw/Mk0vgUOliGRU0UUmuuZHByQ==} dependencies: '@types/body-parser': 1.19.2 '@types/express-serve-static-core': 4.17.33 @@ -872,6 +867,10 @@ packages: resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==} dev: true + /@types/luxon@3.3.2: + resolution: {integrity: sha512-l5cpE57br4BIjK+9BSkFBOsWtwv6J9bJpC7gdXIzZyI0vuKvNTk0wZZrkQxMGsUAuGW9+WMNWF2IJMD7br2yeQ==} + dev: false + /@types/mdast@3.0.10: resolution: {integrity: sha512-W864tg/Osz1+9f4lrGTZpCSO5/z4608eUp19tbozkq2HJK6i3z1kT0H9tlADXuYIb1YYOBByU4Jsqkk75q48qA==} dependencies: @@ -885,7 +884,7 @@ packages: /@types/morgan@1.9.5: resolution: {integrity: sha512-5TgfIWm0lcTGnbCZExwc19dCOMOMmAiiBZQj8Ko3NRxsVDgRxf+AEGRQTqNVA5Yh2xfdWp4clbAEMbYP+jkOqg==} dependencies: - '@types/node': 20.6.0 + '@types/node': 20.6.5 dev: true /@types/ms@0.7.31: @@ -895,17 +894,17 @@ packages: /@types/multer@1.4.7: resolution: {integrity: sha512-/SNsDidUFCvqqcWDwxv2feww/yqhNeTRL5CVoL3jU4Goc4kKEL10T7Eye65ZqPNi4HRx8sAEX59pV1aEH7drNA==} dependencies: - '@types/express': 4.17.17 + '@types/express': 4.17.18 dev: true - /@types/node-7z@2.1.5: - resolution: {integrity: sha512-ZTcXeMhJPcGzZBjkpc6m3FsWpKlr5OotizwzRNyIjoMo57OQ7RkM5GganOHVE1brMX9tvyr6YEYvfj8LSrx0Nw==} + /@types/node-7z@2.1.6: + resolution: {integrity: sha512-26aONb5grye/fklu1FXy5NEnbT2QyLA7DoCjQVTT3zAUJRjp/D44oiYrBVj/CLGmAeCJMkVbdxL6vROxaf7JYQ==} dependencies: - '@types/node': 20.6.0 + '@types/node': 20.6.5 dev: true - /@types/node@20.6.0: - resolution: {integrity: sha512-najjVq5KN2vsH2U/xyh2opaSEz6cZMR2SetLIlxlj08nOcmPOemJmUK2o4kUzfLqfrWE0PIrNeE16XhYDd3nqg==} + /@types/node@20.6.5: + resolution: {integrity: sha512-2qGq5LAOTh9izcc0+F+dToFigBWiK1phKPt7rNhOqJSr35y8rlIBjDwGtFSgAI6MGIhjwOVNSQZVdJsZJ2uR1w==} /@types/parse-json@4.0.0: resolution: {integrity: sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==} @@ -914,14 +913,14 @@ packages: /@types/passport-http@0.3.9: resolution: {integrity: sha512-uQ4vyRdvM0jdWuKpLmi6Q6ri9Nwt8YnHmF7kE6snbthxPrsMWcjRCVc5WcPaQ356ODSZTDgiRYURMPIspCkn3Q==} dependencies: - '@types/express': 4.17.17 + '@types/express': 4.17.18 '@types/passport': 1.0.12 dev: true /@types/passport@1.0.12: resolution: {integrity: sha512-QFdJ2TiAEoXfEQSNDISJR1Tm51I78CymqcBa8imbjo6dNNu+l2huDxxbDEIoFIwOSKMkOfHEikyDuZ38WwWsmw==} dependencies: - '@types/express': 4.17.17 + '@types/express': 4.17.18 dev: true /@types/qs@6.9.7: @@ -940,7 +939,7 @@ packages: resolution: {integrity: sha512-z5xyF6uh8CbjAu9760KDKsH2FcDxZ2tFCsA4HIMWE6IkiYMXfVoa+4f9KX+FN0ZLsaMw1WNG2ETLA6N+/YA+cg==} dependencies: '@types/mime': 3.0.1 - '@types/node': 20.6.0 + '@types/node': 20.6.5 dev: true /@types/string-similarity@4.0.0: @@ -950,7 +949,11 @@ packages: /@types/throttle@1.0.2: resolution: {integrity: sha512-ieT8dv6eJRCcyRXrw6o25/mBkJKT+A92C5JFVqFY99ea6uRWKGta4Q9Xl1IPHEejtHjBeYZK/ffcVcnlIKk8hg==} dependencies: - '@types/node': 20.6.0 + '@types/node': 20.6.5 + dev: true + + /@types/unidecode@0.1.1: + resolution: {integrity: sha512-ReYnWajSY+QnZk5dBpSztek+kHUtpHJkf0pSZBx2ZVIxcSBkRLY7m0caPwVKXC6WjE65G2fVU84EpiFbdJVDuQ==} dev: true /@types/unist@2.0.6: @@ -961,8 +964,8 @@ packages: resolution: {integrity: sha512-t1yxFAR2n0+VO6hd/FJ9F2uezAZVWHLmpmlJzm1eX03+H7+HsuTAp7L8QJs+2pQCfWkP1+EXsGK9Z9v7o/qPVQ==} dev: false - /@typescript-eslint/eslint-plugin@6.7.0(@typescript-eslint/parser@6.7.0)(eslint@8.49.0)(typescript@5.2.2): - resolution: {integrity: sha512-gUqtknHm0TDs1LhY12K2NA3Rmlmp88jK9Tx8vGZMfHeNMLE3GH2e9TRub+y+SOjuYgtOmok+wt1AyDPZqxbNag==} + /@typescript-eslint/eslint-plugin@6.7.2(@typescript-eslint/parser@6.7.2)(eslint@8.50.0)(typescript@5.2.2): + resolution: {integrity: sha512-ooaHxlmSgZTM6CHYAFRlifqh1OAr3PAQEwi7lhYhaegbnXrnh7CDcHmc3+ihhbQC7H0i4JF0psI5ehzkF6Yl6Q==} engines: {node: ^16.0.0 || >=18.0.0} peerDependencies: '@typescript-eslint/parser': ^6.0.0 || ^6.0.0-alpha @@ -973,13 +976,13 @@ packages: optional: true dependencies: '@eslint-community/regexpp': 4.6.2 - '@typescript-eslint/parser': 6.7.0(eslint@8.49.0)(typescript@5.2.2) - '@typescript-eslint/scope-manager': 6.7.0 - '@typescript-eslint/type-utils': 6.7.0(eslint@8.49.0)(typescript@5.2.2) - '@typescript-eslint/utils': 6.7.0(eslint@8.49.0)(typescript@5.2.2) - '@typescript-eslint/visitor-keys': 6.7.0 + '@typescript-eslint/parser': 6.7.2(eslint@8.50.0)(typescript@5.2.2) + '@typescript-eslint/scope-manager': 6.7.2 + '@typescript-eslint/type-utils': 6.7.2(eslint@8.50.0)(typescript@5.2.2) + '@typescript-eslint/utils': 6.7.2(eslint@8.50.0)(typescript@5.2.2) + '@typescript-eslint/visitor-keys': 6.7.2 debug: 4.3.4 - eslint: 8.49.0 + eslint: 8.50.0 graphemer: 1.4.0 ignore: 5.2.4 natural-compare: 1.4.0 @@ -990,8 +993,8 @@ packages: - supports-color dev: true - /@typescript-eslint/parser@6.7.0(eslint@8.49.0)(typescript@5.2.2): - resolution: {integrity: sha512-jZKYwqNpNm5kzPVP5z1JXAuxjtl2uG+5NpaMocFPTNC2EdYIgbXIPImObOkhbONxtFTTdoZstLZefbaK+wXZng==} + /@typescript-eslint/parser@6.7.2(eslint@8.50.0)(typescript@5.2.2): + resolution: {integrity: sha512-KA3E4ox0ws+SPyxQf9iSI25R6b4Ne78ORhNHeVKrPQnoYsb9UhieoiRoJgrzgEeKGOXhcY1i8YtOeCHHTDa6Fw==} engines: {node: ^16.0.0 || >=18.0.0} peerDependencies: eslint: ^7.0.0 || ^8.0.0 @@ -1000,27 +1003,27 @@ packages: typescript: optional: true dependencies: - '@typescript-eslint/scope-manager': 6.7.0 - '@typescript-eslint/types': 6.7.0 - '@typescript-eslint/typescript-estree': 6.7.0(typescript@5.2.2) - '@typescript-eslint/visitor-keys': 6.7.0 + '@typescript-eslint/scope-manager': 6.7.2 + '@typescript-eslint/types': 6.7.2 + '@typescript-eslint/typescript-estree': 6.7.2(typescript@5.2.2) + '@typescript-eslint/visitor-keys': 6.7.2 debug: 4.3.4 - eslint: 8.49.0 + eslint: 8.50.0 typescript: 5.2.2 transitivePeerDependencies: - supports-color dev: true - /@typescript-eslint/scope-manager@6.7.0: - resolution: {integrity: sha512-lAT1Uau20lQyjoLUQ5FUMSX/dS07qux9rYd5FGzKz/Kf8W8ccuvMyldb8hadHdK/qOI7aikvQWqulnEq2nCEYA==} + /@typescript-eslint/scope-manager@6.7.2: + resolution: {integrity: sha512-bgi6plgyZjEqapr7u2mhxGR6E8WCzKNUFWNh6fkpVe9+yzRZeYtDTbsIBzKbcxI+r1qVWt6VIoMSNZ4r2A+6Yw==} engines: {node: ^16.0.0 || >=18.0.0} dependencies: - '@typescript-eslint/types': 6.7.0 - '@typescript-eslint/visitor-keys': 6.7.0 + '@typescript-eslint/types': 6.7.2 + '@typescript-eslint/visitor-keys': 6.7.2 dev: true - /@typescript-eslint/type-utils@6.7.0(eslint@8.49.0)(typescript@5.2.2): - resolution: {integrity: sha512-f/QabJgDAlpSz3qduCyQT0Fw7hHpmhOzY/Rv6zO3yO+HVIdPfIWhrQoAyG+uZVtWAIS85zAyzgAFfyEr+MgBpg==} + /@typescript-eslint/type-utils@6.7.2(eslint@8.50.0)(typescript@5.2.2): + resolution: {integrity: sha512-36F4fOYIROYRl0qj95dYKx6kybddLtsbmPIYNK0OBeXv2j9L5nZ17j9jmfy+bIDHKQgn2EZX+cofsqi8NPATBQ==} engines: {node: ^16.0.0 || >=18.0.0} peerDependencies: eslint: ^7.0.0 || ^8.0.0 @@ -1029,23 +1032,23 @@ packages: typescript: optional: true dependencies: - '@typescript-eslint/typescript-estree': 6.7.0(typescript@5.2.2) - '@typescript-eslint/utils': 6.7.0(eslint@8.49.0)(typescript@5.2.2) + '@typescript-eslint/typescript-estree': 6.7.2(typescript@5.2.2) + '@typescript-eslint/utils': 6.7.2(eslint@8.50.0)(typescript@5.2.2) debug: 4.3.4 - eslint: 8.49.0 + eslint: 8.50.0 ts-api-utils: 1.0.1(typescript@5.2.2) typescript: 5.2.2 transitivePeerDependencies: - supports-color dev: true - /@typescript-eslint/types@6.7.0: - resolution: {integrity: sha512-ihPfvOp7pOcN/ysoj0RpBPOx3HQTJTrIN8UZK+WFd3/iDeFHHqeyYxa4hQk4rMhsz9H9mXpR61IzwlBVGXtl9Q==} + /@typescript-eslint/types@6.7.2: + resolution: {integrity: sha512-flJYwMYgnUNDAN9/GAI3l8+wTmvTYdv64fcH8aoJK76Y+1FCZ08RtI5zDerM/FYT5DMkAc+19E4aLmd5KqdFyg==} engines: {node: ^16.0.0 || >=18.0.0} dev: true - /@typescript-eslint/typescript-estree@6.7.0(typescript@5.2.2): - resolution: {integrity: sha512-dPvkXj3n6e9yd/0LfojNU8VMUGHWiLuBZvbM6V6QYD+2qxqInE7J+J/ieY2iGwR9ivf/R/haWGkIj04WVUeiSQ==} + /@typescript-eslint/typescript-estree@6.7.2(typescript@5.2.2): + resolution: {integrity: sha512-kiJKVMLkoSciGyFU0TOY0fRxnp9qq1AzVOHNeN1+B9erKFCJ4Z8WdjAkKQPP+b1pWStGFqezMLltxO+308dJTQ==} engines: {node: ^16.0.0 || >=18.0.0} peerDependencies: typescript: '*' @@ -1053,8 +1056,8 @@ packages: typescript: optional: true dependencies: - '@typescript-eslint/types': 6.7.0 - '@typescript-eslint/visitor-keys': 6.7.0 + '@typescript-eslint/types': 6.7.2 + '@typescript-eslint/visitor-keys': 6.7.2 debug: 4.3.4 globby: 11.1.0 is-glob: 4.0.3 @@ -1065,30 +1068,30 @@ packages: - supports-color dev: true - /@typescript-eslint/utils@6.7.0(eslint@8.49.0)(typescript@5.2.2): - resolution: {integrity: sha512-MfCq3cM0vh2slSikQYqK2Gq52gvOhe57vD2RM3V4gQRZYX4rDPnKLu5p6cm89+LJiGlwEXU8hkYxhqqEC/V3qA==} + /@typescript-eslint/utils@6.7.2(eslint@8.50.0)(typescript@5.2.2): + resolution: {integrity: sha512-ZCcBJug/TS6fXRTsoTkgnsvyWSiXwMNiPzBUani7hDidBdj1779qwM1FIAmpH4lvlOZNF3EScsxxuGifjpLSWQ==} engines: {node: ^16.0.0 || >=18.0.0} peerDependencies: eslint: ^7.0.0 || ^8.0.0 dependencies: - '@eslint-community/eslint-utils': 4.4.0(eslint@8.49.0) + '@eslint-community/eslint-utils': 4.4.0(eslint@8.50.0) '@types/json-schema': 7.0.12 '@types/semver': 7.5.0 - '@typescript-eslint/scope-manager': 6.7.0 - '@typescript-eslint/types': 6.7.0 - '@typescript-eslint/typescript-estree': 6.7.0(typescript@5.2.2) - eslint: 8.49.0 + '@typescript-eslint/scope-manager': 6.7.2 + '@typescript-eslint/types': 6.7.2 + '@typescript-eslint/typescript-estree': 6.7.2(typescript@5.2.2) + eslint: 8.50.0 semver: 7.5.4 transitivePeerDependencies: - supports-color - typescript dev: true - /@typescript-eslint/visitor-keys@6.7.0: - resolution: {integrity: sha512-/C1RVgKFDmGMcVGeD8HjKv2bd72oI1KxQDeY8uc66gw9R0OK0eMq48cA+jv9/2Ag6cdrsUGySm1yzYmfz0hxwQ==} + /@typescript-eslint/visitor-keys@6.7.2: + resolution: {integrity: sha512-uVw9VIMFBUTz8rIeaUT3fFe8xIUx8r4ywAdlQv1ifH+6acn/XF8Y6rwJ7XNmkNMDrTW+7+vxFFPIF40nJCVsMQ==} engines: {node: ^16.0.0 || >=18.0.0} dependencies: - '@typescript-eslint/types': 6.7.0 + '@typescript-eslint/types': 6.7.2 eslint-visitor-keys: 3.4.3 dev: true @@ -1444,8 +1447,12 @@ packages: is-shared-array-buffer: 1.0.2 dev: true - /async-g-i-s@1.5.0: - resolution: {integrity: sha512-rxTYAKu+9V2f5Akn518rU/PZ4GpvdqH/9q/jD/ngHhpup3sW14XN8BhbH3yLznuoMreKugxChosITBVAu/9GJQ==} + /async-g-i-s@1.5.1(node-fetch@2.7.0): + resolution: {integrity: sha512-JAoA0xftSU5wXKh9otxXZk5ZUD+LRXDmwJJWXG+Kcja7ml3N8Kn+pMJXiAh16eYtOurjVQYEyVD6TAJSkdiCWw==} + peerDependencies: + node-fetch: ^2.7.0 + dependencies: + node-fetch: 2.7.0 dev: false /async@3.2.4: @@ -2002,10 +2009,11 @@ packages: /create-require@1.1.1: resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==} - /cron@2.4.1: - resolution: {integrity: sha512-ty0hUSPuENwDtIShDFxUxWEIsqiu2vhoFtt6Vwrbg4lHGtJX2/cV2p0hH6/qaEM9Pj+i6mQoau48BO5wBpkP4w==} + /cron@2.4.3: + resolution: {integrity: sha512-YBvExkQYF7w0PxyeFLRyr817YVDhGxaCi5/uRRMqa4aWD3IFKRd+uNbpW1VWMdqQy8PZ7CElc+accXJcauPKzQ==} dependencies: - luxon: 3.2.1 + '@types/luxon': 3.3.2 + luxon: 3.3.0 dev: false /cross-spawn@7.0.3: @@ -2314,13 +2322,13 @@ packages: engines: {node: '>=10'} dev: true - /eslint-config-prettier@9.0.0(eslint@8.49.0): + /eslint-config-prettier@9.0.0(eslint@8.50.0): resolution: {integrity: sha512-IcJsTkJae2S35pRsRAwoCE+925rJJStOdkKnLVgtE+tEpqU0EVVM7OqrwxqgptKdX29NUwC82I5pXsGFIgSevw==} hasBin: true peerDependencies: eslint: '>=7.0.0' dependencies: - eslint: 8.49.0 + eslint: 8.50.0 dev: true /eslint-import-resolver-node@0.3.7: @@ -2333,7 +2341,7 @@ packages: - supports-color dev: true - /eslint-module-utils@2.8.0(@typescript-eslint/parser@6.7.0)(eslint-import-resolver-node@0.3.7)(eslint@8.49.0): + /eslint-module-utils@2.8.0(@typescript-eslint/parser@6.7.2)(eslint-import-resolver-node@0.3.7)(eslint@8.50.0): resolution: {integrity: sha512-aWajIYfsqCKRDgUfjEXNN/JlrzauMuSEy5sbd7WXbtW3EH6A6MpwEh42c7qD+MqQo9QMJ6fWLAeIJynx0g6OAw==} engines: {node: '>=4'} peerDependencies: @@ -2354,15 +2362,15 @@ packages: eslint-import-resolver-webpack: optional: true dependencies: - '@typescript-eslint/parser': 6.7.0(eslint@8.49.0)(typescript@5.2.2) + '@typescript-eslint/parser': 6.7.2(eslint@8.50.0)(typescript@5.2.2) debug: 3.2.7 - eslint: 8.49.0 + eslint: 8.50.0 eslint-import-resolver-node: 0.3.7 transitivePeerDependencies: - supports-color dev: true - /eslint-plugin-import@2.28.1(@typescript-eslint/parser@6.7.0)(eslint@8.49.0): + /eslint-plugin-import@2.28.1(@typescript-eslint/parser@6.7.2)(eslint@8.50.0): resolution: {integrity: sha512-9I9hFlITvOV55alzoKBI+K9q74kv0iKMeY6av5+umsNwayt59fz692daGyjR+oStBQgx6nwR9rXldDev3Clw+A==} engines: {node: '>=4'} peerDependencies: @@ -2372,16 +2380,16 @@ packages: '@typescript-eslint/parser': optional: true dependencies: - '@typescript-eslint/parser': 6.7.0(eslint@8.49.0)(typescript@5.2.2) + '@typescript-eslint/parser': 6.7.2(eslint@8.50.0)(typescript@5.2.2) array-includes: 3.1.6 array.prototype.findlastindex: 1.2.2 array.prototype.flat: 1.3.1 array.prototype.flatmap: 1.3.1 debug: 3.2.7 doctrine: 2.1.0 - eslint: 8.49.0 + eslint: 8.50.0 eslint-import-resolver-node: 0.3.7 - eslint-module-utils: 2.8.0(@typescript-eslint/parser@6.7.0)(eslint-import-resolver-node@0.3.7)(eslint@8.49.0) + eslint-module-utils: 2.8.0(@typescript-eslint/parser@6.7.2)(eslint-import-resolver-node@0.3.7)(eslint@8.50.0) has: 1.0.3 is-core-module: 2.13.0 is-glob: 4.0.3 @@ -2397,7 +2405,7 @@ packages: - supports-color dev: true - /eslint-plugin-prettier@5.0.0(eslint-config-prettier@9.0.0)(eslint@8.49.0)(prettier@3.0.3): + /eslint-plugin-prettier@5.0.0(eslint-config-prettier@9.0.0)(eslint@8.50.0)(prettier@3.0.3): resolution: {integrity: sha512-AgaZCVuYDXHUGxj/ZGu1u8H8CYgDY3iG6w5kUFw4AzMVXzB7VvbKgYR4nATIN+OvUrghMbiDLeimVjVY5ilq3w==} engines: {node: ^14.18.0 || >=16.0.0} peerDependencies: @@ -2411,8 +2419,8 @@ packages: eslint-config-prettier: optional: true dependencies: - eslint: 8.49.0 - eslint-config-prettier: 9.0.0(eslint@8.49.0) + eslint: 8.50.0 + eslint-config-prettier: 9.0.0(eslint@8.50.0) prettier: 3.0.3 prettier-linter-helpers: 1.0.0 synckit: 0.8.5 @@ -2439,15 +2447,15 @@ packages: engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dev: true - /eslint@8.49.0: - resolution: {integrity: sha512-jw03ENfm6VJI0jA9U+8H5zfl5b+FvuU3YYvZRdZHOlU2ggJkxrlkJH4HcDrZpj6YwD8kuYqvQM8LyesoazrSOQ==} + /eslint@8.50.0: + resolution: {integrity: sha512-FOnOGSuFuFLv/Sa+FDVRZl4GGVAAFFi8LecRsI5a1tMO5HIE8nCm4ivAlzt4dT3ol/PaaGC0rJEEXQmHJBGoOg==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} hasBin: true dependencies: - '@eslint-community/eslint-utils': 4.4.0(eslint@8.49.0) + '@eslint-community/eslint-utils': 4.4.0(eslint@8.50.0) '@eslint-community/regexpp': 4.6.2 '@eslint/eslintrc': 2.1.2 - '@eslint/js': 8.49.0 + '@eslint/js': 8.50.0 '@humanwhocodes/config-array': 0.11.11 '@humanwhocodes/module-importer': 1.0.1 '@nodelib/fs.walk': 1.2.8 @@ -2709,8 +2717,8 @@ packages: resolution: {integrity: sha512-cIusKBIt/R/oI6z/1nyfe2FvGKVTohVRfvkOhvx0nCEW+xf5NoCXjAHcWp93uOUBchzYcsvPlrapAdX1uW+YGg==} dev: false - /fastify@4.23.0: - resolution: {integrity: sha512-u4aQUjAqf+GQQI+IeIJtzOKCJHtdwPlGxzopq/Kv6QcEdJ7xuJFSQ5Bi7+uJ+F8990jWECLzRcAyZ4pVsloRpQ==} + /fastify@4.23.2: + resolution: {integrity: sha512-WFSxsHES115svC7NrerNqZwwM0UOxbC/P6toT9LRHgAAFvG7o2AN5W+H4ihCtOGuYXjZf4z+2jXC89rVEoPWOA==} dependencies: '@fastify/ajv-compiler': 3.5.0 '@fastify/error': 3.2.0 @@ -2848,7 +2856,7 @@ packages: signal-exit: 4.0.1 dev: false - /fork-ts-checker-webpack-plugin@8.0.0(typescript@5.1.6)(webpack@5.88.2): + /fork-ts-checker-webpack-plugin@8.0.0(typescript@5.2.2)(webpack@5.88.2): resolution: {integrity: sha512-mX3qW3idpueT2klaQXBzrIM/pHw+T0B/V9KHEvNrqijTq9NFnMZU6oreVxDYcf33P8a5cW+67PjodNHthGnNVg==} engines: {node: '>=12.13.0', yarn: '>=1.0.0'} peerDependencies: @@ -2867,7 +2875,7 @@ packages: schema-utils: 3.3.0 semver: 7.5.4 tapable: 2.2.1 - typescript: 5.1.6 + typescript: 5.2.2 webpack: 5.88.2 dev: true @@ -3503,7 +3511,7 @@ packages: resolution: {integrity: sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==} engines: {node: '>= 10.13.0'} dependencies: - '@types/node': 20.6.0 + '@types/node': 20.6.5 merge-stream: 2.0.0 supports-color: 8.1.1 dev: true @@ -3658,8 +3666,8 @@ packages: resolution: {integrity: sha512-65/Jky17UwSb0BuB9V+MyDpsOtXKmYwzhyl+cOa9XUiI4uV2Ouy/2voFP3+al0BjZbJgMBD8FojMpAf+Z+qn4A==} engines: {node: 14 || >=16.14} - /luxon@3.2.1: - resolution: {integrity: sha512-QrwPArQCNLAKGO/C+ZIilgIuDnEnKx5QYODdDtbFaxzsbZcc/a7WFq7MhsVYgRlwawLtvOUESTlfJ+hc/USqPg==} + /luxon@3.3.0: + resolution: {integrity: sha512-An0UCfG/rSiqtAIiBPO0Y9/zAnHUZxAMiCpTd5h2smgsj7GGmcenvrvww2cqNA8/4A5ZrD1gJpHN2mIHZQF+Mg==} engines: {node: '>=12'} dev: false @@ -4118,18 +4126,18 @@ packages: resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==} dev: true - /nest-winston@1.9.4(@nestjs/common@10.2.5)(winston@3.10.0): + /nest-winston@1.9.4(@nestjs/common@10.2.6)(winston@3.10.0): resolution: {integrity: sha512-ilEmHuuYSAI6aMNR120fLBl42EdY13QI9WRggHdEizt9M7qZlmXJwpbemVWKW/tqRmULjSx/otKNQ3GMQbfoUQ==} peerDependencies: '@nestjs/common': ^5.0.0 || ^6.6.0 || ^7.0.0 || ^8.0.0 || ^9.0.0 || ^10.0.0 winston: ^3.0.0 dependencies: - '@nestjs/common': 10.2.5(class-transformer@0.5.1)(class-validator@0.14.0)(reflect-metadata@0.1.13)(rxjs@7.8.1) + '@nestjs/common': 10.2.6(class-transformer@0.5.1)(class-validator@0.14.0)(reflect-metadata@0.1.13)(rxjs@7.8.1) fast-safe-stringify: 2.1.1 winston: 3.10.0 dev: false - /nestjs-paginate@8.3.0(@nestjs/common@10.2.5)(@nestjs/swagger@7.1.11)(express@4.18.2)(fastify@4.23.0)(typeorm@0.3.17): + /nestjs-paginate@8.3.0(@nestjs/common@10.2.6)(@nestjs/swagger@7.1.12)(express@4.18.2)(fastify@4.23.2)(typeorm@0.3.17): resolution: {integrity: sha512-ZZLzPhsngxeNUUY9AdnLFlnpDhD2paitaTFuvYIVMNURhcRG/XfgEIU4sfxXKzJWVU95zQEyMmcuTacYCybScw==} peerDependencies: '@nestjs/common': ^10.2.4 @@ -4138,10 +4146,10 @@ packages: fastify: ^4.22.2 typeorm: ^0.3.17 dependencies: - '@nestjs/common': 10.2.5(class-transformer@0.5.1)(class-validator@0.14.0)(reflect-metadata@0.1.13)(rxjs@7.8.1) - '@nestjs/swagger': 7.1.11(@nestjs/common@10.2.5)(@nestjs/core@10.2.5)(class-transformer@0.5.1)(class-validator@0.14.0)(reflect-metadata@0.1.13) + '@nestjs/common': 10.2.6(class-transformer@0.5.1)(class-validator@0.14.0)(reflect-metadata@0.1.13)(rxjs@7.8.1) + '@nestjs/swagger': 7.1.12(@nestjs/common@10.2.6)(@nestjs/core@10.2.6)(class-transformer@0.5.1)(class-validator@0.14.0)(reflect-metadata@0.1.13) express: 4.18.2 - fastify: 4.23.0 + fastify: 4.23.2 lodash: 4.17.21 typeorm: 0.3.17(better-sqlite3@8.6.0)(pg@8.11.3)(ts-node@10.9.1) dev: false @@ -4198,6 +4206,18 @@ packages: whatwg-url: 5.0.0 dev: false + /node-fetch@2.7.0: + resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==} + engines: {node: 4.x || >=6.0.0} + peerDependencies: + encoding: ^0.1.0 + peerDependenciesMeta: + encoding: + optional: true + dependencies: + whatwg-url: 5.0.0 + dev: false + /node-releases@2.0.6: resolution: {integrity: sha512-PiVXnNuFm5+iYkLBNeq5211hvO38y63T0i2KKh2KnUs3RpzJ+JtODFjkD8yjLwnDkTYF1eKXheUwdssR+NRZdg==} dev: true @@ -4991,6 +5011,12 @@ packages: /safer-buffer@2.1.2: resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + /sanitize-filename@1.6.3: + resolution: {integrity: sha512-y/52Mcy7aw3gRm7IrcGDFx/bCk4AhRh2eI9luHOQM86nZsqwiRkkq2GekHXBBD+SmPidc8i2PqtYZl+pWJ8Oeg==} + dependencies: + truncate-utf8-bytes: 1.0.2 + dev: false + /schema-utils@3.3.0: resolution: {integrity: sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==} engines: {node: '>= 10.13.0'} @@ -5074,8 +5100,8 @@ packages: safe-buffer: 5.2.1 dev: false - /sharp@0.32.5: - resolution: {integrity: sha512-0dap3iysgDkNaPOaOL4X/0akdu0ma62GcdC2NBQ+93eqpePdDdr2/LM0sFdDSMmN7yS+odyZtPsb7tx/cYBKnQ==} + /sharp@0.32.6: + resolution: {integrity: sha512-KyLTWwgcR9Oe4d9HwCwNM2l7+J0dUQwn/yf7S0EnTtb0eVS4RxO0eUSvxPtzT4F3SY+C4K6fqdv/DO27sJ/v/w==} engines: {node: '>=14.15.0'} requiresBuild: true dependencies: @@ -5314,8 +5340,8 @@ packages: engines: {node: '>= 0.4'} dev: true - /swagger-ui-dist@5.6.2: - resolution: {integrity: sha512-2LKVuU2m6RHkemJloKiKJOTpN2RPmbsiad0OfSdtmFHOXJKAgYRZMwJcpT96RX6E9HUB5RkVOFC6vWqVjRgSOg==} + /swagger-ui-dist@5.7.2: + resolution: {integrity: sha512-mVZc9QVQ6pTCV5crli3+Ng+DoMPwdtMHK8QLk2oX8Mtamp4D/hV+uYdC3lV0JZrDgpNEcjs0RrWTqMwwosuLPQ==} dev: false /symbol-observable@4.0.0: @@ -5502,6 +5528,12 @@ packages: resolution: {integrity: sha512-XrHUvV5HpdLmIj4uVMxHggLbFSZYIn7HEWsqePZcI50pco+MPqJ50wMGY794X7AOOhxOBAjbkqfAbEe/QMp2Lw==} dev: false + /truncate-utf8-bytes@1.0.2: + resolution: {integrity: sha512-95Pu1QXQvruGEhv62XCMO3Mm90GscOCClvrIUwCM0PYOXK3kaF3l3sIHxx71ThJfcbM2O5Au6SO3AWCSEfW4mQ==} + dependencies: + utf8-byte-length: 1.0.4 + dev: false + /ts-api-utils@1.0.1(typescript@5.2.2): resolution: {integrity: sha512-lC/RGlPmwdrIBFTX59wwNzqh7aR2otPNPR/5brHZm/XKFYKsfqxihXUe9pU3JI+3vGkl+vyCoNNnPhJn3aLK1A==} engines: {node: '>=16.13.0'} @@ -5511,7 +5543,7 @@ packages: typescript: 5.2.2 dev: true - /ts-node@10.9.1(@types/node@20.6.0)(typescript@5.2.2): + /ts-node@10.9.1(@types/node@20.6.5)(typescript@5.2.2): resolution: {integrity: sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==} hasBin: true peerDependencies: @@ -5530,7 +5562,7 @@ packages: '@tsconfig/node12': 1.0.11 '@tsconfig/node14': 1.0.3 '@tsconfig/node16': 1.0.3 - '@types/node': 20.6.0 + '@types/node': 20.6.5 acorn: 8.10.0 acorn-walk: 8.2.0 arg: 4.1.3 @@ -5727,7 +5759,7 @@ packages: pg: 8.11.3 reflect-metadata: 0.1.13 sha.js: 2.4.11 - ts-node: 10.9.1(@types/node@20.6.0)(typescript@5.2.2) + ts-node: 10.9.1(@types/node@20.6.5)(typescript@5.2.2) tslib: 2.6.1 uuid: 9.0.0 yargs: 17.6.2 @@ -5735,12 +5767,6 @@ packages: - supports-color dev: false - /typescript@5.1.6: - resolution: {integrity: sha512-zaWCozRZ6DLEWAWFrVDz1H6FVXzUSfTy5FUMWsQlU8Ym5JP9eO4xkTIROFCQvhQf61z6O/G6ugw3SgAnvvm+HA==} - engines: {node: '>=14.17'} - hasBin: true - dev: true - /typescript@5.2.2: resolution: {integrity: sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==} engines: {node: '>=14.17'} @@ -5762,6 +5788,11 @@ packages: which-boxed-primitive: 1.0.2 dev: true + /unidecode@0.1.8: + resolution: {integrity: sha512-SdoZNxCWpN2tXTCrGkPF/0rL2HEq+i2gwRG1ReBvx8/0yTzC3enHfugOf8A9JBShVwwrRIkLX0YcDUGbzjbVCA==} + engines: {node: '>= 0.4.12'} + dev: false + /unist-util-stringify-position@3.0.3: resolution: {integrity: sha512-k5GzIBZ/QatR8N5X2y+drfpWG8IDBzdnVj6OInRNWm1oXrzydiaAT2OQiA8DPRRZyAKb9b6I2a6PxYklZD0gKg==} dependencies: @@ -5799,6 +5830,10 @@ packages: dependencies: punycode: 2.1.1 + /utf8-byte-length@1.0.4: + resolution: {integrity: sha512-4+wkEYLBbWxqTahEsWrhxepcoVOJ+1z5PGIjPZxRkytcdSUaNjIjBM7Xn8E+pdSuV7SzvWovBFA54FO0JSoqhA==} + dev: false + /util-deprecate@1.0.2: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} @@ -5812,6 +5847,11 @@ packages: hasBin: true dev: false + /uuid@9.0.1: + resolution: {integrity: sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==} + hasBin: true + dev: false + /uvu@0.5.6: resolution: {integrity: sha512-+g8ENReyr8YsOc6fv/NVJs2vFdHBnBNdfE49rshrTzDWOlUx4Gq7KOS2GD8eqhy2j+Ejq29+SbKH8yjkAqXqoA==} engines: {node: '>=8'} diff --git a/src/app.module.ts b/src/app.module.ts index da17ad58..37e79d86 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -18,7 +18,6 @@ import { RawgModule } from "./modules/providers/rawg/rawg.module"; import { DefaultStrategy } from "./modules/auth/basic-auth.strategy"; import { TypeOrmModule } from "@nestjs/typeorm"; import { getDatabaseConfiguration } from "./modules/database/db_configuration"; -import { UtilityController } from "./deprecated/utility.controller"; @Module({ imports: [ TypeOrmModule.forRoot(getDatabaseConfiguration()), @@ -37,7 +36,6 @@ import { UtilityController } from "./deprecated/utility.controller"; GenresModule, GamesModule, ], - controllers: [UtilityController], providers: [ { provide: APP_FILTER, useClass: LoggingExceptionFilter }, DefaultStrategy, diff --git a/src/configuration.ts b/src/configuration.ts index eae3d1f7..346397c4 100644 --- a/src/configuration.ts +++ b/src/configuration.ts @@ -35,9 +35,10 @@ export default { ), REQUEST_LOG_FORMAT: process.env.SERVER_REQUEST_LOG_FORMAT || globals.LOGGING_FORMAT, - CORS_ALLOWED_ORIGINS: parseList(process.env.SERVER_CORS_ALLOWED_ORIGINS, [ - "*", - ]), + CORS_ALLOWED_ORIGINS: parseList( + process.env.SERVER_CORS_ALLOWED_ORIGINS, + [], + ), REGISTRATION_DISABLED: parseBooleanEnvVariable( process.env.SERVER_REGISTRATION_DISABLED, ), diff --git a/src/deprecated/utility.controller.ts b/src/deprecated/utility.controller.ts deleted file mode 100644 index 10fb0cca..00000000 --- a/src/deprecated/utility.controller.ts +++ /dev/null @@ -1,145 +0,0 @@ -import { Body, Controller, Param, Put, Request } from "@nestjs/common"; -import { - ApiBasicAuth, - ApiBody, - ApiOkResponse, - ApiOperation, - ApiTags, -} from "@nestjs/swagger"; -import { ImageUrlDto } from "../modules/images/models/image-url.dto"; -import { RawgIdDto } from "../modules/games/models/rawg_id.dto"; -import { Game } from "../modules/games/game.entity"; -import { GamesService } from "../modules/games/games.service"; -import { ImagesService } from "../modules/images/images.service"; -import { RawgService } from "../modules/providers/rawg/rawg.service"; -import { MinimumRole } from "../modules/pagination/minimum-role.decorator"; -import { Role } from "../modules/users/models/role.enum"; -import { BoxArtsService } from "../modules/boxarts/boxarts.service"; -import { IdDto } from "../modules/database/models/id.dto"; -import { FilesService } from "../modules/files/files.service"; -import { GamevaultUser } from "../modules/users/gamevault-user.entity"; - -@ApiTags("utility") -@Controller("utility") -@ApiBasicAuth() -export class UtilityController { - constructor( - private gamesService: GamesService, - private rawgService: RawgService, - private imagesService: ImagesService, - private boxartService: BoxArtsService, - private filesService: FilesService, - ) {} - - /** Manually triggers a reindex on all games. */ - @Put("reindex") - @ApiOperation({ - summary: "manually triggers a reindex on all games", - operationId: "reindexGames", - deprecated: true, - }) - @ApiOkResponse({ type: () => Game, isArray: true }) - @MinimumRole(Role.ADMIN) - async reindexGames() { - return await this.filesService.index(); - } - - /** - * Manually triggers a recache from rawg-api for a specific game, also updates - * boxart. - * - * @param params - An object containing the game's ID. - * @returns - A promise that resolves with the updated game. - */ - @Put("recache/:id") - @ApiOperation({ - summary: - "manually triggers a recache from rawg-api for a specific game, also updates boxart", - operationId: "recacheGame", - deprecated: true, - }) - @ApiOkResponse({ type: () => Game }) - @MinimumRole(Role.ADMIN) - async recacheGame(@Param() params: IdDto): Promise { - let game = await this.gamesService.getGameById(Number(params.id)); - game.cache_date = null; - game = await this.gamesService.saveGame(game); - await this.rawgService.cacheGames([game]); - await this.boxartService.checkBoxArt(game); - return await this.gamesService.getGameById(Number(params.id), true); - } - - /** Manually triggers a recache from rawg-api for all games. */ - @Put("recache") - @ApiOperation({ - summary: "manually triggers a recache from rawg-api for all games", - description: - "DANGER: This is a very expensive operation and should be used sparingly", - operationId: "recacheAllGames", - deprecated: true, - }) - @ApiOkResponse({ type: () => Game, isArray: true }) - @MinimumRole(Role.ADMIN) - async recacheAllGames(): Promise { - const gamesInDatabase = await this.gamesService.getAllGames(); - for (const game of gamesInDatabase) { - game.cache_date = null; - await this.gamesService.saveGame(game); - } - await this.rawgService.cacheGames(gamesInDatabase); - await this.boxartService.checkBoxArts(gamesInDatabase); - return "Recache successfuly completed"; - } - - /** - * Manually remaps a database entry to a rawg game and recaches it. - * - * @param params - An object containing the game's ID. - * @param dto - An object containing the new rawg ID. - * @returns - A promise that resolves with the updated game. - */ - @Put("overwrite/:id/rawg_id") - @ApiOperation({ - summary: "manually remaps a database entry to a rawg game and recaches it", - operationId: "remapGame", - deprecated: true, - }) - @ApiOkResponse({ type: () => Game }) - @ApiBody({ type: () => RawgIdDto }) - @MinimumRole(Role.EDITOR) - async remapGame( - @Param() params: IdDto, - @Body() dto: RawgIdDto, - ): Promise { - return await this.gamesService.remapGame(Number(params.id), dto.rawg_id); - } - - /** - * Manually overwrites box art for a game. - * - * @param params - An object containing the game's ID. - * @param dto - An object containing the new image URL. - * @returns - A promise that resolves with the updated game. - */ - @Put("overwrite/:id/box_image") - @ApiOperation({ - summary: "manually overwrites box art for a game", - operationId: "remapBoxArt", - deprecated: true, - }) - @ApiOkResponse({ type: () => Game }) - @ApiBody({ type: () => ImageUrlDto }) - @MinimumRole(Role.EDITOR) - async remapBoxArt( - @Param() params: IdDto, - @Body() dto: ImageUrlDto, - @Request() req: { gamevaultuser: GamevaultUser }, - ): Promise { - const game = await this.gamesService.getGameById(Number(params.id)); - game.box_image = await this.imagesService.downloadImageByUrl( - dto.image_url, - req.gamevaultuser.username, - ); - return await this.gamesService.saveGame(game); - } -} diff --git a/src/main.ts b/src/main.ts index f62555bc..2e09519e 100644 --- a/src/main.ts +++ b/src/main.ts @@ -14,6 +14,7 @@ import { default as logger, default as winston, stream } from "./logging"; import { DocumentBuilder, SwaggerModule } from "@nestjs/swagger"; import { AuthenticationGuard } from "./modules/auth/authentication.guard"; import { AuthorizationGuard } from "./modules/auth/authorization.guard"; +import { LoggingExceptionFilter } from "./modules/log/exception.filter"; /** * Bootstraps the application by creating a NestJS application, configuring it, * and setting up global settings and routes. @@ -27,7 +28,15 @@ async function bootstrap(): Promise { app.set("trust proxy", 1); app.set("json spaces", 2); - app.enableCors({ origin: configuration.SERVER.CORS_ALLOWED_ORIGINS }); + if (configuration.SERVER.CORS_ALLOWED_ORIGINS.length) { + app.enableCors({ + origin: configuration.SERVER.CORS_ALLOWED_ORIGINS, + credentials: true, + methods: "GET,HEAD,PUT,PATCH,POST,DELETE", + }); + } else { + app.enableCors(); + } app.use(compression()); app.use(helmet({ contentSecurityPolicy: false })); app.use(cookieparser()); @@ -43,6 +52,8 @@ async function bootstrap(): Promise { }), ); + app.useGlobalFilters(new LoggingExceptionFilter()); + app.setGlobalPrefix("api/v1"); const reflector = app.get(Reflector); @@ -77,7 +88,6 @@ async function bootstrap(): Promise { .addTag("tags", "apis for tags") .addTag("genres", "apis for genres") .addTag("user", "apis for user management") - .addTag("utility", "apis for miscellaneous utilities") .addTag("rawg", "apis for rawg services") .addTag("images", "apis for handling images") .build(), diff --git a/src/modules/boxarts/boxarts.service.ts b/src/modules/boxarts/boxarts.service.ts index 4ca05a5c..611409f0 100644 --- a/src/modules/boxarts/boxarts.service.ts +++ b/src/modules/boxarts/boxarts.service.ts @@ -186,10 +186,10 @@ export class BoxArtsService { ); return true; } catch (error) { + this.logger.error(error, `Error downloading image from ${image.url}`); continue; } } - return false; } diff --git a/src/modules/database/database.entity.ts b/src/modules/database/database.entity.ts index b9551449..28348b15 100644 --- a/src/modules/database/database.entity.ts +++ b/src/modules/database/database.entity.ts @@ -1,13 +1,15 @@ -import { ApiProperty } from "@nestjs/swagger"; +import { ApiProperty, ApiPropertyOptional } from "@nestjs/swagger"; import { CreateDateColumn, DeleteDateColumn, + Index, PrimaryGeneratedColumn, UpdateDateColumn, VersionColumn, } from "typeorm"; export abstract class DatabaseEntity { + @Index() @PrimaryGeneratedColumn() @ApiProperty({ example: 1, @@ -23,17 +25,16 @@ export abstract class DatabaseEntity { created_at: Date; @UpdateDateColumn() - @ApiProperty({ + @ApiPropertyOptional({ description: "date the entity was updated", example: "2021-01-01T00:00:00.000Z", }) updated_at?: Date; @DeleteDateColumn() - @ApiProperty({ + @ApiPropertyOptional({ description: "date the entity was soft-deleted (null if not deleted)", example: "2021-01-01T00:00:00.000Z", - nullable: true, }) deleted_at?: Date; diff --git a/src/modules/database/migrations/postgres/1695590841420-create_indices.ts b/src/modules/database/migrations/postgres/1695590841420-create_indices.ts new file mode 100644 index 00000000..794df67b --- /dev/null +++ b/src/modules/database/migrations/postgres/1695590841420-create_indices.ts @@ -0,0 +1,113 @@ +import { MigrationInterface, QueryRunner } from "typeorm"; + +export class CreateIndices1695590841420 implements MigrationInterface { + name = "CreateIndices1695590841420"; + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + DROP INDEX "public"."IDX_e0626148aee5829fd312447001" + `); + await queryRunner.query(` + CREATE INDEX IF NOT EXISTS "IDX_0285d4f1655d080cfcf7d1ab14" ON "genre" ("id") + `); + await queryRunner.query(` + CREATE INDEX IF NOT EXISTS "IDX_dd8cd9e50dd049656e4be1f7e8" ON "genre" ("name") + `); + await queryRunner.query(` + CREATE INDEX IF NOT EXISTS "IDX_d6db1ab4ee9ad9dbe86c64e4cc" ON "image" ("id") + `); + await queryRunner.query(` + CREATE INDEX IF NOT EXISTS "IDX_c2a3f8b06558be9508161af22e" ON "gamevault_user" ("id") + `); + await queryRunner.query(` + CREATE INDEX IF NOT EXISTS "IDX_79abdfd87a688f9de756a162b6" ON "progress" ("id") + `); + await queryRunner.query(` + CREATE INDEX IF NOT EXISTS "IDX_ddcaca3a9db9d77105d51c02c2" ON "progress" ("user_id") + `); + await queryRunner.query(` + CREATE INDEX IF NOT EXISTS "IDX_feaddf361921db1df3a6fe3965" ON "progress" ("game_id") + `); + await queryRunner.query(` + CREATE INDEX IF NOT EXISTS "IDX_70a5936b43177f76161724da3e" ON "publisher" ("id") + `); + await queryRunner.query(` + CREATE INDEX IF NOT EXISTS "IDX_9dc496f2e5b912da9edd2aa445" ON "publisher" ("name") + `); + await queryRunner.query(` + CREATE INDEX IF NOT EXISTS "IDX_f3172007d4de5ae8e7692759d7" ON "store" ("id") + `); + await queryRunner.query(` + CREATE INDEX IF NOT EXISTS "IDX_66df34da7fb037e24fc7fee642" ON "store" ("name") + `); + await queryRunner.query(` + CREATE INDEX IF NOT EXISTS "IDX_8e4052373c579afc1471f52676" ON "tag" ("id") + `); + await queryRunner.query(` + CREATE INDEX IF NOT EXISTS "IDX_6a9775008add570dc3e5a0bab7" ON "tag" ("name") + `); + await queryRunner.query(` + CREATE INDEX IF NOT EXISTS "IDX_352a30652cd352f552fef73dec" ON "game" ("id") + `); + await queryRunner.query(` + CREATE INDEX IF NOT EXISTS "IDX_71b846918f80786eed6bfb68b7" ON "developer" ("id") + `); + await queryRunner.query(` + CREATE INDEX IF NOT EXISTS "IDX_5c2989f7bc37f907cfd937c0fd" ON "developer" ("name") + `); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + DROP INDEX "public"."IDX_5c2989f7bc37f907cfd937c0fd" + `); + await queryRunner.query(` + DROP INDEX "public"."IDX_71b846918f80786eed6bfb68b7" + `); + await queryRunner.query(` + DROP INDEX "public"."IDX_352a30652cd352f552fef73dec" + `); + await queryRunner.query(` + DROP INDEX "public"."IDX_6a9775008add570dc3e5a0bab7" + `); + await queryRunner.query(` + DROP INDEX "public"."IDX_8e4052373c579afc1471f52676" + `); + await queryRunner.query(` + DROP INDEX "public"."IDX_66df34da7fb037e24fc7fee642" + `); + await queryRunner.query(` + DROP INDEX "public"."IDX_f3172007d4de5ae8e7692759d7" + `); + await queryRunner.query(` + DROP INDEX "public"."IDX_9dc496f2e5b912da9edd2aa445" + `); + await queryRunner.query(` + DROP INDEX "public"."IDX_70a5936b43177f76161724da3e" + `); + await queryRunner.query(` + DROP INDEX "public"."IDX_feaddf361921db1df3a6fe3965" + `); + await queryRunner.query(` + DROP INDEX "public"."IDX_ddcaca3a9db9d77105d51c02c2" + `); + await queryRunner.query(` + DROP INDEX "public"."IDX_79abdfd87a688f9de756a162b6" + `); + await queryRunner.query(` + DROP INDEX "public"."IDX_c2a3f8b06558be9508161af22e" + `); + await queryRunner.query(` + DROP INDEX "public"."IDX_d6db1ab4ee9ad9dbe86c64e4cc" + `); + await queryRunner.query(` + DROP INDEX "public"."IDX_dd8cd9e50dd049656e4be1f7e8" + `); + await queryRunner.query(` + DROP INDEX "public"."IDX_0285d4f1655d080cfcf7d1ab14" + `); + await queryRunner.query(` + CREATE INDEX IF NOT EXISTS "IDX_e0626148aee5829fd312447001" ON "image" ("source") + `); + } +} diff --git a/src/modules/database/migrations/sqlite/1695590724678-create_indices.ts b/src/modules/database/migrations/sqlite/1695590724678-create_indices.ts new file mode 100644 index 00000000..4dbd2471 --- /dev/null +++ b/src/modules/database/migrations/sqlite/1695590724678-create_indices.ts @@ -0,0 +1,237 @@ +import { MigrationInterface, QueryRunner } from "typeorm"; + +export class CreateIndices1695590724678 implements MigrationInterface { + name = "CreateIndices1695590724678"; + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + DROP INDEX "IDX_e0626148aee5829fd312447001" + `); + await queryRunner.query(` + CREATE TABLE "temporary_gamevault_user" ( + "id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, + "created_at" datetime NOT NULL DEFAULT (datetime('now')), + "updated_at" datetime NOT NULL DEFAULT (datetime('now')), + "deleted_at" datetime, + "entity_version" integer NOT NULL, + "username" varchar NOT NULL, + "password" varchar NOT NULL, + "email" varchar, + "first_name" varchar, + "last_name" varchar, + "activated" boolean NOT NULL DEFAULT (0), + "role" varchar CHECK("role" IN ('0', '1', '2', '3')) NOT NULL DEFAULT (1), + "profile_picture_id" integer, + "background_image_id" integer, + CONSTRAINT "UQ_d0e7d50057240e5752a2c303ffb" UNIQUE ("email"), + CONSTRAINT "UQ_ad2fda40ce941655c838fb1435f" UNIQUE ("username"), + CONSTRAINT "FK_4b83e27ed50c1e183a69fceef68" FOREIGN KEY ("background_image_id") REFERENCES "image" ("id") ON DELETE CASCADE ON UPDATE NO ACTION, + CONSTRAINT "FK_c1779b9b22212754248aa404bad" FOREIGN KEY ("profile_picture_id") REFERENCES "image" ("id") ON DELETE CASCADE ON UPDATE NO ACTION + ) + `); + await queryRunner.query(` + INSERT INTO "temporary_gamevault_user"( + "id", + "created_at", + "updated_at", + "deleted_at", + "entity_version", + "username", + "password", + "email", + "first_name", + "last_name", + "activated", + "role", + "profile_picture_id", + "background_image_id" + ) + SELECT "id", + "created_at", + "updated_at", + "deleted_at", + "entity_version", + "username", + "password", + "email", + "first_name", + "last_name", + "activated", + "role", + "profile_picture_id", + "background_image_id" + FROM "gamevault_user" + `); + await queryRunner.query(` + DROP TABLE "gamevault_user" + `); + await queryRunner.query(` + ALTER TABLE "temporary_gamevault_user" + RENAME TO "gamevault_user" + `); + await queryRunner.query(` + CREATE INDEX "IDX_0285d4f1655d080cfcf7d1ab14" ON "genre" ("id") + `); + await queryRunner.query(` + CREATE INDEX "IDX_dd8cd9e50dd049656e4be1f7e8" ON "genre" ("name") + `); + await queryRunner.query(` + CREATE INDEX "IDX_d6db1ab4ee9ad9dbe86c64e4cc" ON "image" ("id") + `); + await queryRunner.query(` + CREATE INDEX "IDX_c2a3f8b06558be9508161af22e" ON "gamevault_user" ("id") + `); + await queryRunner.query(` + CREATE INDEX "IDX_79abdfd87a688f9de756a162b6" ON "progress" ("id") + `); + await queryRunner.query(` + CREATE INDEX "IDX_ddcaca3a9db9d77105d51c02c2" ON "progress" ("user_id") + `); + await queryRunner.query(` + CREATE INDEX "IDX_feaddf361921db1df3a6fe3965" ON "progress" ("game_id") + `); + 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_f3172007d4de5ae8e7692759d7" ON "store" ("id") + `); + await queryRunner.query(` + CREATE INDEX "IDX_66df34da7fb037e24fc7fee642" ON "store" ("name") + `); + 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_352a30652cd352f552fef73dec" ON "game" ("id") + `); + await queryRunner.query(` + CREATE INDEX "IDX_71b846918f80786eed6bfb68b7" ON "developer" ("id") + `); + await queryRunner.query(` + CREATE INDEX "IDX_5c2989f7bc37f907cfd937c0fd" ON "developer" ("name") + `); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + DROP INDEX "IDX_5c2989f7bc37f907cfd937c0fd" + `); + await queryRunner.query(` + DROP INDEX "IDX_71b846918f80786eed6bfb68b7" + `); + await queryRunner.query(` + DROP INDEX "IDX_352a30652cd352f552fef73dec" + `); + await queryRunner.query(` + DROP INDEX "IDX_6a9775008add570dc3e5a0bab7" + `); + await queryRunner.query(` + DROP INDEX "IDX_8e4052373c579afc1471f52676" + `); + await queryRunner.query(` + DROP INDEX "IDX_66df34da7fb037e24fc7fee642" + `); + await queryRunner.query(` + DROP INDEX "IDX_f3172007d4de5ae8e7692759d7" + `); + await queryRunner.query(` + DROP INDEX "IDX_9dc496f2e5b912da9edd2aa445" + `); + await queryRunner.query(` + DROP INDEX "IDX_70a5936b43177f76161724da3e" + `); + await queryRunner.query(` + DROP INDEX "IDX_feaddf361921db1df3a6fe3965" + `); + await queryRunner.query(` + DROP INDEX "IDX_ddcaca3a9db9d77105d51c02c2" + `); + await queryRunner.query(` + DROP INDEX "IDX_79abdfd87a688f9de756a162b6" + `); + await queryRunner.query(` + DROP INDEX "IDX_c2a3f8b06558be9508161af22e" + `); + await queryRunner.query(` + DROP INDEX "IDX_d6db1ab4ee9ad9dbe86c64e4cc" + `); + await queryRunner.query(` + DROP INDEX "IDX_dd8cd9e50dd049656e4be1f7e8" + `); + await queryRunner.query(` + DROP INDEX "IDX_0285d4f1655d080cfcf7d1ab14" + `); + await queryRunner.query(` + ALTER TABLE "gamevault_user" + RENAME TO "temporary_gamevault_user" + `); + await queryRunner.query(` + CREATE TABLE "gamevault_user" ( + "id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, + "created_at" datetime NOT NULL DEFAULT (datetime('now')), + "updated_at" datetime NOT NULL DEFAULT (datetime('now')), + "deleted_at" datetime, + "entity_version" integer NOT NULL, + "username" varchar NOT NULL, + "password" varchar NOT NULL, + "email" varchar, + "first_name" varchar, + "last_name" varchar, + "activated" boolean NOT NULL DEFAULT (0), + "role" varchar CHECK("role" IN ('0', '1', '2', '3')) NOT NULL DEFAULT (1), + "profile_picture_id" integer, + "background_image_id" integer, + CONSTRAINT "UQ_d0e7d50057240e5752a2c303ffb" UNIQUE ("email"), + CONSTRAINT "UQ_ad2fda40ce941655c838fb1435f" UNIQUE ("username"), + CONSTRAINT "FK_4b83e27ed50c1e183a69fceef68" FOREIGN KEY ("background_image_id") REFERENCES "image" ("id") ON DELETE CASCADE ON UPDATE NO ACTION, + CONSTRAINT "FK_c1779b9b22212754248aa404bad" FOREIGN KEY ("profile_picture_id") REFERENCES "image" ("id") ON DELETE CASCADE ON UPDATE NO ACTION + ) + `); + await queryRunner.query(` + INSERT INTO "gamevault_user"( + "id", + "created_at", + "updated_at", + "deleted_at", + "entity_version", + "username", + "password", + "email", + "first_name", + "last_name", + "activated", + "role", + "profile_picture_id", + "background_image_id" + ) + SELECT "id", + "created_at", + "updated_at", + "deleted_at", + "entity_version", + "username", + "password", + "email", + "first_name", + "last_name", + "activated", + "role", + "profile_picture_id", + "background_image_id" + FROM "temporary_gamevault_user" + `); + await queryRunner.query(` + DROP TABLE "temporary_gamevault_user" + `); + await queryRunner.query(` + CREATE INDEX "IDX_e0626148aee5829fd312447001" ON "image" ("source") + `); + } +} diff --git a/src/modules/database/models/paginated-entity.model.ts b/src/modules/database/models/paginated-entity.model.ts index f237adab..118df016 100644 --- a/src/modules/database/models/paginated-entity.model.ts +++ b/src/modules/database/models/paginated-entity.model.ts @@ -1,4 +1,4 @@ -import { ApiProperty } from "@nestjs/swagger"; +import { ApiProperty, ApiPropertyOptional } from "@nestjs/swagger"; import { Paginated } from "nestjs-paginate"; import { SortBy, Column } from "nestjs-paginate/lib/helper"; @@ -19,20 +19,22 @@ export class Metadata { search: string; @ApiProperty({ description: "select string" }) select: string[]; - @ApiProperty({ description: "filters that were applied by the query" }) + @ApiPropertyOptional({ + description: "filters that were applied by the query", + }) filter?: { [column: string]: string | string[]; }; } export class Links { - @ApiProperty({ + @ApiPropertyOptional({ example: "http://localhost:8080/games?limit=5&page=1&sortBy=title:DESC&search=i&filter.early_access=$not:true", description: "first page", }) first?: string; - @ApiProperty({ + @ApiPropertyOptional({ example: "http://localhost:8080/games?limit=5&page=1&sortBy=title:DESC&search=i&filter.early_access=$not:true", description: "previous page", @@ -44,13 +46,13 @@ export class Links { description: "current page", }) current: string; - @ApiProperty({ + @ApiPropertyOptional({ example: "http://localhost:8080/games?limit=5&page=3&sortBy=title:DESC&search=i&filter.early_access=$not:true", description: "next page", }) next?: string; - @ApiProperty({ + @ApiPropertyOptional({ example: "http://localhost:8080/games?limit=5&page=3&sortBy=title:DESC&search=i&filter.early_access=$not:true", description: "last page", diff --git a/src/modules/developers/developer.entity.ts b/src/modules/developers/developer.entity.ts index 69f2b54c..6fa3374f 100644 --- a/src/modules/developers/developer.entity.ts +++ b/src/modules/developers/developer.entity.ts @@ -1,5 +1,5 @@ import { ApiProperty } from "@nestjs/swagger"; -import { Entity, Column, ManyToMany } from "typeorm"; +import { Entity, Column, ManyToMany, Index } from "typeorm"; import { Game } from "../games/game.entity"; import { DatabaseEntity } from "../database/database.entity"; @@ -12,6 +12,7 @@ export class Developer extends DatabaseEntity { }) rawg_id: number; + @Index() @Column({ unique: true }) @ApiProperty({ example: "Rockstar North", diff --git a/src/modules/files/files.service.ts b/src/modules/files/files.service.ts index 7da170e2..bb66fe9f 100644 --- a/src/modules/files/files.service.ts +++ b/src/modules/files/files.service.ts @@ -1,5 +1,6 @@ import { Injectable, + InternalServerErrorException, Logger, OnApplicationBootstrap, StreamableFile, @@ -14,7 +15,7 @@ import { readdirSync, statSync, } from "fs"; -import { basename, extname } from "path"; +import path, { basename, extname } from "path"; import configuration from "../../configuration"; import mock from "../games/games.mock"; import mime from "mime"; @@ -26,6 +27,8 @@ import { RawgService } from "../providers/rawg/rawg.service"; import { BoxArtsService } from "../boxarts/boxarts.service"; import globals from "../../globals"; import Throttle from "throttle"; +import filenameSanitizer from "sanitize-filename"; +import unidecode from "unidecode"; @Injectable() export class FilesService implements OnApplicationBootstrap { @@ -71,15 +74,10 @@ export class FilesService implements OnApplicationBootstrap { try { gameToIndex.size = file.size; gameToIndex.file_path = `${configuration.VOLUMES.FILES}/${file.name}`; - gameToIndex.type = await this.detectGameType(gameToIndex.file_path); - this.logger.debug( - `Detected game "${gameToIndex.file_path}" type as ${gameToIndex.type}`, - ); gameToIndex.title = this.extractTitle(file.name); gameToIndex.release_date = this.extractReleaseYear(file.name); gameToIndex.version = this.extractVersion(file.name); gameToIndex.early_access = this.extractEarlyAccessFlag(file.name); - // For each file, check if it already exists in the database. const existingGameTuple: [GameExistence, Game] = await this.gamesService.checkIfGameExistsInDatabase(gameToIndex); @@ -94,6 +92,7 @@ export class FilesService implements OnApplicationBootstrap { case GameExistence.DOES_NOT_EXIST: { this.logger.debug(`Indexing new file "${gameToIndex.file_path}"`); + gameToIndex.type = await this.detectGameType(gameToIndex.file_path); await this.gamesService.saveGame(gameToIndex); continue; } @@ -105,6 +104,7 @@ export class FilesService implements OnApplicationBootstrap { const restoredGame = await this.gamesService.restoreGame( existingGameTuple[1].id, ); + gameToIndex.type = await this.detectGameType(gameToIndex.file_path); await this.updateGame(restoredGame, gameToIndex); continue; } @@ -113,6 +113,7 @@ export class FilesService implements OnApplicationBootstrap { this.logger.debug( `Detected changes in file "${gameToIndex.file_path}" in the database. Updating the information.`, ); + gameToIndex.type = await this.detectGameType(gameToIndex.file_path); await this.updateGame(existingGameTuple[1], gameToIndex); continue; } @@ -161,13 +162,12 @@ export class FilesService implements OnApplicationBootstrap { * regular expression. * * @private - * @param fileName - A string representing the file name. + * @param filePath - A string representing the files path. * @returns - The extracted game title string. */ - private extractTitle(fileName: string): string { - const directoryRemoved = fileName.replace(/^.*[\\/]/, ""); - const extensionRemoved = directoryRemoved.replace(/\.([^.]*)$/, ""); - const parenthesesRemoved = extensionRemoved.replace(/\([^)]*\)/g, ""); + private extractTitle(filePath: string): string { + const basename = path.basename(filePath, path.extname(filePath)); + const parenthesesRemoved = basename.replace(/\([^)]*\)/g, ""); return parenthesesRemoved.trim(); } @@ -254,25 +254,31 @@ export class FilesService implements OnApplicationBootstrap { try { if (/\(W_P\)/.test(path)) { this.logger.debug( - `Detected game "${path}" type as ${GameType.WINDOWS_PORTABLE} because of (W_P) override in filename.`, + `Detected game "${path}" type as ${GameType.WINDOWS_PORTABLE}, because of (W_P) override in filename.`, ); return GameType.WINDOWS_PORTABLE; } if (/\(W_S\)/.test(path)) { this.logger.debug( - `Detected game "${path}" type as ${GameType.WINDOWS_SETUP} because of (W_S) override in filename.`, + `Detected game "${path}" type as ${GameType.WINDOWS_SETUP}, because of (W_S) override in filename.`, ); return GameType.WINDOWS_SETUP; } // Failsafe for Mock-Files because we cant look into them if (configuration.TESTING.MOCK_FILES) { + this.logger.debug( + `Detected game "${path}" type as ${GameType.WINDOWS_SETUP}, because TESTING_MOCK_FILES is set to true.`, + ); return GameType.WINDOWS_SETUP; } // Detect single File executable if (path.toLowerCase().endsWith(".exe")) { + this.logger.debug( + `Detected game "${path}" type as ${GameType.WINDOWS_SETUP}, because it ends with .exe.`, + ); return GameType.WINDOWS_SETUP; } @@ -281,15 +287,22 @@ export class FilesService implements OnApplicationBootstrap { if (windowsExecutablesInArchive.length > 0) { if (this.detectWindowsSetupExecutable(windowsExecutablesInArchive)) { + this.logger.debug( + `Detected game "${path}" type as ${GameType.WINDOWS_SETUP}, because there are windows setup executables in the archive.`, + ); return GameType.WINDOWS_SETUP; } + this.logger.debug( + `Detected game "${path}" type as ${GameType.WINDOWS_SETUP}, because there are no windows setup executables in the archive.`, + ); return GameType.WINDOWS_PORTABLE; } // More Platforms and Game Types can be added here. + this.logger.debug(`Could not detect game type for "${path}"`); return GameType.UNDETECTABLE; } catch (error) { - this.logger.error("Error detecting game type:", error); + this.logger.warn(`Could not detect game type for "${path}"`, error); return GameType.UNDETECTABLE; } } @@ -406,28 +419,35 @@ export class FilesService implements OnApplicationBootstrap { * @public */ private fetchFiles(): IGameVaultFile[] { - if (configuration.TESTING.MOCK_FILES) { - return mock; - } + try { + if (configuration.TESTING.MOCK_FILES) { + return mock; + } - return readdirSync(configuration.VOLUMES.FILES, { - encoding: "utf8", - recursive: configuration.GAMES.SEARCH_RECURSIVE, - }) - .filter((file) => - configuration.GAMES.SUPPORTED_FILE_FORMATS.includes( - extname(file).toLowerCase(), - ), - ) - .map( - (file) => - ({ - name: file, - size: BigInt( - statSync(`${configuration.VOLUMES.FILES}/${file}`).size, - ), - }) as IGameVaultFile, + return readdirSync(configuration.VOLUMES.FILES, { + encoding: "utf8", + recursive: configuration.GAMES.SEARCH_RECURSIVE, + }) + .filter((file) => + configuration.GAMES.SUPPORTED_FILE_FORMATS.includes( + extname(file).toLowerCase(), + ), + ) + .map( + (file) => + ({ + name: file, + size: BigInt( + statSync(`${configuration.VOLUMES.FILES}/${file}`).size, + ), + }) as IGameVaultFile, + ); + } catch (error) { + throw new InternalServerErrorException( + error, + "Error reading /files directory!", ); + } } /** @@ -454,10 +474,9 @@ export class FilesService implements OnApplicationBootstrap { } const game = await this.gamesService.getGameById(gameId); - const fileExtension = RegExp(/(?:\.([^.]+))?$/).exec(game.file_path)[0]; let fileDownloadPath = game.file_path; - if (!globals.ARCHIVE_FORMATS.includes(fileExtension)) { + if (!globals.ARCHIVE_FORMATS.includes(path.extname(game.file_path))) { fileDownloadPath = `/tmp/${gameId}.tar`; if (existsSync(fileDownloadPath)) { @@ -477,11 +496,12 @@ export class FilesService implements OnApplicationBootstrap { ); const type = mime.getType(fileDownloadPath); - const encodedFilename = encodeURIComponent( - fileDownloadPath.replace(/^.*[\\/]/, ""), + const filename = filenameSanitizer( + unidecode(path.basename(fileDownloadPath)), ); + const headers = { - disposition: `attachment; filename*=UTF-8''${encodedFilename}; filename="${encodedFilename}"`, + disposition: `attachment; filename="${filename}"`, length: statSync(fileDownloadPath).size, type, }; diff --git a/src/modules/games/game.entity.ts b/src/modules/games/game.entity.ts index 87413c60..10ee3020 100644 --- a/src/modules/games/game.entity.ts +++ b/src/modules/games/game.entity.ts @@ -22,10 +22,9 @@ import { GameType } from "./models/game-type.enum"; @Entity() export class Game extends DatabaseEntity { @Column({ nullable: true }) - @ApiProperty({ + @ApiPropertyOptional({ description: "unique rawg-api-identifier of the game", example: 1212, - required: false, }) rawg_id?: number; @@ -43,15 +42,13 @@ export class Game extends DatabaseEntity { description: "rawg-api-title of the game (a correction needed if different from title)", example: "Grand Theft Auto V", - required: false, }) rawg_title?: string; @Column({ nullable: true }) - @ApiProperty({ + @ApiPropertyOptional({ description: "version tag (extracted from the filename e.g. '(v1.0.0)')", example: "v1.0.0", - required: false, }) version?: string; @@ -63,18 +60,16 @@ export class Game extends DatabaseEntity { release_date: Date; @Column({ nullable: true }) - @ApiProperty({ + @ApiPropertyOptional({ description: "release date of the game (from rawg-api)", example: "2013-09-17T00:00:00.000Z", - required: false, }) rawg_release_date?: Date; @Column({ nullable: true }) - @ApiProperty({ + @ApiPropertyOptional({ description: "date the game was last updated using the rawg-api", example: "2021-03-01T00:00:00.000Z", - required: false, }) cache_date?: Date; @@ -108,7 +103,6 @@ export class Game extends DatabaseEntity { description: "rawg-api description of the game", example: "An open world action-adventure video game developed by Rockstar North and published by Rockstar Games.", - required: false, }) description?: string; @@ -122,7 +116,6 @@ export class Game extends DatabaseEntity { @ApiPropertyOptional({ description: "box image of the game", type: () => Image, - required: false, }) box_image?: Image; @@ -136,7 +129,6 @@ export class Game extends DatabaseEntity { @ApiPropertyOptional({ description: "background image of the game", type: () => Image, - required: false, }) background_image?: Image; @@ -144,7 +136,6 @@ export class Game extends DatabaseEntity { @ApiPropertyOptional({ description: "website url of the game from rawg-api", example: "https://www.escapefromtarkov.com/", - required: false, }) website_url?: string; @@ -152,16 +143,14 @@ export class Game extends DatabaseEntity { @ApiPropertyOptional({ description: "metacritic rating from rawg-api of the game", example: 90, - required: false, }) metacritic_rating?: number; @Column({ nullable: true, type: "integer" }) - @ApiProperty({ + @ApiPropertyOptional({ description: "average playtime of other people in the game (similar to howlongtobeat.com) from rawg-api (in minutes)", example: 180, - required: false, }) average_playtime?: number; diff --git a/src/modules/games/games.controller.ts b/src/modules/games/games.controller.ts index 9b1ded4d..b07cbcfe 100644 --- a/src/modules/games/games.controller.ts +++ b/src/modules/games/games.controller.ts @@ -61,7 +61,10 @@ export class GamesController { @Get() @PaginateQueryOptions() @ApiOkResponsePaginated(Game) - @ApiOperation({ summary: "get a list of games", operationId: "getGames" }) + @ApiOperation({ + summary: "get a list of games", + operationId: "getGames", + }) @MinimumRole(Role.GUEST) async getGames(@Paginate() query: PaginateQuery): Promise> { return paginate(query, this.gamesRepository, { @@ -69,15 +72,7 @@ export class GamesController { defaultLimit: 100, maxLimit: NO_PAGINATION, nullSort: "last", - relations: [ - "developers", - "genres", - "publishers", - "tags", - "progresses", - "box_image", - "background_image", - ], + relations: ["progresses", "box_image"], sortableColumns: [ "id", "title", @@ -90,14 +85,7 @@ export class GamesController { "early_access", "type", ], - searchableColumns: [ - "title", - "description", - "developers.name", - "genres.name", - "publishers.name", - "tags.name", - ], + searchableColumns: ["title", "description"], filterableColumns: { id: all_filters, title: all_filters, @@ -108,10 +96,6 @@ export class GamesController { average_playtime: all_filters, early_access: all_filters, type: all_filters, - "developers.name": all_filters, - "genres.name": all_filters, - "publishers.name": all_filters, - "tags.name": all_filters, "progresses.created_at": all_filters, "progresses.updated_at": all_filters, "progresses.minutes_played": all_filters, diff --git a/src/modules/games/models/minimal-game.ts b/src/modules/games/models/minimal-game.ts new file mode 100644 index 00000000..634aebc8 --- /dev/null +++ b/src/modules/games/models/minimal-game.ts @@ -0,0 +1,40 @@ +import { ApiPropertyOptional } from "@nestjs/swagger"; +import { Image } from "../../images/image.entity"; + +export class MinimalGame { + @ApiPropertyOptional({ + description: "unique gamevault-identifier of the game", + example: 1212, + }) + id?: number; + + @ApiPropertyOptional({ + description: "unique rawg-api-identifier of the game", + example: 1212, + }) + rawg_id?: number; + + @ApiPropertyOptional({ + description: "title of the game", + example: "Grand Theft Auto V", + }) + title?: string; + + @ApiPropertyOptional({ + description: "release date of the game", + example: "2013-09-17T00:00:00.000Z", + }) + release_date?: Date; + + @ApiPropertyOptional({ + description: "box image of the game", + type: () => Image, + }) + box_image?: Image; + + @ApiPropertyOptional({ + description: "box image url of the game", + example: "example.com/example.jpg", + }) + box_image_url?: string; +} diff --git a/src/modules/games/models/update-game.dto.ts b/src/modules/games/models/update-game.dto.ts index 926c3493..87670ef5 100644 --- a/src/modules/games/models/update-game.dto.ts +++ b/src/modules/games/models/update-game.dto.ts @@ -1,4 +1,4 @@ -import { ApiProperty } from "@nestjs/swagger"; +import { ApiPropertyOptional } from "@nestjs/swagger"; import { IsNotEmpty, IsNumber, @@ -12,7 +12,7 @@ export class UpdateGameDto { @IsNumber() @IsPositive() @IsNotEmpty() - @ApiProperty({ + @ApiPropertyOptional({ example: 1000, description: "unique rawg-api-identifier of the game", }) @@ -20,7 +20,7 @@ export class UpdateGameDto { @IsOptional() @IsUrl() - @ApiProperty({ + @ApiPropertyOptional({ pattern: "url", example: "https://example.com/image.png", description: "url to the image", @@ -29,7 +29,7 @@ export class UpdateGameDto { @IsOptional() @IsNumber() - @ApiProperty({ + @ApiPropertyOptional({ example: 69_420, description: "id of the image", }) @@ -37,7 +37,7 @@ export class UpdateGameDto { @IsOptional() @IsUrl() - @ApiProperty({ + @ApiPropertyOptional({ pattern: "url", example: "https://example.com/image.png", description: "url to the image", @@ -46,7 +46,7 @@ export class UpdateGameDto { @IsOptional() @IsNumber() - @ApiProperty({ + @ApiPropertyOptional({ example: 69_420, description: "id of the image", }) diff --git a/src/modules/genres/genre.entity.ts b/src/modules/genres/genre.entity.ts index 59f0022c..ab0f15af 100644 --- a/src/modules/genres/genre.entity.ts +++ b/src/modules/genres/genre.entity.ts @@ -1,5 +1,5 @@ import { ApiProperty } from "@nestjs/swagger"; -import { Entity, Column, ManyToMany } from "typeorm"; +import { Entity, Column, ManyToMany, Index } from "typeorm"; import { Game } from "../games/game.entity"; import { DatabaseEntity } from "../database/database.entity"; @@ -12,6 +12,7 @@ export class Genre extends DatabaseEntity { }) rawg_id: number; + @Index() @Column({ unique: true }) @ApiProperty({ example: "Platformer", @@ -25,5 +26,5 @@ export class Genre extends DatabaseEntity { type: () => Game, isArray: true, }) - games?: Game[]; + games: Game[]; } diff --git a/src/modules/images/image.entity.ts b/src/modules/images/image.entity.ts index cfe6e90a..9c5837e6 100644 --- a/src/modules/images/image.entity.ts +++ b/src/modules/images/image.entity.ts @@ -1,13 +1,12 @@ -import { ApiProperty } from "@nestjs/swagger"; +import { ApiPropertyOptional } from "@nestjs/swagger"; import { Entity, Column, Index, ManyToOne } from "typeorm"; import { DatabaseEntity } from "../database/database.entity"; import { GamevaultUser } from "../users/gamevault-user.entity"; @Entity() export class Image extends DatabaseEntity { - @Index() @Column({ nullable: true }) - @ApiProperty({ + @ApiPropertyOptional({ example: "https://upload.wikimedia.org/wikipedia/commons/thumb/e/e1/Grand_Theft_Auto_logo_series.svg", description: "the original source URL of the image", @@ -17,14 +16,14 @@ export class Image extends DatabaseEntity { @Column({ unique: true, nullable: true }) @Index() - @ApiProperty({ + @ApiPropertyOptional({ example: "/images/14", description: "the path of the image on the filesystem", }) path?: string; @Column({ nullable: true }) - @ApiProperty({ + @ApiPropertyOptional({ example: "image/jpeg", description: "the media type of the image on the filesystem", }) @@ -33,10 +32,9 @@ export class Image extends DatabaseEntity { @ManyToOne(() => GamevaultUser, (user) => user.uploaded_images, { nullable: true, }) - @ApiProperty({ + @ApiPropertyOptional({ description: "the uploader of the image", type: () => GamevaultUser, - nullable: true, }) uploader?: GamevaultUser; } diff --git a/src/modules/images/images.service.ts b/src/modules/images/images.service.ts index 0350c70f..b05ed9f0 100644 --- a/src/modules/images/images.service.ts +++ b/src/modules/images/images.service.ts @@ -5,7 +5,6 @@ import { InternalServerErrorException, Logger, NotFoundException, - UnprocessableEntityException, forwardRef, } from "@nestjs/common"; import { InjectRepository } from "@nestjs/typeorm"; @@ -70,6 +69,7 @@ export class ImagesService { image.uploader = await this.usersService.getUserByUsernameOrFail(uploaderUsername); } + this.logger.debug(`Downloading Image from "${image.source}" ...`); const response = await this.downloadImageFromUrl(image.source); const imageBuffer = Buffer.from(response.data); const fileType = this.checkImageFileType(imageBuffer); @@ -84,7 +84,8 @@ export class ImagesService { if (image.id) { await this.deleteImage(image); } - throw new UnprocessableEntityException( + throw new InternalServerErrorException( + error, `Failed to download image from '${sourceUrl}'.`, ); } diff --git a/src/modules/log/exception.filter.ts b/src/modules/log/exception.filter.ts index 34ba4071..350e5c7e 100644 --- a/src/modules/log/exception.filter.ts +++ b/src/modules/log/exception.filter.ts @@ -46,10 +46,7 @@ export class LoggingExceptionFilter implements ExceptionFilter { { url: request.url, error: exception }, `Unhandled ${exception.name} occurred: ${exception.message}`, ); - response.status(httpStatusCode).json({ - statusCode: httpStatusCode, - error: exception.message, - }); + response.status(httpStatusCode).json(exception.message); } } } diff --git a/src/modules/progress/progress.entity.ts b/src/modules/progress/progress.entity.ts index cf931aa0..8962a493 100644 --- a/src/modules/progress/progress.entity.ts +++ b/src/modules/progress/progress.entity.ts @@ -1,5 +1,5 @@ -import { ApiProperty } from "@nestjs/swagger"; -import { Entity, Column, ManyToOne } from "typeorm"; +import { ApiProperty, ApiPropertyOptional } from "@nestjs/swagger"; +import { Entity, Column, ManyToOne, Index } from "typeorm"; import { State } from "./models/state.enum"; import { Game } from "../games/game.entity"; import { GamevaultUser } from "../users/gamevault-user.entity"; @@ -7,6 +7,7 @@ import { DatabaseEntity } from "../database/database.entity"; @Entity() export class Progress extends DatabaseEntity { + @Index() @ManyToOne(() => GamevaultUser, (user) => user.progresses) @ApiProperty({ description: "user the progress belongs to", @@ -14,6 +15,7 @@ export class Progress extends DatabaseEntity { }) user: GamevaultUser; + @Index() @ManyToOne(() => Game, (game) => game.progresses) @ApiProperty({ description: "game the progress belongs to", @@ -38,10 +40,9 @@ export class Progress extends DatabaseEntity { state: State; @Column({ nullable: true }) - @ApiProperty({ + @ApiPropertyOptional({ description: "date the progress was updated", example: "2020-01-01T00:00:00.000Z", - required: false, }) last_played_at?: Date; } diff --git a/src/modules/progress/progress.service.ts b/src/modules/progress/progress.service.ts index 0d66a931..434ac165 100644 --- a/src/modules/progress/progress.service.ts +++ b/src/modules/progress/progress.service.ts @@ -30,19 +30,22 @@ export class ProgressService { } /** - * Reads the .progressignore file and updates the ignoreList property. + * Reads the ignored-executables.txt file and updates the ignoreList property. * * @private */ private readIgnoreFile() { try { - const filePath = path.join(__dirname, "../../../static/.progressignore"); + const filePath = path.join( + __dirname, + "../../../assets/ignored-executables.txt", + ); const fileContent = fs.readFileSync(filePath, "utf-8"); this.ignoreList = fileContent.split("\n").map((line) => line.trim()); } catch (error) { throw new InternalServerErrorException( error, - "Error reading .progressignore file", + "Error reading ignored-executables.txt file", ); } } diff --git a/src/modules/providers/rawg/mapper.service.ts b/src/modules/providers/rawg/mapper.service.ts index 97452c5a..9ea1d20b 100644 --- a/src/modules/providers/rawg/mapper.service.ts +++ b/src/modules/providers/rawg/mapper.service.ts @@ -25,20 +25,23 @@ export class RawgMapperService { * Maps a RawgGame to a Game entity, filling missing information in the entity * using the RawgGame. * - * @param entity - The Game entity to fill with information. + * @param game - The Game entity to fill with information. * @param rawg_game - The RawgGame to extract information from. * @returns A Promise that resolves with the updated Game entity. */ - public async map(entity: Game, rawg_game: RawgGame): Promise { - this.logger.debug(`Mapping game "${rawg_game.name}" to "${entity.title}"`); - entity = await this.mapStoresToEntity(rawg_game, entity); - entity = await this.mapDevelopersToEntity(rawg_game, entity); - entity = await this.mapPublishersToEntity(rawg_game, entity); - entity = await this.mapTagsToEntity(rawg_game, entity); - entity = await this.mapGenresToEntity(rawg_game, entity); - entity = await this.mapRawgGameToEntity(rawg_game, entity); - entity.cache_date = new Date(); - return entity; + public async mapRawgGameToGame( + rawg_game: RawgGame, + game: Game, + ): Promise { + this.logger.debug(`Mapping game "${rawg_game.name}" to "${game.title}"`); + game = await this.mapRawgStoresToGame(rawg_game, game); + game = await this.mapRawgDevelopersToGame(rawg_game, game); + game = await this.mapRawgPublishersToGame(rawg_game, game); + game = await this.mapRawgTagsToGame(rawg_game, game); + game = await this.mapRawgGenresToGame(rawg_game, game); + game = await this.mapRawgGameDetailsToGame(rawg_game, game); + game.cache_date = new Date(); + return game; } /** @@ -48,7 +51,10 @@ export class RawgMapperService { * @param entity - The Game entity. * @returns The updated Game entity. */ - private async mapStoresToEntity(game: RawgGame, entity: Game): Promise { + private async mapRawgStoresToGame( + game: RawgGame, + entity: Game, + ): Promise { try { entity.stores = []; if (!game.stores) return entity; @@ -71,7 +77,7 @@ export class RawgMapperService { * @param entity - The Game entity to update with the developers information. * @returns The updated Game entity. */ - private async mapDevelopersToEntity( + private async mapRawgDevelopersToGame( game: RawgGame, entity: Game, ): Promise { @@ -99,7 +105,7 @@ export class RawgMapperService { * @param entity - The Game entity. * @returns The updated Game entity. */ - private async mapPublishersToEntity( + private async mapRawgPublishersToGame( game: RawgGame, entity: Game, ): Promise { @@ -127,7 +133,7 @@ export class RawgMapperService { * @param entity - The Game entity. * @returns The updated Game entity. */ - private async mapTagsToEntity(game: RawgGame, entity: Game): Promise { + private async mapRawgTagsToGame(game: RawgGame, entity: Game): Promise { try { entity.tags = []; @@ -165,7 +171,10 @@ export class RawgMapperService { * @param entity - The Game entity. * @returns The updated Game entity. */ - private async mapGenresToEntity(game: RawgGame, entity: Game): Promise { + private async mapRawgGenresToGame( + game: RawgGame, + entity: Game, + ): Promise { try { entity.genres = []; if (!game.genres) return entity; @@ -187,7 +196,7 @@ export class RawgMapperService { * @param entity - The Game entity to update. * @returns The updated Game entity. */ - private async mapRawgGameToEntity( + private async mapRawgGameDetailsToGame( game: RawgGame, entity: Game, ): Promise { diff --git a/src/modules/providers/rawg/rawg.controller.ts b/src/modules/providers/rawg/rawg.controller.ts index 3d849d9d..722cc884 100644 --- a/src/modules/providers/rawg/rawg.controller.ts +++ b/src/modules/providers/rawg/rawg.controller.ts @@ -6,15 +6,14 @@ import { ApiQuery, ApiTags, } from "@nestjs/swagger"; -import { RawgMapperService } from "./mapper.service"; import { RawgService } from "./rawg.service"; import { Game } from "../../games/game.entity"; import { MinimumRole } from "../../pagination/minimum-role.decorator"; import { Role } from "../../users/models/role.enum"; -import { RawgGame } from "./models/game.interface"; import { IdDto } from "../../database/models/id.dto"; import { GamesService } from "../../games/games.service"; import { BoxArtsService } from "../../boxarts/boxarts.service"; +import { MinimalGame } from "../../games/models/minimal-game"; @ApiTags("rawg") @Controller("rawg") @@ -23,7 +22,6 @@ export class RawgController { constructor( private gamesService: GamesService, private rawgService: RawgService, - private mapper: RawgMapperService, private boxartService: BoxArtsService, ) {} @@ -40,19 +38,23 @@ export class RawgController { }) @ApiQuery({ name: "query", description: "search query" }) @ApiOkResponse({ - type: () => Game, + type: () => MinimalGame, isArray: true, - description: "These objects may have lost some data in conversion", + description: + "These are minimal game objects, without a lot of information.", }) @MinimumRole(Role.EDITOR) - async searchRawg(@Query("query") query: string): Promise { - const rawggames = await this.rawgService.getRawgGames(query); - // for each rawggames entry use mapper to map rawg-games to a new game-model and return it as an array - const games: Game[] = []; - for (const rawggame of rawggames) { - games.push( - await this.mapper.map(new Game(), rawggame as unknown as RawgGame), - ); + async searchRawg(@Query("query") query: string): Promise { + const rawgGames = await this.rawgService.getRawgGames(query); + // for each search result return a minimal gamevault game + const games: MinimalGame[] = []; + for (const rawgGame of rawgGames) { + const newGame = new MinimalGame(); + newGame.rawg_id = rawgGame.id; + newGame.title = rawgGame.name; + newGame.box_image_url = rawgGame.background_image; + newGame.release_date = new Date(rawgGame.released); + games.push(newGame); } return games; } diff --git a/src/modules/providers/rawg/rawg.service.ts b/src/modules/providers/rawg/rawg.service.ts index 6e142c40..c07e801b 100644 --- a/src/modules/providers/rawg/rawg.service.ts +++ b/src/modules/providers/rawg/rawg.service.ts @@ -86,10 +86,19 @@ export class RawgService { */ private async cacheGame(game: Game): Promise { this.logger.debug(`Caching Game: "${game.title}"`); + + if (game.file_path.includes("(NC)")) { + this.logger.debug( + { gameId: game.id, title: game.title, file_path: game.file_path }, + `Game Caching Skipped, because file path contains NO CACHE flag (NC).`, + ); + return game; + } + if (!this.isOutdated(game)) { this.logger.debug( { gameId: game.id, title: game.title, cachedAt: game.cache_date }, - `Game Caching Skipped. Game is up to date.`, + `Game Caching Skipped, because game is already up to date.`, ); return game; } @@ -103,7 +112,7 @@ export class RawgService { game.release_date?.getFullYear() || undefined, ); } - const mappedGame = await this.mapper.map(game, rawgEntry); + const mappedGame = await this.mapper.mapRawgGameToGame(rawgEntry, game); return await this.gamesService.saveGame(mappedGame); } @@ -188,7 +197,6 @@ export class RawgService { `No game found in RAWG for "${title} (${releaseYear || "No Year"})"`, ); } - // Calculate and assign probabilities searchResults.forEach((game) => { const titleCleaned = title.toLowerCase().replaceAll(/[^\w\s]/g, ""); @@ -205,10 +213,8 @@ export class RawgService { game.probability -= Math.abs(releaseYear - gameReleaseYear) / 10; } }); - // Sort search results by probability in descending order searchResults.sort((a, b) => b.probability - a.probability); - return searchResults; } @@ -251,8 +257,9 @@ export class RawgService { }) .pipe( catchError((error: AxiosError) => { - throw new Error( - `Serverside Request Error: ${error.status} ${error.message}`, + throw new InternalServerErrorException( + error, + `Serverside RAWG Request Error: ${error.status} ${error.message}`, ); }), ), @@ -303,8 +310,9 @@ export class RawgService { }) .pipe( catchError((error: AxiosError) => { - throw new Error( - `Serverside Request Error: ${error.status} ${error.message}`, + throw new InternalServerErrorException( + error, + `Serverside RAWG Request Error`, ); }), ), diff --git a/src/modules/publishers/publisher.entity.ts b/src/modules/publishers/publisher.entity.ts index 8fca6c2a..de8be4f5 100644 --- a/src/modules/publishers/publisher.entity.ts +++ b/src/modules/publishers/publisher.entity.ts @@ -1,5 +1,5 @@ import { ApiProperty } from "@nestjs/swagger"; -import { Entity, Column, ManyToMany } from "typeorm"; +import { Entity, Column, ManyToMany, Index } from "typeorm"; import { Game } from "../games/game.entity"; import { DatabaseEntity } from "../database/database.entity"; @@ -12,6 +12,7 @@ export class Publisher extends DatabaseEntity { }) rawg_id: number; + @Index() @Column({ unique: true }) @ApiProperty({ example: "Rockstar Games", diff --git a/src/modules/stores/store.entity.ts b/src/modules/stores/store.entity.ts index 203fc5f6..5513ad70 100644 --- a/src/modules/stores/store.entity.ts +++ b/src/modules/stores/store.entity.ts @@ -1,5 +1,5 @@ import { ApiProperty } from "@nestjs/swagger"; -import { Entity, Column, ManyToMany } from "typeorm"; +import { Entity, Column, ManyToMany, Index } from "typeorm"; import { Game } from "../games/game.entity"; import { DatabaseEntity } from "../database/database.entity"; @@ -12,6 +12,7 @@ export class Store extends DatabaseEntity { }) rawg_id: number; + @Index() @Column({ unique: true }) @ApiProperty({ example: "Steam", diff --git a/src/modules/tags/tag.entity.ts b/src/modules/tags/tag.entity.ts index f54b5a98..b33d1d51 100644 --- a/src/modules/tags/tag.entity.ts +++ b/src/modules/tags/tag.entity.ts @@ -1,5 +1,5 @@ import { ApiProperty } from "@nestjs/swagger"; -import { Entity, Column, ManyToMany } from "typeorm"; +import { Entity, Column, ManyToMany, Index } from "typeorm"; import { Game } from "../games/game.entity"; import { DatabaseEntity } from "../database/database.entity"; @@ -12,6 +12,7 @@ export class Tag extends DatabaseEntity { }) rawg_id: number; + @Index() @Column({ unique: true }) @ApiProperty({ example: "battle-royale", diff --git a/src/modules/users/models/register-user.dto.ts b/src/modules/users/models/register-user.dto.ts index 386d8720..4cb3782b 100644 --- a/src/modules/users/models/register-user.dto.ts +++ b/src/modules/users/models/register-user.dto.ts @@ -30,18 +30,27 @@ export class RegisterUserDto { @ApiProperty({ example: "john.doe@mail.com", description: "email of the user", + required: configuration.USERS.REQUIRE_EMAIL, }) email?: string; @ValidateIf(() => configuration.USERS.REQUIRE_FIRST_NAME) @IsAlpha("de-DE") @IsNotEmpty() - @ApiProperty({ example: "John", description: "first name of the user" }) + @ApiProperty({ + example: "John", + description: "first name of the user", + required: configuration.USERS.REQUIRE_FIRST_NAME, + }) first_name?: string; @ValidateIf(() => configuration.USERS.REQUIRE_LAST_NAME) @IsAlpha("de-DE") @IsNotEmpty() - @ApiProperty({ example: "Doe", description: "last name of the user" }) + @ApiProperty({ + example: "Doe", + description: "last name of the user", + required: configuration.USERS.REQUIRE_LAST_NAME, + }) last_name?: string; } diff --git a/src/modules/users/models/update-user.dto.ts b/src/modules/users/models/update-user.dto.ts index 7d0e3c62..d4f48d3d 100644 --- a/src/modules/users/models/update-user.dto.ts +++ b/src/modules/users/models/update-user.dto.ts @@ -1,4 +1,4 @@ -import { ApiProperty } from "@nestjs/swagger"; +import { ApiPropertyOptional } from "@nestjs/swagger"; import { IsAlpha, IsAlphanumeric, @@ -17,67 +17,61 @@ export class UpdateUserDto { @IsAlphanumeric() @IsOptional() @IsNotEmpty() - @ApiProperty({ + @ApiPropertyOptional({ example: "JohnDoe", description: "username of the user", - nullable: true, }) username?: string; @IsEmail() @IsOptional() @IsNotEmpty() - @ApiProperty({ + @ApiPropertyOptional({ example: "john.doe@mail.com", description: "email of the user", - nullable: true, }) email?: string; @MinLength(8) @IsOptional() @IsNotEmpty() - @ApiProperty({ + @ApiPropertyOptional({ example: "SecretPw822!", minLength: 8, description: "password of the user", - nullable: true, }) password?: string; @IsAlpha("de-DE") @IsOptional() @IsNotEmpty() - @ApiProperty({ + @ApiPropertyOptional({ example: "John", description: "first name of the user", - nullable: true, }) first_name?: string; @IsAlpha("de-DE") @IsOptional() @IsNotEmpty() - @ApiProperty({ + @ApiPropertyOptional({ example: "Doe", description: "last name of the user", - nullable: true, }) last_name?: string; @IsUrl() @IsOptional() - @ApiProperty({ + @ApiPropertyOptional({ pattern: "url", example: "https://example.com/profile-picture.png", description: "url to the profile picture of the user", - nullable: true, }) profile_picture_url?: string; @IsNumber() @IsOptional() - @ApiProperty({ + @ApiPropertyOptional({ example: 69_420, description: "id of the profile picture of the user", }) @@ -85,17 +79,16 @@ export class UpdateUserDto { @IsUrl() @IsOptional() - @ApiProperty({ + @ApiPropertyOptional({ pattern: "url", example: "https://example.com/profile-art.png", description: "url to the profile art (background-image) of the User", - nullable: true, }) background_image_url?: string; @IsNumber() @IsOptional() - @ApiProperty({ + @ApiPropertyOptional({ example: 69_420, description: "id of the profile art (background-image) of the User", }) @@ -103,17 +96,16 @@ export class UpdateUserDto { @IsBoolean() @IsOptional() - @ApiProperty({ + @ApiPropertyOptional({ pattern: "boolean", example: true, description: "wether or not the user is activated. Not yet working.", - nullable: true, }) activated?: boolean; @IsEnum(Role) @IsOptional() - @ApiProperty({ + @ApiPropertyOptional({ type: "enum", enum: Role, example: Role.EDITOR,