diff --git a/backend/src/main.ts b/backend/src/main.ts index 5eac647..9ead653 100644 --- a/backend/src/main.ts +++ b/backend/src/main.ts @@ -11,6 +11,7 @@ async function bootstrap() { .setTitle('Cabi Admin v2 API') .setDescription('Cabi Admin v2 API 명세') .setVersion('2.0') + .addBearerAuth() .build(); const SwaggerDocument = SwaggerModule.createDocument(app, swaggerConfig); SwaggerModule.setup('docs', app, SwaggerDocument); diff --git a/backend/src/v3/log/dto/cabinet-lent-log.dto.ts b/backend/src/v3/log/dto/cabinet-lent-log.dto.ts new file mode 100644 index 0000000..75c64d4 --- /dev/null +++ b/backend/src/v3/log/dto/cabinet-lent-log.dto.ts @@ -0,0 +1,57 @@ +import { ApiProperty } from '@nestjs/swagger'; + +export class CabinetLentLogDto { + @ApiProperty({ + description: '42 고유 ID', + example: 12345, + }) + user_id: number; // 42 고유 ID + + @ApiProperty({ + description: '42 로그인 ID', + example: 'joopark', + }) + intra_id: string; // 42 로그인 ID + + @ApiProperty({ + description: '캐비넷 고유 ID', + example: 1234, + }) + cabinet_id: number; // 캐비넷 고유 ID + + @ApiProperty({ + description: '사물함에 붙어있는 숫자', + example: 12, + }) + cabinet_num: number; // 사물함에 붙어있는 숫자 + + @ApiProperty({ + description: '사물함이 존재하는 건물 이름', + example: '새롬관', + }) + location: string; // 사물함 건물 + + @ApiProperty({ + description: '사물함이 존재하는 층수', + example: 2, + }) + floor: number; // 사물함 층수 + + @ApiProperty({ + description: '사물함의 섹션 종류 (오아시스 등)', + example: 'Oasis', + }) + section: string; // 사물함의 섹션 종류 (오아시스 등) + + @ApiProperty({ + description: '대여한 시간', + example: '2022-08-24 13:03:03', + }) + lent_time: Date; // 대여한 시간 + + @ApiProperty({ + description: '반납 시간', + example: '2022-08-24 13:03:03', + }) + return_time: Date; // 반납 시간 +} diff --git a/backend/src/v3/log/dto/log.pagenation.dto.ts b/backend/src/v3/log/dto/log.pagenation.dto.ts new file mode 100644 index 0000000..765f65a --- /dev/null +++ b/backend/src/v3/log/dto/log.pagenation.dto.ts @@ -0,0 +1,16 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { CabinetLentLogDto } from './cabinet-lent-log.dto'; + +export class LogPagenationDto { + @ApiProperty({ + description: '로그 배열', + type: [CabinetLentLogDto], + }) + result: CabinetLentLogDto[]; // 대여 정보 + + @ApiProperty({ + description: 'DB에 저장된 총 결과의 길이', + example: 42, + }) + total_length: number; // DB에 저장된 총 결과의 길이 +} diff --git a/backend/src/v3/log/log.controller.ts b/backend/src/v3/log/log.controller.ts new file mode 100644 index 0000000..e0f6022 --- /dev/null +++ b/backend/src/v3/log/log.controller.ts @@ -0,0 +1,120 @@ +import { + Controller, + Get, + Logger, + Param, + ParseIntPipe, + Query, + UseGuards, +} from '@nestjs/common'; +import { + ApiBadRequestResponse, + ApiBearerAuth, + ApiOkResponse, + ApiOperation, + ApiParam, + ApiQuery, + ApiTags, + ApiUnauthorizedResponse, +} from '@nestjs/swagger'; +import { JWTAuthGuard } from 'src/auth/auth.guard'; +import { LogPagenationDto } from './dto/log.pagenation.dto'; +import { LogService } from './log.service'; + +@ApiTags('(V3) 로그') +@ApiBearerAuth() +@ApiUnauthorizedResponse({ + description: '로그아웃 상태', +}) +@Controller({ + version: '3', + path: 'log', +}) +@UseGuards(JWTAuthGuard) +export class LogController { + constructor(private cabinetService: LogService) {} + + private logger = new Logger(LogController.name); + + @ApiOperation({ + summary: '특정 유저의 사물함 대여 기록 반환', + description: '특정 유저의 사물함 대여 기록을 반환합니다.', + }) + @ApiQuery({ + name: 'index', + description: '(페이지네이션) 가져올 데이터 인덱스', + }) + @ApiQuery({ + name: 'length', + description: '(페이지네이션) 가져올 데이터 길이', + }) + @ApiParam({ + name: 'user_id', + description: '유저 고유 ID', + }) + @ApiOkResponse({ + type: LogPagenationDto, + description: + '파라미터로 받은 사물함의 정보를 CabinetInfoResponseDto 형식으로 받아옵니다', + }) + @ApiBadRequestResponse({ + description: '존재하지 않는 유저, 잘못된 페이지네이션 요청', + }) + @Get('/user/:user_id') + async getUserLogs( + @Param('user_id', ParseIntPipe) user_id: number, + @Query('index', ParseIntPipe) index: number, + @Query('length', ParseIntPipe) length: number, + ): Promise { + this.logger.debug(`Called ${this.getUserLogs.name}`); + try { + return await this.cabinetService.getUserLogs(user_id, index, length); + } catch (err) { + this.logger.error(err); + throw err; + } + } + + @ApiOperation({ + summary: '특정 사물함의 대여 기록 반환', + description: '특정 사물함의 대여 기록을 반환합니다.', + }) + @ApiQuery({ + name: 'index', + description: '(페이지네이션) 가져올 데이터 인덱스', + }) + @ApiQuery({ + name: 'length', + description: '(페이지네이션) 가져올 데이터 길이', + }) + @ApiParam({ + name: 'cabinet_id', + description: '사물함 고유 ID', + }) + @ApiOkResponse({ + type: LogPagenationDto, + description: + '파라미터로 받은 사물함의 정보를 CabinetInfoResponseDto 형식으로 받아옵니다', + }) + @ApiBadRequestResponse({ + description: '존재하지 않는 사물함, 잘못된 페이지네이션 요청', + }) + @Get('/cabinet/:cabinet_id') + async getCabinetLogs( + @Param('cabinet_id', ParseIntPipe) cabinet_id: number, + @Query('index', ParseIntPipe) index: number, + @Query('length', ParseIntPipe) length: number, + ): Promise { + this.logger.debug(`Called ${this.getCabinetLogs.name}`); + try { + return await this.cabinetService.getCabinetLogs( + cabinet_id, + index, + length, + ); + } catch (err) { + this.logger.error(err); + throw err; + } + } +} diff --git a/backend/src/v3/log/log.module.ts b/backend/src/v3/log/log.module.ts new file mode 100644 index 0000000..f72c769 --- /dev/null +++ b/backend/src/v3/log/log.module.ts @@ -0,0 +1,20 @@ +import { Module } from '@nestjs/common'; +import { TypeOrmModule } from '@nestjs/typeorm'; +import { AuthModule } from 'src/auth/auth.module'; +import LentLog from 'src/entities/lent.log.entity'; +import { LogController } from './log.controller'; +import { LogService } from './log.service'; +import { LogRepository } from './repository/log.repository'; + +const repo = { + provide: 'ILogRepository', + useClass: LogRepository, +}; + +@Module({ + imports: [AuthModule, TypeOrmModule.forFeature([LentLog])], + exports: [LogService], + controllers: [LogController], + providers: [LogService, repo], +}) +export class LogModule {} diff --git a/backend/src/v3/log/log.service.ts b/backend/src/v3/log/log.service.ts new file mode 100644 index 0000000..c676474 --- /dev/null +++ b/backend/src/v3/log/log.service.ts @@ -0,0 +1,65 @@ +import { + BadRequestException, + Inject, + Injectable, + Logger, +} from '@nestjs/common'; +import { CabinetLentLogDto } from './dto/cabinet-lent-log.dto'; +import { LogPagenationDto } from './dto/log.pagenation.dto'; +import { ILogRepository } from './repository/log.repository.interface'; + +@Injectable() +export class LogService { + private logger = new Logger(LogService.name); + + constructor( + @Inject('ILogRepository') + private logRepository: ILogRepository, + ) {} + + /** + * 특정 유저의 사물함 대여 기록을 반환합니다. + * + * @param user_id 유저 고유 ID + * @param index 가져올 데이터 인덱스 + * @param length 가져올 데이터 길이 + * @returns LogPagenationDto + * @throw HTTPError + */ + async getUserLogs( + user_id: number, + index: number, + length: number, + ): Promise { + const result = await this.logRepository.getUserLogs(user_id, index, length); + if (index !== 0 && length !== 0 && result.total_length === 0) { + throw new BadRequestException('Index Error'); + } + return result; + } + + /** + * 특정 사물함의 대여 기록을 반환합니다. + * + * @param cabinet_id 캐비넷 고유 ID + * @param index 가져올 데이터 인덱스 + * @param length 가져올 데이터 길이 + * @returns LogPagenationDto + * @throw HTTPError + */ + async getCabinetLogs( + cabinet_id: number, + index: number, + length: number, + ): Promise { + const result = await this.logRepository.getCabinetLogs( + cabinet_id, + index, + length, + ); + if (index !== 0 && length !== 0 && result.total_length === 0) { + throw new BadRequestException('Index Error'); + } + return result; + } +} diff --git a/backend/src/v3/log/repository/log.repository.interface.ts b/backend/src/v3/log/repository/log.repository.interface.ts new file mode 100644 index 0000000..a4181fa --- /dev/null +++ b/backend/src/v3/log/repository/log.repository.interface.ts @@ -0,0 +1,30 @@ +import { CabinetLentLogDto } from '../dto/cabinet-lent-log.dto'; +import { LogPagenationDto } from '../dto/log.pagenation.dto'; + +export interface ILogRepository { + /** + * 특정 유저의 사물함 대여 기록을 반환합니다. + * + * @param user_id 유저 고유 ID + * @param index 가져올 데이터 인덱스 + * @param length 가져올 데이터 길이 + */ + getUserLogs( + user_id: number, + index: number, + length: number, + ): Promise; + + /** + * 특정 사물함의 대여 기록을 반환합니다. + * + * @param cabinet_id 캐비넷 고유 ID + * @param index 가져올 데이터 인덱스 + * @param length 가져올 데이터 길이 + */ + getCabinetLogs( + cabinet_id: number, + index: number, + length: number, + ): Promise; +} diff --git a/backend/src/v3/log/repository/log.repository.ts b/backend/src/v3/log/repository/log.repository.ts new file mode 100644 index 0000000..6bb945a --- /dev/null +++ b/backend/src/v3/log/repository/log.repository.ts @@ -0,0 +1,105 @@ +import { InjectRepository } from '@nestjs/typeorm'; +import Cabinet from 'src/entities/cabinet.entity'; +import LentLog from 'src/entities/lent.log.entity'; +import { Repository } from 'typeorm'; +import { LogPagenationDto } from '../dto/log.pagenation.dto'; +import { ILogRepository } from './log.repository.interface'; + +export class LogRepository implements ILogRepository { + constructor( + @InjectRepository(LentLog) + private lentlogRepository: Repository, + ) {} + + async getUserLogs( + user_id: number, + index: number, + length: number, + ): Promise { + // lent_log 테이블과 cabinet 테이블을 조인함. + const result = await this.lentlogRepository + .createQueryBuilder('ll') + .select([ + 'll.log_user_id', + 'll.log_intra_id', + 'll.log_cabinet_id', + 'll.lent_time', + 'll.return_time', + 'COUNT(*) OVER () AS cnt', + ]) + .leftJoin(Cabinet, 'c', 'll.log_cabinet_id = c.cabinet_id') + .addSelect([ + 'c.cabinet_id', + 'c.cabinet_num', + 'c.location', + 'c.section', + 'c.floor', + ]) + .where('ll.log_user_id = :user_id', { user_id }) + .limit(length) + .offset(index) + .orderBy('ll.lent_time', 'ASC') + .execute(); + const rtn = { + result: result.map((r) => ({ + user_id: r.ll_log_user_id, + intra_id: r.ll_log_intra_id, + cabinet_id: r.ll_log_cabinet_id, + cabinet_num: r.c_cabinet_num, + location: r.c_location, + floor: r.c_floor, + section: r.c_section, + lent_time: r.ll_lent_time, + return_time: r.ll_return_time, + })), + total_length: result.length !== 0 ? parseInt(result[0].cnt, 10) : 0, + }; + return rtn; + } + + async getCabinetLogs( + cabinet_id: number, + index: number, + length: number, + ): Promise { + // lent_log 테이블과 cabinet 테이블을 조인함. + const result = await this.lentlogRepository + .createQueryBuilder('ll') + .select([ + 'll.log_user_id', + 'll.log_intra_id', + 'll.log_cabinet_id', + 'll.lent_time', + 'll.return_time', + 'COUNT(*) OVER () AS cnt', + ]) + .leftJoin(Cabinet, 'c', 'll.log_cabinet_id = c.cabinet_id') + .addSelect([ + 'c.cabinet_id', + 'c.cabinet_num', + 'c.location', + 'c.section', + 'c.floor', + ]) + .where('ll.log_cabinet_id = :cabinet_id', { cabinet_id }) + .limit(length) + .offset(index) + .orderBy('ll.lent_time', 'ASC') + .execute(); + const rtn = { + result: result.map((r) => ({ + user_id: r.ll_log_user_id, + intra_id: r.ll_log_intra_id, + cabinet_id: r.ll_log_cabinet_id, + cabinet_num: r.c_cabinet_num, + location: r.c_location, + floor: r.c_floor, + section: r.c_section, + lent_time: r.ll_lent_time, + return_time: r.ll_return_time, + })), + total_length: result.length !== 0 ? parseInt(result[0].cnt, 10) : 0, + }; + return rtn; + } +} diff --git a/backend/src/v3/v3.module.ts b/backend/src/v3/v3.module.ts index 1f0fbdc..a4b0e86 100644 --- a/backend/src/v3/v3.module.ts +++ b/backend/src/v3/v3.module.ts @@ -1,12 +1,20 @@ import { Module } from '@nestjs/common'; import { AuthModule } from 'src/auth/auth.module'; import { CabinetModule } from './cabinet/cabinet.module'; +import { LogModule } from './log/log.module'; import { ReturnModule } from './return/return.module'; import { SearchModule } from './search/search.module'; import { UserModule } from './user/user.module'; @Module({ - imports: [AuthModule, CabinetModule, ReturnModule, SearchModule, UserModule], + imports: [ + AuthModule, + CabinetModule, + ReturnModule, + SearchModule, + UserModule, + LogModule, + ], controllers: [], providers: [], })