From a4df904ee04406db6d7347906d5e44c0904a8d32 Mon Sep 17 00:00:00 2001 From: ChoiWheatley Date: Sun, 9 Mar 2025 00:29:09 +0900 Subject: [PATCH 1/5] feat(gift.service): Change repository into query builder --- src/features/gift/gift.service.ts | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/features/gift/gift.service.ts b/src/features/gift/gift.service.ts index baa6543e..29186671 100644 --- a/src/features/gift/gift.service.ts +++ b/src/features/gift/gift.service.ts @@ -34,11 +34,14 @@ export class GiftService { giftImgUrls: string[]; count: number; }> { - const [gifts, count] = await this.giftRepository.findAndCount({ - where: { funding: { fundId: fund.fundId } }, - relations: ['funding'], - order: { giftOrd: 'ASC' }, - }); + const qb = this.giftRepository + .createQueryBuilder('g') + .leftJoinAndSelect('g.funding', 'f', 'g.fundId = :fundId') + .where('g.fundId = :fundId', { + fundId: fund.fundId, + }) + .orderBy('g.giftOrd', 'ASC'); + const [gifts, count] = await qb.getManyAndCount(); const giftImgUrls: string[] = []; From ddbfdfc4808173dc8f03ce31f81eeb3e99567386 Mon Sep 17 00:00:00 2001 From: ChoiWheatley Date: Sun, 9 Mar 2025 00:53:56 +0900 Subject: [PATCH 2/5] fix(gift.service): gift now joins exact image using mapImage --- src/features/gift/gift.service.ts | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/src/features/gift/gift.service.ts b/src/features/gift/gift.service.ts index 29186671..1ff19876 100644 --- a/src/features/gift/gift.service.ts +++ b/src/features/gift/gift.service.ts @@ -41,21 +41,18 @@ export class GiftService { fundId: fund.fundId, }) .orderBy('g.giftOrd', 'ASC'); + this.imageManager.mapImage(qb); const [gifts, count] = await qb.getManyAndCount(); - const giftImgUrls: string[] = []; - // Gift 배열을 ResponseGiftDto 배열로 변환 const responseGifts = await Promise.all( gifts.map(async (gift) => { - const { imgUrl, isDef } = await this.getGiftImageUrl(gift); - if (imgUrl && !isDef) { - giftImgUrls.push(imgUrl); - } - return new ResponseGiftDto(gift, imgUrl || ''); + return new ResponseGiftDto(gift, gift.image.imgUrl); }), ); + const giftImgUrls: string[] = gifts.map((g) => g.image.imgUrl); + return { gifts: responseGifts, giftImgUrls, count }; } From d87d12b713555793487df7e601d5b1adba0f0d4e Mon Sep 17 00:00:00 2001 From: ChoiWheatley Date: Sun, 9 Mar 2025 01:34:36 +0900 Subject: [PATCH 3/5] fix(funding.service): N+1 query problem with funding.fundUser.image --- src/features/funding/funding.service.ts | 56 ++++++++++++++++--------- 1 file changed, 36 insertions(+), 20 deletions(-) diff --git a/src/features/funding/funding.service.ts b/src/features/funding/funding.service.ts index 7fd200fe..151f0571 100644 --- a/src/features/funding/funding.service.ts +++ b/src/features/funding/funding.service.ts @@ -95,15 +95,13 @@ export class FundingService { .createQueryBuilder('friend') .where( '((friend.userId = :userId AND friend.friendId = :friendId) OR (friend.userId = :friendId AND friend.friendId = :userId))', - { userId: user.userId, friendId: userId }) + { userId: user.userId, friendId: userId }, + ) .andWhere('friend.status = :status', { status: FriendStatus.Friend }) .getOne(); if (!friendship) { - queryBuilder.andWhere( - 'fund.fundPubl = :publ', - { publ: true } - ) + queryBuilder.andWhere('fund.fundPubl = :publ', { publ: true }); } } } else { @@ -132,10 +130,10 @@ export class FundingService { if (friendIdsArray.length > 0) { // 친구 목록이 있는 경우 if (fundPublFilter === 'all') { - queryBuilder.andWhere( - 'funding.fundPubl = :publ', - { publ: true, ids: friendIdsArray }, - ); + queryBuilder.andWhere('funding.fundPubl = :publ', { + publ: true, + ids: friendIdsArray, + }); } else if (fundPublFilter === 'friends') { queryBuilder.andWhere('funding.fundUser IN (:...ids)', { ids: friendIdsArray, @@ -224,21 +222,31 @@ export class FundingService { queryBuilder.take(limit); - queryBuilder.leftJoinAndSelect('funding.fundUser', 'user'); - // .leftJoinAndSelect('user.image', 'img'); + queryBuilder.leftJoinAndSelect('funding.fundUser', 'u'); + queryBuilder.leftJoinAndMapOne( + 'u.image', + 'image', + 'ui', + ` + (u.defaultImgId IS NOT NULL AND ui.imgId = u.defaultImgId) OR + (u.defaultImgId IS NULL AND ui.subId = u.userId AND ui.imgType = :imgType) + `, + { imgType: ImageType.User }, + ); const fundingPromies: Promise[] = await queryBuilder .getMany() .then((fundings: Funding[]) => fundings.map(async (funding) => { - const fundUserImgUrl = await this.imageManager - .getImages(funding.fundUser) - .then((images) => images[0].imgUrl); - const { gifts, giftImgUrls } = await this.giftService.findAllGift(funding); - return new FundingDto(funding, fundUserImgUrl, gifts, giftImgUrls); + return new FundingDto( + funding, + funding.fundUser.image.imgUrl, + gifts, + giftImgUrls, + ); }), ); @@ -288,7 +296,7 @@ export class FundingService { user: User, ): Promise { // TODO - accessToken -> User 객체로 변환하기 - let funding = new Funding( + const funding = new Funding( user, createFundingDto.fundTitle, createFundingDto.fundCont, @@ -306,7 +314,7 @@ export class FundingService { const funding_save = await this.fundingRepository.save(funding); - let fundImg: string[] = []; + const fundImg: string[] = []; if (createFundingDto.fundImg) { // subId = fundId, imgType = "Funding" Image 객체를 만든다. const fundImgUrls = [createFundingDto.fundImg]; // TODO - CreateFundingDto.fundImg -> fundImgUrls 로 변경하기 @@ -344,7 +352,10 @@ export class FundingService { updateFundingDto: UpdateFundingDto, user: User, ): Promise { - const funding = await this.findFundingByUuidAndUserId(fundUuid, user.userId); + const funding = await this.findFundingByUuidAndUserId( + fundUuid, + user.userId, + ); const fundId = funding.fundId; // endAt이 앞당겨지면 안된다. @@ -356,7 +367,12 @@ export class FundingService { } // 이미지 업데이트 - const fundingImg = await this.updateFundingImage(funding, updateFundingDto.fundImg, fundId, user); + const fundingImg = await this.updateFundingImage( + funding, + updateFundingDto.fundImg, + fundId, + user, + ); // Funding 업데이트 await this.fundingRepository.update( From 386d6644962ede512b1ae3f37b500b214e312ed2 Mon Sep 17 00:00:00 2001 From: ChoiWheatley Date: Sun, 9 Mar 2025 02:07:04 +0900 Subject: [PATCH 4/5] refactor(gift.service): remove clutters --- src/features/gift/gift.service.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/features/gift/gift.service.ts b/src/features/gift/gift.service.ts index 1ff19876..7866f3cb 100644 --- a/src/features/gift/gift.service.ts +++ b/src/features/gift/gift.service.ts @@ -46,9 +46,7 @@ export class GiftService { // Gift 배열을 ResponseGiftDto 배열로 변환 const responseGifts = await Promise.all( - gifts.map(async (gift) => { - return new ResponseGiftDto(gift, gift.image.imgUrl); - }), + gifts.map((gift) => new ResponseGiftDto(gift, gift.image.imgUrl)), ); const giftImgUrls: string[] = gifts.map((g) => g.image.imgUrl); From 10b6af5862cd990f19f1733ca7119be105472ce2 Mon Sep 17 00:00:00 2001 From: ChoiWheatley Date: Sun, 9 Mar 2025 02:48:14 +0900 Subject: [PATCH 5/5] doc(funding.service) Add reaason why mapImage is not able to use nested entities --- src/features/funding/funding.service.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/features/funding/funding.service.ts b/src/features/funding/funding.service.ts index 151f0571..9e18181c 100644 --- a/src/features/funding/funding.service.ts +++ b/src/features/funding/funding.service.ts @@ -223,6 +223,8 @@ export class FundingService { queryBuilder.take(limit); queryBuilder.leftJoinAndSelect('funding.fundUser', 'u'); + + // 현재 Nested Entity에는 ImageInstanceManager#mapImage를 사용할 수 없다. queryBuilder.leftJoinAndMapOne( 'u.image', 'image',