Skip to content

Commit b7f5634

Browse files
authored
Merge pull request #94 from umc-commit/feat/93-get-requestform
[FEAT] 제출된 신청서 조회
2 parents 6683416 + 525cedd commit b7f5634

File tree

7 files changed

+299
-2
lines changed

7 files changed

+299
-2
lines changed

src/commission/service/commission.service.js

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import multer from 'multer';
22
import path from 'path';
33
import fs from 'fs';
44
import { CommissionRepository } from "../repository/commission.repository.js";
5+
import { RequestRepository } from "../../request/repository/request.repository.js";
56
import {
67
CommissionNotFoundError,
78
FileSizeExceededError,
@@ -286,6 +287,21 @@ export const CommissionService = {
286287
waitlist: waitlist
287288
});
288289

290+
// 참고 이미지들을 Image 테이블에 저장
291+
const fileFieldId = (customFields.length + 2).toString();
292+
const imageUrls = formAnswer[fileFieldId] || [];
293+
294+
if (imageUrls.length > 0) {
295+
for (let i = 0; i < imageUrls.length; i++) {
296+
await RequestRepository.createRequestImage({
297+
target: 'request',
298+
targetId: newRequest.id,
299+
imageUrl: imageUrls[i],
300+
orderIndex: i
301+
});
302+
}
303+
}
304+
289305
// 응답 데이터 구성
290306
return {
291307
requestId: newRequest.id,

src/common/swagger/request.json

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -355,6 +355,115 @@
355355
}
356356
}
357357
}
358+
},
359+
"/api/requests/{requestId}/forms": {
360+
"get": {
361+
"tags": ["Request"],
362+
"summary": "제출된 신청서 조회",
363+
"description": "사용자가 제출한 신청서의 상세 정보를 조회합니다. 커미션 정보, 작가 정보, 작성한 폼 내용, 신청 내용을 포함합니다.",
364+
"security": [
365+
{
366+
"bearerAuth": []
367+
}
368+
],
369+
"parameters": [
370+
{
371+
"name": "requestId",
372+
"in": "path",
373+
"required": true,
374+
"schema": {
375+
"type": "integer",
376+
"format": "int64"
377+
},
378+
"description": "신청서 ID"
379+
}
380+
],
381+
"responses": {
382+
"200": {
383+
"description": "제출된 신청서 조회 성공",
384+
"content": {
385+
"application/json": {
386+
"schema": {
387+
"type": "object",
388+
"properties": {
389+
"resultType": { "type": "string", "example": "SUCCESS" },
390+
"error": { "type": "null", "example": null },
391+
"success": {
392+
"type": "object",
393+
"properties": {
394+
"requestId": { "type": "integer", "example": 1 },
395+
"status": {
396+
"type": "string",
397+
"enum": ["PENDING", "APPROVED", "REJECTED", "IN_PROGRESS", "SUBMITTED", "COMPLETED", "CANCELED"],
398+
"example": "PENDING"
399+
},
400+
"displayTime": {
401+
"type": "string",
402+
"format": "date-time",
403+
"description": "CANCELED 상태면 updatedAt, 그 외에는 createdAt",
404+
"example": "2025-07-01T10:00:00.000Z"
405+
},
406+
"commission": {
407+
"type": "object",
408+
"properties": {
409+
"id": { "type": "integer", "example": 12 },
410+
"title": { "type": "string", "example": "커미션 제목" }
411+
}
412+
},
413+
"artist": {
414+
"type": "object",
415+
"properties": {
416+
"id": { "type": "integer", "example": 4 },
417+
"nickname": { "type": "string", "example": "위시" },
418+
"profileImageUrl": {
419+
"type": "string",
420+
"nullable": true,
421+
"example": "https://example.com/profile.jpg"
422+
}
423+
}
424+
},
425+
"formResponses": {
426+
"type": "array",
427+
"description": "커스텀 필드들의 질문-답변 쌍",
428+
"items": {
429+
"type": "object",
430+
"properties": {
431+
"questionId": { "type": "string", "example": "1" },
432+
"questionLabel": { "type": "string", "example": "당일마감" },
433+
"answer": { "type": "string", "example": "O" }
434+
}
435+
}
436+
},
437+
"requestContent": {
438+
"type": "object",
439+
"properties": {
440+
"text": {
441+
"type": "string",
442+
"nullable": true,
443+
"example": "예쁘게 그려주세요 ~"
444+
},
445+
"images": {
446+
"type": "array",
447+
"items": {
448+
"type": "object",
449+
"properties": {
450+
"id": { "type": "integer", "example": 101 },
451+
"imageUrl": { "type": "string", "example": "https://example.com/request-image1.jpg" },
452+
"orderIndex": { "type": "integer", "example": 0 }
453+
}
454+
}
455+
}
456+
}
457+
}
458+
}
459+
}
460+
}
461+
}
462+
}
463+
}
464+
}
465+
}
466+
}
358467
}
359468
},
360469
"components": {

src/request/controller/request.controller.js

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@ import { RequestService } from '../service/request.service.js';
33
import {
44
GetRequestListDto,
55
UpdateRequestStatusDto,
6-
GetRequestDetailDto
6+
GetRequestDetailDto,
7+
GetRequestFormDto
78
} from "../dto/request.dto.js";
89
import { parseWithBigInt, stringifyWithBigInt } from "../../bigintJson.js";
910

@@ -59,4 +60,21 @@ export const getRequestDetail = async (req, res, next) => {
5960
} catch (err) {
6061
next(err);
6162
}
63+
};
64+
65+
// 제출된 신청서 조회
66+
export const getSubmittedRequestForm = async (req, res, next) => {
67+
try {
68+
const userId = BigInt(req.user.userId);
69+
const dto = new GetRequestFormDto({
70+
requestId: BigInt(req.params.requestId)
71+
});
72+
73+
const result = await RequestService.getSubmittedRequestForm(userId, dto);
74+
const responseData = parseWithBigInt(stringifyWithBigInt(result));
75+
76+
res.status(StatusCodes.OK).success(responseData);
77+
} catch (err) {
78+
next(err);
79+
}
6280
};

