diff --git a/src/services/getProfileFeed.js b/src/services/getProfileFeed.js index bb859d0..87882d3 100644 --- a/src/services/getProfileFeed.js +++ b/src/services/getProfileFeed.js @@ -17,7 +17,7 @@ const getBook = new GetBook({ accessToken: SSC_TOKEN }); * @returns {Promise} 응답 DTO. */ export default async function getProfileFeed(reqDto, user) { - // (1) 유저 ID 디코드 + // (1) 유저 ID 디코드 및 권한 검사 if (!user.is_admin && user.id !== reqDto.id) { // 권한 없음 throw new FetchError('Unauthorized request.', 403); @@ -37,7 +37,7 @@ export default async function getProfileFeed(reqDto, user) { const page = reqDto.page ?? 0; const offset = page * limit; - // (3) 메모와 리뷰 조회 + // (3) 메모와 리뷰 집계 조회 const [memoCounts, reviewCounts] = await Promise.all([ Memo.findAll({ attributes: [ @@ -45,10 +45,7 @@ export default async function getProfileFeed(reqDto, user) { [fn('COUNT', col('memo_id')), 'memo_count'], [fn('MAX', col('updated_at')), 'last_updated'], ], - where: { - writer_id: userId, - deleted_at: null, - }, + where: { writer_id: userId, deleted_at: null }, group: ['book_isbn'], raw: true, }).catch(handleSqlError), @@ -58,15 +55,13 @@ export default async function getProfileFeed(reqDto, user) { [fn('COUNT', col('review_id')), 'review_count'], [fn('MAX', col('updated_at')), 'last_updated'], ], - where: { - writer_id: userId, - deleted_at: null, - }, + where: { writer_id: userId, deleted_at: null }, group: ['book_isbn'], raw: true, }).catch(handleSqlError), ]); + // (4) ISBN 별 메모/리뷰 정보 병합 const contentMap = new Map(); /* eslint-disable camelcase */ memoCounts.forEach(({ book_isbn, memo_count, last_updated }) => { @@ -76,7 +71,6 @@ export default async function getProfileFeed(reqDto, user) { lastUpdated: last_updated, }); }); - reviewCounts.forEach(({ book_isbn, review_count, last_updated }) => { if (!contentMap.has(book_isbn)) { contentMap.set(book_isbn, { @@ -94,43 +88,36 @@ export default async function getProfileFeed(reqDto, user) { }); /* eslint-enable camelcase */ - // (5) 페이지네이션 적용 + // (5) 최신 활동 기준 정렬 및 페이지네이션 적용 const entries = Array.from(contentMap.entries()) - .sort((a, b) => { - // 최신 활동 시간 기준으로 정렬 - return new Date(b[1].lastUpdated) - new Date(a[1].lastUpdated); - }) + .sort((a, b) => new Date(b[1].lastUpdated) - new Date(a[1].lastUpdated)) .slice(offset, offset + limit); - // (6) 도서 정보와 우물 정보 조회 - const items = await Promise.all( - entries.map(async ([isbn, counts]) => { - // (1) 도서 정보 조회 - const bookInfo = await getBook.fetch({ isbn }); + // (6) 도서 정보 및 우물 정보 조회 + const items = []; + for (const [isbn, counts] of entries) { + // (6.1) 도서 정보 조회 (실패 시 null 반환) + let bookInfo = null; + try { + bookInfo = await getBook.fetch({ isbn }); + } catch (err) { + // 도서 정보 조회 실패 시 해당 항목은 건너뜀 + continue; + } - // (2) 우물 정보 조회 - const well = await Well.findOne({ - include: [ - { - model: WellItem, - where: { - book_isbn: isbn, - }, - }, - ], - where: { - owner_id: userId, - }, - }).catch(handleSqlError); + // (6.2) 우물 정보 조회 + const well = await Well.findOne({ + include: [{ model: WellItem, where: { book_isbn: isbn } }], + where: { owner_id: userId }, + }).catch(handleSqlError); - return { - memoCount: counts.memoCount, - reviewCount: counts.reviewCount, - book: bookInfo, - wellId: well ? encodeHashId(well.well_id) : undefined, - }; - }), - ); + items.push({ + memoCount: counts.memoCount, + reviewCount: counts.reviewCount, + book: bookInfo, + wellId: well ? encodeHashId(well.well_id) : undefined, + }); + } // (7) 데이터 반환 return {