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
106 changes: 106 additions & 0 deletions src/common/swagger/request.json
Original file line number Diff line number Diff line change
Expand Up @@ -464,6 +464,112 @@
}
}
}
},
"/api/requests/record": {
"get": {
"tags": ["Request"],
"summary": "완료된 신청내역 조회",
"description": "사용자가 신청한 커미션 중 완료된(COMPLETED) 내역을 조회합니다. 정렬 기능과 페이지네이션을 지원합니다.",
"security": [
{
"bearerAuth": []
}
],
"parameters": [
{
"name": "sort",
"in": "query",
"required": false,
"schema": {
"type": "string",
"default": "latest",
"enum": ["latest", "oldest", "price_low", "price_high"]
},
"description": "정렬 방식 (latest: 최신순, oldest: 오래된순, price_low: 저가순, price_high: 고가순)"
},
{
"name": "page",
"in": "query",
"required": false,
"schema": {
"type": "integer",
"default": 1
},
"description": "페이지 번호"
},
{
"name": "limit",
"in": "query",
"required": false,
"schema": {
"type": "integer",
"default": 10
},
"description": "페이지당 항목 수"
}
],
"responses": {
"200": {
"description": "완료된 신청내역 조회 성공",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"resultType": { "type": "string", "example": "SUCCESS" },
"error": { "type": "null", "example": null },
"success": {
"type": "object",
"properties": {
"requests": {
"type": "array",
"items": {
"type": "object",
"properties": {
"requestId": { "type": "integer", "example": 1 },
"status": { "type": "string", "example": "COMPLETED" },
"title": { "type": "string", "example": "낙서 타입 커미션" },
"totalPrice": { "type": "integer", "example": 50000 },
"completedAt": { "type": "string", "format": "date-time", "example": "2025-06-04T15:30:00.000Z" },
"thumbnailImageUrl": {
"type": "string",
"nullable": true,
"example": "https://example.com/commission-thumbnail.jpg"
},
"artist": {
"type": "object",
"properties": {
"id": { "type": "integer", "example": 1 },
"nickname": { "type": "string", "example": "키르" }
}
},
"commission": {
"type": "object",
"properties": {
"id": { "type": "integer", "example": 12 }
}
}
}
}
},
"pagination": {
"type": "object",
"properties": {
"page": { "type": "integer", "example": 1 },
"limit": { "type": "integer", "example": 10 },
"totalCount": { "type": "integer", "example": 25 },
"totalPages": { "type": "integer", "example": 3 }
}
}
}
}
}
}
}
}
}
}
}
}
},
"components": {
Expand Down
22 changes: 21 additions & 1 deletion src/request/controller/request.controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ import {
GetRequestListDto,
UpdateRequestStatusDto,
GetRequestDetailDto,
GetRequestFormDto
GetRequestFormDto,
GetCompletedRequestsDto
} from "../dto/request.dto.js";
import { parseWithBigInt, stringifyWithBigInt } from "../../bigintJson.js";

Expand Down Expand Up @@ -73,6 +74,25 @@ export const getSubmittedRequestForm = async (req, res, next) => {
const result = await RequestService.getSubmittedRequestForm(userId, dto);
const responseData = parseWithBigInt(stringifyWithBigInt(result));

res.status(StatusCodes.OK).success(responseData);
} catch (err) {
next(err);
}
};

// 완료된 신청내역 조회
export const getCompletedRequests = async (req, res, next) => {
try {
const userId = BigInt(req.user.userId);
const dto = new GetCompletedRequestsDto({
sort: req.query.sort,
page: req.query.page,
limit: req.query.limit
});

const result = await RequestService.getCompletedRequests(userId, dto);
const responseData = parseWithBigInt(stringifyWithBigInt(result));

res.status(StatusCodes.OK).success(responseData);
} catch (err) {
next(err);
Expand Down
9 changes: 9 additions & 0 deletions src/request/dto/request.dto.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,13 @@ export class GetRequestFormDto {
constructor({ requestId }) {
this.requestId = requestId;
}
}

// 완료된 신청내역 조회 dto
export class GetCompletedRequestsDto {
constructor({ sort = 'latest', page = 1, limit = 10 }) {
this.sort = sort;
this.page = parseInt(page);
this.limit = parseInt(limit);
}
}
61 changes: 61 additions & 0 deletions src/request/repository/request.repository.js
Original file line number Diff line number Diff line change
Expand Up @@ -242,5 +242,66 @@ async createRequestImage(imageData) {
orderIndex: imageData.orderIndex
}
});
},