src/request/dto/request.dto.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,4 +21,11 @@ export class GetRequestDetailDto {
2121
constructor({ requestId }) {
2222
this.requestId = BigInt(requestId);
2323
}
24+
}
25+
26+
// 제출된 신청서 조회 dto
27+
export class GetRequestFormDto {
28+
constructor({ requestId }) {
29+
this.requestId = requestId;
30+
}
2431
}

src/request/repository/request.repository.js

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,5 +188,59 @@ async findLatestPointTransactionByRequestId(requestId) {
188188
createdAt: 'desc'
189189
}
190190
});
191+
},
192+
193+
/**
194+
* 제출된 신청서 조회용 - Request 상세 정보
195+
*/
196+
async findSubmittedRequestById(requestId) {
197+
return await prisma.request.findUnique({
198+
where: {
199+
id: BigInt(requestId)
200+
},
201+
include: {
202+
commission: {
203+
select: {
204+
id: true,
205+
title: true,
206+
formSchema: true,
207+
artist: {
208+
select: {
209+
id: true,
210+
nickname: true,
211+
profileImage: true
212+
}
213+
}
214+
}
215+
}
216+
}
217+
});
218+
},
219+
220+
/**
221+
* Request 참고 이미지 조회
222+
*/
223+
async findImagesByRequestId(requestId) {
224+
return await prisma.image.findMany({
225+
where: {
226+
target: 'request',
227+
targetId: BigInt(requestId)
228+
},
229+
orderBy: { orderIndex: 'asc' }
230+
});
231+
},
232+
233+
/**
234+
* Request 이미지 생성
235+
*/
236+
async createRequestImage(imageData) {
237+
return await prisma.image.create({
238+
data: {
239+
target: imageData.target,
240+
targetId: imageData.targetId,
241+
imageUrl: imageData.imageUrl,
242+
orderIndex: imageData.orderIndex
243+
}
244+
});
191245
}
192246
};

