From 9fac46263ca3104b460825d095406e7592193d5e Mon Sep 17 00:00:00 2001 From: wooo73 Date: Mon, 6 Jan 2025 00:11:40 +0900 Subject: [PATCH 1/8] =?UTF-8?q?[chore]:=20class-validator,=20class-=20tran?= =?UTF-8?q?sformer=20=EC=84=A4=EC=B9=98=20=EB=B0=8F=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package-lock.json | 65 +++++++++++++++++++++++++++++++++++++++++++++++ package.json | 4 ++- src/main.ts | 3 +++ yarn.lock | 29 +++++++++++++++++++++ 4 files changed, 100 insertions(+), 1 deletion(-) diff --git a/package-lock.json b/package-lock.json index 8eeb993..52ecc3b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,6 +16,8 @@ "@nestjs/swagger": "^8.1.0", "@nestjs/typeorm": "^10.0.2", "@prisma/client": "^6.1.0", + "class-transformer": "^0.5.1", + "class-validator": "^0.14.1", "cross-env": "^7.0.3", "dotenv": "^16.4.7", "mysql2": "^3.9.2", @@ -2576,6 +2578,11 @@ "@types/superagent": "^8.1.0" } }, + "node_modules/@types/validator": { + "version": "13.12.2", + "resolved": "https://registry.npmjs.org/@types/validator/-/validator-13.12.2.tgz", + "integrity": "sha512-6SlHBzUW8Jhf3liqrGGXyTJSIFe4nqlJ5A5KaMZ2l/vbM3Wh3KSybots/wfWVzNLK4D1NZluDlSQIbIEPx6oyA==" + }, "node_modules/@types/yargs": { "version": "17.0.32", "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.32.tgz", @@ -3857,6 +3864,21 @@ "integrity": "sha512-0TNiGstbQmCFwt4akjjBg5pLRTSyj/PkWQ1ZoO2zntmg9yLqSRxwEa4iCfQLGjqhiqBfOJa7W/E8wfGrTDmlZQ==", "dev": true }, + "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==" + }, + "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==", + "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", @@ -7071,6 +7093,11 @@ "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==" + }, "node_modules/lines-and-columns": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", @@ -10012,6 +10039,14 @@ "node": ">=10.12.0" } }, + "node_modules/validator": { + "version": "13.12.0", + "resolved": "https://registry.npmjs.org/validator/-/validator-13.12.0.tgz", + "integrity": "sha512-c1Q0mCiPlgdTVVVIJIrBuxNicYE+t/7oKeI9MWLj3fh/uq2Pxh/3eeWbVZ4OcGW1TUf53At0njHw5SMdA3tmMg==", + "engines": { + "node": ">= 0.10" + } + }, "node_modules/vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", @@ -12357,6 +12392,11 @@ "@types/superagent": "^8.1.0" } }, + "@types/validator": { + "version": "13.12.2", + "resolved": "https://registry.npmjs.org/@types/validator/-/validator-13.12.2.tgz", + "integrity": "sha512-6SlHBzUW8Jhf3liqrGGXyTJSIFe4nqlJ5A5KaMZ2l/vbM3Wh3KSybots/wfWVzNLK4D1NZluDlSQIbIEPx6oyA==" + }, "@types/yargs": { "version": "17.0.32", "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.32.tgz", @@ -13317,6 +13357,21 @@ "integrity": "sha512-0TNiGstbQmCFwt4akjjBg5pLRTSyj/PkWQ1ZoO2zntmg9yLqSRxwEa4iCfQLGjqhiqBfOJa7W/E8wfGrTDmlZQ==", "dev": true }, + "class-transformer": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/class-transformer/-/class-transformer-0.5.1.tgz", + "integrity": "sha512-SQa1Ws6hUbfC98vKGxZH3KFY0Y1lm5Zm0SY8XX9zbK7FJCyVEac3ATW0RIpwzW+oOfmHE5PMPufDG9hCfoEOMw==" + }, + "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==", + "requires": { + "@types/validator": "^13.11.8", + "libphonenumber-js": "^1.10.53", + "validator": "^13.9.0" + } + }, "cli-cursor": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", @@ -15722,6 +15777,11 @@ "type-check": "~0.4.0" } }, + "libphonenumber-js": { + "version": "1.11.17", + "resolved": "https://registry.npmjs.org/libphonenumber-js/-/libphonenumber-js-1.11.17.tgz", + "integrity": "sha512-Jr6v8thd5qRlOlc6CslSTzGzzQW03uiscab7KHQZX1Dfo4R6n6FDhZ0Hri6/X7edLIDv9gl4VMZXhxTjLnl0VQ==" + }, "lines-and-columns": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", @@ -17829,6 +17889,11 @@ "convert-source-map": "^2.0.0" } }, + "validator": { + "version": "13.12.0", + "resolved": "https://registry.npmjs.org/validator/-/validator-13.12.0.tgz", + "integrity": "sha512-c1Q0mCiPlgdTVVVIJIrBuxNicYE+t/7oKeI9MWLj3fh/uq2Pxh/3eeWbVZ4OcGW1TUf53At0njHw5SMdA3tmMg==" + }, "vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", diff --git a/package.json b/package.json index b099b44..8874501 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,7 @@ "start:debug": "nest start --debug --watch", "start:prod": "node dist/main", "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix", - "test": "dotenv -e .env.test -- jest", + "test": "jest", "test:watch": "jest --watch", "test:cov": "jest --coverage", "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", @@ -33,6 +33,8 @@ "@nestjs/swagger": "^8.1.0", "@nestjs/typeorm": "^10.0.2", "@prisma/client": "^6.1.0", + "class-transformer": "^0.5.1", + "class-validator": "^0.14.1", "cross-env": "^7.0.3", "dotenv": "^16.4.7", "mysql2": "^3.9.2", diff --git a/src/main.ts b/src/main.ts index 21ef1ea..420313b 100644 --- a/src/main.ts +++ b/src/main.ts @@ -2,10 +2,13 @@ import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger'; import { PrismaService } from './database/prisma/prisma.service'; +import { ValidationPipe } from '@nestjs/common'; async function bootstrap() { const app = await NestFactory.create(AppModule); + app.useGlobalPipes(new ValidationPipe({ transform: true })); + const prismaService = app.get(PrismaService); await prismaService.enableShutdownHooks(); diff --git a/yarn.lock b/yarn.lock index d7b4310..c8e663a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1222,6 +1222,11 @@ "@types/methods" "^1.1.4" "@types/superagent" "^8.1.0" +"@types/validator@^13.11.8": + version "13.12.2" + resolved "https://registry.npmjs.org/@types/validator/-/validator-13.12.2.tgz" + integrity sha512-6SlHBzUW8Jhf3liqrGGXyTJSIFe4nqlJ5A5KaMZ2l/vbM3Wh3KSybots/wfWVzNLK4D1NZluDlSQIbIEPx6oyA== + "@types/yargs-parser@*": version "21.0.3" resolved "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz" @@ -2054,6 +2059,20 @@ cjs-module-lexer@^1.0.0: resolved "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.2.3.tgz" integrity sha512-0TNiGstbQmCFwt4akjjBg5pLRTSyj/PkWQ1ZoO2zntmg9yLqSRxwEa4iCfQLGjqhiqBfOJa7W/E8wfGrTDmlZQ== +class-transformer@*, "class-transformer@^0.4.0 || ^0.5.0", class-transformer@^0.5.1: + version "0.5.1" + resolved "https://registry.npmjs.org/class-transformer/-/class-transformer-0.5.1.tgz" + integrity sha512-SQa1Ws6hUbfC98vKGxZH3KFY0Y1lm5Zm0SY8XX9zbK7FJCyVEac3ATW0RIpwzW+oOfmHE5PMPufDG9hCfoEOMw== + +class-validator@*, "class-validator@^0.13.0 || ^0.14.0", class-validator@^0.14.1: + version "0.14.1" + resolved "https://registry.npmjs.org/class-validator/-/class-validator-0.14.1.tgz" + integrity sha512-2VEG9JICxIqTpoK1eMzZqaV+u/EiwEJkMGzTrZf6sU/fwsnOITVgYJ8yojSy6CaXtO9V0Cc6ZQZ8h8m4UBuLwQ== + dependencies: + "@types/validator" "^13.11.8" + libphonenumber-js "^1.10.53" + validator "^13.9.0" + cli-cursor@^3.1.0: version "3.1.0" resolved "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz" @@ -4002,6 +4021,11 @@ levn@^0.4.1: prelude-ls "^1.2.1" type-check "~0.4.0" +libphonenumber-js@^1.10.53: + version "1.11.17" + resolved "https://registry.npmjs.org/libphonenumber-js/-/libphonenumber-js-1.11.17.tgz" + integrity sha512-Jr6v8thd5qRlOlc6CslSTzGzzQW03uiscab7KHQZX1Dfo4R6n6FDhZ0Hri6/X7edLIDv9gl4VMZXhxTjLnl0VQ== + lines-and-columns@^1.1.6: version "1.2.4" resolved "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz" @@ -5754,6 +5778,11 @@ v8-to-istanbul@^9.0.1: "@types/istanbul-lib-coverage" "^2.0.1" convert-source-map "^2.0.0" +validator@^13.9.0: + version "13.12.0" + resolved "https://registry.npmjs.org/validator/-/validator-13.12.0.tgz" + integrity sha512-c1Q0mCiPlgdTVVVIJIrBuxNicYE+t/7oKeI9MWLj3fh/uq2Pxh/3eeWbVZ4OcGW1TUf53At0njHw5SMdA3tmMg== + vary@^1, vary@~1.1.2: version "1.1.2" resolved "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz" From 0d4ac749f75ee992aacac9d1c67757dc1f0227b4 Mon Sep 17 00:00:00 2001 From: wooo73 Date: Mon, 6 Jan 2025 00:14:43 +0900 Subject: [PATCH 2/8] =?UTF-8?q?[docs]:=20swagger=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - user controller swagger 명세 분리 및 데코레이터 적용 --- .../presentation/dto/user.response.dto.ts | 4 --- src/user/presentation/user.controller.ts | 36 +++++-------------- src/user/swagger/user.swagger.ts | 31 ++++++++++++++++ 3 files changed, 40 insertions(+), 31 deletions(-) create mode 100644 src/user/swagger/user.swagger.ts diff --git a/src/user/presentation/dto/user.response.dto.ts b/src/user/presentation/dto/user.response.dto.ts index b400c77..e69de29 100644 --- a/src/user/presentation/dto/user.response.dto.ts +++ b/src/user/presentation/dto/user.response.dto.ts @@ -1,4 +0,0 @@ -import { OmitType } from '@nestjs/swagger'; -import { User } from '../../domain/user'; - -export class UserBalanceResponseDto extends OmitType(User, ['createdAt', 'updatedAt'] as const) {} diff --git a/src/user/presentation/user.controller.ts b/src/user/presentation/user.controller.ts index f3c8dcc..8ba190e 100644 --- a/src/user/presentation/user.controller.ts +++ b/src/user/presentation/user.controller.ts @@ -1,15 +1,8 @@ import { Body, Controller, Get, Param, Post } from '@nestjs/common'; import { UserService } from '../application/user.service'; -import { - ApiBody, - ApiNotFoundResponse, - ApiOkResponse, - ApiOperation, - ApiParam, - ApiTags, -} from '@nestjs/swagger'; +import { ApiTags } from '@nestjs/swagger'; import { UserChargePointRequestDto } from './dto/user.request.dto'; -import { UserBalanceResponseDto } from './dto/user.response.dto'; +import { GetUserBalanceSwagger, ChargeUserBalanceSwagger } from '../swagger/user.swagger'; @Controller('user') @ApiTags('User') @@ -17,28 +10,17 @@ export class UserController { constructor(private readonly userService: UserService) {} @Get(':userId/balance') - @ApiOperation({ summary: '잔액 조회', description: '사용자의 잔액을 조회합니다.' }) - @ApiParam({ type: String, name: 'userId', description: '사용자 ID' }) - @ApiOkResponse({ - type: UserBalanceResponseDto, - }) - @ApiNotFoundResponse({ description: '사용자를 찾을 수 없습니다.' }) - async getUserBalance(@Param('userId') userId: string) { - return { userId: 1, balance: 10_000 }; + @GetUserBalanceSwagger() + async getUserBalance(@Param('userId') userId: number) { + return await this.userService.getUserBalance(userId); } @Post(':userId/balance/charge') - @ApiOperation({ summary: '잔액 충전', description: '사용자의 잔액을 충전합니다.' }) - @ApiParam({ type: String, name: 'userId', description: '사용자 ID' }) - @ApiBody({ type: UserChargePointRequestDto }) - @ApiOkResponse({ - type: UserBalanceResponseDto, - }) - @ApiNotFoundResponse({ description: '사용자를 찾을 수 없습니다.' }) + @ChargeUserBalanceSwagger() async chargePoint( - @Param('userId') userId: string, - @Body() UserChargePointRequestDto: UserChargePointRequestDto, + @Param('userId') userId: number, + @Body() userChargePointRequestDto: UserChargePointRequestDto, ) { - return { id: 1, balance: 15_000 }; + return await this.userService.chargeUserBalance(userId, userChargePointRequestDto); } } diff --git a/src/user/swagger/user.swagger.ts b/src/user/swagger/user.swagger.ts new file mode 100644 index 0000000..bd1704e --- /dev/null +++ b/src/user/swagger/user.swagger.ts @@ -0,0 +1,31 @@ +import { applyDecorators } from '@nestjs/common'; +import { + ApiOperation, + ApiParam, + ApiOkResponse, + ApiNotFoundResponse, + ApiBody, +} from '@nestjs/swagger'; +import { UserChargePointRequestDto } from '../presentation/dto/user.request.dto'; +import { User } from '../domain/user'; + +export function GetUserBalanceSwagger() { + return applyDecorators( + ApiOperation({ summary: '잔액 조회', description: '사용자의 잔액을 조회합니다.' }), + ApiParam({ type: String, name: 'userId', description: '사용자 ID' }), + ApiOkResponse({ type: User }), + ApiNotFoundResponse({ description: '사용자를 찾을 수 없습니다.' }), + ); +} + +export function ChargeUserBalanceSwagger() { + return applyDecorators( + ApiOperation({ summary: '잔액 충전', description: '사용자의 잔액을 충전합니다.' }), + ApiParam({ type: String, name: 'userId', description: '사용자 ID' }), + ApiBody({ type: UserChargePointRequestDto }), + ApiOkResponse({ + type: User, + }), + ApiNotFoundResponse({ description: '사용자를 찾을 수 없습니다.' }), + ); +} From 9a489036cff246be76b71e8b3aaeda01974f1512 Mon Sep 17 00:00:00 2001 From: wooo73 Date: Mon, 6 Jan 2025 02:46:32 +0900 Subject: [PATCH 3/8] =?UTF-8?q?[test]:=20Testcontainer=20prisma=20?= =?UTF-8?q?=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - table 생성 및 seed 생성 prisma 적용 --- test/it/down.ts | 4 ++-- test/it/import.sql | 3 ++- test/it/setup.ts | 37 ++++++++++++++++++++++--------------- test/it/util.ts | 24 ++++++++++++++++++++++++ 4 files changed, 50 insertions(+), 18 deletions(-) diff --git a/test/it/down.ts b/test/it/down.ts index 210b1ba..6d99b3d 100644 --- a/test/it/down.ts +++ b/test/it/down.ts @@ -1,8 +1,8 @@ -import { getDatasource } from './util'; +import { getPrismaClient } from './util'; const down = async () => { await global.mysql.stop(); - await (await getDatasource()).destroy(); + await (await getPrismaClient()).$disconnect(); }; export default down; diff --git a/test/it/import.sql b/test/it/import.sql index e0ac49d..e2729de 100644 --- a/test/it/import.sql +++ b/test/it/import.sql @@ -1 +1,2 @@ -SELECT 1; +INSERT INTO user (id, balance, updated_at) +VALUES(1, 1000, NOW()); diff --git a/test/it/setup.ts b/test/it/setup.ts index 11ef814..7f4870b 100644 --- a/test/it/setup.ts +++ b/test/it/setup.ts @@ -1,7 +1,8 @@ -import { DataSource } from 'typeorm'; import * as fs from 'fs'; import { MySqlContainer } from '@testcontainers/mysql'; -import { getDatasource } from './util'; +import { getPrismaClient } from './util'; +import { PrismaClient } from '@prisma/client'; +import { execSync } from 'child_process'; const init = async () => { await Promise.all([initMysql()]); @@ -9,29 +10,35 @@ const init = async () => { const initMysql = async () => { const mysql = await new MySqlContainer('mysql:8') - .withDatabase('dbname') + .withDatabase('testDB') .withUser('root') - .withRootPassword('pw') + .withRootPassword('test1234') .start(); global.mysql = mysql; - process.env.DB_HOST = mysql.getHost(); - process.env.DB_PORT = mysql.getPort().toString(); - process.env.DB_USERNAME = mysql.getUsername(); - process.env.DB_PASSWORD = mysql.getUserPassword(); - process.env.DB_DATABASE = mysql.getDatabase(); - process.env.DB_LOGGING_ENABLED = 'true'; + process.env.DATABASE_URL = `mysql://${mysql.getUsername()}:${mysql.getUserPassword()}@${mysql.getHost()}:${mysql.getPort()}/${mysql.getDatabase()}`; - const datasource = await getDatasource(); - await datasource.runMigrations(); - await insertTestData(datasource); + try { + // schema.prisma 기반으로 테이블 생성 + execSync('npx prisma db push --force-reset', { + stdio: 'inherit', + env: { ...process.env }, + }); + } catch (error) { + console.error('Migration failed:', error); + throw error; + } + + const prisma = await getPrismaClient(); + + await insertTestData(prisma); }; -const insertTestData = async (datasource: DataSource) => { +const insertTestData = async (prisma: PrismaClient) => { const importSql = fs.readFileSync('./test/it/import.sql').toString(); for (const sql of importSql.split(';').filter((s) => s.trim() !== '')) { - await datasource.query(sql); + await prisma.$executeRawUnsafe(sql); } }; diff --git a/test/it/util.ts b/test/it/util.ts index 3ba8770..c5226ec 100644 --- a/test/it/util.ts +++ b/test/it/util.ts @@ -1,5 +1,6 @@ import { DataSource } from 'typeorm'; import * as process from 'process'; +import { PrismaClient } from '@prisma/client'; let datasource: DataSource; @@ -22,3 +23,26 @@ export const getDatasource = async () => { await datasource.initialize(); return datasource; }; + +let prismaClient: PrismaClient; + +export const getPrismaClient = async () => { + if (prismaClient) { + return prismaClient; + } + + prismaClient = new PrismaClient({ + datasources: { + db: { + url: process.env.DATABASE_URL, + }, + }, + }); + try { + await prismaClient.$connect(); + return prismaClient; + } catch (error) { + console.error('Failed to connect to database:', error); + throw error; + } +}; From 6df6901b43cd83a234be301f3cb0428bd31e0625 Mon Sep 17 00:00:00 2001 From: wooo73 Date: Mon, 6 Jan 2025 02:49:36 +0900 Subject: [PATCH 4/8] =?UTF-8?q?[feat]:=20=EC=9E=94=EC=95=A1=20=EC=A1=B0?= =?UTF-8?q?=ED=9A=8C=20/=20=EC=9E=94=EC=95=A1=20=EC=B6=A9=EC=A0=84=20?= =?UTF-8?q?=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - controller, service, repository 구현 - 기능별 테스트 코드 작성 --- src/database/prisma/prisma.service.ts | 2 +- src/user/application/user.Irepository.ts | 1 - src/user/application/user.repository.ts | 12 ++++++ src/user/application/user.service.spec.ts | 37 +++++++++++++++++-- src/user/application/user.service.ts | 29 ++++++++++++++- src/user/domain/user.ts | 2 +- .../infrastructure/user.prisma.repository.ts | 24 ++++++++++++ src/user/infrastructure/user.repository.ts | 1 - src/user/presentation/dto/user.request.dto.ts | 3 ++ src/user/presentation/user.controller.spec.ts | 20 ---------- src/user/presentation/user.controller.ts | 2 +- src/user/user.module.ts | 6 ++- 12 files changed, 108 insertions(+), 31 deletions(-) delete mode 100644 src/user/application/user.Irepository.ts create mode 100644 src/user/application/user.repository.ts create mode 100644 src/user/infrastructure/user.prisma.repository.ts delete mode 100644 src/user/infrastructure/user.repository.ts delete mode 100644 src/user/presentation/user.controller.spec.ts diff --git a/src/database/prisma/prisma.service.ts b/src/database/prisma/prisma.service.ts index 75f14d1..27e2c12 100644 --- a/src/database/prisma/prisma.service.ts +++ b/src/database/prisma/prisma.service.ts @@ -23,7 +23,7 @@ export class PrismaService extends PrismaClient implements OnModuleInit { } prismaLog() { - if (this.configService.get('NODE_ENV') === 'dev') { + if (this.configService.get('NODE_ENV') !== 'prod') { (this.$on as any)('query', (e: Prisma.QueryEvent) => { this.logger.debug(`Query: ${e.query}`); this.logger.debug(`Duration: ${e.duration}ms`); diff --git a/src/user/application/user.Irepository.ts b/src/user/application/user.Irepository.ts deleted file mode 100644 index 55d2f3a..0000000 --- a/src/user/application/user.Irepository.ts +++ /dev/null @@ -1 +0,0 @@ -export interface IUserRepository {} diff --git a/src/user/application/user.repository.ts b/src/user/application/user.repository.ts new file mode 100644 index 0000000..b9a65b9 --- /dev/null +++ b/src/user/application/user.repository.ts @@ -0,0 +1,12 @@ +import { User } from '@prisma/client'; +import { UserChargePointRequestDto } from '../presentation/dto/user.request.dto'; + +export interface UserRepository { + findById(userId: number): Promise; + updateUserBalance( + userId: number, + userChargePointRequestDto: UserChargePointRequestDto, + ): Promise; +} + +export const USER_REPOSITORY = Symbol('userRepository'); diff --git a/src/user/application/user.service.spec.ts b/src/user/application/user.service.spec.ts index 9d2e9be..f560647 100644 --- a/src/user/application/user.service.spec.ts +++ b/src/user/application/user.service.spec.ts @@ -1,18 +1,49 @@ import { Test, TestingModule } from '@nestjs/testing'; -import { UserService } from '../user/service/user.service'; +import { UserService } from './user.service'; +import { USER_REPOSITORY } from './user.repository'; +import { UserPrismaRepository } from '../infrastructure/user.prisma.repository'; +import { NotFoundException } from '@nestjs/common'; + +jest.mock('../infrastructure/user.prisma.repository'); describe('UserService', () => { let service: UserService; - + let userRepository; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ - providers: [UserService], + providers: [UserService, { provide: USER_REPOSITORY, useClass: UserPrismaRepository }], }).compile(); service = module.get(UserService); + userRepository = module.get(USER_REPOSITORY); }); it('should be defined', () => { expect(service).toBeDefined(); }); + + it('FAIL_사용자 조회 실패시 "사용자를 찾을 수 없습니다." 라는 에러를 던져야 합니다.', async () => { + //given + const userId = 1; + userRepository.findById.mockResolvedValue(null); + + //when & then + await expect(service.getUserBalance(userId)).rejects.toThrow( + new NotFoundException('사용자를 찾을 수 없습니다.'), + ); + }); + + it('FAIL_사용자 잔액 충전에서 사용자 조회 실패시 "사용자를 찾을 수 없습니다." 라는 에러를 던져야 합니다.', async () => { + //given + const userId = 1; + const userChargePointRequestDto = { + amount: 1000, + }; + userRepository.findById.mockResolvedValue(null); + + //when & then + await expect(service.chargeUserBalance(userId, userChargePointRequestDto)).rejects.toThrow( + new NotFoundException('사용자를 찾을 수 없습니다.'), + ); + }); }); diff --git a/src/user/application/user.service.ts b/src/user/application/user.service.ts index 668a7d6..c6c7b73 100644 --- a/src/user/application/user.service.ts +++ b/src/user/application/user.service.ts @@ -1,4 +1,29 @@ -import { Injectable } from '@nestjs/common'; +import { Inject, Injectable, NotFoundException } from '@nestjs/common'; +import { USER_REPOSITORY, UserRepository } from './user.repository'; +import { UserChargePointRequestDto } from '../presentation/dto/user.request.dto'; +import { User } from '../domain/user'; @Injectable() -export class UserService {} +export class UserService { + constructor(@Inject(USER_REPOSITORY) private readonly userRepository: UserRepository) {} + + async getUserBalance(userId: number): Promise { + const user = await this.userRepository.findById(userId); + if (!user) { + throw new NotFoundException('사용자를 찾을 수 없습니다.'); + } + return user; + } + + async chargeUserBalance( + userId: number, + userChargePointRequestDto: UserChargePointRequestDto, + ): Promise { + try { + await this.getUserBalance(userId); + return await this.userRepository.updateUserBalance(userId, userChargePointRequestDto); + } catch (err) { + throw err; + } + } +} diff --git a/src/user/domain/user.ts b/src/user/domain/user.ts index 533773d..ac5c36f 100644 --- a/src/user/domain/user.ts +++ b/src/user/domain/user.ts @@ -2,7 +2,7 @@ import { ApiProperty } from '@nestjs/swagger'; export class User { @ApiProperty({ example: '1', description: '사용자 ID' }) - id: string; + id: number; @ApiProperty({ example: 15_000, description: '사용자 잔액' }) balance: number; diff --git a/src/user/infrastructure/user.prisma.repository.ts b/src/user/infrastructure/user.prisma.repository.ts new file mode 100644 index 0000000..cf13f8f --- /dev/null +++ b/src/user/infrastructure/user.prisma.repository.ts @@ -0,0 +1,24 @@ +import { Injectable } from '@nestjs/common'; +import { PrismaService } from '../../database/prisma/prisma.service'; +import { UserRepository } from '../application/user.repository'; +import { User } from '../domain/user'; +import { UserChargePointRequestDto } from '../presentation/dto/user.request.dto'; + +@Injectable() +export class UserPrismaRepository implements UserRepository { + constructor(private readonly prisma: PrismaService) {} + + async findById(userId: number): Promise { + return await this.prisma.user.findUnique({ where: { id: userId } }); + } + + async updateUserBalance( + userId: number, + userChargePointRequestDto: UserChargePointRequestDto, + ): Promise { + return await this.prisma.user.update({ + where: { id: userId }, + data: { balance: { increment: userChargePointRequestDto.amount } }, + }); + } +} diff --git a/src/user/infrastructure/user.repository.ts b/src/user/infrastructure/user.repository.ts deleted file mode 100644 index 9e2239b..0000000 --- a/src/user/infrastructure/user.repository.ts +++ /dev/null @@ -1 +0,0 @@ -export class UserRepository {} diff --git a/src/user/presentation/dto/user.request.dto.ts b/src/user/presentation/dto/user.request.dto.ts index 67eced6..767fcc7 100644 --- a/src/user/presentation/dto/user.request.dto.ts +++ b/src/user/presentation/dto/user.request.dto.ts @@ -1,6 +1,9 @@ import { ApiProperty } from '@nestjs/swagger'; +import { IsNotEmpty, IsNumber } from 'class-validator'; export class UserChargePointRequestDto { @ApiProperty({ example: 5_000, description: '충전 금액' }) + @IsNumber() + @IsNotEmpty() amount: number; } diff --git a/src/user/presentation/user.controller.spec.ts b/src/user/presentation/user.controller.spec.ts deleted file mode 100644 index e536cca..0000000 --- a/src/user/presentation/user.controller.spec.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { Test, TestingModule } from '@nestjs/testing'; -import { UserController } from './user.controller'; -import { UserService } from '../application/user.service'; - -describe('UserController', () => { - let controller: UserController; - - beforeEach(async () => { - const module: TestingModule = await Test.createTestingModule({ - controllers: [UserController], - providers: [UserService], - }).compile(); - - controller = module.get(UserController); - }); - - it('should be defined', () => { - expect(controller).toBeDefined(); - }); -}); diff --git a/src/user/presentation/user.controller.ts b/src/user/presentation/user.controller.ts index 8ba190e..fa3854c 100644 --- a/src/user/presentation/user.controller.ts +++ b/src/user/presentation/user.controller.ts @@ -17,7 +17,7 @@ export class UserController { @Post(':userId/balance/charge') @ChargeUserBalanceSwagger() - async chargePoint( + async chargeBalance( @Param('userId') userId: number, @Body() userChargePointRequestDto: UserChargePointRequestDto, ) { diff --git a/src/user/user.module.ts b/src/user/user.module.ts index a849b35..b8250de 100644 --- a/src/user/user.module.ts +++ b/src/user/user.module.ts @@ -1,9 +1,13 @@ import { Module } from '@nestjs/common'; import { UserService } from './application/user.service'; import { UserController } from './presentation/user.controller'; +import { USER_REPOSITORY } from './application/user.repository'; +import { UserPrismaRepository } from './infrastructure/user.prisma.repository'; +import { PrismaModule } from 'src/database/prisma/prisma.module'; @Module({ + imports: [PrismaModule], controllers: [UserController], - providers: [UserService], + providers: [UserService, { provide: USER_REPOSITORY, useClass: UserPrismaRepository }], }) export class UserModule {} From 9233509c6991198c972e4fc492aee9f2e2cc17ff Mon Sep 17 00:00:00 2001 From: wooo73 Date: Mon, 6 Jan 2025 19:33:10 +0900 Subject: [PATCH 5/8] =?UTF-8?q?[fix]:=20=EC=83=81=ED=92=88=20<->=20?= =?UTF-8?q?=EC=A3=BC=EB=AC=B8=20=EC=83=81=ED=92=88=20=EA=B4=80=EA=B3=84=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 상품 주문 가능 상태값 추가 --- .../migration.sql | 2 +- prisma/schema/order-item.prisma | 2 +- prisma/schema/product.prisma | 3 ++- 3 files changed, 4 insertions(+), 3 deletions(-) rename prisma/migrations/{20250104170541_init => 20250106102002_initial}/migration.sql (98%) diff --git a/prisma/migrations/20250104170541_init/migration.sql b/prisma/migrations/20250106102002_initial/migration.sql similarity index 98% rename from prisma/migrations/20250104170541_init/migration.sql rename to prisma/migrations/20250106102002_initial/migration.sql index d8ae11d..ce32de2 100644 --- a/prisma/migrations/20250104170541_init/migration.sql +++ b/prisma/migrations/20250106102002_initial/migration.sql @@ -34,7 +34,6 @@ CREATE TABLE `order_item` ( `created_at` TIMESTAMP(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0), `updated_at` TIMESTAMP(0) NOT NULL, - UNIQUE INDEX `order_item_product_id_key`(`product_id`), PRIMARY KEY (`id`) ) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; @@ -70,6 +69,7 @@ CREATE TABLE `product` ( `id` INTEGER NOT NULL AUTO_INCREMENT, `name` VARCHAR(50) NOT NULL, `price` INTEGER NOT NULL, + `status` BOOLEAN NOT NULL DEFAULT true, `created_at` TIMESTAMP(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0), `updated_at` TIMESTAMP(0) NOT NULL, diff --git a/prisma/schema/order-item.prisma b/prisma/schema/order-item.prisma index 6165e92..4c91796 100644 --- a/prisma/schema/order-item.prisma +++ b/prisma/schema/order-item.prisma @@ -3,7 +3,7 @@ model OrderItem { order Order @relation(fields: [orderId], references: [id]) orderId Int @map("order_id") product Product @relation(fields: [productId], references: [id]) - productId Int @unique @map("product_id") + productId Int @map("product_id") quantity Int price Int createdAt DateTime @default(now()) @map("created_at") @db.Timestamp(0) diff --git a/prisma/schema/product.prisma b/prisma/schema/product.prisma index 70b18a2..9678ef6 100644 --- a/prisma/schema/product.prisma +++ b/prisma/schema/product.prisma @@ -2,9 +2,10 @@ model Product { id Int @id @default(autoincrement()) name String @db.VarChar(50) price Int @db.Int + status Boolean @default(true) createdAt DateTime @default(now()) @map("created_at") @db.Timestamp(0) updatedAt DateTime @updatedAt @map("updated_at") @db.Timestamp(0) - orderItem OrderItem? + orderItem OrderItem[] ProductQuantity ProductQuantity? @@map("product") From b05a2051c067bfd0692be9f8d20387fd671cac66 Mon Sep 17 00:00:00 2001 From: wooo73 Date: Mon, 6 Jan 2025 19:33:29 +0900 Subject: [PATCH 6/8] =?UTF-8?q?[feat]:=20=EC=83=81=ED=92=88=20=EB=AA=A9?= =?UTF-8?q?=EB=A1=9D=20=EC=A1=B0=ED=9A=8C=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - swagger 분리 - controller, service, repository 구현 --- .../application/product.Irepository.ts | 1 - src/product/application/product.repository.ts | 8 ++++ src/product/application/product.service.ts | 16 +++++++- src/product/domain/product.ts | 4 ++ .../infrastructure/product.repository.ts | 1 - .../product.typeorm.repository.ts | 20 ++++++++++ .../presentation/dto/product.request.dto.ts | 21 +++++++++++ .../presentation/dto/product.response.dto.ts | 29 ++++++++++++--- .../presentation/product.controller.ts | 37 +++++-------------- src/product/product.module.ts | 7 +++- src/product/swagger/product.swagger.ts | 20 ++++++++++ 11 files changed, 126 insertions(+), 38 deletions(-) delete mode 100644 src/product/application/product.Irepository.ts create mode 100644 src/product/application/product.repository.ts delete mode 100644 src/product/infrastructure/product.repository.ts create mode 100644 src/product/infrastructure/product.typeorm.repository.ts create mode 100644 src/product/presentation/dto/product.request.dto.ts create mode 100644 src/product/swagger/product.swagger.ts diff --git a/src/product/application/product.Irepository.ts b/src/product/application/product.Irepository.ts deleted file mode 100644 index 458a4a1..0000000 --- a/src/product/application/product.Irepository.ts +++ /dev/null @@ -1 +0,0 @@ -export interface IProductRepository {} diff --git a/src/product/application/product.repository.ts b/src/product/application/product.repository.ts new file mode 100644 index 0000000..83dddc5 --- /dev/null +++ b/src/product/application/product.repository.ts @@ -0,0 +1,8 @@ +import { Product } from '../domain/product'; +import { GetProductsQueryDTO } from '../presentation/dto/product.request.dto'; + +export interface ProductRepository { + getProducts(query: GetProductsQueryDTO): Promise; +} + +export const PRODUCT_REPOSITORY = Symbol('PRODUCT_REPOSITORY'); diff --git a/src/product/application/product.service.ts b/src/product/application/product.service.ts index 9cad81a..04cfee1 100644 --- a/src/product/application/product.service.ts +++ b/src/product/application/product.service.ts @@ -1,4 +1,16 @@ -import { Injectable } from '@nestjs/common'; +import { Inject, Injectable } from '@nestjs/common'; +import { PRODUCT_REPOSITORY, ProductRepository } from './product.repository'; +import { GetProductsQueryDTO } from '../presentation/dto/product.request.dto'; +import { ProductResponseDto } from '../presentation/dto/product.response.dto'; @Injectable() -export class ProductService {} +export class ProductService { + constructor( + @Inject(PRODUCT_REPOSITORY) private readonly productRepository: ProductRepository, + ) {} + + async getProducts(query: GetProductsQueryDTO): Promise { + const products = await this.productRepository.getProducts(query); + return products.map((product) => ProductResponseDto.of(product)); + } +} diff --git a/src/product/domain/product.ts b/src/product/domain/product.ts index 4c6a894..4d03c52 100644 --- a/src/product/domain/product.ts +++ b/src/product/domain/product.ts @@ -1,4 +1,5 @@ import { ApiProperty } from '@nestjs/swagger'; +import { ProductQuantity } from './product-quantity'; export class Product { @ApiProperty({ example: 1, description: '상품 ID' }) @@ -18,4 +19,7 @@ export class Product { @ApiProperty({ example: '2025-01-01', description: '수정일' }) updatedAt: Date; + + @ApiProperty({ type: ProductQuantity, description: '상품 수량' }) + ProductQuantity: ProductQuantity; } diff --git a/src/product/infrastructure/product.repository.ts b/src/product/infrastructure/product.repository.ts deleted file mode 100644 index 58673f2..0000000 --- a/src/product/infrastructure/product.repository.ts +++ /dev/null @@ -1 +0,0 @@ -export class ProductRepository {} diff --git a/src/product/infrastructure/product.typeorm.repository.ts b/src/product/infrastructure/product.typeorm.repository.ts new file mode 100644 index 0000000..99184b4 --- /dev/null +++ b/src/product/infrastructure/product.typeorm.repository.ts @@ -0,0 +1,20 @@ +import { PrismaService } from 'src/database/prisma/prisma.service'; +import { GetProductsQueryDTO } from '../presentation/dto/product.request.dto'; +import { Injectable } from '@nestjs/common'; +import { ProductRepository } from '../application/product.repository'; +import { Product } from '../domain/product'; + +@Injectable() +export class ProductTypeOrmRepository implements ProductRepository { + constructor(private readonly prisma: PrismaService) {} + + async getProducts(query: GetProductsQueryDTO): Promise { + return await this.prisma.product.findMany({ + include: { + ProductQuantity: true, + }, + skip: query.offset, + take: query.size, + }); + } +} diff --git a/src/product/presentation/dto/product.request.dto.ts b/src/product/presentation/dto/product.request.dto.ts new file mode 100644 index 0000000..e53f5d4 --- /dev/null +++ b/src/product/presentation/dto/product.request.dto.ts @@ -0,0 +1,21 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { Transform } from 'class-transformer'; +import { IsNumber, Min } from 'class-validator'; + +export class GetProductsQueryDTO { + @ApiProperty({ example: 1, description: '페이지 번호' }) + @IsNumber() + @Transform(({ value }) => parseInt(value)) + @Min(1) + page: number = 1; + + @ApiProperty({ example: 10, description: '페이지 크기' }) + @Transform(({ value }) => parseInt(value)) + @IsNumber() + @Min(10) + size: number = 10; + + get offset() { + return (this.page - 1) * this.size; + } +} diff --git a/src/product/presentation/dto/product.response.dto.ts b/src/product/presentation/dto/product.response.dto.ts index 5a4209c..d92fe95 100644 --- a/src/product/presentation/dto/product.response.dto.ts +++ b/src/product/presentation/dto/product.response.dto.ts @@ -1,8 +1,27 @@ -import { IntersectionType, OmitType, PickType } from '@nestjs/swagger'; +import { ApiProperty, OmitType, PickType } from '@nestjs/swagger'; import { Product } from '../../domain/product'; import { ProductQuantity } from '../../domain/product-quantity'; -export class ProductResponseDto extends IntersectionType( - OmitType(Product, ['createdAt', 'updatedAt'] as const), - PickType(ProductQuantity, ['quantity', 'remainingQuantity'] as const), -) {} +class ProductQuantityDto extends PickType(ProductQuantity, [ + 'quantity', + 'remainingQuantity', +] as const) {} + +export class ProductResponseDto extends OmitType(Product, ['ProductQuantity'] as const) { + @ApiProperty({ type: ProductQuantityDto }) + ProductQuantity: ProductQuantityDto; + + constructor(product: Product) { + super(); + Object.assign(this, product); + } + + static of(product: Product): ProductResponseDto { + const dto = new ProductResponseDto(product); + dto.ProductQuantity = { + quantity: product.ProductQuantity?.quantity, + remainingQuantity: product.ProductQuantity?.remainingQuantity, + }; + return dto; + } +} diff --git a/src/product/presentation/product.controller.ts b/src/product/presentation/product.controller.ts index 8c01520..80e4c88 100644 --- a/src/product/presentation/product.controller.ts +++ b/src/product/presentation/product.controller.ts @@ -1,7 +1,8 @@ -import { Controller, Get } from '@nestjs/common'; +import { Controller, Get, Query } from '@nestjs/common'; import { ProductService } from '../application/product.service'; -import { ApiOkResponse, ApiOperation, ApiTags } from '@nestjs/swagger'; -import { ProductResponseDto } from './dto/product.response.dto'; +import { ApiTags } from '@nestjs/swagger'; +import { getProductsSwagger, getSpecialProductsSwagger } from '../swagger/product.swagger'; +import { GetProductsQueryDTO } from './dto/product.request.dto'; @Controller('product') @ApiTags('Product') @@ -9,34 +10,14 @@ export class ProductController { constructor(private readonly productService: ProductService) {} @Get() - @ApiOperation({ - summary: '상품 목록 조회', - description: '상품 목록을 조회합니다.', - }) - @ApiOkResponse({ - description: '상품 목록', - type: [ProductResponseDto], - }) - async getProducts() { - return [ - { - id: 1, - name: '상품1', - price: 10_000, - }, - ]; + @getProductsSwagger() + async getProducts(@Query() query: GetProductsQueryDTO) { + return await this.productService.getProducts(query); } @Get('/special') - @ApiOperation({ - summary: '상위 상품 목록 조회', - description: '최근 3일 기준 많이 팔린 상위 상품 목록을 조회합니다.', - }) - @ApiOkResponse({ - description: '상위 상품 목록', - type: [ProductResponseDto], - }) - async getSpecialProducts() { + @getSpecialProductsSwagger() + async getSpecialProducts(@Query() query: GetProductsQueryDTO) { return [ { id: 1, diff --git a/src/product/product.module.ts b/src/product/product.module.ts index 9dacb2d..8ccf016 100644 --- a/src/product/product.module.ts +++ b/src/product/product.module.ts @@ -1,9 +1,14 @@ import { Module } from '@nestjs/common'; import { ProductService } from './application/product.service'; import { ProductController } from './presentation/product.controller'; +import { PRODUCT_REPOSITORY } from './application/product.repository'; +import { ProductTypeOrmRepository } from './infrastructure/product.typeorm.repository'; @Module({ controllers: [ProductController], - providers: [ProductService], + providers: [ + ProductService, + { provide: PRODUCT_REPOSITORY, useClass: ProductTypeOrmRepository }, + ], }) export class ProductModule {} diff --git a/src/product/swagger/product.swagger.ts b/src/product/swagger/product.swagger.ts new file mode 100644 index 0000000..c3467bd --- /dev/null +++ b/src/product/swagger/product.swagger.ts @@ -0,0 +1,20 @@ +import { applyDecorators } from '@nestjs/common'; +import { ApiOkResponse, ApiOperation } from '@nestjs/swagger'; +import { ProductResponseDto } from '../presentation/dto/product.response.dto'; + +export function getProductsSwagger() { + return applyDecorators( + ApiOperation({ summary: '상품 목록 조회', description: '상품 목록을 조회합니다.' }), + ApiOkResponse({ description: '상품 목록', type: [ProductResponseDto] }), + ); +} + +export function getSpecialProductsSwagger() { + return applyDecorators( + ApiOperation({ + summary: '상위 상품 목록 조회', + description: '최근 3일 기준 많이 팔린 상위 상품 목록을 조회합니다.', + }), + ApiOkResponse({ description: '상위 상품 목록', type: [ProductResponseDto] }), + ); +} From 9e0d51390b8b0878e34f500660f5f3be40e6ce93 Mon Sep 17 00:00:00 2001 From: wooo73 Date: Mon, 6 Jan 2025 19:52:25 +0900 Subject: [PATCH 7/8] =?UTF-8?q?[fix]:=20seed=20=EC=83=9D=EC=84=B1=20?= =?UTF-8?q?=EB=A1=9C=EC=A7=81=20=EB=B0=8F=20=EB=AA=85=EB=A0=B9=EC=96=B4=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 2 +- prisma/seed.ts | 51 ++++++++++++++++++++++++++++++++++++++++++++------ 2 files changed, 46 insertions(+), 7 deletions(-) diff --git a/package.json b/package.json index 8874501..8a135b7 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,7 @@ "docker:up": "docker-compose -f docker-compose.yaml up -d", "docker:down": "docker-compose -f docker-compose.yaml down -v", "migrate:test": "dotenv -e .env.test -- npx prisma migrate deploy", - "seed:test": "dotenv -e .env.test -- npx prisma db seed" + "seed:test": "npx prisma db seed" }, "dependencies": { "@nestjs/common": "^10.0.0", diff --git a/prisma/seed.ts b/prisma/seed.ts index 529b07c..aa7fa11 100644 --- a/prisma/seed.ts +++ b/prisma/seed.ts @@ -19,14 +19,53 @@ async function initTable() { } async function createMockData() { - await prisma.user.create({ - data: { balance: 10000 }, - }); + await Promise.all( + Array.from({ length: 10 }, () => + prisma.user.create({ + data: { balance: 10000 }, + }), + ), + ); + + //사용자 생성 + await Promise.all( + Array.from({ length: 10 }, () => + prisma.user.create({ + data: { balance: 10000 }, + }), + ), + ); + + //상품 생성 및 재고 생성 + for (const index of [...Array(30).keys()]) { + await prisma.product.create({ + data: { + name: `테스트상품${index + 1}`, + price: Math.floor(Math.random() * 90001) + 10000, // 10000 ~ 100000 사이 랜덤 가격 + ProductQuantity: { + create: { + quantity: 10, + remainingQuantity: 10, + }, + }, + }, + }); + } - await prisma.product.create({ + //쿠폰 생성 및 재고 생성 + await prisma.coupon.create({ data: { - name: '테스트 상품2', - price: 50000, + name: '테스트쿠폰', + discountType: 'PERCENT', + discountValue: 10, + startAt: new Date(), + endAt: new Date(new Date().getTime() + 1000 * 60 * 60 * 24 * 1), + couponQuantity: { + create: { + quantity: 10, + remainingQuantity: 10, + }, + }, }, }); } From af3e95a2374fa2b93b6659cd602ef0ae036ca7ea Mon Sep 17 00:00:00 2001 From: wooo73 Date: Tue, 7 Jan 2025 01:58:01 +0900 Subject: [PATCH 8/8] =?UTF-8?q?[fix]:=20schema=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - status 타입 boolean -> string --- prisma/schema/coupon.prisma | 4 ++-- prisma/schema/product.prisma | 4 ++-- prisma/schema/user-coupon.prisma | 2 +- prisma/schema/user.prisma | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/prisma/schema/coupon.prisma b/prisma/schema/coupon.prisma index 38f4c42..df9c4d7 100644 --- a/prisma/schema/coupon.prisma +++ b/prisma/schema/coupon.prisma @@ -3,13 +3,13 @@ model Coupon { name String @db.VarChar(50) discountType String @map("discount_type") @db.VarChar(10) discountValue Int @map("discount_value") @db.Int - status Boolean @default(true) + status String @default("AVAILABLE") startAt DateTime @map("start_at") @db.Timestamp(0) endAt DateTime @map("end_at") @db.Timestamp(0) createdAt DateTime @default(now()) @map("created_at") @db.Timestamp(0) updatedAt DateTime @updatedAt @map("updated_at") @db.Timestamp(0) couponQuantity CouponQuantity? - UserCoupon UserCoupon? + userCoupon UserCoupon? @@map("coupon") } diff --git a/prisma/schema/product.prisma b/prisma/schema/product.prisma index 9678ef6..e625ab1 100644 --- a/prisma/schema/product.prisma +++ b/prisma/schema/product.prisma @@ -2,11 +2,11 @@ model Product { id Int @id @default(autoincrement()) name String @db.VarChar(50) price Int @db.Int - status Boolean @default(true) + status String @default("IN_STOCK") createdAt DateTime @default(now()) @map("created_at") @db.Timestamp(0) updatedAt DateTime @updatedAt @map("updated_at") @db.Timestamp(0) orderItem OrderItem[] - ProductQuantity ProductQuantity? + productQuantity ProductQuantity? @@map("product") } diff --git a/prisma/schema/user-coupon.prisma b/prisma/schema/user-coupon.prisma index 45abe22..2992223 100644 --- a/prisma/schema/user-coupon.prisma +++ b/prisma/schema/user-coupon.prisma @@ -8,7 +8,7 @@ model UserCoupon { usedAt DateTime @map("used_at") @db.Timestamp(0) createdAt DateTime @default(now()) @map("created_at") @db.Timestamp(0) updatedAt DateTime @updatedAt @map("updated_at") @db.Timestamp(0) - Order Order? + order Order? @@map("user_coupon") } diff --git a/prisma/schema/user.prisma b/prisma/schema/user.prisma index ce994aa..29eeb7f 100644 --- a/prisma/schema/user.prisma +++ b/prisma/schema/user.prisma @@ -4,7 +4,7 @@ model User { createdAt DateTime @default(now()) @map("created_at") @db.Timestamp(0) updatedAt DateTime @updatedAt @map("updated_at") @db.Timestamp(0) order Order[] - UserCoupon UserCoupon? + userCoupon UserCoupon? @@map("user") }