Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/bookmark/controller/bookmark.controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -76,8 +76,8 @@ export const getBookmarks = async (req, res, next) => {
const userId = BigInt(req.user.userId);
const dto = new GetBookmarksDto({
sort: req.query.sort,
page: req.query.page ? parseInt(req.query.page) : undefined,
limit: req.query.limit ? parseInt(req.query.limit) : undefined,
cursor: req.query.cursor,
excludeFullSlots: req.query.excludeFullSlots === 'true'
});

Expand Down
4 changes: 2 additions & 2 deletions src/bookmark/dto/bookmark.dto.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,10 @@ export class DeleteSelectedBookmarksDto {

// 사용자 북마크 조회 dto
export class GetBookmarksDto {
constructor({ sort, limit, cursor, excludeFullSlots }) {
constructor({ sort, page, limit, excludeFullSlots }) {
this.sort = sort || 'latest';
this.page = page || 1;
this.limit = limit || 12;
this.cursor = cursor || null;
this.excludeFullSlots = excludeFullSlots || false;
}
}
65 changes: 21 additions & 44 deletions src/bookmark/repository/bookmark.repository.js
Original file line number Diff line number Diff line change
Expand Up @@ -70,60 +70,36 @@ export const BookmarkRepository = {
},

/**
* 사용자의 북마크 목록 조회 (커서 기반 페이징)
* 사용자의 북마크 목록 조회
*/
async findBookmarksByUserId(userId, dto) {
const { sort, limit, cursor, excludeFullSlots = false } = dto;
const { sort, page, limit, excludeFullSlots = false } = dto;

const baseCondition = {
userId: BigInt(userId)
userId: BigInt(userId)
};

let whereCondition = { ...baseCondition };
let orderBy = [];

if (cursor) {
const decodedCursor = JSON.parse(Buffer.from(cursor, 'base64').toString());
switch (sort) {
case 'latest': // 최신순 정렬
whereCondition.createdAt = { lt: new Date(decodedCursor.created_at) };
orderBy = [{ createdAt: 'desc' }, { id: 'desc' }];
break;
case 'price_low': // 저가순 정렬
whereCondition.OR = [
{ commission: { minPrice: { gt: decodedCursor.min_price } } },
{
commission: { minPrice: decodedCursor.min_price },
createdAt: { lt: new Date(decodedCursor.created_at) }
}
];
orderBy = [{ commission: { minPrice: 'asc' } }, { createdAt: 'desc' }];
break;
case 'price_high': // 고가순 정렬
whereCondition.OR = [
{ commission: { minPrice: { lt: decodedCursor.min_price } } },
{
commission: { minPrice: decodedCursor.min_price },
createdAt: { lt: new Date(decodedCursor.created_at) }
}
];
orderBy = [{ commission: { minPrice: 'desc' } }, { createdAt: 'desc' }];
break;
}
} else {
switch (sort) {
case 'latest':
orderBy = [{ createdAt: 'desc' }, { id: 'desc' }];
break;
case 'price_low':
orderBy = [{ commission: { minPrice: 'asc' } }, { createdAt: 'desc' }];
break;
case 'price_high':
orderBy = [{ commission: { minPrice: 'desc' } }, { createdAt: 'desc' }];
break;
}
// 정렬 조건 설정
switch (sort) {
case 'latest':
orderBy = [{ createdAt: 'desc' }, { id: 'desc' }];
break;
case 'price_low':
orderBy = [{ commission: { minPrice: 'asc' } }, { createdAt: 'desc' }];
break;
case 'price_high':
orderBy = [{ commission: { minPrice: 'desc' } }, { createdAt: 'desc' }];
break;
default:
orderBy = [{ createdAt: 'desc' }, { id: 'desc' }];
}

// 페이지네이션 계산
const skip = (page - 1) * limit;

return await prisma.bookmark.findMany({
where: whereCondition,
include: {
Expand Down Expand Up @@ -165,7 +141,8 @@ export const BookmarkRepository = {
}
},
orderBy,
take: excludeFullSlots ? 100 : limit + 1, // hasNext 확인용
skip,
take: limit
});
},

Expand Down
40 changes: 12 additions & 28 deletions src/bookmark/service/bookmark.service.js
Original file line number Diff line number Diff line change
Expand Up @@ -94,17 +94,17 @@ export const BookmarkService = {
};
},

// 북마크 목록 조회
// 북마크 목록 조회
async getBookmarks(userId, dto) {
const { sort, limit, cursor, excludeFullSlots = false } = dto;
const { sort, page, limit, excludeFullSlots = false } = dto;

const bookmarks = await BookmarkRepository.findBookmarksByUserId(userId, dto);

const totalCount = excludeFullSlots
? await BookmarkRepository.countAvailableBookmarksByUserId(userId)
: await BookmarkRepository.countBookmarksByUserId(userId);

// 응답 데이터 가공 (모든 데이터 포맷팅)
// 응답 데이터 가공
const formattedItems = await Promise.all(
bookmarks.map(async (bookmark) => {
// 썸네일 이미지 조회
Expand Down Expand Up @@ -134,33 +134,17 @@ export const BookmarkService = {
? formattedItems.filter(item => item.remainingSlots > 0)
: formattedItems;

const hasNext = filteredItems.length > limit;
const finalItems = hasNext ? filteredItems.slice(0, -1) : filteredItems;

// nextCursor 생성
let nextCursor = null;
if (hasNext && finalItems.length > 0) {
const lastFormattedItem = finalItems[finalItems.length - 1];
const originalBookmark = bookmarks.find(b => Number(b.commission.id) === lastFormattedItem.id);

const cursorData = {
id: lastFormattedItem.id,
created_at: originalBookmark.createdAt.toISOString()
};

if (sort === 'price_low' || sort === 'price_high') {
cursorData.min_price = lastFormattedItem.minPrice;
}

nextCursor = Buffer.from(JSON.stringify(cursorData)).toString('base64');
}

// 페이지네이션 계산
const totalPages = Math.ceil(totalCount / limit);

return {
totalCount,
hasNext,
nextCursor,
items: finalItems
items: filteredItems,
pagination: {
page,
limit,
totalCount,
totalPages
}
};
},
};
30 changes: 19 additions & 11 deletions src/common/swagger/bookmark.json
Original file line number Diff line number Diff line change
Expand Up @@ -219,7 +219,7 @@
"get": {
"tags": ["Bookmark"],
"summary": "북마크 목록 조회",
"description": "사용자의 북마크한 커미션 목록을 조회합니다. 정렬 및 무한스크롤 페이징을 지원합니다.",
"description": "사용자의 북마크한 커미션 목록을 조회합니다.",
"security": [
{
"bearerAuth": []
Expand All @@ -238,25 +238,27 @@
"description": "정렬 방식"
},
{
"name": "limit",
"name": "page",
"in": "query",
"required": false,
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 50,
"default": 12
"default": 1
},
"description": "한 번에 가져올 개수"
"description": "페이지 번호"
},
{
"name": "cursor",
"name": "limit",
"in": "query",
"required": false,
"schema": {
"type": "string"
"type": "integer",
"minimum": 1,
"maximum": 50,
"default": 12
},
"description": "다음 페이지 조회용 커서"
"description": "한 번에 가져올 개수"
},
{
"name": "excludeFullSlots",
Expand All @@ -282,9 +284,6 @@
"success": {
"type": "object",
"properties": {
"totalCount": { "type": "integer", "example": 24 },
"hasNext": { "type": "boolean", "example": true },
"nextCursor": { "type": "string", "example": "eyJpZCI6MTIzLCJjcmVhdGVkX2F0IjoiMjAyNS0wNy0wMVQxMDowMDowMCJ9" },
"items": {
"type": "array",
"items": {
Expand Down Expand Up @@ -326,6 +325,15 @@
}
}
}
},
"pagination": {
"type": "object",
"properties": {
"page": { "type": "integer", "example": 1 },
"limit": { "type": "integer", "example": 12 },
"totalCount": { "type": "integer", "example": 24 },
"totalPages": { "type": "integer", "example": 2 }
}
}
}
}
Expand Down