src/request/request.routes.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@ import { Router } from 'express';
22
import {
33
getRequestList,
44
getRequestDetail,
5-
updateRequestStatus
5+
updateRequestStatus,
6+
getSubmittedRequestForm
67
} from "./controller/request.controller.js";
78
import { authenticate } from "../middlewares/auth.middleware.js";
89
import reviewController from '../review/controller/review.controller.js';
@@ -15,6 +16,8 @@ router.get('/', authenticate, getRequestList);
1516
router.get('/:requestId', authenticate, getRequestDetail);
1617
// 신청 상태 변경 API
1718
router.patch('/:requestId/status', authenticate, updateRequestStatus);
19+
// 제출된 신청서 조회 API
20+
router.get('/:requestId/forms', authenticate, getSubmittedRequestForm);
1821

1922
/**
2023
* 리뷰 작성 API

src/request/service/request.service.js

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -314,5 +314,95 @@ export const RequestService = {
314314
timeline: timeline,
315315
formData: formData
316316
};
317+
},
318+
319+
/**
320+
* 제출된 신청서 조회
321+
*/
322+
async getSubmittedRequestForm(userId, dto) {
323+
const { requestId } = dto;
324+
325+
// Request 존재 여부 및 권한 확인
326+
const request = await RequestRepository.findSubmittedRequestById(requestId);
327+
if (!request) {
328+
throw new RequestNotFoundError({ requestId });
329+
}
330+
331+
// 본인 신청서인지 확인
332+
if (request.userId !== BigInt(userId)) {
333+
throw new UnauthorizedRequestStatusChangeError({ userId, requestId });
334+
}
335+
336+
// 참고 이미지 조회
337+
const images = await RequestRepository.findImagesByRequestId(requestId);
338+
339+
// formSchema와 formAnswer 처리
340+
const customFields = request.commission.formSchema?.fields || [];
341+
const defaultFields = [
342+
{
343+
id: (customFields.length + 1).toString(),
344+
type: "textarea",
345+
label: "신청 내용"
346+
},
347+
{
348+
id: (customFields.length + 2).toString(),
349+
type: "file",
350+
label: "참고 이미지"
351+
}
352+
];
353+
const allFields = [...customFields, ...defaultFields];
354+
355+
// formResponses 구성 (커스텀 필드만)
356+
const formResponses = [];
357+
for (const field of customFields) {
358+
const answer = request.formAnswer[field.id];
359+
let answerLabel = null;
360+
361+
if (field.type === 'radio' && field.options) {
362+
const selectedOption = field.options.find(option => option.value === answer);
363+
answerLabel = selectedOption ? selectedOption.label : null;
364+
}
365+
366+
formResponses.push({
367+
questionId: field.id,
368+
questionLabel: field.label,
369+
answer: answerLabel
370+
});
371+
}
372+
373+
// requestContent 구성 (기본 필드들)
374+
const textFieldId = (customFields.length + 1).toString();
375+
const fileFieldId = (customFields.length + 2).toString();
376+
377+
const requestContent = {
378+
text: request.formAnswer[textFieldId] || null,
379+
images: images.map(img => ({
380+
id: img.id,
381+
imageUrl: img.imageUrl,
382+
orderIndex: img.orderIndex
383+
}))
384+
};
385+
386+
// displayTime 계산
387+
const displayTime = request.status === 'CANCELED'
388+
? request.updatedAt.toISOString()
389+
: request.createdAt.toISOString();
390+
391+
return {
392+
requestId: request.id,
393+
status: request.status,
394+
displayTime: displayTime,
395+
commission: {
396+
id: request.commission.id,
397+
title: request.commission.title
398+
},
399+
artist: {
400+
id: request.commission.artist.id,
401+
nickname: request.commission.artist.nickname,
402+
profileImageUrl: request.commission.artist.profileImage
403+
},
404+
formResponses: formResponses,
405+
requestContent: requestContent
406+
};
317407
}
318408
};

0 commit comments

Comments
 (0)