From 380a3a9cc0912dbe1411bd55cbf2c63d5af852ff Mon Sep 17 00:00:00 2001 From: Yulia Demir Date: Thu, 29 May 2025 12:53:29 +0300 Subject: [PATCH 01/32] feat: add modules and dtos --- package-lock.json | 1430 ++++++++++++++++---- package.json | 10 +- src/albums/albums.controller.spec.ts | 18 + src/albums/albums.controller.ts | 4 + src/albums/albums.module.ts | 9 + src/albums/albums.service.spec.ts | 18 + src/albums/albums.service.ts | 4 + src/albums/dto/create-album.dto.ts | 16 + src/albums/entities/album.entity.ts | 6 + src/app.module.ts | 7 +- src/artists/artists.controller.spec.ts | 18 + src/artists/artists.controller.ts | 4 + src/artists/artists.module.ts | 9 + src/artists/artists.service.spec.ts | 18 + src/artists/artists.service.ts | 4 + src/artists/dto/create-artist.dto.ts | 12 + src/artists/entities/artist.entity.ts | 5 + src/favorites/dto/create-favorites.dto.ts | 15 + src/favorites/entities/favorites.entity.ts | 5 + src/favorites/favorites.controller.spec.ts | 18 + src/favorites/favorites.controller.ts | 4 + src/favorites/favorites.module.ts | 9 + src/favorites/favorites.service.spec.ts | 18 + src/favorites/favorites.service.ts | 4 + src/tracks/dto/create-track.dto.ts | 20 + src/tracks/entities/track.entity.ts | 7 + src/tracks/tracks.controller.spec.ts | 18 + src/tracks/tracks.controller.ts | 4 + src/tracks/tracks.module.ts | 9 + src/tracks/tracks.service.spec.ts | 18 + src/tracks/tracks.service.ts | 4 + src/users/dto/create-user.dto.ts | 22 + src/users/entities/user.entity.ts | 8 + src/users/users.controller.spec.ts | 18 + src/users/users.controller.ts | 4 + src/users/users.module.ts | 9 + src/users/users.service.spec.ts | 18 + src/users/users.service.ts | 4 + 38 files changed, 1536 insertions(+), 292 deletions(-) create mode 100644 src/albums/albums.controller.spec.ts create mode 100644 src/albums/albums.controller.ts create mode 100644 src/albums/albums.module.ts create mode 100644 src/albums/albums.service.spec.ts create mode 100644 src/albums/albums.service.ts create mode 100644 src/albums/dto/create-album.dto.ts create mode 100644 src/albums/entities/album.entity.ts create mode 100644 src/artists/artists.controller.spec.ts create mode 100644 src/artists/artists.controller.ts create mode 100644 src/artists/artists.module.ts create mode 100644 src/artists/artists.service.spec.ts create mode 100644 src/artists/artists.service.ts create mode 100644 src/artists/dto/create-artist.dto.ts create mode 100644 src/artists/entities/artist.entity.ts create mode 100644 src/favorites/dto/create-favorites.dto.ts create mode 100644 src/favorites/entities/favorites.entity.ts create mode 100644 src/favorites/favorites.controller.spec.ts create mode 100644 src/favorites/favorites.controller.ts create mode 100644 src/favorites/favorites.module.ts create mode 100644 src/favorites/favorites.service.spec.ts create mode 100644 src/favorites/favorites.service.ts create mode 100644 src/tracks/dto/create-track.dto.ts create mode 100644 src/tracks/entities/track.entity.ts create mode 100644 src/tracks/tracks.controller.spec.ts create mode 100644 src/tracks/tracks.controller.ts create mode 100644 src/tracks/tracks.module.ts create mode 100644 src/tracks/tracks.service.spec.ts create mode 100644 src/tracks/tracks.service.ts create mode 100644 src/users/dto/create-user.dto.ts create mode 100644 src/users/entities/user.entity.ts create mode 100644 src/users/users.controller.spec.ts create mode 100644 src/users/users.controller.ts create mode 100644 src/users/users.module.ts create mode 100644 src/users/users.service.spec.ts create mode 100644 src/users/users.service.ts diff --git a/package-lock.json b/package-lock.json index 94ce102..2cd9013 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,7 +17,8 @@ "bcrypt": "^5.1.1", "class-transformer": "^0.5.1", "class-validator": "^0.14.1", - "dotenv": "^16.4.5", + "dotenv": "^16.5.0", + "express": "^5.1.0", "http-status-codes": "^2.2.0", "reflect-metadata": "^0.2.1", "rimraf": "^5.0.5", @@ -29,10 +30,10 @@ "@nestjs/schematics": "^10.0.3", "@nestjs/testing": "^10.2.8", "@types/bcrypt": "^5.0.2", - "@types/express": "^4.17.21", + "@types/express": "^4.17.22", "@types/jest": "^29.5.12", "@types/jsonwebtoken": "^9.0.5", - "@types/node": "^20.11.24", + "@types/node": "^20.17.52", "@types/supertest": "^6.0.2", "@types/uuid": "^9.0.8", "@typescript-eslint/eslint-plugin": "^7.1.1", @@ -42,6 +43,7 @@ "eslint-config-prettier": "^9.1.0", "eslint-plugin-prettier": "^5.1.3", "jest": "^29.7.0", + "nodemon": "^3.1.10", "prettier": "^3.2.5", "source-map-support": "^0.5.21", "supertest": "^6.3.4", @@ -49,7 +51,7 @@ "ts-loader": "^9.5.1", "ts-node": "^10.9.2", "tsconfig-paths": "^4.2.0", - "typescript": "^5.3.3" + "typescript": "^5.8.3" }, "engines": { "node": ">=22.14.0" @@ -1850,6 +1852,203 @@ "@nestjs/core": "^10.0.0" } }, + "node_modules/@nestjs/platform-express/node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "license": "MIT", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@nestjs/platform-express/node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "license": "MIT", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@nestjs/platform-express/node_modules/cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", + "license": "MIT" + }, + "node_modules/@nestjs/platform-express/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/@nestjs/platform-express/node_modules/express": { + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", + "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", + "license": "MIT", + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "1.20.3", + "content-disposition": "0.5.4", + "content-type": "~1.0.4", + "cookie": "0.7.1", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.3.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "merge-descriptors": "1.0.3", + "methods": "~1.1.2", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.12", + "proxy-addr": "~2.0.7", + "qs": "6.13.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "0.19.0", + "serve-static": "1.16.2", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/@nestjs/platform-express/node_modules/finalhandler": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", + "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "statuses": "2.0.1", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/@nestjs/platform-express/node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@nestjs/platform-express/node_modules/merge-descriptors": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@nestjs/platform-express/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/@nestjs/platform-express/node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@nestjs/platform-express/node_modules/path-to-regexp": { + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", + "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", + "license": "MIT" + }, + "node_modules/@nestjs/platform-express/node_modules/send": { + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", + "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/@nestjs/platform-express/node_modules/send/node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/@nestjs/platform-express/node_modules/send/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/@nestjs/platform-express/node_modules/serve-static": { + "version": "1.16.2", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", + "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", + "license": "MIT", + "dependencies": { + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.19.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/@nestjs/platform-express/node_modules/tslib": { "version": "2.8.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", @@ -2162,9 +2361,9 @@ "dev": true }, "node_modules/@types/express": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.21.tgz", - "integrity": "sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ==", + "version": "4.17.22", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.22.tgz", + "integrity": "sha512-eZUmSnhRX9YRSkplpz0N+k6NljUUn5l3EWZIKZvYzhvMphEuNiyyy1viH/ejgt66JWgALwC/gtSUAeQKtSwW/w==", "dev": true, "license": "MIT", "dependencies": { @@ -2273,9 +2472,9 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "20.17.30", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.17.30.tgz", - "integrity": "sha512-7zf4YyHA+jvBNfVrk2Gtvs6x7E8V+YDW05bNfG2XkWDJfYRXrTiP/DsB2zSYTaHX0bGIujTBQdMVAhb+j7mwpg==", + "version": "20.17.52", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.17.52.tgz", + "integrity": "sha512-2aj++KfxubvW/Lc0YyXE3OEW7Es8TWn1MsRzYgcOGyTNQxi0L8rxQUCZ7ZbyOBWZQD5I63PV9egZWMsapVaklg==", "license": "MIT", "dependencies": { "undici-types": "~6.19.2" @@ -2768,12 +2967,34 @@ "license": "ISC" }, "node_modules/accepts": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", - "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", + "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", + "license": "MIT", "dependencies": { - "mime-types": "~2.1.34", - "negotiator": "0.6.3" + "mime-types": "^3.0.0", + "negotiator": "^1.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/accepts/node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/accepts/node_modules/mime-types": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz", + "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==", + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" }, "engines": { "node": ">= 0.6" @@ -2997,7 +3218,8 @@ "node_modules/array-flatten": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", - "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", + "license": "MIT" }, "node_modules/array-timsort": { "version": "1.0.3", @@ -3790,9 +4012,10 @@ "license": "ISC" }, "node_modules/content-disposition": { - "version": "0.5.4", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", - "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.0.tgz", + "integrity": "sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg==", + "license": "MIT", "dependencies": { "safe-buffer": "5.2.1" }, @@ -3824,9 +4047,13 @@ } }, "node_modules/cookie-signature": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", - "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", + "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", + "license": "MIT", + "engines": { + "node": ">=6.6.0" + } }, "node_modules/cookiejar": { "version": "2.1.4", @@ -4181,6 +4408,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", "engines": { "node": ">= 0.8" } @@ -4268,7 +4496,8 @@ "node_modules/escape-html": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" }, "node_modules/escape-string-regexp": { "version": "4.0.0", @@ -4567,6 +4796,7 @@ "version": "1.8.1", "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "license": "MIT", "engines": { "node": ">= 0.6" } @@ -4631,67 +4861,175 @@ } }, "node_modules/express": { - "version": "4.21.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", - "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/express/-/express-5.1.0.tgz", + "integrity": "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==", + "license": "MIT", "dependencies": { - "accepts": "~1.3.8", - "array-flatten": "1.1.1", - "body-parser": "1.20.3", - "content-disposition": "0.5.4", - "content-type": "~1.0.4", - "cookie": "0.7.1", - "cookie-signature": "1.0.6", - "debug": "2.6.9", - "depd": "2.0.0", - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "finalhandler": "1.3.1", - "fresh": "0.5.2", - "http-errors": "2.0.0", - "merge-descriptors": "1.0.3", - "methods": "~1.1.2", - "on-finished": "2.4.1", - "parseurl": "~1.3.3", - "path-to-regexp": "0.1.12", - "proxy-addr": "~2.0.7", - "qs": "6.13.0", - "range-parser": "~1.2.1", - "safe-buffer": "5.2.1", - "send": "0.19.0", - "serve-static": "1.16.2", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "type-is": "~1.6.18", - "utils-merge": "1.0.1", - "vary": "~1.1.2" + "accepts": "^2.0.0", + "body-parser": "^2.2.0", + "content-disposition": "^1.0.0", + "content-type": "^1.0.5", + "cookie": "^0.7.1", + "cookie-signature": "^1.2.1", + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "finalhandler": "^2.1.0", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "merge-descriptors": "^2.0.0", + "mime-types": "^3.0.0", + "on-finished": "^2.4.1", + "once": "^1.4.0", + "parseurl": "^1.3.3", + "proxy-addr": "^2.0.7", + "qs": "^6.14.0", + "range-parser": "^1.2.1", + "router": "^2.2.0", + "send": "^1.1.0", + "serve-static": "^2.2.0", + "statuses": "^2.0.1", + "type-is": "^2.0.1", + "vary": "^1.1.2" }, "engines": { - "node": ">= 0.10.0" + "node": ">= 18" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/express" } }, + "node_modules/express/node_modules/body-parser": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.0.tgz", + "integrity": "sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg==", + "license": "MIT", + "dependencies": { + "bytes": "^3.1.2", + "content-type": "^1.0.5", + "debug": "^4.4.0", + "http-errors": "^2.0.0", + "iconv-lite": "^0.6.3", + "on-finished": "^2.4.1", + "qs": "^6.14.0", + "raw-body": "^3.0.0", + "type-is": "^2.0.0" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/express/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "license": "MIT", "dependencies": { - "ms": "2.0.0" + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/express/node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/express/node_modules/media-typer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", + "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/express/node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/express/node_modules/mime-types": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz", + "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==", + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">= 0.6" } }, "node_modules/express/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" }, - "node_modules/express/node_modules/path-to-regexp": { - "version": "0.1.12", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", - "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==" + "node_modules/express/node_modules/qs": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz", + "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/express/node_modules/raw-body": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.0.tgz", + "integrity": "sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g==", + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.6.3", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/express/node_modules/type-is": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", + "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==", + "license": "MIT", + "dependencies": { + "content-type": "^1.0.5", + "media-typer": "^1.1.0", + "mime-types": "^3.0.0" + }, + "engines": { + "node": ">= 0.6" + } }, "node_modules/external-editor": { "version": "3.1.0", @@ -4856,34 +5194,44 @@ } }, "node_modules/finalhandler": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", - "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.0.tgz", + "integrity": "sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q==", + "license": "MIT", "dependencies": { - "debug": "2.6.9", - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "on-finished": "2.4.1", - "parseurl": "~1.3.3", - "statuses": "2.0.1", - "unpipe": "~1.0.0" + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "on-finished": "^2.4.1", + "parseurl": "^1.3.3", + "statuses": "^2.0.1" }, "engines": { "node": ">= 0.8" } }, "node_modules/finalhandler/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "license": "MIT", "dependencies": { - "ms": "2.0.0" + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, "node_modules/finalhandler/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" }, "node_modules/find-up": { "version": "5.0.0", @@ -5060,11 +5408,12 @@ } }, "node_modules/fresh": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", + "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", + "license": "MIT", "engines": { - "node": ">= 0.6" + "node": ">= 0.8" } }, "node_modules/fs-extra": { @@ -5496,6 +5845,13 @@ "node": ">= 4" } }, + "node_modules/ignore-by-default": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", + "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==", + "dev": true, + "license": "ISC" + }, "node_modules/import-fresh": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", @@ -5706,6 +6062,12 @@ "node": ">=8" } }, + "node_modules/is-promise": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", + "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", + "license": "MIT" + }, "node_modules/is-stream": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", @@ -6874,9 +7236,13 @@ } }, "node_modules/merge-descriptors": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", - "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", + "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", + "license": "MIT", + "engines": { + "node": ">=18" + }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } @@ -6923,6 +7289,7 @@ "version": "1.6.0", "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "license": "MIT", "bin": { "mime": "cli.js" }, @@ -7060,9 +7427,10 @@ "dev": true }, "node_modules/negotiator": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", - "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", + "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", + "license": "MIT", "engines": { "node": ">= 0.6" } @@ -7145,6 +7513,58 @@ "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==", "dev": true }, + "node_modules/nodemon": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.10.tgz", + "integrity": "sha512-WDjw3pJ0/0jMFmyNDp3gvY2YizjLmmOUQo6DEBY+JgdvW/yQ9mEeSw6H5ythl5Ny2ytb7f9C2nIbjSxMNzbJXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "chokidar": "^3.5.2", + "debug": "^4", + "ignore-by-default": "^1.0.1", + "minimatch": "^3.1.2", + "pstree.remy": "^1.1.8", + "semver": "^7.5.3", + "simple-update-notifier": "^2.0.0", + "supports-color": "^5.5.0", + "touch": "^3.1.0", + "undefsafe": "^2.0.5" + }, + "bin": { + "nodemon": "bin/nodemon.js" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/nodemon" + } + }, + "node_modules/nodemon/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/nodemon/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/nopt": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", @@ -7380,6 +7800,7 @@ "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "license": "MIT", "engines": { "node": ">= 0.8" } @@ -7663,6 +8084,13 @@ "node": ">= 0.10" } }, + "node_modules/pstree.remy": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", + "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==", + "dev": true, + "license": "MIT" + }, "node_modules/punycode": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", @@ -7737,6 +8165,7 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "license": "MIT", "engines": { "node": ">= 0.6" } @@ -7982,6 +8411,54 @@ "node": ">=16 || 14 >=14.17" } }, + "node_modules/router": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", + "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "depd": "^2.0.0", + "is-promise": "^4.0.0", + "parseurl": "^1.3.3", + "path-to-regexp": "^8.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/router/node_modules/debug": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/router/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/router/node_modules/path-to-regexp": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.2.0.tgz", + "integrity": "sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ==", + "license": "MIT", + "engines": { + "node": ">=16" + } + }, "node_modules/run-async": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz", @@ -8109,53 +8586,70 @@ } }, "node_modules/send": { - "version": "0.19.0", - "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", - "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/send/-/send-1.2.0.tgz", + "integrity": "sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==", + "license": "MIT", "dependencies": { - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "fresh": "0.5.2", - "http-errors": "2.0.0", - "mime": "1.6.0", - "ms": "2.1.3", - "on-finished": "2.4.1", - "range-parser": "~1.2.1", - "statuses": "2.0.1" + "debug": "^4.3.5", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "mime-types": "^3.0.1", + "ms": "^2.1.3", + "on-finished": "^2.4.1", + "range-parser": "^1.2.1", + "statuses": "^2.0.1" }, "engines": { - "node": ">= 0.8.0" + "node": ">= 18" } }, "node_modules/send/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "license": "MIT", "dependencies": { - "ms": "2.0.0" + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, - "node_modules/send/node_modules/debug/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + "node_modules/send/node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } }, - "node_modules/send/node_modules/encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "node_modules/send/node_modules/mime-types": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz", + "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==", + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, "engines": { - "node": ">= 0.8" + "node": ">= 0.6" } }, "node_modules/send/node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" }, "node_modules/serialize-javascript": { "version": "6.0.2", @@ -8167,17 +8661,18 @@ } }, "node_modules/serve-static": { - "version": "1.16.2", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", - "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.0.tgz", + "integrity": "sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ==", + "license": "MIT", "dependencies": { - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "parseurl": "~1.3.3", - "send": "0.19.0" + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "parseurl": "^1.3.3", + "send": "^1.2.0" }, "engines": { - "node": ">= 0.8.0" + "node": ">= 18" } }, "node_modules/set-blocking": { @@ -8300,6 +8795,19 @@ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==" }, + "node_modules/simple-update-notifier": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz", + "integrity": "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/sisteransi": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", @@ -8787,6 +9295,16 @@ "node": ">=0.6" } }, + "node_modules/touch": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.1.tgz", + "integrity": "sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==", + "dev": true, + "license": "ISC", + "bin": { + "nodetouch": "bin/nodetouch.js" + } + }, "node_modules/tree-kill": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", @@ -9069,6 +9587,13 @@ "node": ">=8" } }, + "node_modules/undefsafe": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", + "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==", + "dev": true, + "license": "MIT" + }, "node_modules/undici-types": { "version": "6.19.8", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", @@ -9140,6 +9665,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "license": "MIT", "engines": { "node": ">= 0.4.0" } @@ -10674,29 +11200,179 @@ "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.7.tgz", "integrity": "sha512-ugo316mmTYBl2g81zDFnZ7cfxlut3o+/EQdaP7J8QN2kY6lJ22hmQYCK5EHcJHbrW+dkCGSCPgbG8JtYj6qSrg==", "requires": { - "@types/node": "*" + "@types/node": "*" + } + } + } + }, + "@nestjs/mapped-types": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@nestjs/mapped-types/-/mapped-types-2.1.0.tgz", + "integrity": "sha512-W+n+rM69XsFdwORF11UqJahn4J3xi4g/ZEOlJNL6KoW5ygWSmBB2p0S2BZ4FQeS/NDH72e6xIcu35SfJnE8bXw==", + "requires": {} + }, + "@nestjs/platform-express": { + "version": "10.4.15", + "resolved": "https://registry.npmjs.org/@nestjs/platform-express/-/platform-express-10.4.15.tgz", + "integrity": "sha512-63ZZPkXHjoDyO7ahGOVcybZCRa7/Scp6mObQKjcX/fTEq1YJeU75ELvMsuQgc8U2opMGOBD7GVuc4DV0oeDHoA==", + "requires": { + "body-parser": "1.20.3", + "cors": "2.8.5", + "express": "4.21.2", + "multer": "1.4.4-lts.1", + "tslib": "2.8.1" + }, + "dependencies": { + "accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "requires": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + } + }, + "content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "requires": { + "safe-buffer": "5.2.1" + } + }, + "cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" + }, + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + } + }, + "express": { + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", + "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", + "requires": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "1.20.3", + "content-disposition": "0.5.4", + "content-type": "~1.0.4", + "cookie": "0.7.1", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.3.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "merge-descriptors": "1.0.3", + "methods": "~1.1.2", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.12", + "proxy-addr": "~2.0.7", + "qs": "6.13.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "0.19.0", + "serve-static": "1.16.2", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + } + }, + "finalhandler": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", + "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", + "requires": { + "debug": "2.6.9", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "statuses": "2.0.1", + "unpipe": "~1.0.0" + } + }, + "fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==" + }, + "merge-descriptors": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==" + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==" + }, + "path-to-regexp": { + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", + "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==" + }, + "send": { + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", + "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", + "requires": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" + }, + "dependencies": { + "encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==" + }, + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + } } - } - } - }, - "@nestjs/mapped-types": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@nestjs/mapped-types/-/mapped-types-2.1.0.tgz", - "integrity": "sha512-W+n+rM69XsFdwORF11UqJahn4J3xi4g/ZEOlJNL6KoW5ygWSmBB2p0S2BZ4FQeS/NDH72e6xIcu35SfJnE8bXw==", - "requires": {} - }, - "@nestjs/platform-express": { - "version": "10.4.15", - "resolved": "https://registry.npmjs.org/@nestjs/platform-express/-/platform-express-10.4.15.tgz", - "integrity": "sha512-63ZZPkXHjoDyO7ahGOVcybZCRa7/Scp6mObQKjcX/fTEq1YJeU75ELvMsuQgc8U2opMGOBD7GVuc4DV0oeDHoA==", - "requires": { - "body-parser": "1.20.3", - "cors": "2.8.5", - "express": "4.21.2", - "multer": "1.4.4-lts.1", - "tslib": "2.8.1" - }, - "dependencies": { + }, + "serve-static": { + "version": "1.16.2", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", + "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", + "requires": { + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.19.0" + } + }, "tslib": { "version": "2.8.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", @@ -10939,9 +11615,9 @@ "dev": true }, "@types/express": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.21.tgz", - "integrity": "sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ==", + "version": "4.17.22", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.22.tgz", + "integrity": "sha512-eZUmSnhRX9YRSkplpz0N+k6NljUUn5l3EWZIKZvYzhvMphEuNiyyy1viH/ejgt66JWgALwC/gtSUAeQKtSwW/w==", "dev": true, "requires": { "@types/body-parser": "*", @@ -11040,9 +11716,9 @@ "dev": true }, "@types/node": { - "version": "20.17.30", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.17.30.tgz", - "integrity": "sha512-7zf4YyHA+jvBNfVrk2Gtvs6x7E8V+YDW05bNfG2XkWDJfYRXrTiP/DsB2zSYTaHX0bGIujTBQdMVAhb+j7mwpg==", + "version": "20.17.52", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.17.52.tgz", + "integrity": "sha512-2aj++KfxubvW/Lc0YyXE3OEW7Es8TWn1MsRzYgcOGyTNQxi0L8rxQUCZ7ZbyOBWZQD5I63PV9egZWMsapVaklg==", "requires": { "undici-types": "~6.19.2" } @@ -11419,12 +12095,27 @@ "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==" }, "accepts": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", - "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", + "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", "requires": { - "mime-types": "~2.1.34", - "negotiator": "0.6.3" + "mime-types": "^3.0.0", + "negotiator": "^1.0.0" + }, + "dependencies": { + "mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==" + }, + "mime-types": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz", + "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==", + "requires": { + "mime-db": "^1.54.0" + } + } } }, "acorn": { @@ -12132,9 +12823,9 @@ "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==" }, "content-disposition": { - "version": "0.5.4", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", - "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.0.tgz", + "integrity": "sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg==", "requires": { "safe-buffer": "5.2.1" } @@ -12156,9 +12847,9 @@ "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==" }, "cookie-signature": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", - "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", + "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==" }, "cookiejar": { "version": "2.1.4", @@ -12710,60 +13401,122 @@ } }, "express": { - "version": "4.21.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", - "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", - "requires": { - "accepts": "~1.3.8", - "array-flatten": "1.1.1", - "body-parser": "1.20.3", - "content-disposition": "0.5.4", - "content-type": "~1.0.4", - "cookie": "0.7.1", - "cookie-signature": "1.0.6", - "debug": "2.6.9", - "depd": "2.0.0", - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "finalhandler": "1.3.1", - "fresh": "0.5.2", - "http-errors": "2.0.0", - "merge-descriptors": "1.0.3", - "methods": "~1.1.2", - "on-finished": "2.4.1", - "parseurl": "~1.3.3", - "path-to-regexp": "0.1.12", - "proxy-addr": "~2.0.7", - "qs": "6.13.0", - "range-parser": "~1.2.1", - "safe-buffer": "5.2.1", - "send": "0.19.0", - "serve-static": "1.16.2", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "type-is": "~1.6.18", - "utils-merge": "1.0.1", - "vary": "~1.1.2" - }, - "dependencies": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/express/-/express-5.1.0.tgz", + "integrity": "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==", + "requires": { + "accepts": "^2.0.0", + "body-parser": "^2.2.0", + "content-disposition": "^1.0.0", + "content-type": "^1.0.5", + "cookie": "^0.7.1", + "cookie-signature": "^1.2.1", + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "finalhandler": "^2.1.0", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "merge-descriptors": "^2.0.0", + "mime-types": "^3.0.0", + "on-finished": "^2.4.1", + "once": "^1.4.0", + "parseurl": "^1.3.3", + "proxy-addr": "^2.0.7", + "qs": "^6.14.0", + "range-parser": "^1.2.1", + "router": "^2.2.0", + "send": "^1.1.0", + "serve-static": "^2.2.0", + "statuses": "^2.0.1", + "type-is": "^2.0.1", + "vary": "^1.1.2" + }, + "dependencies": { + "body-parser": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.0.tgz", + "integrity": "sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg==", + "requires": { + "bytes": "^3.1.2", + "content-type": "^1.0.5", + "debug": "^4.4.0", + "http-errors": "^2.0.0", + "iconv-lite": "^0.6.3", + "on-finished": "^2.4.1", + "qs": "^6.14.0", + "raw-body": "^3.0.0", + "type-is": "^2.0.0" + } + }, "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", "requires": { - "ms": "2.0.0" + "ms": "^2.1.3" + } + }, + "iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "requires": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + } + }, + "media-typer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", + "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==" + }, + "mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==" + }, + "mime-types": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz", + "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==", + "requires": { + "mime-db": "^1.54.0" } }, "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" }, - "path-to-regexp": { - "version": "0.1.12", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", - "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==" + "qs": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz", + "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==", + "requires": { + "side-channel": "^1.1.0" + } + }, + "raw-body": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.0.tgz", + "integrity": "sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g==", + "requires": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.6.3", + "unpipe": "1.0.0" + } + }, + "type-is": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", + "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==", + "requires": { + "content-type": "^1.0.5", + "media-typer": "^1.1.0", + "mime-types": "^3.0.0" + } } } }, @@ -12903,31 +13656,30 @@ } }, "finalhandler": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", - "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.0.tgz", + "integrity": "sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q==", "requires": { - "debug": "2.6.9", - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "on-finished": "2.4.1", - "parseurl": "~1.3.3", - "statuses": "2.0.1", - "unpipe": "~1.0.0" + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "on-finished": "^2.4.1", + "parseurl": "^1.3.3", + "statuses": "^2.0.1" }, "dependencies": { "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", "requires": { - "ms": "2.0.0" + "ms": "^2.1.3" } }, "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" } } }, @@ -13048,9 +13800,9 @@ "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==" }, "fresh": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==" + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", + "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==" }, "fs-extra": { "version": "10.1.0", @@ -13339,6 +14091,12 @@ "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", "dev": true }, + "ignore-by-default": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", + "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==", + "dev": true + }, "import-fresh": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", @@ -13488,6 +14246,11 @@ "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", "dev": true }, + "is-promise": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", + "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==" + }, "is-stream": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", @@ -14346,9 +15109,9 @@ } }, "merge-descriptors": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", - "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==" + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", + "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==" }, "merge-stream": { "version": "2.0.0", @@ -14483,9 +15246,9 @@ "dev": true }, "negotiator": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", - "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==" + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", + "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==" }, "neo-async": { "version": "2.6.2", @@ -14554,6 +15317,41 @@ "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==", "dev": true }, + "nodemon": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.10.tgz", + "integrity": "sha512-WDjw3pJ0/0jMFmyNDp3gvY2YizjLmmOUQo6DEBY+JgdvW/yQ9mEeSw6H5ythl5Ny2ytb7f9C2nIbjSxMNzbJXw==", + "dev": true, + "requires": { + "chokidar": "^3.5.2", + "debug": "^4", + "ignore-by-default": "^1.0.1", + "minimatch": "^3.1.2", + "pstree.remy": "^1.1.8", + "semver": "^7.5.3", + "simple-update-notifier": "^2.0.0", + "supports-color": "^5.5.0", + "touch": "^3.1.0", + "undefsafe": "^2.0.5" + }, + "dependencies": { + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, "nopt": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", @@ -14905,6 +15703,12 @@ "ipaddr.js": "1.9.1" } }, + "pstree.remy": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", + "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==", + "dev": true + }, "punycode": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", @@ -15115,6 +15919,38 @@ } } }, + "router": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", + "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", + "requires": { + "debug": "^4.4.0", + "depd": "^2.0.0", + "is-promise": "^4.0.0", + "parseurl": "^1.3.3", + "path-to-regexp": "^8.0.0" + }, + "dependencies": { + "debug": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "requires": { + "ms": "^2.1.3" + } + }, + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "path-to-regexp": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.2.0.tgz", + "integrity": "sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ==" + } + } + }, "run-async": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz", @@ -15192,44 +16028,43 @@ "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==" }, "send": { - "version": "0.19.0", - "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", - "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", - "requires": { - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "fresh": "0.5.2", - "http-errors": "2.0.0", - "mime": "1.6.0", - "ms": "2.1.3", - "on-finished": "2.4.1", - "range-parser": "~1.2.1", - "statuses": "2.0.1" + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/send/-/send-1.2.0.tgz", + "integrity": "sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==", + "requires": { + "debug": "^4.3.5", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "mime-types": "^3.0.1", + "ms": "^2.1.3", + "on-finished": "^2.4.1", + "range-parser": "^1.2.1", + "statuses": "^2.0.1" }, "dependencies": { "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", "requires": { - "ms": "2.0.0" - }, - "dependencies": { - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" - } + "ms": "^2.1.3" } }, - "encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==" + "mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==" + }, + "mime-types": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz", + "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==", + "requires": { + "mime-db": "^1.54.0" + } }, "ms": { "version": "2.1.3", @@ -15248,14 +16083,14 @@ } }, "serve-static": { - "version": "1.16.2", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", - "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.0.tgz", + "integrity": "sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ==", "requires": { - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "parseurl": "~1.3.3", - "send": "0.19.0" + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "parseurl": "^1.3.3", + "send": "^1.2.0" } }, "set-blocking": { @@ -15344,6 +16179,15 @@ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==" }, + "simple-update-notifier": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz", + "integrity": "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==", + "dev": true, + "requires": { + "semver": "^7.5.3" + } + }, "sisteransi": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", @@ -15688,6 +16532,12 @@ "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==" }, + "touch": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.1.tgz", + "integrity": "sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==", + "dev": true + }, "tree-kill": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", @@ -15854,6 +16704,12 @@ "@lukeed/csprng": "^1.0.0" } }, + "undefsafe": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", + "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==", + "dev": true + }, "undici-types": { "version": "6.19.8", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", diff --git a/package.json b/package.json index a1b89e3..3e9b020 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,8 @@ "bcrypt": "^5.1.1", "class-transformer": "^0.5.1", "class-validator": "^0.14.1", - "dotenv": "^16.4.5", + "dotenv": "^16.5.0", + "express": "^5.1.0", "http-status-codes": "^2.2.0", "reflect-metadata": "^0.2.1", "rimraf": "^5.0.5", @@ -45,10 +46,10 @@ "@nestjs/schematics": "^10.0.3", "@nestjs/testing": "^10.2.8", "@types/bcrypt": "^5.0.2", - "@types/express": "^4.17.21", + "@types/express": "^4.17.22", "@types/jest": "^29.5.12", "@types/jsonwebtoken": "^9.0.5", - "@types/node": "^20.11.24", + "@types/node": "^20.17.52", "@types/supertest": "^6.0.2", "@types/uuid": "^9.0.8", "@typescript-eslint/eslint-plugin": "^7.1.1", @@ -58,6 +59,7 @@ "eslint-config-prettier": "^9.1.0", "eslint-plugin-prettier": "^5.1.3", "jest": "^29.7.0", + "nodemon": "^3.1.10", "prettier": "^3.2.5", "source-map-support": "^0.5.21", "supertest": "^6.3.4", @@ -65,6 +67,6 @@ "ts-loader": "^9.5.1", "ts-node": "^10.9.2", "tsconfig-paths": "^4.2.0", - "typescript": "^5.3.3" + "typescript": "^5.8.3" } } diff --git a/src/albums/albums.controller.spec.ts b/src/albums/albums.controller.spec.ts new file mode 100644 index 0000000..11c0438 --- /dev/null +++ b/src/albums/albums.controller.spec.ts @@ -0,0 +1,18 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { AlbumsController } from './albums.controller'; + +describe('AlbumsController', () => { + let controller: AlbumsController; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + controllers: [AlbumsController], + }).compile(); + + controller = module.get(AlbumsController); + }); + + it('should be defined', () => { + expect(controller).toBeDefined(); + }); +}); diff --git a/src/albums/albums.controller.ts b/src/albums/albums.controller.ts new file mode 100644 index 0000000..475f75e --- /dev/null +++ b/src/albums/albums.controller.ts @@ -0,0 +1,4 @@ +import { Controller } from '@nestjs/common'; + +@Controller('albums') +export class AlbumsController {} diff --git a/src/albums/albums.module.ts b/src/albums/albums.module.ts new file mode 100644 index 0000000..ceac6d5 --- /dev/null +++ b/src/albums/albums.module.ts @@ -0,0 +1,9 @@ +import { Module } from '@nestjs/common'; +import { AlbumsController } from './albums.controller'; +import { AlbumsService } from './albums.service'; + +@Module({ + controllers: [AlbumsController], + providers: [AlbumsService] +}) +export class AlbumsModule {} diff --git a/src/albums/albums.service.spec.ts b/src/albums/albums.service.spec.ts new file mode 100644 index 0000000..80691a5 --- /dev/null +++ b/src/albums/albums.service.spec.ts @@ -0,0 +1,18 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { AlbumsService } from './albums.service'; + +describe('AlbumsService', () => { + let service: AlbumsService; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + providers: [AlbumsService], + }).compile(); + + service = module.get(AlbumsService); + }); + + it('should be defined', () => { + expect(service).toBeDefined(); + }); +}); diff --git a/src/albums/albums.service.ts b/src/albums/albums.service.ts new file mode 100644 index 0000000..755f18b --- /dev/null +++ b/src/albums/albums.service.ts @@ -0,0 +1,4 @@ +import { Injectable } from '@nestjs/common'; + +@Injectable() +export class AlbumsService {} diff --git a/src/albums/dto/create-album.dto.ts b/src/albums/dto/create-album.dto.ts new file mode 100644 index 0000000..ba5b5bb --- /dev/null +++ b/src/albums/dto/create-album.dto.ts @@ -0,0 +1,16 @@ +import { IsInt, IsOptional, IsString, IsUUID } from "class-validator"; + +export class CreateAlbumDto { + @IsUUID('4') + id: string; // uuid v4 + + @IsString() + name: string; + + @IsInt() + year: number; + + @IsUUID('4') + @IsOptional() + artistId: string | null; // refers to Artist +} \ No newline at end of file diff --git a/src/albums/entities/album.entity.ts b/src/albums/entities/album.entity.ts new file mode 100644 index 0000000..01e3836 --- /dev/null +++ b/src/albums/entities/album.entity.ts @@ -0,0 +1,6 @@ +export class Album { + id: string; // uuid v4 + name: string; + year: number; + artistId: string | null; // refers to Artist +} \ No newline at end of file diff --git a/src/app.module.ts b/src/app.module.ts index 8662803..91dc376 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -1,9 +1,14 @@ import { Module } from '@nestjs/common'; import { AppController } from './app.controller'; import { AppService } from './app.service'; +import { UsersModule } from './users/users.module'; +import { ArtistsModule } from './artists/artists.module'; +import { AlbumsModule } from './albums/albums.module'; +import { TracksModule } from './tracks/tracks.module'; +import { FavoritesModule } from './favorites/favorites.module'; @Module({ - imports: [], + imports: [UsersModule, ArtistsModule, AlbumsModule, TracksModule, FavoritesModule], controllers: [AppController], providers: [AppService], }) diff --git a/src/artists/artists.controller.spec.ts b/src/artists/artists.controller.spec.ts new file mode 100644 index 0000000..1e0b0f7 --- /dev/null +++ b/src/artists/artists.controller.spec.ts @@ -0,0 +1,18 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { ArtistsController } from './artists.controller'; + +describe('ArtistsController', () => { + let controller: ArtistsController; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + controllers: [ArtistsController], + }).compile(); + + controller = module.get(ArtistsController); + }); + + it('should be defined', () => { + expect(controller).toBeDefined(); + }); +}); diff --git a/src/artists/artists.controller.ts b/src/artists/artists.controller.ts new file mode 100644 index 0000000..757277b --- /dev/null +++ b/src/artists/artists.controller.ts @@ -0,0 +1,4 @@ +import { Controller } from '@nestjs/common'; + +@Controller('artists') +export class ArtistsController {} diff --git a/src/artists/artists.module.ts b/src/artists/artists.module.ts new file mode 100644 index 0000000..b90e73e --- /dev/null +++ b/src/artists/artists.module.ts @@ -0,0 +1,9 @@ +import { Module } from '@nestjs/common'; +import { ArtistsController } from './artists.controller'; +import { ArtistsService } from './artists.service'; + +@Module({ + controllers: [ArtistsController], + providers: [ArtistsService] +}) +export class ArtistsModule {} diff --git a/src/artists/artists.service.spec.ts b/src/artists/artists.service.spec.ts new file mode 100644 index 0000000..51bf01f --- /dev/null +++ b/src/artists/artists.service.spec.ts @@ -0,0 +1,18 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { ArtistsService } from './artists.service'; + +describe('ArtistsService', () => { + let service: ArtistsService; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + providers: [ArtistsService], + }).compile(); + + service = module.get(ArtistsService); + }); + + it('should be defined', () => { + expect(service).toBeDefined(); + }); +}); diff --git a/src/artists/artists.service.ts b/src/artists/artists.service.ts new file mode 100644 index 0000000..feec628 --- /dev/null +++ b/src/artists/artists.service.ts @@ -0,0 +1,4 @@ +import { Injectable } from '@nestjs/common'; + +@Injectable() +export class ArtistsService {} diff --git a/src/artists/dto/create-artist.dto.ts b/src/artists/dto/create-artist.dto.ts new file mode 100644 index 0000000..960a3e9 --- /dev/null +++ b/src/artists/dto/create-artist.dto.ts @@ -0,0 +1,12 @@ +import { IsBoolean, IsString, IsUUID, MinLength } from "class-validator"; + +export class CreateArtistDto { + @IsUUID('4') + id: string; // uuid v4 + + @IsString() + name: string; + + @IsBoolean() + grammy: boolean; +} \ No newline at end of file diff --git a/src/artists/entities/artist.entity.ts b/src/artists/entities/artist.entity.ts new file mode 100644 index 0000000..24e472a --- /dev/null +++ b/src/artists/entities/artist.entity.ts @@ -0,0 +1,5 @@ +export class Artist { + id: string; // uuid v4 + name: string; + grammy: boolean; +} \ No newline at end of file diff --git a/src/favorites/dto/create-favorites.dto.ts b/src/favorites/dto/create-favorites.dto.ts new file mode 100644 index 0000000..4718cc6 --- /dev/null +++ b/src/favorites/dto/create-favorites.dto.ts @@ -0,0 +1,15 @@ +import { IsArray, IsString, IsUUID, MinLength } from "class-validator"; + +export class CreateFavoritesDto { + @IsArray() + @IsUUID('4', {each: true}) + artists: string[]; // favorite artists ids + + @IsArray() + @IsUUID('4', {each: true}) + albums: string[]; // favorite albums ids + + @IsArray() + @IsUUID('4', {each: true}) + tracks: string[]; +} \ No newline at end of file diff --git a/src/favorites/entities/favorites.entity.ts b/src/favorites/entities/favorites.entity.ts new file mode 100644 index 0000000..a5b02b5 --- /dev/null +++ b/src/favorites/entities/favorites.entity.ts @@ -0,0 +1,5 @@ +export class Favorites { + artists: string[]; // favorite artists ids + albums: string[]; // favorite albums ids + tracks: string[]; // favorite tracks ids +} \ No newline at end of file diff --git a/src/favorites/favorites.controller.spec.ts b/src/favorites/favorites.controller.spec.ts new file mode 100644 index 0000000..7602205 --- /dev/null +++ b/src/favorites/favorites.controller.spec.ts @@ -0,0 +1,18 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { FavoritesController } from './favorites.controller'; + +describe('FavoritesController', () => { + let controller: FavoritesController; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + controllers: [FavoritesController], + }).compile(); + + controller = module.get(FavoritesController); + }); + + it('should be defined', () => { + expect(controller).toBeDefined(); + }); +}); diff --git a/src/favorites/favorites.controller.ts b/src/favorites/favorites.controller.ts new file mode 100644 index 0000000..f07a193 --- /dev/null +++ b/src/favorites/favorites.controller.ts @@ -0,0 +1,4 @@ +import { Controller } from '@nestjs/common'; + +@Controller('favorites') +export class FavoritesController {} diff --git a/src/favorites/favorites.module.ts b/src/favorites/favorites.module.ts new file mode 100644 index 0000000..cb53027 --- /dev/null +++ b/src/favorites/favorites.module.ts @@ -0,0 +1,9 @@ +import { Module } from '@nestjs/common'; +import { FavoritesController } from './favorites.controller'; +import { FavoritesService } from './favorites.service'; + +@Module({ + controllers: [FavoritesController], + providers: [FavoritesService] +}) +export class FavoritesModule {} diff --git a/src/favorites/favorites.service.spec.ts b/src/favorites/favorites.service.spec.ts new file mode 100644 index 0000000..4813b08 --- /dev/null +++ b/src/favorites/favorites.service.spec.ts @@ -0,0 +1,18 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { FavoritesService } from './favorites.service'; + +describe('FavoritesService', () => { + let service: FavoritesService; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + providers: [FavoritesService], + }).compile(); + + service = module.get(FavoritesService); + }); + + it('should be defined', () => { + expect(service).toBeDefined(); + }); +}); diff --git a/src/favorites/favorites.service.ts b/src/favorites/favorites.service.ts new file mode 100644 index 0000000..9f9ee5a --- /dev/null +++ b/src/favorites/favorites.service.ts @@ -0,0 +1,4 @@ +import { Injectable } from '@nestjs/common'; + +@Injectable() +export class FavoritesService {} diff --git a/src/tracks/dto/create-track.dto.ts b/src/tracks/dto/create-track.dto.ts new file mode 100644 index 0000000..8b39a45 --- /dev/null +++ b/src/tracks/dto/create-track.dto.ts @@ -0,0 +1,20 @@ +import { IsInt, IsOptional, IsString, IsUUID, MinLength } from "class-validator"; + +export class CreateTrackDto { + @IsUUID('4') + id: string; // uuid v4 + + @IsString() + name: string; + + @IsUUID('4') + @IsOptional() + artistId: string | null; // refers to Artist + + @IsUUID('4') + @IsOptional() + albumId: string | null; // refers to Album + + @IsInt() + duration: number; // integer number +} \ No newline at end of file diff --git a/src/tracks/entities/track.entity.ts b/src/tracks/entities/track.entity.ts new file mode 100644 index 0000000..df06e03 --- /dev/null +++ b/src/tracks/entities/track.entity.ts @@ -0,0 +1,7 @@ +export class Track { + id: string; // uuid v4 + name: string; + artistId: string | null; // refers to Artist + albumId: string | null; // refers to Album + duration: number; // integer number +} \ No newline at end of file diff --git a/src/tracks/tracks.controller.spec.ts b/src/tracks/tracks.controller.spec.ts new file mode 100644 index 0000000..5cab501 --- /dev/null +++ b/src/tracks/tracks.controller.spec.ts @@ -0,0 +1,18 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { TracksController } from './tracks.controller'; + +describe('TracksController', () => { + let controller: TracksController; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + controllers: [TracksController], + }).compile(); + + controller = module.get(TracksController); + }); + + it('should be defined', () => { + expect(controller).toBeDefined(); + }); +}); diff --git a/src/tracks/tracks.controller.ts b/src/tracks/tracks.controller.ts new file mode 100644 index 0000000..743df99 --- /dev/null +++ b/src/tracks/tracks.controller.ts @@ -0,0 +1,4 @@ +import { Controller } from '@nestjs/common'; + +@Controller('tracks') +export class TracksController {} diff --git a/src/tracks/tracks.module.ts b/src/tracks/tracks.module.ts new file mode 100644 index 0000000..007565d --- /dev/null +++ b/src/tracks/tracks.module.ts @@ -0,0 +1,9 @@ +import { Module } from '@nestjs/common'; +import { TracksController } from './tracks.controller'; +import { TracksService } from './tracks.service'; + +@Module({ + controllers: [TracksController], + providers: [TracksService] +}) +export class TracksModule {} diff --git a/src/tracks/tracks.service.spec.ts b/src/tracks/tracks.service.spec.ts new file mode 100644 index 0000000..5c5c4eb --- /dev/null +++ b/src/tracks/tracks.service.spec.ts @@ -0,0 +1,18 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { TracksService } from './tracks.service'; + +describe('TracksService', () => { + let service: TracksService; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + providers: [TracksService], + }).compile(); + + service = module.get(TracksService); + }); + + it('should be defined', () => { + expect(service).toBeDefined(); + }); +}); diff --git a/src/tracks/tracks.service.ts b/src/tracks/tracks.service.ts new file mode 100644 index 0000000..193549e --- /dev/null +++ b/src/tracks/tracks.service.ts @@ -0,0 +1,4 @@ +import { Injectable } from '@nestjs/common'; + +@Injectable() +export class TracksService {} diff --git a/src/users/dto/create-user.dto.ts b/src/users/dto/create-user.dto.ts new file mode 100644 index 0000000..8452af0 --- /dev/null +++ b/src/users/dto/create-user.dto.ts @@ -0,0 +1,22 @@ +import { IsInt, IsString, IsUUID, Min, MinLength } from "class-validator"; + +export class CreateUserDto { + @IsUUID('4') + id: string; // uuid v4 + + @IsString() + login: string; + + @IsString() + password: string; + + @IsInt() + @Min(1) + version: number; // integer number, increments on update + + @IsInt() + createdAt: number; // timestamp of creation + + @IsInt() + updatedAt: number; // timestamp of last update +} \ No newline at end of file diff --git a/src/users/entities/user.entity.ts b/src/users/entities/user.entity.ts new file mode 100644 index 0000000..19cf92c --- /dev/null +++ b/src/users/entities/user.entity.ts @@ -0,0 +1,8 @@ +export class User { + id: string; // uuid v4 + login: string; + password: string; + version: number; // integer number, increments on update + createdAt: number; // timestamp of creation + updatedAt: number; // timestamp of last update +} \ No newline at end of file diff --git a/src/users/users.controller.spec.ts b/src/users/users.controller.spec.ts new file mode 100644 index 0000000..3e27c39 --- /dev/null +++ b/src/users/users.controller.spec.ts @@ -0,0 +1,18 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { UsersController } from './users.controller'; + +describe('UsersController', () => { + let controller: UsersController; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + controllers: [UsersController], + }).compile(); + + controller = module.get(UsersController); + }); + + it('should be defined', () => { + expect(controller).toBeDefined(); + }); +}); diff --git a/src/users/users.controller.ts b/src/users/users.controller.ts new file mode 100644 index 0000000..43b3842 --- /dev/null +++ b/src/users/users.controller.ts @@ -0,0 +1,4 @@ +import { Controller } from '@nestjs/common'; + +@Controller('users') +export class UsersController {} diff --git a/src/users/users.module.ts b/src/users/users.module.ts new file mode 100644 index 0000000..8d4de4b --- /dev/null +++ b/src/users/users.module.ts @@ -0,0 +1,9 @@ +import { Module } from '@nestjs/common'; +import { UsersController } from './users.controller'; +import { UsersService } from './users.service'; + +@Module({ + controllers: [UsersController], + providers: [UsersService] +}) +export class UsersModule {} diff --git a/src/users/users.service.spec.ts b/src/users/users.service.spec.ts new file mode 100644 index 0000000..62815ba --- /dev/null +++ b/src/users/users.service.spec.ts @@ -0,0 +1,18 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { UsersService } from './users.service'; + +describe('UsersService', () => { + let service: UsersService; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + providers: [UsersService], + }).compile(); + + service = module.get(UsersService); + }); + + it('should be defined', () => { + expect(service).toBeDefined(); + }); +}); diff --git a/src/users/users.service.ts b/src/users/users.service.ts new file mode 100644 index 0000000..ef0d82d --- /dev/null +++ b/src/users/users.service.ts @@ -0,0 +1,4 @@ +import { Injectable } from '@nestjs/common'; + +@Injectable() +export class UsersService {} From a0b04014e2a61982f2e11f8cc7a97d274391558a Mon Sep 17 00:00:00 2001 From: Yulia Demir Date: Thu, 29 May 2025 18:44:59 +0300 Subject: [PATCH 02/32] feat: add update dtos --- .env | 7 +++++++ src/albums/dto/update-album.dto.ts | 18 ++++++++++++++++++ src/artists/dto/update-artist.dto.ts | 14 ++++++++++++++ src/db.ts | 17 +++++++++++++++++ src/favorites/dto/update-favorites.dto.ts | 18 ++++++++++++++++++ src/tracks/dto/update-track.dto.ts | 22 ++++++++++++++++++++++ src/users/dto/return-user.dto.ts | 19 +++++++++++++++++++ src/users/dto/update-user.dto.ts | 14 ++++++++++++++ src/users/users.controller.ts | 6 +++++- src/users/users.service.ts | 6 +++++- 10 files changed, 139 insertions(+), 2 deletions(-) create mode 100644 .env create mode 100644 src/albums/dto/update-album.dto.ts create mode 100644 src/artists/dto/update-artist.dto.ts create mode 100644 src/db.ts create mode 100644 src/favorites/dto/update-favorites.dto.ts create mode 100644 src/tracks/dto/update-track.dto.ts create mode 100644 src/users/dto/return-user.dto.ts create mode 100644 src/users/dto/update-user.dto.ts diff --git a/.env b/.env new file mode 100644 index 0000000..8d594af --- /dev/null +++ b/.env @@ -0,0 +1,7 @@ +PORT=4000 + +CRYPT_SALT=10 +JWT_SECRET_KEY=secret123123 +JWT_SECRET_REFRESH_KEY=secret123123 +TOKEN_EXPIRE_TIME=1h +TOKEN_REFRESH_EXPIRE_TIME=24h diff --git a/src/albums/dto/update-album.dto.ts b/src/albums/dto/update-album.dto.ts new file mode 100644 index 0000000..a1ed51a --- /dev/null +++ b/src/albums/dto/update-album.dto.ts @@ -0,0 +1,18 @@ +import { IsInt, IsOptional, IsString, IsUUID } from "class-validator"; + +export class UpdateAlbumDto { + @IsUUID('4') + id: string; // uuid v4 + + @IsOptional() + @IsString() + name: string; + + @IsOptional() + @IsInt() + year: number; + + @IsUUID('4') + @IsOptional() + artistId: string | null; // refers to Artist +} \ No newline at end of file diff --git a/src/artists/dto/update-artist.dto.ts b/src/artists/dto/update-artist.dto.ts new file mode 100644 index 0000000..c924f62 --- /dev/null +++ b/src/artists/dto/update-artist.dto.ts @@ -0,0 +1,14 @@ +import { IsBoolean, IsOptional, IsString, IsUUID, MinLength } from "class-validator"; + +export class UpdateArtistDto { + @IsUUID('4') + id: string; // uuid v4 + + @IsOptional() + @IsString() + name: string; + + @IsOptional() + @IsBoolean() + grammy: boolean; +} \ No newline at end of file diff --git a/src/db.ts b/src/db.ts new file mode 100644 index 0000000..85b7004 --- /dev/null +++ b/src/db.ts @@ -0,0 +1,17 @@ +import { Album } from './albums/entities/album.entity'; +import { Artist } from './artists/entities/artist.entity'; +import { Favorites } from './favorites/entities/favorites.entity'; +import { Track } from './tracks/entities/track.entity'; +import { User } from './users/entities/user.entity'; + +export const db = { + users: [] as User[], + artists: [] as Artist[], + albums: [] as Album[], + tracks: [] as Track[], + favorites:{ + artists: [] as string[], + albums: [] as string[], + tracks: [] as string[], + } +} \ No newline at end of file diff --git a/src/favorites/dto/update-favorites.dto.ts b/src/favorites/dto/update-favorites.dto.ts new file mode 100644 index 0000000..38fb0f3 --- /dev/null +++ b/src/favorites/dto/update-favorites.dto.ts @@ -0,0 +1,18 @@ +import { IsArray, IsOptional, IsString, IsUUID, MinLength } from "class-validator"; + +export class UpdateFavoritesDto { + @IsOptional() + @IsArray() + @IsUUID('4', {each: true}) + artists?: string[]; // favorite artists ids + + @IsOptional() + @IsArray() + @IsUUID('4', {each: true}) + albums?: string[]; // favorite albums ids + + @IsOptional() + @IsArray() + @IsUUID('4', {each: true}) + tracks?: string[]; +} \ No newline at end of file diff --git a/src/tracks/dto/update-track.dto.ts b/src/tracks/dto/update-track.dto.ts new file mode 100644 index 0000000..cc894e7 --- /dev/null +++ b/src/tracks/dto/update-track.dto.ts @@ -0,0 +1,22 @@ +import { IsInt, IsOptional, IsString, IsUUID, MinLength } from "class-validator"; + +export class UpdateTrackDto { + @IsUUID('4') + id: string; // uuid v4 + + @IsOptional() + @IsString() + name: string; + + @IsUUID('4') + @IsOptional() + artistId: string | null; // refers to Artist + + @IsUUID('4') + @IsOptional() + albumId: string | null; // refers to Album + + @IsOptional() + @IsInt() + duration: number; // integer number +} \ No newline at end of file diff --git a/src/users/dto/return-user.dto.ts b/src/users/dto/return-user.dto.ts new file mode 100644 index 0000000..5521404 --- /dev/null +++ b/src/users/dto/return-user.dto.ts @@ -0,0 +1,19 @@ +import { IsInt, IsString, IsUUID, Min, MinLength } from "class-validator"; + +export class ReturnUserDto { + @IsUUID('4') + id: string; // uuid v4 + + @IsString() + login: string; + + @IsInt() + @Min(1) + version: number; // integer number, increments on update + + @IsInt() + createdAt: number; // timestamp of creation + + @IsInt() + updatedAt: number; // timestamp of last update +} \ No newline at end of file diff --git a/src/users/dto/update-user.dto.ts b/src/users/dto/update-user.dto.ts new file mode 100644 index 0000000..797c715 --- /dev/null +++ b/src/users/dto/update-user.dto.ts @@ -0,0 +1,14 @@ +import { IsInt, IsOptional, IsString, IsUUID, Min, MinLength } from "class-validator"; + +export class UpdateUserDto { + @IsUUID('4') + id: string; // uuid v4 + + @IsOptional() + @IsString() + login: string; + + @IsOptional() + @IsString() + password: string; +} \ No newline at end of file diff --git a/src/users/users.controller.ts b/src/users/users.controller.ts index 43b3842..f130272 100644 --- a/src/users/users.controller.ts +++ b/src/users/users.controller.ts @@ -1,4 +1,8 @@ import { Controller } from '@nestjs/common'; @Controller('users') -export class UsersController {} +export class UsersController { + + + +} diff --git a/src/users/users.service.ts b/src/users/users.service.ts index ef0d82d..73bba55 100644 --- a/src/users/users.service.ts +++ b/src/users/users.service.ts @@ -1,4 +1,8 @@ import { Injectable } from '@nestjs/common'; @Injectable() -export class UsersService {} +export class UsersService { + + + +} From a71f3ad914c6797100fe599ac1a2990198d2bfad Mon Sep 17 00:00:00 2001 From: Yulia Demir Date: Sat, 31 May 2025 07:45:09 +0300 Subject: [PATCH 03/32] feat: add user endpoints --- src/main.ts | 7 ++++- src/users/dto/create-user.dto.ts | 15 ++------- src/users/dto/update-user.dto.ts | 6 ++-- src/users/users.controller.ts | 47 +++++++++++++++++++++++++++-- src/users/users.module.ts | 1 + src/users/users.service.ts | 52 ++++++++++++++++++++++++++++++-- 6 files changed, 106 insertions(+), 22 deletions(-) diff --git a/src/main.ts b/src/main.ts index 8e348dc..74cf7b4 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,8 +1,13 @@ import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; +import { configDotenv } from 'dotenv'; +import { ValidationPipe } from '@nestjs/common'; + +configDotenv(); async function bootstrap() { const app = await NestFactory.create(AppModule); - await app.listen(4000); + app.useGlobalPipes(new ValidationPipe({ whitelist: true })); + await app.listen(process.env.PORT || 4000); } bootstrap(); diff --git a/src/users/dto/create-user.dto.ts b/src/users/dto/create-user.dto.ts index 8452af0..d9b0283 100644 --- a/src/users/dto/create-user.dto.ts +++ b/src/users/dto/create-user.dto.ts @@ -1,22 +1,11 @@ import { IsInt, IsString, IsUUID, Min, MinLength } from "class-validator"; export class CreateUserDto { - @IsUUID('4') - id: string; // uuid v4 - @IsString() + @MinLength(3) login: string; @IsString() + @MinLength(5) password: string; - - @IsInt() - @Min(1) - version: number; // integer number, increments on update - - @IsInt() - createdAt: number; // timestamp of creation - - @IsInt() - updatedAt: number; // timestamp of last update } \ No newline at end of file diff --git a/src/users/dto/update-user.dto.ts b/src/users/dto/update-user.dto.ts index 797c715..6543aa3 100644 --- a/src/users/dto/update-user.dto.ts +++ b/src/users/dto/update-user.dto.ts @@ -4,11 +4,9 @@ export class UpdateUserDto { @IsUUID('4') id: string; // uuid v4 - @IsOptional() @IsString() - login: string; + oldPassword: string; - @IsOptional() @IsString() - password: string; + newPassword: string; } \ No newline at end of file diff --git a/src/users/users.controller.ts b/src/users/users.controller.ts index f130272..770bbed 100644 --- a/src/users/users.controller.ts +++ b/src/users/users.controller.ts @@ -1,8 +1,51 @@ -import { Controller } from '@nestjs/common'; +import { Body, Controller, Get, HttpStatus, Param, ParseUUIDPipe, Post, Put, Res } from '@nestjs/common'; +import { UsersService } from './users.service'; +import { Response } from 'express'; +import { CreateUserDto } from './dto/create-user.dto'; +import { User } from './entities/user.entity'; +import { validate } from 'uuid'; +import { UpdateUserDto } from './dto/update-user.dto'; -@Controller('users') +@Controller('user') export class UsersController { + constructor(private readonly userService: UsersService) {} + @Get() + getAll(@Res() res: Response) { + const users = this.userService.findAll(); + return res.status(HttpStatus.OK).json(users); + } + @Get(':id') + getById( + @Param('id', new ParseUUIDPipe({ version: '4'})) id: string, + @Res() res: Response + ) { + if (!validate(id)) return res.status(400).json({ message: 'Invalid userId format' }); + const user = this.userService.findById(id); + if (!user) { + return res.status(HttpStatus.NOT_FOUND).json({ message: 'User not found' }); + } + return res.status(HttpStatus.OK).json(user); + } + + @Post() + create( + @Body() createUserDto: CreateUserDto, + @Res() res: Response + ) { + res.status(HttpStatus.CREATED).json(this.userService.create(createUserDto)); + } + + @Put(':id') + update( + @Param('id') id: string, + @Body() dto: UpdateUserDto, + @Res() res: Response + ): Omit { + const user = this.userService.update(id, dto) + res.status(HttpStatus.CREATED).json(user); + return user; + } } diff --git a/src/users/users.module.ts b/src/users/users.module.ts index 8d4de4b..03c0b2a 100644 --- a/src/users/users.module.ts +++ b/src/users/users.module.ts @@ -3,6 +3,7 @@ import { UsersController } from './users.controller'; import { UsersService } from './users.service'; @Module({ + imports: [UsersModule], controllers: [UsersController], providers: [UsersService] }) diff --git a/src/users/users.service.ts b/src/users/users.service.ts index 73bba55..f3e4bb8 100644 --- a/src/users/users.service.ts +++ b/src/users/users.service.ts @@ -1,8 +1,56 @@ -import { Injectable } from '@nestjs/common'; +import { Injectable, NotFoundException } from '@nestjs/common'; +import { db } from 'src/db'; +import { CreateUserDto } from './dto/create-user.dto'; +import { randomUUID } from 'crypto'; +import { User } from './entities/user.entity'; +import { UpdateUserDto } from './dto/update-user.dto'; +import { validate } from 'uuid'; @Injectable() export class UsersService { + create(createUserDto: CreateUserDto): Omit { + const newUser: User = { + id: randomUUID(), + login:createUserDto.login, + password: createUserDto.password, + version: 1, + createdAt: Date.now(), + updatedAt: Date.now(), + }; + db.users.push(newUser); + const { password, ...safeUser } = newUser; + return safeUser; + } + findAll(): Omit[] { + return db.users.map(({ password, ...rest }) => rest); + } - + findById(id: string): Omit { + const user = db.users.find(user => user.id === id); + if (!user) throw new NotFoundException('User Not found'); + const { password, ...safeUser } = user; + return safeUser; + } + + update(id: string, updateDto: UpdateUserDto) { + const user = db.users.find(user => user.id === id); + if (!user) throw new NotFoundException('User Not found'); + if (user.password !== updateDto.oldPassword) + throw new NotFoundException('User Not found'); + + user.password = updateDto.newPassword; + user.version++; + user.updatedAt = Date.now(); + + const { password, ...safeUser } = user; + return safeUser; + } + + remove(id: string) { + const index = db.users.findIndex(user => user.id === id); + if (index === -1) throw new NotFoundException('User not found'); + db.users.splice(index, 1); + return { message: 'User deleted' }; + } } From b52afe3260a5b712d109ab18fca6b9f4cd9e5d9f Mon Sep 17 00:00:00 2001 From: Yulia Demir Date: Sat, 31 May 2025 10:18:51 +0300 Subject: [PATCH 04/32] refactor: delete unnecessary files --- src/albums/albums.controller.spec.ts | 18 --------------- src/albums/albums.service.spec.ts | 18 --------------- src/artists/artists.controller.spec.ts | 18 --------------- src/artists/artists.service.spec.ts | 18 --------------- src/favorites/favorites.controller.spec.ts | 18 --------------- src/favorites/favorites.service.spec.ts | 18 --------------- src/tracks/tracks.controller.spec.ts | 18 --------------- src/tracks/tracks.service.spec.ts | 18 --------------- src/users/users.controller.spec.ts | 18 --------------- src/users/users.controller.ts | 27 ++++++++++++++++++---- src/users/users.service.spec.ts | 18 --------------- 11 files changed, 23 insertions(+), 184 deletions(-) delete mode 100644 src/albums/albums.controller.spec.ts delete mode 100644 src/albums/albums.service.spec.ts delete mode 100644 src/artists/artists.controller.spec.ts delete mode 100644 src/artists/artists.service.spec.ts delete mode 100644 src/favorites/favorites.controller.spec.ts delete mode 100644 src/favorites/favorites.service.spec.ts delete mode 100644 src/tracks/tracks.controller.spec.ts delete mode 100644 src/tracks/tracks.service.spec.ts delete mode 100644 src/users/users.controller.spec.ts delete mode 100644 src/users/users.service.spec.ts diff --git a/src/albums/albums.controller.spec.ts b/src/albums/albums.controller.spec.ts deleted file mode 100644 index 11c0438..0000000 --- a/src/albums/albums.controller.spec.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { Test, TestingModule } from '@nestjs/testing'; -import { AlbumsController } from './albums.controller'; - -describe('AlbumsController', () => { - let controller: AlbumsController; - - beforeEach(async () => { - const module: TestingModule = await Test.createTestingModule({ - controllers: [AlbumsController], - }).compile(); - - controller = module.get(AlbumsController); - }); - - it('should be defined', () => { - expect(controller).toBeDefined(); - }); -}); diff --git a/src/albums/albums.service.spec.ts b/src/albums/albums.service.spec.ts deleted file mode 100644 index 80691a5..0000000 --- a/src/albums/albums.service.spec.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { Test, TestingModule } from '@nestjs/testing'; -import { AlbumsService } from './albums.service'; - -describe('AlbumsService', () => { - let service: AlbumsService; - - beforeEach(async () => { - const module: TestingModule = await Test.createTestingModule({ - providers: [AlbumsService], - }).compile(); - - service = module.get(AlbumsService); - }); - - it('should be defined', () => { - expect(service).toBeDefined(); - }); -}); diff --git a/src/artists/artists.controller.spec.ts b/src/artists/artists.controller.spec.ts deleted file mode 100644 index 1e0b0f7..0000000 --- a/src/artists/artists.controller.spec.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { Test, TestingModule } from '@nestjs/testing'; -import { ArtistsController } from './artists.controller'; - -describe('ArtistsController', () => { - let controller: ArtistsController; - - beforeEach(async () => { - const module: TestingModule = await Test.createTestingModule({ - controllers: [ArtistsController], - }).compile(); - - controller = module.get(ArtistsController); - }); - - it('should be defined', () => { - expect(controller).toBeDefined(); - }); -}); diff --git a/src/artists/artists.service.spec.ts b/src/artists/artists.service.spec.ts deleted file mode 100644 index 51bf01f..0000000 --- a/src/artists/artists.service.spec.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { Test, TestingModule } from '@nestjs/testing'; -import { ArtistsService } from './artists.service'; - -describe('ArtistsService', () => { - let service: ArtistsService; - - beforeEach(async () => { - const module: TestingModule = await Test.createTestingModule({ - providers: [ArtistsService], - }).compile(); - - service = module.get(ArtistsService); - }); - - it('should be defined', () => { - expect(service).toBeDefined(); - }); -}); diff --git a/src/favorites/favorites.controller.spec.ts b/src/favorites/favorites.controller.spec.ts deleted file mode 100644 index 7602205..0000000 --- a/src/favorites/favorites.controller.spec.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { Test, TestingModule } from '@nestjs/testing'; -import { FavoritesController } from './favorites.controller'; - -describe('FavoritesController', () => { - let controller: FavoritesController; - - beforeEach(async () => { - const module: TestingModule = await Test.createTestingModule({ - controllers: [FavoritesController], - }).compile(); - - controller = module.get(FavoritesController); - }); - - it('should be defined', () => { - expect(controller).toBeDefined(); - }); -}); diff --git a/src/favorites/favorites.service.spec.ts b/src/favorites/favorites.service.spec.ts deleted file mode 100644 index 4813b08..0000000 --- a/src/favorites/favorites.service.spec.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { Test, TestingModule } from '@nestjs/testing'; -import { FavoritesService } from './favorites.service'; - -describe('FavoritesService', () => { - let service: FavoritesService; - - beforeEach(async () => { - const module: TestingModule = await Test.createTestingModule({ - providers: [FavoritesService], - }).compile(); - - service = module.get(FavoritesService); - }); - - it('should be defined', () => { - expect(service).toBeDefined(); - }); -}); diff --git a/src/tracks/tracks.controller.spec.ts b/src/tracks/tracks.controller.spec.ts deleted file mode 100644 index 5cab501..0000000 --- a/src/tracks/tracks.controller.spec.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { Test, TestingModule } from '@nestjs/testing'; -import { TracksController } from './tracks.controller'; - -describe('TracksController', () => { - let controller: TracksController; - - beforeEach(async () => { - const module: TestingModule = await Test.createTestingModule({ - controllers: [TracksController], - }).compile(); - - controller = module.get(TracksController); - }); - - it('should be defined', () => { - expect(controller).toBeDefined(); - }); -}); diff --git a/src/tracks/tracks.service.spec.ts b/src/tracks/tracks.service.spec.ts deleted file mode 100644 index 5c5c4eb..0000000 --- a/src/tracks/tracks.service.spec.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { Test, TestingModule } from '@nestjs/testing'; -import { TracksService } from './tracks.service'; - -describe('TracksService', () => { - let service: TracksService; - - beforeEach(async () => { - const module: TestingModule = await Test.createTestingModule({ - providers: [TracksService], - }).compile(); - - service = module.get(TracksService); - }); - - it('should be defined', () => { - expect(service).toBeDefined(); - }); -}); diff --git a/src/users/users.controller.spec.ts b/src/users/users.controller.spec.ts deleted file mode 100644 index 3e27c39..0000000 --- a/src/users/users.controller.spec.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { Test, TestingModule } from '@nestjs/testing'; -import { UsersController } from './users.controller'; - -describe('UsersController', () => { - let controller: UsersController; - - beforeEach(async () => { - const module: TestingModule = await Test.createTestingModule({ - controllers: [UsersController], - }).compile(); - - controller = module.get(UsersController); - }); - - it('should be defined', () => { - expect(controller).toBeDefined(); - }); -}); diff --git a/src/users/users.controller.ts b/src/users/users.controller.ts index 770bbed..113c375 100644 --- a/src/users/users.controller.ts +++ b/src/users/users.controller.ts @@ -1,10 +1,11 @@ -import { Body, Controller, Get, HttpStatus, Param, ParseUUIDPipe, Post, Put, Res } from '@nestjs/common'; +import { Body, Controller, Delete, Get, HttpCode, HttpStatus, NotFoundException, Param, ParseUUIDPipe, Post, Put, Res } from '@nestjs/common'; import { UsersService } from './users.service'; import { Response } from 'express'; import { CreateUserDto } from './dto/create-user.dto'; import { User } from './entities/user.entity'; import { validate } from 'uuid'; import { UpdateUserDto } from './dto/update-user.dto'; +import { NotFoundError } from 'rxjs'; @Controller('user') export class UsersController { @@ -18,7 +19,10 @@ export class UsersController { @Get(':id') getById( - @Param('id', new ParseUUIDPipe({ version: '4'})) id: string, + @Param('id', new ParseUUIDPipe({ + version: '4', + errorHttpStatusCode: 400, + })) id: string, @Res() res: Response ) { if (!validate(id)) return res.status(400).json({ message: 'Invalid userId format' }); @@ -39,13 +43,28 @@ export class UsersController { @Put(':id') update( - @Param('id') id: string, + @Param('id', new ParseUUIDPipe({ + version: '4', + })) id: string, @Body() dto: UpdateUserDto, @Res() res: Response ): Omit { - const user = this.userService.update(id, dto) + const user = this.userService.update(id, dto); + if (!user) { + res.status(HttpStatus.NOT_FOUND).json({ message: 'User does not found'}) + return; + } res.status(HttpStatus.CREATED).json(user); return user; } + @Delete(':id') + @HttpCode(204) + remove(@Param('id', new ParseUUIDPipe({ + version: '4', + errorHttpStatusCode: 400, + })) id: string,): void { + this.userService.remove(id); + } + } diff --git a/src/users/users.service.spec.ts b/src/users/users.service.spec.ts deleted file mode 100644 index 62815ba..0000000 --- a/src/users/users.service.spec.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { Test, TestingModule } from '@nestjs/testing'; -import { UsersService } from './users.service'; - -describe('UsersService', () => { - let service: UsersService; - - beforeEach(async () => { - const module: TestingModule = await Test.createTestingModule({ - providers: [UsersService], - }).compile(); - - service = module.get(UsersService); - }); - - it('should be defined', () => { - expect(service).toBeDefined(); - }); -}); From 1d89cdf15fc3653201f36d21813ae778d3a9ae28 Mon Sep 17 00:00:00 2001 From: Yulia Demir Date: Sat, 31 May 2025 11:59:23 +0300 Subject: [PATCH 05/32] feat: implement track endpoint --- src/main.ts | 5 ++- src/tracks/dto/create-track.dto.ts | 3 -- src/tracks/dto/update-track.dto.ts | 3 -- src/tracks/tracks.controller.ts | 55 ++++++++++++++++++++++++++++-- src/tracks/tracks.module.ts | 3 +- src/tracks/tracks.service.ts | 49 ++++++++++++++++++++++++-- 6 files changed, 105 insertions(+), 13 deletions(-) diff --git a/src/main.ts b/src/main.ts index 74cf7b4..437b101 100644 --- a/src/main.ts +++ b/src/main.ts @@ -7,7 +7,10 @@ configDotenv(); async function bootstrap() { const app = await NestFactory.create(AppModule); - app.useGlobalPipes(new ValidationPipe({ whitelist: true })); + app.useGlobalPipes(new ValidationPipe({ + whitelist: true, + transform: true, + })); await app.listen(process.env.PORT || 4000); } bootstrap(); diff --git a/src/tracks/dto/create-track.dto.ts b/src/tracks/dto/create-track.dto.ts index 8b39a45..ee61c6b 100644 --- a/src/tracks/dto/create-track.dto.ts +++ b/src/tracks/dto/create-track.dto.ts @@ -1,9 +1,6 @@ import { IsInt, IsOptional, IsString, IsUUID, MinLength } from "class-validator"; export class CreateTrackDto { - @IsUUID('4') - id: string; // uuid v4 - @IsString() name: string; diff --git a/src/tracks/dto/update-track.dto.ts b/src/tracks/dto/update-track.dto.ts index cc894e7..1e01b55 100644 --- a/src/tracks/dto/update-track.dto.ts +++ b/src/tracks/dto/update-track.dto.ts @@ -1,9 +1,6 @@ import { IsInt, IsOptional, IsString, IsUUID, MinLength } from "class-validator"; export class UpdateTrackDto { - @IsUUID('4') - id: string; // uuid v4 - @IsOptional() @IsString() name: string; diff --git a/src/tracks/tracks.controller.ts b/src/tracks/tracks.controller.ts index 743df99..b777da4 100644 --- a/src/tracks/tracks.controller.ts +++ b/src/tracks/tracks.controller.ts @@ -1,4 +1,53 @@ -import { Controller } from '@nestjs/common'; +import { Body, Controller, Delete, Get, HttpCode, HttpStatus, Param, ParseUUIDPipe, Post, Put, Res } from '@nestjs/common'; +import { Response } from 'express'; +import { TracksService } from './tracks.service'; +import { validate } from 'uuid'; +import { CreateTrackDto } from './dto/create-track.dto'; +import { UpdateTrackDto } from './dto/update-track.dto'; +import { Track } from './entities/track.entity'; -@Controller('tracks') -export class TracksController {} +@Controller('track') +export class TracksController { + constructor(private readonly tracksService: TracksService) {} + + @Get() + getAll() { + return this.tracksService.findAll(); + } + + @Get(':id') + getById( + @Param('id', new ParseUUIDPipe({ + version: '4', + errorHttpStatusCode: 400, + })) id: string, + ) { + return this.tracksService.findById(id); + } + + @Post() + create( + @Body() dto: CreateTrackDto, + ) { + return this.tracksService.create(dto); + } + + @Put(':id') + update( + @Param('id', new ParseUUIDPipe({ + version: '4', + errorHttpStatusCode: 400, + })) id: string, + @Body() dto: UpdateTrackDto, + ): Track { + return this.tracksService.update(id, dto); + } + + @Delete(':id') + @HttpCode(204) + remove(@Param('id', new ParseUUIDPipe({ + version: '4', + errorHttpStatusCode: 400, + })) id: string,): void { + this.tracksService.remove(id); + }} diff --git a/src/tracks/tracks.module.ts b/src/tracks/tracks.module.ts index 007565d..8c7c304 100644 --- a/src/tracks/tracks.module.ts +++ b/src/tracks/tracks.module.ts @@ -3,7 +3,8 @@ import { TracksController } from './tracks.controller'; import { TracksService } from './tracks.service'; @Module({ + imports: [TracksModule], controllers: [TracksController], providers: [TracksService] }) -export class TracksModule {} +export class TracksModule {} \ No newline at end of file diff --git a/src/tracks/tracks.service.ts b/src/tracks/tracks.service.ts index 193549e..39bbb24 100644 --- a/src/tracks/tracks.service.ts +++ b/src/tracks/tracks.service.ts @@ -1,4 +1,49 @@ -import { Injectable } from '@nestjs/common'; +import { Injectable, NotFoundException } from '@nestjs/common'; +import { CreateTrackDto } from './dto/create-track.dto'; +import { Track } from './entities/track.entity'; +import { randomUUID } from 'crypto'; +import { db } from 'src/db'; +import { UpdateTrackDto } from './dto/update-track.dto'; @Injectable() -export class TracksService {} +export class TracksService { + create(createTrackDto: CreateTrackDto): Track { + const newTrack: Track = { + id: randomUUID(), + name: createTrackDto.name, + artistId: createTrackDto.artistId, + albumId: createTrackDto.albumId, + duration: createTrackDto.duration + }; + db.tracks.push(newTrack); + return newTrack; + } + + findAll(): Track[] { + return db.tracks; + } + + findById(id: string): Track { + const track = db.tracks.find(track => track.id === id); + if (!track) throw new NotFoundException('track Not found'); + return track; + } + + update(id: string, updateDto: UpdateTrackDto) { + const track = db.tracks.find(track => track.id === id); + if (!track) throw new NotFoundException('track Not found'); + track.name = updateDto.name; + track.artistId = updateDto.artistId; + track.albumId = updateDto.albumId; + track.duration = updateDto.duration; + return track; + } + + remove(id: string) { + const index = db.tracks.findIndex(track => track.id === id); + if (index === -1) throw new NotFoundException('track not found'); + db.tracks.splice(index, 1); + return { message: 'track deleted' }; + } + +} From 3ecccc3f789a3e9ec309fed5c7e587f68d95da27 Mon Sep 17 00:00:00 2001 From: Yulia Demir Date: Sat, 31 May 2025 11:59:42 +0300 Subject: [PATCH 06/32] fix: user put --- src/users/dto/update-user.dto.ts | 3 --- src/users/users.controller.ts | 9 +-------- src/users/users.service.ts | 4 ++-- 3 files changed, 3 insertions(+), 13 deletions(-) diff --git a/src/users/dto/update-user.dto.ts b/src/users/dto/update-user.dto.ts index 6543aa3..aeecaa1 100644 --- a/src/users/dto/update-user.dto.ts +++ b/src/users/dto/update-user.dto.ts @@ -1,9 +1,6 @@ import { IsInt, IsOptional, IsString, IsUUID, Min, MinLength } from "class-validator"; export class UpdateUserDto { - @IsUUID('4') - id: string; // uuid v4 - @IsString() oldPassword: string; diff --git a/src/users/users.controller.ts b/src/users/users.controller.ts index 113c375..e7060d3 100644 --- a/src/users/users.controller.ts +++ b/src/users/users.controller.ts @@ -47,15 +47,8 @@ export class UsersController { version: '4', })) id: string, @Body() dto: UpdateUserDto, - @Res() res: Response ): Omit { - const user = this.userService.update(id, dto); - if (!user) { - res.status(HttpStatus.NOT_FOUND).json({ message: 'User does not found'}) - return; - } - res.status(HttpStatus.CREATED).json(user); - return user; + return this.userService.update(id, dto); } @Delete(':id') diff --git a/src/users/users.service.ts b/src/users/users.service.ts index f3e4bb8..e66d332 100644 --- a/src/users/users.service.ts +++ b/src/users/users.service.ts @@ -1,4 +1,4 @@ -import { Injectable, NotFoundException } from '@nestjs/common'; +import { ForbiddenException, Injectable, NotFoundException } from '@nestjs/common'; import { db } from 'src/db'; import { CreateUserDto } from './dto/create-user.dto'; import { randomUUID } from 'crypto'; @@ -37,7 +37,7 @@ export class UsersService { const user = db.users.find(user => user.id === id); if (!user) throw new NotFoundException('User Not found'); if (user.password !== updateDto.oldPassword) - throw new NotFoundException('User Not found'); + throw new ForbiddenException('The password is wrong!'); user.password = updateDto.newPassword; user.version++; From 48e3ec6e4fde1015cc9f37ebcc322a7bdeda9d07 Mon Sep 17 00:00:00 2001 From: Yulia Demir Date: Sat, 31 May 2025 14:33:15 +0300 Subject: [PATCH 07/32] feat: implement artist endpoint --- src/artists/artists.controller.ts | 53 +++++++++++++++++++++++-- src/artists/artists.module.ts | 1 + src/artists/artists.service.ts | 58 +++++++++++++++++++++++++++- src/artists/dto/create-artist.dto.ts | 3 -- src/artists/dto/update-artist.dto.ts | 3 -- 5 files changed, 107 insertions(+), 11 deletions(-) diff --git a/src/artists/artists.controller.ts b/src/artists/artists.controller.ts index 757277b..f82e107 100644 --- a/src/artists/artists.controller.ts +++ b/src/artists/artists.controller.ts @@ -1,4 +1,51 @@ -import { Controller } from '@nestjs/common'; +import { Body, Controller, Delete, Get, HttpCode, Param, ParseUUIDPipe, Post, Put } from '@nestjs/common'; +import { ArtistsService } from './artists.service'; +import { CreateArtistDto } from './dto/create-artist.dto'; +import { UpdateArtistDto } from './dto/update-artist.dto'; +import { Artist } from './entities/artist.entity'; -@Controller('artists') -export class ArtistsController {} +@Controller('artist') +export class ArtistsController {constructor(private readonly artistsService: ArtistsService) {} + + @Get() + getAll() { + return this.artistsService.findAll(); + } + + @Get(':id') + getById( + @Param('id', new ParseUUIDPipe({ + version: '4', + errorHttpStatusCode: 400, + })) id: string, + ) { + return this.artistsService.findById(id); + } + + @Post() + create( + @Body() dto: CreateArtistDto, + ) { + return this.artistsService.create(dto); + } + + @Put(':id') + update( + @Param('id', new ParseUUIDPipe({ + version: '4', + errorHttpStatusCode: 400, + })) id: string, + @Body() dto: UpdateArtistDto, + ): Artist { + return this.artistsService.update(id, dto); + } + + @Delete(':id') + @HttpCode(204) + remove(@Param('id', new ParseUUIDPipe({ + version: '4', + errorHttpStatusCode: 400, + })) id: string,): void { + this.artistsService.remove(id); + } +} diff --git a/src/artists/artists.module.ts b/src/artists/artists.module.ts index b90e73e..7183619 100644 --- a/src/artists/artists.module.ts +++ b/src/artists/artists.module.ts @@ -3,6 +3,7 @@ import { ArtistsController } from './artists.controller'; import { ArtistsService } from './artists.service'; @Module({ + imports: [ArtistsModule], controllers: [ArtistsController], providers: [ArtistsService] }) diff --git a/src/artists/artists.service.ts b/src/artists/artists.service.ts index feec628..854f95f 100644 --- a/src/artists/artists.service.ts +++ b/src/artists/artists.service.ts @@ -1,4 +1,58 @@ -import { Injectable } from '@nestjs/common'; +import { Injectable, NotFoundException } from '@nestjs/common'; +import { CreateArtistDto } from './dto/create-artist.dto'; +import { Artist } from './entities/artist.entity'; +import { randomUUID } from 'crypto'; +import { db } from 'src/db'; +import { UpdateArtistDto } from './dto/update-artist.dto'; @Injectable() -export class ArtistsService {} +export class ArtistsService { + create(dto: CreateArtistDto): Artist { + const newArtist: Artist = { + id: randomUUID(), + name: dto.name, + grammy: dto.grammy, + }; + db.artists.push(newArtist); + return newArtist; + } + + findAll(): Artist[] { + return db.artists; + } + + findById(id: string): Artist { + const artist = db.artists.find(artist => artist.id === id); + if (!artist) throw new NotFoundException('artist Not found'); + return artist; + } + + update(id: string, dto: UpdateArtistDto) { + const artist = db.artists.find(artist => artist.id === id); + if (!artist) throw new NotFoundException('artist Not found'); + artist.name = dto.name; + artist.grammy = dto.grammy; + return artist; + } + + remove(id: string) { + const index = db.artists.findIndex(artist => artist.id === id); + if (index === -1) throw new NotFoundException('artist not found'); + + db.tracks.forEach(track => { + if (track.artistId === id) { + track.artistId = null; + } + }); + + db.albums.forEach(album => { + if (album.artistId === id) { + album.artistId = null; + } + }); + + db.artists.splice(index, 1); + return { message: 'artist deleted' }; + } + +} diff --git a/src/artists/dto/create-artist.dto.ts b/src/artists/dto/create-artist.dto.ts index 960a3e9..821dbd0 100644 --- a/src/artists/dto/create-artist.dto.ts +++ b/src/artists/dto/create-artist.dto.ts @@ -1,9 +1,6 @@ import { IsBoolean, IsString, IsUUID, MinLength } from "class-validator"; export class CreateArtistDto { - @IsUUID('4') - id: string; // uuid v4 - @IsString() name: string; diff --git a/src/artists/dto/update-artist.dto.ts b/src/artists/dto/update-artist.dto.ts index c924f62..2a271f7 100644 --- a/src/artists/dto/update-artist.dto.ts +++ b/src/artists/dto/update-artist.dto.ts @@ -1,9 +1,6 @@ import { IsBoolean, IsOptional, IsString, IsUUID, MinLength } from "class-validator"; export class UpdateArtistDto { - @IsUUID('4') - id: string; // uuid v4 - @IsOptional() @IsString() name: string; From 1a0526eb2c84eb5fa8780cd6f4a4d55f5a39309c Mon Sep 17 00:00:00 2001 From: Yulia Demir Date: Sat, 31 May 2025 15:15:14 +0300 Subject: [PATCH 08/32] feat: implement album endpoint --- src/albums/albums.controller.ts | 54 ++++++++++++++++++++++++++++-- src/albums/albums.module.ts | 1 + src/albums/albums.service.ts | 53 +++++++++++++++++++++++++++-- src/albums/dto/create-album.dto.ts | 5 ++- src/albums/dto/update-album.dto.ts | 3 -- src/artists/artists.controller.ts | 3 +- 6 files changed, 107 insertions(+), 12 deletions(-) diff --git a/src/albums/albums.controller.ts b/src/albums/albums.controller.ts index 475f75e..c9ee1ea 100644 --- a/src/albums/albums.controller.ts +++ b/src/albums/albums.controller.ts @@ -1,4 +1,52 @@ -import { Controller } from '@nestjs/common'; +import { Body, Controller, Delete, Get, HttpCode, Param, ParseUUIDPipe, Post, Put } from '@nestjs/common'; +import { CreateAlbumDto } from './dto/create-album.dto'; +import { UpdateAlbumDto } from './dto/update-album.dto'; +import { Album } from './entities/album.entity'; +import { AlbumsService } from './albums.service'; -@Controller('albums') -export class AlbumsController {} +@Controller('album') +export class AlbumsController { + constructor(private readonly albumsService: AlbumsService) {} + + @Get() + getAll() { + return this.albumsService.findAll(); + } + + @Get(':id') + getById( + @Param('id', new ParseUUIDPipe({ + version: '4', + errorHttpStatusCode: 400, + })) id: string, + ) { + return this.albumsService.findById(id); + } + + @Post() + create( + @Body() dto: CreateAlbumDto, + ) { + return this.albumsService.create(dto); + } + + @Put(':id') + update( + @Param('id', new ParseUUIDPipe({ + version: '4', + errorHttpStatusCode: 400, + })) id: string, + @Body() dto: UpdateAlbumDto, + ): Album { + return this.albumsService.update(id, dto); + } + + @Delete(':id') + @HttpCode(204) + remove(@Param('id', new ParseUUIDPipe({ + version: '4', + errorHttpStatusCode: 400, + })) id: string,): void { + this.albumsService.remove(id); + } +} diff --git a/src/albums/albums.module.ts b/src/albums/albums.module.ts index ceac6d5..2078997 100644 --- a/src/albums/albums.module.ts +++ b/src/albums/albums.module.ts @@ -3,6 +3,7 @@ import { AlbumsController } from './albums.controller'; import { AlbumsService } from './albums.service'; @Module({ + imports: [AlbumsModule], controllers: [AlbumsController], providers: [AlbumsService] }) diff --git a/src/albums/albums.service.ts b/src/albums/albums.service.ts index 755f18b..388864b 100644 --- a/src/albums/albums.service.ts +++ b/src/albums/albums.service.ts @@ -1,4 +1,53 @@ -import { Injectable } from '@nestjs/common'; +import { Injectable, NotFoundException } from '@nestjs/common'; +import { randomUUID } from 'crypto'; +import { UpdateAlbumDto } from 'src/albums/dto/update-album.dto'; +import { db } from 'src/db'; +import { CreateAlbumDto } from './dto/create-album.dto'; +import { Album } from './entities/album.entity'; @Injectable() -export class AlbumsService {} +export class AlbumsService { + create(dto: CreateAlbumDto): Album { + const newAlbum: Album = { + id: randomUUID(), + name: dto.name, + year: dto.year, + artistId: dto.artistId, + }; + db.albums.push(newAlbum); + return newAlbum; + } + + findAll(): Album[] { + return db.albums; + } + + findById(id: string): Album { + const album = db.albums.find(album => album.id === id); + if (!album) throw new NotFoundException('album Not found'); + return album; + } + + update(id: string, dto: UpdateAlbumDto) { + const album = db.albums.find(album => album.id === id); + if (!album) throw new NotFoundException('album Not found'); + album.name = dto.name; + album.year = dto.year; + album.artistId = dto.artistId; + return album; + } + + remove(id: string) { + const index = db.albums.findIndex(album => album.id === id); + if (index === -1) throw new NotFoundException('album not found'); + + db.tracks.forEach(track => { + if (track.albumId === id) { + track.albumId = null; + } + }); + + db.albums.splice(index, 1); + return { message: 'album deleted' }; + } + } diff --git a/src/albums/dto/create-album.dto.ts b/src/albums/dto/create-album.dto.ts index ba5b5bb..f0524a7 100644 --- a/src/albums/dto/create-album.dto.ts +++ b/src/albums/dto/create-album.dto.ts @@ -1,13 +1,12 @@ +import { Type } from "class-transformer"; import { IsInt, IsOptional, IsString, IsUUID } from "class-validator"; export class CreateAlbumDto { - @IsUUID('4') - id: string; // uuid v4 - @IsString() name: string; @IsInt() + @Type(() => Number) year: number; @IsUUID('4') diff --git a/src/albums/dto/update-album.dto.ts b/src/albums/dto/update-album.dto.ts index a1ed51a..4a8dfac 100644 --- a/src/albums/dto/update-album.dto.ts +++ b/src/albums/dto/update-album.dto.ts @@ -1,9 +1,6 @@ import { IsInt, IsOptional, IsString, IsUUID } from "class-validator"; export class UpdateAlbumDto { - @IsUUID('4') - id: string; // uuid v4 - @IsOptional() @IsString() name: string; diff --git a/src/artists/artists.controller.ts b/src/artists/artists.controller.ts index f82e107..0256b33 100644 --- a/src/artists/artists.controller.ts +++ b/src/artists/artists.controller.ts @@ -5,7 +5,8 @@ import { UpdateArtistDto } from './dto/update-artist.dto'; import { Artist } from './entities/artist.entity'; @Controller('artist') -export class ArtistsController {constructor(private readonly artistsService: ArtistsService) {} +export class ArtistsController { + constructor(private readonly artistsService: ArtistsService) {} @Get() getAll() { From db568a4036e566b80e23c1166bc1674824bc0567 Mon Sep 17 00:00:00 2001 From: Yulia Demir Date: Sat, 31 May 2025 18:39:16 +0300 Subject: [PATCH 09/32] fix: lint errors --- src/albums/albums.controller.ts | 104 +++++++++++------- src/albums/albums.module.ts | 2 +- src/albums/albums.service.ts | 84 +++++++-------- src/albums/dto/create-album.dto.ts | 22 ++-- src/albums/dto/update-album.dto.ts | 22 ++-- src/albums/entities/album.entity.ts | 2 +- src/app.module.ts | 8 +- src/artists/artists.controller.ts | 102 +++++++++++------- src/artists/artists.module.ts | 2 +- src/artists/artists.service.ts | 95 ++++++++--------- src/artists/dto/create-artist.dto.ts | 12 +-- src/artists/dto/update-artist.dto.ts | 16 +-- src/artists/entities/artist.entity.ts | 2 +- src/db.ts | 21 ++-- src/favorites/dto/create-favorites.dto.ts | 24 ++--- src/favorites/dto/update-favorites.dto.ts | 30 +++--- src/favorites/entities/favorites.entity.ts | 2 +- src/favorites/favorites.module.ts | 2 +- src/main.ts | 10 +- src/tracks/dto/create-track.dto.ts | 24 ++--- src/tracks/dto/update-track.dto.ts | 28 ++--- src/tracks/entities/track.entity.ts | 2 +- src/tracks/tracks.controller.ts | 105 ++++++++++-------- src/tracks/tracks.module.ts | 4 +- src/tracks/tracks.service.ts | 69 ++++++------ src/users/dto/create-user.dto.ts | 16 +-- src/users/dto/return-user.dto.ts | 30 +++--- src/users/dto/update-user.dto.ts | 12 +-- src/users/entities/user.entity.ts | 2 +- src/users/users.controller.ts | 118 +++++++++++++-------- src/users/users.module.ts | 2 +- src/users/users.service.ts | 91 +++++++++------- 32 files changed, 584 insertions(+), 481 deletions(-) diff --git a/src/albums/albums.controller.ts b/src/albums/albums.controller.ts index c9ee1ea..f20d098 100644 --- a/src/albums/albums.controller.ts +++ b/src/albums/albums.controller.ts @@ -1,4 +1,14 @@ -import { Body, Controller, Delete, Get, HttpCode, Param, ParseUUIDPipe, Post, Put } from '@nestjs/common'; +import { + Body, + Controller, + Delete, + Get, + HttpCode, + Param, + ParseUUIDPipe, + Post, + Put, +} from '@nestjs/common'; import { CreateAlbumDto } from './dto/create-album.dto'; import { UpdateAlbumDto } from './dto/update-album.dto'; import { Album } from './entities/album.entity'; @@ -6,47 +16,59 @@ import { AlbumsService } from './albums.service'; @Controller('album') export class AlbumsController { - constructor(private readonly albumsService: AlbumsService) {} - - @Get() - getAll() { - return this.albumsService.findAll(); - } + constructor(private readonly albumsService: AlbumsService) {} - @Get(':id') - getById( - @Param('id', new ParseUUIDPipe({ - version: '4', - errorHttpStatusCode: 400, - })) id: string, - ) { - return this.albumsService.findById(id); - } + @Get() + getAll() { + return this.albumsService.findAll(); + } - @Post() - create( - @Body() dto: CreateAlbumDto, - ) { - return this.albumsService.create(dto); - } + @Get(':id') + getById( + @Param( + 'id', + new ParseUUIDPipe({ + version: '4', + errorHttpStatusCode: 400, + }), + ) + id: string, + ) { + return this.albumsService.findById(id); + } - @Put(':id') - update( - @Param('id', new ParseUUIDPipe({ - version: '4', - errorHttpStatusCode: 400, - })) id: string, - @Body() dto: UpdateAlbumDto, - ): Album { - return this.albumsService.update(id, dto); - } - - @Delete(':id') - @HttpCode(204) - remove(@Param('id', new ParseUUIDPipe({ - version: '4', - errorHttpStatusCode: 400, - })) id: string,): void { - this.albumsService.remove(id); - } + @Post() + create(@Body() dto: CreateAlbumDto) { + return this.albumsService.create(dto); + } + + @Put(':id') + update( + @Param( + 'id', + new ParseUUIDPipe({ + version: '4', + errorHttpStatusCode: 400, + }), + ) + id: string, + @Body() dto: UpdateAlbumDto, + ): Album { + return this.albumsService.update(id, dto); + } + + @Delete(':id') + @HttpCode(204) + remove( + @Param( + 'id', + new ParseUUIDPipe({ + version: '4', + errorHttpStatusCode: 400, + }), + ) + id: string, + ): void { + this.albumsService.remove(id); + } } diff --git a/src/albums/albums.module.ts b/src/albums/albums.module.ts index 2078997..e88fded 100644 --- a/src/albums/albums.module.ts +++ b/src/albums/albums.module.ts @@ -5,6 +5,6 @@ import { AlbumsService } from './albums.service'; @Module({ imports: [AlbumsModule], controllers: [AlbumsController], - providers: [AlbumsService] + providers: [AlbumsService], }) export class AlbumsModule {} diff --git a/src/albums/albums.service.ts b/src/albums/albums.service.ts index 388864b..65db12a 100644 --- a/src/albums/albums.service.ts +++ b/src/albums/albums.service.ts @@ -7,47 +7,47 @@ import { Album } from './entities/album.entity'; @Injectable() export class AlbumsService { - create(dto: CreateAlbumDto): Album { - const newAlbum: Album = { - id: randomUUID(), - name: dto.name, - year: dto.year, - artistId: dto.artistId, - }; - db.albums.push(newAlbum); - return newAlbum; - } - - findAll(): Album[] { - return db.albums; - } - - findById(id: string): Album { - const album = db.albums.find(album => album.id === id); - if (!album) throw new NotFoundException('album Not found'); - return album; - } - - update(id: string, dto: UpdateAlbumDto) { - const album = db.albums.find(album => album.id === id); - if (!album) throw new NotFoundException('album Not found'); - album.name = dto.name; - album.year = dto.year; - album.artistId = dto.artistId; - return album; - } - - remove(id: string) { - const index = db.albums.findIndex(album => album.id === id); - if (index === -1) throw new NotFoundException('album not found'); + create(dto: CreateAlbumDto): Album { + const newAlbum: Album = { + id: randomUUID(), + name: dto.name, + year: dto.year, + artistId: dto.artistId, + }; + db.albums.push(newAlbum); + return newAlbum; + } - db.tracks.forEach(track => { - if (track.albumId === id) { - track.albumId = null; - } - }); + findAll(): Album[] { + return db.albums; + } - db.albums.splice(index, 1); - return { message: 'album deleted' }; - } - } + findById(id: string): Album { + const album = db.albums.find((album) => album.id === id); + if (!album) throw new NotFoundException('album Not found'); + return album; + } + + update(id: string, dto: UpdateAlbumDto) { + const album = db.albums.find((album) => album.id === id); + if (!album) throw new NotFoundException('album Not found'); + album.name = dto.name; + album.year = dto.year; + album.artistId = dto.artistId; + return album; + } + + remove(id: string) { + const index = db.albums.findIndex((album) => album.id === id); + if (index === -1) throw new NotFoundException('album not found'); + + db.tracks.forEach((track) => { + if (track.albumId === id) { + track.albumId = null; + } + }); + + db.albums.splice(index, 1); + return { message: 'album deleted' }; + } +} diff --git a/src/albums/dto/create-album.dto.ts b/src/albums/dto/create-album.dto.ts index f0524a7..ccb68be 100644 --- a/src/albums/dto/create-album.dto.ts +++ b/src/albums/dto/create-album.dto.ts @@ -1,15 +1,15 @@ -import { Type } from "class-transformer"; -import { IsInt, IsOptional, IsString, IsUUID } from "class-validator"; +import { Type } from 'class-transformer'; +import { IsInt, IsOptional, IsString, IsUUID } from 'class-validator'; export class CreateAlbumDto { - @IsString() - name: string; + @IsString() + name: string; - @IsInt() - @Type(() => Number) - year: number; + @IsInt() + @Type(() => Number) + year: number; - @IsUUID('4') - @IsOptional() - artistId: string | null; // refers to Artist -} \ No newline at end of file + @IsUUID('4') + @IsOptional() + artistId: string | null; // refers to Artist +} diff --git a/src/albums/dto/update-album.dto.ts b/src/albums/dto/update-album.dto.ts index 4a8dfac..a54846a 100644 --- a/src/albums/dto/update-album.dto.ts +++ b/src/albums/dto/update-album.dto.ts @@ -1,15 +1,15 @@ -import { IsInt, IsOptional, IsString, IsUUID } from "class-validator"; +import { IsInt, IsOptional, IsString, IsUUID } from 'class-validator'; export class UpdateAlbumDto { - @IsOptional() - @IsString() - name: string; + @IsOptional() + @IsString() + name: string; - @IsOptional() - @IsInt() - year: number; + @IsOptional() + @IsInt() + year: number; - @IsUUID('4') - @IsOptional() - artistId: string | null; // refers to Artist -} \ No newline at end of file + @IsUUID('4') + @IsOptional() + artistId: string | null; // refers to Artist +} diff --git a/src/albums/entities/album.entity.ts b/src/albums/entities/album.entity.ts index 01e3836..b74349b 100644 --- a/src/albums/entities/album.entity.ts +++ b/src/albums/entities/album.entity.ts @@ -3,4 +3,4 @@ export class Album { name: string; year: number; artistId: string | null; // refers to Artist -} \ No newline at end of file +} diff --git a/src/app.module.ts b/src/app.module.ts index 91dc376..a1de42e 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -8,7 +8,13 @@ import { TracksModule } from './tracks/tracks.module'; import { FavoritesModule } from './favorites/favorites.module'; @Module({ - imports: [UsersModule, ArtistsModule, AlbumsModule, TracksModule, FavoritesModule], + imports: [ + UsersModule, + ArtistsModule, + AlbumsModule, + TracksModule, + FavoritesModule, + ], controllers: [AppController], providers: [AppService], }) diff --git a/src/artists/artists.controller.ts b/src/artists/artists.controller.ts index 0256b33..c8bae6a 100644 --- a/src/artists/artists.controller.ts +++ b/src/artists/artists.controller.ts @@ -1,4 +1,14 @@ -import { Body, Controller, Delete, Get, HttpCode, Param, ParseUUIDPipe, Post, Put } from '@nestjs/common'; +import { + Body, + Controller, + Delete, + Get, + HttpCode, + Param, + ParseUUIDPipe, + Post, + Put, +} from '@nestjs/common'; import { ArtistsService } from './artists.service'; import { CreateArtistDto } from './dto/create-artist.dto'; import { UpdateArtistDto } from './dto/update-artist.dto'; @@ -6,47 +16,59 @@ import { Artist } from './entities/artist.entity'; @Controller('artist') export class ArtistsController { - constructor(private readonly artistsService: ArtistsService) {} + constructor(private readonly artistsService: ArtistsService) {} - @Get() - getAll() { - return this.artistsService.findAll(); - } + @Get() + getAll() { + return this.artistsService.findAll(); + } - @Get(':id') - getById( - @Param('id', new ParseUUIDPipe({ - version: '4', - errorHttpStatusCode: 400, - })) id: string, - ) { - return this.artistsService.findById(id); - } + @Get(':id') + getById( + @Param( + 'id', + new ParseUUIDPipe({ + version: '4', + errorHttpStatusCode: 400, + }), + ) + id: string, + ) { + return this.artistsService.findById(id); + } - @Post() - create( - @Body() dto: CreateArtistDto, - ) { - return this.artistsService.create(dto); - } + @Post() + create(@Body() dto: CreateArtistDto) { + return this.artistsService.create(dto); + } - @Put(':id') - update( - @Param('id', new ParseUUIDPipe({ - version: '4', - errorHttpStatusCode: 400, - })) id: string, - @Body() dto: UpdateArtistDto, - ): Artist { - return this.artistsService.update(id, dto); - } - - @Delete(':id') - @HttpCode(204) - remove(@Param('id', new ParseUUIDPipe({ - version: '4', - errorHttpStatusCode: 400, - })) id: string,): void { - this.artistsService.remove(id); - } + @Put(':id') + update( + @Param( + 'id', + new ParseUUIDPipe({ + version: '4', + errorHttpStatusCode: 400, + }), + ) + id: string, + @Body() dto: UpdateArtistDto, + ): Artist { + return this.artistsService.update(id, dto); + } + + @Delete(':id') + @HttpCode(204) + remove( + @Param( + 'id', + new ParseUUIDPipe({ + version: '4', + errorHttpStatusCode: 400, + }), + ) + id: string, + ): void { + this.artistsService.remove(id); + } } diff --git a/src/artists/artists.module.ts b/src/artists/artists.module.ts index 7183619..396d381 100644 --- a/src/artists/artists.module.ts +++ b/src/artists/artists.module.ts @@ -5,6 +5,6 @@ import { ArtistsService } from './artists.service'; @Module({ imports: [ArtistsModule], controllers: [ArtistsController], - providers: [ArtistsService] + providers: [ArtistsService], }) export class ArtistsModule {} diff --git a/src/artists/artists.service.ts b/src/artists/artists.service.ts index 854f95f..1277f15 100644 --- a/src/artists/artists.service.ts +++ b/src/artists/artists.service.ts @@ -7,52 +7,51 @@ import { UpdateArtistDto } from './dto/update-artist.dto'; @Injectable() export class ArtistsService { - create(dto: CreateArtistDto): Artist { - const newArtist: Artist = { - id: randomUUID(), - name: dto.name, - grammy: dto.grammy, - }; - db.artists.push(newArtist); - return newArtist; - } - - findAll(): Artist[] { - return db.artists; - } - - findById(id: string): Artist { - const artist = db.artists.find(artist => artist.id === id); - if (!artist) throw new NotFoundException('artist Not found'); - return artist; - } - - update(id: string, dto: UpdateArtistDto) { - const artist = db.artists.find(artist => artist.id === id); - if (!artist) throw new NotFoundException('artist Not found'); - artist.name = dto.name; - artist.grammy = dto.grammy; - return artist; - } - - remove(id: string) { - const index = db.artists.findIndex(artist => artist.id === id); - if (index === -1) throw new NotFoundException('artist not found'); - - db.tracks.forEach(track => { - if (track.artistId === id) { - track.artistId = null; - } - }); - - db.albums.forEach(album => { - if (album.artistId === id) { - album.artistId = null; - } - }); - - db.artists.splice(index, 1); - return { message: 'artist deleted' }; - } - + create(dto: CreateArtistDto): Artist { + const newArtist: Artist = { + id: randomUUID(), + name: dto.name, + grammy: dto.grammy, + }; + db.artists.push(newArtist); + return newArtist; + } + + findAll(): Artist[] { + return db.artists; + } + + findById(id: string): Artist { + const artist = db.artists.find((artist) => artist.id === id); + if (!artist) throw new NotFoundException('artist Not found'); + return artist; + } + + update(id: string, dto: UpdateArtistDto) { + const artist = db.artists.find((artist) => artist.id === id); + if (!artist) throw new NotFoundException('artist Not found'); + artist.name = dto.name; + artist.grammy = dto.grammy; + return artist; + } + + remove(id: string) { + const index = db.artists.findIndex((artist) => artist.id === id); + if (index === -1) throw new NotFoundException('artist not found'); + + db.tracks.forEach((track) => { + if (track.artistId === id) { + track.artistId = null; + } + }); + + db.albums.forEach((album) => { + if (album.artistId === id) { + album.artistId = null; + } + }); + + db.artists.splice(index, 1); + return { message: 'artist deleted' }; + } } diff --git a/src/artists/dto/create-artist.dto.ts b/src/artists/dto/create-artist.dto.ts index 821dbd0..1901072 100644 --- a/src/artists/dto/create-artist.dto.ts +++ b/src/artists/dto/create-artist.dto.ts @@ -1,9 +1,9 @@ -import { IsBoolean, IsString, IsUUID, MinLength } from "class-validator"; +import { IsBoolean, IsString } from 'class-validator'; export class CreateArtistDto { - @IsString() - name: string; + @IsString() + name: string; - @IsBoolean() - grammy: boolean; -} \ No newline at end of file + @IsBoolean() + grammy: boolean; +} diff --git a/src/artists/dto/update-artist.dto.ts b/src/artists/dto/update-artist.dto.ts index 2a271f7..1f6ae77 100644 --- a/src/artists/dto/update-artist.dto.ts +++ b/src/artists/dto/update-artist.dto.ts @@ -1,11 +1,11 @@ -import { IsBoolean, IsOptional, IsString, IsUUID, MinLength } from "class-validator"; +import { IsBoolean, IsOptional, IsString } from 'class-validator'; export class UpdateArtistDto { - @IsOptional() - @IsString() - name: string; + @IsOptional() + @IsString() + name: string; - @IsOptional() - @IsBoolean() - grammy: boolean; -} \ No newline at end of file + @IsOptional() + @IsBoolean() + grammy: boolean; +} diff --git a/src/artists/entities/artist.entity.ts b/src/artists/entities/artist.entity.ts index 24e472a..a9b57ad 100644 --- a/src/artists/entities/artist.entity.ts +++ b/src/artists/entities/artist.entity.ts @@ -2,4 +2,4 @@ export class Artist { id: string; // uuid v4 name: string; grammy: boolean; -} \ No newline at end of file +} diff --git a/src/db.ts b/src/db.ts index 85b7004..de45ca1 100644 --- a/src/db.ts +++ b/src/db.ts @@ -1,17 +1,16 @@ import { Album } from './albums/entities/album.entity'; import { Artist } from './artists/entities/artist.entity'; -import { Favorites } from './favorites/entities/favorites.entity'; import { Track } from './tracks/entities/track.entity'; import { User } from './users/entities/user.entity'; export const db = { - users: [] as User[], - artists: [] as Artist[], - albums: [] as Album[], - tracks: [] as Track[], - favorites:{ - artists: [] as string[], - albums: [] as string[], - tracks: [] as string[], - } -} \ No newline at end of file + users: [] as User[], + artists: [] as Artist[], + albums: [] as Album[], + tracks: [] as Track[], + favorites: { + artists: [] as string[], + albums: [] as string[], + tracks: [] as string[], + }, +}; diff --git a/src/favorites/dto/create-favorites.dto.ts b/src/favorites/dto/create-favorites.dto.ts index 4718cc6..87e5a53 100644 --- a/src/favorites/dto/create-favorites.dto.ts +++ b/src/favorites/dto/create-favorites.dto.ts @@ -1,15 +1,15 @@ -import { IsArray, IsString, IsUUID, MinLength } from "class-validator"; +import { IsArray, IsUUID } from 'class-validator'; export class CreateFavoritesDto { - @IsArray() - @IsUUID('4', {each: true}) - artists: string[]; // favorite artists ids + @IsArray() + @IsUUID('4', { each: true }) + artists: string[]; // favorite artists ids - @IsArray() - @IsUUID('4', {each: true}) - albums: string[]; // favorite albums ids - - @IsArray() - @IsUUID('4', {each: true}) - tracks: string[]; -} \ No newline at end of file + @IsArray() + @IsUUID('4', { each: true }) + albums: string[]; // favorite albums ids + + @IsArray() + @IsUUID('4', { each: true }) + tracks: string[]; +} diff --git a/src/favorites/dto/update-favorites.dto.ts b/src/favorites/dto/update-favorites.dto.ts index 38fb0f3..09a6939 100644 --- a/src/favorites/dto/update-favorites.dto.ts +++ b/src/favorites/dto/update-favorites.dto.ts @@ -1,18 +1,18 @@ -import { IsArray, IsOptional, IsString, IsUUID, MinLength } from "class-validator"; +import { IsArray, IsOptional, IsUUID } from 'class-validator'; export class UpdateFavoritesDto { - @IsOptional() - @IsArray() - @IsUUID('4', {each: true}) - artists?: string[]; // favorite artists ids + @IsOptional() + @IsArray() + @IsUUID('4', { each: true }) + artists?: string[]; // favorite artists ids - @IsOptional() - @IsArray() - @IsUUID('4', {each: true}) - albums?: string[]; // favorite albums ids - - @IsOptional() - @IsArray() - @IsUUID('4', {each: true}) - tracks?: string[]; -} \ No newline at end of file + @IsOptional() + @IsArray() + @IsUUID('4', { each: true }) + albums?: string[]; // favorite albums ids + + @IsOptional() + @IsArray() + @IsUUID('4', { each: true }) + tracks?: string[]; +} diff --git a/src/favorites/entities/favorites.entity.ts b/src/favorites/entities/favorites.entity.ts index a5b02b5..4c8ec56 100644 --- a/src/favorites/entities/favorites.entity.ts +++ b/src/favorites/entities/favorites.entity.ts @@ -2,4 +2,4 @@ export class Favorites { artists: string[]; // favorite artists ids albums: string[]; // favorite albums ids tracks: string[]; // favorite tracks ids -} \ No newline at end of file +} diff --git a/src/favorites/favorites.module.ts b/src/favorites/favorites.module.ts index cb53027..98e9385 100644 --- a/src/favorites/favorites.module.ts +++ b/src/favorites/favorites.module.ts @@ -4,6 +4,6 @@ import { FavoritesService } from './favorites.service'; @Module({ controllers: [FavoritesController], - providers: [FavoritesService] + providers: [FavoritesService], }) export class FavoritesModule {} diff --git a/src/main.ts b/src/main.ts index 437b101..69c6ffb 100644 --- a/src/main.ts +++ b/src/main.ts @@ -7,10 +7,12 @@ configDotenv(); async function bootstrap() { const app = await NestFactory.create(AppModule); - app.useGlobalPipes(new ValidationPipe({ - whitelist: true, - transform: true, - })); + app.useGlobalPipes( + new ValidationPipe({ + whitelist: true, + transform: true, + }), + ); await app.listen(process.env.PORT || 4000); } bootstrap(); diff --git a/src/tracks/dto/create-track.dto.ts b/src/tracks/dto/create-track.dto.ts index ee61c6b..2c6b9ef 100644 --- a/src/tracks/dto/create-track.dto.ts +++ b/src/tracks/dto/create-track.dto.ts @@ -1,17 +1,17 @@ -import { IsInt, IsOptional, IsString, IsUUID, MinLength } from "class-validator"; +import { IsInt, IsOptional, IsString, IsUUID } from 'class-validator'; export class CreateTrackDto { - @IsString() - name: string; + @IsString() + name: string; - @IsUUID('4') - @IsOptional() - artistId: string | null; // refers to Artist + @IsUUID('4') + @IsOptional() + artistId: string | null; // refers to Artist - @IsUUID('4') - @IsOptional() - albumId: string | null; // refers to Album + @IsUUID('4') + @IsOptional() + albumId: string | null; // refers to Album - @IsInt() - duration: number; // integer number -} \ No newline at end of file + @IsInt() + duration: number; // integer number +} diff --git a/src/tracks/dto/update-track.dto.ts b/src/tracks/dto/update-track.dto.ts index 1e01b55..5d6ea58 100644 --- a/src/tracks/dto/update-track.dto.ts +++ b/src/tracks/dto/update-track.dto.ts @@ -1,19 +1,19 @@ -import { IsInt, IsOptional, IsString, IsUUID, MinLength } from "class-validator"; +import { IsInt, IsOptional, IsString, IsUUID } from 'class-validator'; export class UpdateTrackDto { - @IsOptional() - @IsString() - name: string; + @IsOptional() + @IsString() + name: string; - @IsUUID('4') - @IsOptional() - artistId: string | null; // refers to Artist + @IsUUID('4') + @IsOptional() + artistId: string | null; // refers to Artist - @IsUUID('4') - @IsOptional() - albumId: string | null; // refers to Album + @IsUUID('4') + @IsOptional() + albumId: string | null; // refers to Album - @IsOptional() - @IsInt() - duration: number; // integer number -} \ No newline at end of file + @IsOptional() + @IsInt() + duration: number; // integer number +} diff --git a/src/tracks/entities/track.entity.ts b/src/tracks/entities/track.entity.ts index df06e03..0c0b6c0 100644 --- a/src/tracks/entities/track.entity.ts +++ b/src/tracks/entities/track.entity.ts @@ -4,4 +4,4 @@ export class Track { artistId: string | null; // refers to Artist albumId: string | null; // refers to Album duration: number; // integer number -} \ No newline at end of file +} diff --git a/src/tracks/tracks.controller.ts b/src/tracks/tracks.controller.ts index b777da4..7f10cf9 100644 --- a/src/tracks/tracks.controller.ts +++ b/src/tracks/tracks.controller.ts @@ -1,53 +1,74 @@ -import { Body, Controller, Delete, Get, HttpCode, HttpStatus, Param, ParseUUIDPipe, Post, Put, Res } from '@nestjs/common'; -import { Response } from 'express'; +import { + Body, + Controller, + Delete, + Get, + HttpCode, + Param, + ParseUUIDPipe, + Post, + Put, +} from '@nestjs/common'; import { TracksService } from './tracks.service'; -import { validate } from 'uuid'; import { CreateTrackDto } from './dto/create-track.dto'; import { UpdateTrackDto } from './dto/update-track.dto'; import { Track } from './entities/track.entity'; @Controller('track') export class TracksController { - constructor(private readonly tracksService: TracksService) {} + constructor(private readonly tracksService: TracksService) {} - @Get() - getAll() { - return this.tracksService.findAll(); - } + @Get() + getAll() { + return this.tracksService.findAll(); + } - @Get(':id') - getById( - @Param('id', new ParseUUIDPipe({ - version: '4', - errorHttpStatusCode: 400, - })) id: string, - ) { - return this.tracksService.findById(id); - } + @Get(':id') + getById( + @Param( + 'id', + new ParseUUIDPipe({ + version: '4', + errorHttpStatusCode: 400, + }), + ) + id: string, + ) { + return this.tracksService.findById(id); + } - @Post() - create( - @Body() dto: CreateTrackDto, - ) { - return this.tracksService.create(dto); - } + @Post() + create(@Body() dto: CreateTrackDto) { + return this.tracksService.create(dto); + } - @Put(':id') - update( - @Param('id', new ParseUUIDPipe({ - version: '4', - errorHttpStatusCode: 400, - })) id: string, - @Body() dto: UpdateTrackDto, - ): Track { - return this.tracksService.update(id, dto); - } - - @Delete(':id') - @HttpCode(204) - remove(@Param('id', new ParseUUIDPipe({ - version: '4', - errorHttpStatusCode: 400, - })) id: string,): void { - this.tracksService.remove(id); - }} + @Put(':id') + update( + @Param( + 'id', + new ParseUUIDPipe({ + version: '4', + errorHttpStatusCode: 400, + }), + ) + id: string, + @Body() dto: UpdateTrackDto, + ): Track { + return this.tracksService.update(id, dto); + } + + @Delete(':id') + @HttpCode(204) + remove( + @Param( + 'id', + new ParseUUIDPipe({ + version: '4', + errorHttpStatusCode: 400, + }), + ) + id: string, + ): void { + this.tracksService.remove(id); + } +} diff --git a/src/tracks/tracks.module.ts b/src/tracks/tracks.module.ts index 8c7c304..cf556f0 100644 --- a/src/tracks/tracks.module.ts +++ b/src/tracks/tracks.module.ts @@ -5,6 +5,6 @@ import { TracksService } from './tracks.service'; @Module({ imports: [TracksModule], controllers: [TracksController], - providers: [TracksService] + providers: [TracksService], }) -export class TracksModule {} \ No newline at end of file +export class TracksModule {} diff --git a/src/tracks/tracks.service.ts b/src/tracks/tracks.service.ts index 39bbb24..45dd071 100644 --- a/src/tracks/tracks.service.ts +++ b/src/tracks/tracks.service.ts @@ -7,43 +7,42 @@ import { UpdateTrackDto } from './dto/update-track.dto'; @Injectable() export class TracksService { - create(createTrackDto: CreateTrackDto): Track { - const newTrack: Track = { - id: randomUUID(), - name: createTrackDto.name, - artistId: createTrackDto.artistId, - albumId: createTrackDto.albumId, - duration: createTrackDto.duration - }; - db.tracks.push(newTrack); - return newTrack; - } + create(createTrackDto: CreateTrackDto): Track { + const newTrack: Track = { + id: randomUUID(), + name: createTrackDto.name, + artistId: createTrackDto.artistId, + albumId: createTrackDto.albumId, + duration: createTrackDto.duration, + }; + db.tracks.push(newTrack); + return newTrack; + } - findAll(): Track[] { - return db.tracks; - } + findAll(): Track[] { + return db.tracks; + } - findById(id: string): Track { - const track = db.tracks.find(track => track.id === id); - if (!track) throw new NotFoundException('track Not found'); - return track; - } + findById(id: string): Track { + const track = db.tracks.find((track) => track.id === id); + if (!track) throw new NotFoundException('track Not found'); + return track; + } - update(id: string, updateDto: UpdateTrackDto) { - const track = db.tracks.find(track => track.id === id); - if (!track) throw new NotFoundException('track Not found'); - track.name = updateDto.name; - track.artistId = updateDto.artistId; - track.albumId = updateDto.albumId; - track.duration = updateDto.duration; - return track; - } - - remove(id: string) { - const index = db.tracks.findIndex(track => track.id === id); - if (index === -1) throw new NotFoundException('track not found'); - db.tracks.splice(index, 1); - return { message: 'track deleted' }; - } + update(id: string, updateDto: UpdateTrackDto) { + const track = db.tracks.find((track) => track.id === id); + if (!track) throw new NotFoundException('track Not found'); + track.name = updateDto.name; + track.artistId = updateDto.artistId; + track.albumId = updateDto.albumId; + track.duration = updateDto.duration; + return track; + } + remove(id: string) { + const index = db.tracks.findIndex((track) => track.id === id); + if (index === -1) throw new NotFoundException('track not found'); + db.tracks.splice(index, 1); + return { message: 'track deleted' }; + } } diff --git a/src/users/dto/create-user.dto.ts b/src/users/dto/create-user.dto.ts index d9b0283..7fa64aa 100644 --- a/src/users/dto/create-user.dto.ts +++ b/src/users/dto/create-user.dto.ts @@ -1,11 +1,11 @@ -import { IsInt, IsString, IsUUID, Min, MinLength } from "class-validator"; +import { IsString, MinLength } from 'class-validator'; export class CreateUserDto { - @IsString() - @MinLength(3) - login: string; + @IsString() + @MinLength(3) + login: string; - @IsString() - @MinLength(5) - password: string; -} \ No newline at end of file + @IsString() + @MinLength(5) + password: string; +} diff --git a/src/users/dto/return-user.dto.ts b/src/users/dto/return-user.dto.ts index 5521404..95f3e60 100644 --- a/src/users/dto/return-user.dto.ts +++ b/src/users/dto/return-user.dto.ts @@ -1,19 +1,19 @@ -import { IsInt, IsString, IsUUID, Min, MinLength } from "class-validator"; +import { IsInt, IsString, IsUUID, Min } from 'class-validator'; export class ReturnUserDto { - @IsUUID('4') - id: string; // uuid v4 + @IsUUID('4') + id: string; // uuid v4 - @IsString() - login: string; + @IsString() + login: string; - @IsInt() - @Min(1) - version: number; // integer number, increments on update - - @IsInt() - createdAt: number; // timestamp of creation - - @IsInt() - updatedAt: number; // timestamp of last update -} \ No newline at end of file + @IsInt() + @Min(1) + version: number; // integer number, increments on update + + @IsInt() + createdAt: number; // timestamp of creation + + @IsInt() + updatedAt: number; // timestamp of last update +} diff --git a/src/users/dto/update-user.dto.ts b/src/users/dto/update-user.dto.ts index aeecaa1..553ea34 100644 --- a/src/users/dto/update-user.dto.ts +++ b/src/users/dto/update-user.dto.ts @@ -1,9 +1,9 @@ -import { IsInt, IsOptional, IsString, IsUUID, Min, MinLength } from "class-validator"; +import { IsString } from 'class-validator'; export class UpdateUserDto { - @IsString() - oldPassword: string; + @IsString() + oldPassword: string; - @IsString() - newPassword: string; -} \ No newline at end of file + @IsString() + newPassword: string; +} diff --git a/src/users/entities/user.entity.ts b/src/users/entities/user.entity.ts index 19cf92c..642d656 100644 --- a/src/users/entities/user.entity.ts +++ b/src/users/entities/user.entity.ts @@ -5,4 +5,4 @@ export class User { version: number; // integer number, increments on update createdAt: number; // timestamp of creation updatedAt: number; // timestamp of last update -} \ No newline at end of file +} diff --git a/src/users/users.controller.ts b/src/users/users.controller.ts index e7060d3..6ed7131 100644 --- a/src/users/users.controller.ts +++ b/src/users/users.controller.ts @@ -1,63 +1,87 @@ -import { Body, Controller, Delete, Get, HttpCode, HttpStatus, NotFoundException, Param, ParseUUIDPipe, Post, Put, Res } from '@nestjs/common'; +import { + Body, + Controller, + Delete, + Get, + HttpCode, + HttpStatus, + Param, + ParseUUIDPipe, + Post, + Put, + Res, +} from '@nestjs/common'; import { UsersService } from './users.service'; import { Response } from 'express'; import { CreateUserDto } from './dto/create-user.dto'; import { User } from './entities/user.entity'; import { validate } from 'uuid'; import { UpdateUserDto } from './dto/update-user.dto'; -import { NotFoundError } from 'rxjs'; @Controller('user') export class UsersController { - constructor(private readonly userService: UsersService) {} + constructor(private readonly userService: UsersService) {} - @Get() - getAll(@Res() res: Response) { - const users = this.userService.findAll(); - return res.status(HttpStatus.OK).json(users); - } + @Get() + getAll(@Res() res: Response) { + const users = this.userService.findAll(); + return res.status(HttpStatus.OK).json(users); + } - @Get(':id') - getById( - @Param('id', new ParseUUIDPipe({ - version: '4', - errorHttpStatusCode: 400, - })) id: string, - @Res() res: Response - ) { - if (!validate(id)) return res.status(400).json({ message: 'Invalid userId format' }); - const user = this.userService.findById(id); - if (!user) { - return res.status(HttpStatus.NOT_FOUND).json({ message: 'User not found' }); - } - return res.status(HttpStatus.OK).json(user); + @Get(':id') + getById( + @Param( + 'id', + new ParseUUIDPipe({ + version: '4', + errorHttpStatusCode: 400, + }), + ) + id: string, + @Res() res: Response, + ) { + if (!validate(id)) + return res.status(400).json({ message: 'Invalid userId format' }); + const user = this.userService.findById(id); + if (!user) { + return res + .status(HttpStatus.NOT_FOUND) + .json({ message: 'User not found' }); } + return res.status(HttpStatus.OK).json(user); + } - @Post() - create( - @Body() createUserDto: CreateUserDto, - @Res() res: Response - ) { - res.status(HttpStatus.CREATED).json(this.userService.create(createUserDto)); - } + @Post() + create(@Body() createUserDto: CreateUserDto, @Res() res: Response) { + res.status(HttpStatus.CREATED).json(this.userService.create(createUserDto)); + } - @Put(':id') - update( - @Param('id', new ParseUUIDPipe({ - version: '4', - })) id: string, - @Body() dto: UpdateUserDto, - ): Omit { - return this.userService.update(id, dto); - } - - @Delete(':id') - @HttpCode(204) - remove(@Param('id', new ParseUUIDPipe({ - version: '4', - errorHttpStatusCode: 400, - })) id: string,): void { - this.userService.remove(id); - } + @Put(':id') + update( + @Param( + 'id', + new ParseUUIDPipe({ + version: '4', + }), + ) + id: string, + @Body() dto: UpdateUserDto, + ): Omit { + return this.userService.update(id, dto); + } + @Delete(':id') + @HttpCode(204) + remove( + @Param( + 'id', + new ParseUUIDPipe({ + version: '4', + errorHttpStatusCode: 400, + }), + ) + id: string, + ): void { + this.userService.remove(id); + } } diff --git a/src/users/users.module.ts b/src/users/users.module.ts index 03c0b2a..82063a5 100644 --- a/src/users/users.module.ts +++ b/src/users/users.module.ts @@ -5,6 +5,6 @@ import { UsersService } from './users.service'; @Module({ imports: [UsersModule], controllers: [UsersController], - providers: [UsersService] + providers: [UsersService], }) export class UsersModule {} diff --git a/src/users/users.service.ts b/src/users/users.service.ts index e66d332..b4e46fd 100644 --- a/src/users/users.service.ts +++ b/src/users/users.service.ts @@ -1,56 +1,65 @@ -import { ForbiddenException, Injectable, NotFoundException } from '@nestjs/common'; +import { + ForbiddenException, + Injectable, + NotFoundException, +} from '@nestjs/common'; import { db } from 'src/db'; import { CreateUserDto } from './dto/create-user.dto'; import { randomUUID } from 'crypto'; import { User } from './entities/user.entity'; import { UpdateUserDto } from './dto/update-user.dto'; -import { validate } from 'uuid'; @Injectable() export class UsersService { - create(createUserDto: CreateUserDto): Omit { - const newUser: User = { - id: randomUUID(), - login:createUserDto.login, - password: createUserDto.password, - version: 1, - createdAt: Date.now(), - updatedAt: Date.now(), - }; - db.users.push(newUser); - const { password, ...safeUser } = newUser; - return safeUser; - } + create(createUserDto: CreateUserDto): Omit { + const newUser: User = { + id: randomUUID(), + login: createUserDto.login, + password: createUserDto.password, + version: 1, + createdAt: Date.now(), + updatedAt: Date.now(), + }; + db.users.push(newUser); + const safeUser = { ...newUser }; + delete safeUser.password; + return safeUser; + } - findAll(): Omit[] { - return db.users.map(({ password, ...rest }) => rest); - } + findAll(): Omit[] { + return db.users.map((user) => { + delete user.password; + return user; + }); + } - findById(id: string): Omit { - const user = db.users.find(user => user.id === id); - if (!user) throw new NotFoundException('User Not found'); - const { password, ...safeUser } = user; - return safeUser; - } + findById(id: string): Omit { + const user = db.users.find((user) => user.id === id); + if (!user) throw new NotFoundException('User Not found'); + const safeUser = { ...user }; + delete safeUser.password; + return safeUser; + } - update(id: string, updateDto: UpdateUserDto) { - const user = db.users.find(user => user.id === id); - if (!user) throw new NotFoundException('User Not found'); - if (user.password !== updateDto.oldPassword) - throw new ForbiddenException('The password is wrong!'); + update(id: string, updateDto: UpdateUserDto) { + const user = db.users.find((user) => user.id === id); + if (!user) throw new NotFoundException('User Not found'); + if (user.password !== updateDto.oldPassword) + throw new ForbiddenException('The password is wrong!'); - user.password = updateDto.newPassword; - user.version++; - user.updatedAt = Date.now(); + user.password = updateDto.newPassword; + user.version++; + user.updatedAt = Date.now(); - const { password, ...safeUser } = user; - return safeUser; - } + const safeUser = { ...user }; + delete safeUser.password; + return safeUser; + } - remove(id: string) { - const index = db.users.findIndex(user => user.id === id); - if (index === -1) throw new NotFoundException('User not found'); - db.users.splice(index, 1); - return { message: 'User deleted' }; - } + remove(id: string) { + const index = db.users.findIndex((user) => user.id === id); + if (index === -1) throw new NotFoundException('User not found'); + db.users.splice(index, 1); + return { message: 'User deleted' }; + } } From 22954c04a60b19327d8b42da33b90c9694acc688 Mon Sep 17 00:00:00 2001 From: Yulia Demir Date: Sun, 1 Jun 2025 11:38:27 +0300 Subject: [PATCH 10/32] feat: implement favorites endpoints --- src/albums/albums.module.ts | 1 + src/artists/artists.module.ts | 1 + src/db.ts | 6 +- src/favorites/entities/favorites.entity.ts | 6 +- src/favorites/favorites.controller.ts | 106 ++++++++++++++++++++- src/favorites/favorites.module.ts | 4 + src/favorites/favorites.service.ts | 56 ++++++++++- src/tracks/tracks.module.ts | 1 + 8 files changed, 170 insertions(+), 11 deletions(-) diff --git a/src/albums/albums.module.ts b/src/albums/albums.module.ts index e88fded..f0e3a7c 100644 --- a/src/albums/albums.module.ts +++ b/src/albums/albums.module.ts @@ -6,5 +6,6 @@ import { AlbumsService } from './albums.service'; imports: [AlbumsModule], controllers: [AlbumsController], providers: [AlbumsService], + exports: [AlbumsService] }) export class AlbumsModule {} diff --git a/src/artists/artists.module.ts b/src/artists/artists.module.ts index 396d381..18904b8 100644 --- a/src/artists/artists.module.ts +++ b/src/artists/artists.module.ts @@ -6,5 +6,6 @@ import { ArtistsService } from './artists.service'; imports: [ArtistsModule], controllers: [ArtistsController], providers: [ArtistsService], + exports: [ArtistsService], }) export class ArtistsModule {} diff --git a/src/db.ts b/src/db.ts index de45ca1..70753ab 100644 --- a/src/db.ts +++ b/src/db.ts @@ -9,8 +9,8 @@ export const db = { albums: [] as Album[], tracks: [] as Track[], favorites: { - artists: [] as string[], - albums: [] as string[], - tracks: [] as string[], + artists: new Set(), + albums: new Set(), + tracks: new Set(), }, }; diff --git a/src/favorites/entities/favorites.entity.ts b/src/favorites/entities/favorites.entity.ts index 4c8ec56..1a37677 100644 --- a/src/favorites/entities/favorites.entity.ts +++ b/src/favorites/entities/favorites.entity.ts @@ -1,5 +1,5 @@ export class Favorites { - artists: string[]; // favorite artists ids - albums: string[]; // favorite albums ids - tracks: string[]; // favorite tracks ids + artists: Set; // favorite artists ids + albums: Set; // favorite albums ids + tracks: Set; // favorite tracks ids } diff --git a/src/favorites/favorites.controller.ts b/src/favorites/favorites.controller.ts index f07a193..3d000d2 100644 --- a/src/favorites/favorites.controller.ts +++ b/src/favorites/favorites.controller.ts @@ -1,4 +1,104 @@ -import { Controller } from '@nestjs/common'; +import { Controller, Delete, Get, HttpCode, Param, ParseUUIDPipe, Post } from '@nestjs/common'; +import { FavoritesService } from './favorites.service'; +import { ArtistsService } from 'src/artists/artists.service'; +import { AlbumsService } from 'src/albums/albums.service'; +import { TracksService } from 'src/tracks/tracks.service'; -@Controller('favorites') -export class FavoritesController {} +@Controller('favs') +export class FavoritesController { + constructor( + private readonly favoritesService: FavoritesService, + private readonly artistsService: ArtistsService, + private readonly albumsService: AlbumsService, + private readonly tracksService: TracksService, + ) {} + + @Get() + getAll() { + return this.favoritesService.findAll(); + } + + //TRACKS + @Post('track/:id') + addTrackToFavorites( + @Param( + 'id', + new ParseUUIDPipe({ + version: '4', + errorHttpStatusCode: 400, + }), + ) id: string + ) { + this.favoritesService.addTrack(id); + } + + @Delete('track/:id') + @HttpCode(204) + deleteTrackToFavorites( + @Param( + 'id', + new ParseUUIDPipe({ + version: '4', + errorHttpStatusCode: 400, + }), + ) id: string + ) { + this.favoritesService.removeTrack(id); + } + + //ARTISTS + @Post('artist/:id') + addArtistToFavorites( + @Param( + 'id', + new ParseUUIDPipe({ + version: '4', + errorHttpStatusCode: 400, + }), + ) id: string + ) { + this.favoritesService.addArtist(id); + } + + @Delete('artist/:id') + @HttpCode(204) + deleteArtistFromFavorites( + @Param( + 'id', + new ParseUUIDPipe({ + version: '4', + errorHttpStatusCode: 400, + }), + ) id: string + ) { + this.favoritesService.removeArtist(id); + } + + //ALBUMS + @Post('album/:id') + addAlbumToFavorites( + @Param( + 'id', + new ParseUUIDPipe({ + version: '4', + errorHttpStatusCode: 400, + }), + ) id: string + ) { + this.favoritesService.addAlbum(id); + } + + @Delete('album/:id') + @HttpCode(204) + deleteAlbumFromFavorites( + @Param( + 'id', + new ParseUUIDPipe({ + version: '4', + errorHttpStatusCode: 400, + }), + ) id: string + ) { + this.favoritesService.removeAlbum(id); + } +} diff --git a/src/favorites/favorites.module.ts b/src/favorites/favorites.module.ts index 98e9385..6e6800c 100644 --- a/src/favorites/favorites.module.ts +++ b/src/favorites/favorites.module.ts @@ -1,8 +1,12 @@ import { Module } from '@nestjs/common'; import { FavoritesController } from './favorites.controller'; import { FavoritesService } from './favorites.service'; +import { ArtistsModule } from 'src/artists/artists.module'; +import { TracksModule } from 'src/tracks/tracks.module'; +import { AlbumsModule } from 'src/albums/albums.module'; @Module({ + imports: [FavoritesModule, ArtistsModule, TracksModule, AlbumsModule], controllers: [FavoritesController], providers: [FavoritesService], }) diff --git a/src/favorites/favorites.service.ts b/src/favorites/favorites.service.ts index 9f9ee5a..6736f7b 100644 --- a/src/favorites/favorites.service.ts +++ b/src/favorites/favorites.service.ts @@ -1,4 +1,56 @@ -import { Injectable } from '@nestjs/common'; +import { Injectable, NotFoundException, UnprocessableEntityException } from '@nestjs/common'; +import { Favorites } from './entities/favorites.entity'; +import { db } from 'src/db'; +import { Track } from 'src/tracks/entities/track.entity'; +import { Artist } from 'src/artists/entities/artist.entity'; +import { Album } from 'src/albums/entities/album.entity'; @Injectable() -export class FavoritesService {} +export class FavoritesService { + findAll(): { artists: Artist[], albums: Album[], tracks: Track[] } { + return { + artists: Array.from(db.favorites.artists).map(artistId => db.artists.find(artist => artist?.id === artistId)) + .filter((a): a is Artist => a !== undefined), + albums: Array.from(db.favorites.albums).map(albumId => db.albums.find(album => album?.id === albumId)) + .filter((a): a is Album => a !== undefined), + tracks: Array.from(db.favorites.tracks).map(trackId => db.tracks.find(track => track?.id === trackId)) + .filter((a): a is Track => a !== undefined), + } + } + + addTrack(id: string): Track { + const track = db.tracks.find((track) => track.id === id); + if (!track) throw new UnprocessableEntityException('track Not found'); + db.favorites.tracks.add(track.id); + return track; + } + + removeTrack(id: string): void { + const removed = db.favorites.tracks.delete(id); + if (!removed) throw new NotFoundException('track Not found'); + } + + addArtist(id: string): Artist { + const artist = db.artists.find((artist) => artist.id === id); + if (!artist) throw new UnprocessableEntityException('artist Not found'); + db.favorites.artists.add(artist.id); + return artist; + } + + removeArtist(id: string): void { + const removed = db.favorites.artists.delete(id); + if (!removed) throw new NotFoundException('artist Not found'); + } + + addAlbum(id: string): Album { + const album = db.albums.find((album) => album.id === id); + if (!album) throw new UnprocessableEntityException('album Not found'); + db.favorites.albums.add(album.id); + return album; + } + + removeAlbum(id: string): void { + const removed = db.favorites.albums.delete(id); + if (!removed) throw new NotFoundException('album Not found'); + } +} \ No newline at end of file diff --git a/src/tracks/tracks.module.ts b/src/tracks/tracks.module.ts index cf556f0..11a2d47 100644 --- a/src/tracks/tracks.module.ts +++ b/src/tracks/tracks.module.ts @@ -6,5 +6,6 @@ import { TracksService } from './tracks.service'; imports: [TracksModule], controllers: [TracksController], providers: [TracksService], + exports: [TracksService] }) export class TracksModule {} From fa0d498796e1a6c5583f2e31e8f251777e9640f7 Mon Sep 17 00:00:00 2001 From: Yulia Demir Date: Sun, 1 Jun 2025 11:41:20 +0300 Subject: [PATCH 11/32] fix: lint errors --- src/albums/albums.module.ts | 2 +- src/favorites/favorites.controller.ts | 192 ++++++++++++++------------ src/favorites/favorites.service.ts | 92 ++++++------ src/tracks/tracks.module.ts | 2 +- tsconfig.json | 2 +- 5 files changed, 155 insertions(+), 135 deletions(-) diff --git a/src/albums/albums.module.ts b/src/albums/albums.module.ts index f0e3a7c..f2acb0f 100644 --- a/src/albums/albums.module.ts +++ b/src/albums/albums.module.ts @@ -6,6 +6,6 @@ import { AlbumsService } from './albums.service'; imports: [AlbumsModule], controllers: [AlbumsController], providers: [AlbumsService], - exports: [AlbumsService] + exports: [AlbumsService], }) export class AlbumsModule {} diff --git a/src/favorites/favorites.controller.ts b/src/favorites/favorites.controller.ts index 3d000d2..9fe7531 100644 --- a/src/favorites/favorites.controller.ts +++ b/src/favorites/favorites.controller.ts @@ -1,4 +1,12 @@ -import { Controller, Delete, Get, HttpCode, Param, ParseUUIDPipe, Post } from '@nestjs/common'; +import { + Controller, + Delete, + Get, + HttpCode, + Param, + ParseUUIDPipe, + Post, +} from '@nestjs/common'; import { FavoritesService } from './favorites.service'; import { ArtistsService } from 'src/artists/artists.service'; import { AlbumsService } from 'src/albums/albums.service'; @@ -6,99 +14,105 @@ import { TracksService } from 'src/tracks/tracks.service'; @Controller('favs') export class FavoritesController { - constructor( - private readonly favoritesService: FavoritesService, - private readonly artistsService: ArtistsService, - private readonly albumsService: AlbumsService, - private readonly tracksService: TracksService, - ) {} + constructor( + private readonly favoritesService: FavoritesService, + private readonly artistsService: ArtistsService, + private readonly albumsService: AlbumsService, + private readonly tracksService: TracksService, + ) {} - @Get() - getAll() { - return this.favoritesService.findAll(); - } + @Get() + getAll() { + return this.favoritesService.findAll(); + } - //TRACKS - @Post('track/:id') - addTrackToFavorites( - @Param( - 'id', - new ParseUUIDPipe({ - version: '4', - errorHttpStatusCode: 400, - }), - ) id: string - ) { - this.favoritesService.addTrack(id); - } + //TRACKS + @Post('track/:id') + addTrackToFavorites( + @Param( + 'id', + new ParseUUIDPipe({ + version: '4', + errorHttpStatusCode: 400, + }), + ) + id: string, + ) { + this.favoritesService.addTrack(id); + } - @Delete('track/:id') - @HttpCode(204) - deleteTrackToFavorites( - @Param( - 'id', - new ParseUUIDPipe({ - version: '4', - errorHttpStatusCode: 400, - }), - ) id: string - ) { - this.favoritesService.removeTrack(id); - } + @Delete('track/:id') + @HttpCode(204) + deleteTrackToFavorites( + @Param( + 'id', + new ParseUUIDPipe({ + version: '4', + errorHttpStatusCode: 400, + }), + ) + id: string, + ) { + this.favoritesService.removeTrack(id); + } - //ARTISTS - @Post('artist/:id') - addArtistToFavorites( - @Param( - 'id', - new ParseUUIDPipe({ - version: '4', - errorHttpStatusCode: 400, - }), - ) id: string - ) { - this.favoritesService.addArtist(id); - } + //ARTISTS + @Post('artist/:id') + addArtistToFavorites( + @Param( + 'id', + new ParseUUIDPipe({ + version: '4', + errorHttpStatusCode: 400, + }), + ) + id: string, + ) { + this.favoritesService.addArtist(id); + } - @Delete('artist/:id') - @HttpCode(204) - deleteArtistFromFavorites( - @Param( - 'id', - new ParseUUIDPipe({ - version: '4', - errorHttpStatusCode: 400, - }), - ) id: string - ) { - this.favoritesService.removeArtist(id); - } + @Delete('artist/:id') + @HttpCode(204) + deleteArtistFromFavorites( + @Param( + 'id', + new ParseUUIDPipe({ + version: '4', + errorHttpStatusCode: 400, + }), + ) + id: string, + ) { + this.favoritesService.removeArtist(id); + } - //ALBUMS - @Post('album/:id') - addAlbumToFavorites( - @Param( - 'id', - new ParseUUIDPipe({ - version: '4', - errorHttpStatusCode: 400, - }), - ) id: string - ) { - this.favoritesService.addAlbum(id); - } + //ALBUMS + @Post('album/:id') + addAlbumToFavorites( + @Param( + 'id', + new ParseUUIDPipe({ + version: '4', + errorHttpStatusCode: 400, + }), + ) + id: string, + ) { + this.favoritesService.addAlbum(id); + } - @Delete('album/:id') - @HttpCode(204) - deleteAlbumFromFavorites( - @Param( - 'id', - new ParseUUIDPipe({ - version: '4', - errorHttpStatusCode: 400, - }), - ) id: string - ) { - this.favoritesService.removeAlbum(id); - } + @Delete('album/:id') + @HttpCode(204) + deleteAlbumFromFavorites( + @Param( + 'id', + new ParseUUIDPipe({ + version: '4', + errorHttpStatusCode: 400, + }), + ) + id: string, + ) { + this.favoritesService.removeAlbum(id); + } } diff --git a/src/favorites/favorites.service.ts b/src/favorites/favorites.service.ts index 6736f7b..b3f0680 100644 --- a/src/favorites/favorites.service.ts +++ b/src/favorites/favorites.service.ts @@ -1,5 +1,8 @@ -import { Injectable, NotFoundException, UnprocessableEntityException } from '@nestjs/common'; -import { Favorites } from './entities/favorites.entity'; +import { + Injectable, + NotFoundException, + UnprocessableEntityException, +} from '@nestjs/common'; import { db } from 'src/db'; import { Track } from 'src/tracks/entities/track.entity'; import { Artist } from 'src/artists/entities/artist.entity'; @@ -7,50 +10,53 @@ import { Album } from 'src/albums/entities/album.entity'; @Injectable() export class FavoritesService { - findAll(): { artists: Artist[], albums: Album[], tracks: Track[] } { - return { - artists: Array.from(db.favorites.artists).map(artistId => db.artists.find(artist => artist?.id === artistId)) - .filter((a): a is Artist => a !== undefined), - albums: Array.from(db.favorites.albums).map(albumId => db.albums.find(album => album?.id === albumId)) - .filter((a): a is Album => a !== undefined), - tracks: Array.from(db.favorites.tracks).map(trackId => db.tracks.find(track => track?.id === trackId)) - .filter((a): a is Track => a !== undefined), - } - } + findAll(): { artists: Artist[]; albums: Album[]; tracks: Track[] } { + return { + artists: Array.from(db.favorites.artists) + .map((artistId) => db.artists.find((artist) => artist?.id === artistId)) + .filter((a): a is Artist => a !== undefined), + albums: Array.from(db.favorites.albums) + .map((albumId) => db.albums.find((album) => album?.id === albumId)) + .filter((a): a is Album => a !== undefined), + tracks: Array.from(db.favorites.tracks) + .map((trackId) => db.tracks.find((track) => track?.id === trackId)) + .filter((a): a is Track => a !== undefined), + }; + } - addTrack(id: string): Track { - const track = db.tracks.find((track) => track.id === id); - if (!track) throw new UnprocessableEntityException('track Not found'); - db.favorites.tracks.add(track.id); - return track; - } + addTrack(id: string): Track { + const track = db.tracks.find((track) => track.id === id); + if (!track) throw new UnprocessableEntityException('track Not found'); + db.favorites.tracks.add(track.id); + return track; + } - removeTrack(id: string): void { - const removed = db.favorites.tracks.delete(id); - if (!removed) throw new NotFoundException('track Not found'); - } + removeTrack(id: string): void { + const removed = db.favorites.tracks.delete(id); + if (!removed) throw new NotFoundException('track Not found'); + } - addArtist(id: string): Artist { - const artist = db.artists.find((artist) => artist.id === id); - if (!artist) throw new UnprocessableEntityException('artist Not found'); - db.favorites.artists.add(artist.id); - return artist; - } + addArtist(id: string): Artist { + const artist = db.artists.find((artist) => artist.id === id); + if (!artist) throw new UnprocessableEntityException('artist Not found'); + db.favorites.artists.add(artist.id); + return artist; + } - removeArtist(id: string): void { - const removed = db.favorites.artists.delete(id); - if (!removed) throw new NotFoundException('artist Not found'); - } + removeArtist(id: string): void { + const removed = db.favorites.artists.delete(id); + if (!removed) throw new NotFoundException('artist Not found'); + } - addAlbum(id: string): Album { - const album = db.albums.find((album) => album.id === id); - if (!album) throw new UnprocessableEntityException('album Not found'); - db.favorites.albums.add(album.id); - return album; - } + addAlbum(id: string): Album { + const album = db.albums.find((album) => album.id === id); + if (!album) throw new UnprocessableEntityException('album Not found'); + db.favorites.albums.add(album.id); + return album; + } - removeAlbum(id: string): void { - const removed = db.favorites.albums.delete(id); - if (!removed) throw new NotFoundException('album Not found'); - } -} \ No newline at end of file + removeAlbum(id: string): void { + const removed = db.favorites.albums.delete(id); + if (!removed) throw new NotFoundException('album Not found'); + } +} diff --git a/src/tracks/tracks.module.ts b/src/tracks/tracks.module.ts index 11a2d47..adb0ea9 100644 --- a/src/tracks/tracks.module.ts +++ b/src/tracks/tracks.module.ts @@ -6,6 +6,6 @@ import { TracksService } from './tracks.service'; imports: [TracksModule], controllers: [TracksController], providers: [TracksService], - exports: [TracksService] + exports: [TracksService], }) export class TracksModule {} diff --git a/tsconfig.json b/tsconfig.json index adb614c..95f5641 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -6,7 +6,7 @@ "emitDecoratorMetadata": true, "experimentalDecorators": true, "allowSyntheticDefaultImports": true, - "target": "es2017", + "target": "ES2021", "sourceMap": true, "outDir": "./dist", "baseUrl": "./", From 833c713eb7a391719ca5e5ed6fd4df4c441e6ea7 Mon Sep 17 00:00:00 2001 From: Yulia Demir Date: Sun, 1 Jun 2025 12:18:34 +0300 Subject: [PATCH 12/32] docs: api --- doc/api.yaml | 88 +++++----------------------------------------------- 1 file changed, 8 insertions(+), 80 deletions(-) diff --git a/doc/api.yaml b/doc/api.yaml index c3ec873..5bc1e62 100644 --- a/doc/api.yaml +++ b/doc/api.yaml @@ -117,79 +117,7 @@ security: - bearerAuth: [] paths: - /login: - post: - tags: - - Login - security: [] - summary: Login - description: Logins a user and returns a JWT-token - requestBody: - required: true - content: - application/json: - schema: - type: object - title: example - properties: - login: - type: string - description: Username - password: - type: string - description: Password - required: - - user - - login - responses: - 200: - description: Successful login. - content: - application/json: - schema: - type: object - properties: - token: - type: string - description: JWT Token - 403: - description: Incorrect login or password - /signup: - post: - tags: - - Signup - security: [ ] - summary: Signup - description: Signup a user - requestBody: - required: true - content: - application/json: - schema: - type: object - title: example - properties: - login: - type: string - minLength: 3 - maxLength: 255 - description: Username - password: - type: string - format: password - pattern: ^[a-zA-Z0-9]{3,30} - description: Password - required: - - login - - password - responses: - 204: - description: Successful signup - 400: - description: Bad request - 409: - description: Conflict. Login already exists - /users: + /user: get: tags: - Users @@ -239,7 +167,7 @@ paths: description: Bad request. body does not contain required fields 401: $ref: '#/components/responses/UnauthorizedError' - /users/{userId}: + /user/{userId}: parameters: - name: userId in: path @@ -334,7 +262,7 @@ paths: $ref: '#/components/responses/UnauthorizedError' 404: description: User not found - /tracks: + /track: get: tags: - Track @@ -388,7 +316,7 @@ paths: description: Bad request. body does not contain required fields 401: $ref: '#/components/responses/UnauthorizedError' - /tracks/{id}: + /track/{id}: parameters: - name: id in: path @@ -492,7 +420,7 @@ paths: $ref: '#/components/responses/UnauthorizedError' 404: description: Track was not found. - /albums: + /album: get: tags: - Album @@ -544,7 +472,7 @@ paths: description: Bad request. body does not contain required fields 401: $ref: '#/components/responses/UnauthorizedError' - /albums/{id}: + /album/{id}: parameters: - name: id in: path @@ -621,7 +549,7 @@ paths: $ref: '#/components/responses/UnauthorizedError' 404: description: Album was not found. - /artists: + /artist: get: tags: - Artist @@ -668,7 +596,7 @@ paths: description: Bad request. body does not contain required fields 401: $ref: '#/components/responses/UnauthorizedError' - /artists/{id}: + /artist/{id}: parameters: - name: id in: path From bb391c2e4cf0dca163c34d31de19aec51ef51020 Mon Sep 17 00:00:00 2001 From: Yulia Demir Date: Sun, 1 Jun 2025 15:22:27 +0300 Subject: [PATCH 13/32] docs: add information to README --- README.md | 16 ++++++++++++---- doc/api.yaml | 2 +- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index b8b8bdc..bcab782 100644 --- a/README.md +++ b/README.md @@ -5,19 +5,27 @@ - Git - [Download & Install Git](https://git-scm.com/downloads). - Node.js - [Download & Install Node.js](https://nodejs.org/en/download/) and the npm package manager. -## Downloading +### INSTALATION + +## 1. Downloading + +``` +git clone https://github.com/YuliaDemir/nodejs2025Q2-service.git +``` + +## 2. Moving to the development branch ``` -git clone {repository URL} +git checkout dev ``` -## Installing NPM modules +## 3. Installing NPM modules ``` npm install ``` -## Running application +### RUNNING application ``` npm start diff --git a/doc/api.yaml b/doc/api.yaml index 5bc1e62..5b8b20d 100644 --- a/doc/api.yaml +++ b/doc/api.yaml @@ -5,7 +5,7 @@ info: version: 1.0.0 servers: - - url: /api + - url: /4000 components: schemas: From 58e123135461851cce89f3255605ae2efcd2c988 Mon Sep 17 00:00:00 2001 From: Yulia Demir Date: Fri, 6 Jun 2025 11:07:39 +0300 Subject: [PATCH 14/32] docs: add dockers files --- .dockerignore | 7 + .env | 6 + .env.example | 6 + Dockerfile | 12 + README.md | 10 +- docker-compose.yml | 33 ++ package-lock.json | 950 ++++++++++++++++++++++++++++++++------------ package.json | 3 + postgres/Dockerfile | 7 + 9 files changed, 780 insertions(+), 254 deletions(-) create mode 100644 .dockerignore create mode 100644 Dockerfile create mode 100644 docker-compose.yml create mode 100644 postgres/Dockerfile diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..de76f6e --- /dev/null +++ b/.dockerignore @@ -0,0 +1,7 @@ +node_modules +npm-debug.log +Dockerfile* +docker-compose.yml +.env +.git +.gitignore \ No newline at end of file diff --git a/.env b/.env index 8d594af..1f2f9ab 100644 --- a/.env +++ b/.env @@ -5,3 +5,9 @@ JWT_SECRET_KEY=secret123123 JWT_SECRET_REFRESH_KEY=secret123123 TOKEN_EXPIRE_TIME=1h TOKEN_REFRESH_EXPIRE_TIME=24h + +DB_HOST=localhost +DB_PORT=5432 +DB_USERNAME=postgres +DB_PASSWORD=postgres +DB_NAME=musicdb \ No newline at end of file diff --git a/.env.example b/.env.example index 8d594af..2d17723 100644 --- a/.env.example +++ b/.env.example @@ -5,3 +5,9 @@ JWT_SECRET_KEY=secret123123 JWT_SECRET_REFRESH_KEY=secret123123 TOKEN_EXPIRE_TIME=1h TOKEN_REFRESH_EXPIRE_TIME=24h + +DB_HOST=localhost +DB_PORT=5432 +DB_USERNAME=postgres +DB_PASSWORD=postgres +DB_NAME=musicdb diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..3d13975 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,12 @@ +FROM node:20 + +WORKDIR /app + +COPY package*.json +RUN npm install + +COPY . . + +EXPOSE 3000 + +CMD ["npm", "start"] \ No newline at end of file diff --git a/README.md b/README.md index bcab782..1f8fbb1 100644 --- a/README.md +++ b/README.md @@ -5,27 +5,27 @@ - Git - [Download & Install Git](https://git-scm.com/downloads). - Node.js - [Download & Install Node.js](https://nodejs.org/en/download/) and the npm package manager. -### INSTALATION +## INSTALATION -## 1. Downloading +### 1. Downloading ``` git clone https://github.com/YuliaDemir/nodejs2025Q2-service.git ``` -## 2. Moving to the development branch +### 2. Moving to the development branch ``` git checkout dev ``` -## 3. Installing NPM modules +### 3. Installing NPM modules ``` npm install ``` -### RUNNING application +## RUNNING application ``` npm start diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..2e9333e --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,33 @@ +version: '3.9' + +networks: + app-network: + driver: bridge + +services: + db: + build: ./postgres + container_name: postgres-db + environment: + POSTGRES_USER: appuser + POSTGRES_PASSWORD: apppassword + POSTGRES_DB: appdb + ports: + - "5432:5432" + networks: + - app-network + + app: + build: . + container_name: node-app + environment: + DB_HOST: db + DB_USER: appuser + DB_PASSWORD: apppassword + DB_NAME: appdb + ports: + - "3000:3000" + depends_on: + - db + networks: + - app-network \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 2cd9013..7e8ad31 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,15 +14,18 @@ "@nestjs/jwt": "^11.0.0", "@nestjs/mapped-types": "^2.1.0", "@nestjs/platform-express": "^10.4.15", + "@nestjs/typeorm": "^11.0.0", "bcrypt": "^5.1.1", "class-transformer": "^0.5.1", "class-validator": "^0.14.1", "dotenv": "^16.5.0", "express": "^5.1.0", "http-status-codes": "^2.2.0", + "pg": "^8.16.0", "reflect-metadata": "^0.2.1", "rimraf": "^5.0.5", "rxjs": "^7.8.1", + "typeorm": "^0.3.24", "uuid": "^9.0.1" }, "devDependencies": { @@ -808,7 +811,7 @@ "version": "0.8.1", "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@jridgewell/trace-mapping": "0.3.9" @@ -821,7 +824,7 @@ "version": "0.3.9", "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@jridgewell/resolve-uri": "^3.0.3", @@ -1502,7 +1505,7 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", - "dev": true, + "devOptional": true, "engines": { "node": ">=6.0.0" } @@ -1530,7 +1533,7 @@ "version": "1.5.0", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", - "dev": true + "devOptional": true }, "node_modules/@jridgewell/trace-mapping": { "version": "0.3.25", @@ -2098,6 +2101,19 @@ } } }, + "node_modules/@nestjs/typeorm": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/@nestjs/typeorm/-/typeorm-11.0.0.tgz", + "integrity": "sha512-SOeUQl70Lb2OfhGkvnh4KXWlsd+zA08RuuQgT7kKbzivngxzSo1Oc7Usu5VxCxACQC9wc2l9esOHILSJeK7rJA==", + "license": "MIT", + "peerDependencies": { + "@nestjs/common": "^10.0.0 || ^11.0.0", + "@nestjs/core": "^10.0.0 || ^11.0.0", + "reflect-metadata": "^0.1.13 || ^0.2.0", + "rxjs": "^7.2.0", + "typeorm": "^0.3.0" + } + }, "node_modules/@noble/hashes": { "version": "1.7.2", "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.7.2.tgz", @@ -2225,32 +2241,38 @@ "@sinonjs/commons": "^3.0.0" } }, + "node_modules/@sqltools/formatter": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/@sqltools/formatter/-/formatter-1.2.5.tgz", + "integrity": "sha512-Uy0+khmZqUrUGm5dmMqVlnvufZRSK0FbYzVgp0UMstm+F5+W2/jnEEQyc9vo1ZR/E5ZI/B1WjjoTqBqwJL6Krw==", + "license": "MIT" + }, "node_modules/@tsconfig/node10": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz", "integrity": "sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/@tsconfig/node12": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/@tsconfig/node14": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/@tsconfig/node16": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/@types/babel__core": { @@ -3004,7 +3026,7 @@ "version": "8.14.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz", "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==", - "dev": true, + "devOptional": true, "bin": { "acorn": "bin/acorn" }, @@ -3026,7 +3048,7 @@ "version": "8.3.4", "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz", "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "acorn": "^8.11.0" @@ -3150,6 +3172,15 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/ansis": { + "version": "3.17.0", + "resolved": "https://registry.npmjs.org/ansis/-/ansis-3.17.0.tgz", + "integrity": "sha512-0qWUglt9JEqLFr3w1I1pbrChn1grhaiAR2ocX1PP/flRmxgtwTzPFFFnfIlD6aMOLQZgSuCRlidD70lvx8yhzg==", + "license": "ISC", + "engines": { + "node": ">=14" + } + }, "node_modules/anymatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", @@ -3163,6 +3194,15 @@ "node": ">= 8" } }, + "node_modules/app-root-path": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/app-root-path/-/app-root-path-3.1.0.tgz", + "integrity": "sha512-biN3PwB2gUtjaYy/isrU3aNWI5w+fAfvHkSvCKeQGxhmYpwKFUxudR3Yya+KqVRHBmEDYh+/lTozYCFbmzX4nA==", + "license": "MIT", + "engines": { + "node": ">= 6.0.0" + } + }, "node_modules/append-field": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/append-field/-/append-field-1.0.0.tgz", @@ -3206,7 +3246,7 @@ "version": "4.1.3", "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/argparse": { @@ -3393,7 +3433,6 @@ "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "dev": true, "funding": [ { "type": "github", @@ -3870,7 +3909,6 @@ "version": "8.0.1", "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", - "dev": true, "license": "ISC", "dependencies": { "string-width": "^4.2.0", @@ -4105,7 +4143,7 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/cross-env": { @@ -4139,12 +4177,19 @@ "node": ">= 8" } }, + "node_modules/dayjs": { + "version": "1.11.13", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.13.tgz", + "integrity": "sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==", + "license": "MIT" + }, "node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "license": "MIT", "dependencies": { - "ms": "2.1.2" + "ms": "^2.1.3" }, "engines": { "node": ">=6.0" @@ -4156,10 +4201,9 @@ } }, "node_modules/dedent": { - "version": "1.5.3", - "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.5.3.tgz", - "integrity": "sha512-NHQtfOOW68WD8lgypbLA5oT+Bt0xXJhiYvoR6SmmNXZfpzOGXwdKWmcwG8N7PwVVWV3eF/68nmD9BaJSsTBhyQ==", - "dev": true, + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.6.0.tgz", + "integrity": "sha512-F1Z+5UCFpmQUzJa11agbyPVMbpgT/qA3/SKyJ1jyBgm7dUcUEa8v9JwDkerSQXfakBwFljIxhOJqGkjUwZ9FSA==", "license": "MIT", "peerDependencies": { "babel-plugin-macros": "^3.1.0" @@ -4279,7 +4323,7 @@ "version": "4.0.2", "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", - "dev": true, + "devOptional": true, "license": "BSD-3-Clause", "engines": { "node": ">=0.3.1" @@ -4488,7 +4532,6 @@ "version": "3.2.0", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", - "dev": true, "engines": { "node": ">=6" } @@ -4922,23 +4965,6 @@ "node": ">=18" } }, - "node_modules/express/node_modules/debug": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", - "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, "node_modules/express/node_modules/iconv-lite": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", @@ -4981,12 +5007,6 @@ "node": ">= 0.6" } }, - "node_modules/express/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "license": "MIT" - }, "node_modules/express/node_modules/qs": { "version": "6.14.0", "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz", @@ -5210,29 +5230,6 @@ "node": ">= 0.8" } }, - "node_modules/finalhandler/node_modules/debug": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", - "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/finalhandler/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "license": "MIT" - }, "node_modules/find-up": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", @@ -5529,7 +5526,6 @@ "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true, "license": "ISC", "engines": { "node": "6.* || 8.* || >= 10.*" @@ -5819,7 +5815,6 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "dev": true, "funding": [ { "type": "github", @@ -7195,7 +7190,7 @@ "version": "1.3.6", "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", - "dev": true + "devOptional": true }, "node_modules/makeerror": { "version": "1.0.12", @@ -7393,9 +7388,10 @@ } }, "node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" }, "node_modules/multer": { "version": "1.4.4-lts.1", @@ -7881,6 +7877,95 @@ "node": ">=8" } }, + "node_modules/pg": { + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/pg/-/pg-8.16.0.tgz", + "integrity": "sha512-7SKfdvP8CTNXjMUzfcVTaI+TDzBEeaUnVwiVGZQD1Hh33Kpev7liQba9uLd4CfN8r9mCVsD0JIpq03+Unpz+kg==", + "license": "MIT", + "dependencies": { + "pg-connection-string": "^2.9.0", + "pg-pool": "^3.10.0", + "pg-protocol": "^1.10.0", + "pg-types": "2.2.0", + "pgpass": "1.0.5" + }, + "engines": { + "node": ">= 8.0.0" + }, + "optionalDependencies": { + "pg-cloudflare": "^1.2.5" + }, + "peerDependencies": { + "pg-native": ">=3.0.1" + }, + "peerDependenciesMeta": { + "pg-native": { + "optional": true + } + } + }, + "node_modules/pg-cloudflare": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/pg-cloudflare/-/pg-cloudflare-1.2.5.tgz", + "integrity": "sha512-OOX22Vt0vOSRrdoUPKJ8Wi2OpE/o/h9T8X1s4qSkCedbNah9ei2W2765be8iMVxQUsvgT7zIAT2eIa9fs5+vtg==", + "license": "MIT", + "optional": true + }, + "node_modules/pg-connection-string": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.9.0.tgz", + "integrity": "sha512-P2DEBKuvh5RClafLngkAuGe9OUlFV7ebu8w1kmaaOgPcpJd1RIFh7otETfI6hAR8YupOLFTY7nuvvIn7PLciUQ==", + "license": "MIT" + }, + "node_modules/pg-int8": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz", + "integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==", + "license": "ISC", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/pg-pool": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.10.0.tgz", + "integrity": "sha512-DzZ26On4sQ0KmqnO34muPcmKbhrjmyiO4lCCR0VwEd7MjmiKf5NTg/6+apUEu0NF7ESa37CGzFxH513CoUmWnA==", + "license": "MIT", + "peerDependencies": { + "pg": ">=8.0" + } + }, + "node_modules/pg-protocol": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.10.0.tgz", + "integrity": "sha512-IpdytjudNuLv8nhlHs/UrVBhU0e78J0oIS/0AVdTbWxSOkFUVdsHC/NrorO6nXsQNDTT1kzDSOMJubBQviX18Q==", + "license": "MIT" + }, + "node_modules/pg-types": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz", + "integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==", + "license": "MIT", + "dependencies": { + "pg-int8": "1.0.1", + "postgres-array": "~2.0.0", + "postgres-bytea": "~1.0.0", + "postgres-date": "~1.0.4", + "postgres-interval": "^1.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/pgpass": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/pgpass/-/pgpass-1.0.5.tgz", + "integrity": "sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==", + "license": "MIT", + "dependencies": { + "split2": "^4.1.0" + } + }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", @@ -7987,6 +8072,45 @@ "node": ">=4" } }, + "node_modules/postgres-array": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz", + "integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/postgres-bytea": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.0.tgz", + "integrity": "sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/postgres-date": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz", + "integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/postgres-interval": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz", + "integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==", + "license": "MIT", + "dependencies": { + "xtend": "^4.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -8241,7 +8365,6 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", - "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -8427,29 +8550,6 @@ "node": ">= 18" } }, - "node_modules/router/node_modules/debug": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", - "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/router/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "license": "MIT" - }, "node_modules/router/node_modules/path-to-regexp": { "version": "8.2.0", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.2.0.tgz", @@ -8607,23 +8707,6 @@ "node": ">= 18" } }, - "node_modules/send/node_modules/debug": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", - "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, "node_modules/send/node_modules/mime-db": { "version": "1.54.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", @@ -8645,12 +8728,6 @@ "node": ">= 0.6" } }, - "node_modules/send/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "license": "MIT" - }, "node_modules/serialize-javascript": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", @@ -8703,6 +8780,19 @@ "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" }, + "node_modules/sha.js": { + "version": "2.4.11", + "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", + "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", + "license": "(MIT AND BSD-3-Clause)", + "dependencies": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + }, + "bin": { + "sha.js": "bin.js" + } + }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -8845,6 +8935,15 @@ "source-map": "^0.6.0" } }, + "node_modules/split2": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", + "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==", + "license": "ISC", + "engines": { + "node": ">= 10.x" + } + }, "node_modules/sprintf-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", @@ -8852,6 +8951,22 @@ "dev": true, "license": "BSD-3-Clause" }, + "node_modules/sql-highlight": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/sql-highlight/-/sql-highlight-6.0.0.tgz", + "integrity": "sha512-+fLpbAbWkQ+d0JEchJT/NrRRXbYRNbG15gFpANx73EwxQB1PRjj+k/OI0GTU0J63g8ikGkJECQp9z8XEJZvPRw==", + "funding": [ + "https://github.com/scriptcoded/sql-highlight?sponsor=1", + { + "type": "github", + "url": "https://github.com/sponsors/scriptcoded" + } + ], + "license": "MIT", + "engines": { + "node": ">=14" + } + }, "node_modules/stack-utils": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", @@ -9425,7 +9540,7 @@ "version": "10.9.2", "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@cspotcode/source-map-support": "^0.8.0", @@ -9562,29 +9677,231 @@ "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==" }, - "node_modules/typescript": { - "version": "5.8.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", - "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", - "dev": true, - "license": "Apache-2.0", + "node_modules/typeorm": { + "version": "0.3.24", + "resolved": "https://registry.npmjs.org/typeorm/-/typeorm-0.3.24.tgz", + "integrity": "sha512-4IrHG7A0tY8l5gEGXfW56VOMfUVWEkWlH/h5wmcyZ+V8oCiLj7iTPp0lEjMEZVrxEkGSdP9ErgTKHKXQApl/oA==", + "license": "MIT", + "dependencies": { + "@sqltools/formatter": "^1.2.5", + "ansis": "^3.17.0", + "app-root-path": "^3.1.0", + "buffer": "^6.0.3", + "dayjs": "^1.11.13", + "debug": "^4.4.0", + "dedent": "^1.6.0", + "dotenv": "^16.4.7", + "glob": "^10.4.5", + "sha.js": "^2.4.11", + "sql-highlight": "^6.0.0", + "tslib": "^2.8.1", + "uuid": "^11.1.0", + "yargs": "^17.7.2" + }, "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" + "typeorm": "cli.js", + "typeorm-ts-node-commonjs": "cli-ts-node-commonjs.js", + "typeorm-ts-node-esm": "cli-ts-node-esm.js" }, "engines": { - "node": ">=14.17" - } - }, - "node_modules/uid": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/uid/-/uid-2.0.2.tgz", - "integrity": "sha512-u3xV3X7uzvi5b1MncmZo3i2Aw222Zk1keqLA1YkHldREkAhAqi65wuPfe7lHx8H/Wzy+8CE7S7uS3jekIM5s8g==", - "dependencies": { - "@lukeed/csprng": "^1.0.0" + "node": ">=16.13.0" }, - "engines": { - "node": ">=8" + "funding": { + "url": "https://opencollective.com/typeorm" + }, + "peerDependencies": { + "@google-cloud/spanner": "^5.18.0 || ^6.0.0 || ^7.0.0", + "@sap/hana-client": "^2.12.25", + "better-sqlite3": "^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0", + "hdb-pool": "^0.1.6", + "ioredis": "^5.0.4", + "mongodb": "^5.8.0 || ^6.0.0", + "mssql": "^9.1.1 || ^10.0.1 || ^11.0.1", + "mysql2": "^2.2.5 || ^3.0.1", + "oracledb": "^6.3.0", + "pg": "^8.5.1", + "pg-native": "^3.0.0", + "pg-query-stream": "^4.0.0", + "redis": "^3.1.1 || ^4.0.0", + "reflect-metadata": "^0.1.14 || ^0.2.0", + "sql.js": "^1.4.0", + "sqlite3": "^5.0.3", + "ts-node": "^10.7.0", + "typeorm-aurora-data-api-driver": "^2.0.0 || ^3.0.0" + }, + "peerDependenciesMeta": { + "@google-cloud/spanner": { + "optional": true + }, + "@sap/hana-client": { + "optional": true + }, + "better-sqlite3": { + "optional": true + }, + "hdb-pool": { + "optional": true + }, + "ioredis": { + "optional": true + }, + "mongodb": { + "optional": true + }, + "mssql": { + "optional": true + }, + "mysql2": { + "optional": true + }, + "oracledb": { + "optional": true + }, + "pg": { + "optional": true + }, + "pg-native": { + "optional": true + }, + "pg-query-stream": { + "optional": true + }, + "redis": { + "optional": true + }, + "sql.js": { + "optional": true + }, + "sqlite3": { + "optional": true + }, + "ts-node": { + "optional": true + }, + "typeorm-aurora-data-api-driver": { + "optional": true + } + } + }, + "node_modules/typeorm/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/typeorm/node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/typeorm/node_modules/glob": { + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/typeorm/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/typeorm/node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "license": "ISC", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/typeorm/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/typeorm/node_modules/uuid": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.0.tgz", + "integrity": "sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/esm/bin/uuid" + } + }, + "node_modules/typescript": { + "version": "5.8.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", + "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", + "devOptional": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/uid": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/uid/-/uid-2.0.2.tgz", + "integrity": "sha512-u3xV3X7uzvi5b1MncmZo3i2Aw222Zk1keqLA1YkHldREkAhAqi65wuPfe7lHx8H/Wzy+8CE7S7uS3jekIM5s8g==", + "dependencies": { + "@lukeed/csprng": "^1.0.0" + }, + "engines": { + "node": ">=8" } }, "node_modules/undefsafe": { @@ -9687,7 +10004,7 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/v8-to-istanbul": { @@ -9855,7 +10172,6 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, "license": "MIT", "dependencies": { "ansi-styles": "^4.0.0", @@ -9918,7 +10234,6 @@ "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "dev": true, "license": "ISC", "engines": { "node": ">=10" @@ -9935,7 +10250,6 @@ "version": "17.7.2", "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", - "dev": true, "license": "MIT", "dependencies": { "cliui": "^8.0.1", @@ -9954,7 +10268,6 @@ "version": "21.1.1", "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", - "dev": true, "license": "ISC", "engines": { "node": ">=12" @@ -9964,7 +10277,7 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=6" @@ -10505,7 +10818,7 @@ "version": "0.8.1", "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", - "dev": true, + "devOptional": true, "requires": { "@jridgewell/trace-mapping": "0.3.9" }, @@ -10514,7 +10827,7 @@ "version": "0.3.9", "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", - "dev": true, + "devOptional": true, "requires": { "@jridgewell/resolve-uri": "^3.0.3", "@jridgewell/sourcemap-codec": "^1.4.10" @@ -11002,7 +11315,7 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", - "dev": true + "devOptional": true }, "@jridgewell/set-array": { "version": "1.2.1", @@ -11024,7 +11337,7 @@ "version": "1.5.0", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", - "dev": true + "devOptional": true }, "@jridgewell/trace-mapping": { "version": "0.3.25", @@ -11402,6 +11715,12 @@ "tslib": "2.6.2" } }, + "@nestjs/typeorm": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/@nestjs/typeorm/-/typeorm-11.0.0.tgz", + "integrity": "sha512-SOeUQl70Lb2OfhGkvnh4KXWlsd+zA08RuuQgT7kKbzivngxzSo1Oc7Usu5VxCxACQC9wc2l9esOHILSJeK7rJA==", + "requires": {} + }, "@noble/hashes": { "version": "1.7.2", "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.7.2.tgz", @@ -11489,29 +11808,34 @@ "@sinonjs/commons": "^3.0.0" } }, + "@sqltools/formatter": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/@sqltools/formatter/-/formatter-1.2.5.tgz", + "integrity": "sha512-Uy0+khmZqUrUGm5dmMqVlnvufZRSK0FbYzVgp0UMstm+F5+W2/jnEEQyc9vo1ZR/E5ZI/B1WjjoTqBqwJL6Krw==" + }, "@tsconfig/node10": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz", "integrity": "sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==", - "dev": true + "devOptional": true }, "@tsconfig/node12": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", - "dev": true + "devOptional": true }, "@tsconfig/node14": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", - "dev": true + "devOptional": true }, "@tsconfig/node16": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", - "dev": true + "devOptional": true }, "@types/babel__core": { "version": "7.20.5", @@ -12122,7 +12446,7 @@ "version": "8.14.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz", "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==", - "dev": true + "devOptional": true }, "acorn-jsx": { "version": "5.3.2", @@ -12135,7 +12459,7 @@ "version": "8.3.4", "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz", "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==", - "dev": true, + "devOptional": true, "requires": { "acorn": "^8.11.0" } @@ -12214,6 +12538,11 @@ "color-convert": "^2.0.1" } }, + "ansis": { + "version": "3.17.0", + "resolved": "https://registry.npmjs.org/ansis/-/ansis-3.17.0.tgz", + "integrity": "sha512-0qWUglt9JEqLFr3w1I1pbrChn1grhaiAR2ocX1PP/flRmxgtwTzPFFFnfIlD6aMOLQZgSuCRlidD70lvx8yhzg==" + }, "anymatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", @@ -12224,6 +12553,11 @@ "picomatch": "^2.0.4" } }, + "app-root-path": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/app-root-path/-/app-root-path-3.1.0.tgz", + "integrity": "sha512-biN3PwB2gUtjaYy/isrU3aNWI5w+fAfvHkSvCKeQGxhmYpwKFUxudR3Yya+KqVRHBmEDYh+/lTozYCFbmzX4nA==" + }, "append-field": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/append-field/-/append-field-1.0.0.tgz", @@ -12259,7 +12593,7 @@ "version": "4.1.3", "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", - "dev": true + "devOptional": true }, "argparse": { "version": "2.0.1", @@ -12404,8 +12738,7 @@ "base64-js": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "dev": true + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==" }, "bcrypt": { "version": "5.1.1", @@ -12719,7 +13052,6 @@ "version": "8.0.1", "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", - "dev": true, "requires": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", @@ -12890,7 +13222,7 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", - "dev": true + "devOptional": true }, "cross-env": { "version": "7.0.3", @@ -12911,19 +13243,23 @@ "which": "^2.0.1" } }, + "dayjs": { + "version": "1.11.13", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.13.tgz", + "integrity": "sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==" + }, "debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", "requires": { - "ms": "2.1.2" + "ms": "^2.1.3" } }, "dedent": { - "version": "1.5.3", - "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.5.3.tgz", - "integrity": "sha512-NHQtfOOW68WD8lgypbLA5oT+Bt0xXJhiYvoR6SmmNXZfpzOGXwdKWmcwG8N7PwVVWV3eF/68nmD9BaJSsTBhyQ==", - "dev": true, + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.6.0.tgz", + "integrity": "sha512-F1Z+5UCFpmQUzJa11agbyPVMbpgT/qA3/SKyJ1jyBgm7dUcUEa8v9JwDkerSQXfakBwFljIxhOJqGkjUwZ9FSA==", "requires": {} }, "deep-is": { @@ -13004,7 +13340,7 @@ "version": "4.0.2", "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", - "dev": true + "devOptional": true }, "diff-sequences": { "version": "29.6.3", @@ -13152,8 +13488,7 @@ "escalade": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", - "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", - "dev": true + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==" }, "escape-html": { "version": "1.0.3", @@ -13450,14 +13785,6 @@ "type-is": "^2.0.0" } }, - "debug": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", - "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", - "requires": { - "ms": "^2.1.3" - } - }, "iconv-lite": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", @@ -13484,11 +13811,6 @@ "mime-db": "^1.54.0" } }, - "ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" - }, "qs": { "version": "6.14.0", "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz", @@ -13666,21 +13988,6 @@ "on-finished": "^2.4.1", "parseurl": "^1.3.3", "statuses": "^2.0.1" - }, - "dependencies": { - "debug": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", - "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", - "requires": { - "ms": "^2.1.3" - } - }, - "ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" - } } }, "find-up": { @@ -13886,8 +14193,7 @@ "get-caller-file": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==" }, "get-intrinsic": { "version": "1.2.6", @@ -14082,8 +14388,7 @@ "ieee754": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", - "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "dev": true + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==" }, "ignore": { "version": "5.3.2", @@ -15078,7 +15383,7 @@ "version": "1.3.6", "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", - "dev": true + "devOptional": true }, "makeerror": { "version": "1.0.12", @@ -15215,9 +15520,9 @@ } }, "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" }, "multer": { "version": "1.4.4-lts.1", @@ -15567,6 +15872,66 @@ "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", "dev": true }, + "pg": { + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/pg/-/pg-8.16.0.tgz", + "integrity": "sha512-7SKfdvP8CTNXjMUzfcVTaI+TDzBEeaUnVwiVGZQD1Hh33Kpev7liQba9uLd4CfN8r9mCVsD0JIpq03+Unpz+kg==", + "requires": { + "pg-cloudflare": "^1.2.5", + "pg-connection-string": "^2.9.0", + "pg-pool": "^3.10.0", + "pg-protocol": "^1.10.0", + "pg-types": "2.2.0", + "pgpass": "1.0.5" + } + }, + "pg-cloudflare": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/pg-cloudflare/-/pg-cloudflare-1.2.5.tgz", + "integrity": "sha512-OOX22Vt0vOSRrdoUPKJ8Wi2OpE/o/h9T8X1s4qSkCedbNah9ei2W2765be8iMVxQUsvgT7zIAT2eIa9fs5+vtg==", + "optional": true + }, + "pg-connection-string": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.9.0.tgz", + "integrity": "sha512-P2DEBKuvh5RClafLngkAuGe9OUlFV7ebu8w1kmaaOgPcpJd1RIFh7otETfI6hAR8YupOLFTY7nuvvIn7PLciUQ==" + }, + "pg-int8": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz", + "integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==" + }, + "pg-pool": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.10.0.tgz", + "integrity": "sha512-DzZ26On4sQ0KmqnO34muPcmKbhrjmyiO4lCCR0VwEd7MjmiKf5NTg/6+apUEu0NF7ESa37CGzFxH513CoUmWnA==", + "requires": {} + }, + "pg-protocol": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.10.0.tgz", + "integrity": "sha512-IpdytjudNuLv8nhlHs/UrVBhU0e78J0oIS/0AVdTbWxSOkFUVdsHC/NrorO6nXsQNDTT1kzDSOMJubBQviX18Q==" + }, + "pg-types": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz", + "integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==", + "requires": { + "pg-int8": "1.0.1", + "postgres-array": "~2.0.0", + "postgres-bytea": "~1.0.0", + "postgres-date": "~1.0.4", + "postgres-interval": "^1.1.0" + } + }, + "pgpass": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/pgpass/-/pgpass-1.0.5.tgz", + "integrity": "sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==", + "requires": { + "split2": "^4.1.0" + } + }, "picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", @@ -15639,6 +16004,29 @@ "integrity": "sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==", "dev": true }, + "postgres-array": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz", + "integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==" + }, + "postgres-bytea": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.0.tgz", + "integrity": "sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w==" + }, + "postgres-date": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz", + "integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==" + }, + "postgres-interval": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz", + "integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==", + "requires": { + "xtend": "^4.0.0" + } + }, "prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -15810,8 +16198,7 @@ "require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", - "dev": true + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==" }, "require-from-string": { "version": "2.0.2", @@ -15931,19 +16318,6 @@ "path-to-regexp": "^8.0.0" }, "dependencies": { - "debug": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", - "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", - "requires": { - "ms": "^2.1.3" - } - }, - "ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" - }, "path-to-regexp": { "version": "8.2.0", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.2.0.tgz", @@ -16045,14 +16419,6 @@ "statuses": "^2.0.1" }, "dependencies": { - "debug": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", - "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", - "requires": { - "ms": "^2.1.3" - } - }, "mime-db": { "version": "1.54.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", @@ -16065,11 +16431,6 @@ "requires": { "mime-db": "^1.54.0" } - }, - "ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" } } }, @@ -16117,6 +16478,15 @@ "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" }, + "sha.js": { + "version": "2.4.11", + "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", + "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", + "requires": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, "shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -16216,12 +16586,22 @@ "source-map": "^0.6.0" } }, + "split2": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", + "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==" + }, "sprintf-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", "dev": true }, + "sql-highlight": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/sql-highlight/-/sql-highlight-6.0.0.tgz", + "integrity": "sha512-+fLpbAbWkQ+d0JEchJT/NrRRXbYRNbG15gFpANx73EwxQB1PRjj+k/OI0GTU0J63g8ikGkJECQp9z8XEJZvPRw==" + }, "stack-utils": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", @@ -16602,7 +16982,7 @@ "version": "10.9.2", "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", - "dev": true, + "devOptional": true, "requires": { "@cspotcode/source-map-support": "^0.8.0", "@tsconfig/node10": "^1.0.7", @@ -16690,11 +17070,87 @@ "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==" }, + "typeorm": { + "version": "0.3.24", + "resolved": "https://registry.npmjs.org/typeorm/-/typeorm-0.3.24.tgz", + "integrity": "sha512-4IrHG7A0tY8l5gEGXfW56VOMfUVWEkWlH/h5wmcyZ+V8oCiLj7iTPp0lEjMEZVrxEkGSdP9ErgTKHKXQApl/oA==", + "requires": { + "@sqltools/formatter": "^1.2.5", + "ansis": "^3.17.0", + "app-root-path": "^3.1.0", + "buffer": "^6.0.3", + "dayjs": "^1.11.13", + "debug": "^4.4.0", + "dedent": "^1.6.0", + "dotenv": "^16.4.7", + "glob": "^10.4.5", + "sha.js": "^2.4.11", + "sql-highlight": "^6.0.0", + "tslib": "^2.8.1", + "uuid": "^11.1.0", + "yargs": "^17.7.2" + }, + "dependencies": { + "brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "requires": { + "balanced-match": "^1.0.0" + } + }, + "buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "requires": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "glob": { + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "requires": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + } + }, + "minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "requires": { + "brace-expansion": "^2.0.1" + } + }, + "minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==" + }, + "tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" + }, + "uuid": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.0.tgz", + "integrity": "sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==" + } + } + }, "typescript": { "version": "5.8.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", - "dev": true + "devOptional": true }, "uid": { "version": "2.0.2", @@ -16764,7 +17220,7 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", - "dev": true + "devOptional": true }, "v8-to-istanbul": { "version": "9.3.0", @@ -16884,7 +17340,6 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, "requires": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", @@ -16924,8 +17379,7 @@ "y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "dev": true + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==" }, "yallist": { "version": "3.1.1", @@ -16937,7 +17391,6 @@ "version": "17.7.2", "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", - "dev": true, "requires": { "cliui": "^8.0.1", "escalade": "^3.1.1", @@ -16951,14 +17404,13 @@ "yargs-parser": { "version": "21.1.1", "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", - "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", - "dev": true + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==" }, "yn": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", - "dev": true + "devOptional": true }, "yocto-queue": { "version": "0.1.0", diff --git a/package.json b/package.json index 3e9b020..be4c167 100644 --- a/package.json +++ b/package.json @@ -30,15 +30,18 @@ "@nestjs/jwt": "^11.0.0", "@nestjs/mapped-types": "^2.1.0", "@nestjs/platform-express": "^10.4.15", + "@nestjs/typeorm": "^11.0.0", "bcrypt": "^5.1.1", "class-transformer": "^0.5.1", "class-validator": "^0.14.1", "dotenv": "^16.5.0", "express": "^5.1.0", "http-status-codes": "^2.2.0", + "pg": "^8.16.0", "reflect-metadata": "^0.2.1", "rimraf": "^5.0.5", "rxjs": "^7.8.1", + "typeorm": "^0.3.24", "uuid": "^9.0.1" }, "devDependencies": { diff --git a/postgres/Dockerfile b/postgres/Dockerfile new file mode 100644 index 0000000..ccb6370 --- /dev/null +++ b/postgres/Dockerfile @@ -0,0 +1,7 @@ +FROM postgres:16 + +ENV POSTGRES_USER=appuser +ENV POSTGRES_PASSWORD=apppassword +ENV POSTGRES_DB=appdb + +EXPOSE 5433 \ No newline at end of file From 48c8fa5b78aba0cec4b3b95fd14a8b558d77ca1b Mon Sep 17 00:00:00 2001 From: Yulia Demir Date: Sun, 8 Jun 2025 07:23:54 +0300 Subject: [PATCH 15/32] feat: user module for ql --- package-lock.json | 74 +++++++++++++++++++++++++++++-- package.json | 1 + src/app.module.ts | 16 +++++++ src/users/entities/user.entity.ts | 14 ++++++ src/users/users.controller.ts | 5 +-- src/users/users.module.ts | 5 ++- src/users/users.service.ts | 44 +++++++++--------- 7 files changed, 131 insertions(+), 28 deletions(-) diff --git a/package-lock.json b/package-lock.json index 7e8ad31..b7da6be 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,6 +10,7 @@ "license": "UNLICENSED", "dependencies": { "@nestjs/common": "^10.4.16", + "@nestjs/config": "^4.0.2", "@nestjs/core": "^10.4.15", "@nestjs/jwt": "^11.0.0", "@nestjs/mapped-types": "^2.1.0", @@ -1751,6 +1752,33 @@ "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", "license": "0BSD" }, + "node_modules/@nestjs/config": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@nestjs/config/-/config-4.0.2.tgz", + "integrity": "sha512-McMW6EXtpc8+CwTUwFdg6h7dYcBUpH5iUILCclAsa+MbCEvC9ZKu4dCHRlJqALuhjLw97pbQu62l4+wRwGeZqA==", + "license": "MIT", + "dependencies": { + "dotenv": "16.4.7", + "dotenv-expand": "12.0.1", + "lodash": "4.17.21" + }, + "peerDependencies": { + "@nestjs/common": "^10.0.0 || ^11.0.0", + "rxjs": "^7.1.0" + } + }, + "node_modules/@nestjs/config/node_modules/dotenv": { + "version": "16.4.7", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.7.tgz", + "integrity": "sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, "node_modules/@nestjs/core": { "version": "10.4.15", "resolved": "https://registry.npmjs.org/@nestjs/core/-/core-10.4.15.tgz", @@ -4376,6 +4404,21 @@ "url": "https://dotenvx.com" } }, + "node_modules/dotenv-expand": { + "version": "12.0.1", + "resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-12.0.1.tgz", + "integrity": "sha512-LaKRbou8gt0RNID/9RoI+J2rvXsBRPMV7p+ElHlPhcSARbCPDYcYG2s1TIzAfWv4YSgyY5taidWzzs31lNV3yQ==", + "license": "BSD-2-Clause", + "dependencies": { + "dotenv": "^16.4.5" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, "node_modules/dunder-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", @@ -7079,8 +7122,7 @@ "node_modules/lodash": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "dev": true + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" }, "node_modules/lodash.includes": { "version": "4.3.0", @@ -11479,6 +11521,23 @@ } } }, + "@nestjs/config": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@nestjs/config/-/config-4.0.2.tgz", + "integrity": "sha512-McMW6EXtpc8+CwTUwFdg6h7dYcBUpH5iUILCclAsa+MbCEvC9ZKu4dCHRlJqALuhjLw97pbQu62l4+wRwGeZqA==", + "requires": { + "dotenv": "16.4.7", + "dotenv-expand": "12.0.1", + "lodash": "4.17.21" + }, + "dependencies": { + "dotenv": { + "version": "16.4.7", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.7.tgz", + "integrity": "sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==" + } + } + }, "@nestjs/core": { "version": "10.4.15", "resolved": "https://registry.npmjs.org/@nestjs/core/-/core-10.4.15.tgz", @@ -13371,6 +13430,14 @@ "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.5.0.tgz", "integrity": "sha512-m/C+AwOAr9/W1UOIZUo232ejMNnJAJtYQjUbHoNTBNTJSvqzzDh7vnrei3o3r3m9blf6ZoDkvcw0VmozNRFJxg==" }, + "dotenv-expand": { + "version": "12.0.1", + "resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-12.0.1.tgz", + "integrity": "sha512-LaKRbou8gt0RNID/9RoI+J2rvXsBRPMV7p+ElHlPhcSARbCPDYcYG2s1TIzAfWv4YSgyY5taidWzzs31lNV3yQ==", + "requires": { + "dotenv": "^16.4.5" + } + }, "dunder-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", @@ -15295,8 +15362,7 @@ "lodash": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "dev": true + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" }, "lodash.includes": { "version": "4.3.0", diff --git a/package.json b/package.json index be4c167..a0e32ae 100644 --- a/package.json +++ b/package.json @@ -26,6 +26,7 @@ }, "dependencies": { "@nestjs/common": "^10.4.16", + "@nestjs/config": "^4.0.2", "@nestjs/core": "^10.4.15", "@nestjs/jwt": "^11.0.0", "@nestjs/mapped-types": "^2.1.0", diff --git a/src/app.module.ts b/src/app.module.ts index a1de42e..eda8cac 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -6,6 +6,9 @@ import { ArtistsModule } from './artists/artists.module'; import { AlbumsModule } from './albums/albums.module'; import { TracksModule } from './tracks/tracks.module'; import { FavoritesModule } from './favorites/favorites.module'; +import { ConfigModule } from '@nestjs/config' +import { TypeOrmModule } from '@nestjs/typeorm' + @Module({ imports: [ @@ -14,6 +17,19 @@ import { FavoritesModule } from './favorites/favorites.module'; AlbumsModule, TracksModule, FavoritesModule, + ConfigModule.forRoot({ + isGlobal: true, + }), + TypeOrmModule.forRoot({ + type: 'postgres', + host: process.env.DB_HOST, + port: parseInt(process.env.DB_PORT || '5432'), + username: process.env.DB_USERNAME || process.env.DB_USER, + password: process.env.DB_PASSWORD, + database: process.env.DB_NAME, + autoLoadEntities: true, + synchronize: true, + }) ], controllers: [AppController], providers: [AppService], diff --git a/src/users/entities/user.entity.ts b/src/users/entities/user.entity.ts index 642d656..f1760ab 100644 --- a/src/users/entities/user.entity.ts +++ b/src/users/entities/user.entity.ts @@ -1,8 +1,22 @@ +import { Column, CreateDateColumn, Entity, PrimaryGeneratedColumn, UpdateDateColumn, VersionColumn } from "typeorm"; + +@Entity('user') export class User { + @PrimaryGeneratedColumn('uuid') id: string; // uuid v4 + + @Column({ unique: true }) login: string; + + @Column() password: string; + + @VersionColumn() version: number; // integer number, increments on update + + @CreateDateColumn({ type: 'timestamptz'}) createdAt: number; // timestamp of creation + + @UpdateDateColumn({ type: 'timestamptz'}) updatedAt: number; // timestamp of last update } diff --git a/src/users/users.controller.ts b/src/users/users.controller.ts index 6ed7131..a58b031 100644 --- a/src/users/users.controller.ts +++ b/src/users/users.controller.ts @@ -23,9 +23,8 @@ export class UsersController { constructor(private readonly userService: UsersService) {} @Get() - getAll(@Res() res: Response) { - const users = this.userService.findAll(); - return res.status(HttpStatus.OK).json(users); + async getAll(): Promise[]> { + return this.userService.findAll(); } @Get(':id') diff --git a/src/users/users.module.ts b/src/users/users.module.ts index 82063a5..bb8dc88 100644 --- a/src/users/users.module.ts +++ b/src/users/users.module.ts @@ -1,10 +1,13 @@ import { Module } from '@nestjs/common'; import { UsersController } from './users.controller'; import { UsersService } from './users.service'; +import { TypeOrmModule } from '@nestjs/typeorm'; +import { User } from './entities/user.entity'; @Module({ - imports: [UsersModule], + imports: [TypeOrmModule.forFeature([User])], controllers: [UsersController], providers: [UsersService], + exports: [UsersService], }) export class UsersModule {} diff --git a/src/users/users.service.ts b/src/users/users.service.ts index b4e46fd..d2c0970 100644 --- a/src/users/users.service.ts +++ b/src/users/users.service.ts @@ -3,7 +3,8 @@ import { Injectable, NotFoundException, } from '@nestjs/common'; -import { db } from 'src/db'; +import { InjectRepository } from '@nestjs/typeorm'; +import { Repository } from 'typeorm'; import { CreateUserDto } from './dto/create-user.dto'; import { randomUUID } from 'crypto'; import { User } from './entities/user.entity'; @@ -11,55 +12,58 @@ import { UpdateUserDto } from './dto/update-user.dto'; @Injectable() export class UsersService { - create(createUserDto: CreateUserDto): Omit { - const newUser: User = { + constructor( + @InjectRepository(User) + private readonly userRepository: Repository, + ) {} + + async create(createUserDto: CreateUserDto): Promise> { + const newUser = this.userRepository.create({ id: randomUUID(), login: createUserDto.login, password: createUserDto.password, - version: 1, - createdAt: Date.now(), - updatedAt: Date.now(), - }; - db.users.push(newUser); - const safeUser = { ...newUser }; + }); + const saved = await this.userRepository.save(newUser); + const safeUser = { ...saved }; delete safeUser.password; return safeUser; } - findAll(): Omit[] { - return db.users.map((user) => { + async findAll(): Promise[]> { + const users = await this.userRepository.find(); + return users.map((user) => { delete user.password; return user; }); } - findById(id: string): Omit { - const user = db.users.find((user) => user.id === id); + async findById(id: string): Promise> { + const user = await this.userRepository.findOne({ where: { id } }); if (!user) throw new NotFoundException('User Not found'); const safeUser = { ...user }; delete safeUser.password; return safeUser; } - update(id: string, updateDto: UpdateUserDto) { - const user = db.users.find((user) => user.id === id); + async update(id: string, updateDto: UpdateUserDto): Promise> { + const user = await this.userRepository.findOne({ where: { id } }); if (!user) throw new NotFoundException('User Not found'); if (user.password !== updateDto.oldPassword) throw new ForbiddenException('The password is wrong!'); user.password = updateDto.newPassword; user.version++; - user.updatedAt = Date.now(); + await this.userRepository.save(user); const safeUser = { ...user }; delete safeUser.password; return safeUser; } - remove(id: string) { - const index = db.users.findIndex((user) => user.id === id); - if (index === -1) throw new NotFoundException('User not found'); - db.users.splice(index, 1); + async remove(id: string) { + const user = await this.userRepository.findOne({ where: { id } }); + if (!user) throw new NotFoundException('User not found'); + await this.userRepository.remove(user); return { message: 'User deleted' }; } } From 2ce2b1627e4957ffb28ccff2904e01ae23c7dbf1 Mon Sep 17 00:00:00 2001 From: Yulia Demir Date: Sun, 8 Jun 2025 07:51:34 +0300 Subject: [PATCH 16/32] feat: track module for ql --- src/db.ts | 2 +- src/tracks/entities/track.entity.ts | 22 +++++++++++++ src/tracks/tracks.controller.ts | 16 ++++----- src/tracks/tracks.module.ts | 4 ++- src/tracks/tracks.service.ts | 51 +++++++++++++++-------------- 5 files changed, 60 insertions(+), 35 deletions(-) diff --git a/src/db.ts b/src/db.ts index 70753ab..a32371b 100644 --- a/src/db.ts +++ b/src/db.ts @@ -3,7 +3,7 @@ import { Artist } from './artists/entities/artist.entity'; import { Track } from './tracks/entities/track.entity'; import { User } from './users/entities/user.entity'; -export const db = { +const db = { users: [] as User[], artists: [] as Artist[], albums: [] as Album[], diff --git a/src/tracks/entities/track.entity.ts b/src/tracks/entities/track.entity.ts index 0c0b6c0..da2c7c8 100644 --- a/src/tracks/entities/track.entity.ts +++ b/src/tracks/entities/track.entity.ts @@ -1,7 +1,29 @@ +import { Album } from "src/albums/entities/album.entity"; +import { Artist } from "src/artists/entities/artist.entity"; +import { Column, Entity, JoinColumn, ManyToOne, PrimaryGeneratedColumn } from "typeorm"; + +@Entity() export class Track { + @PrimaryGeneratedColumn('uuid') id: string; // uuid v4 + + @Column() name: string; + + @Column({ nullable: true }) artistId: string | null; // refers to Artist + + @Column({ nullable: true }) albumId: string | null; // refers to Album + + @Column('int') duration: number; // integer number + + @ManyToOne(() => Artist, (artist) => artist.tracks, { nullable: true, onDelete: 'SET NULL'}) + @JoinColumn({ name: 'artistId' }) + artist: Artist; + + @ManyToOne(() => Album, (album) => album.tracks, { nullable: true, onDelete: 'SET NULL'}) + @JoinColumn({ name: 'albumId' }) + album: Album; } diff --git a/src/tracks/tracks.controller.ts b/src/tracks/tracks.controller.ts index 7f10cf9..5a7a5fd 100644 --- a/src/tracks/tracks.controller.ts +++ b/src/tracks/tracks.controller.ts @@ -19,12 +19,12 @@ export class TracksController { constructor(private readonly tracksService: TracksService) {} @Get() - getAll() { + async getAll() { return this.tracksService.findAll(); } @Get(':id') - getById( + async getById( @Param( 'id', new ParseUUIDPipe({ @@ -33,17 +33,17 @@ export class TracksController { }), ) id: string, - ) { + ): Promise { return this.tracksService.findById(id); } @Post() - create(@Body() dto: CreateTrackDto) { + async create(@Body() dto: CreateTrackDto): Promise { return this.tracksService.create(dto); } @Put(':id') - update( + async update( @Param( 'id', new ParseUUIDPipe({ @@ -53,13 +53,13 @@ export class TracksController { ) id: string, @Body() dto: UpdateTrackDto, - ): Track { + ): Promise { return this.tracksService.update(id, dto); } @Delete(':id') @HttpCode(204) - remove( + async remove( @Param( 'id', new ParseUUIDPipe({ @@ -68,7 +68,7 @@ export class TracksController { }), ) id: string, - ): void { + ): Promise { this.tracksService.remove(id); } } diff --git a/src/tracks/tracks.module.ts b/src/tracks/tracks.module.ts index adb0ea9..712ab26 100644 --- a/src/tracks/tracks.module.ts +++ b/src/tracks/tracks.module.ts @@ -1,9 +1,11 @@ import { Module } from '@nestjs/common'; import { TracksController } from './tracks.controller'; import { TracksService } from './tracks.service'; +import { TypeOrmModule } from '@nestjs/typeorm'; +import { Track } from './entities/track.entity'; @Module({ - imports: [TracksModule], + imports: [TypeOrmModule.forFeature([Track])], controllers: [TracksController], providers: [TracksService], exports: [TracksService], diff --git a/src/tracks/tracks.service.ts b/src/tracks/tracks.service.ts index 45dd071..46b9f80 100644 --- a/src/tracks/tracks.service.ts +++ b/src/tracks/tracks.service.ts @@ -4,45 +4,46 @@ import { Track } from './entities/track.entity'; import { randomUUID } from 'crypto'; import { db } from 'src/db'; import { UpdateTrackDto } from './dto/update-track.dto'; +import { InjectRepository } from '@nestjs/typeorm'; +import { Repository } from 'typeorm'; @Injectable() export class TracksService { - create(createTrackDto: CreateTrackDto): Track { - const newTrack: Track = { - id: randomUUID(), - name: createTrackDto.name, - artistId: createTrackDto.artistId, - albumId: createTrackDto.albumId, - duration: createTrackDto.duration, - }; - db.tracks.push(newTrack); - return newTrack; + constructor( + @InjectRepository(Track) + private readonly trackRepository: Repository, + ) {} + + async create(createTrackDto: CreateTrackDto): Promise { + const newTrack: Track = this.trackRepository.create(createTrackDto); + return await this.trackRepository.save(newTrack); } - findAll(): Track[] { - return db.tracks; + async findAll(): Promise { + return await this.trackRepository.find({ relations: ['artist', 'album'] }); } - findById(id: string): Track { - const track = db.tracks.find((track) => track.id === id); + async findById(id: string): Promise { + const track = await this.trackRepository.findOne({ + where: { id }, + relations: ['artist', 'album'], + }); if (!track) throw new NotFoundException('track Not found'); return track; } - update(id: string, updateDto: UpdateTrackDto) { - const track = db.tracks.find((track) => track.id === id); + async update(id: string, updateDto: UpdateTrackDto): Promise { + const track = await this.trackRepository.preload({ + id, + ...updateDto, + }); if (!track) throw new NotFoundException('track Not found'); - track.name = updateDto.name; - track.artistId = updateDto.artistId; - track.albumId = updateDto.albumId; - track.duration = updateDto.duration; - return track; + return await this.trackRepository.save(track); } - remove(id: string) { - const index = db.tracks.findIndex((track) => track.id === id); - if (index === -1) throw new NotFoundException('track not found'); - db.tracks.splice(index, 1); + async remove(id: string) { + const result = await this.trackRepository.delete(id); + if (result.affected === 0) throw new NotFoundException('track not found'); return { message: 'track deleted' }; } } From 6f3fa245589c6c7b862ac0c716b85d88e6a2fed7 Mon Sep 17 00:00:00 2001 From: Yulia Demir Date: Sun, 8 Jun 2025 09:33:01 +0300 Subject: [PATCH 17/32] feat: artist module for ql --- src/artists/artists.controller.ts | 14 +++---- src/artists/artists.module.ts | 4 +- src/artists/artists.service.ts | 56 +++++++++++---------------- src/artists/entities/artist.entity.ts | 12 ++++++ 4 files changed, 45 insertions(+), 41 deletions(-) diff --git a/src/artists/artists.controller.ts b/src/artists/artists.controller.ts index c8bae6a..ae40e59 100644 --- a/src/artists/artists.controller.ts +++ b/src/artists/artists.controller.ts @@ -19,12 +19,12 @@ export class ArtistsController { constructor(private readonly artistsService: ArtistsService) {} @Get() - getAll() { + async getAll() { return this.artistsService.findAll(); } @Get(':id') - getById( + async getById( @Param( 'id', new ParseUUIDPipe({ @@ -38,12 +38,12 @@ export class ArtistsController { } @Post() - create(@Body() dto: CreateArtistDto) { + async create(@Body() dto: CreateArtistDto) { return this.artistsService.create(dto); } @Put(':id') - update( + async update( @Param( 'id', new ParseUUIDPipe({ @@ -53,13 +53,13 @@ export class ArtistsController { ) id: string, @Body() dto: UpdateArtistDto, - ): Artist { + ): Promise { return this.artistsService.update(id, dto); } @Delete(':id') @HttpCode(204) - remove( + async remove( @Param( 'id', new ParseUUIDPipe({ @@ -68,7 +68,7 @@ export class ArtistsController { }), ) id: string, - ): void { + ): Promise { this.artistsService.remove(id); } } diff --git a/src/artists/artists.module.ts b/src/artists/artists.module.ts index 18904b8..1fb7f22 100644 --- a/src/artists/artists.module.ts +++ b/src/artists/artists.module.ts @@ -1,9 +1,11 @@ import { Module } from '@nestjs/common'; import { ArtistsController } from './artists.controller'; import { ArtistsService } from './artists.service'; +import { TypeOrmModule } from '@nestjs/typeorm'; +import { Artist } from './entities/artist.entity'; @Module({ - imports: [ArtistsModule], + imports: [TypeOrmModule.forFeature([Artist])], controllers: [ArtistsController], providers: [ArtistsService], exports: [ArtistsService], diff --git a/src/artists/artists.service.ts b/src/artists/artists.service.ts index 1277f15..affc44a 100644 --- a/src/artists/artists.service.ts +++ b/src/artists/artists.service.ts @@ -2,56 +2,46 @@ import { Injectable, NotFoundException } from '@nestjs/common'; import { CreateArtistDto } from './dto/create-artist.dto'; import { Artist } from './entities/artist.entity'; import { randomUUID } from 'crypto'; -import { db } from 'src/db'; import { UpdateArtistDto } from './dto/update-artist.dto'; +import { InjectRepository } from '@nestjs/typeorm'; +import { Repository } from 'typeorm'; @Injectable() export class ArtistsService { - create(dto: CreateArtistDto): Artist { - const newArtist: Artist = { - id: randomUUID(), - name: dto.name, - grammy: dto.grammy, - }; - db.artists.push(newArtist); - return newArtist; + constructor( + @InjectRepository(Artist) + private readonly artistRepository: Repository, + ) {} + + async create(dto: CreateArtistDto): Promise { + const newArtist = this.artistRepository.create(dto); + return await this.artistRepository.save(newArtist); } - findAll(): Artist[] { - return db.artists; + async findAll(): Promise { + return await this.artistRepository.find(); } - findById(id: string): Artist { - const artist = db.artists.find((artist) => artist.id === id); + async findById(id: string): Promise { + const artist = await this.artistRepository.findOneBy({ id }); if (!artist) throw new NotFoundException('artist Not found'); return artist; } - update(id: string, dto: UpdateArtistDto) { - const artist = db.artists.find((artist) => artist.id === id); + async update(id: string, dto: UpdateArtistDto) { + const artist = await this.artistRepository.preload({ + id, + ...dto, + }); if (!artist) throw new NotFoundException('artist Not found'); artist.name = dto.name; artist.grammy = dto.grammy; - return artist; + return await this.artistRepository.save(artist); } - remove(id: string) { - const index = db.artists.findIndex((artist) => artist.id === id); - if (index === -1) throw new NotFoundException('artist not found'); - - db.tracks.forEach((track) => { - if (track.artistId === id) { - track.artistId = null; - } - }); - - db.albums.forEach((album) => { - if (album.artistId === id) { - album.artistId = null; - } - }); - - db.artists.splice(index, 1); + async remove(id: string) { + const result = await this.artistRepository.delete(id); + if (result.affected === 0) throw new NotFoundException('artist not found'); return { message: 'artist deleted' }; } } diff --git a/src/artists/entities/artist.entity.ts b/src/artists/entities/artist.entity.ts index a9b57ad..f07163c 100644 --- a/src/artists/entities/artist.entity.ts +++ b/src/artists/entities/artist.entity.ts @@ -1,5 +1,17 @@ +import { Track } from "src/tracks/entities/track.entity"; +import { Column, Entity, OneToMany, PrimaryGeneratedColumn } from "typeorm"; + +@Entity() export class Artist { + @PrimaryGeneratedColumn('uuid') id: string; // uuid v4 + + @Column() name: string; + + @Column() grammy: boolean; + + @OneToMany(() => Track, (track) => track.artist) + tracks: Track[]; } From 851d5c2c7286b242d227b470dcba7f153e368569 Mon Sep 17 00:00:00 2001 From: Yulia Demir Date: Sun, 8 Jun 2025 09:40:53 +0300 Subject: [PATCH 18/32] feat: album module for ql --- src/albums/albums.controller.ts | 14 ++++---- src/albums/albums.module.ts | 4 ++- src/albums/albums.service.ts | 51 +++++++++++++---------------- src/albums/entities/album.entity.ts | 19 +++++++++++ 4 files changed, 52 insertions(+), 36 deletions(-) diff --git a/src/albums/albums.controller.ts b/src/albums/albums.controller.ts index f20d098..2a84a49 100644 --- a/src/albums/albums.controller.ts +++ b/src/albums/albums.controller.ts @@ -19,12 +19,12 @@ export class AlbumsController { constructor(private readonly albumsService: AlbumsService) {} @Get() - getAll() { + async getAll() { return this.albumsService.findAll(); } @Get(':id') - getById( + async getById( @Param( 'id', new ParseUUIDPipe({ @@ -38,12 +38,12 @@ export class AlbumsController { } @Post() - create(@Body() dto: CreateAlbumDto) { + async create(@Body() dto: CreateAlbumDto) { return this.albumsService.create(dto); } @Put(':id') - update( + async update( @Param( 'id', new ParseUUIDPipe({ @@ -53,13 +53,13 @@ export class AlbumsController { ) id: string, @Body() dto: UpdateAlbumDto, - ): Album { + ): Promise { return this.albumsService.update(id, dto); } @Delete(':id') @HttpCode(204) - remove( + async remove( @Param( 'id', new ParseUUIDPipe({ @@ -68,7 +68,7 @@ export class AlbumsController { }), ) id: string, - ): void { + ): Promise { this.albumsService.remove(id); } } diff --git a/src/albums/albums.module.ts b/src/albums/albums.module.ts index f2acb0f..23a0a57 100644 --- a/src/albums/albums.module.ts +++ b/src/albums/albums.module.ts @@ -1,9 +1,11 @@ import { Module } from '@nestjs/common'; import { AlbumsController } from './albums.controller'; import { AlbumsService } from './albums.service'; +import { TypeOrmModule } from '@nestjs/typeorm'; +import { Album } from './entities/album.entity'; @Module({ - imports: [AlbumsModule], + imports: [TypeOrmModule.forFeature([Album])], controllers: [AlbumsController], providers: [AlbumsService], exports: [AlbumsService], diff --git a/src/albums/albums.service.ts b/src/albums/albums.service.ts index 65db12a..281edbb 100644 --- a/src/albums/albums.service.ts +++ b/src/albums/albums.service.ts @@ -1,53 +1,48 @@ import { Injectable, NotFoundException } from '@nestjs/common'; import { randomUUID } from 'crypto'; import { UpdateAlbumDto } from 'src/albums/dto/update-album.dto'; -import { db } from 'src/db'; import { CreateAlbumDto } from './dto/create-album.dto'; import { Album } from './entities/album.entity'; +import { InjectRepository } from '@nestjs/typeorm'; +import { Repository } from 'typeorm'; @Injectable() export class AlbumsService { - create(dto: CreateAlbumDto): Album { - const newAlbum: Album = { - id: randomUUID(), - name: dto.name, - year: dto.year, - artistId: dto.artistId, - }; - db.albums.push(newAlbum); - return newAlbum; + constructor( + @InjectRepository(Album) + private readonly albumRepository: Repository, + ) {} + + async create(dto: CreateAlbumDto): Promise { + const newAlbum = this.albumRepository.create(dto); + return await this.albumRepository.save(newAlbum); } - findAll(): Album[] { - return db.albums; + async findAll(): Promise { + return await this.albumRepository.find(); } - findById(id: string): Album { - const album = db.albums.find((album) => album.id === id); + async findById(id: string): Promise { + const album = await this.albumRepository.findOneBy({ id }); if (!album) throw new NotFoundException('album Not found'); return album; } - update(id: string, dto: UpdateAlbumDto) { - const album = db.albums.find((album) => album.id === id); + async update(id: string, dto: UpdateAlbumDto) { + const album = await this.albumRepository.preload({ + id, + ...dto, + }); if (!album) throw new NotFoundException('album Not found'); album.name = dto.name; album.year = dto.year; album.artistId = dto.artistId; - return album; + return await this.albumRepository.save(album); } - remove(id: string) { - const index = db.albums.findIndex((album) => album.id === id); - if (index === -1) throw new NotFoundException('album not found'); - - db.tracks.forEach((track) => { - if (track.albumId === id) { - track.albumId = null; - } - }); - - db.albums.splice(index, 1); + async remove(id: string) { + const result = await this.albumRepository.delete(id); + if (result.affected === 0) throw new NotFoundException('album not found'); return { message: 'album deleted' }; } } diff --git a/src/albums/entities/album.entity.ts b/src/albums/entities/album.entity.ts index b74349b..29aee09 100644 --- a/src/albums/entities/album.entity.ts +++ b/src/albums/entities/album.entity.ts @@ -1,6 +1,25 @@ +import { Artist } from "src/artists/entities/artist.entity"; +import { Track } from "src/tracks/entities/track.entity"; +import { Column, Entity, JoinColumn, ManyToOne, OneToMany, PrimaryGeneratedColumn } from "typeorm"; + +@Entity() export class Album { + @PrimaryGeneratedColumn('uuid') id: string; // uuid v4 + + @Column() name: string; + + @Column('int') year: number; + + @Column({ nullable: true }) artistId: string | null; // refers to Artist + + @ManyToOne(() => Artist, (artist) => artist.albums, { nullable: true, onDelete: 'SET NULL' }) + @JoinColumn({ name: 'artistId' }) + artist: Artist; + + @OneToMany(() => Track, (track) => track.album) + tracks: Track[]; } From 328086c904d8439a274b2a4cab5ad620fe32aacd Mon Sep 17 00:00:00 2001 From: Yulia Demir Date: Sun, 8 Jun 2025 12:00:28 +0300 Subject: [PATCH 19/32] feat: favorite module --- src/favorites/entities/favorites.entity.ts | 28 +++++- src/favorites/favorites.module.ts | 10 +- src/favorites/favorites.service.ts | 106 +++++++++++++++------ src/users/entities/user.entity.ts | 6 +- 4 files changed, 113 insertions(+), 37 deletions(-) diff --git a/src/favorites/entities/favorites.entity.ts b/src/favorites/entities/favorites.entity.ts index 1a37677..ce341e8 100644 --- a/src/favorites/entities/favorites.entity.ts +++ b/src/favorites/entities/favorites.entity.ts @@ -1,5 +1,27 @@ +import { Album } from "src/albums/entities/album.entity"; +import { Artist } from "src/artists/entities/artist.entity"; +import { Track } from "src/tracks/entities/track.entity"; +import { User } from "src/users/entities/user.entity"; +import { Entity, JoinColumn, JoinTable, ManyToMany, OneToOne, PrimaryColumn, PrimaryGeneratedColumn } from "typeorm"; + +@Entity() export class Favorites { - artists: Set; // favorite artists ids - albums: Set; // favorite albums ids - tracks: Set; // favorite tracks ids + @PrimaryGeneratedColumn('uuid') + id: string; + + @OneToOne(() => User, { onDelete: 'CASCADE' }) + @JoinColumn({ name: 'userId' }) + user: User; + + @ManyToMany(() => Track, { eager: true }) + @JoinTable() + tracks: Track[]; + + @ManyToMany(() => Album, { eager: true }) + @JoinTable() + albums: Album[]; + + @ManyToMany(() => Artist, { eager: true }) + @JoinTable() + artists: Artist[]; } diff --git a/src/favorites/favorites.module.ts b/src/favorites/favorites.module.ts index 6e6800c..942add3 100644 --- a/src/favorites/favorites.module.ts +++ b/src/favorites/favorites.module.ts @@ -1,12 +1,14 @@ import { Module } from '@nestjs/common'; import { FavoritesController } from './favorites.controller'; import { FavoritesService } from './favorites.service'; -import { ArtistsModule } from 'src/artists/artists.module'; -import { TracksModule } from 'src/tracks/tracks.module'; -import { AlbumsModule } from 'src/albums/albums.module'; +import { TypeOrmModule } from '@nestjs/typeorm'; +import { Favorites } from './entities/favorites.entity'; +import { Artist } from 'src/artists/entities/artist.entity'; +import { Track } from 'src/tracks/entities/track.entity'; +import { Album } from 'src/albums/entities/album.entity'; @Module({ - imports: [FavoritesModule, ArtistsModule, TracksModule, AlbumsModule], + imports: [TypeOrmModule.forFeature([Favorites, Artist, Track, Album])], controllers: [FavoritesController], providers: [FavoritesService], }) diff --git a/src/favorites/favorites.service.ts b/src/favorites/favorites.service.ts index b3f0680..e75aa50 100644 --- a/src/favorites/favorites.service.ts +++ b/src/favorites/favorites.service.ts @@ -3,60 +3,108 @@ import { NotFoundException, UnprocessableEntityException, } from '@nestjs/common'; -import { db } from 'src/db'; import { Track } from 'src/tracks/entities/track.entity'; import { Artist } from 'src/artists/entities/artist.entity'; import { Album } from 'src/albums/entities/album.entity'; +import { Favorites } from './entities/favorites.entity'; +import { InjectRepository } from '@nestjs/typeorm'; +import { Repository } from 'typeorm'; @Injectable() export class FavoritesService { - findAll(): { artists: Artist[]; albums: Album[]; tracks: Track[] } { + constructor( + @InjectRepository(Favorites) + private readonly favoritesRepository: Repository, + + @InjectRepository(Track) + private readonly trackRepository: Repository, + + @InjectRepository(Album) + private readonly albumRepository: Repository, + + @InjectRepository(Artist) + private readonly artistRepository: Repository, + ) {} + + private async getFavorites(): Promise { + let favorites = await this.favoritesRepository.findOne(); + if (!favorites) { + favorites = this.favoritesRepository.create({ + tracks: [], + albums: [], + artists: [], + }); + await this.favoritesRepository.save(favorites); + } + return favorites; + } + + async findAll() { + const fav = await this.getFavorites(); return { - artists: Array.from(db.favorites.artists) - .map((artistId) => db.artists.find((artist) => artist?.id === artistId)) - .filter((a): a is Artist => a !== undefined), - albums: Array.from(db.favorites.albums) - .map((albumId) => db.albums.find((album) => album?.id === albumId)) - .filter((a): a is Album => a !== undefined), - tracks: Array.from(db.favorites.tracks) - .map((trackId) => db.tracks.find((track) => track?.id === trackId)) - .filter((a): a is Track => a !== undefined), + artists: fav.artists, + albums: fav.albums, + tracks: fav.tracks, }; } - addTrack(id: string): Track { - const track = db.tracks.find((track) => track.id === id); + async addTrack(id: string): Promise { + const track = await this.trackRepository.findOneBy({ id }); if (!track) throw new UnprocessableEntityException('track Not found'); - db.favorites.tracks.add(track.id); + + const fav = await this.getFavorites(); + if (!fav.tracks.some((t) => t.id === id)) { + fav.tracks.push(track); + await this.favoritesRepository.save(fav); + } return track; } - removeTrack(id: string): void { - const removed = db.favorites.tracks.delete(id); - if (!removed) throw new NotFoundException('track Not found'); + async removeTrack(id: string): Promise { + const fav = await this.getFavorites(); + const index= fav.tracks.findIndex((t) => t.id === id); + if (index === -1) throw new NotFoundException('track Not found'); + fav.tracks.splice(index, 1); + await this.favoritesRepository.save(fav); } - addArtist(id: string): Artist { - const artist = db.artists.find((artist) => artist.id === id); + async addArtist(id: string): Promise { + const artist = await this.artistRepository.findOneBy({ id }); if (!artist) throw new UnprocessableEntityException('artist Not found'); - db.favorites.artists.add(artist.id); + + const fav = await this.getFavorites(); + if (!fav.artists.some((a) => a.id === id)) { + fav.artists.push(artist); + await this.favoritesRepository.save(fav); + } return artist; } - removeArtist(id: string): void { - const removed = db.favorites.artists.delete(id); - if (!removed) throw new NotFoundException('artist Not found'); + async removeArtist(id: string): Promise { + const fav = await this.getFavorites(); + const index= fav.artists.findIndex((t) => t.id === id); + if (index === -1) throw new NotFoundException('artist Not found'); + fav.artists.splice(index, 1); + await this.favoritesRepository.save(fav); } - addAlbum(id: string): Album { - const album = db.albums.find((album) => album.id === id); + async addAlbum(id: string): Promise { + const album = await this.albumRepository.findOneBy({ id }); if (!album) throw new UnprocessableEntityException('album Not found'); - db.favorites.albums.add(album.id); + + const fav = await this.getFavorites(); + if (!fav.albums.some((a) => a.id === id)) { + fav.albums.push(album); + await this.favoritesRepository.save(fav); + } return album; } - removeAlbum(id: string): void { - const removed = db.favorites.albums.delete(id); - if (!removed) throw new NotFoundException('album Not found'); + async removeAlbum(id: string): Promise { + const fav = await this.getFavorites(); + const index= fav.albums.findIndex((t) => t.id === id); + if (index === -1) throw new NotFoundException('album Not found'); + fav.albums.splice(index, 1); + await this.favoritesRepository.save(fav); } } diff --git a/src/users/entities/user.entity.ts b/src/users/entities/user.entity.ts index f1760ab..ad158ce 100644 --- a/src/users/entities/user.entity.ts +++ b/src/users/entities/user.entity.ts @@ -1,4 +1,5 @@ -import { Column, CreateDateColumn, Entity, PrimaryGeneratedColumn, UpdateDateColumn, VersionColumn } from "typeorm"; +import { Favorites } from "src/favorites/entities/favorites.entity"; +import { Column, CreateDateColumn, Entity, OneToOne, PrimaryGeneratedColumn, UpdateDateColumn, VersionColumn } from "typeorm"; @Entity('user') export class User { @@ -19,4 +20,7 @@ export class User { @UpdateDateColumn({ type: 'timestamptz'}) updatedAt: number; // timestamp of last update + + @OneToOne(() => Favorites, (favorite) => favorite.user) + favorite: Favorites; } From 7a0eb0e8cfb3ff4061465e8c04fa7a52fe0cc59e Mon Sep 17 00:00:00 2001 From: Yulia Demir Date: Sun, 8 Jun 2025 16:51:20 +0300 Subject: [PATCH 20/32] fix: lint problems --- src/albums/albums.service.ts | 9 ++++----- src/albums/entities/album.entity.ts | 15 +++++++++++---- src/app.module.ts | 7 +++---- src/artists/artists.service.ts | 1 - src/artists/entities/artist.entity.ts | 4 ++-- src/db.ts | 16 ---------------- src/favorites/entities/favorites.entity.ts | 17 ++++++++++++----- src/favorites/favorites.service.ts | 20 ++++++++++---------- src/tracks/entities/track.entity.ts | 22 +++++++++++++++++----- src/tracks/tracks.service.ts | 2 -- src/users/entities/user.entity.ts | 16 ++++++++++++---- src/users/users.controller.ts | 2 +- src/users/users.service.ts | 7 +++++-- 13 files changed, 77 insertions(+), 61 deletions(-) delete mode 100644 src/db.ts diff --git a/src/albums/albums.service.ts b/src/albums/albums.service.ts index 281edbb..97154f9 100644 --- a/src/albums/albums.service.ts +++ b/src/albums/albums.service.ts @@ -1,5 +1,4 @@ import { Injectable, NotFoundException } from '@nestjs/common'; -import { randomUUID } from 'crypto'; import { UpdateAlbumDto } from 'src/albums/dto/update-album.dto'; import { CreateAlbumDto } from './dto/create-album.dto'; import { Album } from './entities/album.entity'; @@ -9,10 +8,10 @@ import { Repository } from 'typeorm'; @Injectable() export class AlbumsService { constructor( - @InjectRepository(Album) - private readonly albumRepository: Repository, - ) {} - + @InjectRepository(Album) + private readonly albumRepository: Repository, + ) {} + async create(dto: CreateAlbumDto): Promise { const newAlbum = this.albumRepository.create(dto); return await this.albumRepository.save(newAlbum); diff --git a/src/albums/entities/album.entity.ts b/src/albums/entities/album.entity.ts index 29aee09..46c88ac 100644 --- a/src/albums/entities/album.entity.ts +++ b/src/albums/entities/album.entity.ts @@ -1,6 +1,13 @@ -import { Artist } from "src/artists/entities/artist.entity"; -import { Track } from "src/tracks/entities/track.entity"; -import { Column, Entity, JoinColumn, ManyToOne, OneToMany, PrimaryGeneratedColumn } from "typeorm"; +import { Artist } from 'src/artists/entities/artist.entity'; +import { Track } from 'src/tracks/entities/track.entity'; +import { + Column, + Entity, + JoinColumn, + ManyToOne, + OneToMany, + PrimaryGeneratedColumn, +} from 'typeorm'; @Entity() export class Album { @@ -16,7 +23,7 @@ export class Album { @Column({ nullable: true }) artistId: string | null; // refers to Artist - @ManyToOne(() => Artist, (artist) => artist.albums, { nullable: true, onDelete: 'SET NULL' }) + @ManyToOne(() => Artist, { nullable: true, onDelete: 'SET NULL' }) @JoinColumn({ name: 'artistId' }) artist: Artist; diff --git a/src/app.module.ts b/src/app.module.ts index eda8cac..c7638f0 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -6,9 +6,8 @@ import { ArtistsModule } from './artists/artists.module'; import { AlbumsModule } from './albums/albums.module'; import { TracksModule } from './tracks/tracks.module'; import { FavoritesModule } from './favorites/favorites.module'; -import { ConfigModule } from '@nestjs/config' -import { TypeOrmModule } from '@nestjs/typeorm' - +import { ConfigModule } from '@nestjs/config'; +import { TypeOrmModule } from '@nestjs/typeorm'; @Module({ imports: [ @@ -29,7 +28,7 @@ import { TypeOrmModule } from '@nestjs/typeorm' database: process.env.DB_NAME, autoLoadEntities: true, synchronize: true, - }) + }), ], controllers: [AppController], providers: [AppService], diff --git a/src/artists/artists.service.ts b/src/artists/artists.service.ts index affc44a..4eb8de5 100644 --- a/src/artists/artists.service.ts +++ b/src/artists/artists.service.ts @@ -1,7 +1,6 @@ import { Injectable, NotFoundException } from '@nestjs/common'; import { CreateArtistDto } from './dto/create-artist.dto'; import { Artist } from './entities/artist.entity'; -import { randomUUID } from 'crypto'; import { UpdateArtistDto } from './dto/update-artist.dto'; import { InjectRepository } from '@nestjs/typeorm'; import { Repository } from 'typeorm'; diff --git a/src/artists/entities/artist.entity.ts b/src/artists/entities/artist.entity.ts index f07163c..6e401f7 100644 --- a/src/artists/entities/artist.entity.ts +++ b/src/artists/entities/artist.entity.ts @@ -1,5 +1,5 @@ -import { Track } from "src/tracks/entities/track.entity"; -import { Column, Entity, OneToMany, PrimaryGeneratedColumn } from "typeorm"; +import { Track } from 'src/tracks/entities/track.entity'; +import { Column, Entity, OneToMany, PrimaryGeneratedColumn } from 'typeorm'; @Entity() export class Artist { diff --git a/src/db.ts b/src/db.ts deleted file mode 100644 index a32371b..0000000 --- a/src/db.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { Album } from './albums/entities/album.entity'; -import { Artist } from './artists/entities/artist.entity'; -import { Track } from './tracks/entities/track.entity'; -import { User } from './users/entities/user.entity'; - -const db = { - users: [] as User[], - artists: [] as Artist[], - albums: [] as Album[], - tracks: [] as Track[], - favorites: { - artists: new Set(), - albums: new Set(), - tracks: new Set(), - }, -}; diff --git a/src/favorites/entities/favorites.entity.ts b/src/favorites/entities/favorites.entity.ts index ce341e8..0dc2fa5 100644 --- a/src/favorites/entities/favorites.entity.ts +++ b/src/favorites/entities/favorites.entity.ts @@ -1,8 +1,15 @@ -import { Album } from "src/albums/entities/album.entity"; -import { Artist } from "src/artists/entities/artist.entity"; -import { Track } from "src/tracks/entities/track.entity"; -import { User } from "src/users/entities/user.entity"; -import { Entity, JoinColumn, JoinTable, ManyToMany, OneToOne, PrimaryColumn, PrimaryGeneratedColumn } from "typeorm"; +import { Album } from 'src/albums/entities/album.entity'; +import { Artist } from 'src/artists/entities/artist.entity'; +import { Track } from 'src/tracks/entities/track.entity'; +import { User } from 'src/users/entities/user.entity'; +import { + Entity, + JoinColumn, + JoinTable, + ManyToMany, + OneToOne, + PrimaryGeneratedColumn, +} from 'typeorm'; @Entity() export class Favorites { diff --git a/src/favorites/favorites.service.ts b/src/favorites/favorites.service.ts index e75aa50..a85bc57 100644 --- a/src/favorites/favorites.service.ts +++ b/src/favorites/favorites.service.ts @@ -14,20 +14,20 @@ import { Repository } from 'typeorm'; export class FavoritesService { constructor( @InjectRepository(Favorites) - private readonly favoritesRepository: Repository, + private readonly favoritesRepository: Repository, @InjectRepository(Track) - private readonly trackRepository: Repository, + private readonly trackRepository: Repository, @InjectRepository(Album) - private readonly albumRepository: Repository, + private readonly albumRepository: Repository, @InjectRepository(Artist) - private readonly artistRepository: Repository, - ) {} + private readonly artistRepository: Repository, + ) {} private async getFavorites(): Promise { - let favorites = await this.favoritesRepository.findOne(); + let favorites = await this.favoritesRepository.findOne({ where: {} }); if (!favorites) { favorites = this.favoritesRepository.create({ tracks: [], @@ -62,7 +62,7 @@ export class FavoritesService { async removeTrack(id: string): Promise { const fav = await this.getFavorites(); - const index= fav.tracks.findIndex((t) => t.id === id); + const index = fav.tracks.findIndex((t) => t.id === id); if (index === -1) throw new NotFoundException('track Not found'); fav.tracks.splice(index, 1); await this.favoritesRepository.save(fav); @@ -80,9 +80,9 @@ export class FavoritesService { return artist; } - async removeArtist(id: string): Promise { + async removeArtist(id: string): Promise { const fav = await this.getFavorites(); - const index= fav.artists.findIndex((t) => t.id === id); + const index = fav.artists.findIndex((t) => t.id === id); if (index === -1) throw new NotFoundException('artist Not found'); fav.artists.splice(index, 1); await this.favoritesRepository.save(fav); @@ -102,7 +102,7 @@ export class FavoritesService { async removeAlbum(id: string): Promise { const fav = await this.getFavorites(); - const index= fav.albums.findIndex((t) => t.id === id); + const index = fav.albums.findIndex((t) => t.id === id); if (index === -1) throw new NotFoundException('album Not found'); fav.albums.splice(index, 1); await this.favoritesRepository.save(fav); diff --git a/src/tracks/entities/track.entity.ts b/src/tracks/entities/track.entity.ts index da2c7c8..f2ae6e7 100644 --- a/src/tracks/entities/track.entity.ts +++ b/src/tracks/entities/track.entity.ts @@ -1,6 +1,12 @@ -import { Album } from "src/albums/entities/album.entity"; -import { Artist } from "src/artists/entities/artist.entity"; -import { Column, Entity, JoinColumn, ManyToOne, PrimaryGeneratedColumn } from "typeorm"; +import { Album } from 'src/albums/entities/album.entity'; +import { Artist } from 'src/artists/entities/artist.entity'; +import { + Column, + Entity, + JoinColumn, + ManyToOne, + PrimaryGeneratedColumn, +} from 'typeorm'; @Entity() export class Track { @@ -19,11 +25,17 @@ export class Track { @Column('int') duration: number; // integer number - @ManyToOne(() => Artist, (artist) => artist.tracks, { nullable: true, onDelete: 'SET NULL'}) + @ManyToOne(() => Artist, (artist) => artist.tracks, { + nullable: true, + onDelete: 'SET NULL', + }) @JoinColumn({ name: 'artistId' }) artist: Artist; - @ManyToOne(() => Album, (album) => album.tracks, { nullable: true, onDelete: 'SET NULL'}) + @ManyToOne(() => Album, (album) => album.tracks, { + nullable: true, + onDelete: 'SET NULL', + }) @JoinColumn({ name: 'albumId' }) album: Album; } diff --git a/src/tracks/tracks.service.ts b/src/tracks/tracks.service.ts index 46b9f80..a516246 100644 --- a/src/tracks/tracks.service.ts +++ b/src/tracks/tracks.service.ts @@ -1,8 +1,6 @@ import { Injectable, NotFoundException } from '@nestjs/common'; import { CreateTrackDto } from './dto/create-track.dto'; import { Track } from './entities/track.entity'; -import { randomUUID } from 'crypto'; -import { db } from 'src/db'; import { UpdateTrackDto } from './dto/update-track.dto'; import { InjectRepository } from '@nestjs/typeorm'; import { Repository } from 'typeorm'; diff --git a/src/users/entities/user.entity.ts b/src/users/entities/user.entity.ts index ad158ce..8d9d0a9 100644 --- a/src/users/entities/user.entity.ts +++ b/src/users/entities/user.entity.ts @@ -1,5 +1,13 @@ -import { Favorites } from "src/favorites/entities/favorites.entity"; -import { Column, CreateDateColumn, Entity, OneToOne, PrimaryGeneratedColumn, UpdateDateColumn, VersionColumn } from "typeorm"; +import { Favorites } from 'src/favorites/entities/favorites.entity'; +import { + Column, + CreateDateColumn, + Entity, + OneToOne, + PrimaryGeneratedColumn, + UpdateDateColumn, + VersionColumn, +} from 'typeorm'; @Entity('user') export class User { @@ -15,10 +23,10 @@ export class User { @VersionColumn() version: number; // integer number, increments on update - @CreateDateColumn({ type: 'timestamptz'}) + @CreateDateColumn({ type: 'timestamptz' }) createdAt: number; // timestamp of creation - @UpdateDateColumn({ type: 'timestamptz'}) + @UpdateDateColumn({ type: 'timestamptz' }) updatedAt: number; // timestamp of last update @OneToOne(() => Favorites, (favorite) => favorite.user) diff --git a/src/users/users.controller.ts b/src/users/users.controller.ts index a58b031..6fc06fe 100644 --- a/src/users/users.controller.ts +++ b/src/users/users.controller.ts @@ -65,7 +65,7 @@ export class UsersController { ) id: string, @Body() dto: UpdateUserDto, - ): Omit { + ): Promise> { return this.userService.update(id, dto); } diff --git a/src/users/users.service.ts b/src/users/users.service.ts index d2c0970..bdd411c 100644 --- a/src/users/users.service.ts +++ b/src/users/users.service.ts @@ -37,7 +37,7 @@ export class UsersService { }); } - async findById(id: string): Promise> { + async findById(id: string): Promise> { const user = await this.userRepository.findOne({ where: { id } }); if (!user) throw new NotFoundException('User Not found'); const safeUser = { ...user }; @@ -45,7 +45,10 @@ export class UsersService { return safeUser; } - async update(id: string, updateDto: UpdateUserDto): Promise> { + async update( + id: string, + updateDto: UpdateUserDto, + ): Promise> { const user = await this.userRepository.findOne({ where: { id } }); if (!user) throw new NotFoundException('User Not found'); if (user.password !== updateDto.oldPassword) From fda5f173a4aeb19bede21d9873936e24d0a2e7b1 Mon Sep 17 00:00:00 2001 From: Yulia Demir Date: Mon, 9 Jun 2025 16:39:09 +0300 Subject: [PATCH 21/32] fix: a few problems --- Dockerfile | 2 +- docker-compose.yml | 4 ++- src/albums/albums.controller.ts | 12 ++++----- src/albums/albums.service.ts | 2 +- src/artists/artists.controller.ts | 2 +- src/artists/artists.service.ts | 3 ++- src/artists/entities/artist.entity.ts | 2 +- src/favorites/favorites.controller.ts | 28 ++++++++++---------- src/favorites/favorites.module.ts | 5 +++- src/main.ts | 10 ++++++- src/tracks/tracks.controller.ts | 2 +- src/users/dto/return-user.dto.ts | 6 ++--- src/users/entities/user.entity.ts | 23 +++++++++++++--- src/users/users.controller.ts | 38 +++++++++------------------ src/users/users.service.ts | 19 +++++++++----- 15 files changed, 91 insertions(+), 67 deletions(-) diff --git a/Dockerfile b/Dockerfile index 3d13975..de009bc 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,7 +2,7 @@ FROM node:20 WORKDIR /app -COPY package*.json +COPY package*.json ./ RUN npm install COPY . . diff --git a/docker-compose.yml b/docker-compose.yml index 2e9333e..b2a24f1 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -19,14 +19,16 @@ services: app: build: . + restart: unless-stopped container_name: node-app environment: DB_HOST: db DB_USER: appuser DB_PASSWORD: apppassword DB_NAME: appdb + PORT: 4000 ports: - - "3000:3000" + - "4000:4000" depends_on: - db networks: diff --git a/src/albums/albums.controller.ts b/src/albums/albums.controller.ts index 2a84a49..8a0fcae 100644 --- a/src/albums/albums.controller.ts +++ b/src/albums/albums.controller.ts @@ -20,7 +20,7 @@ export class AlbumsController { @Get() async getAll() { - return this.albumsService.findAll(); + return await this.albumsService.findAll(); } @Get(':id') @@ -34,12 +34,12 @@ export class AlbumsController { ) id: string, ) { - return this.albumsService.findById(id); + return await this.albumsService.findById(id); } @Post() async create(@Body() dto: CreateAlbumDto) { - return this.albumsService.create(dto); + return await this.albumsService.create(dto); } @Put(':id') @@ -54,7 +54,7 @@ export class AlbumsController { id: string, @Body() dto: UpdateAlbumDto, ): Promise { - return this.albumsService.update(id, dto); + return await this.albumsService.update(id, dto); } @Delete(':id') @@ -68,7 +68,7 @@ export class AlbumsController { }), ) id: string, - ): Promise { - this.albumsService.remove(id); + ) { + return await this.albumsService.remove(id); } } diff --git a/src/albums/albums.service.ts b/src/albums/albums.service.ts index 97154f9..ab9ebff 100644 --- a/src/albums/albums.service.ts +++ b/src/albums/albums.service.ts @@ -18,7 +18,7 @@ export class AlbumsService { } async findAll(): Promise { - return await this.albumRepository.find(); + return await this.albumRepository.find({ relations: ['artist', 'tracks']}); } async findById(id: string): Promise { diff --git a/src/artists/artists.controller.ts b/src/artists/artists.controller.ts index ae40e59..95915bf 100644 --- a/src/artists/artists.controller.ts +++ b/src/artists/artists.controller.ts @@ -69,6 +69,6 @@ export class ArtistsController { ) id: string, ): Promise { - this.artistsService.remove(id); + await this.artistsService.remove(id); } } diff --git a/src/artists/artists.service.ts b/src/artists/artists.service.ts index 4eb8de5..09e58d9 100644 --- a/src/artists/artists.service.ts +++ b/src/artists/artists.service.ts @@ -14,7 +14,8 @@ export class ArtistsService { async create(dto: CreateArtistDto): Promise { const newArtist = this.artistRepository.create(dto); - return await this.artistRepository.save(newArtist); + const art = await this.artistRepository.save(newArtist); + return art; } async findAll(): Promise { diff --git a/src/artists/entities/artist.entity.ts b/src/artists/entities/artist.entity.ts index 6e401f7..703db19 100644 --- a/src/artists/entities/artist.entity.ts +++ b/src/artists/entities/artist.entity.ts @@ -4,7 +4,7 @@ import { Column, Entity, OneToMany, PrimaryGeneratedColumn } from 'typeorm'; @Entity() export class Artist { @PrimaryGeneratedColumn('uuid') - id: string; // uuid v4 + id: string; @Column() name: string; diff --git a/src/favorites/favorites.controller.ts b/src/favorites/favorites.controller.ts index 9fe7531..3384df7 100644 --- a/src/favorites/favorites.controller.ts +++ b/src/favorites/favorites.controller.ts @@ -22,13 +22,13 @@ export class FavoritesController { ) {} @Get() - getAll() { - return this.favoritesService.findAll(); + async getAll() { + return await this.favoritesService.findAll(); } //TRACKS @Post('track/:id') - addTrackToFavorites( + async addTrackToFavorites( @Param( 'id', new ParseUUIDPipe({ @@ -38,12 +38,12 @@ export class FavoritesController { ) id: string, ) { - this.favoritesService.addTrack(id); + return await this.favoritesService.addTrack(id); } @Delete('track/:id') @HttpCode(204) - deleteTrackToFavorites( + async deleteTrackToFavorites( @Param( 'id', new ParseUUIDPipe({ @@ -53,12 +53,12 @@ export class FavoritesController { ) id: string, ) { - this.favoritesService.removeTrack(id); + return await this.favoritesService.removeTrack(id); } //ARTISTS @Post('artist/:id') - addArtistToFavorites( + async addArtistToFavorites( @Param( 'id', new ParseUUIDPipe({ @@ -68,12 +68,12 @@ export class FavoritesController { ) id: string, ) { - this.favoritesService.addArtist(id); + return await this.favoritesService.addArtist(id); } @Delete('artist/:id') @HttpCode(204) - deleteArtistFromFavorites( + async deleteArtistFromFavorites( @Param( 'id', new ParseUUIDPipe({ @@ -83,12 +83,12 @@ export class FavoritesController { ) id: string, ) { - this.favoritesService.removeArtist(id); + return await this.favoritesService.removeArtist(id); } //ALBUMS @Post('album/:id') - addAlbumToFavorites( + async addAlbumToFavorites( @Param( 'id', new ParseUUIDPipe({ @@ -98,12 +98,12 @@ export class FavoritesController { ) id: string, ) { - this.favoritesService.addAlbum(id); + return await this.favoritesService.addAlbum(id); } @Delete('album/:id') @HttpCode(204) - deleteAlbumFromFavorites( + async deleteAlbumFromFavorites( @Param( 'id', new ParseUUIDPipe({ @@ -113,6 +113,6 @@ export class FavoritesController { ) id: string, ) { - this.favoritesService.removeAlbum(id); + return await this.favoritesService.removeAlbum(id); } } diff --git a/src/favorites/favorites.module.ts b/src/favorites/favorites.module.ts index 942add3..c6c4e1b 100644 --- a/src/favorites/favorites.module.ts +++ b/src/favorites/favorites.module.ts @@ -6,9 +6,12 @@ import { Favorites } from './entities/favorites.entity'; import { Artist } from 'src/artists/entities/artist.entity'; import { Track } from 'src/tracks/entities/track.entity'; import { Album } from 'src/albums/entities/album.entity'; +import { ArtistsModule } from 'src/artists/artists.module'; +import { AlbumsModule } from 'src/albums/albums.module'; +import { TracksModule } from 'src/tracks/tracks.module'; @Module({ - imports: [TypeOrmModule.forFeature([Favorites, Artist, Track, Album])], + imports: [TypeOrmModule.forFeature([Favorites, Artist, Track, Album]), ArtistsModule, AlbumsModule, TracksModule], controllers: [FavoritesController], providers: [FavoritesService], }) diff --git a/src/main.ts b/src/main.ts index 69c6ffb..a48f142 100644 --- a/src/main.ts +++ b/src/main.ts @@ -5,6 +5,14 @@ import { ValidationPipe } from '@nestjs/common'; configDotenv(); +process.on('uncaughtException', (err) => { + console.error('[uncaughtException]', err); +}); + +process.on('unhandledRejection', (reason, promise) => { + console.error('[unhandledRejection]', reason); +}); + async function bootstrap() { const app = await NestFactory.create(AppModule); app.useGlobalPipes( @@ -13,6 +21,6 @@ async function bootstrap() { transform: true, }), ); - await app.listen(process.env.PORT || 4000); + await app.listen(process.env.PORT || 4000, '0.0.0.0'); } bootstrap(); diff --git a/src/tracks/tracks.controller.ts b/src/tracks/tracks.controller.ts index 5a7a5fd..faa5ac9 100644 --- a/src/tracks/tracks.controller.ts +++ b/src/tracks/tracks.controller.ts @@ -69,6 +69,6 @@ export class TracksController { ) id: string, ): Promise { - this.tracksService.remove(id); + await this.tracksService.remove(id); } } diff --git a/src/users/dto/return-user.dto.ts b/src/users/dto/return-user.dto.ts index 95f3e60..b650a5d 100644 --- a/src/users/dto/return-user.dto.ts +++ b/src/users/dto/return-user.dto.ts @@ -9,11 +9,11 @@ export class ReturnUserDto { @IsInt() @Min(1) - version: number; // integer number, increments on update + version: number; @IsInt() - createdAt: number; // timestamp of creation + createdAt: number; @IsInt() - updatedAt: number; // timestamp of last update + updatedAt: number; } diff --git a/src/users/entities/user.entity.ts b/src/users/entities/user.entity.ts index 8d9d0a9..77e7147 100644 --- a/src/users/entities/user.entity.ts +++ b/src/users/entities/user.entity.ts @@ -1,5 +1,7 @@ import { Favorites } from 'src/favorites/entities/favorites.entity'; import { + BeforeInsert, + BeforeUpdate, Column, CreateDateColumn, Entity, @@ -14,7 +16,7 @@ export class User { @PrimaryGeneratedColumn('uuid') id: string; // uuid v4 - @Column({ unique: true }) + @Column() login: string; @Column() @@ -23,12 +25,25 @@ export class User { @VersionColumn() version: number; // integer number, increments on update - @CreateDateColumn({ type: 'timestamptz' }) + @Column({ type: 'bigint' }) createdAt: number; // timestamp of creation - @UpdateDateColumn({ type: 'timestamptz' }) + @Column({ type: 'bigint' }) updatedAt: number; // timestamp of last update - @OneToOne(() => Favorites, (favorite) => favorite.user) + @OneToOne(() => Favorites, (favorite) => favorite.user, { nullable: true }) favorite: Favorites; + + @BeforeInsert() + setCreateTimestamp() { + const now = Date.now(); + this.createdAt = now; + this.updatedAt = now; + } + + @BeforeUpdate() + setUpdateTimestamp() { + this.createdAt = Number(this.createdAt); + this.updatedAt = Date.now(); + } } diff --git a/src/users/users.controller.ts b/src/users/users.controller.ts index 6fc06fe..6046bc4 100644 --- a/src/users/users.controller.ts +++ b/src/users/users.controller.ts @@ -9,13 +9,9 @@ import { ParseUUIDPipe, Post, Put, - Res, } from '@nestjs/common'; import { UsersService } from './users.service'; -import { Response } from 'express'; import { CreateUserDto } from './dto/create-user.dto'; -import { User } from './entities/user.entity'; -import { validate } from 'uuid'; import { UpdateUserDto } from './dto/update-user.dto'; @Controller('user') @@ -23,12 +19,12 @@ export class UsersController { constructor(private readonly userService: UsersService) {} @Get() - async getAll(): Promise[]> { - return this.userService.findAll(); + async getAll() { + return await this.userService.findAll(); } @Get(':id') - getById( + async getById( @Param( 'id', new ParseUUIDPipe({ @@ -37,26 +33,18 @@ export class UsersController { }), ) id: string, - @Res() res: Response, ) { - if (!validate(id)) - return res.status(400).json({ message: 'Invalid userId format' }); - const user = this.userService.findById(id); - if (!user) { - return res - .status(HttpStatus.NOT_FOUND) - .json({ message: 'User not found' }); - } - return res.status(HttpStatus.OK).json(user); + return await this.userService.findById(id); } @Post() - create(@Body() createUserDto: CreateUserDto, @Res() res: Response) { - res.status(HttpStatus.CREATED).json(this.userService.create(createUserDto)); + @HttpCode(HttpStatus.CREATED) + async create(@Body() createUserDto: CreateUserDto) { + return await this.userService.create(createUserDto); } @Put(':id') - update( + async update( @Param( 'id', new ParseUUIDPipe({ @@ -65,13 +53,13 @@ export class UsersController { ) id: string, @Body() dto: UpdateUserDto, - ): Promise> { - return this.userService.update(id, dto); + ) { + return await this.userService.update(id, dto); } @Delete(':id') @HttpCode(204) - remove( + async remove( @Param( 'id', new ParseUUIDPipe({ @@ -80,7 +68,7 @@ export class UsersController { }), ) id: string, - ): void { - this.userService.remove(id); + ) { + return await this.userService.remove(id); } } diff --git a/src/users/users.service.ts b/src/users/users.service.ts index bdd411c..e0e23ba 100644 --- a/src/users/users.service.ts +++ b/src/users/users.service.ts @@ -9,6 +9,7 @@ import { CreateUserDto } from './dto/create-user.dto'; import { randomUUID } from 'crypto'; import { User } from './entities/user.entity'; import { UpdateUserDto } from './dto/update-user.dto'; +import { ReturnUserDto } from './dto/return-user.dto'; @Injectable() export class UsersService { @@ -17,19 +18,23 @@ export class UsersService { private readonly userRepository: Repository, ) {} - async create(createUserDto: CreateUserDto): Promise> { + async create(createUserDto: CreateUserDto): Promise { const newUser = this.userRepository.create({ id: randomUUID(), login: createUserDto.login, password: createUserDto.password, }); const saved = await this.userRepository.save(newUser); - const safeUser = { ...saved }; + const safeUser = { ...saved, + createdAt: Number(saved.createdAt), + updatedAt: Number(saved.updatedAt), + }; delete safeUser.password; + safeUser.version = Number(safeUser.version); return safeUser; } - async findAll(): Promise[]> { + async findAll(): Promise { const users = await this.userRepository.find(); return users.map((user) => { delete user.password; @@ -37,7 +42,7 @@ export class UsersService { }); } - async findById(id: string): Promise> { + async findById(id: string): Promise { const user = await this.userRepository.findOne({ where: { id } }); if (!user) throw new NotFoundException('User Not found'); const safeUser = { ...user }; @@ -48,7 +53,7 @@ export class UsersService { async update( id: string, updateDto: UpdateUserDto, - ): Promise> { + ): Promise { const user = await this.userRepository.findOne({ where: { id } }); if (!user) throw new NotFoundException('User Not found'); if (user.password !== updateDto.oldPassword) @@ -59,12 +64,14 @@ export class UsersService { await this.userRepository.save(user); const safeUser = { ...user }; + safeUser.createdAt = Number(safeUser.createdAt); + safeUser.updatedAt = Number(safeUser.updatedAt); delete safeUser.password; return safeUser; } async remove(id: string) { - const user = await this.userRepository.findOne({ where: { id } }); + const user = await this.userRepository.findOne({ where: { id }, relations: ['favorite'] }); if (!user) throw new NotFoundException('User not found'); await this.userRepository.remove(user); return { message: 'User deleted' }; From bb8a4ee87745c1452674fe02e29b9aeff77b5909 Mon Sep 17 00:00:00 2001 From: Yulia Demir Date: Mon, 9 Jun 2025 16:40:20 +0300 Subject: [PATCH 22/32] fix: lint problems --- src/albums/albums.service.ts | 2 +- src/favorites/favorites.module.ts | 7 ++++++- src/main.ts | 2 +- src/users/dto/return-user.dto.ts | 6 +++--- src/users/entities/user.entity.ts | 2 -- src/users/users.service.ts | 15 ++++++++------- 6 files changed, 19 insertions(+), 15 deletions(-) diff --git a/src/albums/albums.service.ts b/src/albums/albums.service.ts index ab9ebff..7a72efe 100644 --- a/src/albums/albums.service.ts +++ b/src/albums/albums.service.ts @@ -18,7 +18,7 @@ export class AlbumsService { } async findAll(): Promise { - return await this.albumRepository.find({ relations: ['artist', 'tracks']}); + return await this.albumRepository.find({ relations: ['artist', 'tracks'] }); } async findById(id: string): Promise { diff --git a/src/favorites/favorites.module.ts b/src/favorites/favorites.module.ts index c6c4e1b..43fa9ac 100644 --- a/src/favorites/favorites.module.ts +++ b/src/favorites/favorites.module.ts @@ -11,7 +11,12 @@ import { AlbumsModule } from 'src/albums/albums.module'; import { TracksModule } from 'src/tracks/tracks.module'; @Module({ - imports: [TypeOrmModule.forFeature([Favorites, Artist, Track, Album]), ArtistsModule, AlbumsModule, TracksModule], + imports: [ + TypeOrmModule.forFeature([Favorites, Artist, Track, Album]), + ArtistsModule, + AlbumsModule, + TracksModule, + ], controllers: [FavoritesController], providers: [FavoritesService], }) diff --git a/src/main.ts b/src/main.ts index a48f142..433046c 100644 --- a/src/main.ts +++ b/src/main.ts @@ -9,7 +9,7 @@ process.on('uncaughtException', (err) => { console.error('[uncaughtException]', err); }); -process.on('unhandledRejection', (reason, promise) => { +process.on('unhandledRejection', (reason) => { console.error('[unhandledRejection]', reason); }); diff --git a/src/users/dto/return-user.dto.ts b/src/users/dto/return-user.dto.ts index b650a5d..f707475 100644 --- a/src/users/dto/return-user.dto.ts +++ b/src/users/dto/return-user.dto.ts @@ -9,11 +9,11 @@ export class ReturnUserDto { @IsInt() @Min(1) - version: number; + version: number; @IsInt() - createdAt: number; + createdAt: number; @IsInt() - updatedAt: number; + updatedAt: number; } diff --git a/src/users/entities/user.entity.ts b/src/users/entities/user.entity.ts index 77e7147..9460e60 100644 --- a/src/users/entities/user.entity.ts +++ b/src/users/entities/user.entity.ts @@ -3,11 +3,9 @@ import { BeforeInsert, BeforeUpdate, Column, - CreateDateColumn, Entity, OneToOne, PrimaryGeneratedColumn, - UpdateDateColumn, VersionColumn, } from 'typeorm'; diff --git a/src/users/users.service.ts b/src/users/users.service.ts index e0e23ba..8032c66 100644 --- a/src/users/users.service.ts +++ b/src/users/users.service.ts @@ -25,10 +25,11 @@ export class UsersService { password: createUserDto.password, }); const saved = await this.userRepository.save(newUser); - const safeUser = { ...saved, + const safeUser = { + ...saved, createdAt: Number(saved.createdAt), updatedAt: Number(saved.updatedAt), - }; + }; delete safeUser.password; safeUser.version = Number(safeUser.version); return safeUser; @@ -50,10 +51,7 @@ export class UsersService { return safeUser; } - async update( - id: string, - updateDto: UpdateUserDto, - ): Promise { + async update(id: string, updateDto: UpdateUserDto): Promise { const user = await this.userRepository.findOne({ where: { id } }); if (!user) throw new NotFoundException('User Not found'); if (user.password !== updateDto.oldPassword) @@ -71,7 +69,10 @@ export class UsersService { } async remove(id: string) { - const user = await this.userRepository.findOne({ where: { id }, relations: ['favorite'] }); + const user = await this.userRepository.findOne({ + where: { id }, + relations: ['favorite'], + }); if (!user) throw new NotFoundException('User not found'); await this.userRepository.remove(user); return { message: 'User deleted' }; From 60488fb8b3686ecc84397617bba15589c2335b22 Mon Sep 17 00:00:00 2001 From: Yulia Demir Date: Mon, 9 Jun 2025 18:15:05 +0300 Subject: [PATCH 23/32] feat: volume and container size --- Dockerfile | 5 +++-- docker-compose.yml | 11 +++++++++-- src/favorites/favorites.controller.ts | 6 ------ 3 files changed, 12 insertions(+), 10 deletions(-) diff --git a/Dockerfile b/Dockerfile index de009bc..43b9197 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,12 +1,13 @@ -FROM node:20 +FROM node:20-alpine WORKDIR /app COPY package*.json ./ + RUN npm install COPY . . EXPOSE 3000 -CMD ["npm", "start"] \ No newline at end of file +CMD ["npm", "start", "start:dev"] \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index b2a24f1..71d5f3d 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -14,12 +14,17 @@ services: POSTGRES_DB: appdb ports: - "5432:5432" + volumes: + - pgdata:/var/lib/postgresql/data networks: - app-network app: build: . - restart: unless-stopped + restart: always + volumes: + - ./src:/app/src + - /app/node_modules container_name: node-app environment: DB_HOST: db @@ -32,4 +37,6 @@ services: depends_on: - db networks: - - app-network \ No newline at end of file + - app-network +volumes: + pgdata: \ No newline at end of file diff --git a/src/favorites/favorites.controller.ts b/src/favorites/favorites.controller.ts index 3384df7..deb1f38 100644 --- a/src/favorites/favorites.controller.ts +++ b/src/favorites/favorites.controller.ts @@ -8,17 +8,11 @@ import { Post, } from '@nestjs/common'; import { FavoritesService } from './favorites.service'; -import { ArtistsService } from 'src/artists/artists.service'; -import { AlbumsService } from 'src/albums/albums.service'; -import { TracksService } from 'src/tracks/tracks.service'; @Controller('favs') export class FavoritesController { constructor( private readonly favoritesService: FavoritesService, - private readonly artistsService: ArtistsService, - private readonly albumsService: AlbumsService, - private readonly tracksService: TracksService, ) {} @Get() From f57eafa73f1e5b15f06c3662e60dab1eac171b84 Mon Sep 17 00:00:00 2001 From: Yulia Demir Date: Mon, 9 Jun 2025 20:22:52 +0300 Subject: [PATCH 24/32] feat: reduce container weight --- .dockerignore | 1 - .env | 10 +++++----- .env.example | 8 ++++---- Dockerfile | 20 ++++++++++++++++++-- docker-compose.yml | 8 ++++---- 5 files changed, 31 insertions(+), 16 deletions(-) diff --git a/.dockerignore b/.dockerignore index de76f6e..8d391cc 100644 --- a/.dockerignore +++ b/.dockerignore @@ -2,6 +2,5 @@ node_modules npm-debug.log Dockerfile* docker-compose.yml -.env .git .gitignore \ No newline at end of file diff --git a/.env b/.env index 1f2f9ab..5403537 100644 --- a/.env +++ b/.env @@ -1,13 +1,13 @@ PORT=4000 CRYPT_SALT=10 -JWT_SECRET_KEY=secret123123 -JWT_SECRET_REFRESH_KEY=secret123123 +JWT_SECRET_KEY=123123 +JWT_SECRET_REFRESH_KEY=123123 TOKEN_EXPIRE_TIME=1h TOKEN_REFRESH_EXPIRE_TIME=24h DB_HOST=localhost DB_PORT=5432 -DB_USERNAME=postgres -DB_PASSWORD=postgres -DB_NAME=musicdb \ No newline at end of file +DB_USERNAME=user123 +DB_PASSWORD=111222 +DB_NAME=appdb \ No newline at end of file diff --git a/.env.example b/.env.example index 2d17723..2787a67 100644 --- a/.env.example +++ b/.env.example @@ -1,13 +1,13 @@ PORT=4000 CRYPT_SALT=10 -JWT_SECRET_KEY=secret123123 -JWT_SECRET_REFRESH_KEY=secret123123 +JWT_SECRET_KEY=123123 +JWT_SECRET_REFRESH_KEY=123123 TOKEN_EXPIRE_TIME=1h TOKEN_REFRESH_EXPIRE_TIME=24h DB_HOST=localhost DB_PORT=5432 -DB_USERNAME=postgres -DB_PASSWORD=postgres +DB_USERNAME=user123 +DB_PASSWORD=111222 DB_NAME=musicdb diff --git a/Dockerfile b/Dockerfile index 43b9197..4f292f9 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,5 @@ -FROM node:20-alpine +#stage1 +FROM node:20-alpine AS builder WORKDIR /app @@ -8,6 +9,21 @@ RUN npm install COPY . . +RUN npm run build + +#stage2 +FROM node:20-alpine + +WORKDIR /app + +COPY package*.json ./ + +RUN npm install --production + +COPY --from=builder /app/dist ./dist +COPY --from=builder /app/.env ./ + + EXPOSE 3000 -CMD ["npm", "start", "start:dev"] \ No newline at end of file +CMD ["node", "dist/main.js"] \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index 71d5f3d..c672616 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -9,8 +9,8 @@ services: build: ./postgres container_name: postgres-db environment: - POSTGRES_USER: appuser - POSTGRES_PASSWORD: apppassword + POSTGRES_USER: user123 + POSTGRES_PASSWORD: 111222 POSTGRES_DB: appdb ports: - "5432:5432" @@ -28,8 +28,8 @@ services: container_name: node-app environment: DB_HOST: db - DB_USER: appuser - DB_PASSWORD: apppassword + DB_USERNAME: user123 + DB_PASSWORD: 111222 DB_NAME: appdb PORT: 4000 ports: From d624a0343e7a4c1f95679ea13ad525bc6a61bb1e Mon Sep 17 00:00:00 2001 From: Yulia Demir Date: Mon, 9 Jun 2025 22:06:02 +0300 Subject: [PATCH 25/32] fix: remove hardcode variables --- .env | 6 +++++- docker-compose.yml | 20 +++++++++----------- 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/.env b/.env index 5403537..31ef785 100644 --- a/.env +++ b/.env @@ -10,4 +10,8 @@ DB_HOST=localhost DB_PORT=5432 DB_USERNAME=user123 DB_PASSWORD=111222 -DB_NAME=appdb \ No newline at end of file +DB_NAME=appdb + +POSTGRES_USER=user123 +POSTGRES_PASSWORD=111222 +POSTGRES_DB=appdb \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index c672616..8630340 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -9,11 +9,11 @@ services: build: ./postgres container_name: postgres-db environment: - POSTGRES_USER: user123 - POSTGRES_PASSWORD: 111222 - POSTGRES_DB: appdb + POSTGRES_USER: ${POSTGRES_USER} + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} + POSTGRES_DB: ${POSTGRES_DB} ports: - - "5432:5432" + - "${DB_PORT}:${DB_PORT}" volumes: - pgdata:/var/lib/postgresql/data networks: @@ -28,14 +28,12 @@ services: container_name: node-app environment: DB_HOST: db - DB_USERNAME: user123 - DB_PASSWORD: 111222 - DB_NAME: appdb - PORT: 4000 + DB_USERNAME: ${DB_USERNAME} + DB_PASSWORD: ${DB_PASSWORD} + DB_NAME: ${DB_NAME} + PORT: ${PORT} ports: - - "4000:4000" - depends_on: - - db + - "${PORT}:${PORT}" networks: - app-network volumes: From d9f6fe1c38e0202b9d84f1db6dcfbec88a3306db Mon Sep 17 00:00:00 2001 From: Yulia Demir Date: Mon, 9 Jun 2025 22:07:40 +0300 Subject: [PATCH 26/32] fix: lint improvement --- src/favorites/favorites.controller.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/favorites/favorites.controller.ts b/src/favorites/favorites.controller.ts index deb1f38..77272b2 100644 --- a/src/favorites/favorites.controller.ts +++ b/src/favorites/favorites.controller.ts @@ -11,9 +11,7 @@ import { FavoritesService } from './favorites.service'; @Controller('favs') export class FavoritesController { - constructor( - private readonly favoritesService: FavoritesService, - ) {} + constructor(private readonly favoritesService: FavoritesService) {} @Get() async getAll() { From 3626a0b9369fcf81da1d5960c0207d71f3686c1d Mon Sep 17 00:00:00 2001 From: Yulia Demir Date: Mon, 9 Jun 2025 23:36:12 +0300 Subject: [PATCH 27/32] docs: add instructions to readme --- README.md | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 1f8fbb1..5d1d543 100644 --- a/README.md +++ b/README.md @@ -11,29 +11,26 @@ ``` git clone https://github.com/YuliaDemir/nodejs2025Q2-service.git +cd nodejs2025Q2-service ``` ### 2. Moving to the development branch ``` -git checkout dev +git checkout dev2 ``` -### 3. Installing NPM modules +## RUNNING application -``` -npm install -``` +### 1. Launch your docker app -## RUNNING application +### 2. Assemble and launch the containers ``` -npm start +docker-compose up --build ``` After starting the app on port (4000 as default) you can open -in your browser OpenAPI documentation by typing http://localhost:4000/doc/. -For more information about OpenAPI/Swagger please visit https://swagger.io/. ## Testing From edd4ce990b2149fe4717b9d639043768d55b2fa9 Mon Sep 17 00:00:00 2001 From: Yulia Demir Date: Fri, 13 Jun 2025 11:21:55 +0300 Subject: [PATCH 28/32] feat: add error handler --- src/filters/all-exceptions.filter.ts | 21 ++++++++++++++ src/logging/logging.service.ts | 43 ++++++++++++++++++++++++++++ src/main.ts | 6 ++++ 3 files changed, 70 insertions(+) create mode 100644 src/filters/all-exceptions.filter.ts create mode 100644 src/logging/logging.service.ts diff --git a/src/filters/all-exceptions.filter.ts b/src/filters/all-exceptions.filter.ts new file mode 100644 index 0000000..4a146a6 --- /dev/null +++ b/src/filters/all-exceptions.filter.ts @@ -0,0 +1,21 @@ +import { ArgumentsHost, Catch, ExceptionFilter, HttpException, HttpStatus } from "@nestjs/common"; +import { LoggingService } from "src/logging/logging.service"; +import { Request, Response } from "express"; + +@Catch() +export class ExceptionsFilter implements ExceptionFilter { + constructor(private readonly logger: LoggingService) {} + + catch (exeption: unknown, host: ArgumentsHost) { + const ctx = host.switchToHttp(); + const req = ctx.getRequest(); + const res = ctx.getResponse(); + + const status = exeption instanceof HttpException ? exeption.getStatus() : HttpStatus.INTERNAL_SERVER_ERROR; + + const message = exeption instanceof HttpException ? exeption.getResponse() : 'Internal server error'; + + this.logger.error(`Exception at ${req.method} ${req.url}`, JSON.stringify(exeption)); + res.status(status).json({ statusCode: status, message}); + } +} \ No newline at end of file diff --git a/src/logging/logging.service.ts b/src/logging/logging.service.ts new file mode 100644 index 0000000..c9a2f81 --- /dev/null +++ b/src/logging/logging.service.ts @@ -0,0 +1,43 @@ +import { Injectable, LoggerService, LogLevel } from "@nestjs/common"; +import path from "path"; +import fs from 'fs'; + +@Injectable() +export class LoggingService implements LoggerService { + private logFile: fs.WriteStream; + private level: LogLevel; + + constructor() { + const logLevel = process.env.LOG_LEVEL || 'log'; + this.level = logLevel as LogLevel; + const logPath = path.join(__dirname, '../../logs/app.log'); + this.logFile = fs.createWriteStream(logPath, { flags: 'a' }); + + process.on('uncaughtException', (err) => this.error(`Uncaught ${err.message}`, err.stack)); + process.on('unhandledRejection', (reas) => this.error(`Uncaught ${reas}`)); + } + + private write(message: string) { + this.logFile.write(`[${new Date().toString()}] --> ${message}\n`); + } + + log(message: string) { + if (this.level === 'log') this.write(`LOG: ${message}`); + } + + error(message: string, trace?: string) { + this.write(`ERROR: ${message} ${trace || ''}`); + } + + warn(message: string) { + if (['log', 'warn'].includes(this.level)) this.write(`WARN: ${message}`); + } + + debug(message: string) { + if (this.level === 'debug') this.write(`DEBUG: ${message}`); + } + + verbose(message: string) { + if (this.level === 'verbose') this.write(`VEBOSE: ${message}`); + } +} \ No newline at end of file diff --git a/src/main.ts b/src/main.ts index 433046c..c5a09b2 100644 --- a/src/main.ts +++ b/src/main.ts @@ -2,6 +2,8 @@ import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; import { configDotenv } from 'dotenv'; import { ValidationPipe } from '@nestjs/common'; +import { LoggingService } from './logging/logging.service'; +import { ExceptionsFilter } from './filters/all-exceptions.filter'; configDotenv(); @@ -15,6 +17,9 @@ process.on('unhandledRejection', (reason) => { async function bootstrap() { const app = await NestFactory.create(AppModule); + const logger = new LoggingService(); + + app.useGlobalFilters(new ExceptionsFilter(logger)); app.useGlobalPipes( new ValidationPipe({ whitelist: true, @@ -22,5 +27,6 @@ async function bootstrap() { }), ); await app.listen(process.env.PORT || 4000, '0.0.0.0'); + logger.log(`Server: http://localhost:${process.env.PORT || 4000}`); } bootstrap(); From 94e988d17e4380321d0d6686a30463e3ddc43ebd Mon Sep 17 00:00:00 2001 From: Yulia Demir Date: Fri, 13 Jun 2025 15:51:23 +0300 Subject: [PATCH 29/32] feat: add authorisation --- package-lock.json | 89 +++++++++++++++++++++++++++++++ package.json | 3 ++ src/app.module.ts | 2 + src/auth/auth.controller.ts | 27 ++++++++++ src/auth/auth.module.ts | 9 ++++ src/auth/auth.service.ts | 79 +++++++++++++++++++++++++++ src/auth/dto/login.dto.ts | 9 ++++ src/auth/dto/refresh-token.dto.ts | 6 +++ src/auth/dto/signup.dto.ts | 9 ++++ src/auth/jwt.strategy.ts | 20 +++++++ 10 files changed, 253 insertions(+) create mode 100644 src/auth/auth.controller.ts create mode 100644 src/auth/auth.module.ts create mode 100644 src/auth/auth.service.ts create mode 100644 src/auth/dto/login.dto.ts create mode 100644 src/auth/dto/refresh-token.dto.ts create mode 100644 src/auth/dto/signup.dto.ts create mode 100644 src/auth/jwt.strategy.ts diff --git a/package-lock.json b/package-lock.json index b7da6be..ea8864f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,6 +14,7 @@ "@nestjs/core": "^10.4.15", "@nestjs/jwt": "^11.0.0", "@nestjs/mapped-types": "^2.1.0", + "@nestjs/passport": "^11.0.5", "@nestjs/platform-express": "^10.4.15", "@nestjs/typeorm": "^11.0.0", "bcrypt": "^5.1.1", @@ -22,6 +23,8 @@ "dotenv": "^16.5.0", "express": "^5.1.0", "http-status-codes": "^2.2.0", + "passport": "^0.7.0", + "passport-jwt": "^4.0.1", "pg": "^8.16.0", "reflect-metadata": "^0.2.1", "rimraf": "^5.0.5", @@ -1863,6 +1866,16 @@ } } }, + "node_modules/@nestjs/passport": { + "version": "11.0.5", + "resolved": "https://registry.npmjs.org/@nestjs/passport/-/passport-11.0.5.tgz", + "integrity": "sha512-ulQX6mbjlws92PIM15Naes4F4p2JoxGnIJuUsdXQPT+Oo2sqQmENEZXM7eYuimocfHnKlcfZOuyzbA33LwUlOQ==", + "license": "MIT", + "peerDependencies": { + "@nestjs/common": "^10.0.0 || ^11.0.0", + "passport": "^0.5.0 || ^0.6.0 || ^0.7.0" + } + }, "node_modules/@nestjs/platform-express": { "version": "10.4.15", "resolved": "https://registry.npmjs.org/@nestjs/platform-express/-/platform-express-10.4.15.tgz", @@ -7843,6 +7856,42 @@ "node": ">= 0.8" } }, + "node_modules/passport": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/passport/-/passport-0.7.0.tgz", + "integrity": "sha512-cPLl+qZpSc+ireUvt+IzqbED1cHHkDoVYMo30jbJIdOOjQ1MQYZBPiNvmi8UM6lJuOpTPXJGZQk0DtC4y61MYQ==", + "license": "MIT", + "dependencies": { + "passport-strategy": "1.x.x", + "pause": "0.0.1", + "utils-merge": "^1.0.1" + }, + "engines": { + "node": ">= 0.4.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/jaredhanson" + } + }, + "node_modules/passport-jwt": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/passport-jwt/-/passport-jwt-4.0.1.tgz", + "integrity": "sha512-UCKMDYhNuGOBE9/9Ycuoyh7vP6jpeTp/+sfMJl7nLff/t6dps+iaeE0hhNkKN8/HZHcJ7lCdOyDxHdDoxoSvdQ==", + "license": "MIT", + "dependencies": { + "jsonwebtoken": "^9.0.0", + "passport-strategy": "^1.0.0" + } + }, + "node_modules/passport-strategy": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/passport-strategy/-/passport-strategy-1.0.0.tgz", + "integrity": "sha512-CB97UUvDKJde2V0KDWWB3lyf6PC3FaZP7YxZ2G8OAtn9p4HI9j9JLP9qjOGZFvyl8uwNT8qM+hGnz/n16NI7oA==", + "engines": { + "node": ">= 0.4.0" + } + }, "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -7919,6 +7968,11 @@ "node": ">=8" } }, + "node_modules/pause": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/pause/-/pause-0.0.1.tgz", + "integrity": "sha512-KG8UEiEVkR3wGEb4m5yZkVCzigAD+cVEJck2CzYZO37ZGJfctvVptVO192MwrtPhzONn6go8ylnOdMhKqi4nfg==" + }, "node_modules/pg": { "version": "8.16.0", "resolved": "https://registry.npmjs.org/pg/-/pg-8.16.0.tgz", @@ -11583,6 +11637,12 @@ "integrity": "sha512-W+n+rM69XsFdwORF11UqJahn4J3xi4g/ZEOlJNL6KoW5ygWSmBB2p0S2BZ4FQeS/NDH72e6xIcu35SfJnE8bXw==", "requires": {} }, + "@nestjs/passport": { + "version": "11.0.5", + "resolved": "https://registry.npmjs.org/@nestjs/passport/-/passport-11.0.5.tgz", + "integrity": "sha512-ulQX6mbjlws92PIM15Naes4F4p2JoxGnIJuUsdXQPT+Oo2sqQmENEZXM7eYuimocfHnKlcfZOuyzbA33LwUlOQ==", + "requires": {} + }, "@nestjs/platform-express": { "version": "10.4.15", "resolved": "https://registry.npmjs.org/@nestjs/platform-express/-/platform-express-10.4.15.tgz", @@ -15884,6 +15944,30 @@ "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==" }, + "passport": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/passport/-/passport-0.7.0.tgz", + "integrity": "sha512-cPLl+qZpSc+ireUvt+IzqbED1cHHkDoVYMo30jbJIdOOjQ1MQYZBPiNvmi8UM6lJuOpTPXJGZQk0DtC4y61MYQ==", + "requires": { + "passport-strategy": "1.x.x", + "pause": "0.0.1", + "utils-merge": "^1.0.1" + } + }, + "passport-jwt": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/passport-jwt/-/passport-jwt-4.0.1.tgz", + "integrity": "sha512-UCKMDYhNuGOBE9/9Ycuoyh7vP6jpeTp/+sfMJl7nLff/t6dps+iaeE0hhNkKN8/HZHcJ7lCdOyDxHdDoxoSvdQ==", + "requires": { + "jsonwebtoken": "^9.0.0", + "passport-strategy": "^1.0.0" + } + }, + "passport-strategy": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/passport-strategy/-/passport-strategy-1.0.0.tgz", + "integrity": "sha512-CB97UUvDKJde2V0KDWWB3lyf6PC3FaZP7YxZ2G8OAtn9p4HI9j9JLP9qjOGZFvyl8uwNT8qM+hGnz/n16NI7oA==" + }, "path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -15938,6 +16022,11 @@ "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", "dev": true }, + "pause": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/pause/-/pause-0.0.1.tgz", + "integrity": "sha512-KG8UEiEVkR3wGEb4m5yZkVCzigAD+cVEJck2CzYZO37ZGJfctvVptVO192MwrtPhzONn6go8ylnOdMhKqi4nfg==" + }, "pg": { "version": "8.16.0", "resolved": "https://registry.npmjs.org/pg/-/pg-8.16.0.tgz", diff --git a/package.json b/package.json index a0e32ae..7a8782f 100644 --- a/package.json +++ b/package.json @@ -30,6 +30,7 @@ "@nestjs/core": "^10.4.15", "@nestjs/jwt": "^11.0.0", "@nestjs/mapped-types": "^2.1.0", + "@nestjs/passport": "^11.0.5", "@nestjs/platform-express": "^10.4.15", "@nestjs/typeorm": "^11.0.0", "bcrypt": "^5.1.1", @@ -38,6 +39,8 @@ "dotenv": "^16.5.0", "express": "^5.1.0", "http-status-codes": "^2.2.0", + "passport": "^0.7.0", + "passport-jwt": "^4.0.1", "pg": "^8.16.0", "reflect-metadata": "^0.2.1", "rimraf": "^5.0.5", diff --git a/src/app.module.ts b/src/app.module.ts index c7638f0..baf90cf 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -8,6 +8,7 @@ import { TracksModule } from './tracks/tracks.module'; import { FavoritesModule } from './favorites/favorites.module'; import { ConfigModule } from '@nestjs/config'; import { TypeOrmModule } from '@nestjs/typeorm'; +import { AuthModule } from './auth/auth.module'; @Module({ imports: [ @@ -29,6 +30,7 @@ import { TypeOrmModule } from '@nestjs/typeorm'; autoLoadEntities: true, synchronize: true, }), + AuthModule, ], controllers: [AppController], providers: [AppService], diff --git a/src/auth/auth.controller.ts b/src/auth/auth.controller.ts new file mode 100644 index 0000000..c0f5b73 --- /dev/null +++ b/src/auth/auth.controller.ts @@ -0,0 +1,27 @@ +import { Body, Controller, HttpCode, HttpStatus, Post } from '@nestjs/common'; +import { AuthService } from './auth.service'; +import { SignupDto } from './dto/signup.dto'; +import { LoginDto } from './dto/login.dto'; +import { RefreshTokenDto } from './dto/refresh-token.dto'; + +@Controller('auth') +export class AuthController { + constructor(private readonly authService: AuthService) {} + + @Post('signup') + async signup(@Body() dto: SignupDto) { + return this.authService.signup(dto.login, dto.password); + } + + @Post('login') + @HttpCode(HttpStatus.OK) + async login(@Body() dto: LoginDto) { + return this.authService.login(dto.login, dto.password); + } + + @Post('refresh') + async refresh(@Body() dto: RefreshTokenDto) { + return this.authService.refreshToken(dto.refreshToken); + } + +} diff --git a/src/auth/auth.module.ts b/src/auth/auth.module.ts new file mode 100644 index 0000000..b51ac54 --- /dev/null +++ b/src/auth/auth.module.ts @@ -0,0 +1,9 @@ +import { Module } from '@nestjs/common'; +import { AuthController } from './auth.controller'; +import { AuthService } from './auth.service'; + +@Module({ + controllers: [AuthController], + providers: [AuthService] +}) +export class AuthModule {} diff --git a/src/auth/auth.service.ts b/src/auth/auth.service.ts new file mode 100644 index 0000000..fd7022b --- /dev/null +++ b/src/auth/auth.service.ts @@ -0,0 +1,79 @@ +import { BadRequestException, Injectable, UnauthorizedException } from '@nestjs/common'; +import { JwtService } from '@nestjs/jwt'; +import { InjectRepository } from '@nestjs/typeorm'; +import { User } from 'src/users/entities/user.entity'; +import { Repository } from 'typeorm'; +import * as bcrypt from 'bcrypt'; + +@Injectable() +export class AuthService { + constructor( + private readonly jwtService: JwtService, + @InjectRepository(User) + private readonly userRepo: Repository, + ) {} + + async signup(login: string, password: string) { + const existing = await this.userRepo.findOne({ where: { login } }); + if (existing) { + throw new BadRequestException('User with this login already exists'); + } + + const hashedPassword = await bcrypt.hash(password, 10); + + const newUser = this.userRepo.create({ + login, + password: hashedPassword, + }); + + await this.userRepo.save(newUser); + + return { message: 'User created' }; + } + + async login(login: string, password: string) { + const user = await this.userRepo.findOne({ where: { login } }); + if (!user || !(await bcrypt.compare(password, user.password))) { + throw new UnauthorizedException('Invalid login or wrong password'); + } + + return this.generateTokens(user); + } + + async refreshToken(refreshToken: string) { + try { + const payload = await this.jwtService.verifyAsync(refreshToken, { + secret: process.env.JWT_REFRESH_SECRET, + }); + + const user = await this.userRepo.findOne({ + where: { id: payload.userId, login: payload.login }, + }); + + if (!user) throw new UnauthorizedException(); + + return this.generateTokens(user); + } catch (e) { + throw new UnauthorizedException('Invalid or expired refresh token'); + } + } + + private generateTokens (user: User) { + const payload = { userId: user.id, login: user.login}; + + const accessToken = this.jwtService.sign(payload, { + secret: process.env.JWT_ACCESS_SECRET, + expiresIn: '15m', + }); + + const refreshToken = this.jwtService.sign(payload, { + secret: process.env.JWT_REFRESH_SECRET, + expiresIn: '7d', + }); + + return { + accessToken, + refreshToken, + }; + } +} diff --git a/src/auth/dto/login.dto.ts b/src/auth/dto/login.dto.ts new file mode 100644 index 0000000..5a67dd2 --- /dev/null +++ b/src/auth/dto/login.dto.ts @@ -0,0 +1,9 @@ +import { IsString } from "class-validator"; + +export class LoginDto { + @IsString() + login: string; + + @IsString() + password: string; +} \ No newline at end of file diff --git a/src/auth/dto/refresh-token.dto.ts b/src/auth/dto/refresh-token.dto.ts new file mode 100644 index 0000000..fa8337f --- /dev/null +++ b/src/auth/dto/refresh-token.dto.ts @@ -0,0 +1,6 @@ +import { IsString } from "class-validator"; + +export class RefreshTokenDto { + @IsString() + refreshToken: string; +} \ No newline at end of file diff --git a/src/auth/dto/signup.dto.ts b/src/auth/dto/signup.dto.ts new file mode 100644 index 0000000..51c3675 --- /dev/null +++ b/src/auth/dto/signup.dto.ts @@ -0,0 +1,9 @@ +import { IsString } from "class-validator"; + +export class SignupDto { + @IsString() + login: string; + + @IsString() + password: string; +} \ No newline at end of file diff --git a/src/auth/jwt.strategy.ts b/src/auth/jwt.strategy.ts new file mode 100644 index 0000000..949c7ce --- /dev/null +++ b/src/auth/jwt.strategy.ts @@ -0,0 +1,20 @@ +import { Injectable } from "@nestjs/common"; +import { AuthGuard, PassportStrategy } from "@nestjs/passport"; +import { Strategy, ExtractJwt } from 'passport-jwt'; + +@Injectable() +export class JwtAccessStrategy extends PassportStrategy(Strategy, 'jwt') { + constructor() { + super({ + jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), + secretOrKey: process.env.JWT_ACCESS_SECRET, + }); + } + + async validate (payload: any) { + return payload; + } +} + +@Injectable() +export class JwtGuard extends AuthGuard('jwt') {} \ No newline at end of file From ee216e45ad5a18371f86aba5f2c875cc89b928ff Mon Sep 17 00:00:00 2001 From: Yulia Demir Date: Sat, 14 Jun 2025 09:42:10 +0300 Subject: [PATCH 30/32] fix: authorisation --- .env | 5 +++-- src/albums/albums.controller.ts | 3 +++ src/albums/albums.module.ts | 3 ++- src/artists/artists.controller.ts | 3 +++ src/artists/artists.module.ts | 3 ++- src/auth/auth.module.ts | 15 ++++++++++++++- src/auth/auth.service.ts | 4 ++-- src/auth/jwt.guard.ts | 5 +++++ src/auth/jwt.strategy.ts | 3 --- src/favorites/favorites.controller.ts | 11 +++++++++++ src/favorites/favorites.module.ts | 2 ++ src/filters/all-exceptions.filter.ts | 26 +++++++++++++++++++++++++- src/logging/logging.service.ts | 6 +++--- src/tracks/tracks.controller.ts | 4 ++++ src/tracks/tracks.module.ts | 3 ++- src/users/users.controller.ts | 4 ++++ src/users/users.module.ts | 4 +++- 17 files changed, 88 insertions(+), 16 deletions(-) create mode 100644 src/auth/jwt.guard.ts diff --git a/.env b/.env index 31ef785..524fc08 100644 --- a/.env +++ b/.env @@ -1,8 +1,9 @@ PORT=4000 CRYPT_SALT=10 -JWT_SECRET_KEY=123123 -JWT_SECRET_REFRESH_KEY=123123 +JWT_ACCESS_SECRET=123123 +JWT_REFRESH_SECRET=123123 +LOG_LEVEL = log TOKEN_EXPIRE_TIME=1h TOKEN_REFRESH_EXPIRE_TIME=24h diff --git a/src/albums/albums.controller.ts b/src/albums/albums.controller.ts index 8a0fcae..043866d 100644 --- a/src/albums/albums.controller.ts +++ b/src/albums/albums.controller.ts @@ -8,12 +8,15 @@ import { ParseUUIDPipe, Post, Put, + UseGuards, } from '@nestjs/common'; import { CreateAlbumDto } from './dto/create-album.dto'; import { UpdateAlbumDto } from './dto/update-album.dto'; import { Album } from './entities/album.entity'; import { AlbumsService } from './albums.service'; +import { JwtGuard } from 'src/auth/jwt.guard'; +@UseGuards(JwtGuard) @Controller('album') export class AlbumsController { constructor(private readonly albumsService: AlbumsService) {} diff --git a/src/albums/albums.module.ts b/src/albums/albums.module.ts index 23a0a57..dfaf96d 100644 --- a/src/albums/albums.module.ts +++ b/src/albums/albums.module.ts @@ -3,9 +3,10 @@ import { AlbumsController } from './albums.controller'; import { AlbumsService } from './albums.service'; import { TypeOrmModule } from '@nestjs/typeorm'; import { Album } from './entities/album.entity'; +import { AuthModule } from 'src/auth/auth.module'; @Module({ - imports: [TypeOrmModule.forFeature([Album])], + imports: [TypeOrmModule.forFeature([Album]), AuthModule], controllers: [AlbumsController], providers: [AlbumsService], exports: [AlbumsService], diff --git a/src/artists/artists.controller.ts b/src/artists/artists.controller.ts index 95915bf..a5ddabf 100644 --- a/src/artists/artists.controller.ts +++ b/src/artists/artists.controller.ts @@ -8,12 +8,15 @@ import { ParseUUIDPipe, Post, Put, + UseGuards, } from '@nestjs/common'; import { ArtistsService } from './artists.service'; import { CreateArtistDto } from './dto/create-artist.dto'; import { UpdateArtistDto } from './dto/update-artist.dto'; import { Artist } from './entities/artist.entity'; +import { JwtGuard } from 'src/auth/jwt.guard'; +@UseGuards(JwtGuard) @Controller('artist') export class ArtistsController { constructor(private readonly artistsService: ArtistsService) {} diff --git a/src/artists/artists.module.ts b/src/artists/artists.module.ts index 1fb7f22..13e6efb 100644 --- a/src/artists/artists.module.ts +++ b/src/artists/artists.module.ts @@ -3,9 +3,10 @@ import { ArtistsController } from './artists.controller'; import { ArtistsService } from './artists.service'; import { TypeOrmModule } from '@nestjs/typeorm'; import { Artist } from './entities/artist.entity'; +import { AuthModule } from 'src/auth/auth.module'; @Module({ - imports: [TypeOrmModule.forFeature([Artist])], + imports: [TypeOrmModule.forFeature([Artist]), AuthModule], controllers: [ArtistsController], providers: [ArtistsService], exports: [ArtistsService], diff --git a/src/auth/auth.module.ts b/src/auth/auth.module.ts index b51ac54..6b10529 100644 --- a/src/auth/auth.module.ts +++ b/src/auth/auth.module.ts @@ -1,9 +1,22 @@ import { Module } from '@nestjs/common'; import { AuthController } from './auth.controller'; import { AuthService } from './auth.service'; +import { JwtModule } from '@nestjs/jwt'; +import { TypeOrmModule } from '@nestjs/typeorm'; +import { User } from 'src/users/entities/user.entity'; +import { JwtAccessStrategy } from './jwt.strategy'; +import { JwtGuard } from './jwt.guard'; @Module({ + imports: [ + JwtModule.register({ + secret: process.env.JWT_ACCESS_SECRET, + signOptions: { expiresIn: '1h' }, + }), + TypeOrmModule.forFeature([User]), + ], controllers: [AuthController], - providers: [AuthService] + providers: [AuthService, JwtAccessStrategy], + exports: [JwtAccessStrategy], }) export class AuthModule {} diff --git a/src/auth/auth.service.ts b/src/auth/auth.service.ts index fd7022b..f9f9808 100644 --- a/src/auth/auth.service.ts +++ b/src/auth/auth.service.ts @@ -26,9 +26,9 @@ export class AuthService { password: hashedPassword, }); - await this.userRepo.save(newUser); + const saveUser = await this.userRepo.save(newUser); - return { message: 'User created' }; + return { id: saveUser.id }; } async login(login: string, password: string) { diff --git a/src/auth/jwt.guard.ts b/src/auth/jwt.guard.ts new file mode 100644 index 0000000..df29c77 --- /dev/null +++ b/src/auth/jwt.guard.ts @@ -0,0 +1,5 @@ +import { Injectable } from "@nestjs/common"; +import { AuthGuard } from "@nestjs/passport"; + +@Injectable() +export class JwtGuard extends AuthGuard('jwt') {} \ No newline at end of file diff --git a/src/auth/jwt.strategy.ts b/src/auth/jwt.strategy.ts index 949c7ce..68e9c84 100644 --- a/src/auth/jwt.strategy.ts +++ b/src/auth/jwt.strategy.ts @@ -15,6 +15,3 @@ export class JwtAccessStrategy extends PassportStrategy(Strategy, 'jwt') { return payload; } } - -@Injectable() -export class JwtGuard extends AuthGuard('jwt') {} \ No newline at end of file diff --git a/src/favorites/favorites.controller.ts b/src/favorites/favorites.controller.ts index 77272b2..28b7088 100644 --- a/src/favorites/favorites.controller.ts +++ b/src/favorites/favorites.controller.ts @@ -6,9 +6,20 @@ import { Param, ParseUUIDPipe, Post, + Req, + UseGuards, } from '@nestjs/common'; import { FavoritesService } from './favorites.service'; +import { JwtGuard } from 'src/auth/jwt.guard'; + interface RequestWithUser extends Request { + user?: { + userId: string; + login: string; + } + } + +@UseGuards(JwtGuard) @Controller('favs') export class FavoritesController { constructor(private readonly favoritesService: FavoritesService) {} diff --git a/src/favorites/favorites.module.ts b/src/favorites/favorites.module.ts index 43fa9ac..c258c9f 100644 --- a/src/favorites/favorites.module.ts +++ b/src/favorites/favorites.module.ts @@ -9,6 +9,7 @@ import { Album } from 'src/albums/entities/album.entity'; import { ArtistsModule } from 'src/artists/artists.module'; import { AlbumsModule } from 'src/albums/albums.module'; import { TracksModule } from 'src/tracks/tracks.module'; +import { AuthModule } from 'src/auth/auth.module'; @Module({ imports: [ @@ -16,6 +17,7 @@ import { TracksModule } from 'src/tracks/tracks.module'; ArtistsModule, AlbumsModule, TracksModule, + AuthModule ], controllers: [FavoritesController], providers: [FavoritesService], diff --git a/src/filters/all-exceptions.filter.ts b/src/filters/all-exceptions.filter.ts index 4a146a6..6c928bb 100644 --- a/src/filters/all-exceptions.filter.ts +++ b/src/filters/all-exceptions.filter.ts @@ -13,8 +13,32 @@ export class ExceptionsFilter implements ExceptionFilter { const status = exeption instanceof HttpException ? exeption.getStatus() : HttpStatus.INTERNAL_SERVER_ERROR; - const message = exeption instanceof HttpException ? exeption.getResponse() : 'Internal server error'; + const response = exeption instanceof HttpException ? exeption.getResponse() : 'Internal server error'; + let message: string; + + if (typeof response === 'string') { + message = response; + } + else if (typeof response === 'object' && response !== null) { + if ('message' in response) { + if (Array.isArray(response.message)) { + message = response.message.join(', '); + } + else if (typeof response.message === 'string') { + message = response.message; + } + else { + message = JSON.stringify(response.message); + } + } + else { + message = JSON.stringify(response); + } + } + else { + message = 'Internal server error'; + } this.logger.error(`Exception at ${req.method} ${req.url}`, JSON.stringify(exeption)); res.status(status).json({ statusCode: status, message}); } diff --git a/src/logging/logging.service.ts b/src/logging/logging.service.ts index c9a2f81..c25e435 100644 --- a/src/logging/logging.service.ts +++ b/src/logging/logging.service.ts @@ -1,6 +1,6 @@ import { Injectable, LoggerService, LogLevel } from "@nestjs/common"; -import path from "path"; -import fs from 'fs'; +import * as path from "path"; +import * as fs from 'fs'; @Injectable() export class LoggingService implements LoggerService { @@ -10,7 +10,7 @@ export class LoggingService implements LoggerService { constructor() { const logLevel = process.env.LOG_LEVEL || 'log'; this.level = logLevel as LogLevel; - const logPath = path.join(__dirname, '../../logs/app.log'); + const logPath = path.join(__dirname, '../../app.log'); this.logFile = fs.createWriteStream(logPath, { flags: 'a' }); process.on('uncaughtException', (err) => this.error(`Uncaught ${err.message}`, err.stack)); diff --git a/src/tracks/tracks.controller.ts b/src/tracks/tracks.controller.ts index faa5ac9..8a338c3 100644 --- a/src/tracks/tracks.controller.ts +++ b/src/tracks/tracks.controller.ts @@ -8,12 +8,16 @@ import { ParseUUIDPipe, Post, Put, + UseGuards, } from '@nestjs/common'; import { TracksService } from './tracks.service'; import { CreateTrackDto } from './dto/create-track.dto'; import { UpdateTrackDto } from './dto/update-track.dto'; import { Track } from './entities/track.entity'; +import { JwtGuard } from 'src/auth/jwt.guard'; + +@UseGuards(JwtGuard) @Controller('track') export class TracksController { constructor(private readonly tracksService: TracksService) {} diff --git a/src/tracks/tracks.module.ts b/src/tracks/tracks.module.ts index 712ab26..f9017ab 100644 --- a/src/tracks/tracks.module.ts +++ b/src/tracks/tracks.module.ts @@ -3,9 +3,10 @@ import { TracksController } from './tracks.controller'; import { TracksService } from './tracks.service'; import { TypeOrmModule } from '@nestjs/typeorm'; import { Track } from './entities/track.entity'; +import { AuthModule } from 'src/auth/auth.module'; @Module({ - imports: [TypeOrmModule.forFeature([Track])], + imports: [TypeOrmModule.forFeature([Track]), AuthModule], controllers: [TracksController], providers: [TracksService], exports: [TracksService], diff --git a/src/users/users.controller.ts b/src/users/users.controller.ts index 6046bc4..67962f8 100644 --- a/src/users/users.controller.ts +++ b/src/users/users.controller.ts @@ -9,15 +9,19 @@ import { ParseUUIDPipe, Post, Put, + UseGuards, } from '@nestjs/common'; import { UsersService } from './users.service'; import { CreateUserDto } from './dto/create-user.dto'; import { UpdateUserDto } from './dto/update-user.dto'; +import { JwtGuard } from 'src/auth/jwt.guard'; +@UseGuards(JwtGuard) @Controller('user') export class UsersController { constructor(private readonly userService: UsersService) {} + @Get() async getAll() { return await this.userService.findAll(); diff --git a/src/users/users.module.ts b/src/users/users.module.ts index bb8dc88..1a3506d 100644 --- a/src/users/users.module.ts +++ b/src/users/users.module.ts @@ -3,9 +3,11 @@ import { UsersController } from './users.controller'; import { UsersService } from './users.service'; import { TypeOrmModule } from '@nestjs/typeorm'; import { User } from './entities/user.entity'; +import { AuthModule } from 'src/auth/auth.module'; +import { JwtAccessStrategy } from 'src/auth/jwt.strategy'; @Module({ - imports: [TypeOrmModule.forFeature([User])], + imports: [TypeOrmModule.forFeature([User]), AuthModule], controllers: [UsersController], providers: [UsersService], exports: [UsersService], From cfd489ecf17d11717f1e171bf2395786920960bc Mon Sep 17 00:00:00 2001 From: Yulia Demir Date: Sat, 14 Jun 2025 09:52:00 +0300 Subject: [PATCH 31/32] feat: add hash for password --- src/users/users.service.ts | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/users/users.service.ts b/src/users/users.service.ts index 8032c66..44d879f 100644 --- a/src/users/users.service.ts +++ b/src/users/users.service.ts @@ -10,6 +10,7 @@ import { randomUUID } from 'crypto'; import { User } from './entities/user.entity'; import { UpdateUserDto } from './dto/update-user.dto'; import { ReturnUserDto } from './dto/return-user.dto'; +import * as bcrypt from 'bcrypt'; @Injectable() export class UsersService { @@ -19,10 +20,11 @@ export class UsersService { ) {} async create(createUserDto: CreateUserDto): Promise { + const hashedPassword = await bcrypt.hash(createUserDto.password, 10); const newUser = this.userRepository.create({ id: randomUUID(), login: createUserDto.login, - password: createUserDto.password, + password: hashedPassword, }); const saved = await this.userRepository.save(newUser); const safeUser = { @@ -54,10 +56,13 @@ export class UsersService { async update(id: string, updateDto: UpdateUserDto): Promise { const user = await this.userRepository.findOne({ where: { id } }); if (!user) throw new NotFoundException('User Not found'); - if (user.password !== updateDto.oldPassword) + + const isOldPasswordCorrect = await bcrypt.compare(updateDto.oldPassword, user.password); + + if (!isOldPasswordCorrect) throw new ForbiddenException('The password is wrong!'); - user.password = updateDto.newPassword; + user.password = await bcrypt.hash(updateDto.newPassword, 10); user.version++; await this.userRepository.save(user); From 12739dc69a7dba9cc22dcd9d1de5642f286c49c2 Mon Sep 17 00:00:00 2001 From: Yulia Demir Date: Sat, 14 Jun 2025 19:24:55 +0300 Subject: [PATCH 32/32] fix: log write to the file --- .env | 2 +- src/app.module.ts | 3 ++- src/logging/logging.interseptor.ts | 26 ++++++++++++++++++++++++++ src/logging/logging.service.ts | 8 +++++++- src/main.ts | 4 +++- 5 files changed, 39 insertions(+), 4 deletions(-) create mode 100644 src/logging/logging.interseptor.ts diff --git a/.env b/.env index 524fc08..1f4376a 100644 --- a/.env +++ b/.env @@ -3,7 +3,7 @@ PORT=4000 CRYPT_SALT=10 JWT_ACCESS_SECRET=123123 JWT_REFRESH_SECRET=123123 -LOG_LEVEL = log +LOG_LEVEL=debug TOKEN_EXPIRE_TIME=1h TOKEN_REFRESH_EXPIRE_TIME=24h diff --git a/src/app.module.ts b/src/app.module.ts index baf90cf..1be5ee3 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -9,6 +9,7 @@ import { FavoritesModule } from './favorites/favorites.module'; import { ConfigModule } from '@nestjs/config'; import { TypeOrmModule } from '@nestjs/typeorm'; import { AuthModule } from './auth/auth.module'; +import { LoggingService } from './logging/logging.service'; @Module({ imports: [ @@ -33,6 +34,6 @@ import { AuthModule } from './auth/auth.module'; AuthModule, ], controllers: [AppController], - providers: [AppService], + providers: [AppService, LoggingService], }) export class AppModule {} diff --git a/src/logging/logging.interseptor.ts b/src/logging/logging.interseptor.ts new file mode 100644 index 0000000..2f44e4c --- /dev/null +++ b/src/logging/logging.interseptor.ts @@ -0,0 +1,26 @@ +import { CallHandler, ExecutionContext, Injectable, NestInterceptor } from "@nestjs/common"; +import { LoggingService } from "./logging.service"; +import { Observable, tap } from "rxjs"; + +@Injectable() +export class LoggingInterceptor implements NestInterceptor { + constructor(private readonly logger: LoggingService) {} + + intercept(context: ExecutionContext, next: CallHandler): Observable { + const req = context.switchToHttp().getRequest(); + const { method, url, query, body } = req; + + this.logger.log(`[Request] ${method} ${url}`); + this.logger.debug(`Query: ${JSON.stringify(query)}`); + this.logger.log(`Body: ${JSON.stringify(body)}`); + + const now = Date.now(); + return next.handle().pipe( + tap(() => { + const res = context.switchToHttp().getResponse(); + const status = res.statusCode; + this.logger.log(`[Response] ${status} (${Date.now() - now} ms)`); + }), + ); + } +} \ No newline at end of file diff --git a/src/logging/logging.service.ts b/src/logging/logging.service.ts index c25e435..4ee1bef 100644 --- a/src/logging/logging.service.ts +++ b/src/logging/logging.service.ts @@ -10,7 +10,12 @@ export class LoggingService implements LoggerService { constructor() { const logLevel = process.env.LOG_LEVEL || 'log'; this.level = logLevel as LogLevel; - const logPath = path.join(__dirname, '../../app.log'); + const logDir = path.join(__dirname, './logs/app.log'); + if (!fs.existsSync(logDir)) { + fs.mkdirSync(logDir, { recursive: true }); + } + + const logPath = path.join(logDir, 'app.log'); this.logFile = fs.createWriteStream(logPath, { flags: 'a' }); process.on('uncaughtException', (err) => this.error(`Uncaught ${err.message}`, err.stack)); @@ -19,6 +24,7 @@ export class LoggingService implements LoggerService { private write(message: string) { this.logFile.write(`[${new Date().toString()}] --> ${message}\n`); + //console.log(`[${new Date().toString()}] --> ${message}\n`); } log(message: string) { diff --git a/src/main.ts b/src/main.ts index c5a09b2..dcca99f 100644 --- a/src/main.ts +++ b/src/main.ts @@ -4,6 +4,7 @@ import { configDotenv } from 'dotenv'; import { ValidationPipe } from '@nestjs/common'; import { LoggingService } from './logging/logging.service'; import { ExceptionsFilter } from './filters/all-exceptions.filter'; +import { LoggingInterceptor } from './logging/logging.interseptor'; configDotenv(); @@ -17,8 +18,9 @@ process.on('unhandledRejection', (reason) => { async function bootstrap() { const app = await NestFactory.create(AppModule); - const logger = new LoggingService(); + const logger = app.get(LoggingService); + app.useGlobalInterceptors(new LoggingInterceptor(logger)); app.useGlobalFilters(new ExceptionsFilter(logger)); app.useGlobalPipes( new ValidationPipe({