diff --git a/src/app.module.ts b/src/app.module.ts index b99a746..d50c81f 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -64,7 +64,7 @@ import { FolderComplexModule } from './folder-complex/folder-complex.module'; TripModule, FolderModule, CollectionModule, - CrawlingModule, + //CrawlingModule, CollectionComplexModule, PlaceComplexModule, FolderComplexModule, diff --git a/src/place/dtos/paginated-places-list-res.dto.ts b/src/place/dtos/paginated-places-list-res.dto.ts index b59f3a6..ed301d5 100644 --- a/src/place/dtos/paginated-places-list-res.dto.ts +++ b/src/place/dtos/paginated-places-list-res.dto.ts @@ -8,6 +8,10 @@ import { CursorPaginationMeta, } from 'src/common/utils/cursor-pagination.util'; import { IeumCategory } from 'src/common/enums/ieum-category.enum'; +import { + createNormalList, + NormalListMeta, +} from 'src/common/utils/normal-list.util'; export class PlaceInfoDto { @ApiProperty() @@ -57,3 +61,20 @@ export class PlacesListResDto { this.items = items; } } + +export class PlacesListNoPaginationResDto { + @ApiProperty() + meta: NormalListMeta; //GET /collections에서 정의한 listMeta 재활용 + @ApiProperty({ type: [PlaceInfoDto] }) + items: PlaceInfoDto[]; + + constructor(rawData: RawPlaceInfo[]) { + const { meta, items } = createNormalList( + rawData, + (item) => new PlaceInfoDto(item), + ); + + this.meta = meta; + this.items = items; + } +} diff --git a/src/place/dtos/rec-places-list-req.dto.ts b/src/place/dtos/rec-places-list-req.dto.ts new file mode 100644 index 0000000..521ea29 --- /dev/null +++ b/src/place/dtos/rec-places-list-req.dto.ts @@ -0,0 +1,19 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { Transform, Type } from 'class-transformer'; +import { IsInt } from 'class-validator'; + +export class RecommendedPlacesListReqDto { + @ApiProperty() + @Type(() => Number) + @IsInt() + userId: number; + + @ApiProperty() + @Transform(({ value }) => (typeof value === 'number' ? [value] : value)) + placeIds: number[]; + + @ApiProperty() + @Type(() => Number) + @IsInt() + numPlaceRec: number; +} diff --git a/src/place/dtos/rec-schedule-req.dto.ts b/src/place/dtos/rec-schedule-req.dto.ts new file mode 100644 index 0000000..389844c --- /dev/null +++ b/src/place/dtos/rec-schedule-req.dto.ts @@ -0,0 +1,14 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { Transform, Type } from 'class-transformer'; +import { IsInt } from 'class-validator'; + +export class RecommendedScheduleReqDto { + @ApiProperty() + @Transform(({ value }) => (typeof value === 'number' ? [value] : value)) + placeIds: number[]; + + @ApiProperty() + @Type(() => Number) + @IsInt() + numCluster: number; +} diff --git a/src/place/repositories/place.repository.ts b/src/place/repositories/place.repository.ts index 2339fe8..06cb29d 100644 --- a/src/place/repositories/place.repository.ts +++ b/src/place/repositories/place.repository.ts @@ -2,6 +2,7 @@ import { Injectable } from '@nestjs/common'; import { Place } from 'src/place/entities/place.entity'; import { DataSource, Repository } from 'typeorm'; import { KakaoLocalSearchRes } from 'src/common/interfaces/kakao-local-search-res.interface'; +import { RawPlaceInfo } from 'src/common/interfaces/raw-place-info.interface'; @Injectable() export class PlaceRepository extends Repository { @@ -47,6 +48,26 @@ export class PlaceRepository extends Repository { return placePreviewInfo; } + async getPlacePreviewInfoByIds(placeIds: number[]): Promise { + const placePreviewInfoList = await this.createQueryBuilder('place') + .leftJoinAndSelect('place.placeTags', 'placeTag') + .leftJoinAndSelect('placeTag.tag', 'tag') + .leftJoinAndSelect('place.placeImages', 'placeImage') + .select([ + 'place.id AS id', + 'place.name AS name', + 'place.address AS address', + 'place.primary_category AS primary_category ', + 'ARRAY_AGG(placeImage.url ORDER BY placeImage.id DESC) AS "image_urls"', + ]) + .where('place.id IN (:...placeIds)', { placeIds }) + .groupBy('place.id') + .orderBy('place.id', 'DESC') + .getRawMany(); + + return placePreviewInfoList; + } + async getPlacesByPlaceName(placeName: string): Promise { const places = await this.createQueryBuilder('place') .select(['place.id', 'place.name']) diff --git a/src/place/services/place.service.ts b/src/place/services/place.service.ts index 4b0334d..b56f0c4 100644 --- a/src/place/services/place.service.ts +++ b/src/place/services/place.service.ts @@ -22,6 +22,8 @@ import { addressSimplifier } from 'src/common/utils/address-simplifier.util'; import { GooglePlacesApiPlaceDetailsRes } from 'src/common/interfaces/google-places-api.interface'; import { KakaoCategoryMappingService } from './kakao-category-mapping.service'; import { IeumCategory } from 'src/common/enums/ieum-category.enum'; +import { PlacesListNoPaginationResDto } from '../dtos/paginated-places-list-res.dto'; +import { RecommendedPlacesListReqDto } from '../dtos/rec-places-list-req.dto'; @Injectable() export class PlaceService { @@ -91,6 +93,14 @@ export class PlaceService { return placePhoto.data; } + + // --------- 외부 API 호출: AI 서버 --------- + async getRecommendedPlacesIds( + recommendedPlacesListReqDto: RecommendedPlacesListReqDto, + ) {} + + async getRecommendedSchedule() {} + // --------- 주요 메서드 --------- @Transactional() async createPlaceDetailByGooglePlacesApi(placeId: number) { @@ -197,6 +207,29 @@ export class PlaceService { return new PlacePreviewResDto(place, ieumCategory); } + async getPlacePreviewInfoByIds( + placeIds: number[], + ): Promise { + //빈 배열 처리. + if (placeIds.length == 0) { + return new PlacesListNoPaginationResDto([]); + } + + const rawPlacesInfoList = + await this.placeRepository.getPlacePreviewInfoByIds(placeIds); + + await Promise.all( + rawPlacesInfoList.map(async (placeInfo) => { + placeInfo.ieumCategory = await this.getIeumCategoryByKakaoCategory( + placeInfo.primary_category, + ); + return placeInfo; // 이 반환값은 실제로 사용되지 않지만, 명시적으로 표현 + }), + ); + + return new PlacesListNoPaginationResDto(rawPlacesInfoList); + } + async getPlacesByPlaceName(placeName: string): Promise { return await this.placeRepository.getPlacesByPlaceName(placeName); } diff --git a/src/trip/trip.controller.ts b/src/trip/trip.controller.ts index d1dd600..a004b19 100644 --- a/src/trip/trip.controller.ts +++ b/src/trip/trip.controller.ts @@ -1,4 +1,4 @@ import { Controller } from '@nestjs/common'; -@Controller('trip') +@Controller('trips') export class TripController {}