diff --git a/backend/src/main.ts b/backend/src/main.ts index ae49ff4..616fe3f 100644 --- a/backend/src/main.ts +++ b/backend/src/main.ts @@ -13,6 +13,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/search/dto/blocked-user-info.dto.ts b/backend/src/v3/search/dto/blocked-user-info.dto.ts new file mode 100644 index 0000000..a7c96ea --- /dev/null +++ b/backend/src/v3/search/dto/blocked-user-info.dto.ts @@ -0,0 +1,27 @@ +import { ApiProperty } from '@nestjs/swagger'; + +export class BlockedUserInfoDto { + @ApiProperty({ + description: '42 고유 ID', + example: 12345, + }) + user_id: number; // 42 고유 ID + + @ApiProperty({ + description: '42 로그인 ID', + example: 'joopark', + }) + intra_id: string; // 42 로그인 ID + + @ApiProperty({ + description: '차단당한 시간', + example: new Date(), + }) + banned_date: Date; // 차단당한 시간 + + @ApiProperty({ + description: '차단 풀리는 시간', + example: new Date(), + }) + unbanned_date: Date; // 차단 풀리는 시간 +} diff --git a/backend/src/v3/search/dto/blocked-user-info.pagenation.dto.ts b/backend/src/v3/search/dto/blocked-user-info.pagenation.dto.ts new file mode 100644 index 0000000..9c7e5e2 --- /dev/null +++ b/backend/src/v3/search/dto/blocked-user-info.pagenation.dto.ts @@ -0,0 +1,16 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { BlockedUserInfoDto } from './blocked-user-info.dto'; + +export class BlockedUserInfoPagenationDto { + @ApiProperty({ + description: '차단당한 유저 정보 배열', + type: [BlockedUserInfoDto], + }) + result: BlockedUserInfoDto[]; // 차단당한 유저 정보 배열 + + @ApiProperty({ + description: 'DB에 저장된 총 결과의 길이', + example: 42, + }) + total_length: number; // DB에 저장된 총 결과의 길이 +} diff --git a/backend/src/v3/search/dto/broken-cabinet-info.dto.ts b/backend/src/v3/search/dto/broken-cabinet-info.dto.ts new file mode 100644 index 0000000..b555c6f --- /dev/null +++ b/backend/src/v3/search/dto/broken-cabinet-info.dto.ts @@ -0,0 +1,41 @@ +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; +import LentType from 'src/enums/lent.type.enum'; + +export class BrokenCabinetInfoDto { + @ApiProperty({ + description: '캐비넷 고유 ID', + example: 1234, + }) + cabinet_id: number; // 캐비넷 고유 ID + + @ApiProperty({ + description: '사물함에 붙어있는 숫자', + example: 12, + }) + cabinet_num: number; // 사물함에 붙어있는 숫자 + + @ApiProperty({ + description: '사물함의 종류 (개인, 공유, 동아리)', + enum: LentType, + example: 'PRIVATE', + }) + lent_type: LentType; // 사물함의 종류 (개인, 공유, 동아리) + + @ApiProperty({ + description: '고장 사유', + example: '잠금장치 고장', + }) + note: string; // 고장 사유 + + @ApiProperty({ + description: '해당 사물함을 대여할 수 있는 최대 유저 수', + example: 3, + }) + max_user: number; // 해당 사물함을 대여할 수 있는 최대 유저 수 + + @ApiProperty({ + description: '사물함의 섹션 종류 (오아시스 등)', + example: 'Oasis', + }) + section: string; // 사물함의 섹션 종류 (오아시스 등) +} diff --git a/backend/src/v3/search/dto/broken-cabinet-info.pagenation.dto.ts b/backend/src/v3/search/dto/broken-cabinet-info.pagenation.dto.ts new file mode 100644 index 0000000..f016d30 --- /dev/null +++ b/backend/src/v3/search/dto/broken-cabinet-info.pagenation.dto.ts @@ -0,0 +1,16 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { BrokenCabinetInfoDto } from './broken-cabinet-info.dto'; + +export class BrokenCabinetInfoPagenationDto { + @ApiProperty({ + description: '캐비넷 정보 배열', + type: [BrokenCabinetInfoDto], + }) + result: BrokenCabinetInfoDto[]; // 캐비넷 정보 배열 + + @ApiProperty({ + description: 'DB에 저장된 총 결과의 길이', + example: 42, + }) + total_length: number; // DB에 저장된 총 결과의 길이 +} diff --git a/backend/src/v3/search/dto/cabinet-info.dto.ts b/backend/src/v3/search/dto/cabinet-info.dto.ts new file mode 100644 index 0000000..bf79262 --- /dev/null +++ b/backend/src/v3/search/dto/cabinet-info.dto.ts @@ -0,0 +1,56 @@ +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; +import CabinetStatusType from 'src/enums/cabinet.status.type.enum'; +import LentType from 'src/enums/lent.type.enum'; +import { LentDto } from './lent.dto'; + +export class CabinetInfoDto { + @ApiProperty({ + description: '캐비넷 고유 ID', + example: 1234, + }) + cabinet_id: number; // 캐비넷 고유 ID + + @ApiProperty({ + description: '사물함에 붙어있는 숫자', + example: 12, + }) + cabinet_num: number; // 사물함에 붙어있는 숫자 + + @ApiProperty({ + description: '사물함의 종류 (개인, 공유, 동아리)', + enum: LentType, + example: 'PRIVATE', + }) + lent_type: LentType; // 사물함의 종류 (개인, 공유, 동아리) + + @ApiProperty({ + description: '사물함에 대한 설명', + example: '푸주와 아이들이 사용하는 사물함입니다', + }) + cabinet_title: string; // 사물함에 대한 설명 + + @ApiProperty({ + description: '해당 사물함을 대여할 수 있는 최대 유저 수', + example: 3, + }) + max_user: number; // 해당 사물함을 대여할 수 있는 최대 유저 수 + + @ApiProperty({ + description: '사물함 상태', + enum: CabinetStatusType, + example: 'PRIVATE', + }) + status: CabinetStatusType; // 사물함의 현재 상태 + + @ApiProperty({ + description: '사물함의 섹션 종류 (오아시스 등)', + example: 'Oasis', + }) + section: string; // 사물함의 섹션 종류 (오아시스 등) + + @ApiPropertyOptional({ + description: '대여되어 있을 경우 대여 정보', + type: [LentDto], + }) + lent_info?: LentDto[]; // 대여 정보 (optional) +} diff --git a/backend/src/v3/search/dto/cabinet-info.pagenation.dto.ts b/backend/src/v3/search/dto/cabinet-info.pagenation.dto.ts new file mode 100644 index 0000000..5db6ec3 --- /dev/null +++ b/backend/src/v3/search/dto/cabinet-info.pagenation.dto.ts @@ -0,0 +1,16 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { CabinetInfoDto } from './cabinet-info.dto'; + +export class CabinetInfoPagenationDto { + @ApiProperty({ + description: '캐비넷 정보 배열', + type: [CabinetInfoDto], + }) + result: CabinetInfoDto[]; // 캐비넷 정보 배열 + + @ApiProperty({ + description: 'DB에 저장된 총 결과의 길이', + example: 42, + }) + total_length: number; // DB에 저장된 총 결과의 길이 +} diff --git a/backend/src/v3/search/dto/lent.dto.ts b/backend/src/v3/search/dto/lent.dto.ts new file mode 100644 index 0000000..c8a1b11 --- /dev/null +++ b/backend/src/v3/search/dto/lent.dto.ts @@ -0,0 +1,36 @@ +import { ApiProperty } from '@nestjs/swagger'; + +/** + * 기본적인 사물함 대여 정보 + */ +export class LentDto { + @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, + }) + lent_id: number; // 대여 고유 ID + + @ApiProperty({ + description: '대여한 시간', + example: '2022-08-24 13:03:03', + }) + lent_time: Date; // 대여한 시간 + + @ApiProperty({ + description: '대여한 시간', + example: '2022-08-24 13:03:03', + }) + expire_time: Date; // 만료 시간 +} diff --git a/backend/src/v3/search/dto/user-info.dto.ts b/backend/src/v3/search/dto/user-info.dto.ts new file mode 100644 index 0000000..0bd1eaf --- /dev/null +++ b/backend/src/v3/search/dto/user-info.dto.ts @@ -0,0 +1,15 @@ +import { ApiProperty } from '@nestjs/swagger'; + +export class UserInfoDto { + @ApiProperty({ + description: '42 고유 ID', + example: 12345, + }) + user_id: number; // 42 고유 ID + + @ApiProperty({ + description: '42 로그인 ID', + example: 'joopark', + }) + intra_id: string; // 42 로그인 ID +} diff --git a/backend/src/v3/search/dto/user-info.pagenation.dto.ts b/backend/src/v3/search/dto/user-info.pagenation.dto.ts new file mode 100644 index 0000000..17b818f --- /dev/null +++ b/backend/src/v3/search/dto/user-info.pagenation.dto.ts @@ -0,0 +1,16 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { UserInfoDto } from './user-info.dto'; + +export class UserInfoPagenationDto { + @ApiProperty({ + description: '유저 정보 배열', + type: [UserInfoDto], + }) + result: UserInfoDto[]; // 유저 정보 배열 + + @ApiProperty({ + description: 'DB에 저장된 총 결과의 길이', + example: 42, + }) + total_length: number; // DB에 저장된 총 결과의 길이 +} diff --git a/backend/src/v3/search/repository/search.repository.interface.ts b/backend/src/v3/search/repository/search.repository.interface.ts index 3d8fe59..6925190 100644 --- a/backend/src/v3/search/repository/search.repository.interface.ts +++ b/backend/src/v3/search/repository/search.repository.interface.ts @@ -1,2 +1,80 @@ -// eslint-disable-next-line @typescript-eslint/no-empty-interface -export interface ISearchRepository {} +import LentType from 'src/enums/lent.type.enum'; +import { BlockedUserInfoPagenationDto } from '../dto/blocked-user-info.pagenation.dto'; +import { BrokenCabinetInfoPagenationDto } from '../dto/broken-cabinet-info.pagenation.dto'; +import { CabinetInfoPagenationDto } from '../dto/cabinet-info.pagenation.dto'; +import { UserInfoPagenationDto } from '../dto/user-info.pagenation.dto'; + +export interface ISearchRepository { + /** + * 인트라 아이디에 대한 검색결과를 가지고 옵니다. + * + * @param intra_id 인트라 아이디 + * @param index 가져올 데이터 인덱스 + * @param length 가져올 데이터 길이 + * @returns CabinetInfoPagenationDto + */ + searchByIntraId( + intra_id: string, + index: number, + length: number, + ): Promise; + + /** + * 특정 캐비넷 타입인 사물함 리스트를 가지고 옵니다. + * + * @param lent_type 대여 타입 + * @param index 가져올 데이터 인덱스 + * @param length 가져올 데이터 길이 + * @returns CabinetInfoPagenationDto + */ + searchByLentType( + lent_type: LentType, + index: number, + length: number, + ): Promise; + + /** + * 해당 사물함 번호를 가진 사물함 리스트를 반환합니다. + * + * @param visible_num 사물함 번호 + * @returns CabinetInfoPagenationDto + */ + searchByCabinetNumber(visible_num: number): Promise; + + /** + * 정지당한 사물함 리스트를 반환합니다. + * + * @param index 가져올 데이터 인덱스 + * @param length 가져올 데이터 길이 + * @returns CabinetInfoPagenationDto + */ + searchByBannedCabinet( + index: number, + length: number, + ): Promise; + + /** + * 고장난 사물함 리스트를 반환합니다. + * + * @param index 가져올 데이터 인덱스 + * @param length 가져올 데이터 길이 + * @returns BrokenCabinetInfoPagenationDto + */ + searchByBrokenCabinet( + index: number, + length: number, + ): Promise; + + /** + * 밴 당한 유저 리스트를 반환합니다. + * + * @param user_id 유저 고유 ID + * @param index 가져올 데이터 인덱스 + * @param length 가져올 데이터 길이 + * @returns LogPagenationDto + */ + searchByBanUser( + index: number, + length: number, + ): Promise; +} diff --git a/backend/src/v3/search/repository/search.repository.ts b/backend/src/v3/search/repository/search.repository.ts index 30d36c3..688d267 100644 --- a/backend/src/v3/search/repository/search.repository.ts +++ b/backend/src/v3/search/repository/search.repository.ts @@ -1,14 +1,201 @@ import { InjectRepository } from '@nestjs/typeorm'; +import BanLog from 'src/entities/ban.log.entity'; import Cabinet from 'src/entities/cabinet.entity'; -import LentLog from 'src/entities/lent.log.entity'; import User from 'src/entities/user.entity'; -import { Repository } from 'typeorm'; +import CabinetStatusType from 'src/enums/cabinet.status.type.enum'; +import LentType from 'src/enums/lent.type.enum'; +import { Like, MoreThan, Repository } from 'typeorm'; +import { BlockedUserInfoPagenationDto } from '../dto/blocked-user-info.pagenation.dto'; +import { BrokenCabinetInfoPagenationDto } from '../dto/broken-cabinet-info.pagenation.dto'; +import { CabinetInfoPagenationDto } from '../dto/cabinet-info.pagenation.dto'; +import { UserInfoPagenationDto } from '../dto/user-info.pagenation.dto'; import { ISearchRepository } from './search.repository.interface'; export class SearchRepository implements ISearchRepository { constructor( @InjectRepository(User) private userRepository: Repository, @InjectRepository(Cabinet) private cabinetRepository: Repository, - @InjectRepository(LentLog) private lentLogRepository: Repository, + @InjectRepository(BanLog) private banLogRepository: Repository, ) {} + + async searchByIntraId( + intra_id: string, + index: number, + length: number, + ): Promise { + const result = await this.userRepository.findAndCount({ + select: { + user_id: true, + intra_id: true, + }, + where: { + intra_id: Like(`%${intra_id}%`), + }, + order: { user_id: 'ASC' }, + take: length, + skip: index, + }); + const rtn = { + result: result[0].map((r) => ({ + user_id: r.user_id, + intra_id: r.intra_id, + })), + total_length: result[1], + }; + return rtn; + } + + async searchByLentType( + lent_type: LentType, + index: number, + length: number, + ): Promise { + const result = await this.cabinetRepository.findAndCount({ + relations: ['lent', 'lent.user'], + where: { + lent_type, + }, + order: { cabinet_id: 'ASC' }, + take: length, + skip: index, + }); + const rtn = { + result: result[0].map((cabinet) => ({ + cabinet_id: cabinet.cabinet_id, + cabinet_num: cabinet.cabinet_num, + lent_type: cabinet.lent_type, + cabinet_title: cabinet.title, + max_user: cabinet.max_user, + status: cabinet.status, + section: cabinet.section, + lent_info: cabinet.lent.map((lent) => ({ + user_id: lent.user.user_id, + intra_id: lent.user.intra_id, + lent_id: lent.lent_id, + lent_time: lent.lent_time, + expire_time: lent.expire_time, + })), + })), + total_length: result[1], + }; + return rtn; + } + + async searchByCabinetNumber( + visible_num: number, + ): Promise { + const result = await this.cabinetRepository.find({ + relations: ['lent', 'lent.user'], + where: { + cabinet_num: visible_num, + }, + order: { cabinet_id: 'ASC' }, + }); + const rtn = { + result: result.map((cabinet) => ({ + cabinet_id: cabinet.cabinet_id, + cabinet_num: cabinet.cabinet_num, + lent_type: cabinet.lent_type, + cabinet_title: cabinet.title, + max_user: cabinet.max_user, + status: cabinet.status, + section: cabinet.section, + lent_info: cabinet.lent.map((lent) => ({ + user_id: lent.user.user_id, + intra_id: lent.user.intra_id, + lent_id: lent.lent_id, + lent_time: lent.lent_time, + expire_time: lent.expire_time, + })), + })), + total_length: result.length, + }; + return rtn; + } + + async searchByBannedCabinet( + index: number, + length: number, + ): Promise { + const result = await this.cabinetRepository.findAndCount({ + relations: ['lent', 'lent.user'], + where: { + status: CabinetStatusType.BANNED, + }, + order: { cabinet_id: 'ASC' }, + take: length, + skip: index, + }); + const rtn = { + result: result[0].map((cabinet) => ({ + cabinet_id: cabinet.cabinet_id, + cabinet_num: cabinet.cabinet_num, + lent_type: cabinet.lent_type, + cabinet_title: cabinet.title, + max_user: cabinet.max_user, + status: cabinet.status, + section: cabinet.section, + lent_info: cabinet.lent.map((lent) => ({ + user_id: lent.user.user_id, + intra_id: lent.user.intra_id, + lent_id: lent.lent_id, + lent_time: lent.lent_time, + expire_time: lent.expire_time, + })), + })), + total_length: result[1], + }; + return rtn; + } + + async searchByBrokenCabinet( + index: number, + length: number, + ): Promise { + const result = await this.cabinetRepository.findAndCount({ + where: { + status: CabinetStatusType.BANNED, + }, + order: { cabinet_id: 'ASC' }, + take: length, + skip: index, + }); + const rtn = { + result: result[0].map((cabinet) => ({ + cabinet_id: cabinet.cabinet_id, + cabinet_num: cabinet.cabinet_num, + lent_type: cabinet.lent_type, + note: cabinet.title, + max_user: cabinet.max_user, + section: cabinet.section, + })), + total_length: result[1], + }; + return rtn; + } + + async searchByBanUser( + index: number, + length: number, + ): Promise { + const result = await this.banLogRepository.findAndCount({ + relations: ['user'], + where: { + unbanned_date: MoreThan(new Date()), + }, + order: { ban_log_id: 'ASC' }, + take: length, + skip: index, + }); + const rtn = { + result: result[0].map((ban) => ({ + user_id: ban.ban_user_id, + intra_id: ban.user.intra_id, + banned_date: ban.banned_date, + unbanned_date: ban.unbanned_date, + })), + total_length: result[1], + }; + return rtn; + } } diff --git a/backend/src/v3/search/search.controller.ts b/backend/src/v3/search/search.controller.ts index 177a115..bca8c28 100644 --- a/backend/src/v3/search/search.controller.ts +++ b/backend/src/v3/search/search.controller.ts @@ -1,9 +1,35 @@ -import { Controller, Logger, UseGuards } from '@nestjs/common'; -import { ApiTags } from '@nestjs/swagger'; +import { + Controller, + Get, + Logger, + Param, + ParseEnumPipe, + ParseIntPipe, + Query, + UseGuards, +} from '@nestjs/common'; +import { + ApiBearerAuth, + ApiOkResponse, + ApiOperation, + ApiParam, + ApiQuery, + ApiTags, + ApiUnauthorizedResponse, +} from '@nestjs/swagger'; import { JWTAuthGuard } from 'src/auth/auth.guard'; +import LentType from 'src/enums/lent.type.enum'; +import { BlockedUserInfoPagenationDto } from './dto/blocked-user-info.pagenation.dto'; +import { BrokenCabinetInfoPagenationDto } from './dto/broken-cabinet-info.pagenation.dto'; +import { CabinetInfoPagenationDto } from './dto/cabinet-info.pagenation.dto'; +import { UserInfoPagenationDto } from './dto/user-info.pagenation.dto'; import { SearchService } from './search.service'; -@ApiTags('(V3) Search') +@ApiTags('(V3) 검색 관련을 다루는 라우터') +@ApiBearerAuth() +@ApiUnauthorizedResponse({ + description: '로그아웃 상태', +}) @Controller({ version: '3', path: 'search', @@ -13,4 +39,198 @@ export class SearchController { private logger = new Logger(SearchController.name); constructor(private searchService: SearchService) {} + + @ApiOperation({ + summary: '인트라 아이디 검색', + description: + '인트라 아이디에 대한 검색결과를 가지고 옵니다. 페이지네이션을 지원합니다.', + }) + @ApiQuery({ + name: 'index', + description: '(페이지네이션) 가져올 데이터 인덱스', + }) + @ApiQuery({ + name: 'length', + description: '(페이지네이션) 가져올 데이터 길이', + }) + @ApiParam({ + name: 'intra_id', + description: '인트라 아이디', + }) + @ApiOkResponse({ + type: UserInfoPagenationDto, + description: '검색 결과를 받아옵니다.', + }) + @Get('/intraid/:intra_id') + async searchByIntraId( + @Param('intra_id') intra_id: string, + @Query('index', ParseIntPipe) index: number, + @Query('length', ParseIntPipe) length: number, + ): Promise { + this.logger.debug(`Called ${this.searchByIntraId.name}`); + try { + return await this.searchService.searchByIntraId(intra_id, index, length); + } catch (err) { + this.logger.error(err); + throw err; + } + } + + @ApiOperation({ + summary: '특정 캐비넷 타입인 사물함 리스트 검색', + description: + '특정 캐비넷 타입인 사물함 리스트를 가지고 옵니다. 페이지네이션을 지원합니다.', + }) + @ApiQuery({ + name: 'index', + description: '(페이지네이션) 가져올 데이터 인덱스', + }) + @ApiQuery({ + name: 'length', + description: '(페이지네이션) 가져올 데이터 길이', + }) + @ApiParam({ + name: 'lent_type', + description: '대여 타입 (ENUM)', + enum: LentType, + }) + @ApiOkResponse({ + type: CabinetInfoPagenationDto, + description: '검색 결과를 받아옵니다.', + }) + @Get('/cabinet/lent_type/:lent_type') + async searchByLentType( + @Param('lent_type', new ParseEnumPipe(LentType)) lent_type: LentType, + @Query('index', ParseIntPipe) index: number, + @Query('length', ParseIntPipe) length: number, + ): Promise { + this.logger.debug(`Called ${this.searchByLentType.name}`); + try { + return await this.searchService.searchByLentType( + lent_type, + index, + length, + ); + } catch (err) { + this.logger.error(err); + throw err; + } + } + + @ApiOperation({ + summary: '해당 사물함 번호를 가진 사물함 리스트', + description: '해당 사물함 번호를 가진 사물함 리스트를 반환합니다.', + }) + @ApiParam({ + name: 'visible_num', + description: '사물함 번호', + }) + @ApiOkResponse({ + type: CabinetInfoPagenationDto, + description: '검색 결과를 받아옵니다.', + }) + @Get('/cabinet/visible_num/:visible_num') + async searchByCabinetNumber( + @Param('visible_num', ParseIntPipe) visible_num: number, + ): Promise { + this.logger.debug(`Called ${this.searchByCabinetNumber.name}`); + try { + return await this.searchService.searchByCabinetNumber(visible_num); + } catch (err) { + this.logger.error(err); + throw err; + } + } + + @ApiOperation({ + summary: '정지당한 사물함 리스트', + description: + '정지당한 사물함 리스트를 반환합니다. 페이지네이션을 지원합니다.', + }) + @ApiQuery({ + name: 'index', + description: '(페이지네이션) 가져올 데이터 인덱스', + }) + @ApiQuery({ + name: 'length', + description: '(페이지네이션) 가져올 데이터 길이', + }) + @ApiOkResponse({ + type: CabinetInfoPagenationDto, + description: '검색 결과를 받아옵니다.', + }) + @Get('/cabinet/banned') + async searchByBannedCabinet( + @Query('index', ParseIntPipe) index: number, + @Query('length', ParseIntPipe) length: number, + ): Promise { + this.logger.debug(`Called ${this.searchByBannedCabinet.name}`); + try { + return await this.searchService.searchByBannedCabinet(index, length); + } catch (err) { + this.logger.error(err); + throw err; + } + } + + @ApiOperation({ + summary: '고장난 사물함 리스트', + description: + '고장난 사물함 리스트를 반환합니다. 페이지네이션을 지원합니다.', + }) + @ApiQuery({ + name: 'index', + description: '(페이지네이션) 가져올 데이터 인덱스', + }) + @ApiQuery({ + name: 'length', + description: '(페이지네이션) 가져올 데이터 길이', + }) + @ApiOkResponse({ + type: BrokenCabinetInfoPagenationDto, + description: '부서진 사물함을 받아옵니다.', + }) + @Get('/cabinet/broken') + async searchByBrokenCabinet( + @Query('index', ParseIntPipe) index: number, + @Query('length', ParseIntPipe) length: number, + ): Promise { + this.logger.debug(`Called ${this.searchByBrokenCabinet.name}`); + try { + return await this.searchService.searchByBrokenCabinet(index, length); + } catch (err) { + this.logger.error(err); + throw err; + } + } + + @ApiOperation({ + summary: '밴 당한 유저 리스트', + description: '밴 당한 유저 리스트를 반환합니다. 페이지네이션을 지원합니다.', + }) + @ApiQuery({ + name: 'index', + description: '(페이지네이션) 가져올 데이터 인덱스', + }) + @ApiQuery({ + name: 'length', + description: '(페이지네이션) 가져올 데이터 길이', + }) + @ApiOkResponse({ + type: BlockedUserInfoPagenationDto, + description: '블록된 유저들을 받아옵니다.', + }) + @Get('/cabinet/banuser') + async searchByBanUser( + @Query('index', ParseIntPipe) index: number, + @Query('length', ParseIntPipe) length: number, + ): Promise { + this.logger.debug(`Called ${this.searchByBanUser.name}`); + try { + return await this.searchService.searchByBanUser(index, length); + } catch (err) { + this.logger.error(err); + throw err; + } + } } diff --git a/backend/src/v3/search/search.module.ts b/backend/src/v3/search/search.module.ts index ccf4933..5176330 100644 --- a/backend/src/v3/search/search.module.ts +++ b/backend/src/v3/search/search.module.ts @@ -1,8 +1,8 @@ import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; import { AuthModule } from 'src/auth/auth.module'; +import BanLog from 'src/entities/ban.log.entity'; import Cabinet from 'src/entities/cabinet.entity'; -import LentLog from 'src/entities/lent.log.entity'; import User from 'src/entities/user.entity'; import { SearchRepository } from './repository/search.repository'; import { SearchController } from './search.controller'; @@ -16,6 +16,6 @@ const repo = { @Module({ controllers: [SearchController], providers: [SearchService, repo], - imports: [AuthModule, TypeOrmModule.forFeature([User, Cabinet, LentLog])], + imports: [AuthModule, TypeOrmModule.forFeature([User, Cabinet, BanLog])], }) export class SearchModule {} diff --git a/backend/src/v3/search/search.service.ts b/backend/src/v3/search/search.service.ts index f0a4a54..1761311 100644 --- a/backend/src/v3/search/search.service.ts +++ b/backend/src/v3/search/search.service.ts @@ -1,4 +1,9 @@ import { Inject, Injectable, Logger } from '@nestjs/common'; +import LentType from 'src/enums/lent.type.enum'; +import { BlockedUserInfoPagenationDto } from './dto/blocked-user-info.pagenation.dto'; +import { BrokenCabinetInfoPagenationDto } from './dto/broken-cabinet-info.pagenation.dto'; +import { CabinetInfoPagenationDto } from './dto/cabinet-info.pagenation.dto'; +import { UserInfoPagenationDto } from './dto/user-info.pagenation.dto'; import { ISearchRepository } from './repository/search.repository.interface'; @Injectable() @@ -7,4 +12,101 @@ export class SearchService { constructor( @Inject('ISearchRepository') private searchRepository: ISearchRepository, ) {} + + /** + * 인트라 아이디에 대한 검색결과를 가지고 옵니다. + * + * @param intra_id 인트라 아이디 + * @param index 가져올 데이터 인덱스 + * @param length 가져올 데이터 길이 + * @returns CabinetInfoPagenationDto + * @throw HTTPError + */ + async searchByIntraId( + intra_id: string, + index: number, + length: number, + ): Promise { + return await this.searchRepository.searchByIntraId(intra_id, index, length); + } + + /** + * 특정 캐비넷 타입인 사물함 리스트를 가지고 옵니다. + * + * @param lent_type 대여 타입 + * @param index 가져올 데이터 인덱스 + * @param length 가져올 데이터 길이 + * @returns CabinetInfoPagenationDto + * @throw HTTPError + */ + async searchByLentType( + lent_type: LentType, + index: number, + length: number, + ): Promise { + return await this.searchRepository.searchByLentType( + lent_type, + index, + length, + ); + } + + /** + * 해당 사물함 번호를 가진 사물함 리스트를 반환합니다. + * + * @param visible_num 사물함 번호 + * @returns CabinetInfoPagenationDto + * @throw HTTPError + */ + async searchByCabinetNumber( + visible_num: number, + ): Promise { + return await this.searchRepository.searchByCabinetNumber(visible_num); + } + + /** + * 정지당한 사물함 리스트를 반환합니다. + * + * @param index 가져올 데이터 인덱스 + * @param length 가져올 데이터 길이 + * @returns CabinetInfoPagenationDto + * @throw HTTPError + */ + async searchByBannedCabinet( + index: number, + length: number, + ): Promise { + return await this.searchRepository.searchByBannedCabinet(index, length); + } + + /** + * 고장난 사물함 리스트를 반환합니다. + * + * @param index 가져올 데이터 인덱스 + * @param length 가져올 데이터 길이 + * @returns BrokenCabinetInfoPagenationDto + * @throw HTTPError + */ + async searchByBrokenCabinet( + index: number, + length: number, + ): Promise { + return await this.searchRepository.searchByBrokenCabinet(index, length); + } + + /** + * 밴 당한 유저 리스트를 반환합니다. + * + * @param user_id 유저 고유 ID + * @param index 가져올 데이터 인덱스 + * @param length 가져올 데이터 길이 + * @returns LogPagenationDto + * @throw HTTPError + */ + async searchByBanUser( + index: number, + length: number, + ): Promise { + return await this.searchRepository.searchByBanUser(index, length); + } } 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: [], })