diff --git a/src/auth/guards/AccessToken.guard.ts b/src/auth/guards/AccessToken.guard.ts index 5c702d8..7d30e78 100644 --- a/src/auth/guards/AccessToken.guard.ts +++ b/src/auth/guards/AccessToken.guard.ts @@ -20,6 +20,11 @@ export class AccessTokenGuard implements CanActivate { canActivate( context: ExecutionContext ): boolean | Promise | Observable { + //@NoAuth 사용시 해당 부분에서 AccessTokenGuard 사용 해제시킴 + const noAuth = this.reflector.get('no-auth', context.getHandler()) + if (noAuth) { + return true; + } const request = context.switchToHttp().getRequest(); return this.validateRequest(request, context); } diff --git a/src/auth/guards/NoAuth.guard.ts b/src/auth/guards/NoAuth.guard.ts new file mode 100644 index 0000000..832620b --- /dev/null +++ b/src/auth/guards/NoAuth.guard.ts @@ -0,0 +1,5 @@ +import { SetMetadata } from '@nestjs/common' + +/** AccessTokenGuard 가 포함된 controller 내부에서 + * AccessToken 없이 접근할 수 있도록 해주는 데코레이터입니다 */ +export const NoAuth = () => SetMetadata('no-auth', true) \ No newline at end of file diff --git a/src/database/repositories/comment.repository.ts b/src/database/repositories/comment.repository.ts index add2cc6..69aba8b 100644 --- a/src/database/repositories/comment.repository.ts +++ b/src/database/repositories/comment.repository.ts @@ -19,7 +19,7 @@ export class CommentRepository { @InjectRepository(Comment) private commentRepository: Repository ) {} - + // 응원 댓글 생성 async makeComment(user: User, requestCommentDto: RequestCommentDto) { const { content, nickName } = requestCommentDto; @@ -27,46 +27,64 @@ export class CommentRepository { const comment = this.commentRepository.create({ nickName, content, - user: ret_user, - }) + user: ret_user + }); await this.commentRepository.save(comment); const ret_comment = { ...comment, iUserId: ret_user.id - } + }; return plainToInstance(ResponseCommentDto, ret_comment); } // 응원 댓글 조회 async getAllComment(userId: number, scrollOptionsDto: ScrollOptionsDto) { - const queryBuilder = await this.commentRepository.createQueryBuilder('comment'); - + const { lastId } = scrollOptionsDto; + const queryBuilder = this.commentRepository.createQueryBuilder('comment'); + // 한 번에 조회하는 댓글 수 + const take = 20; queryBuilder .leftJoinAndSelect('comment.user', 'user') - .orderBy('comment.createdAt', "DESC") - .skip(scrollOptionsDto.skip) - .take(scrollOptionsDto.take); - - const itemCount = await queryBuilder.getCount(); + .orderBy('comment.createdAt', 'DESC') + .limit(take); + + if (lastId) { + queryBuilder.where('comment.id < :lastId', { lastId: lastId }); + } + const { entities } = await queryBuilder.getRawAndEntities(); - console.log(entities); - const lastId = entities[entities.length - 1].id; - const scrollMetaDto = new ScrollMetaDto(scrollOptionsDto, itemCount, lastId); - + // ScrollMetaDto의 인자 값 : checkLastId, lastPage + let checkLastId: null | number; + let lastPage = false; + // lastId값 초기화 + if (entities.length) { + checkLastId = entities[entities.length - 1].id; + } else { + checkLastId = null; + } + // 마지막 페이지인지 확인 + if (entities.length < take) { + lastPage = true; + } else { + lastPage = false; + } + + const scrollMetaDto = new ScrollMetaDto(checkLastId, lastPage); + return new ResponseScrollCommentDto(entities, scrollMetaDto); } // 댓글 삭제 async deleteComment(id: number) { - const comment = await this.commentRepository.findOne({ where: {id: id}}) + const comment = await this.commentRepository.findOne({ where: { id: id } }); const result = await this.commentRepository.delete(id); // 해당 아이디가 존재하는지 확인 후 없으면 오류 메시지 출력 - if (result.affected === 0) { - throw new NotFoundException(`해당 id ${id}를 찾을 수 없습니다.`); - } + if (result.affected === 0) { + throw new NotFoundException(`해당 id ${id}를 찾을 수 없습니다.`); + } return plainToInstance(CommentDto, comment); } -} \ No newline at end of file +} diff --git a/src/database/repositories/ticket.repository.ts b/src/database/repositories/ticket.repository.ts index 6684c63..646434e 100644 --- a/src/database/repositories/ticket.repository.ts +++ b/src/database/repositories/ticket.repository.ts @@ -147,6 +147,12 @@ export class TicketRepository { .getMany(); } + + /** DB에 저장된 티켓의 개수를 반환한다 */ + async countTicket(): Promise { + return await this.ticketRepository.count(); + } + /** * 해당 티켓을 저장한다 * @param ticket 저장할 티켓 diff --git a/src/tickets/dtos/ticket-count.dto.ts b/src/tickets/dtos/ticket-count.dto.ts new file mode 100644 index 0000000..a9bcd2b --- /dev/null +++ b/src/tickets/dtos/ticket-count.dto.ts @@ -0,0 +1,13 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { Expose } from 'class-transformer'; +import { IsNumber } from 'class-validator'; + +/** /tickets/count에 대한 Swagger 응답을 위한 dto 입니다 */ +export class TicketCountDto { + @ApiProperty({ + description: 'DB에 저장된 티켓의 총 개수', + }) + @IsNumber() + @Expose() + readonly count: number; +} diff --git a/src/tickets/tickets.controller.ts b/src/tickets/tickets.controller.ts index 4930574..098c7b7 100644 --- a/src/tickets/tickets.controller.ts +++ b/src/tickets/tickets.controller.ts @@ -34,6 +34,8 @@ import { TicketFindDto } from './dtos/ticket-find.dto'; import { UpdateTicketStatusDto } from './dtos/update-ticket-status.dto'; import { SuccessResponse } from 'src/common/decorators/SuccessResponse.decorator'; import { PageDto } from 'src/common/dtos/page/page.dto'; +import { NoAuth } from 'src/auth/guards/NoAuth.guard'; +import { TicketCountDto } from './dtos/ticket-count.dto'; @ApiTags('tickets') @ApiBearerAuth('accessToken') @@ -141,6 +143,21 @@ export class TicketsController { return this.ticketService.createTicket(createTicketDto); } + @ApiOperation({ + summary: '[랜딩페이지] 티켓 개수를 반환한다' + }) + @ApiResponse({ + status: 200, + description: '요청 성공시', + type: TicketCountDto + }) + @NoAuth() + @Get('/count') + async getTicketCount() { + const count = await this.ticketService.countTicket(); + return { count: count }; + } + @ApiOperation({ summary: '해당 uuid를 포함하는 티켓을 가져온다, req.user 필요' }) @@ -221,7 +238,7 @@ export class TicketsController { description: '어드민이 아닐 경우' }) @Roles(Role.Admin) - @Delete('/delete/:uuid') + @Delete('/:uuid/delete') deleteTicketByUuid(@Param('uuid') ticketUuid: string) { return this.ticketService.deleteTicketByUuid(ticketUuid); } diff --git a/src/tickets/tickets.service.ts b/src/tickets/tickets.service.ts index af22bab..3d58b4b 100644 --- a/src/tickets/tickets.service.ts +++ b/src/tickets/tickets.service.ts @@ -50,7 +50,7 @@ export class TicketsService { //어드민이거나 Ticket.user.id === user.id 일때만 리턴 if (ticket.user.id !== user.id && user.role !== Role.Admin) { - throw new UnauthorizedException('해등 티켓에 대한 접근 권한이 없습니다'); + throw new UnauthorizedException('해당 티켓에 대한 접근 권한이 없습니다'); } return ticket; @@ -88,6 +88,10 @@ export class TicketsService { ); } + async countTicket(): Promise { + return await this.ticketRepository.countTicket(); + } + /** * 어드민이 티켓을 찍었을때 연결할 url에서 검증을 완료한 후 소켓 메세지 전송 * @param uuid TicketValidationDto -> uuid diff --git a/src/users/dtos/Scroll/ScrollMeta.dto.ts b/src/users/dtos/Scroll/ScrollMeta.dto.ts index 780eb8f..197bcb8 100644 --- a/src/users/dtos/Scroll/ScrollMeta.dto.ts +++ b/src/users/dtos/Scroll/ScrollMeta.dto.ts @@ -3,36 +3,16 @@ import { Expose } from 'class-transformer'; import { ScrollOptionsDto } from './ScrollOptions.dto'; export class ScrollMetaDto { - @ApiProperty({ description: '페이지 정보입니다.' }) - @Expose() - readonly page: number; - - @ApiProperty({ description: '몇개를 받아가는지 한페이지 당 원소갯수' }) - @Expose() - readonly take: number; - - @ApiProperty({ description: '총 아이템 숫자' }) - @Expose() - readonly itemCount: number; - - @ApiProperty({ description: '총 페이지 숫자' }) - @Expose() - readonly pageCount: number; - @ApiProperty({ description: '현재 페이지의 마지막 id', nullable: true }) @Expose() - readonly lastId: number; + readonly lastId: number | null; - @ApiProperty({ description: '현재 페이지가 마지막페이지인지에 대한 정보'}) + @ApiProperty({ description: '현재 페이지가 마지막페이지인지에 대한 정보' }) @Expose() readonly lastPage: boolean; - constructor(scrollOptionsDto: ScrollOptionsDto, itemCount: number, lastId: number) { - this.page = scrollOptionsDto.page; - this.take = scrollOptionsDto.take; - this.itemCount = itemCount; - this.pageCount = Math.ceil(this.itemCount / this.take); + constructor(lastId: number | null, lastPage: boolean) { this.lastId = lastId; - this.lastPage = this.page >= this.pageCount; + this.lastPage = lastPage; } } diff --git a/src/users/dtos/Scroll/ScrollOptions.dto.ts b/src/users/dtos/Scroll/ScrollOptions.dto.ts index c26d823..124ba3c 100644 --- a/src/users/dtos/Scroll/ScrollOptions.dto.ts +++ b/src/users/dtos/Scroll/ScrollOptions.dto.ts @@ -1,33 +1,12 @@ -import { ApiPropertyOptional } from '@nestjs/swagger'; +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; import { Expose, Type } from 'class-transformer'; import { IsInt, IsOptional, Max, Min } from 'class-validator'; export class ScrollOptionsDto { @ApiPropertyOptional({ - minimum: 1, - default: 1 + description: '현재 페이지의 마지막 id', + nullable: true }) - @Type(() => Number) - @IsInt() - @Min(1) - @IsOptional() @Expose() - readonly page: number = 1; - - @ApiPropertyOptional({ - minimum: 1, - maximum: 50, - default: 20 - }) - @Type(() => Number) - @IsInt() - @Min(1) - @Max(50) - @IsOptional() - @Expose() - readonly take: number = 10; - - get skip(): number { - return (this.page - 1) * this.take; - } -} \ No newline at end of file + readonly lastId: number; +} diff --git a/src/users/users.service.spec.ts b/src/users/users.service.spec.ts index 2b4b371..2850dd2 100644 --- a/src/users/users.service.spec.ts +++ b/src/users/users.service.spec.ts @@ -1,15 +1,21 @@ import { Test, TestingModule } from '@nestjs/testing'; import { MockType } from 'src/common/funcs/mockType'; +import { CommentRepository } from 'src/database/repositories/comment.repository'; import { UserRepository } from 'src/database/repositories/user.repository'; import { UsersService } from './users.service'; -export const repositoryMockFactory: () => MockType = jest.fn( +const UserRepositoryMockFactory: () => MockType = jest.fn( + () => ({ + findAll: jest.fn(entity => entity) + // ... + }) +); +const CommentRepositoryMockFactory: () => MockType = jest.fn( () => ({ findAll: jest.fn(entity => entity) // ... }) ); - describe('UsersService', () => { let service: UsersService; @@ -19,7 +25,11 @@ describe('UsersService', () => { UsersService, { provide: UserRepository, - useFactory: repositoryMockFactory + useFactory: UserRepositoryMockFactory + }, + { + provide: CommentRepository, + useFactory: CommentRepositoryMockFactory } ] }).compile();