From 1eb954a6c63e109400aa58e4a9e13362ff333e1f Mon Sep 17 00:00:00 2001 From: pinomaker-hoo Date: Sat, 19 Aug 2023 14:32:11 +0900 Subject: [PATCH 1/9] feat : Add Save Phone --- src/api/phone/controller/phone.controller.ts | 35 ++++++++++++++++++ src/api/phone/domain/phone.entity.ts | 18 ++++++++++ src/api/phone/dto/phone.save.dto.ts | 15 ++++++++ src/api/phone/phone.module.ts | 17 +++++++++ src/api/phone/repository/phone.repository.ts | 9 +++++ src/api/phone/service/phone.service.ts | 37 ++++++++++++++++++++ src/app.module.ts | 2 ++ src/config/env/.dev.env | 8 ++--- 8 files changed, 137 insertions(+), 4 deletions(-) create mode 100644 src/api/phone/controller/phone.controller.ts create mode 100644 src/api/phone/domain/phone.entity.ts create mode 100644 src/api/phone/dto/phone.save.dto.ts create mode 100644 src/api/phone/phone.module.ts create mode 100644 src/api/phone/repository/phone.repository.ts create mode 100644 src/api/phone/service/phone.service.ts diff --git a/src/api/phone/controller/phone.controller.ts b/src/api/phone/controller/phone.controller.ts new file mode 100644 index 0000000..229f2f2 --- /dev/null +++ b/src/api/phone/controller/phone.controller.ts @@ -0,0 +1,35 @@ +// ** Nest Imports +import { Body, Controller, Post } from '@nestjs/common'; + +// ** enum, dto, entity Imports + +// ** Swagger Imports +import { + ApiBody, + ApiCreatedResponse, + ApiOperation, + ApiTags, +} from '@nestjs/swagger'; +import RequestPhoneSaveDto from '../dto/phone.save.dto'; +import CommonResponse from 'src/common/dto/api.response'; +import PhoneServiceImpl from '../service/phone.service'; + +@ApiTags('Phone') +@Controller('phone') +export default class PhoneController { + constructor(private readonly phoneService: PhoneServiceImpl) {} + + @ApiOperation({ summary: '전화번호 저장' }) + @ApiBody({ type: RequestPhoneSaveDto }) + @ApiCreatedResponse({ + status: 200, + description: '전화번호 저장', + type: CommonResponse, + }) + @Post() + public async savePhone( + @Body() dto: RequestPhoneSaveDto, + ): Promise> { + return await this.phoneService.savePhone(dto); + } +} diff --git a/src/api/phone/domain/phone.entity.ts b/src/api/phone/domain/phone.entity.ts new file mode 100644 index 0000000..aa297e1 --- /dev/null +++ b/src/api/phone/domain/phone.entity.ts @@ -0,0 +1,18 @@ +// ** Typeorm Imports +import { Column, Entity, PrimaryGeneratedColumn, Unique } from 'typeorm'; + +// ** enum, dto, entity Imports +import BaseTimeEntity from 'src/common/entity/BaseTime.Entity'; + +@Entity({ name: 'TB_PHONE_IH' }) +@Unique(['number']) +export default class Phone extends BaseTimeEntity { + @PrimaryGeneratedColumn() + id: number; + + @Column({ type: 'varchar', length: 30, name: 'phone_name' }) + name: string; + + @Column({ type: 'varchar', length: 30, name: 'phone_number' }) + number: string; +} diff --git a/src/api/phone/dto/phone.save.dto.ts b/src/api/phone/dto/phone.save.dto.ts new file mode 100644 index 0000000..634344b --- /dev/null +++ b/src/api/phone/dto/phone.save.dto.ts @@ -0,0 +1,15 @@ +// ** Swagger Imports +import { ApiProperty } from '@nestjs/swagger'; + +// ** Pipe Imports +import { IsString } from 'class-validator'; + +export default class RequestPhoneSaveDto { + @ApiProperty({ example: '김인후', type: 'string' }) + @IsString() + name: string; + + @ApiProperty({ example: '01063057848', type: 'string' }) + @IsString() + phone: string; +} diff --git a/src/api/phone/phone.module.ts b/src/api/phone/phone.module.ts new file mode 100644 index 0000000..7a68b9d --- /dev/null +++ b/src/api/phone/phone.module.ts @@ -0,0 +1,17 @@ +import { Module } from '@nestjs/common'; +import PhoneService from './service/phone.service'; +import PhoneController from './controller/phone.controller'; +import { TypeOrmModule } from '@nestjs/typeorm'; +import { TypeOrmExModule } from 'src/repository/typeOrmEx.module'; +import Phone from './domain/phone.entity'; +import PhoneRepository from './repository/phone.repository'; + +@Module({ + imports: [ + TypeOrmModule.forFeature([Phone]), + TypeOrmExModule.forCustomRepository([PhoneRepository]), + ], + controllers: [PhoneController], + providers: [PhoneService], +}) +export default class PhoneModule {} diff --git a/src/api/phone/repository/phone.repository.ts b/src/api/phone/repository/phone.repository.ts new file mode 100644 index 0000000..dfb28f5 --- /dev/null +++ b/src/api/phone/repository/phone.repository.ts @@ -0,0 +1,9 @@ +// ** Typeorm Imports +import { Repository } from 'typeorm'; + +// ** Custom Module Imports +import { CustomRepository } from 'src/repository/typeorm-ex.decorator'; +import Phone from '../domain/phone.entity'; + +@CustomRepository(Phone) +export default class PhoneRepository extends Repository {} diff --git a/src/api/phone/service/phone.service.ts b/src/api/phone/service/phone.service.ts new file mode 100644 index 0000000..e9571d6 --- /dev/null +++ b/src/api/phone/service/phone.service.ts @@ -0,0 +1,37 @@ +// ** Nest Imports +import { Injectable } from '@nestjs/common'; +import PhoneRepository from '../repository/phone.repository'; + +// ** enum, dto, entity, types Imports +import CommonResponse from 'src/common/dto/api.response'; +import RequestPhoneSaveDto from '../dto/phone.save.dto'; +import { BadRequestException } from 'src/exception/customException'; + +@Injectable() +export default class PhoneServiceImpl { + constructor(private readonly phoneRepository: PhoneRepository) {} + + public async savePhone( + dto: RequestPhoneSaveDto, + ): Promise> { + const findPhone = await this.phoneRepository.findOne({ + where: { number: dto.phone }, + }); + + if (findPhone) { + throw new BadRequestException('Exist Phone Number'); + } + + await this.phoneRepository.save( + this.phoneRepository.create({ + name: dto.name, + number: dto.phone, + }), + ); + + return CommonResponse.of({ + statusCode: 200, + message: '전화번호를 생성합니다.', + }); + } +} diff --git a/src/app.module.ts b/src/app.module.ts index d0d23d5..eafad87 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -13,6 +13,7 @@ import { TypeOrmModule } from '@nestjs/typeorm'; import { TypeOrmExModule } from './repository/typeOrmEx.module'; import ViewModule from './api/view/view.module'; import AdapterModule from './api/adapter/adapter.module'; +import PhoneModule from './api/phone/phone.module'; @Module({ imports: [ @@ -35,6 +36,7 @@ import AdapterModule from './api/adapter/adapter.module'; TypeOrmExModule, LoggerModule, AuthModule, + PhoneModule, UploadModule, ViewModule, AdapterModule, diff --git a/src/config/env/.dev.env b/src/config/env/.dev.env index 5ac4eb5..1161ed0 100644 --- a/src/config/env/.dev.env +++ b/src/config/env/.dev.env @@ -1,10 +1,10 @@ SERVER_PORT=8080 -DB_HOST=210.223.18.224 +DB_HOST=125.133.34.224 DB_PORT=3306 -DB_USERNAME=pino -DB_PASSWORD=qwer1595 -DB_DATABASE=test +DB_USERNAME=dice +DB_PASSWORD=qwer1234!@ +DB_DATABASE=dice_test JWT_ACCESS_TOKEN_SECRET=bHV4cm9iby1iYWNrZW5kLW5lc3Rqcy1hY2Nlc3N0b2tlbi1qd3Q= JWT_REFRESH_TOKEN_SECRET=bHV4cm9iby1iYWNrZW5kLW5lc3Rqcy1yZWZyZXNodG9rZW4tand0 From 212c49f9e3c5d67ca81c40b34e3fb331833e4e9d Mon Sep 17 00:00:00 2001 From: yoona <103414935+yoonali@users.noreply.github.com> Date: Sun, 27 Aug 2023 16:31:08 +0900 Subject: [PATCH 2/9] feat: add CRUD --- src/api/phone/controller/phone.controller.ts | 2 + src/api/phone/domain/phone.entity.ts | 2 +- src/api/phone/dto/phone.save.dto.ts | 4 +- src/api/phone/service/phone.service.ts | 70 +++++++++++++++++++- 4 files changed, 74 insertions(+), 4 deletions(-) diff --git a/src/api/phone/controller/phone.controller.ts b/src/api/phone/controller/phone.controller.ts index 229f2f2..f5bef46 100644 --- a/src/api/phone/controller/phone.controller.ts +++ b/src/api/phone/controller/phone.controller.ts @@ -33,3 +33,5 @@ export default class PhoneController { return await this.phoneService.savePhone(dto); } } + + diff --git a/src/api/phone/domain/phone.entity.ts b/src/api/phone/domain/phone.entity.ts index aa297e1..572fb47 100644 --- a/src/api/phone/domain/phone.entity.ts +++ b/src/api/phone/domain/phone.entity.ts @@ -4,7 +4,7 @@ import { Column, Entity, PrimaryGeneratedColumn, Unique } from 'typeorm'; // ** enum, dto, entity Imports import BaseTimeEntity from 'src/common/entity/BaseTime.Entity'; -@Entity({ name: 'TB_PHONE_IH' }) +@Entity({ name: 'TB_PHONE_YN' }) @Unique(['number']) export default class Phone extends BaseTimeEntity { @PrimaryGeneratedColumn() diff --git a/src/api/phone/dto/phone.save.dto.ts b/src/api/phone/dto/phone.save.dto.ts index 634344b..223a211 100644 --- a/src/api/phone/dto/phone.save.dto.ts +++ b/src/api/phone/dto/phone.save.dto.ts @@ -5,11 +5,11 @@ import { ApiProperty } from '@nestjs/swagger'; import { IsString } from 'class-validator'; export default class RequestPhoneSaveDto { - @ApiProperty({ example: '김인후', type: 'string' }) + @ApiProperty({ example: '임유나', type: 'string' }) @IsString() name: string; - @ApiProperty({ example: '01063057848', type: 'string' }) + @ApiProperty({ example: '01033618490', type: 'string' }) @IsString() phone: string; } diff --git a/src/api/phone/service/phone.service.ts b/src/api/phone/service/phone.service.ts index e9571d6..a90444c 100644 --- a/src/api/phone/service/phone.service.ts +++ b/src/api/phone/service/phone.service.ts @@ -1,5 +1,5 @@ // ** Nest Imports -import { Injectable } from '@nestjs/common'; +import { Injectable, NotFoundException } from '@nestjs/common'; import PhoneRepository from '../repository/phone.repository'; // ** enum, dto, entity, types Imports @@ -34,4 +34,72 @@ export default class PhoneServiceImpl { message: '전화번호를 생성합니다.', }); } + + //단일 조회 + public async findOnePhone( + id: number, + ): Promise> { + const phone = await this.phoneRepository.findOne({where: {id,}}); + + if (!phone) { + throw new NotFoundException('Phone not found'); + } + + return CommonResponse.of({ + statusCode: 200, + message: '전화번호를 조회합니다.', + data: phone + }); + } + + //전체 조회 + public async findAllPhone(): Promise>{ + + const phones = await this.phoneRepository.find(); + + return CommonResponse.of({ + statusCode: 200, + message: '전화번호를 조회합니다.', + data: phones + }); + } + + public async updatePhone( + id : number, + dto: RequestPhoneSaveDto, + ): Promise>{ + const updatePhone = await this.phoneRepository.findOne({where: {id,}}); + + if (!updatePhone) { + throw new NotFoundException('Phone not found'); + } + + await this.phoneRepository.update(id, dto); + + return CommonResponse.of({ + statusCode: 200, + message: '전화번호를 업데이트합니다.', + data: updatePhone + }); + } + + public async deletePhone( + id : number, + ): Promise>{ + + const deletePhone = await this.phoneRepository.findOne({where: {id,}}) + + if (!deletePhone) { + throw new NotFoundException('Phone not found'); + } + + await this.phoneRepository.delete(id) + + return CommonResponse.of({ + statusCode: 200, + message: '전화번호를 삭제합니다.', + data: deletePhone + }); + } + } From b1166d9807896a1db5e068b2189fb566b3c6dc67 Mon Sep 17 00:00:00 2001 From: pinomaker-hoo Date: Mon, 4 Sep 2023 20:43:08 +0900 Subject: [PATCH 3/9] feat : Add PhoneBook --- src/api/auth/domain/user.entity.ts | 15 ++++++++++++++- src/api/phone/controller/phone.controller.ts | 14 ++++++++++++-- src/api/phone/domain/phone.entity.ts | 15 ++++++++++++++- src/api/phone/service/phone.service.ts | 3 +++ 4 files changed, 43 insertions(+), 4 deletions(-) diff --git a/src/api/auth/domain/user.entity.ts b/src/api/auth/domain/user.entity.ts index 8aa4782..f2e857d 100644 --- a/src/api/auth/domain/user.entity.ts +++ b/src/api/auth/domain/user.entity.ts @@ -1,10 +1,17 @@ // ** Typeorm Imports -import { Column, Entity, PrimaryGeneratedColumn, Unique } from 'typeorm'; +import { + Column, + Entity, + OneToMany, + PrimaryGeneratedColumn, + Unique, +} from 'typeorm'; // ** enum, dto, entity Imports import BaseTimeEntity from 'src/common/entity/BaseTime.Entity'; import { UserRole } from '../dto/user.role'; import { Exclude } from 'class-transformer'; +import Phone from 'src/api/phone/domain/phone.entity'; @Entity({ name: 'tbl_user' }) @Unique(['email']) @@ -30,4 +37,10 @@ export default class User extends BaseTimeEntity { public set role(value: UserRole) { this._role = value; } + + /** + * OneToMany + */ + @OneToMany(() => Phone, (phone) => phone.user) + phone: Phone[]; } diff --git a/src/api/phone/controller/phone.controller.ts b/src/api/phone/controller/phone.controller.ts index 229f2f2..2f18b56 100644 --- a/src/api/phone/controller/phone.controller.ts +++ b/src/api/phone/controller/phone.controller.ts @@ -1,5 +1,5 @@ // ** Nest Imports -import { Body, Controller, Post } from '@nestjs/common'; +import { Body, Controller, Post, Req, UseGuards } from '@nestjs/common'; // ** enum, dto, entity Imports @@ -8,17 +8,25 @@ import { ApiBody, ApiCreatedResponse, ApiOperation, + ApiBearerAuth, ApiTags, } from '@nestjs/swagger'; +import { Role } from 'src/roles/roles.decorator'; import RequestPhoneSaveDto from '../dto/phone.save.dto'; import CommonResponse from 'src/common/dto/api.response'; import PhoneServiceImpl from '../service/phone.service'; +import JwtAccessGuard from 'src/api/auth/passport/auth.jwt-access.guard'; +import { RolesGuard } from 'src/roles/roles.guard'; +import { UserRole } from 'src/api/auth/dto/user.role'; +import { Request } from 'express'; +import { RequestWithUsernDto } from 'src/common/dto/request.user.dto'; @ApiTags('Phone') @Controller('phone') export default class PhoneController { constructor(private readonly phoneService: PhoneServiceImpl) {} + @ApiBearerAuth('access-token') @ApiOperation({ summary: '전화번호 저장' }) @ApiBody({ type: RequestPhoneSaveDto }) @ApiCreatedResponse({ @@ -26,10 +34,12 @@ export default class PhoneController { description: '전화번호 저장', type: CommonResponse, }) + @UseGuards(JwtAccessGuard) @Post() public async savePhone( @Body() dto: RequestPhoneSaveDto, + @Req() { user }: RequestWithUsernDto, ): Promise> { - return await this.phoneService.savePhone(dto); + return await this.phoneService.savePhone(dto, user); } } diff --git a/src/api/phone/domain/phone.entity.ts b/src/api/phone/domain/phone.entity.ts index aa297e1..2ec80da 100644 --- a/src/api/phone/domain/phone.entity.ts +++ b/src/api/phone/domain/phone.entity.ts @@ -1,8 +1,15 @@ // ** Typeorm Imports -import { Column, Entity, PrimaryGeneratedColumn, Unique } from 'typeorm'; +import { + Column, + Entity, + ManyToOne, + PrimaryGeneratedColumn, + Unique, +} from 'typeorm'; // ** enum, dto, entity Imports import BaseTimeEntity from 'src/common/entity/BaseTime.Entity'; +import User from 'src/api/auth/domain/user.entity'; @Entity({ name: 'TB_PHONE_IH' }) @Unique(['number']) @@ -15,4 +22,10 @@ export default class Phone extends BaseTimeEntity { @Column({ type: 'varchar', length: 30, name: 'phone_number' }) number: string; + + /** + * ManyToOne + */ + @ManyToOne(() => User, (user) => user.phone) + user: User; } diff --git a/src/api/phone/service/phone.service.ts b/src/api/phone/service/phone.service.ts index e9571d6..b5a91aa 100644 --- a/src/api/phone/service/phone.service.ts +++ b/src/api/phone/service/phone.service.ts @@ -6,6 +6,7 @@ import PhoneRepository from '../repository/phone.repository'; import CommonResponse from 'src/common/dto/api.response'; import RequestPhoneSaveDto from '../dto/phone.save.dto'; import { BadRequestException } from 'src/exception/customException'; +import User from 'src/api/auth/domain/user.entity'; @Injectable() export default class PhoneServiceImpl { @@ -13,6 +14,7 @@ export default class PhoneServiceImpl { public async savePhone( dto: RequestPhoneSaveDto, + user: User, ): Promise> { const findPhone = await this.phoneRepository.findOne({ where: { number: dto.phone }, @@ -26,6 +28,7 @@ export default class PhoneServiceImpl { this.phoneRepository.create({ name: dto.name, number: dto.phone, + user, }), ); From e0207e802b3ffc7ad175cf3ed6aa0c7f5a3e3a1f Mon Sep 17 00:00:00 2001 From: yoona <103414935+yoonali@users.noreply.github.com> Date: Fri, 8 Sep 2023 11:37:35 +0900 Subject: [PATCH 4/9] feat: update phone entity --- src/api/phone/domain/phone.entity.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/api/phone/domain/phone.entity.ts b/src/api/phone/domain/phone.entity.ts index 572fb47..f152340 100644 --- a/src/api/phone/domain/phone.entity.ts +++ b/src/api/phone/domain/phone.entity.ts @@ -1,8 +1,9 @@ // ** Typeorm Imports -import { Column, Entity, PrimaryGeneratedColumn, Unique } from 'typeorm'; +import { Column, Entity, ManyToOne, PrimaryGeneratedColumn, Unique } from 'typeorm'; // ** enum, dto, entity Imports import BaseTimeEntity from 'src/common/entity/BaseTime.Entity'; +import User from 'src/api/auth/domain/user.entity'; @Entity({ name: 'TB_PHONE_YN' }) @Unique(['number']) @@ -15,4 +16,8 @@ export default class Phone extends BaseTimeEntity { @Column({ type: 'varchar', length: 30, name: 'phone_number' }) number: string; + + + @ManyToOne(() => User, (user) => user.phone) + user: User } From 382281dc5c419832264c13b81b206ac63cd65136 Mon Sep 17 00:00:00 2001 From: yoona <103414935+yoonali@users.noreply.github.com> Date: Fri, 8 Sep 2023 13:01:37 +0900 Subject: [PATCH 5/9] feat: Add Fk userid --- src/api/auth/domain/user.entity.ts | 6 +++++- src/api/phone/domain/phone.entity.ts | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/api/auth/domain/user.entity.ts b/src/api/auth/domain/user.entity.ts index 8aa4782..b23a4ca 100644 --- a/src/api/auth/domain/user.entity.ts +++ b/src/api/auth/domain/user.entity.ts @@ -1,10 +1,11 @@ // ** Typeorm Imports -import { Column, Entity, PrimaryGeneratedColumn, Unique } from 'typeorm'; +import { Column, Entity, OneToMany, PrimaryGeneratedColumn, Unique } from 'typeorm'; // ** enum, dto, entity Imports import BaseTimeEntity from 'src/common/entity/BaseTime.Entity'; import { UserRole } from '../dto/user.role'; import { Exclude } from 'class-transformer'; +import Phone from 'src/api/phone/domain/phone.entity'; @Entity({ name: 'tbl_user' }) @Unique(['email']) @@ -18,6 +19,9 @@ export default class User extends BaseTimeEntity { @Column({ type: 'varchar', length: 120 }) password: string; + /*@OneToMany(() => Phone, (phone) => phone.user) + phones: Phone[]*/ + @Column({ nullable: true }) @Exclude() currentHashedRefreshToken?: string; diff --git a/src/api/phone/domain/phone.entity.ts b/src/api/phone/domain/phone.entity.ts index f152340..55e27d7 100644 --- a/src/api/phone/domain/phone.entity.ts +++ b/src/api/phone/domain/phone.entity.ts @@ -18,6 +18,6 @@ export default class Phone extends BaseTimeEntity { number: string; - @ManyToOne(() => User, (user) => user.phone) + @ManyToOne(() => User, (user) => user.id) user: User } From 364c86807584e512ed6cd4a67c783b6f30199161 Mon Sep 17 00:00:00 2001 From: yoona <103414935+yoonali@users.noreply.github.com> Date: Fri, 8 Sep 2023 13:05:40 +0900 Subject: [PATCH 6/9] fix: Fixed phone.service --- src/api/phone/service/phone.service.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/api/phone/service/phone.service.ts b/src/api/phone/service/phone.service.ts index a90444c..9a9cba3 100644 --- a/src/api/phone/service/phone.service.ts +++ b/src/api/phone/service/phone.service.ts @@ -79,7 +79,6 @@ export default class PhoneServiceImpl { return CommonResponse.of({ statusCode: 200, message: '전화번호를 업데이트합니다.', - data: updatePhone }); } @@ -98,7 +97,6 @@ export default class PhoneServiceImpl { return CommonResponse.of({ statusCode: 200, message: '전화번호를 삭제합니다.', - data: deletePhone }); } From bad26cb92ec36c186ea344e7d3cc35ef333935aa Mon Sep 17 00:00:00 2001 From: yoona <103414935+yoonali@users.noreply.github.com> Date: Fri, 8 Sep 2023 14:15:04 +0900 Subject: [PATCH 7/9] feat: Add Guards in CRUD --- src/api/phone/service/phone.service.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/api/phone/service/phone.service.ts b/src/api/phone/service/phone.service.ts index 9a9cba3..17d722e 100644 --- a/src/api/phone/service/phone.service.ts +++ b/src/api/phone/service/phone.service.ts @@ -1,16 +1,18 @@ // ** Nest Imports -import { Injectable, NotFoundException } from '@nestjs/common'; +import { Injectable, NotFoundException, UseGuards } from '@nestjs/common'; import PhoneRepository from '../repository/phone.repository'; // ** enum, dto, entity, types Imports import CommonResponse from 'src/common/dto/api.response'; import RequestPhoneSaveDto from '../dto/phone.save.dto'; import { BadRequestException } from 'src/exception/customException'; +import { RolesGuard } from 'src/roles/roles.guard'; @Injectable() export default class PhoneServiceImpl { constructor(private readonly phoneRepository: PhoneRepository) {} + @UseGuards(RolesGuard) public async savePhone( dto: RequestPhoneSaveDto, ): Promise> { @@ -36,6 +38,7 @@ export default class PhoneServiceImpl { } //단일 조회 + @UseGuards(RolesGuard) public async findOnePhone( id: number, ): Promise> { @@ -53,6 +56,7 @@ export default class PhoneServiceImpl { } //전체 조회 + @UseGuards(RolesGuard) public async findAllPhone(): Promise>{ const phones = await this.phoneRepository.find(); @@ -64,6 +68,7 @@ export default class PhoneServiceImpl { }); } + @UseGuards(RolesGuard) public async updatePhone( id : number, dto: RequestPhoneSaveDto, @@ -82,6 +87,7 @@ export default class PhoneServiceImpl { }); } + @UseGuards(RolesGuard) public async deletePhone( id : number, ): Promise>{ From c5fcb24d27fa35de5b452438a31b38f33e90d471 Mon Sep 17 00:00:00 2001 From: pinomaker-hoo Date: Mon, 11 Sep 2023 09:59:29 +0900 Subject: [PATCH 8/9] fix : Example Code --- src/api/phone/controller/phone.controller.ts | 54 ++++++---- src/api/phone/domain/phone.entity.ts | 2 +- src/api/phone/dto/phone.find..ts | 20 ++++ src/api/phone/repository/phone.repository.ts | 31 +++++- src/api/phone/service/phone.service.ts | 16 ++- src/response/common/index.ts | 108 +++++++++++++++++++ src/response/phone.response.ts | 36 +++++++ src/types/api.d.ts | 17 +++ 8 files changed, 263 insertions(+), 21 deletions(-) create mode 100644 src/api/phone/dto/phone.find..ts create mode 100644 src/response/common/index.ts create mode 100644 src/response/phone.response.ts create mode 100644 src/types/api.d.ts diff --git a/src/api/phone/controller/phone.controller.ts b/src/api/phone/controller/phone.controller.ts index 2f18b56..89bc2b7 100644 --- a/src/api/phone/controller/phone.controller.ts +++ b/src/api/phone/controller/phone.controller.ts @@ -1,40 +1,46 @@ // ** Nest Imports -import { Body, Controller, Post, Req, UseGuards } from '@nestjs/common'; - -// ** enum, dto, entity Imports +import { + Body, + Controller, + Post, + Req, + UseGuards, + Get, + Query, + ValidationPipe, +} from '@nestjs/common'; // ** Swagger Imports import { ApiBody, - ApiCreatedResponse, ApiOperation, ApiBearerAuth, ApiTags, + ApiResponse, } from '@nestjs/swagger'; -import { Role } from 'src/roles/roles.decorator'; + +// ** enum, dto, entity Imports import RequestPhoneSaveDto from '../dto/phone.save.dto'; import CommonResponse from 'src/common/dto/api.response'; import PhoneServiceImpl from '../service/phone.service'; -import JwtAccessGuard from 'src/api/auth/passport/auth.jwt-access.guard'; -import { RolesGuard } from 'src/roles/roles.guard'; -import { UserRole } from 'src/api/auth/dto/user.role'; -import { Request } from 'express'; import { RequestWithUsernDto } from 'src/common/dto/request.user.dto'; +// ** Guard Imports +import JwtAccessGuard from 'src/api/auth/passport/auth.jwt-access.guard'; +import { PhoneResponse } from 'src/response/phone.response'; +import RequestPhoneFindDto from '../dto/phone.find.'; + @ApiTags('Phone') @Controller('phone') export default class PhoneController { constructor(private readonly phoneService: PhoneServiceImpl) {} - @ApiBearerAuth('access-token') - @ApiOperation({ summary: '전화번호 저장' }) - @ApiBody({ type: RequestPhoneSaveDto }) - @ApiCreatedResponse({ - status: 200, - description: '전화번호 저장', - type: CommonResponse, - }) - @UseGuards(JwtAccessGuard) + @ApiBearerAuth('access-token') // Swagger에서 Access Token 사용 + @ApiOperation({ summary: '전화번호 생성' }) // ** Api에 대한 설명 + @ApiBody({ type: RequestPhoneSaveDto }) // ** Dto에 대한 설명 + @ApiResponse(PhoneResponse.savePhone[200]) // ** Response Example Value + @ApiResponse(PhoneResponse.savePhone[400]) // ** Response Example Value + @UseGuards(JwtAccessGuard) // ** Jwt 검증 @Post() public async savePhone( @Body() dto: RequestPhoneSaveDto, @@ -42,4 +48,16 @@ export default class PhoneController { ): Promise> { return await this.phoneService.savePhone(dto, user); } + + @ApiBearerAuth('access-token') + @ApiOperation({ summary: '전화번호 전체 조회' }) + @ApiResponse(PhoneResponse.findPhoneList[200]) + @UseGuards(JwtAccessGuard) + @Get() + public async findAll( + @Query(ValidationPipe) params: RequestPhoneFindDto, + @Req() { user }: RequestWithUsernDto, + ): Promise> { + return await this.phoneService.findPhoneList(params, user); + } } diff --git a/src/api/phone/domain/phone.entity.ts b/src/api/phone/domain/phone.entity.ts index 2ec80da..0d0c623 100644 --- a/src/api/phone/domain/phone.entity.ts +++ b/src/api/phone/domain/phone.entity.ts @@ -24,7 +24,7 @@ export default class Phone extends BaseTimeEntity { number: string; /** - * ManyToOne + * OneToMany */ @ManyToOne(() => User, (user) => user.phone) user: User; diff --git a/src/api/phone/dto/phone.find..ts b/src/api/phone/dto/phone.find..ts new file mode 100644 index 0000000..90e4c68 --- /dev/null +++ b/src/api/phone/dto/phone.find..ts @@ -0,0 +1,20 @@ +// ** Swagger Imports +import { ApiProperty } from '@nestjs/swagger'; + +// ** Pipe Imports +import { IsOptional, IsString } from 'class-validator'; + +// ** Other Imports +import RequestPagingDto from 'src/common/dto/paging.dto'; + +export default class RequestPhoneFindDto extends RequestPagingDto { + @ApiProperty({ example: '01063057848', required: false }) + @IsOptional() + @IsString() + number: string; + + @ApiProperty({ example: '김인후', required: false }) + @IsOptional() + @IsString() + name: string; +} diff --git a/src/api/phone/repository/phone.repository.ts b/src/api/phone/repository/phone.repository.ts index dfb28f5..7c3aafb 100644 --- a/src/api/phone/repository/phone.repository.ts +++ b/src/api/phone/repository/phone.repository.ts @@ -4,6 +4,35 @@ import { Repository } from 'typeorm'; // ** Custom Module Imports import { CustomRepository } from 'src/repository/typeorm-ex.decorator'; import Phone from '../domain/phone.entity'; +import RequestPhoneFindDto from '../dto/phone.find.'; @CustomRepository(Phone) -export default class PhoneRepository extends Repository {} +export default class PhoneRepository extends Repository { + public async findAll(dto: RequestPhoneFindDto, userId: number) { + const queryBuilder = this.createQueryBuilder('phone') // ** phone entity 선택 + .select(['phone.id', 'phone.name', 'phone.number', 'phone.createdAt']) // ** 원하는 컬럼 선택 + .where('phone.userId = :userId', { userId }); // ** 조건문 + + // ** Request에 페이징이 있으면 where + if (dto.page && dto.pageSize) { + queryBuilder.skip(dto.page * dto.pageSize).take(dto.pageSize); + } + + // ** Request에 name 있으면 where + if (dto.name) { + queryBuilder.where('ho.userName LIKE :name', { + name: `%${dto.name}%`, + }); + } + + // ** Request에 number 있으면 where + if (dto.number) { + queryBuilder.where('ho.userName LIKE :name', { + name: `%${dto.name}%`, + }); + } + + // ** 쿼리 결과 리턴 + return await queryBuilder.getManyAndCount(); + } +} diff --git a/src/api/phone/service/phone.service.ts b/src/api/phone/service/phone.service.ts index b5a91aa..c8670cc 100644 --- a/src/api/phone/service/phone.service.ts +++ b/src/api/phone/service/phone.service.ts @@ -7,6 +7,7 @@ import CommonResponse from 'src/common/dto/api.response'; import RequestPhoneSaveDto from '../dto/phone.save.dto'; import { BadRequestException } from 'src/exception/customException'; import User from 'src/api/auth/domain/user.entity'; +import RequestPhoneFindDto from '../dto/phone.find.'; @Injectable() export default class PhoneServiceImpl { @@ -21,7 +22,7 @@ export default class PhoneServiceImpl { }); if (findPhone) { - throw new BadRequestException('Exist Phone Number'); + throw new BadRequestException('이미 존재하는 번호 입니다.'); } await this.phoneRepository.save( @@ -37,4 +38,17 @@ export default class PhoneServiceImpl { message: '전화번호를 생성합니다.', }); } + + public async findPhoneList( + dto: RequestPhoneFindDto, + user: User, + ): Promise> { + const [data, count] = await this.phoneRepository.findAll(dto, user.id); + + return CommonResponse.of({ + statusCode: 200, + message: '전화번호 리스트를 조회합니다.', + data: { data, count }, + }); + } } diff --git a/src/response/common/index.ts b/src/response/common/index.ts new file mode 100644 index 0000000..41308e5 --- /dev/null +++ b/src/response/common/index.ts @@ -0,0 +1,108 @@ +import type { + ErrorResponse, + MessageRespons, + SuccessResponse, +} from 'src/types/api'; + +export const createSuccessResponse = (data: SuccessResponse) => { + return { + status: data.statusCode, + description: data.description ? data.description : data.message, + schema: { + properties: { + statusCode: { + type: 'number', + example: data.statusCode, + }, + message: { + type: 'string', + example: data.message, + }, + data: { + type: 'any', + example: data.data, + }, + }, + }, + }; +}; + +export const createMessageResponse = (data: MessageRespons) => { + return { + status: data.statusCode, + description: data.description ? data.description : data.message, + schema: { + properties: { + statusCode: { + type: 'number', + example: data.statusCode, + }, + message: { + type: 'string', + example: data.message, + }, + }, + }, + }; +}; + +export const createErrorResponse = (data: ErrorResponse) => { + return { + status: data.fakeCode ? data.fakeCode : data.statusCode, + description: data.description ? data.description : data.message, + schema: { + properties: { + statusCode: { + type: 'number', + example: data.statusCode, + }, + message: { + type: 'string', + example: data.message, + }, + error: { + type: 'string', + example: data.error, + }, + }, + }, + }; +}; + +export const createServerExceptionResponse = () => { + return { + status: 500, + description: 'Internal server error', + schema: { + properties: { + statusCode: { + type: 'number', + example: 500, + }, + message: { + type: 'string', + example: 'Internal server error', + }, + }, + }, + }; +}; + +export const createUnauthorizedResponse = () => { + return { + status: 401, + description: 'Internal server error', + schema: { + properties: { + statusCode: { + type: 'number', + example: 401, + }, + message: { + type: 'string', + example: 'Unauthorized', + }, + }, + }, + }; +}; diff --git a/src/response/phone.response.ts b/src/response/phone.response.ts new file mode 100644 index 0000000..f274f7d --- /dev/null +++ b/src/response/phone.response.ts @@ -0,0 +1,36 @@ +import { + createErrorResponse, + createMessageResponse, + createSuccessResponse, +} from './common'; + +export const PhoneResponse = { + savePhone: { + 200: createMessageResponse({ + message: '전화번호를 생성합니다.', + statusCode: 200, + }), + 400: createErrorResponse({ + statusCode: 400, + message: '이미 존재하는 번호 입니다.', + error: 'BAD REQUEST', + }), + }, + findPhoneList: { + 200: createSuccessResponse({ + data: { + data: [ + { + createdAt: '2023-09-10T15:56:12.203Z', + id: 3, + name: '김인후', + number: '010631057848', + }, + ], + count: 1, + }, + statusCode: 200, + message: '전화번호 리스트를 조회합니다.', + }), + }, +}; diff --git a/src/types/api.d.ts b/src/types/api.d.ts new file mode 100644 index 0000000..15147f1 --- /dev/null +++ b/src/types/api.d.ts @@ -0,0 +1,17 @@ +export interface Response { + fakeCode?: number; + statusCode: number; + message: string; + description?: string; +} + +export interface SuccessResponse extends Response { + data: any; + statusCode: 201 | 200; +} + +export interface ErrorResponse extends Response { + error: 'BAD REQUEST' | 'NOT FOUND'; +} + +export type MessageRespons = Response; From 77685647416ca72d6f22063abd4fcd0a233e99f8 Mon Sep 17 00:00:00 2001 From: yoona <103414935+yoonali@users.noreply.github.com> Date: Sat, 16 Sep 2023 00:59:39 +0900 Subject: [PATCH 9/9] feat: fix phone --- src/api/auth/domain/user.entity.ts | 4 +- src/api/phone/controller/phone.controller.ts | 29 ++++++++++ src/api/phone/domain/phone.entity.ts | 3 +- src/api/phone/dto/phone.find..ts | 4 +- src/api/phone/repository/phone.repository.ts | 20 +++++-- src/api/phone/service/phone.service.ts | 61 ++++++++++++-------- src/filter/ExceptionFilter.ts | 2 +- src/response/phone.response.ts | 23 +++++++- 8 files changed, 108 insertions(+), 38 deletions(-) diff --git a/src/api/auth/domain/user.entity.ts b/src/api/auth/domain/user.entity.ts index b23a4ca..8a32ef9 100644 --- a/src/api/auth/domain/user.entity.ts +++ b/src/api/auth/domain/user.entity.ts @@ -19,8 +19,8 @@ export default class User extends BaseTimeEntity { @Column({ type: 'varchar', length: 120 }) password: string; - /*@OneToMany(() => Phone, (phone) => phone.user) - phones: Phone[]*/ + @OneToMany(() => Phone, (phone) => phone.user) + phones: Phone[] @Column({ nullable: true }) @Exclude() diff --git a/src/api/phone/controller/phone.controller.ts b/src/api/phone/controller/phone.controller.ts index 736e19c..fdd5355 100644 --- a/src/api/phone/controller/phone.controller.ts +++ b/src/api/phone/controller/phone.controller.ts @@ -8,6 +8,9 @@ import { Get, Query, ValidationPipe, + Param, + Patch, + Delete, } from '@nestjs/common'; // ** Swagger Imports @@ -60,6 +63,32 @@ export default class PhoneController { ): Promise> { return await this.phoneService.findPhoneList(params, user); } + + @ApiBearerAuth('access-token') + @ApiOperation({ summary: '전화번호 업데이트' }) + @ApiResponse(PhoneResponse.updatePhone[200]) + @UseGuards(JwtAccessGuard) + @Patch() + public async updatePhone( + @Param('id') id:number, + @Body() dto: RequestPhoneSaveDto, + @Req() { user }: RequestWithUsernDto, + ): Promise> { + return await this.phoneService.updatePhone(id, dto, user); + } + + @ApiBearerAuth('access-token') + @ApiOperation({ summary: '전화번호 삭제' }) + @ApiResponse(PhoneResponse.deletePhone[200]) + @UseGuards(JwtAccessGuard) + @Delete() + public async deletePhone( + @Param('id') id:number, + @Body() dto: RequestPhoneSaveDto, + @Req() { user }: RequestWithUsernDto, + ): Promise> { + return await this.phoneService.deletePhone(id, user); + } } diff --git a/src/api/phone/domain/phone.entity.ts b/src/api/phone/domain/phone.entity.ts index 55e27d7..647f4c6 100644 --- a/src/api/phone/domain/phone.entity.ts +++ b/src/api/phone/domain/phone.entity.ts @@ -17,7 +17,6 @@ export default class Phone extends BaseTimeEntity { @Column({ type: 'varchar', length: 30, name: 'phone_number' }) number: string; - - @ManyToOne(() => User, (user) => user.id) + @ManyToOne(() => User, (user) => user.phones) user: User } diff --git a/src/api/phone/dto/phone.find..ts b/src/api/phone/dto/phone.find..ts index 90e4c68..ac50ded 100644 --- a/src/api/phone/dto/phone.find..ts +++ b/src/api/phone/dto/phone.find..ts @@ -8,12 +8,12 @@ import { IsOptional, IsString } from 'class-validator'; import RequestPagingDto from 'src/common/dto/paging.dto'; export default class RequestPhoneFindDto extends RequestPagingDto { - @ApiProperty({ example: '01063057848', required: false }) + @ApiProperty({ example: '01033618490', required: false }) @IsOptional() @IsString() number: string; - @ApiProperty({ example: '김인후', required: false }) + @ApiProperty({ example: '임유나', required: false }) @IsOptional() @IsString() name: string; diff --git a/src/api/phone/repository/phone.repository.ts b/src/api/phone/repository/phone.repository.ts index 7c3aafb..fec7f70 100644 --- a/src/api/phone/repository/phone.repository.ts +++ b/src/api/phone/repository/phone.repository.ts @@ -1,5 +1,5 @@ // ** Typeorm Imports -import { Repository } from 'typeorm'; +import { QueryBuilder, Repository } from 'typeorm'; // ** Custom Module Imports import { CustomRepository } from 'src/repository/typeorm-ex.decorator'; @@ -20,19 +20,31 @@ export default class PhoneRepository extends Repository { // ** Request에 name 있으면 where if (dto.name) { - queryBuilder.where('ho.userName LIKE :name', { + queryBuilder.where('phone.name LIKE :name', { name: `%${dto.name}%`, }); } // ** Request에 number 있으면 where if (dto.number) { - queryBuilder.where('ho.userName LIKE :name', { - name: `%${dto.name}%`, + queryBuilder.where('phone.number LIKE :number', { + number: `%${dto.number}%`, }); } // ** 쿼리 결과 리턴 return await queryBuilder.getManyAndCount(); + } + public async checkUser(id: number, userId: number) { + const result = await this.createQueryBuilder('phone') + .select(['phone.id', 'phone.userId', 'phone.name', 'phone.number', 'phone.createdAt']) // 원하는 컬럼 선택 + .where('phone.id = :id', { id }) + .andWhere('phone.userId = :userId', { userId }) + .getOne(); + + return result; + } + + } diff --git a/src/api/phone/service/phone.service.ts b/src/api/phone/service/phone.service.ts index 17d722e..85e2405 100644 --- a/src/api/phone/service/phone.service.ts +++ b/src/api/phone/service/phone.service.ts @@ -1,5 +1,5 @@ // ** Nest Imports -import { Injectable, NotFoundException, UseGuards } from '@nestjs/common'; +import { Injectable, NotFoundException, UnauthorizedException, UseGuards } from '@nestjs/common'; import PhoneRepository from '../repository/phone.repository'; // ** enum, dto, entity, types Imports @@ -7,14 +7,16 @@ import CommonResponse from 'src/common/dto/api.response'; import RequestPhoneSaveDto from '../dto/phone.save.dto'; import { BadRequestException } from 'src/exception/customException'; import { RolesGuard } from 'src/roles/roles.guard'; +import User from 'src/api/auth/domain/user.entity'; +import RequestPhoneFindDto from '../dto/phone.find.'; @Injectable() export default class PhoneServiceImpl { constructor(private readonly phoneRepository: PhoneRepository) {} - @UseGuards(RolesGuard) public async savePhone( dto: RequestPhoneSaveDto, + user: User ): Promise> { const findPhone = await this.phoneRepository.findOne({ where: { number: dto.phone }, @@ -28,6 +30,7 @@ export default class PhoneServiceImpl { this.phoneRepository.create({ name: dto.name, number: dto.phone, + user }), ); @@ -38,61 +41,69 @@ export default class PhoneServiceImpl { } //단일 조회 - @UseGuards(RolesGuard) public async findOnePhone( id: number, + user: User ): Promise> { const phone = await this.phoneRepository.findOne({where: {id,}}); if (!phone) { throw new NotFoundException('Phone not found'); } - - return CommonResponse.of({ - statusCode: 200, - message: '전화번호를 조회합니다.', - data: phone - }); + + if(id !== user.id){ + throw new UnauthorizedException("You do not have permission to access this phone") + } + else{ + return CommonResponse.of({ + statusCode: 200, + message: '전화번호를 조회합니다.', + data: phone + }); + } } //전체 조회 - @UseGuards(RolesGuard) - public async findAllPhone(): Promise>{ - - const phones = await this.phoneRepository.find(); + public async findPhoneList( + dto: RequestPhoneFindDto, + user: User, + ): Promise> { + const [data, count] = await this.phoneRepository.findAll(dto, user.id); return CommonResponse.of({ statusCode: 200, - message: '전화번호를 조회합니다.', - data: phones + message: '전화번호 리스트를 조회합니다.', + data: { data, count }, }); } - @UseGuards(RolesGuard) + public async updatePhone( - id : number, + id: number, dto: RequestPhoneSaveDto, - ): Promise>{ - const updatePhone = await this.phoneRepository.findOne({where: {id,}}); - + user: User + ): Promise> { + const updatePhone = await this.phoneRepository.checkUser(id,user.id); + if (!updatePhone) { throw new NotFoundException('Phone not found'); } - - await this.phoneRepository.update(id, dto); - + + await this.phoneRepository.update({ id }, dto); + return CommonResponse.of({ statusCode: 200, message: '전화번호를 업데이트합니다.', }); } + - @UseGuards(RolesGuard) public async deletePhone( id : number, + user: User, ): Promise>{ - const deletePhone = await this.phoneRepository.findOne({where: {id,}}) + const deletePhone = await this.phoneRepository.checkUser(id, user.id) if (!deletePhone) { throw new NotFoundException('Phone not found'); diff --git a/src/filter/ExceptionFilter.ts b/src/filter/ExceptionFilter.ts index 678bdf6..e53757d 100644 --- a/src/filter/ExceptionFilter.ts +++ b/src/filter/ExceptionFilter.ts @@ -6,7 +6,7 @@ import { HttpException, } from '@nestjs/common'; -@Catch() +@Catch(HttpException) export class AllExceptionsFilter implements ExceptionFilter { private logger = new Logger(); diff --git a/src/response/phone.response.ts b/src/response/phone.response.ts index f274f7d..fb24d1a 100644 --- a/src/response/phone.response.ts +++ b/src/response/phone.response.ts @@ -23,8 +23,8 @@ export const PhoneResponse = { { createdAt: '2023-09-10T15:56:12.203Z', id: 3, - name: '김인후', - number: '010631057848', + name: '임유나', + number: '01033618490', }, ], count: 1, @@ -33,4 +33,23 @@ export const PhoneResponse = { message: '전화번호 리스트를 조회합니다.', }), }, + updatePhone: { + 200: createSuccessResponse({ + data: { + createdAt: '2023-09-10T15:56:12.203Z', + id: 3, + name: '새로운 이름', + number: '01033618490', + }, + statusCode: 200, + message: '전화번호가 업데이트되었습니다.', + }), + }, + + deletePhone: { + 200: createMessageResponse({ + message: '전화번호를 삭제합니다.', + statusCode: 200, + }), + }, };