/**
* 완료된 신청내역 조회
*/
async findCompletedRequestsByUserId(userId, sort, offset, limit) {
// 정렬 조건 설정
let orderBy = {};

switch (sort) {
case 'latest':
orderBy = { completedAt: 'desc' };
break;
case 'oldest':
orderBy = { completedAt: 'asc' };
break;
case 'price_low':
orderBy = { totalPrice: 'asc' };
break;
case 'price_high':
orderBy = { totalPrice: 'desc' };
break;
default:
orderBy = { completedAt: 'desc' };
}

return await prisma.request.findMany({
where: {
userId: BigInt(userId),
status: 'COMPLETED'
},
include: {
commission: {
select: {
id: true,
title: true,
artist: {
select: {
id: true,
nickname: true
}
}
}
}
},
orderBy: orderBy,
skip: offset,
take: limit
});
},

/**
* 완료된 신청내역 총 개수 조회
*/
async countCompletedRequestsByUserId(userId) {
return await prisma.request.count({
where: {
userId: BigInt(userId),
status: 'COMPLETED'
}
});
}
};
5 changes: 4 additions & 1 deletion src/request/request.routes.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ import {
getRequestList,
getRequestDetail,
updateRequestStatus,
getSubmittedRequestForm
getSubmittedRequestForm,
getCompletedRequests
} from "./controller/request.controller.js";
import { authenticate } from "../middlewares/auth.middleware.js";
import reviewController from '../review/controller/review.controller.js';
Expand All @@ -12,6 +13,8 @@ const router = Router();

// 신청 목록 조회 API
router.get('/', authenticate, getRequestList);
// 완료된 신청내역 조회 API
router.get('/record', authenticate, getCompletedRequests);
// 신청 상세 조회 API
router.get('/:requestId', authenticate, getRequestDetail);
// 신청 상태 변경 API
Expand Down
63 changes: 62 additions & 1 deletion src/request/service/request.service.js
Original file line number Diff line number Diff line change
Expand Up @@ -404,5 +404,66 @@ export const RequestService = {
formResponses: formResponses,
requestContent: requestContent
};
}
},

/**
* 완료된 신청내역 조회
*/
async getCompletedRequests(userId, dto) {
const { sort, page, limit } = dto;
const offset = (page - 1) * limit;

// 정렬 옵션 유효성 검증
const validSorts = ['latest', 'oldest', 'price_low', 'price_high'];
if (!validSorts.includes(sort)) {
throw new InvalidRequestFilterError({
filter: sort,
validOptions: validSorts
});
}

// 완료된 신청내역 조회
const requests = await RequestRepository.findCompletedRequestsByUserId(userId, sort, offset, limit);

// 총 개수 조회
const totalCount = await RequestRepository.countCompletedRequestsByUserId(userId);
const totalPages = Math.ceil(totalCount / limit);

// 커미션 ID로 썸네일 이미지 조회
const commissionIds = requests.map(request => request.commission.id);
const thumbnailImages = await RequestRepository.findThumbnailImagesByCommissionIds(commissionIds);

// 썸네일 이미지 매핑
const thumbnailMap = {};
thumbnailImages.forEach(image => {
thumbnailMap[image.targetId.toString()] = image.imageUrl;
});

// 응답 데이터 구성
const responseRequests = requests.map(request => ({
requestId: request.id,
status: request.status,
title: request.commission.title,
totalPrice: request.totalPrice,
completedAt: request.completedAt.toISOString(),
thumbnailImageUrl: thumbnailMap[request.commission.id.toString()] || null,
artist: {
id: request.commission.artist.id,
nickname: request.commission.artist.nickname
},
commission: {
id: request.commission.id
}
}));

return {
requests: responseRequests,
pagination: {
page,
limit,
totalCount,
totalPages
}
};
}
};