From 2cdd34686917e20b0cb77d4446899da2ce509d59 Mon Sep 17 00:00:00 2001 From: Nikita Kudinov Date: Wed, 24 Jan 2024 03:06:55 +0300 Subject: [PATCH] refactor: finished drizzle migration --- .github/workflows/cd.yml | 2 +- package.json | 29 +- pnpm-lock.yaml | 1123 ++++++++++++----- public/swagger.json | 165 ++- src/api/api.module.ts | 12 +- src/api/controllers/auth.controller.ts | 4 +- src/api/controllers/packs.controller.ts | 49 +- src/api/controllers/trades.controller.ts | 70 +- src/api/controllers/users.controller.ts | 79 +- .../accepted-trade.output.dto.ts | 14 +- .../cancelled-trade.output.dto.ts | 14 +- src/api/dtos/packs/get-packs.input.dto.ts | 20 + src/api/dtos/packs/open-pack.output.dto.ts | 4 - src/api/dtos/packs/opened-pack.output.dto.ts | 6 - .../packs/pack-with-pokemons.output.dto.ts | 2 - src/api/dtos/packs/pack.output.dto.ts | 6 - .../pending-trade.output.dto.ts | 11 +- src/api/dtos/pokemons/pokemon.output.dto.ts | 7 - .../rejected-trade.output.dto.ts | 14 +- src/api/dtos/trades/trade.output.dto.ts | 29 +- ...ck-sold-user-inventory-entry.output.dto.ts | 27 - .../user-items/get-user-items.input.dto.ts | 21 + .../quick-sold-user-item.output.dto.ts | 7 + .../user-item.output.dto.ts} | 6 +- .../user-with-inventory-entries.output.dto.ts | 10 - src/api/dtos/users/user.output.dto.ts | 4 - src/api/profiles/pack.profile.ts | 66 +- src/api/profiles/pokemon.profile.ts | 37 +- src/api/profiles/trade.profile.ts | 118 +- .../profiles/user-inventory-entry.profile.ts | 21 - src/api/profiles/user-item.profile.ts | 60 + src/api/profiles/user.profile.ts | 35 +- src/api/strategies/jwt-auth.strategy.ts | 3 +- .../map-array-to-paginated-array.helper.ts | 13 + .../map-array-with-pagination.helper.ts | 6 +- src/common/types.ts | 15 + src/core/services/base.service.ts | 153 --- src/core/services/opened-packs.service.ts | 38 +- src/core/services/packs.service.ts | 117 +- src/core/services/pending-trades.service.ts | 179 +++ src/core/services/pokemons.service.ts | 37 +- .../services/quick-sold-user-items.service.ts | 37 +- .../trades-to-receiver-items.service.ts | 88 ++ .../trades-to-sender-items.service.ts | 88 ++ src/core/services/trades.service.ts | 156 +-- src/core/services/user-items.service.ts | 201 ++- src/core/services/users.service.ts | 134 +- src/core/use-cases/auth.use-case.ts | 2 +- src/core/use-cases/opened-packs.use-case.ts | 34 - src/core/use-cases/packs.use-case.ts | 57 +- src/core/use-cases/pending-trades.use-case.ts | 208 ++- .../quick-sold-user-items.use-case.ts | 33 - src/core/use-cases/user-items.use-case.ts | 180 +-- src/core/use-cases/users.use-case.ts | 88 +- src/infra/config/drizzle/index.ts | 21 + src/infra/config/typeorm/index.ts | 55 - src/infra/consts.ts | 1 + .../decorators/inject-database.decorator.ts | 4 + src/infra/ioc/packs.module.ts | 13 +- src/infra/ioc/pokemons.module.ts | 3 +- src/infra/ioc/trades.module.ts | 36 +- .../ioc/user-inventory-entries.module.ts | 29 - src/infra/ioc/user-items.module.ts | 24 + src/infra/ioc/users.module.ts | 3 +- .../postgres/migrations/0000_initial.sql | 184 +++ .../migrations/1703580255818-initial.ts | 46 - ...65163-quick-sold-user-inventory-entries.ts | 18 - .../migrations/1704836181740-trades.ts | 46 - .../migrations/meta/0000_snapshot.json | 716 +++++++++++ .../postgres/migrations/meta/_journal.json | 13 + src/infra/postgres/migrations/run.ts | 26 + src/infra/postgres/other/types.ts | 140 +- src/infra/postgres/postgres.module.ts | 22 +- src/infra/postgres/seeders/pokemons.seeder.ts | 4 +- src/infra/postgres/tables/index.ts | 6 +- .../postgres/tables/opened-packs.table.ts | 42 +- .../postgres/tables/pack-pokemons.table.ts | 24 - .../tables/packs-to-pokemons.table.ts | 35 + src/infra/postgres/tables/packs.table.ts | 12 +- src/infra/postgres/tables/pokemons.table.ts | 11 +- .../tables/quick-sold-user-items.table.ts | 31 +- .../tables/trade-receiver-items.table.ts | 25 - .../tables/trade-sender-items.table.ts | 25 - .../tables/trades-to-receiver-items.table.ts | 40 + .../tables/trades-to-sender-items.table.ts | 42 + src/infra/postgres/tables/trades.table.ts | 88 +- src/infra/postgres/tables/user-items.table.ts | 40 +- src/infra/postgres/tables/users.table.ts | 20 +- 88 files changed, 3894 insertions(+), 1890 deletions(-) create mode 100644 src/api/dtos/packs/get-packs.input.dto.ts delete mode 100644 src/api/dtos/user-inventory-entries/quick-sold-user-inventory-entry.output.dto.ts create mode 100644 src/api/dtos/user-items/get-user-items.input.dto.ts create mode 100644 src/api/dtos/user-items/quick-sold-user-item.output.dto.ts rename src/api/dtos/{user-inventory-entries/user-inventory-entry.output.dto.ts => user-items/user-item.output.dto.ts} (69%) delete mode 100644 src/api/dtos/users/user-with-inventory-entries.output.dto.ts delete mode 100644 src/api/profiles/user-inventory-entry.profile.ts create mode 100644 src/api/profiles/user-item.profile.ts create mode 100644 src/common/helpers/map-array-to-paginated-array.helper.ts delete mode 100644 src/core/services/base.service.ts create mode 100644 src/core/services/pending-trades.service.ts create mode 100644 src/core/services/trades-to-receiver-items.service.ts create mode 100644 src/core/services/trades-to-sender-items.service.ts delete mode 100644 src/core/use-cases/opened-packs.use-case.ts delete mode 100644 src/core/use-cases/quick-sold-user-items.use-case.ts create mode 100644 src/infra/config/drizzle/index.ts delete mode 100644 src/infra/config/typeorm/index.ts create mode 100644 src/infra/consts.ts create mode 100644 src/infra/decorators/inject-database.decorator.ts delete mode 100644 src/infra/ioc/user-inventory-entries.module.ts create mode 100644 src/infra/ioc/user-items.module.ts create mode 100644 src/infra/postgres/migrations/0000_initial.sql delete mode 100644 src/infra/postgres/migrations/1703580255818-initial.ts delete mode 100644 src/infra/postgres/migrations/1704640865163-quick-sold-user-inventory-entries.ts delete mode 100644 src/infra/postgres/migrations/1704836181740-trades.ts create mode 100644 src/infra/postgres/migrations/meta/0000_snapshot.json create mode 100644 src/infra/postgres/migrations/meta/_journal.json create mode 100644 src/infra/postgres/migrations/run.ts delete mode 100644 src/infra/postgres/tables/pack-pokemons.table.ts create mode 100644 src/infra/postgres/tables/packs-to-pokemons.table.ts delete mode 100644 src/infra/postgres/tables/trade-receiver-items.table.ts delete mode 100644 src/infra/postgres/tables/trade-sender-items.table.ts create mode 100644 src/infra/postgres/tables/trades-to-receiver-items.table.ts create mode 100644 src/infra/postgres/tables/trades-to-sender-items.table.ts diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml index f5033c0..1091fca 100644 --- a/.github/workflows/cd.yml +++ b/.github/workflows/cd.yml @@ -22,4 +22,4 @@ jobs: cd $HOME/apps/poketrade-back/ git pull origin main docker compose up --detach --build - docker compose exec app ./node_modules/typeorm/cli.js -d ./dist/infra/config/typeorm/ migration:run + docker compose exec app node ./dist/infra/postgres/migrations/run diff --git a/package.json b/package.json index ade2a51..743a04c 100644 --- a/package.json +++ b/package.json @@ -11,19 +11,18 @@ "start:dev": "nest start --watch", "start:debug": "nest start --debug --watch", "start:prod": "node ./dist/main", - "typeorm": "pnpx typeorm -d ./dist/infra/config/typeorm/", - "migrations:run": "pnpm typeorm migration:run", - "migrations:generate": "pnpm typeorm migration:generate", - "migrations:revert": "pnpm typeorm migration:revert", + "migrations:generate": "drizzle-kit generate:pg --config ./src/infra/config/drizzle/", + "migrations:run": "node ./dist/infra/postgres/migrations/run", "seeders:run": "node ./dist/infra/postgres/seeders/run", - "seeders:refresh": "pnpm seeders:run --refresh" + "seeders:refresh": "pnpm seeders:run --refresh", + "drizzle:studio": "drizzle-kit studio --config ./src/infra/config/drizzle/" }, "dependencies": { "@automapper/classes": "8.7.7", "@automapper/core": "8.7.7", "@automapper/nestjs": "8.7.7", - "@automapper/pojos": "^8.7.7", - "@knaadh/nestjs-drizzle-pg": "^1.0.0", + "@automapper/pojos": "8.7.7", + "@knaadh/nestjs-drizzle-pg": "1.0.0", "@nestjs/common": "9.0.8", "@nestjs/config": "3.1.1", "@nestjs/core": "9.0.8", @@ -31,24 +30,19 @@ "@nestjs/passport": "10.0.3", "@nestjs/platform-express": "9.0.0", "@nestjs/swagger": "7.1.17", - "@nestjs/typeorm": "10.0.1", "bcrypt": "5.1.1", "class-transformer": "0.5.1", "class-validator": "0.14.0", "dotenv": "16.3.1", - "drizzle-orm": "^0.29.3", - "lodash": "^4.17.21", - "nest-transact": "9.1.2", + "drizzle-orm": "0.29.3", + "lodash": "4.17.21", "nestjs-seeder": "0.3.2", - "nestjs-typeorm-paginate": "^4.0.4", "passport": "0.7.0", "passport-jwt": "4.0.1", "passport-local": "1.0.0", "pg": "8.11.3", "reflect-metadata": "0.1.13", - "rxjs": "7.8.1", - "typeorm": "0.3.17", - "typeorm-naming-strategies": "4.1.0" + "rxjs": "7.8.1" }, "devDependencies": { "@automapper/types": "6.3.1", @@ -56,13 +50,14 @@ "@nestjs/schematics": "9.0.0", "@types/bcrypt": "5.0.2", "@types/express": "4.17.21", - "@types/lodash": "^4.14.202", + "@types/lodash": "4.14.202", "@types/node": "20.10.5", "@types/passport-jwt": "3.0.13", "@types/passport-local": "1.0.38", - "@types/pg": "^8.10.9", + "@types/pg": "8.10.9", "@typescript-eslint/eslint-plugin": "6.15.0", "@typescript-eslint/parser": "6.15.0", + "drizzle-kit": "0.20.13", "eslint": "8.56.0", "eslint-config-prettier": "9.1.0", "eslint-plugin-prettier": "5.1.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 10e1deb..2a9515c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -15,10 +15,10 @@ dependencies: specifier: 8.7.7 version: 8.7.7(@automapper/core@8.7.7)(@nestjs/common@9.0.8)(@nestjs/core@9.0.8) '@automapper/pojos': - specifier: ^8.7.7 + specifier: 8.7.7 version: 8.7.7(@automapper/core@8.7.7) '@knaadh/nestjs-drizzle-pg': - specifier: ^1.0.0 + specifier: 1.0.0 version: 1.0.0(@nestjs/common@9.0.8)(drizzle-orm@0.29.3)(pg@8.11.3) '@nestjs/common': specifier: 9.0.8 @@ -41,9 +41,6 @@ dependencies: '@nestjs/swagger': specifier: 7.1.17 version: 7.1.17(@nestjs/common@9.0.8)(@nestjs/core@9.0.8)(class-transformer@0.5.1)(class-validator@0.14.0)(reflect-metadata@0.1.13) - '@nestjs/typeorm': - specifier: 10.0.1 - version: 10.0.1(@nestjs/common@9.0.8)(@nestjs/core@9.0.8)(reflect-metadata@0.1.13)(rxjs@7.8.1)(typeorm@0.3.17) bcrypt: specifier: 5.1.1 version: 5.1.1 @@ -57,20 +54,14 @@ dependencies: specifier: 16.3.1 version: 16.3.1 drizzle-orm: - specifier: ^0.29.3 + specifier: 0.29.3 version: 0.29.3(@types/pg@8.10.9)(pg@8.11.3) lodash: - specifier: ^4.17.21 + specifier: 4.17.21 version: 4.17.21 - nest-transact: - specifier: 9.1.2 - version: 9.1.2(@nestjs/common@9.0.8)(@nestjs/core@9.0.8)(reflect-metadata@0.1.13)(rxjs@7.8.1)(typeorm@0.3.17) nestjs-seeder: specifier: 0.3.2 version: 0.3.2(@faker-js/faker@8.3.1) - nestjs-typeorm-paginate: - specifier: ^4.0.4 - version: 4.0.4(@nestjs/common@9.0.8)(typeorm@0.3.17) passport: specifier: 0.7.0 version: 0.7.0 @@ -89,12 +80,6 @@ dependencies: rxjs: specifier: 7.8.1 version: 7.8.1 - typeorm: - specifier: 0.3.17 - version: 0.3.17(pg@8.11.3)(ts-node@10.9.2) - typeorm-naming-strategies: - specifier: 4.1.0 - version: 4.1.0(typeorm@0.3.17) devDependencies: '@automapper/types': @@ -102,7 +87,7 @@ devDependencies: version: 6.3.1 '@nestjs/cli': specifier: 9.0.0 - version: 9.0.0 + version: 9.0.0(esbuild@0.19.11) '@nestjs/schematics': specifier: 9.0.0 version: 9.0.0(chokidar@3.5.3)(typescript@4.7.4) @@ -113,7 +98,7 @@ devDependencies: specifier: 4.17.21 version: 4.17.21 '@types/lodash': - specifier: ^4.14.202 + specifier: 4.14.202 version: 4.14.202 '@types/node': specifier: 20.10.5 @@ -125,7 +110,7 @@ devDependencies: specifier: 1.0.38 version: 1.0.38 '@types/pg': - specifier: ^8.10.9 + specifier: 8.10.9 version: 8.10.9 '@typescript-eslint/eslint-plugin': specifier: 6.15.0 @@ -133,6 +118,9 @@ devDependencies: '@typescript-eslint/parser': specifier: 6.15.0 version: 6.15.0(eslint@8.56.0)(typescript@4.7.4) + drizzle-kit: + specifier: 0.20.13 + version: 0.20.13 eslint: specifier: 8.56.0 version: 8.56.0 @@ -278,13 +266,6 @@ packages: js-tokens: 4.0.0 dev: true - /@babel/runtime@7.23.6: - resolution: {integrity: sha512-zHd0eUrf5GZoOWVCXp6koAKQTfZV07eit6bGPmJgnZdnSAvvZee6zniW2XMF7Cmc4ISOOnPy3QaSiIJGJkVEDQ==} - engines: {node: '>=6.9.0'} - dependencies: - regenerator-runtime: 0.14.1 - dev: false - /@colors/colors@1.5.0: resolution: {integrity: sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==} engines: {node: '>=0.1.90'} @@ -297,6 +278,432 @@ packages: engines: {node: '>=12'} dependencies: '@jridgewell/trace-mapping': 0.3.9 + dev: true + + /@drizzle-team/studio@0.0.39: + resolution: {integrity: sha512-c5Hkm7MmQC2n5qAsKShjQrHoqlfGslB8+qWzsGGZ+2dHMRTNG60UuzalF0h0rvBax5uzPXuGkYLGaQ+TUX3yMw==} + dependencies: + superjson: 2.2.1 + dev: true + + /@esbuild-kit/core-utils@3.3.2: + resolution: {integrity: sha512-sPRAnw9CdSsRmEtnsl2WXWdyquogVpB3yZ3dgwJfe8zrOzTsV7cJvmwrKVa+0ma5BoiGJ+BoqkMvawbayKUsqQ==} + dependencies: + esbuild: 0.18.20 + source-map-support: 0.5.21 + dev: true + + /@esbuild-kit/esm-loader@2.6.5: + resolution: {integrity: sha512-FxEMIkJKnodyA1OaCUoEvbYRkoZlLZ4d/eXFu9Fh8CbBBgP5EmZxrfTRyN0qpXZ4vOvqnE5YdRdcrmUUXuU+dA==} + dependencies: + '@esbuild-kit/core-utils': 3.3.2 + get-tsconfig: 4.7.2 + dev: true + + /@esbuild/aix-ppc64@0.19.11: + resolution: {integrity: sha512-FnzU0LyE3ySQk7UntJO4+qIiQgI7KoODnZg5xzXIrFJlKd2P2gwHsHY4927xj9y5PJmJSzULiUCWmv7iWnNa7g==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [aix] + requiresBuild: true + dev: true + optional: true + + /@esbuild/android-arm64@0.18.20: + resolution: {integrity: sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==} + engines: {node: '>=12'} + cpu: [arm64] + os: [android] + requiresBuild: true + dev: true + optional: true + + /@esbuild/android-arm64@0.19.11: + resolution: {integrity: sha512-aiu7K/5JnLj//KOnOfEZ0D90obUkRzDMyqd/wNAUQ34m4YUPVhRZpnqKV9uqDGxT7cToSDnIHsGooyIczu9T+Q==} + engines: {node: '>=12'} + cpu: [arm64] + os: [android] + requiresBuild: true + dev: true + optional: true + + /@esbuild/android-arm@0.18.20: + resolution: {integrity: sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==} + engines: {node: '>=12'} + cpu: [arm] + os: [android] + requiresBuild: true + dev: true + optional: true + + /@esbuild/android-arm@0.19.11: + resolution: {integrity: sha512-5OVapq0ClabvKvQ58Bws8+wkLCV+Rxg7tUVbo9xu034Nm536QTII4YzhaFriQ7rMrorfnFKUsArD2lqKbFY4vw==} + engines: {node: '>=12'} + cpu: [arm] + os: [android] + requiresBuild: true + dev: true + optional: true + + /@esbuild/android-x64@0.18.20: + resolution: {integrity: sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==} + engines: {node: '>=12'} + cpu: [x64] + os: [android] + requiresBuild: true + dev: true + optional: true + + /@esbuild/android-x64@0.19.11: + resolution: {integrity: sha512-eccxjlfGw43WYoY9QgB82SgGgDbibcqyDTlk3l3C0jOVHKxrjdc9CTwDUQd0vkvYg5um0OH+GpxYvp39r+IPOg==} + engines: {node: '>=12'} + cpu: [x64] + os: [android] + requiresBuild: true + dev: true + optional: true + + /@esbuild/darwin-arm64@0.18.20: + resolution: {integrity: sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==} + engines: {node: '>=12'} + cpu: [arm64] + os: [darwin] + requiresBuild: true + dev: true + optional: true + + /@esbuild/darwin-arm64@0.19.11: + resolution: {integrity: sha512-ETp87DRWuSt9KdDVkqSoKoLFHYTrkyz2+65fj9nfXsaV3bMhTCjtQfw3y+um88vGRKRiF7erPrh/ZuIdLUIVxQ==} + engines: {node: '>=12'} + cpu: [arm64] + os: [darwin] + requiresBuild: true + dev: true + optional: true + + /@esbuild/darwin-x64@0.18.20: + resolution: {integrity: sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [darwin] + requiresBuild: true + dev: true + optional: true + + /@esbuild/darwin-x64@0.19.11: + resolution: {integrity: sha512-fkFUiS6IUK9WYUO/+22omwetaSNl5/A8giXvQlcinLIjVkxwTLSktbF5f/kJMftM2MJp9+fXqZ5ezS7+SALp4g==} + engines: {node: '>=12'} + cpu: [x64] + os: [darwin] + requiresBuild: true + dev: true + optional: true + + /@esbuild/freebsd-arm64@0.18.20: + resolution: {integrity: sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==} + engines: {node: '>=12'} + cpu: [arm64] + os: [freebsd] + requiresBuild: true + dev: true + optional: true + + /@esbuild/freebsd-arm64@0.19.11: + resolution: {integrity: sha512-lhoSp5K6bxKRNdXUtHoNc5HhbXVCS8V0iZmDvyWvYq9S5WSfTIHU2UGjcGt7UeS6iEYp9eeymIl5mJBn0yiuxA==} + engines: {node: '>=12'} + cpu: [arm64] + os: [freebsd] + requiresBuild: true + dev: true + optional: true + + /@esbuild/freebsd-x64@0.18.20: + resolution: {integrity: sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [freebsd] + requiresBuild: true + dev: true + optional: true + + /@esbuild/freebsd-x64@0.19.11: + resolution: {integrity: sha512-JkUqn44AffGXitVI6/AbQdoYAq0TEullFdqcMY/PCUZ36xJ9ZJRtQabzMA+Vi7r78+25ZIBosLTOKnUXBSi1Kw==} + engines: {node: '>=12'} + cpu: [x64] + os: [freebsd] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-arm64@0.18.20: + resolution: {integrity: sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==} + engines: {node: '>=12'} + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-arm64@0.19.11: + resolution: {integrity: sha512-LneLg3ypEeveBSMuoa0kwMpCGmpu8XQUh+mL8XXwoYZ6Be2qBnVtcDI5azSvh7vioMDhoJFZzp9GWp9IWpYoUg==} + engines: {node: '>=12'} + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-arm@0.18.20: + resolution: {integrity: sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==} + engines: {node: '>=12'} + cpu: [arm] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-arm@0.19.11: + resolution: {integrity: sha512-3CRkr9+vCV2XJbjwgzjPtO8T0SZUmRZla+UL1jw+XqHZPkPgZiyWvbDvl9rqAN8Zl7qJF0O/9ycMtjU67HN9/Q==} + engines: {node: '>=12'} + cpu: [arm] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-ia32@0.18.20: + resolution: {integrity: sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==} + engines: {node: '>=12'} + cpu: [ia32] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-ia32@0.19.11: + resolution: {integrity: sha512-caHy++CsD8Bgq2V5CodbJjFPEiDPq8JJmBdeyZ8GWVQMjRD0sU548nNdwPNvKjVpamYYVL40AORekgfIubwHoA==} + engines: {node: '>=12'} + cpu: [ia32] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-loong64@0.18.20: + resolution: {integrity: sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==} + engines: {node: '>=12'} + cpu: [loong64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-loong64@0.19.11: + resolution: {integrity: sha512-ppZSSLVpPrwHccvC6nQVZaSHlFsvCQyjnvirnVjbKSHuE5N24Yl8F3UwYUUR1UEPaFObGD2tSvVKbvR+uT1Nrg==} + engines: {node: '>=12'} + cpu: [loong64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-mips64el@0.18.20: + resolution: {integrity: sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==} + engines: {node: '>=12'} + cpu: [mips64el] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-mips64el@0.19.11: + resolution: {integrity: sha512-B5x9j0OgjG+v1dF2DkH34lr+7Gmv0kzX6/V0afF41FkPMMqaQ77pH7CrhWeR22aEeHKaeZVtZ6yFwlxOKPVFyg==} + engines: {node: '>=12'} + cpu: [mips64el] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-ppc64@0.18.20: + resolution: {integrity: sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-ppc64@0.19.11: + resolution: {integrity: sha512-MHrZYLeCG8vXblMetWyttkdVRjQlQUb/oMgBNurVEnhj4YWOr4G5lmBfZjHYQHHN0g6yDmCAQRR8MUHldvvRDA==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-riscv64@0.18.20: + resolution: {integrity: sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==} + engines: {node: '>=12'} + cpu: [riscv64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-riscv64@0.19.11: + resolution: {integrity: sha512-f3DY++t94uVg141dozDu4CCUkYW+09rWtaWfnb3bqe4w5NqmZd6nPVBm+qbz7WaHZCoqXqHz5p6CM6qv3qnSSQ==} + engines: {node: '>=12'} + cpu: [riscv64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-s390x@0.18.20: + resolution: {integrity: sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==} + engines: {node: '>=12'} + cpu: [s390x] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-s390x@0.19.11: + resolution: {integrity: sha512-A5xdUoyWJHMMlcSMcPGVLzYzpcY8QP1RtYzX5/bS4dvjBGVxdhuiYyFwp7z74ocV7WDc0n1harxmpq2ePOjI0Q==} + engines: {node: '>=12'} + cpu: [s390x] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-x64@0.18.20: + resolution: {integrity: sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==} + engines: {node: '>=12'} + cpu: [x64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-x64@0.19.11: + resolution: {integrity: sha512-grbyMlVCvJSfxFQUndw5mCtWs5LO1gUlwP4CDi4iJBbVpZcqLVT29FxgGuBJGSzyOxotFG4LoO5X+M1350zmPA==} + engines: {node: '>=12'} + cpu: [x64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/netbsd-x64@0.18.20: + resolution: {integrity: sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==} + engines: {node: '>=12'} + cpu: [x64] + os: [netbsd] + requiresBuild: true + dev: true + optional: true + + /@esbuild/netbsd-x64@0.19.11: + resolution: {integrity: sha512-13jvrQZJc3P230OhU8xgwUnDeuC/9egsjTkXN49b3GcS5BKvJqZn86aGM8W9pd14Kd+u7HuFBMVtrNGhh6fHEQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [netbsd] + requiresBuild: true + dev: true + optional: true + + /@esbuild/openbsd-x64@0.18.20: + resolution: {integrity: sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==} + engines: {node: '>=12'} + cpu: [x64] + os: [openbsd] + requiresBuild: true + dev: true + optional: true + + /@esbuild/openbsd-x64@0.19.11: + resolution: {integrity: sha512-ysyOGZuTp6SNKPE11INDUeFVVQFrhcNDVUgSQVDzqsqX38DjhPEPATpid04LCoUr2WXhQTEZ8ct/EgJCUDpyNw==} + engines: {node: '>=12'} + cpu: [x64] + os: [openbsd] + requiresBuild: true + dev: true + optional: true + + /@esbuild/sunos-x64@0.18.20: + resolution: {integrity: sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [sunos] + requiresBuild: true + dev: true + optional: true + + /@esbuild/sunos-x64@0.19.11: + resolution: {integrity: sha512-Hf+Sad9nVwvtxy4DXCZQqLpgmRTQqyFyhT3bZ4F2XlJCjxGmRFF0Shwn9rzhOYRB61w9VMXUkxlBy56dk9JJiQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [sunos] + requiresBuild: true + dev: true + optional: true + + /@esbuild/win32-arm64@0.18.20: + resolution: {integrity: sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==} + engines: {node: '>=12'} + cpu: [arm64] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /@esbuild/win32-arm64@0.19.11: + resolution: {integrity: sha512-0P58Sbi0LctOMOQbpEOvOL44Ne0sqbS0XWHMvvrg6NE5jQ1xguCSSw9jQeUk2lfrXYsKDdOe6K+oZiwKPilYPQ==} + engines: {node: '>=12'} + cpu: [arm64] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /@esbuild/win32-ia32@0.18.20: + resolution: {integrity: sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==} + engines: {node: '>=12'} + cpu: [ia32] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /@esbuild/win32-ia32@0.19.11: + resolution: {integrity: sha512-6YOrWS+sDJDmshdBIQU+Uoyh7pQKrdykdefC1avn76ss5c+RN6gut3LZA4E2cH5xUEp5/cA0+YxRaVtRAb0xBg==} + engines: {node: '>=12'} + cpu: [ia32] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /@esbuild/win32-x64@0.18.20: + resolution: {integrity: sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /@esbuild/win32-x64@0.19.11: + resolution: {integrity: sha512-vfkhltrjCAb603XaFhqhAF4LGDi2M4OrCRrFusyQ+iTLQ/o60QQXxc9cZC/FFpihBI9N1Grn6SMKVJ4KP7Fuiw==} + engines: {node: '>=12'} + cpu: [x64] + os: [win32] + requiresBuild: true + dev: true + optional: true /@eslint-community/eslint-utils@4.4.0(eslint@8.56.0): resolution: {integrity: sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==} @@ -372,6 +779,7 @@ packages: /@jridgewell/resolve-uri@3.1.1: resolution: {integrity: sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==} engines: {node: '>=6.0.0'} + dev: true /@jridgewell/set-array@1.1.2: resolution: {integrity: sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==} @@ -387,6 +795,7 @@ packages: /@jridgewell/sourcemap-codec@1.4.15: resolution: {integrity: sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==} + dev: true /@jridgewell/trace-mapping@0.3.20: resolution: {integrity: sha512-R8LcPeWZol2zR8mmH3JeKQ6QRCFb7XgUhV9ZlGhHLGyg4wpPiPZNQOOWhFZhxKw8u//yTbNGI42Bx/3paXEQ+Q==} @@ -400,6 +809,7 @@ packages: dependencies: '@jridgewell/resolve-uri': 3.1.1 '@jridgewell/sourcemap-codec': 1.4.15 + dev: true /@knaadh/nestjs-drizzle-pg@1.0.0(@nestjs/common@9.0.8)(drizzle-orm@0.29.3)(pg@8.11.3): resolution: {integrity: sha512-21Ywzn/5y0QQz3cx1Nc2ZKfzW7LAjWCjATymkT148YECc+BIwpwvWvpejnjPOjFcyM0XfNT2axvRb0913GVfWg==} @@ -432,7 +842,7 @@ packages: - supports-color dev: false - /@nestjs/cli@9.0.0: + /@nestjs/cli@9.0.0(esbuild@0.19.11): resolution: {integrity: sha512-xT5uOoIEcaB/Fn6UeF7atfKqKiEEsTeRKPiM55p+e5H9WVw8FC2r4ceZgaINJbsw0QWskVj/ZQadMo6dA6hXxw==} engines: {node: '>= 12.9.0'} hasBin: true @@ -457,7 +867,7 @@ packages: tsconfig-paths: 3.14.1 tsconfig-paths-webpack-plugin: 3.5.2 typescript: 4.7.4 - webpack: 5.73.0 + webpack: 5.73.0(esbuild@0.19.11) webpack-node-externals: 3.0.0 transitivePeerDependencies: - '@swc/core' @@ -639,23 +1049,6 @@ packages: swagger-ui-dist: 5.10.3 dev: false - /@nestjs/typeorm@10.0.1(@nestjs/common@9.0.8)(@nestjs/core@9.0.8)(reflect-metadata@0.1.13)(rxjs@7.8.1)(typeorm@0.3.17): - resolution: {integrity: sha512-YVFYL7D25VAVp5/G+KLXIgsRfYomA+VaFZBpm2rtwrrBOmkXNrxr7kuI2bBBO/Xy4kKBDe6wbvIVVFeEA7/ngA==} - peerDependencies: - '@nestjs/common': ^8.0.0 || ^9.0.0 || ^10.0.0 - '@nestjs/core': ^8.0.0 || ^9.0.0 || ^10.0.0 - reflect-metadata: ^0.1.13 - rxjs: ^7.2.0 - typeorm: ^0.3.0 - dependencies: - '@nestjs/common': 9.0.8(class-transformer@0.5.1)(class-validator@0.14.0)(reflect-metadata@0.1.13)(rxjs@7.8.1) - '@nestjs/core': 9.0.8(@nestjs/common@9.0.8)(@nestjs/platform-express@9.0.0)(reflect-metadata@0.1.13)(rxjs@7.8.1) - reflect-metadata: 0.1.13 - rxjs: 7.8.1 - typeorm: 0.3.17(pg@8.11.3)(ts-node@10.9.2) - uuid: 9.0.1 - dev: false - /@nodelib/fs.scandir@2.1.5: resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} engines: {node: '>= 8'} @@ -701,21 +1094,21 @@ packages: tslib: 2.6.2 dev: true - /@sqltools/formatter@1.2.5: - resolution: {integrity: sha512-Uy0+khmZqUrUGm5dmMqVlnvufZRSK0FbYzVgp0UMstm+F5+W2/jnEEQyc9vo1ZR/E5ZI/B1WjjoTqBqwJL6Krw==} - dev: false - /@tsconfig/node10@1.0.9: resolution: {integrity: sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==} + dev: true /@tsconfig/node12@1.0.11: resolution: {integrity: sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==} + dev: true /@tsconfig/node14@1.0.3: resolution: {integrity: sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==} + dev: true /@tsconfig/node16@1.0.4: resolution: {integrity: sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==} + dev: true /@types/bcrypt@5.0.2: resolution: {integrity: sha512-6atioO8Y75fNcbmj0G7UjI9lXN2pQ/IGJ2FWT4a/btd0Lk9lQalHLKhkgKVZ3r+spnmWUKfbMi1GEe9wyHQfNQ==} @@ -1267,11 +1660,13 @@ packages: /acorn-walk@8.3.1: resolution: {integrity: sha512-TgUZgYvqZprrl7YldZNoa9OciCAyZR+Ejm9eXzKCmjsF5IKp/wgQ7Z/ZpjpGTIUPwrHQIcYeI8qDh4PsEwxMbw==} engines: {node: '>=0.4.0'} + dev: true /acorn@8.11.2: resolution: {integrity: sha512-nc0Axzp/0FILLEVsm4fNwLCwMttvhEI263QtVPQcbpfZZ3ts0hLsZGOpE6czNlid7CJ9MlyH8reXkpsf3YUY4w==} engines: {node: '>=0.4.0'} hasBin: true + dev: true /agent-base@6.0.2: resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==} @@ -1348,10 +1743,6 @@ packages: dependencies: color-convert: 2.0.1 - /any-promise@1.3.0: - resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==} - dev: false - /anymatch@3.1.3: resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} engines: {node: '>= 8'} @@ -1360,11 +1751,6 @@ packages: picomatch: 2.3.1 dev: true - /app-root-path@3.1.0: - resolution: {integrity: sha512-biN3PwB2gUtjaYy/isrU3aNWI5w+fAfvHkSvCKeQGxhmYpwKFUxudR3Yya+KqVRHBmEDYh+/lTozYCFbmzX4nA==} - engines: {node: '>= 6.0.0'} - dev: false - /append-field@1.0.0: resolution: {integrity: sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw==} dev: false @@ -1383,6 +1769,7 @@ packages: /arg@4.1.3: resolution: {integrity: sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==} + dev: true /argparse@2.0.1: resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} @@ -1401,6 +1788,7 @@ packages: /base64-js@1.5.1: resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} + dev: true /bcrypt@5.1.1: resolution: {integrity: sha512-AGBHOG5hPYZ5Xl9KXzU5iKq9516yEmvCKDg3ecP5kX2aB6UqTeXZxk2ELnDgDm6BQSMlLt9rDB4LoSMx0rYwww==} @@ -1469,7 +1857,7 @@ packages: resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==} dependencies: balanced-match: 1.0.2 - dev: false + dev: true /braces@3.0.2: resolution: {integrity: sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==} @@ -1508,13 +1896,6 @@ packages: ieee754: 1.2.1 dev: true - /buffer@6.0.3: - resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==} - dependencies: - base64-js: 1.5.1 - ieee754: 1.2.1 - dev: false - /bundle-name@3.0.0: resolution: {integrity: sha512-PKA4BeSvBpQKQ8iPOGCSiell+N8P+Tf1DlwqmYhpe2gAhKPHn8EYOxVT+ShuGmhg8lN8XiSlS80yiExKXrURlw==} engines: {node: '>=12'} @@ -1547,6 +1928,11 @@ packages: engines: {node: '>=6'} dev: true + /camelcase@7.0.1: + resolution: {integrity: sha512-xlx1yCK2Oc1APsPXDL2LdlNP6+uu8OCDdhOBSVT279M/S+y75O30C2VuD8T2ogdePBBl7PfPF4504tnLgX3zfw==} + engines: {node: '>=14.16'} + dev: true + /caniuse-lite@1.0.30001570: resolution: {integrity: sha512-+3e0ASu4sw1SWaoCtvPeyXp+5PsjigkSt8OXZbF9StH5pQWbxEjLAZE3n8Aup5udop1uRiKA7a4utUk/uoSpUw==} dev: true @@ -1575,6 +1961,11 @@ packages: ansi-styles: 4.3.0 supports-color: 7.2.0 + /chalk@5.3.0: + resolution: {integrity: sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==} + engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} + dev: true + /chardet@0.7.0: resolution: {integrity: sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==} dev: true @@ -1616,25 +2007,23 @@ packages: validator: 13.11.0 dev: false - /cli-cursor@3.1.0: - resolution: {integrity: sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==} - engines: {node: '>=8'} + /cli-color@2.0.3: + resolution: {integrity: sha512-OkoZnxyC4ERN3zLzZaY9Emb7f/MhBOIpePv0Ycok0fJYT+Ouo00UBEIwsVsr0yoow++n5YWlSUgST9GKhNHiRQ==} + engines: {node: '>=0.10'} dependencies: - restore-cursor: 3.1.0 + d: 1.0.1 + es5-ext: 0.10.62 + es6-iterator: 2.0.3 + memoizee: 0.4.15 + timers-ext: 0.1.7 dev: true - /cli-highlight@2.1.11: - resolution: {integrity: sha512-9KDcoEVwyUXrjcJNvHD0NFc/hiwe/WPVYIleQh2O1N2Zro5gWJZ/K+3DGn8w8P/F6FxOgzyC5bxDyHIgCSPhGg==} - engines: {node: '>=8.0.0', npm: '>=5.0.0'} - hasBin: true + /cli-cursor@3.1.0: + resolution: {integrity: sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==} + engines: {node: '>=8'} dependencies: - chalk: 4.1.2 - highlight.js: 10.7.3 - mz: 2.7.0 - parse5: 5.1.1 - parse5-htmlparser2-tree-adapter: 6.0.1 - yargs: 16.2.0 - dev: false + restore-cursor: 3.1.0 + dev: true /cli-spinners@2.9.2: resolution: {integrity: sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==} @@ -1655,23 +2044,6 @@ packages: engines: {node: '>= 10'} dev: true - /cliui@7.0.4: - resolution: {integrity: sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==} - dependencies: - string-width: 4.2.3 - strip-ansi: 6.0.1 - wrap-ansi: 7.0.0 - dev: false - - /cliui@8.0.1: - resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} - engines: {node: '>=12'} - dependencies: - string-width: 4.2.3 - strip-ansi: 6.0.1 - wrap-ansi: 7.0.0 - dev: false - /clone@1.0.4: resolution: {integrity: sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==} engines: {node: '>=0.8'} @@ -1710,6 +2082,11 @@ packages: engines: {node: '>= 6'} dev: true + /commander@9.5.0: + resolution: {integrity: sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==} + engines: {node: ^12.20.0 || >=14} + dev: true + /concat-map@0.0.1: resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} @@ -1752,6 +2129,13 @@ packages: engines: {node: '>= 0.6'} dev: false + /copy-anything@3.0.5: + resolution: {integrity: sha512-yCEafptTtb4bk7GLEQoM8KVJpxAfdBJYaXyzQEgQQQgYrZiDp8SJmGKlYza6CYjEDNstAdNdKA3UuoULlEbS6w==} + engines: {node: '>=12.13'} + dependencies: + is-what: 4.1.16 + dev: true + /core-util-is@1.0.3: resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==} dev: false @@ -1777,6 +2161,7 @@ packages: /create-require@1.1.1: resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==} + dev: true /cross-spawn@7.0.3: resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==} @@ -1787,12 +2172,12 @@ packages: which: 2.0.2 dev: true - /date-fns@2.30.0: - resolution: {integrity: sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==} - engines: {node: '>=0.11'} + /d@1.0.1: + resolution: {integrity: sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA==} dependencies: - '@babel/runtime': 7.23.6 - dev: false + es5-ext: 0.10.62 + type: 1.2.0 + dev: true /debug@2.6.9: resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==} @@ -1885,6 +2270,13 @@ packages: /diff@4.0.2: resolution: {integrity: sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==} engines: {node: '>=0.3.1'} + dev: true + + /difflib@0.2.4: + resolution: {integrity: sha512-9YVwmMb0wQHQNr5J9m6BSj6fk4pfGITGQOOs+D9Fl+INODWFOfvhIU1hNv6GgR1RBoC/9NJcwu77zShxV0kT7w==} + dependencies: + heap: 0.2.7 + dev: true /dir-glob@3.0.1: resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} @@ -1910,6 +2302,35 @@ packages: engines: {node: '>=12'} dev: false + /dreamopt@0.8.0: + resolution: {integrity: sha512-vyJTp8+mC+G+5dfgsY+r3ckxlz+QMX40VjPQsZc5gxVAxLmi64TBoVkP54A/pRAXMXsbu2GMMBrZPxNv23waMg==} + engines: {node: '>=0.4.0'} + dependencies: + wordwrap: 1.0.0 + dev: true + + /drizzle-kit@0.20.13: + resolution: {integrity: sha512-j9oZSQXNWG+KBJm0Sg3S/zJpncHGKnpqNfFuM4NUxUMGTcihDHhP9SW6Jncqwb5vsP1Xm0a8JLm3PZUIspC/oA==} + hasBin: true + dependencies: + '@drizzle-team/studio': 0.0.39 + '@esbuild-kit/esm-loader': 2.6.5 + camelcase: 7.0.1 + chalk: 5.3.0 + commander: 9.5.0 + env-paths: 3.0.0 + esbuild: 0.19.11 + esbuild-register: 3.5.0(esbuild@0.19.11) + glob: 8.1.0 + hanji: 0.0.5 + json-diff: 0.9.0 + minimatch: 7.4.6 + semver: 7.5.4 + zod: 3.22.4 + transitivePeerDependencies: + - supports-color + dev: true + /drizzle-orm@0.29.3(@types/pg@8.10.9)(pg@8.11.3): resolution: {integrity: sha512-uSE027csliGSGYD0pqtM+SAQATMREb3eSM/U8s6r+Y0RFwTKwftnwwSkqx3oS65UBgqDOM0gMTl5UGNpt6lW0A==} peerDependencies: @@ -2021,6 +2442,11 @@ packages: tapable: 2.2.1 dev: true + /env-paths@3.0.0: + resolution: {integrity: sha512-dtJUTepzMW3Lm/NPxRf3wP4642UWhjL2sQxc+ym2YMj1m/H2zDNQOlezafzkHwn6sMstjHTwG6iQQsctDW/b1A==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + dev: true + /error-ex@1.3.2: resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==} dependencies: @@ -2035,9 +2461,116 @@ packages: resolution: {integrity: sha512-cXLGjP0c4T3flZJKQSuziYoq7MlT+rnvfZjfp7h+I7K9BNX54kP9nyWvdbwjQ4u1iWbOL4u96fgeZLToQlZC7w==} dev: true + /es5-ext@0.10.62: + resolution: {integrity: sha512-BHLqn0klhEpnOKSrzn/Xsz2UIW8j+cGmo9JLzr8BiUapV8hPL9+FliFqjwr9ngW7jWdnxv6eO+/LqyhJVqgrjA==} + engines: {node: '>=0.10'} + requiresBuild: true + dependencies: + es6-iterator: 2.0.3 + es6-symbol: 3.1.3 + next-tick: 1.1.0 + dev: true + + /es6-iterator@2.0.3: + resolution: {integrity: sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g==} + dependencies: + d: 1.0.1 + es5-ext: 0.10.62 + es6-symbol: 3.1.3 + dev: true + + /es6-symbol@3.1.3: + resolution: {integrity: sha512-NJ6Yn3FuDinBaBRWl/q5X/s4koRHBrgKAu+yGI6JCBeiu3qrcbJhwT2GeR/EXVfylRk8dpQVJoLEFhK+Mu31NA==} + dependencies: + d: 1.0.1 + ext: 1.7.0 + dev: true + + /es6-weak-map@2.0.3: + resolution: {integrity: sha512-p5um32HOTO1kP+w7PRnB+5lQ43Z6muuMuIMffvDN8ZB4GcnjLBV6zGStpbASIMk4DCAvEaamhe2zhyCb/QXXsA==} + dependencies: + d: 1.0.1 + es5-ext: 0.10.62 + es6-iterator: 2.0.3 + es6-symbol: 3.1.3 + dev: true + + /esbuild-register@3.5.0(esbuild@0.19.11): + resolution: {integrity: sha512-+4G/XmakeBAsvJuDugJvtyF1x+XJT4FMocynNpxrvEBViirpfUn2PgNpCHedfWhF4WokNsO/OvMKrmJOIJsI5A==} + peerDependencies: + esbuild: '>=0.12 <1' + dependencies: + debug: 4.3.4 + esbuild: 0.19.11 + transitivePeerDependencies: + - supports-color + dev: true + + /esbuild@0.18.20: + resolution: {integrity: sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==} + engines: {node: '>=12'} + hasBin: true + requiresBuild: true + optionalDependencies: + '@esbuild/android-arm': 0.18.20 + '@esbuild/android-arm64': 0.18.20 + '@esbuild/android-x64': 0.18.20 + '@esbuild/darwin-arm64': 0.18.20 + '@esbuild/darwin-x64': 0.18.20 + '@esbuild/freebsd-arm64': 0.18.20 + '@esbuild/freebsd-x64': 0.18.20 + '@esbuild/linux-arm': 0.18.20 + '@esbuild/linux-arm64': 0.18.20 + '@esbuild/linux-ia32': 0.18.20 + '@esbuild/linux-loong64': 0.18.20 + '@esbuild/linux-mips64el': 0.18.20 + '@esbuild/linux-ppc64': 0.18.20 + '@esbuild/linux-riscv64': 0.18.20 + '@esbuild/linux-s390x': 0.18.20 + '@esbuild/linux-x64': 0.18.20 + '@esbuild/netbsd-x64': 0.18.20 + '@esbuild/openbsd-x64': 0.18.20 + '@esbuild/sunos-x64': 0.18.20 + '@esbuild/win32-arm64': 0.18.20 + '@esbuild/win32-ia32': 0.18.20 + '@esbuild/win32-x64': 0.18.20 + dev: true + + /esbuild@0.19.11: + resolution: {integrity: sha512-HJ96Hev2hX/6i5cDVwcqiJBBtuo9+FeIJOtZ9W1kA5M6AMJRHUZlpYZ1/SbEwtO0ioNAW8rUooVpC/WehY2SfA==} + engines: {node: '>=12'} + hasBin: true + requiresBuild: true + optionalDependencies: + '@esbuild/aix-ppc64': 0.19.11 + '@esbuild/android-arm': 0.19.11 + '@esbuild/android-arm64': 0.19.11 + '@esbuild/android-x64': 0.19.11 + '@esbuild/darwin-arm64': 0.19.11 + '@esbuild/darwin-x64': 0.19.11 + '@esbuild/freebsd-arm64': 0.19.11 + '@esbuild/freebsd-x64': 0.19.11 + '@esbuild/linux-arm': 0.19.11 + '@esbuild/linux-arm64': 0.19.11 + '@esbuild/linux-ia32': 0.19.11 + '@esbuild/linux-loong64': 0.19.11 + '@esbuild/linux-mips64el': 0.19.11 + '@esbuild/linux-ppc64': 0.19.11 + '@esbuild/linux-riscv64': 0.19.11 + '@esbuild/linux-s390x': 0.19.11 + '@esbuild/linux-x64': 0.19.11 + '@esbuild/netbsd-x64': 0.19.11 + '@esbuild/openbsd-x64': 0.19.11 + '@esbuild/sunos-x64': 0.19.11 + '@esbuild/win32-arm64': 0.19.11 + '@esbuild/win32-ia32': 0.19.11 + '@esbuild/win32-x64': 0.19.11 + dev: true + /escalade@3.1.1: resolution: {integrity: sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==} engines: {node: '>=6'} + dev: true /escape-html@1.0.3: resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==} @@ -2194,6 +2727,13 @@ packages: engines: {node: '>= 0.6'} dev: false + /event-emitter@0.3.5: + resolution: {integrity: sha512-D9rRn9y7kLPnJ+hMq7S/nhvoKwwvVJahBi2BPmx3bvbsEdK3W9ii8cBSGjP+72/LnM4n6fo3+dkCX5FeTQruXA==} + dependencies: + d: 1.0.1 + es5-ext: 0.10.62 + dev: true + /events@3.3.0: resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==} engines: {node: '>=0.8.x'} @@ -2283,6 +2823,12 @@ packages: - supports-color dev: false + /ext@1.7.0: + resolution: {integrity: sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw==} + dependencies: + type: 2.7.2 + dev: true + /external-editor@3.1.0: resolution: {integrity: sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==} engines: {node: '>=4'} @@ -2409,7 +2955,7 @@ packages: semver: 7.5.4 tapable: 2.2.1 typescript: 4.7.4 - webpack: 5.73.0 + webpack: 5.73.0(esbuild@0.19.11) dev: true /forwarded@0.2.0: @@ -2471,11 +3017,6 @@ packages: wide-align: 1.1.5 dev: false - /get-caller-file@2.0.5: - resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} - engines: {node: 6.* || 8.* || >= 10.*} - dev: false - /get-intrinsic@1.2.2: resolution: {integrity: sha512-0gSo4ml/0j98Y3lngkFEot/zhiCeWsbYIlZ+uZOVgzLyLaUw7wxUL+nCTP0XJvJg1AXulJRI3UJi8GsbDuxdGA==} dependencies: @@ -2497,6 +3038,12 @@ packages: engines: {node: '>=10'} dev: true + /get-tsconfig@4.7.2: + resolution: {integrity: sha512-wuMsz4leaj5hbGgg4IvDU0bqJagpftG5l5cXIAvo8uZrqn0NJqwtfupTN00VnkQJPcIRrxYrm1Ue24btpCha2A==} + dependencies: + resolve-pkg-maps: 1.0.0 + dev: true + /glob-parent@5.1.2: resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} engines: {node: '>= 6'} @@ -2534,7 +3081,7 @@ packages: inherits: 2.0.4 minimatch: 5.1.6 once: 1.4.0 - dev: false + dev: true /globals@13.24.0: resolution: {integrity: sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==} @@ -2569,6 +3116,13 @@ packages: resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} dev: true + /hanji@0.0.5: + resolution: {integrity: sha512-Abxw1Lq+TnYiL4BueXqMau222fPSPMFtya8HdpWsz/xVAhifXou71mPh/kY2+08RgFcVccjG3uZHs6K5HAe3zw==} + dependencies: + lodash.throttle: 4.1.1 + sisteransi: 1.0.5 + dev: true + /has-flag@3.0.0: resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==} engines: {node: '>=4'} @@ -2604,9 +3158,9 @@ packages: dependencies: function-bind: 1.1.2 - /highlight.js@10.7.3: - resolution: {integrity: sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==} - dev: false + /heap@0.2.7: + resolution: {integrity: sha512-2bsegYkkHO+h/9MGbn6KWcE45cHZgPANo5LXF7EvWdT0yT2EguSVO1nDgU5c8+ZOPwp2vMNa7YFsJhVcDR9Sdg==} + dev: true /http-errors@2.0.0: resolution: {integrity: sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==} @@ -2652,6 +3206,7 @@ packages: /ieee754@1.2.1: resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} + dev: true /ignore@5.3.0: resolution: {integrity: sha512-g7dmpshy+gD7mh88OC9NwSGTKoc3kyLAZQRU1mt53Aw/vnvfXnbC+F/7F7QoYVKbV+KNvJx8wArewKy1vXMtlg==} @@ -2798,6 +3353,10 @@ packages: engines: {node: '>=8'} dev: true + /is-promise@2.2.2: + resolution: {integrity: sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ==} + dev: true + /is-stream@2.0.1: resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==} engines: {node: '>=8'} @@ -2813,6 +3372,11 @@ packages: engines: {node: '>=10'} dev: true + /is-what@4.1.16: + resolution: {integrity: sha512-ZhMwEosbFJkA0YhFnNDgTM4ZxDRsS6HqTo7qsZM08fehyRYIYa0yHu5R6mgo1n/8MgaPBXiPimPD77baVFYg+A==} + engines: {node: '>=12.13'} + dev: true + /is-wsl@2.2.0: resolution: {integrity: sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==} engines: {node: '>=8'} @@ -2856,6 +3420,15 @@ packages: resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} dev: true + /json-diff@0.9.0: + resolution: {integrity: sha512-cVnggDrVkAAA3OvFfHpFEhOnmcsUpleEKq4d4O8sQWWSH40MBrWstKigVB1kGrgLWzuom+7rRdaCsnBD6VyObQ==} + hasBin: true + dependencies: + cli-color: 2.0.3 + difflib: 0.2.4 + dreamopt: 0.8.0 + dev: true + /json-parse-even-better-errors@2.3.1: resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==} dev: true @@ -2994,9 +3567,9 @@ packages: resolution: {integrity: sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==} dev: false - /lodash.xor@4.5.0: - resolution: {integrity: sha512-sVN2zimthq7aZ5sPGXnSz32rZPuqcparVW50chJQe+mzTYV+IsxSsl/2gnkWWE2Of7K3myBQBqtLKOUEHJKRsQ==} - dev: false + /lodash.throttle@4.1.1: + resolution: {integrity: sha512-wIkUCfVKpVsWo3JSZlc+8MB5it+2AN5W8J7YVMST30UrvcQNZ1Okbj+rbVniijTWE6FGYy4XJq/rHkas8qJMLQ==} + dev: true /lodash@4.17.21: resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} @@ -3015,6 +3588,12 @@ packages: dependencies: yallist: 4.0.0 + /lru-queue@0.1.0: + resolution: {integrity: sha512-BpdYkt9EvGl8OfWHDQPISVpcl5xZthb+XPsbELj5AQXxIC8IriDZIQYjBJPEm5rS420sjZ0TLEzRcq5KdBhYrQ==} + dependencies: + es5-ext: 0.10.62 + dev: true + /macos-release@2.5.1: resolution: {integrity: sha512-DXqXhEM7gW59OjZO8NIjBCz9AQ1BEMrfiOAl4AYByHCtVHRF4KoGNO8mqQeM8lRCtQe/UnJ4imO/d2HdkKsd+A==} engines: {node: '>=6'} @@ -3036,6 +3615,7 @@ packages: /make-error@1.3.6: resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==} + dev: true /media-typer@0.3.0: resolution: {integrity: sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==} @@ -3049,6 +3629,19 @@ packages: fs-monkey: 1.0.5 dev: true + /memoizee@0.4.15: + resolution: {integrity: sha512-UBWmJpLZd5STPm7PMUlOw/TSy972M+z8gcyQ5veOnSDRREz/0bmpyTfKt3/51DhEBqCZQn1udM/5flcSPYhkdQ==} + dependencies: + d: 1.0.1 + es5-ext: 0.10.62 + es6-weak-map: 2.0.3 + event-emitter: 0.3.5 + is-promise: 2.2.2 + lru-queue: 0.1.0 + next-tick: 1.1.0 + timers-ext: 0.1.7 + dev: true + /merge-descriptors@1.0.1: resolution: {integrity: sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==} dev: false @@ -3111,7 +3704,14 @@ packages: engines: {node: '>=10'} dependencies: brace-expansion: 2.0.1 - dev: false + dev: true + + /minimatch@7.4.6: + resolution: {integrity: sha512-sBz8G/YjVniEz6lKPNpKxXwazJe4c19fEfV2GDMX6AjFz+MX9uDWIZW8XreVhkFW3fkIdTv/gxWr/Kks5FFAVw==} + engines: {node: '>=10'} + dependencies: + brace-expansion: 2.0.1 + dev: true /minimist@1.2.8: resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} @@ -3149,12 +3749,6 @@ packages: hasBin: true dev: false - /mkdirp@2.1.6: - resolution: {integrity: sha512-+hEnITedc8LAtIP9u3HJDFIdcLV2vXP33sqLLIzkv1Db1zO/1OxbvYf0Y1OC/S/Qo5dxHXepofhmxL02PsKe+A==} - engines: {node: '>=10'} - hasBin: true - dev: false - /ms@2.0.0: resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==} dev: false @@ -3183,14 +3777,6 @@ packages: resolution: {integrity: sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==} dev: true - /mz@2.7.0: - resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==} - dependencies: - any-promise: 1.3.0 - object-assign: 4.1.1 - thenify-all: 1.6.0 - dev: false - /natural-compare@1.4.0: resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} dev: true @@ -3204,23 +3790,6 @@ packages: resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==} dev: true - /nest-transact@9.1.2(@nestjs/common@9.0.8)(@nestjs/core@9.0.8)(reflect-metadata@0.1.13)(rxjs@7.8.1)(typeorm@0.3.17): - resolution: {integrity: sha512-WkL2p8CFD0A7vqyK2tG8TNqCxm0xSgQnLFX9bxuhUsrPKpMgD0HUnnHxg4u+IUHhFpPg0pRqYvSmTiVFEwXzrA==} - peerDependencies: - '@nestjs/common': ^9.0.8 - '@nestjs/core': ^9.0.8 - reflect-metadata: ^0.1.13 - rxjs: ^7.5.6 - typeorm: ^0.3.7 - dependencies: - '@nestjs/common': 9.0.8(class-transformer@0.5.1)(class-validator@0.14.0)(reflect-metadata@0.1.13)(rxjs@7.8.1) - '@nestjs/core': 9.0.8(@nestjs/common@9.0.8)(@nestjs/platform-express@9.0.0)(reflect-metadata@0.1.13)(rxjs@7.8.1) - lodash.xor: 4.5.0 - reflect-metadata: 0.1.13 - rxjs: 7.8.1 - typeorm: 0.3.17(pg@8.11.3)(ts-node@10.9.2) - dev: false - /nestjs-seeder@0.3.2(@faker-js/faker@8.3.1): resolution: {integrity: sha512-dCuxmhWdjgAn1HT0K7ExuCmKQc7HCGihFgzYF4keVJkR94d+diVQQg9/K1aQFnBkH9XVuugUqi4OClfwt09XVQ==} peerDependencies: @@ -3229,15 +3798,9 @@ packages: '@faker-js/faker': 8.3.1 dev: false - /nestjs-typeorm-paginate@4.0.4(@nestjs/common@9.0.8)(typeorm@0.3.17): - resolution: {integrity: sha512-arinWDc78wPV/EYWMmLYyeMSE5Lae1FHWD/2QpOdTmHaOVqK4PYf19EqZBqT9gbbPugkNW9JAMz3G2WmvSgR/A==} - peerDependencies: - '@nestjs/common': ^6.1.1 || ^5.6.2 || ^7.0.0 || ^8.0.0 || ^9.0.0 || ^10.0.0 - typeorm: ^0.3.0 - dependencies: - '@nestjs/common': 9.0.8(class-transformer@0.5.1)(class-validator@0.14.0)(reflect-metadata@0.1.13)(rxjs@7.8.1) - typeorm: 0.3.17(pg@8.11.3)(ts-node@10.9.2) - dev: false + /next-tick@1.1.0: + resolution: {integrity: sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==} + dev: true /node-addon-api@5.1.0: resolution: {integrity: sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA==} @@ -3429,20 +3992,6 @@ packages: lines-and-columns: 1.2.4 dev: true - /parse5-htmlparser2-tree-adapter@6.0.1: - resolution: {integrity: sha512-qPuWvbLgvDGilKc5BoicRovlT4MtYT6JfJyBOMDsKoiT+GiuP5qyrPCnR9HcPECIJJmZh5jRndyNThnhhb/vlA==} - dependencies: - parse5: 6.0.1 - dev: false - - /parse5@5.1.1: - resolution: {integrity: sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug==} - dev: false - - /parse5@6.0.1: - resolution: {integrity: sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==} - dev: false - /parseurl@1.3.3: resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} engines: {node: '>= 0.8'} @@ -3763,15 +4312,6 @@ packages: resolution: {integrity: sha512-Ts1Y/anZELhSsjMcU605fU9RE4Oi3p5ORujwbIKXfWa+0Zxs510Qrmrce5/Jowq3cHSZSJqBjypxmHarc+vEWg==} dev: false - /regenerator-runtime@0.14.1: - resolution: {integrity: sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==} - dev: false - - /require-directory@2.1.1: - resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} - engines: {node: '>=0.10.0'} - dev: false - /require-from-string@2.0.2: resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} engines: {node: '>=0.10.0'} @@ -3782,6 +4322,10 @@ packages: engines: {node: '>=4'} dev: true + /resolve-pkg-maps@1.0.0: + resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} + dev: true + /resolve@1.22.8: resolution: {integrity: sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==} hasBin: true @@ -3928,14 +4472,6 @@ packages: resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} dev: false - /sha.js@2.4.11: - resolution: {integrity: sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==} - hasBin: true - dependencies: - inherits: 2.0.4 - safe-buffer: 5.2.1 - dev: false - /shebang-command@2.0.0: resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} engines: {node: '>=8'} @@ -3969,6 +4505,10 @@ packages: /signal-exit@3.0.7: resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} + /sisteransi@1.0.5: + resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==} + dev: true + /slash@3.0.0: resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} engines: {node: '>=8'} @@ -4061,6 +4601,13 @@ packages: engines: {node: '>=8'} dev: true + /superjson@2.2.1: + resolution: {integrity: sha512-8iGv75BYOa0xRJHK5vRLEjE2H/i4lulTjzpUXic3Eg8akftYjkmQDa8JARQ42rlczXyFR3IeRoeFCc7RxHsYZA==} + engines: {node: '>=16'} + dependencies: + copy-anything: 3.0.5 + dev: true + /supports-color@5.5.0: resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==} engines: {node: '>=4'} @@ -4120,7 +4667,7 @@ packages: yallist: 4.0.0 dev: false - /terser-webpack-plugin@5.3.9(webpack@5.73.0): + /terser-webpack-plugin@5.3.9(esbuild@0.19.11)(webpack@5.73.0): resolution: {integrity: sha512-ZuXsqE07EcggTWQjXUj+Aot/OMcD0bMKGgF63f7UxYcu5/AJF53aIpK1YoP5xR9l6s/Hy2b+t1AM0bLNPRuhwA==} engines: {node: '>= 10.13.0'} peerDependencies: @@ -4137,14 +4684,15 @@ packages: optional: true dependencies: '@jridgewell/trace-mapping': 0.3.20 + esbuild: 0.19.11 jest-worker: 27.5.1 schema-utils: 3.3.0 serialize-javascript: 6.0.1 terser: 5.26.0 - webpack: 5.73.0 + webpack: 5.73.0(esbuild@0.19.11) dev: true - /terser-webpack-plugin@5.3.9(webpack@5.89.0): + /terser-webpack-plugin@5.3.9(esbuild@0.19.11)(webpack@5.89.0): resolution: {integrity: sha512-ZuXsqE07EcggTWQjXUj+Aot/OMcD0bMKGgF63f7UxYcu5/AJF53aIpK1YoP5xR9l6s/Hy2b+t1AM0bLNPRuhwA==} engines: {node: '>= 10.13.0'} peerDependencies: @@ -4161,11 +4709,12 @@ packages: optional: true dependencies: '@jridgewell/trace-mapping': 0.3.20 + esbuild: 0.19.11 jest-worker: 27.5.1 schema-utils: 3.3.0 serialize-javascript: 6.0.1 terser: 5.26.0 - webpack: 5.89.0 + webpack: 5.89.0(esbuild@0.19.11) dev: true /terser@5.26.0: @@ -4183,23 +4732,17 @@ packages: resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==} dev: true - /thenify-all@1.6.0: - resolution: {integrity: sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==} - engines: {node: '>=0.8'} - dependencies: - thenify: 3.3.1 - dev: false - - /thenify@3.3.1: - resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==} - dependencies: - any-promise: 1.3.0 - dev: false - /through@2.3.8: resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==} dev: true + /timers-ext@0.1.7: + resolution: {integrity: sha512-b85NUNzTSdodShTIbky6ZF02e8STtVVfD+fu4aXXShEELpozH+bCpJLYMPZbsABN2wDH7fJpqIoXxJpzbf0NqQ==} + dependencies: + es5-ext: 0.10.62 + next-tick: 1.1.0 + dev: true + /titleize@3.0.0: resolution: {integrity: sha512-KxVu8EYHDPBdUYdKZdKtU2aj2XfEx9AfjXxE/Aj0vT06w2icA09Vus1rh6eSu1y01akYg6BjIK/hxyLJINoMLQ==} engines: {node: '>=12'} @@ -4255,7 +4798,7 @@ packages: semver: 7.5.4 source-map: 0.7.4 typescript: 4.7.4 - webpack: 5.89.0 + webpack: 5.89.0(esbuild@0.19.11) dev: true /ts-node@10.9.2(@types/node@20.10.5)(typescript@4.7.4): @@ -4287,6 +4830,7 @@ packages: typescript: 4.7.4 v8-compile-cache-lib: 3.0.1 yn: 3.1.1 + dev: true /tsconfig-paths-webpack-plugin@3.5.2: resolution: {integrity: sha512-EhnfjHbzm5IYI9YPNVIxx1moxMI4bpHD2e0zTXeDNQcwjjRaGepP7IhTHJkyDBG0CAOoxRfe7jCG630Ou+C6Pw==} @@ -4350,101 +4894,23 @@ packages: mime-types: 2.1.35 dev: false - /typedarray@0.0.6: - resolution: {integrity: sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==} - dev: false + /type@1.2.0: + resolution: {integrity: sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg==} + dev: true - /typeorm-naming-strategies@4.1.0(typeorm@0.3.17): - resolution: {integrity: sha512-vPekJXzZOTZrdDvTl1YoM+w+sUIfQHG4kZTpbFYoTsufyv9NIBRe4Q+PdzhEAFA2std3D9LZHEb1EjE9zhRpiQ==} - peerDependencies: - typeorm: ^0.2.0 || ^0.3.0 - dependencies: - typeorm: 0.3.17(pg@8.11.3)(ts-node@10.9.2) - dev: false + /type@2.7.2: + resolution: {integrity: sha512-dzlvlNlt6AXU7EBSfpAscydQ7gXB+pPGsPnfJnZpiNJBDj7IaJzQlBZYGdEi4R9HmPdBv2XmWJ6YUtoTa7lmCw==} + dev: true - /typeorm@0.3.17(pg@8.11.3)(ts-node@10.9.2): - resolution: {integrity: sha512-UDjUEwIQalO9tWw9O2A4GU+sT3oyoUXheHJy4ft+RFdnRdQctdQ34L9SqE2p7LdwzafHx1maxT+bqXON+Qnmig==} - engines: {node: '>= 12.9.0'} - hasBin: true - peerDependencies: - '@google-cloud/spanner': ^5.18.0 - '@sap/hana-client': ^2.12.25 - better-sqlite3: ^7.1.2 || ^8.0.0 - hdb-pool: ^0.1.6 - ioredis: ^5.0.4 - mongodb: ^5.2.0 - mssql: ^9.1.1 - mysql2: ^2.2.5 || ^3.0.1 - oracledb: ^5.1.0 - pg: ^8.5.1 - pg-native: ^3.0.0 - pg-query-stream: ^4.0.0 - redis: ^3.1.1 || ^4.0.0 - sql.js: ^1.4.0 - sqlite3: ^5.0.3 - ts-node: ^10.7.0 - typeorm-aurora-data-api-driver: ^2.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 - dependencies: - '@sqltools/formatter': 1.2.5 - app-root-path: 3.1.0 - buffer: 6.0.3 - chalk: 4.1.2 - cli-highlight: 2.1.11 - date-fns: 2.30.0 - debug: 4.3.4 - dotenv: 16.3.1 - glob: 8.1.0 - mkdirp: 2.1.6 - pg: 8.11.3 - reflect-metadata: 0.1.13 - sha.js: 2.4.11 - ts-node: 10.9.2(@types/node@20.10.5)(typescript@4.7.4) - tslib: 2.6.2 - uuid: 9.0.1 - yargs: 17.7.2 - transitivePeerDependencies: - - supports-color + /typedarray@0.0.6: + resolution: {integrity: sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==} dev: false /typescript@4.7.4: resolution: {integrity: sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ==} engines: {node: '>=4.2.0'} hasBin: true + dev: true /undici-types@5.26.5: resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==} @@ -4499,13 +4965,9 @@ packages: hasBin: true dev: false - /uuid@9.0.1: - resolution: {integrity: sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==} - hasBin: true - dev: false - /v8-compile-cache-lib@3.0.1: resolution: {integrity: sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==} + dev: true /validator@13.11.0: resolution: {integrity: sha512-Ii+sehpSfZy+At5nPdnyMhx78fEoPDkR2XW/zimHEL3MyGJQOCQ7WeP20jPYRz7ZCpcKLB21NxuXHF3bxjStBQ==} @@ -4545,7 +5007,7 @@ packages: engines: {node: '>=10.13.0'} dev: true - /webpack@5.73.0: + /webpack@5.73.0(esbuild@0.19.11): resolution: {integrity: sha512-svjudQRPPa0YiOYa2lM/Gacw0r6PvxptHj4FuEKQ2kX05ZLkjbVc5MnPs6its5j7IZljnIqSVo/OsY2X0IpHGA==} engines: {node: '>=10.13.0'} hasBin: true @@ -4576,7 +5038,7 @@ packages: neo-async: 2.6.2 schema-utils: 3.3.0 tapable: 2.2.1 - terser-webpack-plugin: 5.3.9(webpack@5.73.0) + terser-webpack-plugin: 5.3.9(esbuild@0.19.11)(webpack@5.73.0) watchpack: 2.4.0 webpack-sources: 3.2.3 transitivePeerDependencies: @@ -4585,7 +5047,7 @@ packages: - uglify-js dev: true - /webpack@5.89.0: + /webpack@5.89.0(esbuild@0.19.11): resolution: {integrity: sha512-qyfIC10pOr70V+jkmud8tMfajraGCZMBWJtrmuBymQKCrLTRejBI8STDp1MCyZu/QTdZSeacCQYpYNQVOzX5kw==} engines: {node: '>=10.13.0'} hasBin: true @@ -4616,7 +5078,7 @@ packages: neo-async: 2.6.2 schema-utils: 3.3.0 tapable: 2.2.1 - terser-webpack-plugin: 5.3.9(webpack@5.89.0) + terser-webpack-plugin: 5.3.9(esbuild@0.19.11)(webpack@5.89.0) watchpack: 2.4.0 webpack-sources: 3.2.3 transitivePeerDependencies: @@ -4653,6 +5115,10 @@ packages: execa: 4.1.0 dev: true + /wordwrap@1.0.0: + resolution: {integrity: sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==} + dev: true + /wrap-ansi@7.0.0: resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} engines: {node: '>=10'} @@ -4660,6 +5126,7 @@ packages: ansi-styles: 4.3.0 string-width: 4.2.3 strip-ansi: 6.0.1 + dev: true /wrappy@1.0.2: resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} @@ -4669,11 +5136,6 @@ packages: engines: {node: '>=0.4'} dev: false - /y18n@5.0.8: - resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} - engines: {node: '>=10'} - dev: false - /yallist@4.0.0: resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} @@ -4682,52 +5144,21 @@ packages: engines: {node: '>= 6'} dev: true - /yargs-parser@20.2.9: - resolution: {integrity: sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==} - engines: {node: '>=10'} - dev: false - /yargs-parser@21.0.1: resolution: {integrity: sha512-9BK1jFpLzJROCI5TzwZL/TU4gqjK5xiHV/RfWLOahrjAko/e4DJkRDZQXfvqAsiZzzYhgAzbgz6lg48jcm4GLg==} engines: {node: '>=12'} dev: true - /yargs-parser@21.1.1: - resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} - engines: {node: '>=12'} - dev: false - - /yargs@16.2.0: - resolution: {integrity: sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==} - engines: {node: '>=10'} - dependencies: - cliui: 7.0.4 - escalade: 3.1.1 - get-caller-file: 2.0.5 - require-directory: 2.1.1 - string-width: 4.2.3 - y18n: 5.0.8 - yargs-parser: 20.2.9 - dev: false - - /yargs@17.7.2: - resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==} - engines: {node: '>=12'} - dependencies: - cliui: 8.0.1 - escalade: 3.1.1 - get-caller-file: 2.0.5 - require-directory: 2.1.1 - string-width: 4.2.3 - y18n: 5.0.8 - yargs-parser: 21.1.1 - dev: false - /yn@3.1.1: resolution: {integrity: sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==} engines: {node: '>=6'} + dev: true /yocto-queue@0.1.0: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} dev: true + + /zod@3.22.4: + resolution: {integrity: sha512-iC+8Io04lddc+mVqQ9AZ7OQ2MrUKGN+oIQyq1vemgt46jwCwLfhq7/pwnBnNXXXZb8VTVLKwp9EDkx+ryxIWmg==} + dev: true diff --git a/public/swagger.json b/public/swagger.json index c52f73a..4d7ec97 100644 --- a/public/swagger.json +++ b/public/swagger.json @@ -142,9 +142,9 @@ ] } }, - "/api/users/me/inventory": { + "/api/users/me/items": { "get": { - "operationId": "UsersController_getMeInventory", + "operationId": "UsersController_getMeItems", "parameters": [ { "name": "page", @@ -176,7 +176,7 @@ "items": { "type": "array", "items": { - "$ref": "#/components/schemas/UserInventoryEntryOutputDTO" + "$ref": "#/components/schemas/UserItemOutputDTO" } }, "meta": { @@ -226,9 +226,9 @@ ] } }, - "/api/users/me/inventory/{id}/quick-sell": { + "/api/users/me/items/{id}/quick-sell": { "post": { - "operationId": "UsersController_quickSellPokemonFromInventory", + "operationId": "UsersController_quickSellUserItem", "parameters": [ { "name": "id", @@ -245,7 +245,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/QuickSoldUserInventoryEntryOutputDTO" + "$ref": "#/components/schemas/QuickSoldUserItemOutputDTO" } } } @@ -264,7 +264,50 @@ "/api/packs": { "get": { "operationId": "PacksController_getPacks", - "parameters": [], + "parameters": [ + { + "name": "id", + "required": false, + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "name", + "required": false, + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "nameLike", + "required": false, + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "page", + "required": false, + "in": "query", + "schema": { + "default": 1, + "type": "number" + } + }, + { + "name": "limit", + "required": false, + "in": "query", + "schema": { + "default": 10, + "type": "number" + } + } + ], "responses": { "200": { "description": "", @@ -634,7 +677,7 @@ "image" ] }, - "UserInventoryEntryOutputDTO": { + "UserItemOutputDTO": { "type": "object", "properties": { "id": { @@ -654,33 +697,29 @@ "pokemon" ] }, - "QuickSoldUserInventoryEntryOutputDTO": { + "QuickSoldUserItemOutputDTO": { "type": "object", "properties": { "id": { "type": "string" }, - "soldAt": { - "format": "date-time", - "type": "string" - }, "receivedAt": { "format": "date-time", "type": "string" }, - "user": { - "$ref": "#/components/schemas/UserOutputDTO" - }, "pokemon": { "$ref": "#/components/schemas/PokemonOutputDTO" + }, + "soldAt": { + "format": "date-time", + "type": "string" } }, "required": [ "id", - "soldAt", "receivedAt", - "user", - "pokemon" + "pokemon", + "soldAt" ] }, "PackOutputDTO": { @@ -775,7 +814,7 @@ "CreatePendingTradeInputDTO": { "type": "object", "properties": { - "senderInventoryEntryIds": { + "senderItemIds": { "type": "array", "items": { "type": "string" @@ -784,7 +823,7 @@ "receiverId": { "type": "string" }, - "receiverInventoryEntryIds": { + "receiverItemIds": { "type": "array", "items": { "type": "string" @@ -792,9 +831,9 @@ } }, "required": [ - "senderInventoryEntryIds", + "senderItemIds", "receiverId", - "receiverInventoryEntryIds" + "receiverItemIds" ] }, "PendingTradeOutputDTO": { @@ -811,37 +850,23 @@ "format": "date-time", "type": "string" }, - "status": { - "type": "string" - }, "sender": { "$ref": "#/components/schemas/UserOutputDTO" }, - "senderInventoryEntries": { - "type": "array", - "items": { - "$ref": "#/components/schemas/UserInventoryEntryOutputDTO" - } - }, "receiver": { "$ref": "#/components/schemas/UserOutputDTO" }, - "receiverInventoryEntries": { - "type": "array", - "items": { - "$ref": "#/components/schemas/UserInventoryEntryOutputDTO" - } + "status": { + "type": "string" } }, "required": [ "id", "createdAt", "updatedAt", - "status", "sender", - "senderInventoryEntries", "receiver", - "receiverInventoryEntries" + "status" ] }, "AcceptedTradeOutputDTO": { @@ -858,26 +883,14 @@ "format": "date-time", "type": "string" }, - "status": { - "type": "string" - }, "sender": { "$ref": "#/components/schemas/UserOutputDTO" }, - "senderInventoryEntries": { - "type": "array", - "items": { - "$ref": "#/components/schemas/UserInventoryEntryOutputDTO" - } - }, "receiver": { "$ref": "#/components/schemas/UserOutputDTO" }, - "receiverInventoryEntries": { - "type": "array", - "items": { - "$ref": "#/components/schemas/UserInventoryEntryOutputDTO" - } + "status": { + "type": "string" }, "acceptedAt": { "format": "date-time", @@ -888,11 +901,9 @@ "id", "createdAt", "updatedAt", - "status", "sender", - "senderInventoryEntries", "receiver", - "receiverInventoryEntries", + "status", "acceptedAt" ] }, @@ -910,26 +921,14 @@ "format": "date-time", "type": "string" }, - "status": { - "type": "string" - }, "sender": { "$ref": "#/components/schemas/UserOutputDTO" }, - "senderInventoryEntries": { - "type": "array", - "items": { - "$ref": "#/components/schemas/UserInventoryEntryOutputDTO" - } - }, "receiver": { "$ref": "#/components/schemas/UserOutputDTO" }, - "receiverInventoryEntries": { - "type": "array", - "items": { - "$ref": "#/components/schemas/UserInventoryEntryOutputDTO" - } + "status": { + "type": "string" }, "cancelledAt": { "format": "date-time", @@ -940,11 +939,9 @@ "id", "createdAt", "updatedAt", - "status", "sender", - "senderInventoryEntries", "receiver", - "receiverInventoryEntries", + "status", "cancelledAt" ] }, @@ -962,26 +959,14 @@ "format": "date-time", "type": "string" }, - "status": { - "type": "string" - }, "sender": { "$ref": "#/components/schemas/UserOutputDTO" }, - "senderInventoryEntries": { - "type": "array", - "items": { - "$ref": "#/components/schemas/UserInventoryEntryOutputDTO" - } - }, "receiver": { "$ref": "#/components/schemas/UserOutputDTO" }, - "receiverInventoryEntries": { - "type": "array", - "items": { - "$ref": "#/components/schemas/UserInventoryEntryOutputDTO" - } + "status": { + "type": "string" }, "rejectedAt": { "format": "date-time", @@ -992,11 +977,9 @@ "id", "createdAt", "updatedAt", - "status", "sender", - "senderInventoryEntries", "receiver", - "receiverInventoryEntries", + "status", "rejectedAt" ] } diff --git a/src/api/api.module.ts b/src/api/api.module.ts index 8002542..ddd8113 100644 --- a/src/api/api.module.ts +++ b/src/api/api.module.ts @@ -13,15 +13,15 @@ import { PacksController } from './controllers/packs.controller'; import { PacksModule } from 'src/infra/ioc/packs.module'; import { PokemonsModule } from 'src/infra/ioc/pokemons.module'; import { AutomapperModule } from '@automapper/nestjs'; -import { classes } from '@automapper/classes'; import { UserProfile } from './profiles/user.profile'; import { PokemonProfile } from './profiles/pokemon.profile'; import { PackProfile } from './profiles/pack.profile'; -import { UserInventoryEntriesModule } from 'src/infra/ioc/user-inventory-entries.module'; -import { UserInventoryEntryProfile } from './profiles/user-inventory-entry.profile'; +import { UserItemsModule } from 'src/infra/ioc/user-items.module'; +import { UserItemProfile } from './profiles/user-item.profile'; import { TradesController } from './controllers/trades.controller'; import { TradesModule } from 'src/infra/ioc/trades.module'; import { TradeProfile } from './profiles/trade.profile'; +import { pojos } from '@automapper/pojos'; @Module({ imports: [ @@ -34,14 +34,14 @@ import { TradeProfile } from './profiles/trade.profile'; global: true, }), AutomapperModule.forRoot({ - strategyInitializer: classes(), + strategyInitializer: pojos(), }), PassportModule, AuthModule, PokemonsModule, UsersModule, - UserInventoryEntriesModule, + UserItemsModule, PacksModule, TradesModule, ], @@ -52,7 +52,7 @@ import { TradeProfile } from './profiles/trade.profile'; // profiles UserProfile, - UserInventoryEntryProfile, + UserItemProfile, PokemonProfile, PackProfile, TradeProfile, diff --git a/src/api/controllers/auth.controller.ts b/src/api/controllers/auth.controller.ts index 8d2e8f7..2b2cac5 100644 --- a/src/api/controllers/auth.controller.ts +++ b/src/api/controllers/auth.controller.ts @@ -2,12 +2,12 @@ import { Body, Controller, HttpCode, HttpStatus, Post, UseGuards } from '@nestjs import { AuthUseCase } from 'src/core/use-cases/auth.use-case'; import { LocalAuthGuard } from '../guards/local-auth.guard'; import { User } from '../decorators/user.decorator'; -import { UserModel } from 'src/infra/postgres/entities/user.entity'; import { LoginOutputDTO } from '../dtos/auth/login.output.dto'; import { LoginInputDTO } from '../dtos/auth/login.input.dto'; import { RegisterInputDTO } from '../dtos/auth/register.input.dto'; import { RegisterOutputDTO } from '../dtos/auth/register.output.dto'; import { ApiCreatedResponse, ApiOkResponse, ApiTags } from '@nestjs/swagger'; +import { UserEntity } from 'src/infra/postgres/tables'; @ApiTags('Authorization') @Controller('auth') @@ -21,7 +21,7 @@ export class AuthController { @UseGuards(LocalAuthGuard) public async login( @Body() _dto: LoginInputDTO, - @User() user: UserModel, + @User() user: UserEntity, ): Promise { return this.authUseCase.login(user); } diff --git a/src/api/controllers/packs.controller.ts b/src/api/controllers/packs.controller.ts index 47e20de..f976e68 100644 --- a/src/api/controllers/packs.controller.ts +++ b/src/api/controllers/packs.controller.ts @@ -1,18 +1,20 @@ -import { Controller, Get, Param, ParseUUIDPipe, Post, UseGuards } from '@nestjs/common'; +import { Controller, Get, Param, ParseUUIDPipe, Post, Query, UseGuards } from '@nestjs/common'; import { JwtAuthGuard } from '../guards/jwt-auth.guard'; import { User } from '../decorators/user.decorator'; -import { UserModel } from 'src/infra/postgres/entities/user.entity'; import { PacksUseCase } from 'src/core/use-cases/packs.use-case'; import { ApiConflictResponse, ApiCreatedResponse, ApiNotFoundResponse, ApiOkResponse, ApiSecurity, ApiTags } from '@nestjs/swagger'; -import { UUIDv4 } from 'src/common/types'; +import { PaginatedArray, UUIDv4 } from 'src/common/types'; import { Mapper } from '@automapper/core'; -import { PackEntity } from 'src/infra/postgres/entities/pack.entity'; import { PackOutputDTO } from '../dtos/packs/pack.output.dto'; -import { DataSource } from 'typeorm'; import { InjectMapper } from '@automapper/nestjs'; import { PackWithPokemonsOutputDTO } from '../dtos/packs/pack-with-pokemons.output.dto'; -import { OpenedPackEntity } from 'src/infra/postgres/entities/opened-pack.entity'; import { OpenedPackOutputDTO } from '../dtos/packs/opened-pack.output.dto'; +import { Database } from 'src/infra/postgres/other/types'; +import { PaginationInputDTO } from '../dtos/pagination.input.dto'; +import { mapArrayWithPagination } from 'src/common/helpers/map-array-with-pagination.helper'; +import { OpenedPackEntity, PackEntity, UserEntity } from 'src/infra/postgres/tables'; +import { InjectDatabase } from 'src/infra/decorators/inject-database.decorator'; +import { GetPacksInputDTO } from '../dtos/packs/get-packs.input.dto'; @ApiTags('Packs') @Controller('packs') @@ -20,19 +22,28 @@ export class PacksController { public constructor( @InjectMapper() private readonly mapper: Mapper, + @InjectDatabase() + private readonly db: Database, private readonly packsUseCase: PacksUseCase, - private readonly dataSource: DataSource, ) {} @ApiOkResponse({ type: [PackOutputDTO] }) @ApiSecurity('AccessToken') @Get() @UseGuards(JwtAuthGuard) - public async getPacks(): Promise> { - const packs = await this.packsUseCase.getPacks(); + public async getPacks( + @Query() dto: GetPacksInputDTO, + @Query() paginationDTO: PaginationInputDTO, + ): Promise> { + const packs = await this.packsUseCase.getPacksWithPagination(dto, paginationDTO); - return this.mapper.mapArray(packs, PackEntity, PackOutputDTO); + return mapArrayWithPagination( + this.mapper, + packs, + 'PackEntity', + 'PackOutputDTO', + ) } @ApiOkResponse({ type: PackWithPokemonsOutputDTO }) @@ -45,7 +56,11 @@ export class PacksController { ): Promise { const pack = await this.packsUseCase.getPack(id); - return this.mapper.map(pack, PackEntity, PackWithPokemonsOutputDTO); + return this.mapper.map( + pack, + 'PackEntity', + 'PackOutputDTO', + ); } @ApiCreatedResponse({ type: OpenedPackOutputDTO }) @@ -55,11 +70,17 @@ export class PacksController { @Post(':id/open') @UseGuards(JwtAuthGuard) public async openPack( - @User() user: UserModel, + @User() user: UserEntity, @Param('id', new ParseUUIDPipe({ version: '4' })) id: UUIDv4, ): Promise { - const openedPack = await this.packsUseCase.openPack(user, id, this.dataSource); + const openedPack = await this.db.transaction(async (tx) => ( + this.packsUseCase.openPackById(user, id, tx) + )); - return this.mapper.map(openedPack, OpenedPackEntity, OpenedPackOutputDTO); + return this.mapper.map( + openedPack, + 'OpenedPackEntity', + 'OpenedPackOutputDTO' + ); } } diff --git a/src/api/controllers/trades.controller.ts b/src/api/controllers/trades.controller.ts index fa8b813..1f4de83 100644 --- a/src/api/controllers/trades.controller.ts +++ b/src/api/controllers/trades.controller.ts @@ -1,15 +1,12 @@ import { Mapper } from '@automapper/core'; import { InjectMapper } from '@automapper/nestjs'; -import { Body, Controller, HttpException, HttpStatus, Param, ParseUUIDPipe, Post, UseGuards } from '@nestjs/common'; +import { Body, Controller, Param, ParseUUIDPipe, Post, UseGuards } from '@nestjs/common'; import { ApiCreatedResponse, ApiSecurity, ApiTags } from '@nestjs/swagger'; import { UUIDv4 } from 'src/common/types'; import { PendingTradesUseCase } from 'src/core/use-cases/pending-trades.use-case'; -import { AcceptedTradeEntity } from 'src/infra/postgres/entities/accepted-trade.entity'; -import { CancelledTradeEntity } from 'src/infra/postgres/entities/cancelled-trade.entity'; -import { PendingTradeEntity } from 'src/infra/postgres/entities/pending-trade.entity'; -import { RejectedTradeEntity } from 'src/infra/postgres/entities/rejected-trade.entity'; -import { UserModel } from 'src/infra/postgres/entities/user.entity'; -import { DataSource } from 'typeorm'; +import { InjectDatabase } from 'src/infra/decorators/inject-database.decorator'; +import { Database } from 'src/infra/postgres/other/types'; +import { AcceptedTradeEntity, CancelledTradeEntity, PendingTradeEntity, RejectedTradeEntity, UserEntity } from 'src/infra/postgres/tables'; import { User } from '../decorators/user.decorator'; import { AcceptedTradeOutputDTO } from '../dtos/accepted-trades/accepted-trade.output.dto'; import { CancelledTradeOuputDTO } from '../dtos/cancelled-trades/cancelled-trade.output.dto'; @@ -24,9 +21,10 @@ export class TradesController { public constructor( @InjectMapper() private readonly mapper: Mapper, + @InjectDatabase() + private readonly db: Database, private readonly pendingTradesUseCase: PendingTradesUseCase, - private readonly dataSource: DataSource, ) {} @ApiCreatedResponse({ type: PendingTradeOutputDTO }) @@ -34,19 +32,19 @@ export class TradesController { @Post() @UseGuards(JwtAuthGuard) public async createPendingTrade( - @User() user: UserModel, + @User() user: UserEntity, @Body() dto: CreatePendingTradeInputDTO, ) { - if (!dto.senderItemIds.length && !dto.receiverItemIds.length) { - throw new HttpException( - '`senderItemIds` and `receiverItemIds` cannot be empty simultaneously', - HttpStatus.BAD_REQUEST - ); - } + // TODO: Return `tradesToSenderItems` and `tradesToReceiverItems` to the client + const { pendingTrade, tradesToSenderItems, tradesToReceiverItems } = await this.db.transaction(async (tx) => ( + this.pendingTradesUseCase.createPendingTrade(user, dto, tx) + )); - const pendingTrade = await this.pendingTradesUseCase.createPendingTrade(user, dto); - - return this.mapper.map(pendingTrade, PendingTradeEntity, PendingTradeOutputDTO); + return this.mapper.map( + pendingTrade, + 'PendingTradeEntity', + 'PendingTradeOutputDTO', + ); } @ApiCreatedResponse({ type: AcceptedTradeOutputDTO }) @@ -54,12 +52,18 @@ export class TradesController { @Post(':id/accept') @UseGuards(JwtAuthGuard) public async acceptPendingTradeById( - @User() user: UserModel, + @User() user: UserEntity, @Param('id', new ParseUUIDPipe({ version: '4' })) id: UUIDv4, ) { - const acceptedTrade = await this.pendingTradesUseCase.acceptPendingTradeById(user, id, this.dataSource); + const acceptedTrade = await this.db.transaction(async (tx) => ( + this.pendingTradesUseCase.acceptPendingTradeById(user, id, tx) + )); - return this.mapper.map(acceptedTrade, AcceptedTradeEntity, AcceptedTradeOutputDTO); + return this.mapper.map( + acceptedTrade, + 'AcceptedTradeEntity', + 'AcceptedTradeOutputDTO', + ); } @ApiCreatedResponse({ type: CancelledTradeOuputDTO }) @@ -67,12 +71,18 @@ export class TradesController { @Post(':id/cancel') @UseGuards(JwtAuthGuard) public async cancelPendingTradeById( - @User() user: UserModel, + @User() user: UserEntity, @Param('id', new ParseUUIDPipe({ version: '4' })) id: UUIDv4, ) { - const cancelledTrade = await this.pendingTradesUseCase.cancelPendingTradeById(user, id, this.dataSource); + const cancelledTrade = await this.db.transaction(async (tx) => ( + this.pendingTradesUseCase.cancelPendingTradeById(user, id, tx) + )); - return this.mapper.map(cancelledTrade, CancelledTradeEntity, CancelledTradeOuputDTO); + return this.mapper.map( + cancelledTrade, + 'CancelledTradeEntity', + 'CancelledTradeOuputDTO', + ); } @ApiCreatedResponse({ type: RejectedTradeOutputDTO }) @@ -80,11 +90,17 @@ export class TradesController { @Post(':id/reject') @UseGuards(JwtAuthGuard) public async rejectPendingTradeById( - @User() user: UserModel, + @User() user: UserEntity, @Param('id', new ParseUUIDPipe({ version: '4' })) id: UUIDv4, ) { - const rejectedTrade = await this.pendingTradesUseCase.rejectPendingTradeById(user, id, this.dataSource); + const rejectedTrade = await this.db.transaction(async (tx) => ( + this.pendingTradesUseCase.rejectPendingTradeById(user, id, tx) + )); - return this.mapper.map(rejectedTrade, RejectedTradeEntity, RejectedTradeOutputDTO); + return this.mapper.map( + rejectedTrade, + 'RejectedTradeEntity', + 'RejectedTradeOutputDTO' + ); } } diff --git a/src/api/controllers/users.controller.ts b/src/api/controllers/users.controller.ts index eb69d5f..5cec45a 100644 --- a/src/api/controllers/users.controller.ts +++ b/src/api/controllers/users.controller.ts @@ -1,24 +1,22 @@ import { Controller, Get, Param, ParseUUIDPipe, Post, Query, UseGuards } from '@nestjs/common'; import { UsersUseCase } from 'src/core/use-cases/users.use-case'; import { JwtAuthGuard } from '../guards/jwt-auth.guard'; -import { UserEntity, UserModel } from 'src/infra/postgres/entities/user.entity'; import { User } from '../decorators/user.decorator'; import { ApiOkResponse, ApiCreatedResponse, ApiSecurity, ApiTags } from '@nestjs/swagger'; import { Mapper } from '@automapper/core'; import { InjectMapper } from '@automapper/nestjs'; import { UserOutputDTO } from '../dtos/users/user.output.dto'; -import { UserInventoryEntryEntity } from 'src/infra/postgres/entities/user-inventory-entry.entity'; -import { UserInventoryEntryOutputDTO } from '../dtos/user-inventory-entries/user-inventory-entry.output.dto'; import { mapArrayWithPagination } from 'src/common/helpers/map-array-with-pagination.helper'; import { PaginationInputDTO } from '../dtos/pagination.input.dto'; import { ApiOkResponseWithPagination } from '../decorators/api-ok-response-with-pagination.decorator'; -import { UUIDv4 } from 'src/common/types'; -import { DataSource } from 'typeorm'; -import { Pagination } from 'nestjs-typeorm-paginate'; -import { UserInventoryEntriesUseCase } from 'src/core/use-cases/user-inventory-entries.use-case'; -import { QuickSoldUserInventoryEntryEntity } from 'src/infra/postgres/entities/quick-sold-user-inventory-entry.entity'; -import { QuickSoldUserInventoryEntryOutputDTO } from '../dtos/user-inventory-entries/quick-sold-user-inventory-entry.output.dto'; +import { PaginatedArray, UUIDv4 } from 'src/common/types'; import { GetUsersInputDTO } from '../dtos/users/get-users.input.dto'; +import { UserItemsUseCase } from 'src/core/use-cases/user-items.use-case'; +import { Database } from 'src/infra/postgres/other/types'; +import { UserItemOutputDTO } from '../dtos/user-items/user-item.output.dto'; +import { QuickSoldUserItemOutputDTO } from '../dtos/user-items/quick-sold-user-item.output.dto'; +import { QuickSoldUserItemEntity, UserEntity, UserItemEntity } from 'src/infra/postgres/tables'; +import { InjectDatabase } from 'src/infra/decorators/inject-database.decorator'; @ApiTags('Users') @Controller('users') @@ -26,10 +24,11 @@ export class UsersController { public constructor( @InjectMapper() private readonly mapper: Mapper, + @InjectDatabase() + private readonly db: Database, private readonly usersUseCase: UsersUseCase, - private readonly userInventoryEntriesUseCase: UserInventoryEntriesUseCase, - private readonly dataSource: DataSource, + private readonly userItemsUseCase: UserItemsUseCase, ) {} @ApiOkResponse({ type: UserOutputDTO }) @@ -39,14 +38,14 @@ export class UsersController { public async getUsers( @Query() dto: GetUsersInputDTO, @Query() paginationDTO: PaginationInputDTO, - ): Promise> { - const users = await this.usersUseCase.findUsers(dto, paginationDTO); + ): Promise> { + const users = await this.usersUseCase.getUsersWithPagination(dto, paginationDTO); - return mapArrayWithPagination( + return mapArrayWithPagination( this.mapper, users, - UserEntity, - UserOutputDTO, + 'UserEntity', + 'UserOutputDTO', ) } @@ -54,38 +53,48 @@ export class UsersController { @ApiSecurity('AccessToken') @Get('me') @UseGuards(JwtAuthGuard) - public async getMe(@User() user: UserModel): Promise { - return this.mapper.map(user, UserEntity, UserOutputDTO); + public async getMe(@User() user: UserEntity): Promise { + return this.mapper.map( + user, + 'UserEntity', + 'UserOutputDTO', + ); } - @ApiOkResponseWithPagination({ type: UserInventoryEntryOutputDTO }) + @ApiOkResponseWithPagination({ type: UserItemOutputDTO }) @ApiSecurity('AccessToken') - @Get('me/inventory') + @Get('me/items') @UseGuards(JwtAuthGuard) - public async getMeInventory( - @User() user: UserModel, + public async getMeItems( + @User() user: UserEntity, @Query() paginationDto: PaginationInputDTO, - ): Promise> { - const inventoryWithPagination = await this.userInventoryEntriesUseCase.findUserInventoryEntriesByUser(user, paginationDto); + ): Promise> { + const userItemsWithPagination = await this.userItemsUseCase.getUserItemsWithPaginationByUser(user, paginationDto); - return mapArrayWithPagination( + return mapArrayWithPagination( this.mapper, - inventoryWithPagination, - UserInventoryEntryEntity, - UserInventoryEntryOutputDTO + userItemsWithPagination, + 'UserItemEntity', + 'UserItemOutputDTO', ); } - @ApiCreatedResponse({ type: QuickSoldUserInventoryEntryOutputDTO }) + @ApiCreatedResponse({ type: QuickSoldUserItemOutputDTO }) @ApiSecurity('AccessToken') - @Post('me/inventory/:id/quick-sell') + @Post('me/items/:id/quick-sell') @UseGuards(JwtAuthGuard) - public async quickSellPokemonFromInventory( - @User() user: UserModel, + public async quickSellUserItem( + @User() user: UserEntity, @Param('id', new ParseUUIDPipe({ version: '4' })) id: UUIDv4, - ): Promise { - const quickSoldUserInventoryEntry = await this.userInventoryEntriesUseCase.quickSellUserInventoryEntryById(user, id, this.dataSource); + ): Promise { + const quickSoldUserItem = await this.db.transaction(async (tx) => ( + this.userItemsUseCase.quickSellUserItemById(user, id, tx) + )); - return this.mapper.map(quickSoldUserInventoryEntry, QuickSoldUserInventoryEntryEntity, QuickSoldUserInventoryEntryOutputDTO); + return this.mapper.map( + quickSoldUserItem, + 'QuickSoldUserItemEntity', + 'QuickSoldUserItemOutputDTO', + ); } } diff --git a/src/api/dtos/accepted-trades/accepted-trade.output.dto.ts b/src/api/dtos/accepted-trades/accepted-trade.output.dto.ts index 2cfbe1c..9fb3556 100644 --- a/src/api/dtos/accepted-trades/accepted-trade.output.dto.ts +++ b/src/api/dtos/accepted-trades/accepted-trade.output.dto.ts @@ -1,9 +1,15 @@ -import { AutoMap } from '@automapper/classes'; -import { ApiProperty } from '@nestjs/swagger'; +import { ApiProperty, OmitType } from '@nestjs/swagger'; import { TradeOutputDTO } from '../trades/trade.output.dto'; -export class AcceptedTradeOutputDTO extends TradeOutputDTO { +export class AcceptedTradeOutputDTO extends OmitType(TradeOutputDTO, [ + 'status', + 'cancelledAt', + 'acceptedAt', + 'rejectedAt', +]) { + @ApiProperty() + status: 'ACCEPTED'; + @ApiProperty() - @AutoMap() acceptedAt: Date; } diff --git a/src/api/dtos/cancelled-trades/cancelled-trade.output.dto.ts b/src/api/dtos/cancelled-trades/cancelled-trade.output.dto.ts index 3a6ec50..52a675d 100644 --- a/src/api/dtos/cancelled-trades/cancelled-trade.output.dto.ts +++ b/src/api/dtos/cancelled-trades/cancelled-trade.output.dto.ts @@ -1,9 +1,15 @@ -import { AutoMap } from '@automapper/classes'; -import { ApiProperty } from '@nestjs/swagger'; +import { ApiProperty, OmitType } from '@nestjs/swagger'; import { TradeOutputDTO } from '../trades/trade.output.dto'; -export class CancelledTradeOuputDTO extends TradeOutputDTO { +export class CancelledTradeOuputDTO extends OmitType(TradeOutputDTO, [ + 'status', + 'cancelledAt', + 'acceptedAt', + 'rejectedAt', +]) { + @ApiProperty() + status: 'CANCELLED'; + @ApiProperty() - @AutoMap() cancelledAt: Date; } diff --git a/src/api/dtos/packs/get-packs.input.dto.ts b/src/api/dtos/packs/get-packs.input.dto.ts new file mode 100644 index 0000000..2889ca5 --- /dev/null +++ b/src/api/dtos/packs/get-packs.input.dto.ts @@ -0,0 +1,20 @@ +import { ApiPropertyOptional } from '@nestjs/swagger'; +import { IsOptional, IsString, IsUUID } from 'class-validator'; +import { UUIDv4 } from 'src/common/types'; + +export class GetPacksInputDTO { + @ApiPropertyOptional() + @IsUUID(4) + @IsOptional() + public readonly id?: UUIDv4; + + @ApiPropertyOptional() + @IsString() + @IsOptional() + public readonly name?: string; + + @ApiPropertyOptional() + @IsString() + @IsOptional() + public readonly nameLike?: string; +} diff --git a/src/api/dtos/packs/open-pack.output.dto.ts b/src/api/dtos/packs/open-pack.output.dto.ts index 7e88861..3f025a4 100644 --- a/src/api/dtos/packs/open-pack.output.dto.ts +++ b/src/api/dtos/packs/open-pack.output.dto.ts @@ -1,18 +1,14 @@ -import { AutoMap } from '@automapper/classes'; import { ApiProperty } from '@nestjs/swagger'; import { PokemonOutputDTO } from '../pokemons/pokemon.output.dto'; import { UserOutputDTO } from '../users/user.output.dto'; export class OpenPackOutputDTO { @ApiProperty({ type: UserOutputDTO }) - @AutoMap(() => UserOutputDTO) public readonly user: UserOutputDTO; @ApiProperty({ type: PokemonOutputDTO }) - @AutoMap(() => PokemonOutputDTO) public readonly pokemon: PokemonOutputDTO; @ApiProperty() - @AutoMap() public readonly isDuplicate: boolean; } diff --git a/src/api/dtos/packs/opened-pack.output.dto.ts b/src/api/dtos/packs/opened-pack.output.dto.ts index 18c8d44..d64e463 100644 --- a/src/api/dtos/packs/opened-pack.output.dto.ts +++ b/src/api/dtos/packs/opened-pack.output.dto.ts @@ -1,4 +1,3 @@ -import { AutoMap } from '@automapper/classes'; import { ApiProperty } from '@nestjs/swagger'; import { UUIDv4 } from 'src/common/types'; import { PokemonOutputDTO } from '../pokemons/pokemon.output.dto'; @@ -7,22 +6,17 @@ import { PackOutputDTO } from './pack.output.dto'; export class OpenedPackOutputDTO { @ApiProperty() - @AutoMap() public readonly id: UUIDv4; @ApiProperty() - @AutoMap() public readonly openedAt: Date; @ApiProperty({ type: UserOutputDTO }) - @AutoMap(() => UserOutputDTO) public readonly user: UserOutputDTO; @ApiProperty({ type: PackOutputDTO }) - @AutoMap(() => PackOutputDTO) public readonly pack: PackOutputDTO; @ApiProperty({ type: PokemonOutputDTO }) - @AutoMap(() => PokemonOutputDTO) public readonly pokemon: PokemonOutputDTO; } diff --git a/src/api/dtos/packs/pack-with-pokemons.output.dto.ts b/src/api/dtos/packs/pack-with-pokemons.output.dto.ts index 0549dc3..0d0b417 100644 --- a/src/api/dtos/packs/pack-with-pokemons.output.dto.ts +++ b/src/api/dtos/packs/pack-with-pokemons.output.dto.ts @@ -1,10 +1,8 @@ -import { AutoMap } from '@automapper/classes'; import { ApiProperty } from '@nestjs/swagger'; import { PokemonOutputDTO } from '../pokemons/pokemon.output.dto'; import { PackOutputDTO } from './pack.output.dto'; export class PackWithPokemonsOutputDTO extends PackOutputDTO { @ApiProperty({ type: PokemonOutputDTO, isArray: true }) - @AutoMap(() => [PokemonOutputDTO]) public readonly pokemons: Array; } diff --git a/src/api/dtos/packs/pack.output.dto.ts b/src/api/dtos/packs/pack.output.dto.ts index 516fbc7..f059b3e 100644 --- a/src/api/dtos/packs/pack.output.dto.ts +++ b/src/api/dtos/packs/pack.output.dto.ts @@ -1,26 +1,20 @@ -import { AutoMap } from "@automapper/classes"; import { ApiProperty } from "@nestjs/swagger"; import { UUIDv4 } from "src/common/types"; export class PackOutputDTO { @ApiProperty() - @AutoMap() public readonly id: UUIDv4; @ApiProperty() - @AutoMap() public readonly name: string; @ApiProperty() - @AutoMap() public readonly description: string; @ApiProperty() - @AutoMap() public readonly price: number; // NOTE: URL @ApiProperty() - @AutoMap() public readonly image: string; } diff --git a/src/api/dtos/pending-trades/pending-trade.output.dto.ts b/src/api/dtos/pending-trades/pending-trade.output.dto.ts index 57096b2..d7a6666 100644 --- a/src/api/dtos/pending-trades/pending-trade.output.dto.ts +++ b/src/api/dtos/pending-trades/pending-trade.output.dto.ts @@ -1,3 +1,12 @@ +import { ApiProperty, OmitType } from '@nestjs/swagger'; import { TradeOutputDTO } from '../trades/trade.output.dto'; -export class PendingTradeOutputDTO extends TradeOutputDTO {} +export class PendingTradeOutputDTO extends OmitType(TradeOutputDTO, [ + 'status', + 'cancelledAt', + 'acceptedAt', + 'rejectedAt' +]) { + @ApiProperty() + status: 'PENDING'; +} diff --git a/src/api/dtos/pokemons/pokemon.output.dto.ts b/src/api/dtos/pokemons/pokemon.output.dto.ts index 88a46fc..1339024 100644 --- a/src/api/dtos/pokemons/pokemon.output.dto.ts +++ b/src/api/dtos/pokemons/pokemon.output.dto.ts @@ -1,28 +1,21 @@ -import { AutoMap } from '@automapper/classes'; import { ApiProperty } from '@nestjs/swagger'; export class PokemonOutputDTO { @ApiProperty() - @AutoMap() public readonly id: number; @ApiProperty() - @AutoMap() public readonly name: string; @ApiProperty() - @AutoMap() public readonly worth: number; @ApiProperty() - @AutoMap() public readonly height: number; @ApiProperty() - @AutoMap() public readonly weight: number; @ApiProperty() - @AutoMap() public readonly image: string; } diff --git a/src/api/dtos/rejected-trades/rejected-trade.output.dto.ts b/src/api/dtos/rejected-trades/rejected-trade.output.dto.ts index e854200..8fcbec5 100644 --- a/src/api/dtos/rejected-trades/rejected-trade.output.dto.ts +++ b/src/api/dtos/rejected-trades/rejected-trade.output.dto.ts @@ -1,9 +1,15 @@ -import { AutoMap } from '@automapper/classes'; -import { ApiProperty } from '@nestjs/swagger'; +import { ApiProperty, OmitType } from '@nestjs/swagger'; import { TradeOutputDTO } from '../trades/trade.output.dto'; -export class RejectedTradeOutputDTO extends TradeOutputDTO { - @AutoMap() +export class RejectedTradeOutputDTO extends OmitType(TradeOutputDTO, [ + 'status', + 'cancelledAt', + 'acceptedAt', + 'rejectedAt', +]) { + @ApiProperty() + status: 'REJECTED'; + @ApiProperty() rejectedAt: Date; } diff --git a/src/api/dtos/trades/trade.output.dto.ts b/src/api/dtos/trades/trade.output.dto.ts index 22bd8fc..68a0c28 100644 --- a/src/api/dtos/trades/trade.output.dto.ts +++ b/src/api/dtos/trades/trade.output.dto.ts @@ -1,40 +1,33 @@ -import { AutoMap } from '@automapper/classes'; -import { ApiProperty } from '@nestjs/swagger'; +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; import { UUIDv4 } from 'src/common/types'; -import { TradeStatus } from 'src/infra/postgres/entities/trade.entity'; -import { UserInventoryEntryOutputDTO } from '../user-inventory-entries/user-inventory-entry.output.dto'; +import { TradeStatus } from 'src/infra/postgres/tables/trades.table'; import { UserOutputDTO } from '../users/user.output.dto'; export class TradeOutputDTO { @ApiProperty() - @AutoMap() public readonly id: UUIDv4; @ApiProperty() - @AutoMap() public readonly createdAt: Date; @ApiProperty() - @AutoMap() public readonly updatedAt: Date; @ApiProperty() - @AutoMap() public readonly status: TradeStatus; + @ApiPropertyOptional() + public readonly cancelledAt?: Date; + + @ApiPropertyOptional() + public readonly acceptedAt?: Date; + + @ApiPropertyOptional() + public readonly rejectedAt?: Date; + @ApiProperty({ type: UserOutputDTO }) - @AutoMap(() => UserOutputDTO) public readonly sender: UserOutputDTO; - @ApiProperty({ type: UserInventoryEntryOutputDTO, isArray: true }) - @AutoMap(() => [UserInventoryEntryOutputDTO]) - public readonly senderInventoryEntries: Array; - @ApiProperty({ type: UserOutputDTO }) - @AutoMap(() => UserOutputDTO) public readonly receiver: UserOutputDTO; - - @ApiProperty({ type: UserInventoryEntryOutputDTO, isArray: true }) - @AutoMap(() => [UserInventoryEntryOutputDTO]) - public readonly receiverInventoryEntries: Array; } diff --git a/src/api/dtos/user-inventory-entries/quick-sold-user-inventory-entry.output.dto.ts b/src/api/dtos/user-inventory-entries/quick-sold-user-inventory-entry.output.dto.ts deleted file mode 100644 index 4cdefff..0000000 --- a/src/api/dtos/user-inventory-entries/quick-sold-user-inventory-entry.output.dto.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { AutoMap } from '@automapper/classes'; -import { ApiProperty } from '@nestjs/swagger'; -import { UUIDv4 } from 'src/common/types'; -import { PokemonOutputDTO } from '../pokemons/pokemon.output.dto'; -import { UserOutputDTO } from '../users/user.output.dto'; - -export class QuickSoldUserInventoryEntryOutputDTO { - @ApiProperty() - @AutoMap() - public readonly id: UUIDv4; - - @ApiProperty() - @AutoMap() - public readonly soldAt: Date; - - @ApiProperty() - @AutoMap() - public readonly receivedAt: Date; - - @ApiProperty({ type: UserOutputDTO }) - @AutoMap(() => UserOutputDTO) - public readonly user: UserOutputDTO; - - @ApiProperty({ type: PokemonOutputDTO }) - @AutoMap(() => PokemonOutputDTO) - public readonly pokemon: PokemonOutputDTO; -} diff --git a/src/api/dtos/user-items/get-user-items.input.dto.ts b/src/api/dtos/user-items/get-user-items.input.dto.ts new file mode 100644 index 0000000..f6382e4 --- /dev/null +++ b/src/api/dtos/user-items/get-user-items.input.dto.ts @@ -0,0 +1,21 @@ +import { ApiPropertyOptional } from '@nestjs/swagger'; +import { IsOptional, IsUUID } from 'class-validator'; +import { IsPositiveInt } from 'src/common/decorators/is-positive-int.decorator'; +import { UUIDv4 } from 'src/common/types'; + +export class GetUserItemsInputDTO { + @ApiPropertyOptional() + @IsUUID(4) + @IsOptional() + public readonly id?: UUIDv4; + + @ApiPropertyOptional() + @IsUUID(4) + @IsOptional() + public readonly userId?: UUIDv4; + + @ApiPropertyOptional() + @IsPositiveInt() + @IsOptional() + public readonly pokemonId?: number; +} diff --git a/src/api/dtos/user-items/quick-sold-user-item.output.dto.ts b/src/api/dtos/user-items/quick-sold-user-item.output.dto.ts new file mode 100644 index 0000000..cf4fe9d --- /dev/null +++ b/src/api/dtos/user-items/quick-sold-user-item.output.dto.ts @@ -0,0 +1,7 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { UserItemOutputDTO } from './user-item.output.dto'; + +export class QuickSoldUserItemOutputDTO extends UserItemOutputDTO { + @ApiProperty() + public readonly soldAt: Date; +} diff --git a/src/api/dtos/user-inventory-entries/user-inventory-entry.output.dto.ts b/src/api/dtos/user-items/user-item.output.dto.ts similarity index 69% rename from src/api/dtos/user-inventory-entries/user-inventory-entry.output.dto.ts rename to src/api/dtos/user-items/user-item.output.dto.ts index 9737711..d74f185 100644 --- a/src/api/dtos/user-inventory-entries/user-inventory-entry.output.dto.ts +++ b/src/api/dtos/user-items/user-item.output.dto.ts @@ -1,18 +1,14 @@ -import { AutoMap } from '@automapper/classes'; import { ApiProperty } from '@nestjs/swagger'; import { UUIDv4 } from 'src/common/types'; import { PokemonOutputDTO } from '../pokemons/pokemon.output.dto'; -export class UserInventoryEntryOutputDTO { +export class UserItemOutputDTO { @ApiProperty() - @AutoMap() public readonly id: UUIDv4; @ApiProperty() - @AutoMap() public readonly receivedAt: Date; @ApiProperty({ type: PokemonOutputDTO }) - @AutoMap(() => PokemonOutputDTO) public readonly pokemon: PokemonOutputDTO; } diff --git a/src/api/dtos/users/user-with-inventory-entries.output.dto.ts b/src/api/dtos/users/user-with-inventory-entries.output.dto.ts deleted file mode 100644 index 5b19149..0000000 --- a/src/api/dtos/users/user-with-inventory-entries.output.dto.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { AutoMap } from '@automapper/classes'; -import { ApiProperty } from '@nestjs/swagger'; -import { UserInventoryEntryOutputDTO } from '../user-inventory-entries/user-inventory-entry.output.dto'; -import { UserOutputDTO } from './user.output.dto'; - -export class UserWithInventoryEntriesOutputDTO extends UserOutputDTO { - @ApiProperty({ type: UserInventoryEntryOutputDTO, isArray: true }) - @AutoMap(() => [UserInventoryEntryOutputDTO]) - public readonly inventoryEntries: Array; -} diff --git a/src/api/dtos/users/user.output.dto.ts b/src/api/dtos/users/user.output.dto.ts index 1cc55f9..6aad0d3 100644 --- a/src/api/dtos/users/user.output.dto.ts +++ b/src/api/dtos/users/user.output.dto.ts @@ -1,17 +1,13 @@ import { UUIDv4 } from 'src/common/types'; -import { AutoMap } from '@automapper/classes'; import { ApiProperty } from '@nestjs/swagger'; export class UserOutputDTO { @ApiProperty() - @AutoMap() public readonly id: UUIDv4; @ApiProperty() - @AutoMap() public readonly name: string; @ApiProperty() - @AutoMap() public readonly balance: number; } diff --git a/src/api/profiles/pack.profile.ts b/src/api/profiles/pack.profile.ts index d92ba3a..fa6a009 100644 --- a/src/api/profiles/pack.profile.ts +++ b/src/api/profiles/pack.profile.ts @@ -1,23 +1,79 @@ import { AutomapperProfile, InjectMapper } from '@automapper/nestjs'; import { Injectable } from '@nestjs/common'; import { Mapper, createMap } from '@automapper/core'; -import { PackEntity } from 'src/infra/postgres/entities/pack.entity'; import { PackOutputDTO } from '../dtos/packs/pack.output.dto'; import { PackWithPokemonsOutputDTO } from '../dtos/packs/pack-with-pokemons.output.dto'; -import { OpenedPackEntity } from 'src/infra/postgres/entities/opened-pack.entity'; import { OpenedPackOutputDTO } from '../dtos/packs/opened-pack.output.dto'; +import { PojosMetadataMap } from '@automapper/pojos'; +import { OpenedPackEntity, PackEntity, PackToPokemonEntity } from 'src/infra/postgres/tables'; @Injectable() export class PackProfile extends AutomapperProfile { public constructor(@InjectMapper() mapper: Mapper) { super(mapper); + this.createMetadataEntities(); + this.createMetadataDTOs(); } public override get profile() { return (mapper: Mapper) => { - createMap(mapper, PackEntity, PackOutputDTO); - createMap(mapper, PackEntity, PackWithPokemonsOutputDTO); - createMap(mapper, OpenedPackEntity, OpenedPackOutputDTO); + createMap( + mapper, + 'PackEntity', + 'PackOutputDTO', + ); + createMap( + mapper, + 'OpenedPackEntity', + 'OpenedPackOutputDTO', + ); }; } + + private createMetadataEntities(): void { + PojosMetadataMap.create('PackToPokemonEntity', { + pack: 'PackEntity', + pokemon: 'PokemonEntity', + }); + + PojosMetadataMap.create('PackEntity', { + id: String, + name: String, + description: String, + price: String, + image: String, + }); + + PojosMetadataMap.create('OpenedPackEntity', { + id: String, + openedAt: Date, + user: 'UserEntity', + pack: 'PackEntity', + pokemon: 'PokemonEntity', + }); + } + + private createMetadataDTOs(): void { + const packOutputDTOProperties = { + id: String, + name: String, + description: String, + price: String, + image: String, + } + + PojosMetadataMap.create('PackOutputDTO', packOutputDTOProperties); + PojosMetadataMap.create('PackWithPokemonsOutputDTO', { + ...packOutputDTOProperties, + pokemons: ['PokemonOutputDTO'], + }); + + PojosMetadataMap.create('OpenedPackOutputDTO', { + id: String, + openedAt: Date, + user: 'UserOutputDTO', + pack: 'PackOutputDTO', + pokemon: 'PokemonOutputDTO', + }); + } } diff --git a/src/api/profiles/pokemon.profile.ts b/src/api/profiles/pokemon.profile.ts index e3bce09..a1c6e8f 100644 --- a/src/api/profiles/pokemon.profile.ts +++ b/src/api/profiles/pokemon.profile.ts @@ -1,18 +1,47 @@ -import { Mapper, createMap } from '@automapper/core'; +import { Mapper, createMap, MappingProfile } from '@automapper/core'; import { AutomapperProfile, InjectMapper } from '@automapper/nestjs'; +import { PojosMetadataMap } from '@automapper/pojos'; import { Injectable } from '@nestjs/common'; -import { PokemonEntity } from 'src/infra/postgres/entities/pokemon.entity'; import { PokemonOutputDTO } from '../dtos/pokemons/pokemon.output.dto'; +import { PokemonEntity } from 'src/infra/postgres/tables'; @Injectable() export class PokemonProfile extends AutomapperProfile { public constructor(@InjectMapper() mapper: Mapper) { super(mapper); + this.createMetadataEntities(); + this.createMetadataDTOs(); } - public override get profile() { + public override get profile(): MappingProfile { return (mapper: Mapper) => { - createMap(mapper, PokemonEntity, PokemonOutputDTO); + createMap( + mapper, + 'PokemonEntity', + 'PokemonOutputDTO', + ); }; } + + private createMetadataEntities(): void { + PojosMetadataMap.create('PokemonEntity', { + id: Number, + name: String, + worth: Number, + height: Number, + weight: Number, + image: String, + }); + } + + private createMetadataDTOs(): void { + PojosMetadataMap.create('PokemonOutputDTO', { + id: Number, + name: String, + worth: Number, + height: Number, + weight: Number, + image: String, + }); + } } diff --git a/src/api/profiles/trade.profile.ts b/src/api/profiles/trade.profile.ts index e699b8e..c25b2b5 100644 --- a/src/api/profiles/trade.profile.ts +++ b/src/api/profiles/trade.profile.ts @@ -1,27 +1,125 @@ -import { createMap, Mapper } from '@automapper/core'; +import { createMap, forMember, mapFrom, Mapper, MappingProfile } from '@automapper/core'; import { AutomapperProfile, InjectMapper } from '@automapper/nestjs'; +import { PojoMetadata, PojosMetadataMap } from '@automapper/pojos'; import { Injectable } from '@nestjs/common'; -import { AcceptedTradeEntity } from 'src/infra/postgres/entities/accepted-trade.entity'; -import { CancelledTradeEntity } from 'src/infra/postgres/entities/cancelled-trade.entity'; -import { PendingTradeEntity } from 'src/infra/postgres/entities/pending-trade.entity'; -import { RejectedTradeEntity } from 'src/infra/postgres/entities/rejected-trade.entity'; +import { + AcceptedTradeEntity, + CancelledTradeEntity, + PendingTradeEntity, + RejectedTradeEntity, + TradeEntity, + TradeToReceiverItemEntity, + TradeToSenderItemEntity, +} from 'src/infra/postgres/tables'; import { AcceptedTradeOutputDTO } from '../dtos/accepted-trades/accepted-trade.output.dto'; import { CancelledTradeOuputDTO } from '../dtos/cancelled-trades/cancelled-trade.output.dto'; import { PendingTradeOutputDTO } from '../dtos/pending-trades/pending-trade.output.dto'; import { RejectedTradeOutputDTO } from '../dtos/rejected-trades/rejected-trade.output.dto'; +import { TradeOutputDTO } from '../dtos/trades/trade.output.dto'; @Injectable() export class TradeProfile extends AutomapperProfile { public constructor(@InjectMapper() mapper: Mapper) { super(mapper); + this.createMetadataEntities(); + this.createMetadataDTOs(); } - public override get profile() { + public override get profile(): MappingProfile { return (mapper: Mapper) => { - createMap(mapper, PendingTradeEntity, PendingTradeOutputDTO); - createMap(mapper, AcceptedTradeEntity, AcceptedTradeOutputDTO); - createMap(mapper, CancelledTradeEntity, CancelledTradeOuputDTO); - createMap(mapper, RejectedTradeEntity, RejectedTradeOutputDTO); + createMap( + mapper, + 'TradeEntity', + 'TradeOutputDTO', + ); + createMap( + mapper, + 'PendingTradeEntity', + 'PendingTradeOutputDTO', + ); + createMap( + mapper, + 'CancelledTradeEntity', + 'CancelledTradeOuputDTO', + ); + createMap( + mapper, + 'AcceptedTradeEntity', + 'AcceptedTradeOutputDTO', + ); + createMap( + mapper, + 'RejectedTradeEntity', + 'RejectedTradeOutputDTO', + ); }; } + + private createMetadataEntities(): void { + PojosMetadataMap.create('TradeToSenderItemEntity', { + trade: 'TradeEntity', + senderItem: 'UserItemEntity', + }); + + PojosMetadataMap.create('TradeToReceiverItemEntity', { + trade: 'TradeEntity', + receiverItem: 'UserItemEntity', + }); + + const tradeEntityProperties = { + id: String, + createdAt: Date, + updatedAt: Date, + status: String, + cancelledAt: Date, + acceptedAt: Date, + rejectedAt: Date, + sender: 'UserEntity', + receiver: 'UserEntity', + }; + PojosMetadataMap.create('TradeEntity', tradeEntityProperties); + + let cancelledAt, acceptedAt, rejectedAt; + let pendingTradeEntityProperties, cancelledTradeEntityProperties, acceptedTradeEntityProperties, rejectedTradeEntityProperties; + ({ cancelledAt, acceptedAt, rejectedAt, ...pendingTradeEntityProperties } = tradeEntityProperties); + PojosMetadataMap.create('PendingTradeEntity', pendingTradeEntityProperties); + + ({ acceptedAt, rejectedAt, ...cancelledTradeEntityProperties } = tradeEntityProperties); + PojosMetadataMap.create('CancelledTradeEntity', cancelledTradeEntityProperties); + + ({ cancelledAt, rejectedAt, ...acceptedTradeEntityProperties } = tradeEntityProperties); + PojosMetadataMap.create('AcceptedTradeEntity', acceptedTradeEntityProperties); + + ({ cancelledAt, acceptedAt, ...rejectedTradeEntityProperties } = tradeEntityProperties); + PojosMetadataMap.create('RejectedTradeEntity', rejectedTradeEntityProperties); + } + + private createMetadataDTOs(): void { + const tradeOutputDTOProperties = { + id: String, + createdAt: Date, + updatedAt: Date, + status: String, + cancelledAt: Date, + acceptedAt: Date, + rejectedAt: Date, + sender: 'UserOutputDTO', + receiver: 'UserOutputDTO', + }; + PojosMetadataMap.create('TradeOutputDTO', tradeOutputDTOProperties); + + let cancelledAt, acceptedAt, rejectedAt; + let pendingTradeOutputDTOProperties, cancelledTradeOutputDTOProperties, acceptedTradeOutputDTOProperties, rejectedTradeOutputDTOProperties; + ({ cancelledAt, acceptedAt, rejectedAt, ...pendingTradeOutputDTOProperties } = tradeOutputDTOProperties); + PojosMetadataMap.create('PendingTradeOutputDTO', pendingTradeOutputDTOProperties); + + ({ acceptedAt, rejectedAt, ...cancelledTradeOutputDTOProperties } = tradeOutputDTOProperties); + PojosMetadataMap.create('CancelledTradeOutputDTO', cancelledTradeOutputDTOProperties); + + ({ cancelledAt, rejectedAt, ...acceptedTradeOutputDTOProperties } = tradeOutputDTOProperties); + PojosMetadataMap.create('AcceptedTradeOutputDTO', acceptedTradeOutputDTOProperties); + + ({ cancelledAt, acceptedAt, ...rejectedTradeOutputDTOProperties } = tradeOutputDTOProperties); + PojosMetadataMap.create('RejectedTradeOutputDTO', rejectedTradeOutputDTOProperties); + } } diff --git a/src/api/profiles/user-inventory-entry.profile.ts b/src/api/profiles/user-inventory-entry.profile.ts deleted file mode 100644 index d685932..0000000 --- a/src/api/profiles/user-inventory-entry.profile.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { createMap, Mapper } from '@automapper/core'; -import { AutomapperProfile, InjectMapper } from '@automapper/nestjs'; -import { Injectable } from '@nestjs/common'; -import { UserInventoryEntryEntity } from 'src/infra/postgres/entities/user-inventory-entry.entity'; -import { UserInventoryEntryOutputDTO } from '../dtos/user-inventory-entries/user-inventory-entry.output.dto'; -import { QuickSoldUserInventoryEntryEntity } from 'src/infra/postgres/entities/quick-sold-user-inventory-entry.entity'; -import { QuickSoldUserInventoryEntryOutputDTO } from '../dtos/user-inventory-entries/quick-sold-user-inventory-entry.output.dto'; - -@Injectable() -export class UserInventoryEntryProfile extends AutomapperProfile { - public constructor(@InjectMapper() mapper: Mapper) { - super(mapper); - } - - public override get profile() { - return (mapper: Mapper) => { - createMap(mapper, UserInventoryEntryEntity, UserInventoryEntryOutputDTO); - createMap(mapper, QuickSoldUserInventoryEntryEntity, QuickSoldUserInventoryEntryOutputDTO); - }; - } -} diff --git a/src/api/profiles/user-item.profile.ts b/src/api/profiles/user-item.profile.ts new file mode 100644 index 0000000..217638f --- /dev/null +++ b/src/api/profiles/user-item.profile.ts @@ -0,0 +1,60 @@ +import { createMap, Mapper, MappingProfile } from '@automapper/core'; +import { AutomapperProfile, InjectMapper } from '@automapper/nestjs'; +import { Injectable } from '@nestjs/common'; +import { UserItemOutputDTO } from '../dtos/user-items/user-item.output.dto'; +import { QuickSoldUserItemOutputDTO } from '../dtos/user-items/quick-sold-user-item.output.dto'; +import { PojosMetadataMap } from '@automapper/pojos'; +import { QuickSoldUserItemEntity, UserItemEntity } from 'src/infra/postgres/tables'; + +@Injectable() +export class UserItemProfile extends AutomapperProfile { + public constructor(@InjectMapper() mapper: Mapper) { + super(mapper); + this.createMetadataEntities(); + this.createMetadataDTOs(); + } + + public override get profile(): MappingProfile { + return (mapper: Mapper) => { + createMap( + mapper, + 'UserItemEntity', + 'UserItemOutputDTO', + ); + createMap( + mapper, + 'QuickSoldUserItemEntity', + 'QuickSoldUserItemOutputDTO', + ); + }; + } + + private createMetadataEntities(): void { + const userItemEntityProperties = { + id: String, + receivedAt: Date, + user: 'UserEntity', + pokemon: 'PokemonEntity', + }; + + PojosMetadataMap.create('UserItemEntity', userItemEntityProperties); + PojosMetadataMap.create('QuickSoldUserItemEntity', { + ...userItemEntityProperties, + soldAt: Date, + }); + } + + private createMetadataDTOs(): void { + const userItemOutputDTOProperties = { + id: String, + receivedAt: Date, + pokemon: 'PokemonOutputDTO', + }; + + PojosMetadataMap.create('UserItemOutputDTO', userItemOutputDTOProperties); + PojosMetadataMap.create('QuickSoldUserItemOutputDTO', { + ...userItemOutputDTOProperties, + soldAt: Date, + }) + } +} diff --git a/src/api/profiles/user.profile.ts b/src/api/profiles/user.profile.ts index 228abc1..642cd52 100644 --- a/src/api/profiles/user.profile.ts +++ b/src/api/profiles/user.profile.ts @@ -1,20 +1,43 @@ import { AutomapperProfile, InjectMapper } from '@automapper/nestjs'; import { Injectable } from '@nestjs/common'; -import { Mapper, createMap } from '@automapper/core'; -import { UserEntity } from 'src/infra/postgres/entities/user.entity'; +import { Mapper, createMap, MappingProfile } from '@automapper/core'; import { UserOutputDTO } from '../dtos/users/user.output.dto'; -import { UserWithInventoryEntriesOutputDTO } from '../dtos/users/user-with-inventory-entries.output.dto'; +import { PojosMetadataMap } from '@automapper/pojos'; +import { UserEntity } from 'src/infra/postgres/tables'; @Injectable() export class UserProfile extends AutomapperProfile { public constructor(@InjectMapper() mapper: Mapper) { super(mapper); + this.createMetadataEntities(); + this.createMetadataDTOs(); } - public override get profile() { + public override get profile(): MappingProfile { return (mapper: Mapper) => { - createMap(mapper, UserEntity, UserOutputDTO); - createMap(mapper, UserEntity, UserWithInventoryEntriesOutputDTO); + createMap( + mapper, + 'UserEntity', + 'UserOutputDTO', + ); }; } + + private createMetadataEntities(): void { + PojosMetadataMap.create('UserEntity', { + id: String, + createdAt: Date, + updatedAt: Date, + name: String, + balance: Number, + }); + } + + private createMetadataDTOs(): void { + PojosMetadataMap.create('UserOutputDTO', { + id: String, + name: String, + balance: Number, + }); + } } diff --git a/src/api/strategies/jwt-auth.strategy.ts b/src/api/strategies/jwt-auth.strategy.ts index 2c63fd5..10c6ad1 100644 --- a/src/api/strategies/jwt-auth.strategy.ts +++ b/src/api/strategies/jwt-auth.strategy.ts @@ -5,6 +5,7 @@ import { ExtractJwt, Strategy } from 'passport-jwt'; import { UserTokenPayload } from 'src/common/types'; import { UsersUseCase } from 'src/core/use-cases/users.use-case'; import { EnvVariables } from 'src/infra/config/validation'; +import { UserEntity } from 'src/infra/postgres/tables'; @Injectable() export class JwtAuthStrategy extends PassportStrategy(Strategy) { @@ -21,7 +22,7 @@ export class JwtAuthStrategy extends PassportStrategy(Strategy) { }); } - public async validate(tokenPayload: UserTokenPayload) { + public async validate(tokenPayload: UserTokenPayload): Promise { return this.usersService.getUser({ id: tokenPayload.id }, { errorMessage: 'Unauthorized', errorStatus: HttpStatus.UNAUTHORIZED, diff --git a/src/common/helpers/map-array-to-paginated-array.helper.ts b/src/common/helpers/map-array-to-paginated-array.helper.ts new file mode 100644 index 0000000..0c439d6 --- /dev/null +++ b/src/common/helpers/map-array-to-paginated-array.helper.ts @@ -0,0 +1,13 @@ +import { PaginatedArray } from "../types"; + +export const mapArrayToPaginatedArray = ( + items: Array, + options: { page: number, limit: number }, +): PaginatedArray => ({ + items, + meta: { + itemCount: items.length, + itemsPerPage: options.limit, + currentPage: options.page, + }, +}) diff --git a/src/common/helpers/map-array-with-pagination.helper.ts b/src/common/helpers/map-array-with-pagination.helper.ts index ae4ce9a..3a52f62 100644 --- a/src/common/helpers/map-array-with-pagination.helper.ts +++ b/src/common/helpers/map-array-with-pagination.helper.ts @@ -1,12 +1,12 @@ import { Dictionary, ModelIdentifier, Mapper } from '@automapper/core'; -import { Pagination } from 'nestjs-typeorm-paginate'; +import { PaginatedArray } from '../types'; export const mapArrayWithPagination = , TDestination extends Dictionary>( mapper: Mapper, - sourceObjectWithPagination: Pagination, + sourceObjectWithPagination: PaginatedArray, sourceIdentifier: ModelIdentifier, destinationIdentifier: ModelIdentifier, -): Pagination => { +): PaginatedArray => { return { ...sourceObjectWithPagination, items: mapper.mapArray(sourceObjectWithPagination.items, sourceIdentifier, destinationIdentifier), diff --git a/src/common/types.ts b/src/common/types.ts index 76f7f55..d2cd547 100644 --- a/src/common/types.ts +++ b/src/common/types.ts @@ -2,6 +2,7 @@ export type UUIDv4 = `${string}-${string}-${string}-${string}-${string}`; export type JWT = `${string}.${string}.${string}`; export type Optional = T | undefined; export type Nullable = T | null; +export type ValueOf = T[keyof T]; export type RemovePropertiesWith, U> = { [K in keyof T as T[K] extends U ? never : K]: T[K] } @@ -10,3 +11,17 @@ export type RemovePropertiesWithNever> = Remov export type UserTokenPayload = { id: UUIDv4; }; + +export type PaginatedArray = { + items: Array, + meta: { + itemCount: number, + itemsPerPage: number, + currentPage: number, + } +}; + +export type PaginationOptions = { + page: number, + limit: number, +} diff --git a/src/core/services/base.service.ts b/src/core/services/base.service.ts deleted file mode 100644 index 43a7fda..0000000 --- a/src/core/services/base.service.ts +++ /dev/null @@ -1,153 +0,0 @@ -import { BuildQueryResult, DBQueryConfig, ExtractTablesWithRelations, inArray, KnownKeysOnly } from 'drizzle-orm'; -import { PgInsertValue, PgUpdateSetSource } from 'drizzle-orm/pg-core'; -import { NodePgDatabase, } from 'drizzle-orm/node-postgres'; -import { Nullable } from 'src/common/types'; -import { RemovePropertiesWithNever } from 'src/common/types'; -import * as tables from 'src/infra/postgres/tables'; -import { Entity, EntityRelations, Transaction } from 'src/infra/postgres/other/types'; -import { zip } from 'lodash'; - -export abstract class BaseService< - TTableName extends keyof RemovePropertiesWithNever<{ - [K in keyof ExtractTablesWithRelations]: - 'id' extends keyof ExtractTablesWithRelations[K]['columns'] - ? K - : never - }> -> { - protected readonly table: typeof tables[TTableName]; - - public constructor( - protected readonly tableName: TTableName, - protected readonly drizzle: NodePgDatabase, - ) { - this.table = tables[tableName]; - } - - public async findOne, ExtractTablesWithRelations[TTableName]>, 'limit'>>( - config?: KnownKeysOnly, ExtractTablesWithRelations[TTableName]>, 'limit'>> - ): Promise, ExtractTablesWithRelations[TTableName], TSelection>>> { - return this - .drizzle - .query[this.tableName] - .findFirst(config) - .then((entity) => entity ?? null); - } - - public async exists, ExtractTablesWithRelations[TTableName]>, 'limit'>>( - config?: KnownKeysOnly, ExtractTablesWithRelations[TTableName]>, 'limit'>> - ): Promise { - return this - .findOne(config) - .then((entity) => Boolean(entity)); - } - - public async findMany, ExtractTablesWithRelations[TTableName]>>( - config?: KnownKeysOnly, ExtractTablesWithRelations[TTableName]>> - ): Promise, ExtractTablesWithRelations[TTableName], TConfig>[]> { - return this - .drizzle - .query[this.tableName] - .findMany(config); - } - - public async findManyWithPagination, ExtractTablesWithRelations[TTableName]>>( - { page = 1, limit }: { page?: number, limit: number }, - config?: KnownKeysOnly, ExtractTablesWithRelations[TTableName]>> - ): Promise<{ - items: BuildQueryResult, ExtractTablesWithRelations[TTableName], TConfig>[]; - itemCount: number; - itemsPerPage: number; - currentPage: number; - }> { - // TODO: check for boundaries - const offset = (page - 1) * limit; - - return this - .findMany({ - ...config, - limit, - offset, - } as typeof config) - .then((entities) => ({ - items: entities, - itemCount: entities.length, - itemsPerPage: offset, - currentPage: page, - // TODO: Add fields: totalItems, totalPages - })) - } - - public async createMany( - values: Array>, - tx?: Transaction, - ): Promise>> { - return (tx ?? this.drizzle) - .insert(this.table) - .values(values) - .returning() - } - - public async createOne( - values: PgInsertValue, - tx?: Transaction, - ): Promise> { - return this.createMany([values], tx) - .then(([entity]) => entity!); - } - - public async updateMany< - TEntityRelations extends EntityRelations, ExtractTablesWithRelations[TTableName]>, - >( - entities: Array>, - values: PgUpdateSetSource, - tx?: Transaction, - ): Promise>> { - return (tx ?? this.drizzle) - .update(this.table) - .set(values) - .where(inArray(this.table.id, entities.map(({ id }) => id))) - .returning() - .then((updatedEntities) => zip(entities, [...updatedEntities]).map(([entity, updatedEntity]) => ({ - ...entity!, - ...updatedEntity!, - }))); - } - - public async updateOne< - TEntityRelations extends EntityRelations, ExtractTablesWithRelations[TTableName]>, - >( - entity: Entity, - values: PgUpdateSetSource, - tx?: Transaction, - ): Promise> { - return this.updateMany([entity], values, tx) - .then(([entity]) => entity!); - } - - public async deleteMany< - TEntityRelations extends EntityRelations, ExtractTablesWithRelations[TTableName]>, - >( - entities: Array>, - tx?: Transaction, - ): Promise>> { - return (tx ?? this.drizzle) - .delete(this.table) - .where(inArray(this.table.id, entities.map(({ id }) => id))) - .returning() - .then((deletedEntities) => zip(entities, [...deletedEntities]).map(([entity, deletedEntity]) => ({ - ...entity!, - ...deletedEntity!, - }))); - } - - public async deleteOne< - TEntityRelations extends EntityRelations, ExtractTablesWithRelations[TTableName]>, - >( - entity: Entity, - tx?: Transaction, - ): Promise> { - return this.deleteMany([entity], tx) - .then(([deletedEntity]) => deletedEntity!); - } -} diff --git a/src/core/services/opened-packs.service.ts b/src/core/services/opened-packs.service.ts index b8d85cf..bdae2ad 100644 --- a/src/core/services/opened-packs.service.ts +++ b/src/core/services/opened-packs.service.ts @@ -1,15 +1,35 @@ import { Injectable } from '@nestjs/common'; -import { NodePgDatabase } from 'drizzle-orm/node-postgres'; -import { InjectDrizzle } from 'src/infra/postgres/postgres.module'; -import { BaseService } from './base.service'; -import * as tables from 'src/infra/postgres/tables'; +import { Database, Transaction } from 'src/infra/postgres/other/types'; +import { InjectDatabase } from 'src/infra/decorators/inject-database.decorator'; +import { CreateOpenedPackEntityValues, OpenedPackEntity, openedPacksTable } from 'src/infra/postgres/tables'; @Injectable() -export class OpenedPacksService extends BaseService<'openedPacks'> { +export class OpenedPacksService { public constructor( - @InjectDrizzle() - drizzle: NodePgDatabase, - ) { - super('openedPacks', drizzle); + @InjectDatabase() + private readonly db: Database, + ) {} + + public async createOne( + values: CreateOpenedPackEntityValues, + tx?: Transaction, + ): Promise { + const { user, pack, pokemon } = values; + + return (tx ?? this.db) + .insert(openedPacksTable) + .values({ + ...values, + userId: user.id, + packId: pack.id, + pokemonId: pokemon.id, + }) + .returning() + .then(([openedPack]) => ({ + ...openedPack!, + user, + pack, + pokemon, + })); } } diff --git a/src/core/services/packs.service.ts b/src/core/services/packs.service.ts index c2d2259..c78c593 100644 --- a/src/core/services/packs.service.ts +++ b/src/core/services/packs.service.ts @@ -1,43 +1,98 @@ import { Injectable } from '@nestjs/common'; -import { NodePgDatabase } from 'drizzle-orm/node-postgres'; -import { Nullable } from 'src/common/types'; -import { PackEntity as PackEntityDrizzle, PokemonEntity as PokemonEntityDrizzle } from 'src/infra/postgres/other/types'; -import { InjectDrizzle } from 'src/infra/postgres/postgres.module'; -import { BaseService } from './base.service'; -import * as tables from 'src/infra/postgres/tables'; -import { packPokemons, pokemons } from 'src/infra/postgres/tables'; -import { eq, sql } from 'drizzle-orm'; +import { Nullable, Optional, PaginatedArray, PaginationOptions, UUIDv4 } from 'src/common/types'; +import { PackEntity, packsTable, packsToPokemonsTable, PokemonEntity, pokemonsTable } from 'src/infra/postgres/tables'; +import { and, eq, getTableColumns, inArray, like, SQL, sql } from 'drizzle-orm'; +import { Database } from 'src/infra/postgres/other/types'; +import { InjectDatabase } from 'src/infra/decorators/inject-database.decorator'; +import { mapArrayToPaginatedArray } from 'src/common/helpers/map-array-to-paginated-array.helper'; + +type Where = Partial<{ + id: UUIDv4, + ids: Array, + name: string, + nameLike: string, +}>; + +type FindOptions = Partial<{ + where: Where, +}> + +type FindWithPaginationOptions = FindOptions & { + paginationOptions: PaginationOptions, +} @Injectable() -export class PacksService extends BaseService<'packs'> { +export class PacksService { public constructor( - @InjectDrizzle() - drizzle: NodePgDatabase, + @InjectDatabase() + private readonly db: Database, + ) {} + + private mapWhereToSql( + where: Where + ): Optional { + return and( + where.id !== undefined + ? eq(packsTable.id, where.id) + : undefined, + where.ids !== undefined + ? inArray(packsTable.id, where.ids) + : undefined, + where.name !== undefined + ? eq(packsTable.name, where.name) + : undefined, + where.nameLike !== undefined + ? like(packsTable.name, `%${where.nameLike}%`) + : undefined, + ) + } + + private baseSelectBuilder( + findOptions: FindOptions, ) { - super('packs', drizzle); + const { where = {} } = findOptions; + + return this.db + .select() + .from(packsTable) + .where(this.mapWhereToSql(where)); + } + + public async findManyWithPagination( + findWithPaginationOptions: FindWithPaginationOptions, + ): Promise> { + const { + paginationOptions: { page, limit }, + } = findWithPaginationOptions; + // TODO: check for boundaries + const offset = (page - 1) * limit; + + return this + .baseSelectBuilder(findWithPaginationOptions) + .offset(offset) + .limit(limit) + .then((packs) => mapArrayToPaginatedArray(packs, { page, limit })) + } + + public async findOne( + findOptions: FindOptions, + ): Promise> { + return this.baseSelectBuilder(findOptions) + .limit(1) + .then(([pack]) => pack ?? null); } public async findRandomPokemonFromPack( - pack: PackEntityDrizzle - ): Promise> { - // TODO: Check the sql of the query - // const sqlQuery = this.drizzle - // .select({ pokemon: pokemons }) - // .from(this.table) - // .innerJoin(packPokemons, eq(packPokemons.packId, this.table.id)) - // .innerJoin(pokemons, eq(pokemons.id, packPokemons.pokemonId)) - // .orderBy(sql`random()`) - // .limit(1) - // .getSQL(); - - return this.drizzle - .select({ pokemon: pokemons }) - .from(this.table) - .innerJoin(packPokemons, eq(packPokemons.packId, this.table.id)) - .innerJoin(pokemons, eq(pokemons.id, packPokemons.pokemonId)) - .where(eq(this.table.id, pack.id)) + pack: PackEntity + ): Promise> { + return this.db + .select({ pokemon: getTableColumns(pokemonsTable) }) + .from(packsTable) + .innerJoin(packsToPokemonsTable, eq(packsToPokemonsTable.packId, packsTable.id)) + .innerJoin(pokemonsTable, eq(pokemonsTable.id, packsToPokemonsTable.pokemonId)) + .where(eq(packsTable.id, pack.id)) .orderBy(sql`random()`) .limit(1) - .then(([result]) => result?.pokemon ?? null); + .then(([row]) => row?.pokemon ?? null); } } diff --git a/src/core/services/pending-trades.service.ts b/src/core/services/pending-trades.service.ts new file mode 100644 index 0000000..0f8f4a9 --- /dev/null +++ b/src/core/services/pending-trades.service.ts @@ -0,0 +1,179 @@ +import { Injectable } from '@nestjs/common'; +import { sql } from 'drizzle-orm'; +import { zip } from 'lodash'; +import { Nullable, UUIDv4 } from 'src/common/types'; +import { InjectDatabase } from 'src/infra/decorators/inject-database.decorator'; +import { Database, Transaction } from 'src/infra/postgres/other/types'; +import { AcceptedTradeEntity, CancelledTradeEntity, CreatePendingTradeEntityValues, PendingTradeEntity, RejectedTradeEntity, tradesTable, tradesToReceiverItemsTable, tradesToSenderItemsTable, TradeToReceiverItemEntity, TradeToSenderItemEntity, usersTable } from 'src/infra/postgres/tables'; +import { TradesService } from './trades.service'; + +type Where = Partial<{ + id: UUIDv4, + ids: Array, +}>; + +type FindOptions = Partial<{ + where: Where, +}>; + +@Injectable() +export class PendingTradesService { + public constructor( + @InjectDatabase() + private readonly db: Database, + + private readonly tradesService: TradesService, + ) {} + + public async findOne( + findOptions: FindOptions, + ): Promise> { + const status = 'PENDING'; + + return this.tradesService + .findOne({ + ...findOptions, + where: { + ...findOptions.where, + status + } + }) + .then((trade) => ( + trade ? { + ...trade, + status: status as typeof status, + } : null + )) + } + + + public async createOne( + values: CreatePendingTradeEntityValues, + tx?: Transaction, + ): Promise<{ + pendingTrade: PendingTradeEntity, + tradesToSenderItems: Array, + tradesToReceiverItems: Array, + }> { + const status = 'PENDING'; + const { sender, senderItems, receiver, receiverItems } = values; + + const pendingTrade = await (tx ?? this.db) + .insert(tradesTable) + .values({ + ...values, + status, + senderId: sender.id, + receiverId: receiver.id, + }) + .returning() + .then(([trade]) => ({ + ...trade!, + status: trade!.status as typeof status, + sender, + receiver, + })); + + const [tradesToSenderItems, tradesToReceiverItems] = await Promise.all([ + senderItems.length ? (tx ?? this.db) + .insert(tradesToSenderItemsTable) + .values(senderItems.map((senderItem) => ({ + tradeId: pendingTrade.id, + senderItemId: senderItem.id, + }))) + .returning() + .then((tradesToSenderItems) => zip(senderItems, tradesToSenderItems).map(([senderItem, tradeToSenderItem]) => ({ + ...tradeToSenderItem!, + trade: pendingTrade, + senderItem: senderItem!, + }))) : [], + receiverItems.length ? (tx ?? this.db) + .insert(tradesToReceiverItemsTable) + .values(receiverItems.map((receiverItem) => ({ + tradeId: pendingTrade.id, + receiverItemId: receiverItem.id, + }))) + .returning() + .then((tradesToReceiverItems) => zip(receiverItems, tradesToReceiverItems).map(([receiverItem, tradeToReceiverItem]) => ({ + ...tradeToReceiverItem!, + trade: pendingTrade, + receiverItem: receiverItem!, + }))) : [], + ]); + + return { + pendingTrade, + tradesToSenderItems, + tradesToReceiverItems, + }; + } + + public async updateOneToCancelled( + pendingTrade: PendingTradeEntity, + tx?: Transaction, + ): Promise { + const status = 'CANCELLED'; + const { sender, receiver } = pendingTrade; + + return (tx ?? this.db) + .update(tradesTable) + .set({ + status, + cancelledAt: sql`now()`, + }) + .returning() + .then(([trade]) => ({ + ...trade!, + status: trade!.status as typeof status, + cancelledAt: trade!.cancelledAt!, + sender, + receiver, + })); + } + + public async updateOneToAccepted( + pendingTrade: PendingTradeEntity, + tx?: Transaction, + ): Promise { + const status = 'ACCEPTED'; + const { sender, receiver } = pendingTrade; + + return (tx ?? this.db) + .update(tradesTable) + .set({ + status, + acceptedAt: sql`now()`, + }) + .returning() + .then(([trade]) => ({ + ...trade!, + status: trade!.status as typeof status, + acceptedAt: trade!.acceptedAt!, + sender, + receiver, + })); + } + + public async updateOneToRejected( + pendingTrade: PendingTradeEntity, + tx?: Transaction, + ): Promise { + const status = 'REJECTED'; + const { sender, receiver } = pendingTrade; + + return (tx ?? this.db) + .update(tradesTable) + .set({ + status, + rejectedAt: sql`now()`, + }) + .returning() + .then(([trade]) => ({ + ...trade!, + status: trade!.status as typeof status, + rejectedAt: trade!.rejectedAt!, + sender, + receiver, + })); + } +} diff --git a/src/core/services/pokemons.service.ts b/src/core/services/pokemons.service.ts index ec5c506..fe29222 100644 --- a/src/core/services/pokemons.service.ts +++ b/src/core/services/pokemons.service.ts @@ -1,25 +1,32 @@ import { Injectable } from '@nestjs/common'; -import { NodePgDatabase, NodePgQueryResultHKT } from 'drizzle-orm/node-postgres'; -import { InjectDrizzle } from 'src/infra/postgres/postgres.module'; -import { BaseService } from './base.service'; -import * as tables from 'src/infra/postgres/tables'; -import { PgTransaction } from 'drizzle-orm/pg-core'; -import { ExtractTablesWithRelations } from 'drizzle-orm'; +import { Database, Transaction } from 'src/infra/postgres/other/types'; +import { InjectDatabase } from 'src/infra/decorators/inject-database.decorator'; +import { CreatePokemonEntityValues, PokemonEntity, pokemonsTable } from 'src/infra/postgres/tables'; @Injectable() -export class PokemonsService extends BaseService<'pokemons'> { +export class PokemonsService { public constructor( - @InjectDrizzle() - drizzle: NodePgDatabase, - ) { - super('pokemons', drizzle); + @InjectDatabase() + private readonly db: Database, + ) {} + + public async createMany( + values: Array, + tx?: Transaction, + ): Promise> { + if (!values.length) return []; + + return (tx ?? this.db) + .insert(pokemonsTable) + .values(values) + .returning() } public async deleteAll( - tx?: PgTransaction>, - ): Promise> { - return (tx ?? this.drizzle) - .delete(this.table) + tx?: Transaction, + ): Promise> { + return (tx ?? this.db) + .delete(pokemonsTable) .returning(); } } diff --git a/src/core/services/quick-sold-user-items.service.ts b/src/core/services/quick-sold-user-items.service.ts index 1f44121..084fcc4 100644 --- a/src/core/services/quick-sold-user-items.service.ts +++ b/src/core/services/quick-sold-user-items.service.ts @@ -1,15 +1,34 @@ import { Injectable } from '@nestjs/common'; -import { NodePgDatabase } from 'drizzle-orm/node-postgres'; -import { InjectDrizzle } from 'src/infra/postgres/postgres.module'; -import { BaseService } from './base.service'; -import * as tables from 'src/infra/postgres/tables'; +import { Database, Transaction } from 'src/infra/postgres/other/types'; +import { InjectDatabase } from 'src/infra/decorators/inject-database.decorator'; +import { QuickSoldUserItemEntity, quickSoldUserItemsTable, UserItemEntity } from 'src/infra/postgres/tables'; +import { UserItemsService } from './user-items.service'; @Injectable() -export class QuickSoldUserItemsService extends BaseService<'quickSoldUserItems'> { +export class QuickSoldUserItemsService { public constructor( - @InjectDrizzle() - drizzle: NodePgDatabase, - ) { - super('quickSoldUserItems', drizzle); + @InjectDatabase() + private readonly db: Database, + + private readonly userItemsService: UserItemsService, + ) {} + + public async createOne( + userItem: UserItemEntity, + tx?: Transaction, + ): Promise { + const { user, pokemon } = userItem; + + await this.userItemsService.deleteOne(userItem, tx); + + return (tx ?? this.db) + .insert(quickSoldUserItemsTable) + .values(userItem) + .returning() + .then(([quickSoldUserItem]) => ({ + ...quickSoldUserItem!, + user, + pokemon, + })) } } diff --git a/src/core/services/trades-to-receiver-items.service.ts b/src/core/services/trades-to-receiver-items.service.ts new file mode 100644 index 0000000..e5fc520 --- /dev/null +++ b/src/core/services/trades-to-receiver-items.service.ts @@ -0,0 +1,88 @@ +import { Injectable } from '@nestjs/common'; +import { and, eq, SQL } from 'drizzle-orm'; +import { alias } from 'drizzle-orm/pg-core'; +import { Optional, UUIDv4 } from 'src/common/types'; +import { InjectDatabase } from 'src/infra/decorators/inject-database.decorator'; +import { Database } from 'src/infra/postgres/other/types'; +import { pokemonsTable, tradesTable, tradesToReceiverItemsTable, TradeToReceiverItemEntity, userItemsTable, usersTable } from 'src/infra/postgres/tables'; +import { TradesService } from './trades.service'; +import { UserItemsService } from './user-items.service'; + +type Where = Partial<{ + tradeId: UUIDv4, + receiverItemId: UUIDv4, +}> + +type FindOptions = Partial<{ + where: Where, +}> + +@Injectable() +export class TradesToReceiverItemsService { + public constructor( + @InjectDatabase() + private readonly db: Database, + + private readonly tradesService: TradesService, + private readonly userItemsService: UserItemsService, + ) {} + + private mapWhereToSQL( + where: Where, + ): Optional { + return and( + where.tradeId !== undefined + ? eq(tradesToReceiverItemsTable.tradeId, where.tradeId) + : undefined, + where.receiverItemId !== undefined + ? eq(tradesToReceiverItemsTable.receiverItemId, where.receiverItemId) + : undefined, + ); + } + + private baseSelectBuilder( + findOptions: FindOptions, + ) { + const { where = {} } = findOptions; + + const sendersTable = alias(usersTable, 'senders'); + const receiversTable = alias(usersTable, 'receivers'); + + return this.db + .select() + .from(tradesToReceiverItemsTable) + .innerJoin(tradesTable, eq(tradesTable.id, tradesToReceiverItemsTable.tradeId)) + .innerJoin(sendersTable, eq(sendersTable.id, tradesTable.senderId)) + .innerJoin(receiversTable, eq(receiversTable.id, tradesTable.receiverId)) + .innerJoin(userItemsTable, eq(userItemsTable.id, tradesToReceiverItemsTable.receiverItemId)) + .innerJoin(usersTable, eq(usersTable.id, userItemsTable.userId)) + .innerJoin(pokemonsTable, eq(pokemonsTable.id, userItemsTable.pokemonId)) + .where(this.mapWhereToSQL(where)); + } + + private mapSelectBuilderRowToEntity( + row: Record< + | 'trades_to_receiver_items' + | 'trades' + | 'senders' + | 'receivers' + | 'user_items' + | 'users' + | 'pokemons', + any>, + ) { + return { + ...row.trades_to_receiver_items, + trade: this.tradesService.mapSelectBuilderRowToEntity(row), + receiverItem: this.userItemsService.mapSelectBuilderRowToEntity(row), + } + } + + public async findMany( + findOptions: FindOptions, + ): Promise> { + return this + .baseSelectBuilder(findOptions) + .then((rows) => rows.map((row) => this.mapSelectBuilderRowToEntity(row))); + } +} diff --git a/src/core/services/trades-to-sender-items.service.ts b/src/core/services/trades-to-sender-items.service.ts new file mode 100644 index 0000000..f2600e4 --- /dev/null +++ b/src/core/services/trades-to-sender-items.service.ts @@ -0,0 +1,88 @@ +import { Injectable } from '@nestjs/common'; +import { and, eq, SQL } from 'drizzle-orm'; +import { alias } from 'drizzle-orm/pg-core'; +import { Optional, UUIDv4 } from 'src/common/types'; +import { InjectDatabase } from 'src/infra/decorators/inject-database.decorator'; +import { Database } from 'src/infra/postgres/other/types'; +import { pokemonsTable, tradesTable, tradesToSenderItemsTable, TradeToSenderItemEntity, userItemsTable, usersTable } from 'src/infra/postgres/tables'; +import { TradesService } from './trades.service'; +import { UserItemsService } from './user-items.service'; + +type Where = Partial<{ + tradeId: UUIDv4, + senderItemId: UUIDv4, +}> + +type FindOptions = Partial<{ + where: Where, +}> + +@Injectable() +export class TradesToSenderItemsService { + public constructor( + @InjectDatabase() + private readonly db: Database, + + private readonly tradesService: TradesService, + private readonly userItemsService: UserItemsService, + ) {} + + private mapWhereToSQL( + where: Where, + ): Optional { + return and( + where.tradeId !== undefined + ? eq(tradesToSenderItemsTable.tradeId, where.tradeId) + : undefined, + where.senderItemId !== undefined + ? eq(tradesToSenderItemsTable.senderItemId, where.senderItemId) + : undefined, + ); + } + + private baseSelectBuilder( + findOptions: FindOptions, + ) { + const { where = {} } = findOptions; + + const sendersTable = alias(usersTable, 'senders'); + const receiversTable = alias(usersTable, 'receivers'); + + return this.db + .select() + .from(tradesToSenderItemsTable) + .innerJoin(tradesTable, eq(tradesTable.id, tradesToSenderItemsTable.tradeId)) + .innerJoin(sendersTable, eq(sendersTable.id, tradesTable.senderId)) + .innerJoin(receiversTable, eq(receiversTable.id, tradesTable.receiverId)) + .innerJoin(userItemsTable, eq(userItemsTable.id, tradesToSenderItemsTable.senderItemId)) + .innerJoin(usersTable, eq(usersTable.id, userItemsTable.userId)) + .innerJoin(pokemonsTable, eq(pokemonsTable.id, userItemsTable.pokemonId)) + .where(this.mapWhereToSQL(where)); + } + + private mapSelectBuilderRowToEntity( + row: Record< + | 'trades_to_sender_items' + | 'trades' + | 'senders' + | 'receivers' + | 'user_items' + | 'users' + | 'pokemons', + any>, + ) { + return { + ...row.trades_to_sender_items, + trade: this.tradesService.mapSelectBuilderRowToEntity(row), + senderItem: this.userItemsService.mapSelectBuilderRowToEntity(row), + } + } + + public async findMany( + findOptions: FindOptions, + ): Promise> { + return this + .baseSelectBuilder(findOptions) + .then((rows) => rows.map((row) => this.mapSelectBuilderRowToEntity(row))); + } +} diff --git a/src/core/services/trades.service.ts b/src/core/services/trades.service.ts index 521c9db..55fb2f4 100644 --- a/src/core/services/trades.service.ts +++ b/src/core/services/trades.service.ts @@ -1,108 +1,80 @@ import { Injectable } from '@nestjs/common'; -import { NodePgDatabase } from 'drizzle-orm/node-postgres'; -import { InjectDrizzle } from 'src/infra/postgres/postgres.module'; -import * as tables from 'src/infra/postgres/tables'; -import { BaseService } from './base.service'; -import { PgInsertValue } from 'drizzle-orm/pg-core'; -import { - TradeEntity as TradeEntityDrizzle, - Transaction, - UserItemEntity as UserItemEntityDrizzle, - PendingTradeEntity as PendingTradeEntityDrizzle, - CancelledTradeEntity as CancelledTradeEntityDrizzle, - AcceptedTradeEntity as AcceptedTradeEntityDrizzle, - RejectedTradeEntity as RejectedTradeEntityDrizzle, - EntityRelations, -} from 'src/infra/postgres/other/types'; -import { tradeReceiverItems, tradeSenderItems } from 'src/infra/postgres/tables'; -import { ExtractTablesWithRelations } from 'drizzle-orm'; +import { Database } from 'src/infra/postgres/other/types'; +import { tradesTable, usersTable, TradeEntity, TradeStatus } from 'src/infra/postgres/tables'; +import { InjectDatabase } from 'src/infra/decorators/inject-database.decorator'; +import { and, eq, inArray, SQL } from 'drizzle-orm'; +import { Nullable, Optional, UUIDv4 } from 'src/common/types'; +import { alias } from 'drizzle-orm/pg-core'; +type Where = Partial<{ + id: UUIDv4, + ids: Array + status: TradeStatus, +}> + +type FindOptions = Partial<{ + where: Where, +}> @Injectable() -export class TradesService extends BaseService<'trades'> { +export class TradesService { public constructor( - @InjectDrizzle() - drizzle: NodePgDatabase, - ) { - super('trades', drizzle); - } + @InjectDatabase() + private readonly db: Database, + ) {} - public override async createOne( - values: PgInsertValue & { - senderItems: Array, - receiverItems: Array, - }, - tx?: Transaction, - ): Promise> { - const trade = await super.createOne(values, tx); - - const [senderItems, receiverItems] = await Promise.all([ - (tx ?? super.drizzle) - .insert(tradeSenderItems) - .values(values.senderItems.map((senderItem) => ({ - tradeId: trade.id, - userItemId: senderItem.id, - }))) - .returning(), - (tx ?? super.drizzle) - .insert(tradeReceiverItems) - .values(values.receiverItems.map((receiverItem) => ({ - tradeId: trade.id, - userItemId: receiverItem.id, - }))) - .returning(), - ]); + private mapWhereToSQL( + where: Where, + ): Optional { + return and( + where.id !== undefined + ? eq(tradesTable.id, where.id) + : undefined, + where.ids !== undefined + ? inArray(tradesTable.id, where.ids) + : undefined, + where.status !== undefined + ? eq(tradesTable.status, where.status) + : undefined, + ); + } + public mapSelectBuilderRowToEntity( + row: Record<'trades' | 'senders' | 'receivers', any>, + ) { return { - ...trade, - senderItems, - receiverItems, - }; + ...row.trades, + sender: row.senders, + receiver: row.receivers, + } } - public async createOnePending( - values: Omit & { - senderItems: Array, - receiverItems: Array, - }, 'status'>, - tx?: Transaction, - ): Promise> { - return this.createOne({ - ...values, - status: 'PENDING', - }, tx) as Promise>; - } + private baseSelectBuilder( + findOptions: FindOptions, + ) { + const { where = {} } = findOptions; - public async updateOneToCancelled< - TEntityRelations extends EntityRelations, ExtractTablesWithRelations['trades']>, - >( - pendingTrade: PendingTradeEntityDrizzle, - tx?: Transaction, - ): Promise> { - return super.updateOne(pendingTrade, { - status: 'CANCELLED', - }, tx) as Promise>; - } + const sendersTable = alias(usersTable, 'senders'); + const receiversTable = alias(usersTable, 'receivers'); - public async updateOneToAccepted< - TEntityRelations extends EntityRelations, ExtractTablesWithRelations['trades']>, - >( - pendingTrade: PendingTradeEntityDrizzle, - tx?: Transaction, - ): Promise> { - return super.updateOne(pendingTrade, { - status: 'ACCEPTED', - }, tx) as Promise>; + return this.db + .select() + .from(tradesTable) + .innerJoin(sendersTable, eq(sendersTable.id, tradesTable.senderId)) + .innerJoin(receiversTable, eq(receiversTable.id, tradesTable.receiverId)) + .where(this.mapWhereToSQL(where)); } - public async updateOneToRejected< - TEntityRelations extends EntityRelations, ExtractTablesWithRelations['trades']>, - >( - pendingTrade: PendingTradeEntityDrizzle, - tx?: Transaction, - ): Promise> { - return super.updateOne(pendingTrade, { - status: 'REJECTED', - }, tx) as Promise>; + public async findOne( + findOptions: FindOptions, + ): Promise> { + return this + .baseSelectBuilder(findOptions) + .limit(1) + .then(([row]) => ( + row + ? this.mapSelectBuilderRowToEntity(row) + : null + )); } } diff --git a/src/core/services/user-items.service.ts b/src/core/services/user-items.service.ts index e4df04d..ca0fc01 100644 --- a/src/core/services/user-items.service.ts +++ b/src/core/services/user-items.service.ts @@ -1,15 +1,200 @@ import { Injectable } from '@nestjs/common'; -import { NodePgDatabase } from 'drizzle-orm/node-postgres'; -import { InjectDrizzle } from 'src/infra/postgres/postgres.module'; -import { BaseService } from './base.service'; -import * as tables from 'src/infra/postgres/tables'; +import { Database, Transaction } from 'src/infra/postgres/other/types'; +import { InjectDatabase } from 'src/infra/decorators/inject-database.decorator'; +import { Nullable, Optional, PaginatedArray, PaginationOptions, UUIDv4 } from 'src/common/types'; +import { CreateUserItemEntityValues, pokemonsTable, UpdateUserItemEntityValues, UserItemEntity, userItemsTable, usersTable } from 'src/infra/postgres/tables'; +import { and, eq, inArray, like, SQL } from 'drizzle-orm'; +import { mapArrayToPaginatedArray } from 'src/common/helpers/map-array-to-paginated-array.helper'; +import { zip } from 'lodash'; + +type Where = Partial<{ + id: UUIDv4, + ids: Array, + userId: UUIDv4, + pokemonId: number, + userName: string, + userNameLike: string, + pokemonName: string, + pokemonNameLike: string, +}>; + +type FindOptions = Partial<{ + where: Where, +}> + +type FindWithPaginationOptions = FindOptions & { + paginationOptions: PaginationOptions, +} @Injectable() -export class UserItemsService extends BaseService<'userItems'> { +export class UserItemsService { public constructor( - @InjectDrizzle() - drizzle: NodePgDatabase, + @InjectDatabase() + private readonly db: Database, + ) {} + + private mapWhereToSQL( + where: Where + ): Optional { + return and( + where.id !== undefined + ? eq(userItemsTable.id, where.id) + : undefined, + where.ids !== undefined + ? inArray(userItemsTable.id, where.ids) + : undefined, + + where.userId !== undefined + ? eq(userItemsTable.userId, where.userId) + : undefined, + where.userName !== undefined + ? eq(usersTable.name, where.userName) + : undefined, + where.userNameLike !== undefined + ? like(usersTable.name, `%${where.userNameLike}%`) + : undefined, + + where.pokemonId !== undefined + ? eq(userItemsTable.pokemonId, where.pokemonId) + : undefined, + where.pokemonName !== undefined + ? eq(pokemonsTable.name, where.pokemonName) + : undefined, + where.pokemonNameLike !== undefined + ? like(pokemonsTable.name, `%${where.pokemonNameLike}%`) + : undefined, + ) + } + + private baseSelectBuilder( + findOptions: FindOptions, + ) { + const { where = {} } = findOptions; + + return this.db + .select() + .from(userItemsTable) + .innerJoin(usersTable, eq(userItemsTable.userId, usersTable.id)) + .innerJoin(pokemonsTable, eq(userItemsTable.pokemonId, pokemonsTable.id)) + .where(this.mapWhereToSQL(where)); + } + + public mapSelectBuilderRowToEntity( + row: Record<'user_items' | 'users' | 'pokemons', any>, ) { - super('userItems', drizzle); + return { + ...row.user_items, + user: row.users, + pokemon: row.pokemons, + } + } + + public async findMany( + findOptions: FindOptions, + ): Promise> { + return this + .baseSelectBuilder(findOptions) + .then((rows) => rows.map((row) => this.mapSelectBuilderRowToEntity(row))); + } + + public async findManyWithPagination( + findWithPaginationOptions: FindWithPaginationOptions, + ): Promise> { + const { paginationOptions: { page, limit } } = findWithPaginationOptions; + // TODO: check for boundaries + const offset = (page - 1) * limit; + + return this + .baseSelectBuilder(findWithPaginationOptions) + .offset(offset) + .limit(limit) + .then((rows) => mapArrayToPaginatedArray( + rows.map((row) => this.mapSelectBuilderRowToEntity(row)), + { page, limit }, + )); + } + + public async findOne( + findOptions: FindOptions, + ): Promise> { + return this + .baseSelectBuilder(findOptions) + .limit(1) + .then(([row]) => ( + row + ? this.mapSelectBuilderRowToEntity(row) + : null + )); + } + + public async createOne( + values: CreateUserItemEntityValues, + tx?: Transaction, + ): Promise { + const { user, pokemon } = values; + + return (tx ?? this.db) + .insert(userItemsTable) + .values({ + ...values, + userId: user.id, + pokemonId: pokemon.id, + }) + .returning() + .then(([userItem]) => ({ + ...userItem!, + user, + pokemon, + })); + } + + public async updateMany( + userItems: Array, + values: UpdateUserItemEntityValues, + tx?: Transaction, + ): Promise> { + if (!userItems.length) return []; + + const { user, pokemon, ...restValues } = values; + + return (tx ?? this.db) + .update(userItemsTable) + .set({ + ...restValues, + userId: user?.id, + pokemonId: pokemon?.id, + }) + .where(inArray(userItemsTable.id, userItems.map(({ id }) => id))) + .returning() + .then((updatedUserItems) => zip(userItems, updatedUserItems).map(([userItem, updatedUserItem]) => ({ + ...updatedUserItem!, + user: user ?? userItem!.user, + pokemon: pokemon ?? userItem!.pokemon, + }))); + } + + public async updateOne( + userItem: UserItemEntity, + values: UpdateUserItemEntityValues, + tx?: Transaction, + ): Promise { + return this + .updateMany([userItem], values, tx) + .then(([userItem]) => userItem!); + } + + public async deleteOne( + userItem: UserItemEntity, + tx?: Transaction, + ): Promise { + return (tx ?? this.db) + .delete(userItemsTable) + .where(eq(userItemsTable.id, userItem.id)) + .returning() + .then(([deletedUserItem]) => ({ + ...deletedUserItem!, + user: userItem.user, + pokemon: userItem.pokemon, + })) } } diff --git a/src/core/services/users.service.ts b/src/core/services/users.service.ts index b3c60e1..8e9e3fd 100644 --- a/src/core/services/users.service.ts +++ b/src/core/services/users.service.ts @@ -1,15 +1,133 @@ import { Injectable } from '@nestjs/common'; -import { NodePgDatabase } from 'drizzle-orm/node-postgres'; -import { InjectDrizzle } from 'src/infra/postgres/postgres.module'; -import { BaseService } from './base.service'; -import * as tables from 'src/infra/postgres/tables'; +import { Database, Transaction } from 'src/infra/postgres/other/types'; +import { InjectDatabase } from 'src/infra/decorators/inject-database.decorator'; +import { Nullable, Optional, PaginatedArray, PaginationOptions, UUIDv4 } from 'src/common/types'; +import { CreateUserEntityValues, UpdateUserEntityValues, UserEntity, usersTable } from 'src/infra/postgres/tables'; +import { and, eq, inArray, like, SQL } from 'drizzle-orm'; +import { mapArrayToPaginatedArray } from 'src/common/helpers/map-array-to-paginated-array.helper'; + +type Where = Partial<{ + id: UUIDv4, + ids: Array, + name: string, + nameLike: string, +}>; + +type FindOptions = Partial<{ + where: Where, + extraFields: Record, +}> + +type FindWithPaginationOptions = FindOptions & { + paginationOptions: PaginationOptions, +} @Injectable() -export class UsersService extends BaseService<'users'> { +export class UsersService { public constructor( - @InjectDrizzle() - drizzle: NodePgDatabase, + @InjectDatabase() + private readonly db: Database, + ) {} + + private mapWhereToSQL( + where: Where + ): Optional { + return and( + where.id !== undefined + ? eq(usersTable.id, where.id) + : undefined, + where.ids !== undefined + ? inArray(usersTable.id, where.ids) + : undefined, + where.name !== undefined + ? eq(usersTable.name, where.name) + : undefined, + where.nameLike !== undefined + ? like(usersTable.name, `%${where.nameLike}%`) + : undefined, + ) + } + + private baseSelectBuilder( + findOptions: FindOptions, ) { - super('users', drizzle); + const { where = {} } = findOptions; + + return this.db + .select() + .from(usersTable) + .where(this.mapWhereToSQL(where)); + } + + public async findMany( + findOptions: FindOptions, + ): Promise> { + return this.baseSelectBuilder(findOptions); + } + + public async findManyWithPagination( + findWithPaginationOptions: FindWithPaginationOptions, + ): Promise> { + const { + paginationOptions: { page, limit }, + } = findWithPaginationOptions; + // TODO: check for boundaries + const offset = (page - 1) * limit; + + // TODO: Pass these values to `mapArrayToPaginatedArray` + // const totalItems = await this.db + // .select({ + // totalItems: count(), + // }) + // .from(usersTable) + // .where(this.mapWhereToSQL(where)) + // .then(([row]) => row!.totalItems); + // const totalPages = Math.ceil(totalItems / offset); + + return this + .baseSelectBuilder(findWithPaginationOptions) + .offset(offset) + .limit(limit) + .then((users) => mapArrayToPaginatedArray(users, { page, limit })); + } + + public async findOne( + findOptions: FindOptions, + ): Promise> { + return this.baseSelectBuilder(findOptions) + .limit(1) + .then(([user]) => user ?? null); + } + + public async exists( + findOptions: FindOptions = {}, + ): Promise { + return this + .findOne(findOptions) + .then((user) => Boolean(user)); + } + + public async createOne( + values: CreateUserEntityValues, + tx?: Transaction, + ): Promise { + return (tx ?? this.db) + .insert(usersTable) + .values(values) + .returning() + .then(([user]) => user!); + } + + public async updateOne( + user: UserEntity, + values: UpdateUserEntityValues, + tx?: Transaction, + ): Promise { + return (tx ?? this.db) + .update(usersTable) + .set(values) + .where(eq(usersTable.id, user.id)) + .returning() + .then(([updatedUser]) => updatedUser!); } } diff --git a/src/core/use-cases/auth.use-case.ts b/src/core/use-cases/auth.use-case.ts index 95f6ccb..b4f7e58 100644 --- a/src/core/use-cases/auth.use-case.ts +++ b/src/core/use-cases/auth.use-case.ts @@ -6,7 +6,7 @@ import { RegisterOutputDTO } from 'src/api/dtos/auth/register.output.dto'; import { JWT, UserTokenPayload } from 'src/common/types'; import { UsersUseCase } from './users.use-case'; import * as bcrypt from 'bcrypt'; -import { UserEntity } from 'src/infra/postgres/other/types'; +import { UserEntity } from 'src/infra/postgres/tables'; @Injectable() export class AuthUseCase { diff --git a/src/core/use-cases/opened-packs.use-case.ts b/src/core/use-cases/opened-packs.use-case.ts deleted file mode 100644 index 01af30d..0000000 --- a/src/core/use-cases/opened-packs.use-case.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { Injectable } from '@nestjs/common'; -import { UserEntity, PackEntity, PokemonEntity, OpenedPackEntity, Transaction } from 'src/infra/postgres/other/types'; -import { OpenedPacksService } from '../services/opened-packs.service'; - -@Injectable() -export class OpenedPacksUseCase { - public constructor( - private readonly openedPacksService: OpenedPacksService, - ) {} - - public async createOpenedPack( - user: UserEntity, - pack: PackEntity, - pokemon: PokemonEntity, - tx?: Transaction, - ): Promise> { - return this.openedPacksService - .createOne({ - userId: user.id, - packId: pack.id, - pokemonId: pokemon.id, - }, tx) - .then((openedPack) => ({ - ...openedPack, - user, - pack, - pokemon, - })) - } -} diff --git a/src/core/use-cases/packs.use-case.ts b/src/core/use-cases/packs.use-case.ts index 0fc2beb..e309164 100644 --- a/src/core/use-cases/packs.use-case.ts +++ b/src/core/use-cases/packs.use-case.ts @@ -1,39 +1,48 @@ import { HttpException, HttpStatus, Injectable } from '@nestjs/common'; import { UsersUseCase } from './users.use-case'; -import { UUIDv4 } from 'src/common/types'; +import { PaginatedArray, UUIDv4 } from 'src/common/types'; import { PacksService } from '../services/packs.service'; -import { OpenedPacksUseCase } from './opened-packs.use-case'; import { UserItemsUseCase } from './user-items.use-case'; -import { and, eq } from 'drizzle-orm'; -import { PackEntity, Transaction, UserEntity } from 'src/infra/postgres/other/types'; +import { Transaction } from 'src/infra/postgres/other/types'; +import { OpenedPackEntity, PackEntity, PokemonEntity, UserEntity } from 'src/infra/postgres/tables'; +import { GetPacksInputDTO } from 'src/api/dtos/packs/get-packs.input.dto'; +import { PaginationInputDTO } from 'src/api/dtos/pagination.input.dto'; +import { OpenedPacksService } from '../services/opened-packs.service'; @Injectable() export class PacksUseCase { public constructor( private readonly packsService: PacksService, + private readonly openedPacksService: OpenedPacksService, private readonly usersUseCase: UsersUseCase, private readonly userItemsUseCase: UserItemsUseCase, - private readonly openedPacksUseCase: OpenedPacksUseCase, ) {} public async getPacksWithPagination( - paginationOptions: { page: number, limit: number } - ) { - return this.packsService.findManyWithPagination(paginationOptions); + dto: GetPacksInputDTO, + paginationDTO: PaginationInputDTO, + ): Promise> { + return this.packsService.findManyWithPagination({ + paginationOptions: paginationDTO, + where: dto, + }); } public async getPack( where: Partial<{ id: UUIDv4, name: string }> = {}, - errorMessage: string = 'Pack not found', - errorStatus: HttpStatus = HttpStatus.NOT_FOUND, - ) { + options: Partial<{ + errorMessage: string, + errorStatus: HttpStatus, + }> = {}, + ): Promise { + const { + errorMessage = 'Pack not found', + errorStatus= HttpStatus.NOT_FOUND, + } = options; + const pack = await this.packsService.findOne({ - where: (table) => and( - // TODO: Maybe there is a better way to do that - ...(where.id ? [eq(table.id, where.id)] : []), - ...(where.name ? [eq(table.name, where.name)] : []), - ), + where, }); if (!pack) { @@ -43,7 +52,7 @@ export class PacksUseCase { return pack; } - public async getRandomPokemonFromPack(pack: PackEntity) { + public async getRandomPokemonFromPack(pack: PackEntity): Promise { const pokemon = await this.packsService.findRandomPokemonFromPack(pack); if (!pokemon) { @@ -60,7 +69,7 @@ export class PacksUseCase { user: UserEntity, pack: PackEntity, tx?: Transaction, - ) { + ): Promise { if (user.balance < pack.price) { throw new HttpException('Insufficient balance', HttpStatus.CONFLICT); } @@ -73,22 +82,24 @@ export class PacksUseCase { const pokemon = awaitedPromises[0]; user = awaitedPromises[1]; - const userPokemon = await this.userItemsUseCase.createUserItem( + const userItem = await this.userItemsUseCase.createUserItem( user, pokemon, tx, ); - // TODO: Have to assert that userPokemon.user is not nullable - // Figure it out how to not do that - return this.openedPacksUseCase.createOpenedPack(userPokemon.user!, pack, pokemon, tx); + return this.openedPacksService.createOne({ + user: userItem.user, + pack, + pokemon: userItem.pokemon, + }, tx); } public async openPackById( user: UserEntity, id: UUIDv4, tx?: Transaction, - ) { + ): Promise { const pack = await this.getPack({ id }); return this.openPack(user, pack, tx); diff --git a/src/core/use-cases/pending-trades.use-case.ts b/src/core/use-cases/pending-trades.use-case.ts index a9cf055..798b4c7 100644 --- a/src/core/use-cases/pending-trades.use-case.ts +++ b/src/core/use-cases/pending-trades.use-case.ts @@ -1,25 +1,20 @@ -import { HttpException, HttpStatus } from '@nestjs/common'; -import { and, eq, ExtractTablesWithRelations } from 'drizzle-orm'; +import { HttpException, HttpStatus, Injectable } from '@nestjs/common'; import { CreatePendingTradeInputDTO } from 'src/api/dtos/pending-trades/create-pending-trade.input.dto'; import { UUIDv4 } from 'src/common/types'; -import { - CancelledTradeEntity, - AcceptedTradeEntity, - RejectedTradeEntity, - PendingTradeEntity, - Transaction, - UserEntity, - EntityRelations, - entityRelationsToWith, -} from 'src/infra/postgres/other/types'; -import { TradesService } from '../services/trades.service'; +import { Transaction } from 'src/infra/postgres/other/types'; import { UserItemsUseCase } from './user-items.use-case'; import { UsersUseCase } from './users.use-case'; -import * as tables from 'src/infra/postgres/tables'; +import { AcceptedTradeEntity, CancelledTradeEntity, PendingTradeEntity, RejectedTradeEntity, TradeToReceiverItemEntity, TradeToSenderItemEntity, UserEntity } from 'src/infra/postgres/tables'; +import { PendingTradesService } from '../services/pending-trades.service'; +import { TradesToSenderItemsService } from '../services/trades-to-sender-items.service'; +import { TradesToReceiverItemsService } from '../services/trades-to-receiver-items.service'; +@Injectable() export class PendingTradesUseCase { public constructor( - private readonly tradesService: TradesService, + private readonly pendingTradesService: PendingTradesService, + private readonly tradesToSenderItemsService: TradesToSenderItemsService, + private readonly tradesToReceiverItemsService: TradesToReceiverItemsService, private readonly userItemsUseCase: UserItemsUseCase, private readonly usersUseCase: UsersUseCase, ) {} @@ -28,16 +23,24 @@ export class PendingTradesUseCase { sender: UserEntity, dto: CreatePendingTradeInputDTO, tx?: Transaction, - ): Promise> { + ): Promise<{ + pendingTrade: PendingTradeEntity, + tradesToSenderItems: Array, + tradesToReceiverItems: Array, + }> { + if (!dto.senderItemIds.length && !dto.receiverItemIds.length) { + throw new HttpException( + '`senderItemIds` and `receiverItemIds` cannot be empty simultaneously', + HttpStatus.BAD_REQUEST + ); + } + const [senderItems, receiver, receiverItems] = await Promise.all([ this.userItemsUseCase.getUserItemsByIds( dto.senderItemIds, - (id) => `Trade sender item (\`${id}\`) not found`, + { + errorMessageFn: (id) => `Trade sender item (\`${id}\`) not found`, + }, ), this.usersUseCase.getUserById( dto.receiverId, @@ -47,7 +50,9 @@ export class PendingTradesUseCase { ), this.userItemsUseCase.getUserItemsByIds( dto.receiverItemIds, - (id) => `Trade receiver item (\`${id}\`) not found`, + { + errorMessageFn: (id) => `Trade receiver item (\`${id}\`) not found`, + } ), ]); @@ -56,7 +61,7 @@ export class PendingTradesUseCase { } for (const senderItem of senderItems) { - if (senderItem.user!.id !== sender.id) { + if (senderItem.user.id !== sender.id) { throw new HttpException( `Trade sender item (\`${senderItem.id}\`) does not belong to you`, HttpStatus.CONFLICT, @@ -65,7 +70,7 @@ export class PendingTradesUseCase { } for (const receiverItem of receiverItems) { - if (receiverItem.user!.id !== receiver.id) { + if (receiverItem.user.id !== receiver.id) { throw new HttpException( `Trade receiver item (\`${receiverItem.id}\`) does not belong to them`, HttpStatus.CONFLICT, @@ -73,167 +78,140 @@ export class PendingTradesUseCase { } } - return this.tradesService - .createOnePending({ - senderId: sender.id, - senderItems, - receiverId: receiver.id, - receiverItems, - }, tx) - .then((trade) => ({ - ...trade, - sender, - receiver, - })); + return this.pendingTradesService.createOne({ + sender, + senderItems, + receiver, + receiverItems, + }, tx); } - public async getPendingTrade< - TEntityRelations extends EntityRelations, ExtractTablesWithRelations['trades']> = {}, - >( + public async getPendingTrade( where: Partial<{ id: UUIDv4 }> = {}, options: Partial<{ - entityRelations: TEntityRelations, errorMessage: string; errorStatus: HttpStatus; }> = {}, - ): Promise> { - const pendingTrade = await this.tradesService.findOne({ - where: (table) => and( - ...(where.id ? [eq(table.id, where.id)]: []), - eq(table.status, 'PENDING'), - ), - with: entityRelationsToWith(options.entityRelations ?? {}), - }) as PendingTradeEntity; + ): Promise { + const { + errorMessage = 'Pending trade not found', + errorStatus = HttpStatus.NOT_FOUND, + } = options; + + const pendingTrade = await this.pendingTradesService.findOne({ + where + }); if (!pendingTrade) { - throw new HttpException( - options.errorMessage ?? 'Pending trade not found', - options.errorStatus ?? HttpStatus.NOT_FOUND, - ); + throw new HttpException(errorMessage, errorStatus); } return pendingTrade; } - public async getPendingTradeById< - TEntityRelations extends EntityRelations, ExtractTablesWithRelations['trades']> = {}, - >( + public async getPendingTradeById( id: UUIDv4, options: Partial<{ - entityRelations: TEntityRelations, errorMessageFn: (id: UUIDv4) => string, errorStatus: HttpStatus, }> = {}, - ): Promise> { + ): Promise { + const { + errorMessageFn = (id) => `Pending trade (\`${id}\`) not found`, + errorStatus = HttpStatus.NOT_FOUND, + } = options; + return this.getPendingTrade({ id }, { - entityRelations: options.entityRelations, - errorMessage: options.errorMessageFn?.(id) ?? `Pending trade (\`${id}\`) not found`, - errorStatus: options.errorStatus, + errorMessage: errorMessageFn(id), + errorStatus, }); } - public async cancelPendingTrade< - TEntityRelations extends EntityRelations, ExtractTablesWithRelations['trades']> & { sender: true }, - >( + public async cancelPendingTrade( user: UserEntity, - pendingTrade: PendingTradeEntity, + pendingTrade: PendingTradeEntity, tx?: Transaction, - ): Promise> { - if (user.id !== pendingTrade.sender!.id) { + ): Promise { + if (user.id !== pendingTrade.sender.id) { throw new HttpException('You cannot cancel a trade that is not yours', HttpStatus.CONFLICT); } - return this.tradesService.updateOneToCancelled(pendingTrade, tx); + return this.pendingTradesService.updateOneToCancelled(pendingTrade, tx); } public async cancelPendingTradeById( user: UserEntity, id: UUIDv4, tx?: Transaction, - ): Promise> { - const pendingTrade = await this.getPendingTradeById(id, { - entityRelations: { sender: true } - }); + ): Promise { + const pendingTrade = await this.getPendingTradeById(id); return this.cancelPendingTrade(user, pendingTrade, tx); } public async acceptPendingTrade( user: UserEntity, - pendingTrade: PendingTradeEntity<{ - sender: true, - senderItems: { userItem: true }, - receiver: true, - receiverItems: { userItem: true }, - }>, + pendingTrade: PendingTradeEntity, tx?: Transaction, - ): Promise> { + ): Promise { const { sender, - senderItems, receiver, - receiverItems, } = pendingTrade; - if (user.id !== receiver!.id) { + if (user.id !== receiver.id) { throw new HttpException('You are not a receiver of this trade', HttpStatus.CONFLICT); } + const [tradesToSenderItems, tradesToReceiverItems] = await Promise.all([ + this.tradesToSenderItemsService.findMany({ + where: { + tradeId: pendingTrade.id, + }, + }), + this.tradesToReceiverItemsService.findMany({ + where: { + tradeId: pendingTrade.id, + }, + }), + ]); + await Promise.all([ this.userItemsUseCase.transferUserItemsToAnotherUser( - senderItems.map(({ userItem }) => userItem!), - receiver!, + tradesToSenderItems.map(({ senderItem }) => senderItem), + receiver, tx, ), this.userItemsUseCase.transferUserItemsToAnotherUser( - receiverItems.map(({ userItem }) => userItem!), - sender!, + tradesToReceiverItems.map(({ receiverItem }) => receiverItem), + sender, tx, ), ]); - return this.tradesService.updateOneToAccepted(pendingTrade, tx); + return this.pendingTradesService.updateOneToAccepted(pendingTrade, tx); } public async acceptPendingTradeById( user: UserEntity, id: UUIDv4, tx?: Transaction, - ): Promise> { - const pendingTrade = await this.getPendingTradeById(id, { - entityRelations: { - sender: true, - senderItems: { userItem: true }, - receiver: true, - receiverItems: { userItem: true }, - } - }); + ): Promise { + const pendingTrade = await this.getPendingTradeById(id); return this.acceptPendingTrade(user, pendingTrade, tx); } - public async rejectPendingTrade< - TEntityRelations extends EntityRelations, ExtractTablesWithRelations['trades']> & { receiver: true }, - >( + public async rejectPendingTrade( user: UserEntity, - pendingTrade: PendingTradeEntity, + pendingTrade: PendingTradeEntity, tx?: Transaction, - ): Promise> { - if (user.id !== pendingTrade.receiver!.id) { + ): Promise { + if (user.id !== pendingTrade.receiver.id) { throw new HttpException('You cannot reject a trade that is sent to you', HttpStatus.CONFLICT); } - return this.tradesService.updateOneToRejected(pendingTrade, tx); + return this.pendingTradesService.updateOneToRejected(pendingTrade, tx); } public async rejectPendingTradeById( @@ -241,9 +219,7 @@ export class PendingTradesUseCase { id: UUIDv4, tx?: Transaction, ) { - const pendingTrade = await this.getPendingTradeById(id, { - entityRelations: { receiver: true } - }) + const pendingTrade = await this.getPendingTradeById(id); return this.rejectPendingTrade(user, pendingTrade, tx); } diff --git a/src/core/use-cases/quick-sold-user-items.use-case.ts b/src/core/use-cases/quick-sold-user-items.use-case.ts deleted file mode 100644 index 2a0da99..0000000 --- a/src/core/use-cases/quick-sold-user-items.use-case.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { Injectable } from '@nestjs/common'; -import { - PokemonEntity, - QuickSoldUserItemEntity, - UserEntity, - UserItemEntity, -} from 'src/infra/postgres/other/types'; -import { QuickSoldUserItemsService } from '../services/quick-sold-user-items.service'; - -@Injectable() -export class QuickSoldUserItemsUseCase { - public constructor( - private readonly quickSoldUserItemsService: QuickSoldUserItemsService, - ) {} - - public async createQuickSoldUserItem( - userItem: UserItemEntity, - user: UserEntity, - pokemon: PokemonEntity, - ): Promise> { - return this.quickSoldUserItemsService - .createOne({ - id: userItem.id, - userId: user.id, - pokemonId: pokemon.id, - }) - .then((quickSoldUserItem) => ({ - ...quickSoldUserItem, - user, - pokemon, - })); - } -} diff --git a/src/core/use-cases/user-items.use-case.ts b/src/core/use-cases/user-items.use-case.ts index 24c59fe..c580296 100644 --- a/src/core/use-cases/user-items.use-case.ts +++ b/src/core/use-cases/user-items.use-case.ts @@ -1,36 +1,36 @@ import { HttpException, HttpStatus, Injectable } from '@nestjs/common'; -import { and, eq, ExtractTablesWithRelations, inArray } from 'drizzle-orm'; -import { UUIDv4 } from 'src/common/types'; -import { UserItemEntity, UserEntity, PokemonEntity, Transaction, EntityRelations } from 'src/infra/postgres/other/types'; +import { PaginationInputDTO } from 'src/api/dtos/pagination.input.dto'; +import { GetUserItemsInputDTO } from 'src/api/dtos/user-items/get-user-items.input.dto'; +import { PaginatedArray, UUIDv4 } from 'src/common/types'; +import { Transaction } from 'src/infra/postgres/other/types'; +import { PokemonEntity, QuickSoldUserItemEntity, UserEntity, UserItemEntity } from 'src/infra/postgres/tables'; +import { QuickSoldUserItemsService } from '../services/quick-sold-user-items.service'; import { UserItemsService } from '../services/user-items.service'; -import { QuickSoldUserItemsUseCase } from './quick-sold-user-items.use-case'; import { UsersUseCase } from './users.use-case'; -import * as tables from 'src/infra/postgres/tables'; - @Injectable() export class UserItemsUseCase { public constructor( private readonly userItemsService: UserItemsService, + private readonly quickSoldUserItemsService: QuickSoldUserItemsService, private readonly usersUseCase: UsersUseCase, - private readonly quickSoldUserItemsUseCase: QuickSoldUserItemsUseCase, ) {} public async getUserItem( - where: Partial<{ id: UUIDv4 }> = {}, - errorMessage: string = 'User item not found', - errorStatus: HttpStatus = HttpStatus.NOT_FOUND, - ): Promise> { + where: Partial<{ id: UUIDv4, user: UserEntity }> = {}, + options: Partial<{ + errorMessage: string, + errorStatus: HttpStatus, + }> = {}, + ): Promise { + const { + errorMessage = 'User item not found', + errorStatus = HttpStatus.NOT_FOUND, + } = options; + const userItem = await this.userItemsService.findOne({ - where: (table) => and( - // TODO: Maybe there is a better way to do that - ...(where.id ? [eq(table.id, where.id)] : []), - ), - with: { - user: true, - pokemon: true, - } + where }); if (!userItem) { @@ -42,37 +42,55 @@ export class UserItemsUseCase { public async getUserItemById( id: UUIDv4, - errorMessageFn: (id: UUIDv4) => string = (id) => `User item (\`${id}\`) not found`, - errorStatus: HttpStatus = HttpStatus.NOT_FOUND, - ): Promise> { - return this.getUserItem({ id }, errorMessageFn(id), errorStatus) + options: Partial<{ + errorMessageFn: (id: UUIDv4) => string, + errorStatus: HttpStatus, + }> = {}, + ): Promise { + const { + errorMessageFn = (id) => `User item (\`${id}\`) not found`, + errorStatus = HttpStatus.NOT_FOUND, + } = options; + + return this.getUserItem({ id }, { + errorMessage: errorMessageFn(id), + errorStatus, + }); + } + + public async getUserItemsWithPagination( + dto: GetUserItemsInputDTO, + paginationDTO: PaginationInputDTO, + ): Promise> { + return this.userItemsService.findManyWithPagination({ + paginationOptions: paginationDTO, + where: dto, + }); } - // public async findManyUserItems( - // where: Partial<{ id: UUIDv4 }> = {}, - // ): Promise>> { - // return this.userItemsService.findMany({ - // where: (table) => and( - // ...(where.id ? [eq(table.id, where.id)] : []), - // ), - // with: { - // user: true, - // pokemon: true, - // } - // }); - // } + public async getUserItemsWithPaginationByUser( + user: UserEntity, + paginationDTO: PaginationInputDTO, + ): Promise> { + return this.getUserItemsWithPagination({ userId: user.id }, paginationDTO); + } public async getUserItemsByIds( ids: Array, - errorMessageFn: (id: UUIDv4) => string = (id) => `User item (\`${id}\`) not found`, - errorStatus: HttpStatus = HttpStatus.NOT_FOUND, - ): Promise>> { + options: Partial<{ + errorMessageFn: (id: UUIDv4) => string, + errorStatus: HttpStatus, + }> + ): Promise> { + if (!ids.length) return []; + + const { + errorMessageFn = (id) => `User item (\`${id}\`) not found`, + errorStatus = HttpStatus.NOT_FOUND, + } = options; + const userItems = await this.userItemsService.findMany({ - where: (table) => inArray(table.id, ids), - with: { - user: true, - pokemon: true, - } + where: { ids } }); for (const id of ids) { @@ -90,74 +108,60 @@ export class UserItemsUseCase { user: UserEntity, pokemon: PokemonEntity, tx?: Transaction, - ): Promise> { - return this.userItemsService - .createOne({ - userId: user.id, - pokemonId: pokemon.id, - }, tx) - .then((userItem) => ({ - ...userItem, - user, - pokemon, - })); + ): Promise { + return this.userItemsService.createOne({ user, pokemon }, tx); } public async transferUserItemsToAnotherUser( fromUserItems: Array, toUser: UserEntity, tx?: Transaction, - ) { + ): Promise> { if (!fromUserItems.length) return []; - const set = new Set(fromUserItems.map(({ id }) => id)); + const set = new Set(fromUserItems.map(({ userId }) => userId)); if (set.size > 1) { throw new HttpException('All of the items must have the same user', HttpStatus.CONFLICT); } - const fromUserId = fromUserItems[0]!.id; + const fromUserId = fromUserItems[0]!.userId; if (fromUserId === toUser.id) { throw new HttpException('You cannot transfer items to yourself', HttpStatus.CONFLICT); } - return this.userItemsService.updateMany(fromUserItems, { userId: toUser.id }, tx); + return this.userItemsService.updateMany(fromUserItems, { user: toUser }, tx); } - public async deleteUserItem< - TEntityRelations extends EntityRelations, ExtractTablesWithRelations['userItems']> = {}, - >( - userItem: UserItemEntity, + public async quickSellUserItem( + user: UserEntity, + userItem: UserItemEntity, tx?: Transaction, - ): Promise> { - return this.userItemsService.deleteOne(userItem, tx); + ): Promise { + if (user.id !== userItem.user.id) { + throw new HttpException('You cannot quick sell someone else\'s pokemon', HttpStatus.CONFLICT); + } + + const updatedUser = await this.usersUseCase.replenishUserBalance( + user, + userItem.pokemon.worth, + tx, + ); + + return this.quickSoldUserItemsService + .createOne(userItem, tx) + .then((quickSoldUserItem) => ({ + ...quickSoldUserItem, + user: updatedUser, + })); } - public async deleteUserItemById( + public async quickSellUserItemById( + user: UserEntity, id: UUIDv4, tx?: Transaction, - ): Promise { + ): Promise { const userItem = await this.getUserItemById(id); - return this.deleteUserItem(userItem, tx); - } - - public async quickSellUserItem( - user: UserEntity, - userItem: UserItemEntity<{ user: true, pokemon: true }>, - ) { - if (user.id !== userItem.user!.id) { - throw new HttpException('You cannot quick sell someone else\'s pokemon', HttpStatus.CONFLICT); - } - - const [updatedUser, deletedUserItem] = await Promise.all([ - this.usersUseCase.replenishUserBalance(user, userItem.pokemon!.worth), - this.deleteUserItem(userItem), - ]); - - return this.quickSoldUserItemsUseCase.createQuickSoldUserItem( - userItem, - updatedUser, - deletedUserItem.pokemon!, - ) + return this.quickSellUserItem(user, userItem, tx); } } diff --git a/src/core/use-cases/users.use-case.ts b/src/core/use-cases/users.use-case.ts index a1e0072..f52f19d 100644 --- a/src/core/use-cases/users.use-case.ts +++ b/src/core/use-cases/users.use-case.ts @@ -1,12 +1,11 @@ import { HttpException, HttpStatus, Injectable } from '@nestjs/common'; import { UsersService } from '../services/users.service'; import { CreateUserInputDTO } from 'src/api/dtos/users/create-user.input.dto'; -import { UpdateUserInputDTO } from 'src/api/dtos/users/update-user.input.dto'; -import { UUIDv4 } from 'src/common/types'; -import { EntityRelations, entityRelationsToWith, Transaction, UserEntity } from 'src/infra/postgres/other/types'; +import { PaginatedArray, UUIDv4 } from 'src/common/types'; +import { Transaction } from 'src/infra/postgres/other/types'; import { GetUsersInputDTO } from 'src/api/dtos/users/get-users.input.dto'; -import { and, eq, ExtractTablesWithRelations, like } from 'drizzle-orm'; -import * as tables from 'src/infra/postgres/tables'; +import { UserEntity } from 'src/infra/postgres/tables'; +import { PaginationInputDTO } from 'src/api/dtos/pagination.input.dto'; @Injectable() export class UsersUseCase { @@ -14,99 +13,76 @@ export class UsersUseCase { private readonly usersService: UsersService, ) {} - public async getUser< - TEntityRelations extends EntityRelations, ExtractTablesWithRelations['users']> = {}, - >( + public async getUser( where: Partial<{ id: UUIDv4, name: string }> = {}, options: Partial<{ - entityRelations: TEntityRelations; errorMessage: string; errorStatus: HttpStatus; }> = {}, - ): Promise> { + ): Promise { + const { + errorMessage = 'User not found', + errorStatus = HttpStatus.NOT_FOUND, + } = options; const user = await this.usersService.findOne({ - where: (table) => and( - // TODO: Maybe there is a better way to do that - ...(where.id ? [eq(table.id, where.id)] : []), - ...(where.name ? [eq(table.name, where.name)] : []), - ), - with: entityRelationsToWith(options.entityRelations ?? {}), - }) as UserEntity; + where, + }); if (!user) { - throw new HttpException( - options.errorMessage ?? 'User not found', - options.errorStatus ?? HttpStatus.NOT_FOUND, - ); + throw new HttpException(errorMessage, errorStatus); } return user; } - public async getUserById< - TEntityRelations extends EntityRelations, ExtractTablesWithRelations['users']> = {}, - >( + public async getUserById( id: UUIDv4, - config: { - entityRelations?: TEntityRelations + options: { errorMessageFn?: (id: UUIDv4) => string, errorStatus?: HttpStatus, } = {}, - ): Promise> { + ): Promise { + const { + errorMessageFn = (id) => `User (\`${id}\`) not found`, + errorStatus = HttpStatus.NOT_FOUND, + } = options; return this.getUser({ id }, { - entityRelations: config.entityRelations, - errorMessage: config.errorMessageFn?.(id) ?? `User (\`${id}\`) not found`, - errorStatus: config.errorStatus + errorMessage: errorMessageFn(id), + errorStatus, }); } public getUsersWithPagination( dto: GetUsersInputDTO, - paginationOptions: { page: number, limit: number }, - ): Promise<{ - items: Array, - itemCount: number; - itemsPerPage: number; - currentPage: number; - }> { - return this.usersService.findManyWithPagination(paginationOptions, { - where: (table) => like(table.name, `%${dto.nameLike}%`) - }) + paginationDTO: PaginationInputDTO, + ): Promise> { + return this.usersService.findManyWithPagination({ + paginationOptions: paginationDTO, + where: dto, + }); } public async checkIfUserExists( where: Partial<{ id: UUIDv4, name: string }> = {}, ): Promise { return this.usersService.exists({ - where: (table) => and( - // TODO: Maybe there is a better way to do that - ...(where.id ? [eq(table.id, where.id)] : []), - ...(where.name ? [eq(table.name, where.name)] : []), - ) + where }); } public async createUser( dto: CreateUserInputDTO, tx?: Transaction, - ): Promise { + ) { return this.usersService.createOne(dto, tx); } - public async updateUser( - user: UserEntity, - dto: UpdateUserInputDTO, - tx?: Transaction, - ): Promise { - return this.usersService.updateOne(user, dto, tx); - } - public async replenishUserBalance( user: UserEntity, amount: number, tx?: Transaction, ): Promise { - return this.updateUser(user, { balance: user.balance + amount }, tx); + return this.usersService.updateOne(user, { balance: user.balance + amount }, tx); } public async spendUserBalance( @@ -114,6 +90,6 @@ export class UsersUseCase { amount: number, tx?: Transaction, ): Promise { - return this.updateUser(user, { balance: user.balance - amount }, tx); + return this.usersService.updateOne(user, { balance: user.balance - amount }, tx); } } diff --git a/src/infra/config/drizzle/index.ts b/src/infra/config/drizzle/index.ts new file mode 100644 index 0000000..21cf8bd --- /dev/null +++ b/src/infra/config/drizzle/index.ts @@ -0,0 +1,21 @@ +import 'dotenv/config'; +import { defineConfig } from 'drizzle-kit'; + +const { + POSTGRES_USER, + POSTGRES_PASSWORD, + POSTGRES_HOST, + POSTGRES_DB, + POSTGRES_PORT, +} = process.env; + +export default defineConfig({ + schema: './src/infra/postgres/tables/', + out: './src/infra/postgres/migrations/', + driver: 'pg', + dbCredentials: { + connectionString: `postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@${POSTGRES_HOST}:${POSTGRES_PORT}/${POSTGRES_DB}`, + }, + verbose: true, + strict: true, +}); diff --git a/src/infra/config/typeorm/index.ts b/src/infra/config/typeorm/index.ts deleted file mode 100644 index 94a399e..0000000 --- a/src/infra/config/typeorm/index.ts +++ /dev/null @@ -1,55 +0,0 @@ -import { DataSource, DataSourceOptions } from 'typeorm'; -import { SnakeNamingStrategy } from 'typeorm-naming-strategies'; -import { EnvVariables } from '../validation'; -import { Injectable } from '@nestjs/common'; -import { TypeOrmModuleOptions, TypeOrmOptionsFactory } from '@nestjs/typeorm'; -import { ConfigService } from '@nestjs/config'; -import * as dotenv from 'dotenv'; - -function getOrThrowFromEnv( - envVariableName: T, - configService?: ConfigService, -): EnvVariables[T] { - if (configService) { - return configService.getOrThrow(envVariableName) - } - - const envVariable = process.env[envVariableName] as EnvVariables[T] | undefined; - if (!envVariable) { - throw new TypeError(`Configuration key "${envVariableName}" does not exist`); - } - return envVariable; -} - -const getDataSourceOptions = (configService?: ConfigService): DataSourceOptions => { - return { - type: 'postgres', - host: getOrThrowFromEnv('POSTGRES_HOST', configService), - port: getOrThrowFromEnv('POSTGRES_PORT', configService), - username: getOrThrowFromEnv('POSTGRES_USER', configService), - password: getOrThrowFromEnv('POSTGRES_PASSWORD', configService), - database: getOrThrowFromEnv('POSTGRES_DB', configService), - - entities: ['dist/infra/postgres/entities/*.js'], - migrations: ['dist/infra/postgres/migrations/*.js'], - - synchronize: false, - namingStrategy: new SnakeNamingStrategy(), - }; -}; - -@Injectable() -export class TypeOrmConfigService implements TypeOrmOptionsFactory { - public constructor(private readonly configService: ConfigService) {} - - public createTypeOrmOptions(): TypeOrmModuleOptions { - return { - ...getDataSourceOptions(this.configService), - autoLoadEntities: true, - }; - } -} - -dotenv.config(); -export default new DataSource(getDataSourceOptions()); - diff --git a/src/infra/consts.ts b/src/infra/consts.ts new file mode 100644 index 0000000..849509e --- /dev/null +++ b/src/infra/consts.ts @@ -0,0 +1 @@ +export const DRIZZLE_DB_TAG = 'DRIZZLE_DB_TAG'; diff --git a/src/infra/decorators/inject-database.decorator.ts b/src/infra/decorators/inject-database.decorator.ts new file mode 100644 index 0000000..6906943 --- /dev/null +++ b/src/infra/decorators/inject-database.decorator.ts @@ -0,0 +1,4 @@ +import { Inject } from '@nestjs/common'; +import { DRIZZLE_DB_TAG } from '../consts'; + +export const InjectDatabase = () => Inject(DRIZZLE_DB_TAG); diff --git a/src/infra/ioc/packs.module.ts b/src/infra/ioc/packs.module.ts index 38a5684..8188c67 100644 --- a/src/infra/ioc/packs.module.ts +++ b/src/infra/ioc/packs.module.ts @@ -4,20 +4,17 @@ import { PokemonsModule } from './pokemons.module'; import { PacksUseCase } from 'src/core/use-cases/packs.use-case'; import { UsersModule } from './users.module'; import { PacksService } from 'src/core/services/packs.service'; -import { PackEntity } from '../postgres/entities/pack.entity'; -import { OpenedPacksUseCase } from 'src/core/use-cases/opened-packs.use-case'; import { OpenedPacksService } from 'src/core/services/opened-packs.service'; -import { OpenedPackEntity } from '../postgres/entities/opened-pack.entity'; -import { UserInventoryEntriesModule } from './user-inventory-entries.module'; +import { UserItemsModule } from './user-items.module'; @Module({ imports: [ - PostgresModule.forFeature([PackEntity, OpenedPackEntity]), + PostgresModule, PokemonsModule, UsersModule, - UserInventoryEntriesModule, + UserItemsModule, ], - providers: [PacksUseCase, PacksService, OpenedPacksUseCase, OpenedPacksService], - exports: [PacksUseCase, PacksService, OpenedPacksUseCase, OpenedPacksService], + providers: [PacksUseCase, PacksService, OpenedPacksService], + exports: [PacksUseCase, PacksService, OpenedPacksService], }) export class PacksModule {} diff --git a/src/infra/ioc/pokemons.module.ts b/src/infra/ioc/pokemons.module.ts index 51195ab..bfb87ce 100644 --- a/src/infra/ioc/pokemons.module.ts +++ b/src/infra/ioc/pokemons.module.ts @@ -1,11 +1,10 @@ import { Module } from '@nestjs/common'; import { PostgresModule } from '../postgres/postgres.module'; -import { PokemonEntity } from '../postgres/entities/pokemon.entity'; import { PokemonsUseCase } from 'src/core/use-cases/pokemons.use-case'; import { PokemonsService } from 'src/core/services/pokemons.service'; @Module({ - imports: [PostgresModule.forFeature([PokemonEntity])], + imports: [PostgresModule], providers: [PokemonsUseCase, PokemonsService], exports: [PokemonsUseCase, PokemonsService], }) diff --git a/src/infra/ioc/trades.module.ts b/src/infra/ioc/trades.module.ts index aa23754..40cf4d2 100644 --- a/src/infra/ioc/trades.module.ts +++ b/src/infra/ioc/trades.module.ts @@ -1,49 +1,35 @@ import { Module } from '@nestjs/common'; import { TradesService } from 'src/core/services/trades.service'; import { TradesUseCase } from 'src/core/use-cases/trades.use-case'; -import { TradeEntity } from '../postgres/entities/trade.entity'; import { PostgresModule } from '../postgres/postgres.module'; import { UsersModule } from './users.module'; -import { UserInventoryEntriesModule } from './user-inventory-entries.module'; -import { PendingTradeEntity } from '../postgres/entities/pending-trade.entity'; -import { AcceptedTradeEntity } from '../postgres/entities/accepted-trade.entity'; +import { UserItemsModule } from './user-items.module'; import { PendingTradesUseCase } from 'src/core/use-cases/pending-trades.use-case'; import { PendingTradesService } from 'src/core/services/pending-trades.service'; -import { AcceptedTradesService } from 'src/core/services/accepted-trades.service'; -import { CancelledTradesService } from 'src/core/services/cancelled-trades.service'; -import { CancelledTradeEntity } from '../postgres/entities/cancelled-trade.entity'; -import { RejectedTradeEntity } from '../postgres/entities/rejected-trade.entity'; -import { RejectedTradesService } from 'src/core/services/rejected-trades.service'; +import { TradesToSenderItemsService } from 'src/core/services/trades-to-sender-items.service'; +import { TradesToReceiverItemsService } from 'src/core/services/trades-to-receiver-items.service'; @Module({ imports: [ - PostgresModule.forFeature([ - TradeEntity, - PendingTradeEntity, - AcceptedTradeEntity, - CancelledTradeEntity, - RejectedTradeEntity, - ]), + PostgresModule, UsersModule, - UserInventoryEntriesModule, + UserItemsModule, ], providers: [ TradesUseCase, TradesService, - PendingTradesUseCase, + TradesToSenderItemsService, + TradesToReceiverItemsService, PendingTradesService, - AcceptedTradesService, - CancelledTradesService, - RejectedTradesService, + PendingTradesUseCase, ], exports: [ TradesUseCase, TradesService, - PendingTradesUseCase, + TradesToSenderItemsService, + TradesToReceiverItemsService, PendingTradesService, - AcceptedTradesService, - CancelledTradesService, - RejectedTradesService, + PendingTradesUseCase, ], }) export class TradesModule {} diff --git a/src/infra/ioc/user-inventory-entries.module.ts b/src/infra/ioc/user-inventory-entries.module.ts deleted file mode 100644 index 11effb5..0000000 --- a/src/infra/ioc/user-inventory-entries.module.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { Module } from '@nestjs/common'; -import { QuickSoldUserInventoryEntriesService } from 'src/core/services/quick-sold-user-inventory-entries.service'; -import { UserInventoryEntriesService } from 'src/core/services/user-inventory-entries.service'; -import { UserInventoryEntriesUseCase } from 'src/core/use-cases/user-inventory-entries.use-case'; -import { QuickSoldUserInventoryEntryEntity } from '../postgres/entities/quick-sold-user-inventory-entry.entity'; -import { UserInventoryEntryEntity } from '../postgres/entities/user-inventory-entry.entity'; -import { PostgresModule } from '../postgres/postgres.module'; -import { UsersModule } from './users.module'; - -@Module({ - imports: [ - PostgresModule.forFeature([ - UserInventoryEntryEntity, - QuickSoldUserInventoryEntryEntity, - ]), - UsersModule, - ], - providers: [ - UserInventoryEntriesUseCase, - UserInventoryEntriesService, - QuickSoldUserInventoryEntriesService, - ], - exports: [ - UserInventoryEntriesUseCase, - UserInventoryEntriesService, - QuickSoldUserInventoryEntriesService, - ], -}) -export class UserInventoryEntriesModule {} diff --git a/src/infra/ioc/user-items.module.ts b/src/infra/ioc/user-items.module.ts new file mode 100644 index 0000000..4966474 --- /dev/null +++ b/src/infra/ioc/user-items.module.ts @@ -0,0 +1,24 @@ +import { Module } from '@nestjs/common'; +import { QuickSoldUserItemsService } from 'src/core/services/quick-sold-user-items.service'; +import { UserItemsService } from 'src/core/services/user-items.service'; +import { UserItemsUseCase } from 'src/core/use-cases/user-items.use-case'; +import { PostgresModule } from '../postgres/postgres.module'; +import { UsersModule } from './users.module'; + +@Module({ + imports: [ + PostgresModule, + UsersModule, + ], + providers: [ + UserItemsUseCase, + UserItemsService, + QuickSoldUserItemsService, + ], + exports: [ + UserItemsUseCase, + UserItemsService, + QuickSoldUserItemsService, + ], +}) +export class UserItemsModule {} diff --git a/src/infra/ioc/users.module.ts b/src/infra/ioc/users.module.ts index a56c702..9e11f6f 100644 --- a/src/infra/ioc/users.module.ts +++ b/src/infra/ioc/users.module.ts @@ -1,11 +1,10 @@ import { Module } from '@nestjs/common'; import { PostgresModule } from '../postgres/postgres.module'; -import { UserEntity } from '../postgres/entities/user.entity'; import { UsersService } from 'src/core/services/users.service'; import { UsersUseCase } from 'src/core/use-cases/users.use-case'; @Module({ - imports: [PostgresModule.forFeature([UserEntity])], + imports: [PostgresModule], providers: [UsersUseCase, UsersService], exports: [UsersUseCase, UsersService], }) diff --git a/src/infra/postgres/migrations/0000_initial.sql b/src/infra/postgres/migrations/0000_initial.sql new file mode 100644 index 0000000..9ae8dc5 --- /dev/null +++ b/src/infra/postgres/migrations/0000_initial.sql @@ -0,0 +1,184 @@ +DO $$ BEGIN + CREATE TYPE "trade_status" AS ENUM('PENDING', 'CANCELLED', 'ACCEPTED', 'REJECTED'); +EXCEPTION + WHEN duplicate_object THEN null; +END $$; +--> statement-breakpoint +CREATE TABLE IF NOT EXISTS "users" ( + "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, + "created_at" timestamp with time zone DEFAULT now() NOT NULL, + "updated_at" timestamp with time zone DEFAULT now() NOT NULL, + "name" text NOT NULL, + "hashed_password" text NOT NULL, + "balance" integer DEFAULT 0 NOT NULL, + CONSTRAINT "users_name_unique" UNIQUE("name") +); +--> statement-breakpoint +CREATE TABLE IF NOT EXISTS "pokemons" ( + "id" integer PRIMARY KEY NOT NULL, + "name" text NOT NULL, + "worth" integer NOT NULL, + "height" integer NOT NULL, + "weight" integer NOT NULL, + "image" text NOT NULL +); +--> statement-breakpoint +CREATE TABLE IF NOT EXISTS "packs" ( + "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, + "created_at" timestamp with time zone DEFAULT now() NOT NULL, + "updated_at" timestamp with time zone DEFAULT now() NOT NULL, + "name" text NOT NULL, + "description" text NOT NULL, + "price" integer NOT NULL, + "image" text NOT NULL +); +--> statement-breakpoint +CREATE TABLE IF NOT EXISTS "packs_to_pokemons" ( + "pack_id" uuid NOT NULL, + "pokemon_id" integer NOT NULL, + CONSTRAINT "packs_to_pokemons_pack_id_pokemon_id_pk" PRIMARY KEY("pack_id","pokemon_id") +); +--> statement-breakpoint +CREATE TABLE IF NOT EXISTS "opened_packs" ( + "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, + "opened_at" timestamp with time zone DEFAULT now() NOT NULL, + "user_id" uuid NOT NULL, + "pack_id" uuid NOT NULL, + "pokemon_id" integer NOT NULL +); +--> statement-breakpoint +CREATE TABLE IF NOT EXISTS "user_items" ( + "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, + "received_at" timestamp with time zone DEFAULT now() NOT NULL, + "user_id" uuid NOT NULL, + "pokemon_id" integer NOT NULL +); +--> statement-breakpoint +CREATE TABLE IF NOT EXISTS "quick_sold_user_items" ( + "id" uuid PRIMARY KEY NOT NULL, + "received_at" timestamp with time zone DEFAULT now() NOT NULL, + "user_id" uuid NOT NULL, + "pokemon_id" integer NOT NULL, + "sold_at" timestamp with time zone DEFAULT now() NOT NULL +); +--> statement-breakpoint +CREATE TABLE IF NOT EXISTS "trades" ( + "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, + "created_at" timestamp with time zone DEFAULT now() NOT NULL, + "updated_at" timestamp with time zone DEFAULT now() NOT NULL, + "status" "trade_status" NOT NULL, + "sender_id" uuid NOT NULL, + "receiver_id" uuid NOT NULL, + "cancelled_at" timestamp with time zone, + "accepted_at" timestamp with time zone, + "rejected_at" timestamp with time zone +); +--> statement-breakpoint +CREATE TABLE IF NOT EXISTS "trades_to_sender_items" ( + "trade_id" uuid NOT NULL, + "sender_item_id" uuid NOT NULL, + CONSTRAINT "trades_to_sender_items_trade_id_sender_item_id_pk" PRIMARY KEY("trade_id","sender_item_id") +); +--> statement-breakpoint +CREATE TABLE IF NOT EXISTS "trades_to_receiver_items" ( + "trade_id" uuid NOT NULL, + "receiver_item_id" uuid NOT NULL, + CONSTRAINT "trades_to_receiver_items_trade_id_receiver_item_id_pk" PRIMARY KEY("trade_id","receiver_item_id") +); +--> statement-breakpoint +CREATE INDEX IF NOT EXISTS "packs_to_pokemons_pack_id_index" ON "packs_to_pokemons" ("pack_id");--> statement-breakpoint +CREATE INDEX IF NOT EXISTS "packs_to_pokemons_pokemon_id_index" ON "packs_to_pokemons" ("pokemon_id");--> statement-breakpoint +CREATE INDEX IF NOT EXISTS "trades_status_index" ON "trades" ("status");--> statement-breakpoint +CREATE INDEX IF NOT EXISTS "trades_to_receiver_items_trade_id_index" ON "trades_to_receiver_items" ("trade_id");--> statement-breakpoint +CREATE INDEX IF NOT EXISTS "trades_to_receiver_items_receiver_item_id_index" ON "trades_to_receiver_items" ("receiver_item_id");--> statement-breakpoint +CREATE INDEX IF NOT EXISTS "trades_to_sender_items_trade_id_index" ON "trades_to_sender_items" ("trade_id");--> statement-breakpoint +CREATE INDEX IF NOT EXISTS "trades_to_sender_items_sender_item_id_index" ON "trades_to_sender_items" ("sender_item_id");--> statement-breakpoint +DO $$ BEGIN + ALTER TABLE "packs_to_pokemons" ADD CONSTRAINT "packs_to_pokemons_pack_id_packs_id_fk" FOREIGN KEY ("pack_id") REFERENCES "packs"("id") ON DELETE cascade ON UPDATE cascade; +EXCEPTION + WHEN duplicate_object THEN null; +END $$; +--> statement-breakpoint +DO $$ BEGIN + ALTER TABLE "packs_to_pokemons" ADD CONSTRAINT "packs_to_pokemons_pokemon_id_pokemons_id_fk" FOREIGN KEY ("pokemon_id") REFERENCES "pokemons"("id") ON DELETE cascade ON UPDATE cascade; +EXCEPTION + WHEN duplicate_object THEN null; +END $$; +--> statement-breakpoint +DO $$ BEGIN + ALTER TABLE "opened_packs" ADD CONSTRAINT "opened_packs_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE cascade ON UPDATE cascade; +EXCEPTION + WHEN duplicate_object THEN null; +END $$; +--> statement-breakpoint +DO $$ BEGIN + ALTER TABLE "opened_packs" ADD CONSTRAINT "opened_packs_pack_id_packs_id_fk" FOREIGN KEY ("pack_id") REFERENCES "packs"("id") ON DELETE cascade ON UPDATE cascade; +EXCEPTION + WHEN duplicate_object THEN null; +END $$; +--> statement-breakpoint +DO $$ BEGIN + ALTER TABLE "opened_packs" ADD CONSTRAINT "opened_packs_pokemon_id_pokemons_id_fk" FOREIGN KEY ("pokemon_id") REFERENCES "pokemons"("id") ON DELETE cascade ON UPDATE cascade; +EXCEPTION + WHEN duplicate_object THEN null; +END $$; +--> statement-breakpoint +DO $$ BEGIN + ALTER TABLE "user_items" ADD CONSTRAINT "user_items_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE cascade ON UPDATE cascade; +EXCEPTION + WHEN duplicate_object THEN null; +END $$; +--> statement-breakpoint +DO $$ BEGIN + ALTER TABLE "user_items" ADD CONSTRAINT "user_items_pokemon_id_pokemons_id_fk" FOREIGN KEY ("pokemon_id") REFERENCES "pokemons"("id") ON DELETE cascade ON UPDATE cascade; +EXCEPTION + WHEN duplicate_object THEN null; +END $$; +--> statement-breakpoint +DO $$ BEGIN + ALTER TABLE "quick_sold_user_items" ADD CONSTRAINT "quick_sold_user_items_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE cascade ON UPDATE cascade; +EXCEPTION + WHEN duplicate_object THEN null; +END $$; +--> statement-breakpoint +DO $$ BEGIN + ALTER TABLE "quick_sold_user_items" ADD CONSTRAINT "quick_sold_user_items_pokemon_id_pokemons_id_fk" FOREIGN KEY ("pokemon_id") REFERENCES "pokemons"("id") ON DELETE cascade ON UPDATE cascade; +EXCEPTION + WHEN duplicate_object THEN null; +END $$; +--> statement-breakpoint +DO $$ BEGIN + ALTER TABLE "trades" ADD CONSTRAINT "trades_sender_id_users_id_fk" FOREIGN KEY ("sender_id") REFERENCES "users"("id") ON DELETE cascade ON UPDATE cascade; +EXCEPTION + WHEN duplicate_object THEN null; +END $$; +--> statement-breakpoint +DO $$ BEGIN + ALTER TABLE "trades" ADD CONSTRAINT "trades_receiver_id_users_id_fk" FOREIGN KEY ("receiver_id") REFERENCES "users"("id") ON DELETE cascade ON UPDATE cascade; +EXCEPTION + WHEN duplicate_object THEN null; +END $$; +--> statement-breakpoint +DO $$ BEGIN + ALTER TABLE "trades_to_sender_items" ADD CONSTRAINT "trades_to_sender_items_trade_id_trades_id_fk" FOREIGN KEY ("trade_id") REFERENCES "trades"("id") ON DELETE cascade ON UPDATE cascade; +EXCEPTION + WHEN duplicate_object THEN null; +END $$; +--> statement-breakpoint +DO $$ BEGIN + ALTER TABLE "trades_to_sender_items" ADD CONSTRAINT "trades_to_sender_items_sender_item_id_trades_id_fk" FOREIGN KEY ("sender_item_id") REFERENCES "trades"("id") ON DELETE cascade ON UPDATE cascade; +EXCEPTION + WHEN duplicate_object THEN null; +END $$; +--> statement-breakpoint +DO $$ BEGIN + ALTER TABLE "trades_to_receiver_items" ADD CONSTRAINT "trades_to_receiver_items_trade_id_trades_id_fk" FOREIGN KEY ("trade_id") REFERENCES "trades"("id") ON DELETE cascade ON UPDATE cascade; +EXCEPTION + WHEN duplicate_object THEN null; +END $$; +--> statement-breakpoint +DO $$ BEGIN + ALTER TABLE "trades_to_receiver_items" ADD CONSTRAINT "trades_to_receiver_items_receiver_item_id_trades_id_fk" FOREIGN KEY ("receiver_item_id") REFERENCES "trades"("id") ON DELETE cascade ON UPDATE cascade; +EXCEPTION + WHEN duplicate_object THEN null; +END $$; diff --git a/src/infra/postgres/migrations/1703580255818-initial.ts b/src/infra/postgres/migrations/1703580255818-initial.ts deleted file mode 100644 index 1ab6d5e..0000000 --- a/src/infra/postgres/migrations/1703580255818-initial.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { MigrationInterface, QueryRunner } from "typeorm"; - -export class Initial1703580255818 implements MigrationInterface { - name = 'Initial1703580255818' - - public async up(queryRunner: QueryRunner): Promise { - await queryRunner.query(`CREATE TABLE "users" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "created_at" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), "updated_at" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), "name" text NOT NULL, "hashed_password" text NOT NULL, "balance" integer NOT NULL DEFAULT '0', CONSTRAINT "UQ_51b8b26ac168fbe7d6f5653e6cf" UNIQUE ("name"), CONSTRAINT "PK_a3ffb1c0c8416b9fc6f907b7433" PRIMARY KEY ("id"))`); - await queryRunner.query(`CREATE TABLE "pokemons" ("id" integer NOT NULL, "name" text NOT NULL, "worth" integer NOT NULL, "height" integer NOT NULL, "weight" integer NOT NULL, "image" text NOT NULL, CONSTRAINT "PK_a3172290413af616d9cfa1fdc9a" PRIMARY KEY ("id"))`); - await queryRunner.query(`CREATE TABLE "user_inventory_entries" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "received_at" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), "user_id" uuid NOT NULL, "pokemon_id" integer NOT NULL, CONSTRAINT "PK_a8e698516486cf9673c2aef3bfa" PRIMARY KEY ("id"))`); - await queryRunner.query(`CREATE TABLE "packs" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "created_at" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), "updated_at" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), "name" text NOT NULL, "description" text NOT NULL, "price" integer NOT NULL, "image" text NOT NULL, CONSTRAINT "PK_da3c6e998aaa9331767c51e7f91" PRIMARY KEY ("id"))`); - await queryRunner.query(`CREATE TABLE "pack_pokemons" ("pack_id" uuid NOT NULL, "pokemon_id" integer NOT NULL, CONSTRAINT "PK_c416e0899115c396297f195dffa" PRIMARY KEY ("pack_id", "pokemon_id"))`); - await queryRunner.query(`CREATE TABLE "opened_packs" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "opened_at" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), "user_id" uuid NOT NULL, "pack_id" uuid NOT NULL, "pokemon_id" integer NOT NULL, CONSTRAINT "PK_97f5101e85f0c26cfb0fe95f5d1" PRIMARY KEY ("id"))`); - - await queryRunner.query(`CREATE INDEX "IDX_58a812ace3cc61f45cf7767069" ON "pack_pokemons" ("pack_id") `); - await queryRunner.query(`CREATE INDEX "IDX_663de8bf5f58ec8e3b7abbc201" ON "pack_pokemons" ("pokemon_id") `); - - await queryRunner.query(`ALTER TABLE "user_inventory_entries" ADD CONSTRAINT "FK_8b71afa929fa04837f30c2d9d81" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE`); - await queryRunner.query(`ALTER TABLE "user_inventory_entries" ADD CONSTRAINT "FK_4b87087a201f40a20cc3d0798de" FOREIGN KEY ("pokemon_id") REFERENCES "pokemons"("id") ON DELETE CASCADE ON UPDATE CASCADE`); - await queryRunner.query(`ALTER TABLE "pack_pokemons" ADD CONSTRAINT "FK_58a812ace3cc61f45cf77670695" FOREIGN KEY ("pack_id") REFERENCES "packs"("id") ON DELETE CASCADE ON UPDATE CASCADE`); - await queryRunner.query(`ALTER TABLE "pack_pokemons" ADD CONSTRAINT "FK_663de8bf5f58ec8e3b7abbc2019" FOREIGN KEY ("pokemon_id") REFERENCES "pokemons"("id") ON DELETE CASCADE ON UPDATE CASCADE`); - await queryRunner.query(`ALTER TABLE "opened_packs" ADD CONSTRAINT "FK_ee98df0266a5a13c3d4207db309" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE`); - await queryRunner.query(`ALTER TABLE "opened_packs" ADD CONSTRAINT "FK_c6507fc72215afb5fab7791f00e" FOREIGN KEY ("pack_id") REFERENCES "packs"("id") ON DELETE CASCADE ON UPDATE CASCADE`); - await queryRunner.query(`ALTER TABLE "opened_packs" ADD CONSTRAINT "FK_70fc301a6647b4eea93eddf4612" FOREIGN KEY ("pokemon_id") REFERENCES "pokemons"("id") ON DELETE CASCADE ON UPDATE CASCADE`); - } - - public async down(queryRunner: QueryRunner): Promise { - await queryRunner.query(`ALTER TABLE "opened_packs" DROP CONSTRAINT "FK_70fc301a6647b4eea93eddf4612"`); - await queryRunner.query(`ALTER TABLE "opened_packs" DROP CONSTRAINT "FK_c6507fc72215afb5fab7791f00e"`); - await queryRunner.query(`ALTER TABLE "opened_packs" DROP CONSTRAINT "FK_ee98df0266a5a13c3d4207db309"`); - await queryRunner.query(`ALTER TABLE "pack_pokemons" DROP CONSTRAINT "FK_663de8bf5f58ec8e3b7abbc2019"`); - await queryRunner.query(`ALTER TABLE "pack_pokemons" DROP CONSTRAINT "FK_58a812ace3cc61f45cf77670695"`); - await queryRunner.query(`ALTER TABLE "user_inventory_entries" DROP CONSTRAINT "FK_4b87087a201f40a20cc3d0798de"`); - await queryRunner.query(`ALTER TABLE "user_inventory_entries" DROP CONSTRAINT "FK_8b71afa929fa04837f30c2d9d81"`); - - await queryRunner.query(`DROP INDEX "public"."IDX_663de8bf5f58ec8e3b7abbc201"`); - await queryRunner.query(`DROP INDEX "public"."IDX_58a812ace3cc61f45cf7767069"`); - - await queryRunner.query(`DROP TABLE "opened_packs"`); - await queryRunner.query(`DROP TABLE "pack_pokemons"`); - await queryRunner.query(`DROP TABLE "packs"`); - await queryRunner.query(`DROP TABLE "user_inventory_entries"`); - await queryRunner.query(`DROP TABLE "pokemons"`); - await queryRunner.query(`DROP TABLE "users"`); - } - -} diff --git a/src/infra/postgres/migrations/1704640865163-quick-sold-user-inventory-entries.ts b/src/infra/postgres/migrations/1704640865163-quick-sold-user-inventory-entries.ts deleted file mode 100644 index 6b40f5c..0000000 --- a/src/infra/postgres/migrations/1704640865163-quick-sold-user-inventory-entries.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { MigrationInterface, QueryRunner } from "typeorm"; - -export class QuickSoldUserInventoryEntries1704640865163 implements MigrationInterface { - name = 'QuickSoldUserInventoryEntries1704640865163' - - public async up(queryRunner: QueryRunner): Promise { - await queryRunner.query(`CREATE TABLE "quick_sold_user_inventory_entries" ("id" uuid NOT NULL, "received_at" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), "sold_at" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), "user_id" uuid NOT NULL, "pokemon_id" integer NOT NULL, CONSTRAINT "PK_8a445c7163a9ce39321a593c73c" PRIMARY KEY ("id"))`); - await queryRunner.query(`ALTER TABLE "quick_sold_user_inventory_entries" ADD CONSTRAINT "FK_5824f936a8f191d4a9eda96160d" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE`); - await queryRunner.query(`ALTER TABLE "quick_sold_user_inventory_entries" ADD CONSTRAINT "FK_182f7a7f9017682aeb6e48895b7" FOREIGN KEY ("pokemon_id") REFERENCES "pokemons"("id") ON DELETE CASCADE ON UPDATE CASCADE`); - } - - public async down(queryRunner: QueryRunner): Promise { - await queryRunner.query(`ALTER TABLE "quick_sold_user_inventory_entries" DROP CONSTRAINT "FK_182f7a7f9017682aeb6e48895b7"`); - await queryRunner.query(`ALTER TABLE "quick_sold_user_inventory_entries" DROP CONSTRAINT "FK_5824f936a8f191d4a9eda96160d"`); - await queryRunner.query(`DROP TABLE "quick_sold_user_inventory_entries"`); - } - -} diff --git a/src/infra/postgres/migrations/1704836181740-trades.ts b/src/infra/postgres/migrations/1704836181740-trades.ts deleted file mode 100644 index 4dfc7b9..0000000 --- a/src/infra/postgres/migrations/1704836181740-trades.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { MigrationInterface, QueryRunner } from "typeorm"; - -export class Trades1704836181740 implements MigrationInterface { - name = 'Trades1704836181740' - - public async up(queryRunner: QueryRunner): Promise { - await queryRunner.query(`CREATE TYPE "public"."trades_status_enum" AS ENUM('PENDING', 'CANCELLED', 'ACCEPTED', 'REJECTED')`); - await queryRunner.query(`CREATE TABLE "trades" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "created_at" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), "updated_at" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), "status" "public"."trades_status_enum" NOT NULL, "accepted_at" TIMESTAMP WITH TIME ZONE, "cancelled_at" TIMESTAMP WITH TIME ZONE, "rejected_at" TIMESTAMP WITH TIME ZONE, "sender_id" uuid NOT NULL, "receiver_id" uuid NOT NULL, CONSTRAINT "PK_c6d7c36a837411ba5194dc58595" PRIMARY KEY ("id"))`); - await queryRunner.query(`CREATE INDEX "IDX_358b36d15b38834d8879b74fd0" ON "trades" ("status") `); - await queryRunner.query(`ALTER TABLE "trades" ADD CONSTRAINT "FK_490f4df080a3862cdb4236505a9" FOREIGN KEY ("sender_id") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE`); - await queryRunner.query(`ALTER TABLE "trades" ADD CONSTRAINT "FK_d5bd2471ea5175f4bfdb0df0cde" FOREIGN KEY ("receiver_id") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE`); - - await queryRunner.query(`CREATE TABLE "trade_sender_inventory_entries" ("trade_id" uuid NOT NULL, "user_inventory_entry_id" uuid NOT NULL, CONSTRAINT "PK_15ec1aa76702e2d6035d9ec8dc1" PRIMARY KEY ("trade_id", "user_inventory_entry_id"))`); - await queryRunner.query(`CREATE INDEX "IDX_366016177a6c2b1cbce45c4bed" ON "trade_sender_inventory_entries" ("trade_id") `); - await queryRunner.query(`CREATE INDEX "IDX_e0a105937dbd3783f206aa8a37" ON "trade_sender_inventory_entries" ("user_inventory_entry_id") `); - await queryRunner.query(`ALTER TABLE "trade_sender_inventory_entries" ADD CONSTRAINT "FK_366016177a6c2b1cbce45c4bedd" FOREIGN KEY ("trade_id") REFERENCES "trades"("id") ON DELETE CASCADE ON UPDATE CASCADE`); - await queryRunner.query(`ALTER TABLE "trade_sender_inventory_entries" ADD CONSTRAINT "FK_e0a105937dbd3783f206aa8a37a" FOREIGN KEY ("user_inventory_entry_id") REFERENCES "user_inventory_entries"("id") ON DELETE CASCADE ON UPDATE CASCADE`); - - await queryRunner.query(`CREATE TABLE "trade_receiver_inventory_entries" ("trade_id" uuid NOT NULL, "user_inventory_entry_id" uuid NOT NULL, CONSTRAINT "PK_39eda8b0cd21e2b787317c132a1" PRIMARY KEY ("trade_id", "user_inventory_entry_id"))`); - await queryRunner.query(`CREATE INDEX "IDX_74729de60b6f469b7deda99d42" ON "trade_receiver_inventory_entries" ("trade_id") `); - await queryRunner.query(`CREATE INDEX "IDX_3882b6f9ded8eb8ea156a695e4" ON "trade_receiver_inventory_entries" ("user_inventory_entry_id") `); - await queryRunner.query(`ALTER TABLE "trade_receiver_inventory_entries" ADD CONSTRAINT "FK_74729de60b6f469b7deda99d42c" FOREIGN KEY ("trade_id") REFERENCES "trades"("id") ON DELETE CASCADE ON UPDATE CASCADE`); - await queryRunner.query(`ALTER TABLE "trade_receiver_inventory_entries" ADD CONSTRAINT "FK_3882b6f9ded8eb8ea156a695e43" FOREIGN KEY ("user_inventory_entry_id") REFERENCES "user_inventory_entries"("id") ON DELETE CASCADE ON UPDATE CASCADE`); - } - - public async down(queryRunner: QueryRunner): Promise { - await queryRunner.query(`ALTER TABLE "trade_receiver_inventory_entries" DROP CONSTRAINT "FK_3882b6f9ded8eb8ea156a695e43"`); - await queryRunner.query(`ALTER TABLE "trade_receiver_inventory_entries" DROP CONSTRAINT "FK_74729de60b6f469b7deda99d42c"`); - await queryRunner.query(`DROP INDEX "public"."IDX_3882b6f9ded8eb8ea156a695e4"`); - await queryRunner.query(`DROP INDEX "public"."IDX_74729de60b6f469b7deda99d42"`); - await queryRunner.query(`DROP TABLE "trade_receiver_inventory_entries"`); - - await queryRunner.query(`ALTER TABLE "trade_sender_inventory_entries" DROP CONSTRAINT "FK_e0a105937dbd3783f206aa8a37a"`); - await queryRunner.query(`ALTER TABLE "trade_sender_inventory_entries" DROP CONSTRAINT "FK_366016177a6c2b1cbce45c4bedd"`); - await queryRunner.query(`DROP INDEX "public"."IDX_e0a105937dbd3783f206aa8a37"`); - await queryRunner.query(`DROP INDEX "public"."IDX_366016177a6c2b1cbce45c4bed"`); - await queryRunner.query(`DROP TABLE "trade_sender_inventory_entries"`); - - await queryRunner.query(`ALTER TABLE "trades" DROP CONSTRAINT "FK_d5bd2471ea5175f4bfdb0df0cde"`); - await queryRunner.query(`ALTER TABLE "trades" DROP CONSTRAINT "FK_490f4df080a3862cdb4236505a9"`); - await queryRunner.query(`DROP INDEX "public"."IDX_358b36d15b38834d8879b74fd0"`); - await queryRunner.query(`DROP TABLE "trades"`); - await queryRunner.query(`DROP TYPE "public"."trades_status_enum"`); - } - -} diff --git a/src/infra/postgres/migrations/meta/0000_snapshot.json b/src/infra/postgres/migrations/meta/0000_snapshot.json new file mode 100644 index 0000000..1425cc3 --- /dev/null +++ b/src/infra/postgres/migrations/meta/0000_snapshot.json @@ -0,0 +1,716 @@ +{ + "id": "8abedc24-858d-4c0f-a25a-215fb7b1027d", + "prevId": "00000000-0000-0000-0000-000000000000", + "version": "5", + "dialect": "pg", + "tables": { + "users": { + "name": "users", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "hashed_password": { + "name": "hashed_password", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "balance": { + "name": "balance", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "users_name_unique": { + "name": "users_name_unique", + "nullsNotDistinct": false, + "columns": [ + "name" + ] + } + } + }, + "pokemons": { + "name": "pokemons", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "worth": { + "name": "worth", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "height": { + "name": "height", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "weight": { + "name": "weight", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "image": { + "name": "image", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "packs": { + "name": "packs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "price": { + "name": "price", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "image": { + "name": "image", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "packs_to_pokemons": { + "name": "packs_to_pokemons", + "schema": "", + "columns": { + "pack_id": { + "name": "pack_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "pokemon_id": { + "name": "pokemon_id", + "type": "integer", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "packs_to_pokemons_pack_id_index": { + "name": "packs_to_pokemons_pack_id_index", + "columns": [ + "pack_id" + ], + "isUnique": false + }, + "packs_to_pokemons_pokemon_id_index": { + "name": "packs_to_pokemons_pokemon_id_index", + "columns": [ + "pokemon_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "packs_to_pokemons_pack_id_packs_id_fk": { + "name": "packs_to_pokemons_pack_id_packs_id_fk", + "tableFrom": "packs_to_pokemons", + "tableTo": "packs", + "columnsFrom": [ + "pack_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + }, + "packs_to_pokemons_pokemon_id_pokemons_id_fk": { + "name": "packs_to_pokemons_pokemon_id_pokemons_id_fk", + "tableFrom": "packs_to_pokemons", + "tableTo": "pokemons", + "columnsFrom": [ + "pokemon_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": { + "packs_to_pokemons_pack_id_pokemon_id_pk": { + "name": "packs_to_pokemons_pack_id_pokemon_id_pk", + "columns": [ + "pack_id", + "pokemon_id" + ] + } + }, + "uniqueConstraints": {} + }, + "opened_packs": { + "name": "opened_packs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "opened_at": { + "name": "opened_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "pack_id": { + "name": "pack_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "pokemon_id": { + "name": "pokemon_id", + "type": "integer", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "opened_packs_user_id_users_id_fk": { + "name": "opened_packs_user_id_users_id_fk", + "tableFrom": "opened_packs", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + }, + "opened_packs_pack_id_packs_id_fk": { + "name": "opened_packs_pack_id_packs_id_fk", + "tableFrom": "opened_packs", + "tableTo": "packs", + "columnsFrom": [ + "pack_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + }, + "opened_packs_pokemon_id_pokemons_id_fk": { + "name": "opened_packs_pokemon_id_pokemons_id_fk", + "tableFrom": "opened_packs", + "tableTo": "pokemons", + "columnsFrom": [ + "pokemon_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "user_items": { + "name": "user_items", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "received_at": { + "name": "received_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "pokemon_id": { + "name": "pokemon_id", + "type": "integer", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "user_items_user_id_users_id_fk": { + "name": "user_items_user_id_users_id_fk", + "tableFrom": "user_items", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + }, + "user_items_pokemon_id_pokemons_id_fk": { + "name": "user_items_pokemon_id_pokemons_id_fk", + "tableFrom": "user_items", + "tableTo": "pokemons", + "columnsFrom": [ + "pokemon_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "quick_sold_user_items": { + "name": "quick_sold_user_items", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "received_at": { + "name": "received_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "pokemon_id": { + "name": "pokemon_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "sold_at": { + "name": "sold_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "quick_sold_user_items_user_id_users_id_fk": { + "name": "quick_sold_user_items_user_id_users_id_fk", + "tableFrom": "quick_sold_user_items", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + }, + "quick_sold_user_items_pokemon_id_pokemons_id_fk": { + "name": "quick_sold_user_items_pokemon_id_pokemons_id_fk", + "tableFrom": "quick_sold_user_items", + "tableTo": "pokemons", + "columnsFrom": [ + "pokemon_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "trades": { + "name": "trades", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "status": { + "name": "status", + "type": "trade_status", + "primaryKey": false, + "notNull": true + }, + "sender_id": { + "name": "sender_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "receiver_id": { + "name": "receiver_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "cancelled_at": { + "name": "cancelled_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "accepted_at": { + "name": "accepted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "rejected_at": { + "name": "rejected_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "trades_status_index": { + "name": "trades_status_index", + "columns": [ + "status" + ], + "isUnique": false + } + }, + "foreignKeys": { + "trades_sender_id_users_id_fk": { + "name": "trades_sender_id_users_id_fk", + "tableFrom": "trades", + "tableTo": "users", + "columnsFrom": [ + "sender_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + }, + "trades_receiver_id_users_id_fk": { + "name": "trades_receiver_id_users_id_fk", + "tableFrom": "trades", + "tableTo": "users", + "columnsFrom": [ + "receiver_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "trades_to_sender_items": { + "name": "trades_to_sender_items", + "schema": "", + "columns": { + "trade_id": { + "name": "trade_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "sender_item_id": { + "name": "sender_item_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "trades_to_sender_items_trade_id_index": { + "name": "trades_to_sender_items_trade_id_index", + "columns": [ + "trade_id" + ], + "isUnique": false + }, + "trades_to_sender_items_sender_item_id_index": { + "name": "trades_to_sender_items_sender_item_id_index", + "columns": [ + "sender_item_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "trades_to_sender_items_trade_id_trades_id_fk": { + "name": "trades_to_sender_items_trade_id_trades_id_fk", + "tableFrom": "trades_to_sender_items", + "tableTo": "trades", + "columnsFrom": [ + "trade_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + }, + "trades_to_sender_items_sender_item_id_user_items_id_fk": { + "name": "trades_to_sender_items_sender_item_id_user_items_id_fk", + "tableFrom": "trades_to_sender_items", + "tableTo": "trades", + "columnsFrom": [ + "sender_item_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": { + "trades_to_sender_items_trade_id_sender_item_id_pk": { + "name": "trades_to_sender_items_trade_id_sender_item_id_pk", + "columns": [ + "trade_id", + "sender_item_id" + ] + } + }, + "uniqueConstraints": {} + }, + "trades_to_receiver_items": { + "name": "trades_to_receiver_items", + "schema": "", + "columns": { + "trade_id": { + "name": "trade_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "receiver_item_id": { + "name": "receiver_item_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "trades_to_receiver_items_trade_id_index": { + "name": "trades_to_receiver_items_trade_id_index", + "columns": [ + "trade_id" + ], + "isUnique": false + }, + "trades_to_receiver_items_receiver_item_id_index": { + "name": "trades_to_receiver_items_receiver_item_id_index", + "columns": [ + "receiver_item_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "trades_to_receiver_items_trade_id_trades_id_fk": { + "name": "trades_to_receiver_items_trade_id_trades_id_fk", + "tableFrom": "trades_to_receiver_items", + "tableTo": "trades", + "columnsFrom": [ + "trade_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + }, + "trades_to_receiver_items_receiver_item_id_user_items_id_fk": { + "name": "trades_to_receiver_items_receiver_item_id_user_items_id_fk", + "tableFrom": "trades_to_receiver_items", + "tableTo": "trades", + "columnsFrom": [ + "receiver_item_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": { + "trades_to_receiver_items_trade_id_receiver_item_id_pk": { + "name": "trades_to_receiver_items_trade_id_receiver_item_id_pk", + "columns": [ + "trade_id", + "receiver_item_id" + ] + } + }, + "uniqueConstraints": {} + } + }, + "enums": { + "trade_status": { + "name": "trade_status", + "values": { + "PENDING": "PENDING", + "CANCELLED": "CANCELLED", + "ACCEPTED": "ACCEPTED", + "REJECTED": "REJECTED" + } + } + }, + "schemas": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} diff --git a/src/infra/postgres/migrations/meta/_journal.json b/src/infra/postgres/migrations/meta/_journal.json new file mode 100644 index 0000000..39fb82c --- /dev/null +++ b/src/infra/postgres/migrations/meta/_journal.json @@ -0,0 +1,13 @@ +{ + "version": "5", + "dialect": "pg", + "entries": [ + { + "idx": 0, + "version": "5", + "when": 1705696541687, + "tag": "0000_initial", + "breakpoints": true + } + ] +} diff --git a/src/infra/postgres/migrations/run.ts b/src/infra/postgres/migrations/run.ts new file mode 100644 index 0000000..9d0bee8 --- /dev/null +++ b/src/infra/postgres/migrations/run.ts @@ -0,0 +1,26 @@ +import 'dotenv/config'; +import { drizzle } from 'drizzle-orm/node-postgres'; +import { migrate } from 'drizzle-orm/node-postgres/migrator'; +import { Client } from 'pg'; +import * as tables from 'src/infra/postgres/tables/'; + +const { + POSTGRES_USER, + POSTGRES_PASSWORD, + POSTGRES_HOST, + POSTGRES_DB, + POSTGRES_PORT, +} = process.env; + +(async () => { + const client = new Client({ + connectionString: `postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@${POSTGRES_HOST}:${POSTGRES_PORT}/${POSTGRES_DB}`, + }); + + await client.connect(); + const db = drizzle(client, { schema: tables }); + + await migrate(db, { migrationsFolder: './src/infra/postgres/migrations/' }) + + await client.end(); +})(); diff --git a/src/infra/postgres/other/types.ts b/src/infra/postgres/other/types.ts index d82090a..a4af0a2 100644 --- a/src/infra/postgres/other/types.ts +++ b/src/infra/postgres/other/types.ts @@ -1,141 +1,7 @@ -import { Nullable } from "src/common/types"; -import { ExtractTablesWithRelations, FindTableByDBName, One, TableRelationalConfig, TablesRelationalConfig } from "drizzle-orm"; -import { RemovePropertiesWithNever } from "src/common/types"; +import { ExtractTablesWithRelations } from "drizzle-orm"; import * as tables from '../tables'; import { PgTransaction } from "drizzle-orm/pg-core"; -import { NodePgQueryResultHKT } from "drizzle-orm/node-postgres"; - -export type EntityRelations< - TSchema extends TablesRelationalConfig, - TTableConfig extends TableRelationalConfig, -> = { - [K in keyof TTableConfig['relations']]?: - | true - | EntityRelations< - TSchema, - FindTableByDBName - >; -}; - -export type Entity< - TTableName extends keyof RemovePropertiesWithNever<{ - [K in keyof ExtractTablesWithRelations]: - 'id' extends keyof ExtractTablesWithRelations[K]['columns'] - ? K - : never - }>, - TEntityRelations extends EntityRelations, ExtractTablesWithRelations[TTableName]> = {}, -> = typeof tables[TTableName]['$inferSelect'] & { - [K in keyof TEntityRelations]: - // @ts-ignore - ExtractTablesWithRelations[TTableName]['relations'][K] extends One - // TODO: drizzle query with `with` property does leftJoin that's why the field is null - // This is a annoying because i have all of my one-to-one many-to-one relations with not null constraint - // Find a way to make this less inconvenient - ? Nullable, - ExtractTablesWithRelations[TTableName]['relations'][K]['referencedTableName'] - >['tsName'], - TEntityRelations[K] extends true ? {} : TEntityRelations[K] - >> - : Array, - // @ts-ignore - ExtractTablesWithRelations[TTableName]['relations'][K]['referencedTableName'] - >['tsName'], - TEntityRelations[K] extends true ? {} : TEntityRelations[K] - >> -}; - -export type UserEntity< - TEntityRelations extends EntityRelations, ExtractTablesWithRelations['users']> = {}, -> = Entity<'users', TEntityRelations>; -export type PokemonEntity< - TEntityRelations extends EntityRelations, ExtractTablesWithRelations['pokemons']> = {}, -> = Entity<'pokemons', TEntityRelations>; -export type PackEntity< - TEntityRelations extends EntityRelations, ExtractTablesWithRelations['packs']> = {}, -> = Entity<'packs', TEntityRelations>; -export type OpenedPackEntity< - TEntityRelations extends EntityRelations, ExtractTablesWithRelations['openedPacks']> = {}, -> = Entity<'openedPacks', TEntityRelations>; -export type UserItemEntity< - TEntityRelations extends EntityRelations, ExtractTablesWithRelations['userItems']> = {}, -> = Entity<'userItems', TEntityRelations>; -export type QuickSoldUserItemEntity< - TEntityRelations extends EntityRelations, ExtractTablesWithRelations['quickSoldUserItems']> = {}, -> = Entity<'quickSoldUserItems', TEntityRelations>; -type _TradeEntity< - TEntityRelations extends EntityRelations, ExtractTablesWithRelations['trades']> = {}, -> = Entity<'trades', TEntityRelations>; -export type PendingTradeEntity< - TEntityRelations extends EntityRelations, ExtractTablesWithRelations['trades']> = {}, -> = Omit<_TradeEntity, 'status'> & { status: 'PENDING' }; -export type CancelledTradeEntity< - TEntityRelations extends EntityRelations, ExtractTablesWithRelations['trades']> = {}, -> = Omit<_TradeEntity, 'status'> & { status: 'CANCELLED' }; -export type AcceptedTradeEntity< - TEntityRelations extends EntityRelations, ExtractTablesWithRelations['trades']> = {}, -> = Omit<_TradeEntity, 'status'> & { status: 'ACCEPTED' }; -export type RejectedTradeEntity< - TEntityRelations extends EntityRelations, ExtractTablesWithRelations['trades']> = {}, -> = Omit<_TradeEntity, 'status'> & { status: 'REJECTED' }; -export type TradeEntity< - TEntityRelations extends EntityRelations, ExtractTablesWithRelations['trades']> = {}, -> = - | PendingTradeEntity - | CancelledTradeEntity - | AcceptedTradeEntity - | RejectedTradeEntity +import { NodePgDatabase, NodePgQueryResultHKT } from "drizzle-orm/node-postgres"; export type Transaction = PgTransaction>; - -type With< - TTableName extends keyof ExtractTablesWithRelations, -> = { - [K in keyof ExtractTablesWithRelations[TTableName]['relations']]?: true | { - with?: With< - // @ts-ignore - FindTableByDBName< - ExtractTablesWithRelations, - // @ts-ignore - ExtractTablesWithRelations[TTableName]['relations'][K]['referencedTableName'] - >['tsName'] - > - } -} - -// TODO: move it to a separate file -// TODO: check if it works not only on the type level, but in RUNTIME -export const entityRelationsToWith = < - TTableName extends keyof ExtractTablesWithRelations, ->( - entityRelations: EntityRelations, ExtractTablesWithRelations[TTableName]> -): With => { - return Object.entries(entityRelations).reduce((acc, [key, value]) => { - if (value === true) { - return { - ...acc, - with: { - // @ts-ignore - ...acc.with, - [key]: true, - } - } - } else { - return { - ...acc, - with: { - // @ts-ignore - ...acc.with, - // @ts-ignore - [key]: entityRelationsToWith(value), - }, - } - } - }, {}); -}; +export type Database = NodePgDatabase; diff --git a/src/infra/postgres/postgres.module.ts b/src/infra/postgres/postgres.module.ts index 91c58c7..cb093a7 100644 --- a/src/infra/postgres/postgres.module.ts +++ b/src/infra/postgres/postgres.module.ts @@ -1,16 +1,11 @@ -import { DynamicModule, Inject, Module } from '@nestjs/common'; +import { Module } from '@nestjs/common'; import { ConfigService } from '@nestjs/config'; -import { TypeOrmModule } from '@nestjs/typeorm'; import { EnvVariables } from '../config/validation'; -import { TypeOrmConfigService } from '../config/typeorm'; import { ConfigModule } from "@nestjs/config"; import { validate } from '../config/validation'; import { DrizzlePGModule } from '@knaadh/nestjs-drizzle-pg'; import * as schema from './tables'; - -// TODO: move to separate file -const DRIZZLE_DB_TAG = 'DRIZZLE_DB_TAG'; -export const InjectDrizzle = () => Inject(DRIZZLE_DB_TAG); +import { DRIZZLE_DB_TAG } from '../consts'; @Module({ imports: [ @@ -19,10 +14,6 @@ export const InjectDrizzle = () => Inject(DRIZZLE_DB_TAG); envFilePath: '.env', validate, }), - TypeOrmModule.forRootAsync({ - inject: [ConfigService], - useClass: TypeOrmConfigService, - }), DrizzlePGModule.registerAsync({ tag: DRIZZLE_DB_TAG, inject: [ConfigService], @@ -44,11 +35,4 @@ export const InjectDrizzle = () => Inject(DRIZZLE_DB_TAG); }) ], }) -export class PostgresModule { - public static forFeature(...args: Parameters): DynamicModule { - return { - ...TypeOrmModule.forFeature(...args), - module: PostgresModule, - }; - } -} +export class PostgresModule {} diff --git a/src/infra/postgres/seeders/pokemons.seeder.ts b/src/infra/postgres/seeders/pokemons.seeder.ts index b505c32..e355477 100644 --- a/src/infra/postgres/seeders/pokemons.seeder.ts +++ b/src/infra/postgres/seeders/pokemons.seeder.ts @@ -1,10 +1,10 @@ import { Injectable } from "@nestjs/common"; import { Seeder } from "nestjs-seeder"; -import { PokemonsServiceDrizzle } from 'src/core/services/pokemons.service'; +import { PokemonsService } from 'src/core/services/pokemons.service'; @Injectable() export class PokemonsSeeder implements Seeder { - constructor(private readonly pokemonsService: PokemonsServiceDrizzle) {} + constructor(private readonly pokemonsService: PokemonsService) {} async seed() { // TODO: Maybe i can do that with a single request? Research on that diff --git a/src/infra/postgres/tables/index.ts b/src/infra/postgres/tables/index.ts index e30537f..a2ed9fd 100644 --- a/src/infra/postgres/tables/index.ts +++ b/src/infra/postgres/tables/index.ts @@ -1,10 +1,10 @@ export * from './opened-packs.table'; -export * from './pack-pokemons.table'; +export * from './packs-to-pokemons.table'; export * from './packs.table'; export * from './pokemons.table'; export * from './quick-sold-user-items.table'; -export * from './trade-receiver-items.table'; -export * from './trade-sender-items.table'; +export * from './trades-to-receiver-items.table'; +export * from './trades-to-sender-items.table'; export * from './trades.table'; export * from './user-items.table'; export * from './users.table'; diff --git a/src/infra/postgres/tables/opened-packs.table.ts b/src/infra/postgres/tables/opened-packs.table.ts index 70dc7e0..d1260c0 100644 --- a/src/infra/postgres/tables/opened-packs.table.ts +++ b/src/infra/postgres/tables/opened-packs.table.ts @@ -1,12 +1,12 @@ import { uuid, pgTable, integer, timestamp } from 'drizzle-orm/pg-core'; import { baseIdColumn } from '../other/base-columns'; -import { users } from './users.table'; -import { packs } from './packs.table'; -import { pokemons } from './pokemons.table'; +import { UserEntity, usersTable } from './users.table'; +import { PackEntity, packsTable } from './packs.table'; +import { PokemonEntity, pokemonsTable } from './pokemons.table'; import { UUIDv4 } from 'src/common/types'; import { relations } from 'drizzle-orm'; -export const openedPacks = pgTable('opened_packs', { +export const openedPacksTable = pgTable('opened_packs', { ...baseIdColumn, openedAt: timestamp('opened_at', { withTimezone: true }) .notNull() @@ -14,18 +14,38 @@ export const openedPacks = pgTable('opened_packs', { userId: uuid('user_id') .$type() .notNull() - .references(() => users.id, { onDelete: 'cascade', onUpdate: 'cascade' }), + .references(() => usersTable.id, { onDelete: 'cascade', onUpdate: 'cascade' }), packId: uuid('pack_id') .$type() .notNull() - .references(() => packs.id, { onDelete: 'cascade', onUpdate: 'cascade' }), + .references(() => packsTable.id, { onDelete: 'cascade', onUpdate: 'cascade' }), pokemonId: integer('pokemon_id') .notNull() - .references(() => pokemons.id, { onDelete: 'cascade', onUpdate: 'cascade' }), + .references(() => pokemonsTable.id, { onDelete: 'cascade', onUpdate: 'cascade' }), }) -export const openedPacksRelations = relations(openedPacks, ({ one }) => ({ - user: one(users), - pack: one(packs), - pokemon: one(pokemons), +export const openedPacksTableRelations = relations(openedPacksTable, ({ one }) => ({ + user: one(usersTable, { + fields: [openedPacksTable.userId], + references: [usersTable.id], + }), + pack: one(packsTable, { + fields: [openedPacksTable.packId], + references: [packsTable.id], + }), + pokemon: one(pokemonsTable, { + fields: [openedPacksTable.pokemonId], + references: [pokemonsTable.id], + }), })) + +export type OpenedPackEntity = typeof openedPacksTable.$inferSelect & { + user: UserEntity, + pack: PackEntity, + pokemon: PokemonEntity, +} +export type CreateOpenedPackEntityValues = Omit & { + user: UserEntity, + pack: PackEntity, + pokemon: PokemonEntity, +} diff --git a/src/infra/postgres/tables/pack-pokemons.table.ts b/src/infra/postgres/tables/pack-pokemons.table.ts deleted file mode 100644 index e2038fa..0000000 --- a/src/infra/postgres/tables/pack-pokemons.table.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { relations } from 'drizzle-orm'; -import { uuid, integer, pgTable, index, primaryKey } from 'drizzle-orm/pg-core'; -import { UUIDv4 } from 'src/common/types'; -import { packs } from './packs.table'; -import { pokemons } from './pokemons.table'; - -export const packPokemons = pgTable('pack_pokemons', { - packId: uuid('pack_id') - .$type() - .notNull() - .references(() => packs.id, { onDelete: 'cascade', onUpdate: 'cascade' }), - pokemonId: integer('pokemon_id') - .notNull() - .references(() => pokemons.id, { onDelete: 'cascade', onUpdate: 'cascade' }), -}, (table) => ({ - primaryKey: primaryKey({ columns: [table.packId, table.pokemonId] }), - packIdIdx: index().on(table.packId), - pokemonIdIdx: index().on(table.pokemonId), -})); - -export const packPokemonsRelations = relations(packPokemons, ({ one }) => ({ - pack: one(packs), - pokemon: one(pokemons), -})); diff --git a/src/infra/postgres/tables/packs-to-pokemons.table.ts b/src/infra/postgres/tables/packs-to-pokemons.table.ts new file mode 100644 index 0000000..b499a47 --- /dev/null +++ b/src/infra/postgres/tables/packs-to-pokemons.table.ts @@ -0,0 +1,35 @@ +import { relations } from 'drizzle-orm'; +import { uuid, integer, pgTable, index, primaryKey } from 'drizzle-orm/pg-core'; +import { UUIDv4 } from 'src/common/types'; +import { PackEntity, packsTable } from './packs.table'; +import { PokemonEntity, pokemonsTable } from './pokemons.table'; + +export const packsToPokemonsTable = pgTable('packs_to_pokemons', { + packId: uuid('pack_id') + .$type() + .notNull() + .references(() => packsTable.id, { onDelete: 'cascade', onUpdate: 'cascade' }), + pokemonId: integer('pokemon_id') + .notNull() + .references(() => pokemonsTable.id, { onDelete: 'cascade', onUpdate: 'cascade' }), +}, (table) => ({ + primaryKey: primaryKey({ columns: [table.packId, table.pokemonId] }), + packIdIdx: index().on(table.packId), + pokemonIdIdx: index().on(table.pokemonId), +})); + +export const packsToPokemonsTableRelations = relations(packsToPokemonsTable, ({ one }) => ({ + pack: one(packsTable, { + fields: [packsToPokemonsTable.packId], + references: [packsTable.id], + }), + pokemon: one(pokemonsTable, { + fields: [packsToPokemonsTable.pokemonId], + references: [pokemonsTable.id], + }), +})); + +export type PackToPokemonEntity = typeof packsToPokemonsTable.$inferSelect & { + pack: PackEntity, + pokemon: PokemonEntity, +} diff --git a/src/infra/postgres/tables/packs.table.ts b/src/infra/postgres/tables/packs.table.ts index 6ae063f..336f8f5 100644 --- a/src/infra/postgres/tables/packs.table.ts +++ b/src/infra/postgres/tables/packs.table.ts @@ -1,9 +1,9 @@ import { relations } from 'drizzle-orm'; import { integer, pgTable, text } from 'drizzle-orm/pg-core'; import { baseColumns } from '../other/base-columns'; -import { packPokemons } from './pack-pokemons.table'; +import { packsToPokemonsTable } from './packs-to-pokemons.table'; -export const packs = pgTable('packs', { +export const packsTable = pgTable('packs', { ...baseColumns, name: text('name') .notNull(), @@ -15,6 +15,10 @@ export const packs = pgTable('packs', { .notNull(), }) -export const packsRelations = relations(packs, ({ many }) => ({ - packPokemons: many(packPokemons), +export const packsTableRelations = relations(packsTable, ({ many }) => ({ + packsToPokemons: many(packsToPokemonsTable), })) + +export type PackEntity = typeof packsTable.$inferSelect; +export type CreatePackEntityValues = Omit; +export type UpdatePackEntityValues = Partial; diff --git a/src/infra/postgres/tables/pokemons.table.ts b/src/infra/postgres/tables/pokemons.table.ts index e522143..6055b55 100644 --- a/src/infra/postgres/tables/pokemons.table.ts +++ b/src/infra/postgres/tables/pokemons.table.ts @@ -1,8 +1,8 @@ import { relations } from 'drizzle-orm'; import { pgTable, text, integer } from 'drizzle-orm/pg-core'; -import { packPokemons } from './pack-pokemons.table'; +import { packsToPokemonsTable } from './packs-to-pokemons.table'; -export const pokemons = pgTable('pokemons', { +export const pokemonsTable = pgTable('pokemons', { id: integer('id') .notNull() .primaryKey(), @@ -18,6 +18,9 @@ export const pokemons = pgTable('pokemons', { .notNull(), }); -export const pokemonsRelations = relations(pokemons, ({ many }) => ({ - packPokemons: many(packPokemons), +export const pokemonsTableRelations = relations(pokemonsTable, ({ many }) => ({ + packsToPokemons: many(packsToPokemonsTable), })); + +export type PokemonEntity = typeof pokemonsTable.$inferSelect; +export type CreatePokemonEntityValues = typeof pokemonsTable.$inferInsert; diff --git a/src/infra/postgres/tables/quick-sold-user-items.table.ts b/src/infra/postgres/tables/quick-sold-user-items.table.ts index 6befaec..0f4ad68 100644 --- a/src/infra/postgres/tables/quick-sold-user-items.table.ts +++ b/src/infra/postgres/tables/quick-sold-user-items.table.ts @@ -1,23 +1,38 @@ +import { warn } from 'console'; import { relations } from 'drizzle-orm'; import { uuid, pgTable, timestamp } from 'drizzle-orm/pg-core'; import { UUIDv4 } from 'src/common/types'; -import { pokemons } from './pokemons.table'; -import { userItemsColumns } from './user-items.table'; -import { users } from './users.table'; +import { PokemonEntity, pokemonsTable } from './pokemons.table'; +import { userItemsTableColumns } from './user-items.table'; +import { UserEntity, usersTable } from './users.table'; -export const quickSoldUserItems = pgTable('quick_sold_user_items', { - ...userItemsColumns, +export const quickSoldUserItemsTable = pgTable('quick_sold_user_items', { + ...userItemsTableColumns, // NOTE: Overriding id column without default value id: uuid('id') .$type() .notNull() .primaryKey(), + // NOTE: Overriding received_at column without default value + receivedAt: timestamp('received_at', { withTimezone: true }) + .notNull(), soldAt: timestamp('sold_at', { withTimezone: true }) .notNull() .defaultNow(), }) -export const quickSoldUserItemsRelations = relations(quickSoldUserItems, ({ one }) => ({ - user: one(users), - pokemon: one(pokemons), +export const quickSoldUserItemsTableRelations = relations(quickSoldUserItemsTable, ({ one }) => ({ + user: one(usersTable, { + fields: [quickSoldUserItemsTable.userId], + references: [usersTable.id], + }), + pokemon: one(pokemonsTable, { + fields: [quickSoldUserItemsTable.pokemonId], + references: [pokemonsTable.id], + }), })); + +export type QuickSoldUserItemEntity = typeof quickSoldUserItemsTable.$inferSelect & { + user: UserEntity, + pokemon: PokemonEntity, +}; diff --git a/src/infra/postgres/tables/trade-receiver-items.table.ts b/src/infra/postgres/tables/trade-receiver-items.table.ts deleted file mode 100644 index dd50912..0000000 --- a/src/infra/postgres/tables/trade-receiver-items.table.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { relations } from 'drizzle-orm'; -import { uuid, pgTable, index, primaryKey } from 'drizzle-orm/pg-core'; -import { UUIDv4 } from 'src/common/types'; -import { trades } from './trades.table'; -import { userItems } from './user-items.table'; - -export const tradeReceiverItems = pgTable('trade_receiver_items', { - tradeId: uuid('trade_id') - .$type() - .notNull() - .references(() => trades.id, { onDelete: 'cascade', onUpdate: 'cascade' }), - userItemId: uuid('user_item_id') - .$type() - .notNull() - .references(() => trades.id, { onDelete: 'cascade', onUpdate: 'cascade' }), -}, (table) => ({ - primaryKey: primaryKey({ columns: [table.tradeId, table.userItemId] }), - tradeIdIdx: index().on(table.tradeId), - userItemIdIdx: index().on(table.userItemId), -})); - -export const tradeReceiverItemsRelations = relations(tradeReceiverItems, ({ one }) => ({ - trade: one(trades), - userItem: one(userItems), -})) diff --git a/src/infra/postgres/tables/trade-sender-items.table.ts b/src/infra/postgres/tables/trade-sender-items.table.ts deleted file mode 100644 index b07a836..0000000 --- a/src/infra/postgres/tables/trade-sender-items.table.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { relations } from 'drizzle-orm'; -import { uuid, pgTable, index, primaryKey } from 'drizzle-orm/pg-core'; -import { UUIDv4 } from 'src/common/types'; -import { trades } from './trades.table'; -import { userItems } from './user-items.table'; - -export const tradeSenderItems = pgTable('trade_sender_items', { - tradeId: uuid('trade_id') - .$type() - .notNull() - .references(() => trades.id, { onDelete: 'cascade', onUpdate: 'cascade' }), - userItemId: uuid('user_item_id') - .$type() - .notNull() - .references(() => trades.id, { onDelete: 'cascade', onUpdate: 'cascade' }), -}, (table) => ({ - primaryKey: primaryKey({ columns: [table.tradeId, table.userItemId] }), - tradeIdIdx: index().on(table.tradeId), - userItemIdIdx: index().on(table.userItemId), -})); - -export const tradeSenderItemsRelations = relations(tradeSenderItems, ({ one }) => ({ - trade: one(trades), - userItem: one(userItems), -})); diff --git a/src/infra/postgres/tables/trades-to-receiver-items.table.ts b/src/infra/postgres/tables/trades-to-receiver-items.table.ts new file mode 100644 index 0000000..0c5c39b --- /dev/null +++ b/src/infra/postgres/tables/trades-to-receiver-items.table.ts @@ -0,0 +1,40 @@ +import { relations } from 'drizzle-orm'; +import { uuid, pgTable, index, primaryKey } from 'drizzle-orm/pg-core'; +import { UUIDv4 } from 'src/common/types'; +import { TradeEntity, tradesTable } from './trades.table'; +import { UserItemEntity, userItemsTable } from './user-items.table'; + +export const tradesToReceiverItemsTable = pgTable('trades_to_receiver_items', { + tradeId: uuid('trade_id') + .$type() + .notNull() + .references(() => tradesTable.id, { onDelete: 'cascade', onUpdate: 'cascade' }), + receiverItemId: uuid('receiver_item_id') + .$type() + .notNull() + .references(() => userItemsTable.id, { onDelete: 'cascade', onUpdate: 'cascade' }), +}, (table) => ({ + primaryKey: primaryKey({ columns: [table.tradeId, table.receiverItemId] }), + tradeIdIdx: index().on(table.tradeId), + receiverItemIdIdx: index().on(table.receiverItemId), +})); + +export const tradesToReceiverItemsTableRelations = relations(tradesToReceiverItemsTable, ({ one }) => ({ + trade: one(tradesTable, { + fields: [tradesToReceiverItemsTable.tradeId], + references: [tradesTable.id], + }), + receiverItem: one(userItemsTable, { + fields: [tradesToReceiverItemsTable.receiverItemId], + references: [userItemsTable.id], + }), +})); + +export type TradeToReceiverItemEntity = typeof tradesToReceiverItemsTable.$inferSelect & { + trade: TradeEntity, + receiverItem: UserItemEntity, +}; +export type CreateTradeToReceiverItemEntityValues = Omit & { + trade: TradeEntity, + receiverItem: UserItemEntity, +} diff --git a/src/infra/postgres/tables/trades-to-sender-items.table.ts b/src/infra/postgres/tables/trades-to-sender-items.table.ts new file mode 100644 index 0000000..d3b1efe --- /dev/null +++ b/src/infra/postgres/tables/trades-to-sender-items.table.ts @@ -0,0 +1,42 @@ +import { relations } from 'drizzle-orm'; +import { uuid, pgTable, index, primaryKey } from 'drizzle-orm/pg-core'; +import { UUIDv4 } from 'src/common/types'; +import { TradeEntity, tradesTable } from './trades.table'; +import { UserItemEntity, userItemsTable } from './user-items.table'; + +// TODO: Merge two tables `trades_to_sender_items` and `trades_to_receiver_items` +// to one table `trades_to_user_items` with extra enum column `user_type` with values: 'SENDER' | 'RECEIVER' +export const tradesToSenderItemsTable = pgTable('trades_to_sender_items', { + tradeId: uuid('trade_id') + .$type() + .notNull() + .references(() => tradesTable.id, { onDelete: 'cascade', onUpdate: 'cascade' }), + senderItemId: uuid('sender_item_id') + .$type() + .notNull() + .references(() => userItemsTable.id, { onDelete: 'cascade', onUpdate: 'cascade' }), +}, (table) => ({ + primaryKey: primaryKey({ columns: [table.tradeId, table.senderItemId] }), + tradeIdIdx: index().on(table.tradeId), + senderItemIdIdx: index().on(table.senderItemId), +})); + +export const tradesToSenderItemsTableRelations = relations(tradesToSenderItemsTable, ({ one }) => ({ + trade: one(tradesTable, { + fields: [tradesToSenderItemsTable.tradeId], + references: [tradesTable.id], + }), + senderItem: one(userItemsTable, { + fields: [tradesToSenderItemsTable.senderItemId], + references: [userItemsTable.id], + }), +})); + +export type TradeToSenderItemEntity = typeof tradesToSenderItemsTable.$inferSelect & { + trade: TradeEntity, + senderItem: UserItemEntity, +}; +export type CreateTradeToSenderItemEntityValues = Omit & { + trade: TradeEntity, + senderItem: UserItemEntity, +} diff --git a/src/infra/postgres/tables/trades.table.ts b/src/infra/postgres/tables/trades.table.ts index 7c0f9dd..8886032 100644 --- a/src/infra/postgres/tables/trades.table.ts +++ b/src/infra/postgres/tables/trades.table.ts @@ -2,41 +2,103 @@ import { relations } from 'drizzle-orm'; import { uuid, pgTable, timestamp, index, pgEnum } from 'drizzle-orm/pg-core'; import { UUIDv4 } from 'src/common/types'; import { baseColumns } from '../other/base-columns'; -import { tradeReceiverItems } from './trade-receiver-items.table'; -import { tradeSenderItems } from './trade-sender-items.table'; -import { users } from './users.table'; +import { tradesToReceiverItemsTable } from './trades-to-receiver-items.table'; +import { tradesToSenderItemsTable } from './trades-to-sender-items.table'; +import { UserItemEntity } from './user-items.table'; +import { UserEntity, usersTable } from './users.table'; -const tradeStatusEnum = pgEnum('trade_status', [ +export const tradeStatusEnum = pgEnum('trade_status', [ 'PENDING', 'CANCELLED', 'ACCEPTED', 'REJECTED', ]); -export const trades = pgTable('trades', { +export const tradesTable = pgTable('trades', { ...baseColumns, status: tradeStatusEnum('status') .notNull(), senderId: uuid('sender_id') .$type() .notNull() - .references(() => users.id, { onDelete: 'cascade', onUpdate: 'cascade' }), + .references(() => usersTable.id, { onDelete: 'cascade', onUpdate: 'cascade' }), receiverId: uuid('receiver_id') .$type() .notNull() - .references(() => users.id, { onDelete: 'cascade', onUpdate: 'cascade' }), - cancelledAt: timestamp('accepted_at', { withTimezone: true }), + .references(() => usersTable.id, { onDelete: 'cascade', onUpdate: 'cascade' }), + cancelledAt: timestamp('cancelled_at', { withTimezone: true }), acceptedAt: timestamp('accepted_at', { withTimezone: true }), rejectedAt: timestamp('rejected_at', { withTimezone: true }), }, (table) => ({ statusIdx: index().on(table.status), })); -export const tradesRelations = relations(trades, ({ one, many }) => ({ - sender: one(users), - senderItems: many(tradeSenderItems), - receiver: one(users), - receiverItems: many(tradeReceiverItems), +export const tradesTableRelations = relations(tradesTable, ({ one, many }) => ({ + sender: one(usersTable, { + fields: [tradesTable.senderId], + references: [usersTable.id], + }), + tradesToSenderItems: many(tradesToSenderItemsTable), + receiver: one(usersTable, { + fields: [tradesTable.receiverId], + references: [usersTable.id], + }), + tradesToReceiverItems: many(tradesToReceiverItemsTable), })) export type TradeStatus = typeof tradeStatusEnum.enumValues[number]; + +export type TradeEntity = typeof tradesTable.$inferSelect & { + sender: UserEntity, + receiver: UserEntity, +}; + +export type PendingTradeEntity = Omit & { + status: 'PENDING' +}; +export type CreatePendingTradeEntityValues = Omit< + typeof tradesTable.$inferInsert, + | 'status' + | 'cancelledAt' + | 'acceptedAt' + | 'rejectedAt' + | 'senderId' + | 'receiverId'> & { + sender: UserEntity, + senderItems: Array, + receiver: UserEntity, + receiverItems: Array, +}; +export type CancelledTradeEntity = Omit & { + status: 'CANCELLED', + cancelledAt: Date, +}; +export type AcceptedTradeEntity = Omit & { + status: 'ACCEPTED', + acceptedAt: Date, +}; +export type RejectedTradeEntity = Omit & { + status: 'REJECTED', + rejectedAt: Date, +}; +// export type TradeEntity = +// | PendingTradeEntity +// | CancelledTradeEntity +// | AcceptedTradeEntity +// | RejectedTradeEntity diff --git a/src/infra/postgres/tables/user-items.table.ts b/src/infra/postgres/tables/user-items.table.ts index 0869448..27d459e 100644 --- a/src/infra/postgres/tables/user-items.table.ts +++ b/src/infra/postgres/tables/user-items.table.ts @@ -1,11 +1,13 @@ import { uuid, integer, pgTable, timestamp } from 'drizzle-orm/pg-core'; import { baseIdColumn } from '../other/base-columns'; -import { users } from './users.table'; -import { pokemons } from './pokemons.table'; +import { UserEntity, usersTable } from './users.table'; +import { PokemonEntity, pokemonsTable } from './pokemons.table'; import { UUIDv4 } from 'src/common/types'; import { relations } from 'drizzle-orm'; +import { tradesToSenderItemsTable } from './trades-to-sender-items.table'; +import { tradesToReceiverItemsTable } from './trades-to-receiver-items.table'; -export const userItemsColumns = { +export const userItemsTableColumns = { ...baseIdColumn, receivedAt: timestamp('received_at', { withTimezone: true }) .notNull() @@ -13,15 +15,35 @@ export const userItemsColumns = { userId: uuid('user_id') .$type() .notNull() - .references(() => users.id, { onDelete: 'cascade', onUpdate: 'cascade' }), + .references(() => usersTable.id, { onDelete: 'cascade', onUpdate: 'cascade' }), pokemonId: integer('pokemon_id') .notNull() - .references(() => pokemons.id, { onDelete: 'cascade', onUpdate: 'cascade' }), + .references(() => pokemonsTable.id, { onDelete: 'cascade', onUpdate: 'cascade' }), } -export const userItems = pgTable('user_items', userItemsColumns); +export const userItemsTable = pgTable('user_items', userItemsTableColumns); -export const userItemsRelations = relations(userItems, ({ one }) => ({ - user: one(users), - pokemon: one(pokemons), +export const userItemsTableRelations = relations(userItemsTable, ({ one, many }) => ({ + user: one(usersTable, { + fields: [userItemsTable.userId], + references: [usersTable.id], + }), + pokemon: one(pokemonsTable, { + fields: [userItemsTable.pokemonId], + references: [pokemonsTable.id], + }), + tradesToSenderItems: many(tradesToSenderItemsTable), + tradesToReceiverItems: many(tradesToReceiverItemsTable), })); + +export type UserItemEntity = typeof userItemsTable.$inferSelect & { + user: UserEntity, + pokemon: PokemonEntity, +} +export type CreateUserItemEntityValues = Omit & { + user: UserEntity, + pokemon: PokemonEntity, +} +export type UpdateUserItemEntityValues = Partial; diff --git a/src/infra/postgres/tables/users.table.ts b/src/infra/postgres/tables/users.table.ts index 158470f..fb3af5d 100644 --- a/src/infra/postgres/tables/users.table.ts +++ b/src/infra/postgres/tables/users.table.ts @@ -1,11 +1,11 @@ import { relations } from 'drizzle-orm'; import { pgTable, text, integer } from 'drizzle-orm/pg-core'; import { baseColumns } from '../other/base-columns'; -import { openedPacks } from './opened-packs.table'; -import { quickSoldUserItems } from './quick-sold-user-items.table'; -import { userItems } from './user-items.table'; +import { openedPacksTable } from './opened-packs.table'; +import { quickSoldUserItemsTable } from './quick-sold-user-items.table'; +import { userItemsTable } from './user-items.table'; -export const users = pgTable('users', { +export const usersTable = pgTable('users', { ...baseColumns, name: text('name') .notNull() @@ -17,8 +17,12 @@ export const users = pgTable('users', { .default(0), }); -export const usersRelations = relations(users, ({ many }) => ({ - items: many(userItems), - openedPacks: many(openedPacks), - quickSoldItems: many(quickSoldUserItems), +export const usersTableRelations = relations(usersTable, ({ many }) => ({ + items: many(userItemsTable), + openedPacks: many(openedPacksTable), + quickSoldItems: many(quickSoldUserItemsTable), })) + +export type UserEntity = typeof usersTable.$inferSelect; +export type CreateUserEntityValues = Omit; +export type UpdateUserEntityValues = Partial;