Skip to content

Commit 7772619

Browse files
authored
Merge branch 'develop' into feature/badge
2 parents 1cc8e2e + 374d2f8 commit 7772619

32 files changed

+1426
-153
lines changed

.github/workflows/cd.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ jobs:
5959
After=network.target
6060
6161
[Service]
62+
Environment=NODE_ENV=production
6263
User=ubuntu
6364
ExecStart=/usr/bin/npm run start --prefix /opt/app/
6465
Restart=always

src/bookmark/controller/bookmark.controller.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,8 +76,8 @@ export const getBookmarks = async (req, res, next) => {
7676
const userId = BigInt(req.user.userId);
7777
const dto = new GetBookmarksDto({
7878
sort: req.query.sort,
79+
page: req.query.page ? parseInt(req.query.page) : undefined,
7980
limit: req.query.limit ? parseInt(req.query.limit) : undefined,
80-
cursor: req.query.cursor,
8181
excludeFullSlots: req.query.excludeFullSlots === 'true'
8282
});
8383

src/bookmark/dto/bookmark.dto.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,10 @@ export class DeleteSelectedBookmarksDto {
2222

2323
// 사용자 북마크 조회 dto
2424
export class GetBookmarksDto {
25-
constructor({ sort, limit, cursor, excludeFullSlots }) {
25+
constructor({ sort, page, limit, excludeFullSlots }) {
2626
this.sort = sort || 'latest';
27+
this.page = page || 1;
2728
this.limit = limit || 12;
28-
this.cursor = cursor || null;
2929
this.excludeFullSlots = excludeFullSlots || false;
3030
}
3131
}

src/bookmark/repository/bookmark.repository.js

Lines changed: 21 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -70,60 +70,36 @@ export const BookmarkRepository = {
7070
},
7171

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

7878
const baseCondition = {
79-
userId: BigInt(userId)
79+
userId: BigInt(userId)
8080
};
8181

8282
let whereCondition = { ...baseCondition };
8383
let orderBy = [];
8484

85-
if (cursor) {
86-
const decodedCursor = JSON.parse(Buffer.from(cursor, 'base64').toString());
87-
switch (sort) {
88-
case 'latest': // 최신순 정렬
89-
whereCondition.createdAt = { lt: new Date(decodedCursor.created_at) };
90-
orderBy = [{ createdAt: 'desc' }, { id: 'desc' }];
91-
break;
92-
case 'price_low': // 저가순 정렬
93-
whereCondition.OR = [
94-
{ commission: { minPrice: { gt: decodedCursor.min_price } } },
95-
{
96-
commission: { minPrice: decodedCursor.min_price },
97-
createdAt: { lt: new Date(decodedCursor.created_at) }
98-
}
99-
];
100-
orderBy = [{ commission: { minPrice: 'asc' } }, { createdAt: 'desc' }];
101-
break;
102-
case 'price_high': // 고가순 정렬
103-
whereCondition.OR = [
104-
{ commission: { minPrice: { lt: decodedCursor.min_price } } },
105-
{
106-
commission: { minPrice: decodedCursor.min_price },
107-
createdAt: { lt: new Date(decodedCursor.created_at) }
108-
}
109-
];
110-
orderBy = [{ commission: { minPrice: 'desc' } }, { createdAt: 'desc' }];
111-
break;
112-
}
113-
} else {
114-
switch (sort) {
115-
case 'latest':
116-
orderBy = [{ createdAt: 'desc' }, { id: 'desc' }];
117-
break;
118-
case 'price_low':
119-
orderBy = [{ commission: { minPrice: 'asc' } }, { createdAt: 'desc' }];
120-
break;
121-
case 'price_high':
122-
orderBy = [{ commission: { minPrice: 'desc' } }, { createdAt: 'desc' }];
123-
break;
124-
}
85+
// 정렬 조건 설정
86+
switch (sort) {
87+
case 'latest':
88+
orderBy = [{ createdAt: 'desc' }, { id: 'desc' }];
89+
break;
90+
case 'price_low':
91+
orderBy = [{ commission: { minPrice: 'asc' } }, { createdAt: 'desc' }];
92+
break;
93+
case 'price_high':
94+
orderBy = [{ commission: { minPrice: 'desc' } }, { createdAt: 'desc' }];
95+
break;
96+
default:
97+
orderBy = [{ createdAt: 'desc' }, { id: 'desc' }];
12598
}
12699

100+
// 페이지네이션 계산
101+
const skip = (page - 1) * limit;
102+
127103
return await prisma.bookmark.findMany({
128104
where: whereCondition,
129105
include: {
@@ -165,7 +141,8 @@ export const BookmarkRepository = {
165141
}
166142
},
167143
orderBy,
168-
take: excludeFullSlots ? 100 : limit + 1, // hasNext 확인용
144+
skip,
145+
take: limit
169146
});
170147
},
171148

src/bookmark/service/bookmark.service.js

Lines changed: 12 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -94,17 +94,17 @@ export const BookmarkService = {
9494
};
9595
},
9696

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

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

103103
const totalCount = excludeFullSlots
104104
? await BookmarkRepository.countAvailableBookmarksByUserId(userId)
105105
: await BookmarkRepository.countBookmarksByUserId(userId);
106106

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

137-
const hasNext = filteredItems.length > limit;
138-
const finalItems = hasNext ? filteredItems.slice(0, -1) : filteredItems;
139-
140-
// nextCursor 생성
141-
let nextCursor = null;
142-
if (hasNext && finalItems.length > 0) {
143-
const lastFormattedItem = finalItems[finalItems.length - 1];
144-
const originalBookmark = bookmarks.find(b => Number(b.commission.id) === lastFormattedItem.id);
145-
146-
const cursorData = {
147-
id: lastFormattedItem.id,
148-
created_at: originalBookmark.createdAt.toISOString()
149-
};
150-
151-
if (sort === 'price_low' || sort === 'price_high') {
152-
cursorData.min_price = lastFormattedItem.minPrice;
153-
}
154-
155-
nextCursor = Buffer.from(JSON.stringify(cursorData)).toString('base64');
156-
}
157-
137+
// 페이지네이션 계산
138+
const totalPages = Math.ceil(totalCount / limit);
158139

159140
return {
160-
totalCount,
161-
hasNext,
162-
nextCursor,
163-
items: finalItems
141+
items: filteredItems,
142+
pagination: {
143+
page,
144+
limit,
145+
totalCount,
146+
totalPages
147+
}
164148
};
165149
},
166150
};

