diff --git a/package-lock.json b/package-lock.json index 651ffb2..29576ee 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,7 @@ "version": "0.0.1", "license": "UNLICENSED", "dependencies": { + "@nestjs/cache-manager": "^2.3.0", "@nestjs/common": "^10.0.0", "@nestjs/config": "^3.3.0", "@nestjs/core": "^10.0.0", @@ -17,6 +18,9 @@ "@nestjs/passport": "^10.0.3", "@nestjs/platform-express": "^10.0.0", "argon2": "^0.41.1", + "cache-manager-redis-yet": "^5.1.5", + "class-transformer": "^0.5.1", + "class-validator": "^0.14.1", "dotenv": "^16.4.7", "lodash": "^4.17.21", "passport": "^0.7.0", @@ -1578,6 +1582,18 @@ "node": ">=8" } }, + "node_modules/@nestjs/cache-manager": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@nestjs/cache-manager/-/cache-manager-2.3.0.tgz", + "integrity": "sha512-pxeBp9w/s99HaW2+pezM1P3fLiWmUEnTUoUMLa9UYViCtjj0E0A19W/vaT5JFACCzFIeNrwH4/16jkpAhQ25Vw==", + "license": "MIT", + "peerDependencies": { + "@nestjs/common": "^9.0.0 || ^10.0.0", + "@nestjs/core": "^9.0.0 || ^10.0.0", + "cache-manager": "<=5", + "rxjs": "^7.0.0" + } + }, "node_modules/@nestjs/cli": { "version": "10.4.9", "resolved": "https://registry.npmjs.org/@nestjs/cli/-/cli-10.4.9.tgz", @@ -1923,6 +1939,71 @@ "url": "https://opencollective.com/unts" } }, + "node_modules/@redis/bloom": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@redis/bloom/-/bloom-1.2.0.tgz", + "integrity": "sha512-HG2DFjYKbpNmVXsa0keLHp/3leGJz1mjh09f2RLGGLQZzSHpkmZWuwJbAvo3QcRY8p80m5+ZdXZdYOSBLlp7Cg==", + "license": "MIT", + "peerDependencies": { + "@redis/client": "^1.0.0" + } + }, + "node_modules/@redis/client": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@redis/client/-/client-1.6.0.tgz", + "integrity": "sha512-aR0uffYI700OEEH4gYnitAnv3vzVGXCFvYfdpu/CJKvk4pHfLPEy/JSZyrpQ+15WhXe1yJRXLtfQ84s4mEXnPg==", + "license": "MIT", + "dependencies": { + "cluster-key-slot": "1.1.2", + "generic-pool": "3.9.0", + "yallist": "4.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/@redis/client/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "license": "ISC" + }, + "node_modules/@redis/graph": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@redis/graph/-/graph-1.1.1.tgz", + "integrity": "sha512-FEMTcTHZozZciLRl6GiiIB4zGm5z5F3F6a6FZCyrfxdKOhFlGkiAqlexWMBzCi4DcRoyiOsuLfW+cjlGWyExOw==", + "license": "MIT", + "peerDependencies": { + "@redis/client": "^1.0.0" + } + }, + "node_modules/@redis/json": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/@redis/json/-/json-1.0.7.tgz", + "integrity": "sha512-6UyXfjVaTBTJtKNG4/9Z8PSpKE6XgSyEb8iwaqDcy+uKrd/DGYHTWkUdnQDyzm727V7p21WUMhsqz5oy65kPcQ==", + "license": "MIT", + "peerDependencies": { + "@redis/client": "^1.0.0" + } + }, + "node_modules/@redis/search": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@redis/search/-/search-1.2.0.tgz", + "integrity": "sha512-tYoDBbtqOVigEDMAcTGsRlMycIIjwMCgD8eR2t0NANeQmgK/lvxNAvYyb6bZDD4frHRhIHkJu2TBRvB0ERkOmw==", + "license": "MIT", + "peerDependencies": { + "@redis/client": "^1.0.0" + } + }, + "node_modules/@redis/time-series": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@redis/time-series/-/time-series-1.1.0.tgz", + "integrity": "sha512-c1Q99M5ljsIuc4YdaCwfUEXsofakb9c8+Zse2qxTadu8TalLXuAESzLvFAvNVbkmSlvlzIQOLpBCmWI9wTOt+g==", + "license": "MIT", + "peerDependencies": { + "@redis/client": "^1.0.0" + } + }, "node_modules/@sinclair/typebox": { "version": "0.27.8", "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", @@ -3387,6 +3468,47 @@ "node": ">= 0.8" } }, + "node_modules/cache-manager": { + "version": "5.7.6", + "resolved": "https://registry.npmjs.org/cache-manager/-/cache-manager-5.7.6.tgz", + "integrity": "sha512-wBxnBHjDxF1RXpHCBD6HGvKER003Ts7IIm0CHpggliHzN1RZditb7rXoduE1rplc2DEFYKxhLKgFuchXMJje9w==", + "license": "MIT", + "dependencies": { + "eventemitter3": "^5.0.1", + "lodash.clonedeep": "^4.5.0", + "lru-cache": "^10.2.2", + "promise-coalesce": "^1.1.2" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/cache-manager-redis-yet": { + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/cache-manager-redis-yet/-/cache-manager-redis-yet-5.1.5.tgz", + "integrity": "sha512-NYDxrWBoLXxxVPw4JuBriJW0f45+BVOAsgLiozRo4GoJQyoKPbueQWYStWqmO73/AeHJeWrV7Hzvk6vhCGHlqA==", + "deprecated": "With cache-manager v6 we now are using Keyv", + "license": "MIT", + "dependencies": { + "@redis/bloom": "^1.2.0", + "@redis/client": "^1.6.0", + "@redis/graph": "^1.1.1", + "@redis/json": "^1.0.7", + "@redis/search": "^1.2.0", + "@redis/time-series": "^1.1.0", + "cache-manager": "^5.7.6", + "redis": "^4.7.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/cache-manager/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "license": "ISC" + }, "node_modules/call-bind": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", @@ -3567,6 +3689,23 @@ "dev": true, "license": "MIT" }, + "node_modules/class-transformer": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/class-transformer/-/class-transformer-0.5.1.tgz", + "integrity": "sha512-SQa1Ws6hUbfC98vKGxZH3KFY0Y1lm5Zm0SY8XX9zbK7FJCyVEac3ATW0RIpwzW+oOfmHE5PMPufDG9hCfoEOMw==", + "license": "MIT" + }, + "node_modules/class-validator": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/class-validator/-/class-validator-0.14.1.tgz", + "integrity": "sha512-2VEG9JICxIqTpoK1eMzZqaV+u/EiwEJkMGzTrZf6sU/fwsnOITVgYJ8yojSy6CaXtO9V0Cc6ZQZ8h8m4UBuLwQ==", + "license": "MIT", + "dependencies": { + "@types/validator": "^13.11.8", + "libphonenumber-js": "^1.10.53", + "validator": "^13.9.0" + } + }, "node_modules/cli-cursor": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", @@ -3662,6 +3801,15 @@ "node": ">=0.8" } }, + "node_modules/cluster-key-slot": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/cluster-key-slot/-/cluster-key-slot-1.1.2.tgz", + "integrity": "sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==", + "license": "Apache-2.0", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/co": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", @@ -4562,6 +4710,12 @@ "node": ">= 0.6" } }, + "node_modules/eventemitter3": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", + "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==", + "license": "MIT" + }, "node_modules/events": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", @@ -5099,6 +5253,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/generic-pool": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/generic-pool/-/generic-pool-3.9.0.tgz", + "integrity": "sha512-hymDOu5B53XvN4QT9dBmZxPX4CWhBPPLguTZ9MMFeFa/Kg0xWVfylOVNlJji/E7yTZWFd/q9GO5TxDLq156D7g==", + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, "node_modules/gensync": { "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", @@ -6700,6 +6863,12 @@ "node": ">= 0.8.0" } }, + "node_modules/libphonenumber-js": { + "version": "1.11.17", + "resolved": "https://registry.npmjs.org/libphonenumber-js/-/libphonenumber-js-1.11.17.tgz", + "integrity": "sha512-Jr6v8thd5qRlOlc6CslSTzGzzQW03uiscab7KHQZX1Dfo4R6n6FDhZ0Hri6/X7edLIDv9gl4VMZXhxTjLnl0VQ==", + "license": "MIT" + }, "node_modules/lines-and-columns": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", @@ -6739,6 +6908,12 @@ "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", "license": "MIT" }, + "node_modules/lodash.clonedeep": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", + "integrity": "sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==", + "license": "MIT" + }, "node_modules/lodash.includes": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", @@ -7870,6 +8045,15 @@ "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", "license": "MIT" }, + "node_modules/promise-coalesce": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/promise-coalesce/-/promise-coalesce-1.1.2.tgz", + "integrity": "sha512-zLaJ9b8hnC564fnJH6NFSOGZYYdzrAJn2JUUIwzoQb32fG2QAakpDNM+CZo1km6keXkRXRM+hml1BFAPVnPkxg==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=16" + } + }, "node_modules/prompts": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", @@ -8048,6 +8232,23 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/redis": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/redis/-/redis-4.7.0.tgz", + "integrity": "sha512-zvmkHEAdGMn+hMRXuMBtu4Vo5P6rHQjLoHftu+lBqq8ZTA3RCVC/WzD790bkKKiNFp7d5/9PcSD19fJyyRvOdQ==", + "license": "MIT", + "workspaces": [ + "./packages/*" + ], + "dependencies": { + "@redis/bloom": "1.2.0", + "@redis/client": "1.6.0", + "@redis/graph": "1.1.1", + "@redis/json": "1.0.7", + "@redis/search": "1.2.0", + "@redis/time-series": "1.1.0" + } + }, "node_modules/reflect-metadata": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.2.2.tgz", diff --git a/package.json b/package.json index 7ba8e07..f079bec 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,7 @@ "test:e2e": "jest --config ./test/jest-e2e.json" }, "dependencies": { + "@nestjs/cache-manager": "^2.3.0", "@nestjs/common": "^10.0.0", "@nestjs/config": "^3.3.0", "@nestjs/core": "^10.0.0", @@ -28,6 +29,9 @@ "@nestjs/passport": "^10.0.3", "@nestjs/platform-express": "^10.0.0", "argon2": "^0.41.1", + "cache-manager-redis-yet": "^5.1.5", + "class-transformer": "^0.5.1", + "class-validator": "^0.14.1", "dotenv": "^16.4.7", "lodash": "^4.17.21", "passport": "^0.7.0", diff --git a/src/app.module.ts b/src/app.module.ts index 9c08b73..0c97fc9 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -12,6 +12,7 @@ import { WalletsModule } from './modules/wallets/wallets.module'; import { WalletsTransactionsModule } from './modules/wallets-transactions/wallets-transactions.module'; import { ReceiptsModule } from './modules/receipts/receipts.module'; import { ShoppingCardsModule } from './modules/shopping-cards/shopping-cards.module'; +import { CacheModule } from '@nestjs/cache-manager'; @Module({ imports: [ diff --git a/src/core/constants/index.ts b/src/core/constants/index.ts index 977a3cd..9fbf9f5 100644 --- a/src/core/constants/index.ts +++ b/src/core/constants/index.ts @@ -9,3 +9,4 @@ export const ORDER_STATUSES_REPOSITORY = 'ORDER_STATUSES_REPOSITORY'; export const WALLET_REPOSITORY = 'WALLET_REPOSITORY'; export const WALLET_TRANSACTIONS_REPOSITORY = 'WALLET_TRANSACTIONS_REPOSITORY'; export const RECEIPT_REPOSITORY = 'RECEIPT_REPOSITORY'; +export const SHOPPING_CARD_REPOSITORY = 'SHOPPING_CARD_REPOSITORY'; diff --git a/src/modules/receipts/dto/create-receipt.dto.ts b/src/modules/receipts/dto/create-receipt.dto.ts new file mode 100644 index 0000000..7fed6ec --- /dev/null +++ b/src/modules/receipts/dto/create-receipt.dto.ts @@ -0,0 +1,7 @@ +import { Product } from 'src/modules/products/entities/product.entity'; + +export class CreateReceiptDto { + userId: number; + totalPrice: number; + products: Product[]; +} diff --git a/src/modules/receipts/dto/index.ts b/src/modules/receipts/dto/index.ts new file mode 100644 index 0000000..f593658 --- /dev/null +++ b/src/modules/receipts/dto/index.ts @@ -0,0 +1,2 @@ +export * from './create-receipt.dto'; +export * from './update-receipt.dto'; diff --git a/src/modules/receipts/dto/update-receipt.dto.ts b/src/modules/receipts/dto/update-receipt.dto.ts new file mode 100644 index 0000000..023adbb --- /dev/null +++ b/src/modules/receipts/dto/update-receipt.dto.ts @@ -0,0 +1,4 @@ +import { PartialType } from '@nestjs/mapped-types'; +import { CreateReceiptDto } from './create-receipt.dto'; + +export class UpdateReceiptDto extends PartialType(CreateReceiptDto) {} diff --git a/src/modules/receipts/entities/receipt.entity.ts b/src/modules/receipts/entities/receipt.entity.ts new file mode 100644 index 0000000..163334c --- /dev/null +++ b/src/modules/receipts/entities/receipt.entity.ts @@ -0,0 +1,32 @@ +import { + Table, + Column, + Model, + DataType, + ForeignKey, + BelongsTo, +} from 'sequelize-typescript'; +import { Product } from 'src/modules/products/entities/product.entity'; +import { User } from 'src/modules/users/entities/user.entity'; + +@Table({ tableName: 'receipts', updatedAt: false }) +export class Receipt extends Model { + @Column({ + type: DataType.FLOAT, + allowNull: false, + }) + totalPrice: number; + + @Column({ + type: DataType.ARRAY(DataType.JSON), + allowNull: false, + }) + products: Product[]; + + @ForeignKey(() => User) + @Column + userId: number; + + @BelongsTo(() => User) + user: User; +} diff --git a/src/modules/receipts/receipts.controller.ts b/src/modules/receipts/receipts.controller.ts new file mode 100644 index 0000000..f00b327 --- /dev/null +++ b/src/modules/receipts/receipts.controller.ts @@ -0,0 +1,37 @@ +import { + Controller, + Get, + Post, + Body, + Patch, + Param, + Delete, +} from '@nestjs/common'; +import { ReceiptsService } from './receipts.service'; +import { CreateReceiptDto } from './dto/create-receipt.dto'; +import { UpdateReceiptDto } from './dto/update-receipt.dto'; + +@Controller('receipts') +export class ReceiptsController { + constructor(private readonly receiptsService: ReceiptsService) {} + + @Post() + create(@Body() createReceiptDto: CreateReceiptDto) { + return this.receiptsService.create(createReceiptDto); + } + + @Get() + findAll() { + return this.receiptsService.findAll(); + } + + @Get('users/:id') + findByUser(@Param('id') id: string) { + return this.receiptsService.findByUser(+id); + } + + @Delete(':id') + remove(@Param('id') id: string) { + return this.receiptsService.remove(+id); + } +} diff --git a/src/modules/receipts/receipts.module.ts b/src/modules/receipts/receipts.module.ts new file mode 100644 index 0000000..c302bb4 --- /dev/null +++ b/src/modules/receipts/receipts.module.ts @@ -0,0 +1,11 @@ +import { Module } from '@nestjs/common'; +import { ReceiptsService } from './receipts.service'; +import { ReceiptsController } from './receipts.controller'; +import { receiptsProviders } from './receipts.providers'; + +@Module({ + controllers: [ReceiptsController], + providers: [ReceiptsService, ...receiptsProviders], + exports: [ReceiptsService], +}) +export class ReceiptsModule {} diff --git a/src/modules/receipts/receipts.providers.ts b/src/modules/receipts/receipts.providers.ts new file mode 100644 index 0000000..8c04407 --- /dev/null +++ b/src/modules/receipts/receipts.providers.ts @@ -0,0 +1,9 @@ +import { Receipt } from './entities/receipt.entity'; +import { RECEIPT_REPOSITORY } from '../../core/constants'; + +export const receiptsProviders = [ + { + provide: RECEIPT_REPOSITORY, + useValue: Receipt, + }, +]; diff --git a/src/modules/receipts/receipts.service.ts b/src/modules/receipts/receipts.service.ts new file mode 100644 index 0000000..444ba97 --- /dev/null +++ b/src/modules/receipts/receipts.service.ts @@ -0,0 +1,34 @@ +import { Inject, Injectable } from '@nestjs/common'; +import { CreateReceiptDto, UpdateReceiptDto } from './dto'; +import { RECEIPT_REPOSITORY } from 'src/core/constants'; +import { Receipt } from './entities/receipt.entity'; + +@Injectable() +export class ReceiptsService { + constructor( + @Inject(RECEIPT_REPOSITORY) + private readonly receiptRepository: typeof Receipt, + ) {} + + async create(createReceiptDto: CreateReceiptDto) { + return await this.receiptRepository.create(createReceiptDto); + } + + async findAll() { + return await this.receiptRepository.findAll(); + } + + async findByUser(id: number) { + return await this.receiptRepository.findAll({ where: { userId: id } }); + } + + async remove(id: number) { + const deletedReceipt = await this.receiptRepository.findAll({ + where: { id }, + }); + + await this.receiptRepository.destroy({ where: { id } }); + + return deletedReceipt; + } +}