diff --git a/src/inventory/dto/getInventoryItems.response.dto.ts b/src/inventory/dto/getInventoryItems.response.dto.ts new file mode 100644 index 0000000..4e01e3e --- /dev/null +++ b/src/inventory/dto/getInventoryItems.response.dto.ts @@ -0,0 +1,42 @@ +import { ApiProperty } from '@nestjs/swagger'; + +export class itemDto { + @ApiProperty({ + name: 'itemId', + description: '아이템 id', + example: 1, + }) + itemId: number; + + @ApiProperty({ + name: 'isUsed', + description: '아이템의 사용여부', + example: false, + }) + isUsed: boolean; + + @ApiProperty({ + name: 'name', + description: '아이템 이름', + example: '감자칩', + }) + name: string; + + @ApiProperty({ + name: 'price', + description: '아이템의 가격', + example: 1000, + }) + price: number; + + @ApiProperty({ + name: 'comment', + description: '아이템 설명', + example: '짭잘하고 바삭한 맛있는 감자칩이다', + }) + comment: string; +} + +export class getInventoryItemsResponseDto { + items: itemDto[]; +} diff --git a/src/inventory/inventory.controller.ts b/src/inventory/inventory.controller.ts new file mode 100644 index 0000000..f56c98b --- /dev/null +++ b/src/inventory/inventory.controller.ts @@ -0,0 +1,26 @@ +import { Controller, Get, Req, UseGuards } from '@nestjs/common'; +import { ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger'; +import { InventoryService } from './inventory.service'; +import { JwtAuthGuard } from 'src/auth/jwt-auth.guard'; +import { getInventoryItemsResponseDto } from './dto/getInventoryItems.response.dto'; + +@ApiTags('inventory') +@Controller('api/inventory') +export class InventoryController { + constructor(private inventorySercie: InventoryService) {} + + @UseGuards(JwtAuthGuard) + @Get('/') + @ApiOperation({ + summary: '인벤토리 아이템 가져오기', + description: '인벤토리에 있는 모든 사용자 아이템을 가져옵니다.', + }) + @ApiResponse({ + status: 200, + description: '성공', + type: getInventoryItemsResponseDto, + }) + async getInventoryItems(@Req() req) { + return await this.inventorySercie.getInventoryItems(req.userId); + } +} diff --git a/src/inventory/inventory.module.ts b/src/inventory/inventory.module.ts index 9608e4d..cbfb6d4 100644 --- a/src/inventory/inventory.module.ts +++ b/src/inventory/inventory.module.ts @@ -1,4 +1,13 @@ import { Module } from '@nestjs/common'; +import { TypeOrmModule } from '@nestjs/typeorm'; +import { Users } from 'src/entities/user'; +import { InventoryService } from './inventory.service'; +import { InventoryController } from './inventory.controller'; +import { Inventory } from 'src/entities/inventory'; -@Module({}) +@Module({ + imports: [TypeOrmModule.forFeature([Users, Inventory])], + controllers: [InventoryController, InventoryService], + providers: [InventoryService], +}) export class InventoryModule {} diff --git a/src/inventory/inventory.service.ts b/src/inventory/inventory.service.ts new file mode 100644 index 0000000..d52c332 --- /dev/null +++ b/src/inventory/inventory.service.ts @@ -0,0 +1,40 @@ +import { Injectable } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { Inventory } from 'src/entities/inventory'; + +import { DataSource, Repository } from 'typeorm'; +import { itemDto } from './dto/getInventoryItems.response.dto'; + +@Injectable() +export class InventoryService { + constructor( + private dataSource: DataSource, + @InjectRepository(Inventory) + private inventoryRepository: Repository, + ) {} + + async getInventoryItems(userId: number) { + const inventoryItems = await this.inventoryRepository.find({ + where: { itemOwnerId: userId }, + relations: ['ItemId'], + }); + + const result: itemDto[] = []; + + inventoryItems.forEach((e) => { + const item: itemDto = { + itemId: e.itemId, + isUsed: e.isUsed, + name: e.ItemId.name, + price: e.ItemId.price, + comment: e.ItemId.comment, + }; + + result.push(item); + }); + + return { + items: result, + }; + } +} diff --git a/src/store/dto/buyItem.request.dto.ts b/src/store/dto/buyItem.request.dto.ts new file mode 100644 index 0000000..dcf9da4 --- /dev/null +++ b/src/store/dto/buyItem.request.dto.ts @@ -0,0 +1,13 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsNotEmpty, IsNumber } from 'class-validator'; + +export class buyItemRequestDto { + @IsNumber() + @IsNotEmpty() + @ApiProperty({ + name: 'itemId', + description: '아이템 아이디', + example: 1, + }) + itemId: number; +} diff --git a/src/store/store.controller.spec.ts b/src/store/store.controller.spec.ts new file mode 100644 index 0000000..cb5a344 --- /dev/null +++ b/src/store/store.controller.spec.ts @@ -0,0 +1,18 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { StoreController } from './store.controller'; + +describe('StoreController', () => { + let controller: StoreController; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + controllers: [StoreController], + }).compile(); + + controller = module.get(StoreController); + }); + + it('should be defined', () => { + expect(controller).toBeDefined(); + }); +}); diff --git a/src/store/store.controller.ts b/src/store/store.controller.ts new file mode 100644 index 0000000..f5e5f12 --- /dev/null +++ b/src/store/store.controller.ts @@ -0,0 +1,30 @@ +import { Body, Controller, Post, Req, UseGuards } from '@nestjs/common'; +import { + ApiBody, + ApiHeader, + ApiOperation, + ApiResponse, + ApiTags, +} from '@nestjs/swagger'; +import { JwtAuthGuard } from 'src/auth/jwt-auth.guard'; +import { buyItemRequestDto } from './dto/buyItem.request.dto'; +import { StoreService } from './store.service'; + +@ApiTags('store') +@ApiHeader({ name: 'access', description: 'access token' }) +@Controller('api/store') +export class StoreController { + constructor(private storeService: StoreService) {} + + @UseGuards(JwtAuthGuard) + @Post('/') + @ApiOperation({ summary: '상점에서 아이템 구매' }) + @ApiBody({ type: buyItemRequestDto }) + @ApiResponse({ + status: 200, + description: '성공', + }) + async buyItem(@Req() req, @Body() data: buyItemRequestDto) { + await this.storeService.buyItem(req.userId, data.itemId); + } +} diff --git a/src/store/store.module.ts b/src/store/store.module.ts index 11f13e7..d45f934 100644 --- a/src/store/store.module.ts +++ b/src/store/store.module.ts @@ -1,4 +1,9 @@ import { Module } from '@nestjs/common'; +import { StoreController } from './store.controller'; +import { StoreService } from './store.service'; -@Module({}) +@Module({ + controllers: [StoreController], + providers: [StoreService] +}) export class StoreModule {} diff --git a/src/store/store.service.spec.ts b/src/store/store.service.spec.ts new file mode 100644 index 0000000..1aeff7d --- /dev/null +++ b/src/store/store.service.spec.ts @@ -0,0 +1,18 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { StoreService } from './store.service'; + +describe('StoreService', () => { + let service: StoreService; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + providers: [StoreService], + }).compile(); + + service = module.get(StoreService); + }); + + it('should be defined', () => { + expect(service).toBeDefined(); + }); +}); diff --git a/src/store/store.service.ts b/src/store/store.service.ts new file mode 100644 index 0000000..acd3621 --- /dev/null +++ b/src/store/store.service.ts @@ -0,0 +1,60 @@ +import { BadRequestException, Injectable } from '@nestjs/common'; +import { DataSource, Repository } from 'typeorm'; +import { InjectRepository } from '@nestjs/typeorm'; +import { Inventory } from 'src/entities/inventory'; +import { Users } from 'src/entities/user'; +import { Item } from 'src/entities/item'; + +@Injectable() +export class StoreService { + constructor( + private dataSource: DataSource, + @InjectRepository(Users) private usersRepository: Repository, + @InjectRepository(Inventory) + private inventoryRepository: Repository, + @InjectRepository(Item) + private itemRepository: Repository, + ) {} + + async buyItem(userId: number, itemId: number) { + // 1. get item price + const item = await this.itemRepository.findOne({ + where: { id: itemId }, + }); + + if (item) { + throw new BadRequestException('요청하신 아이템은 존재하지 않습니다!'); + } + + // 2. check user budget + const user = await this.usersRepository.findOne({ + where: { id: userId }, + }); + + if (user) { + throw new BadRequestException('잘못된 요청입니다!'); + } + + if (user.cash < item.price) { + throw new BadRequestException('아이템을 구매할 수 없습니다!'); + } + + // 3. run transaction + // 3-1. insert info to user table + // 3-2. reduce budget from user + await this.dataSource.manager + .transaction(async (manager) => { + const inventory = new Inventory(); + inventory.itemOwnerId = userId; + inventory.itemId = itemId; + inventory.isUsed = false; + + user.cash -= item.price; + await manager.save(user); + await manager.save(inventory); + }) + .catch((e) => { + throw e; + }); + } +}