src/commission/commission.routes.js

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,19 @@ import {
44
getCommissionArtistInfo,
55
getCommissionForm,
66
uploadRequestImage,
7-
submitCommissionRequest
7+
submitCommissionRequest,
8+
getCommissionReport
89
} from "./controller/commission.controller.js";
910
import { authenticate } from "../middlewares/auth.middleware.js";
1011

1112
const router = Router();
1213

14+
// 커미션 리포트 조회 API
15+
router.get('/reports', authenticate, getCommissionReport);
16+
17+
// 커미션 신청 이미지 업로드 API
18+
router.post('/request-images/upload', authenticate, uploadRequestImage);
19+
1320
// 커미션 게시글 상세글 조회 API
1421
router.get('/:commissionId', authenticate, getCommissionDetail);
1522

@@ -19,9 +26,6 @@ router.get('/:commissionId/artist', authenticate, getCommissionArtistInfo);
1926
// 커미션 신청폼 조회 API
2027
router.get('/:commissionId/forms', authenticate, getCommissionForm);
2128

22-
// 커미션 신청 이미지 업로드 API
23-
router.post('/request-images/upload', authenticate, uploadRequestImage);
24-
2529
// 커미션 신청 제출 API
2630
router.post('/:commissionId/requests', authenticate, submitCommissionRequest);
2731

src/commission/controller/commission.controller.js

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
// /src/commission/controller/commission.controller.js
21
import { StatusCodes } from "http-status-codes";
32
import { CommissionService } from '../service/commission.service.js';
43
import
@@ -100,4 +99,18 @@ export const submitCommissionRequest = async (req, res, next) => {
10099
} catch (err) {
101100
next(err);
102101
}
102+
};
103+
104+
// 커미션 리포트 조회
105+
export const getCommissionReport = async (req, res, next) => {
106+
try {
107+
const userId = BigInt(req.user.userId);
108+
109+
const result = await CommissionService.getReport(userId);
110+
const responseData = parseWithBigInt(stringifyWithBigInt(result));
111+
112+
res.status(StatusCodes.OK).success(responseData);
113+
} catch (err) {
114+
next(err);
115+
}
103116
};

src/commission/repository/commission.repository.js

Lines changed: 60 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
// /src/commission/repository/commission.repository.js
21
import { prisma } from "../../db.config.js"
32

43
export const CommissionRepository = {
@@ -275,5 +274,64 @@ export const CommissionRepository = {
275274
},
276275
orderBy: { orderIndex: 'asc' }
277276
});
278-
}
277+
},
278+
279+
/**
280+
* 특정 월에 승인받은 사용자의 리퀘스트 조회 (커미션 리포트용)
281+
*/
282+
async findApprovedRequestsByUserAndMonth(userId, year, month) {
283+
const startDate = new Date(year, month - 1, 1);
284+
const endDate = new Date(year, month, 1);
285+
286+
return await prisma.request.findMany({
287+
where: {
288+
userId: BigInt(userId),
289+
approvedAt: {
290+
gte: startDate,
291+
lt: endDate
292+
}
293+
},
294+
include: {
295+
commission: {
296+
select: {
297+
id: true,
298+
categoryId: true,
299+
artist: {
300+
select: {
301+
id: true,
302+
nickname: true,
303+
profileImage: true
304+
}
305+
},
306+
category: {
307+
select: {
308+
name: true
309+
}
310+
}
311+
}
312+
},
313+
reviews: {
314+
select: {
315+
id: true
316+
}
317+
}
318+
}
319+
});
320+
},
321+
322+
/**
323+
* 사용자 닉네임 조회
324+
*/
325+
async findUserNicknameById(userId) {
326+
const user = await prisma.user.findUnique({
327+
where: {
328+
id: BigInt(userId)
329+
},
330+
select: {
331+
nickname: true
332+
}
333+
});
334+
335+
return user?.nickname || null;
336+
}
279337
}

0 commit comments

Comments
 (0)