-
Notifications
You must be signed in to change notification settings - Fork 0
[CMAT-48] feat: 커리어 작성 API에 이미지 업로드 기능 추가 #31
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
af69291
481a4e2
4408c76
6a0a9b3
422ad55
c8b7728
c05b991
1b2f21a
e05fef2
34869a2
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -4,9 +4,6 @@ on: | |
| push: | ||
| branches: | ||
| - develop | ||
| pull_request: | ||
| branches: | ||
| - develop | ||
|
|
||
| permissions: | ||
| contents: read | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -17,18 +17,20 @@ | |
| import org.springframework.web.bind.annotation.GetMapping; | ||
| import org.springframework.web.bind.annotation.PatchMapping; | ||
| import org.springframework.web.bind.annotation.PostMapping; | ||
| import org.springframework.web.bind.annotation.RequestBody; | ||
| import org.springframework.web.bind.annotation.RequestMapping; | ||
| import org.springframework.web.bind.annotation.RequestParam; | ||
| import org.springframework.web.bind.annotation.RequestPart; | ||
| import org.springframework.web.bind.annotation.RestController; | ||
| import org.springframework.web.multipart.MultipartFile; | ||
|
|
||
| import java.io.IOException; | ||
| import java.util.List; | ||
|
|
||
| import static UMC.career_mate.global.response.result.code.CommonResultCode.*; | ||
|
|
||
| @RestController | ||
| @RequestMapping("/answers") | ||
| @Tag(name = "05. 답변 API", description = "답변 도메인의 API 입니다.") | ||
| @Tag(name = "답변 API", description = "답변 도메인의 API 입니다.") | ||
| @RequiredArgsConstructor | ||
| public class AnswerController { | ||
| private final AnswerCommandService answerCommandService; | ||
|
|
@@ -39,49 +41,50 @@ public class AnswerController { | |
| summary = "커리어 작성 API", | ||
| description = | ||
| """ | ||
| 커리어를 작성하는 API 입니다. | ||
| 커리어를 처음 작성할 때, 반드시 2개의 `answerInfoList` 데이터를 요청해야 합니다. 입력하지 않을 경우엔 빈 문자열로 요청을 할 수 있습니다. | ||
| 커리어를 작성하는 API입니다. | ||
| 커리어를 처음 작성할 때, 반드시 2개의 `answerInfoList` 데이터를 요청해야 합니다. | ||
| 입력하지 않을 경우엔 빈 문자열로 요청할 수 있습니다. | ||
| ## 하나의 커리어만 작성하는 경우 | ||
| ### Example REQUEST JSON: | ||
| ```json | ||
| { | ||
| "answerList": [ | ||
| "answerGroupDTOList": [ | ||
| { | ||
| "sequence": 1, | ||
| "answerInfoList": [ | ||
| { | ||
| "questionId": 30, | ||
| "content": "삼성전자 / IoT 개발팀" | ||
| }, | ||
| "answerInfoDTOList": [ | ||
| { | ||
| "questionId": 31, | ||
| "content": "백엔드 개발자" | ||
| "content": "카카오뱅크 / 대출상품기획팀" | ||
| }, | ||
| { | ||
| "questionId": 32, | ||
| "content": "2022.03.01~2022.08.01" | ||
| }, | ||
| { | ||
| "questionId": 33, | ||
| "content": "IoT 디바이스 통신 프로토콜을 설계하며, 효율적인 데이터 전송 방식에 대해 배웠습니다." | ||
| "content": "회사명 / IoT 디바이스 팀" | ||
| }, | ||
| { | ||
| "questionId": 34, | ||
| "content": "스마트 홈 서비스의 핵심 모듈을 개발하며, 사용자 경험 중심의 설계 중요성을 깨달았습니다." | ||
| "content": "IoT 디바이스 통신 프로토콜을 설계하며, 효율적인 데이터 전송 방식에 대해 배웠습니다." | ||
| }, | ||
| { | ||
| "questionId": 35, | ||
| "content": "스마트 홈 서비스의 핵심 모듈을 개발하며, 사용자 경험 중심의 설계 중요성을 깨달았습니다." | ||
| }, | ||
| { | ||
| "questionId": 36, | ||
| "content": "팀원들과의 코드 리뷰를 통해 문제를 다양한 시각으로 바라보는 법을 배웠습니다." | ||
| }, | ||
| { | ||
| "questionId": 37, | ||
| "content": "복잡한 문제를 해결하는 과정에서 논리적인 사고력과 협업 능력이 향상되었습니다." | ||
| } | ||
| ] | ||
| }, | ||
| { | ||
| "sequence": 2, | ||
| "answerInfoList": [ | ||
| { | ||
| "questionId": 30, | ||
| "content": "" | ||
| }, | ||
| "answerInfoDTOList": [ | ||
| { | ||
| "questionId": 31, | ||
| "content": "" | ||
|
|
@@ -101,6 +104,14 @@ public class AnswerController { | |
| { | ||
| "questionId": 35, | ||
| "content": "" | ||
| }, | ||
| { | ||
| "questionId": 36, | ||
| "content": "" | ||
| }, | ||
| { | ||
| "questionId": 37, | ||
| "content": "" | ||
| } | ||
| ] | ||
| } | ||
|
|
@@ -110,8 +121,9 @@ public class AnswerController { | |
| """ | ||
| ) | ||
| public ApiResponse<CommonResultCode> saveAnswerList(@LoginMember Member member, | ||
| @Valid @RequestBody AnswerCreateOrUpdateDTO answerCreateOrUpdateDTO) { | ||
| answerCommandService.saveAnswerList(member, answerCreateOrUpdateDTO); | ||
| @RequestPart(value = "data") @Valid AnswerCreateOrUpdateDTO answerCreateOrUpdateDTO, | ||
| @RequestPart(value = "image", required = false) List<MultipartFile> imageFileList) throws IOException { | ||
| answerCommandService.saveAnswerList(member, answerCreateOrUpdateDTO, imageFileList); | ||
| return ApiResponse.onSuccess(CREATE_ANSWER_LIST); | ||
| } | ||
|
|
||
|
|
@@ -143,62 +155,70 @@ public ApiResponse<List<AnswerInfoListDTO>> getAnswerList(@LoginMember Member me | |
| ### Example REQUEST JSON: | ||
| ```json | ||
| { | ||
| "answerList": [ | ||
| "answerGroupDTOList": [ | ||
| { | ||
| "sequence": 1, | ||
| "answerInfoList": [ | ||
| { | ||
| "questionId": 30, | ||
| "content": "삼성전자 / IoT 개발팀" | ||
| }, | ||
| "answerInfoDTOList": [ | ||
| { | ||
| "questionId": 31, | ||
| "content": "백엔드 개발자" | ||
| "content": "네이버 / 데이터 분석팀" | ||
| }, | ||
| { | ||
| "questionId": 32, | ||
| "content": "2022.03.01~2022.08.01" | ||
| "content": "2021.05.01~2022.02.28" | ||
| }, | ||
| { | ||
| "questionId": 33, | ||
| "content": "IoT 디바이스 통신 프로토콜을 설계하며, 효율적인 데이터 전송 방식에 대해 배웠습니다." | ||
| "content": "스타트업 / 마케팅 데이터 분석팀" | ||
| }, | ||
| { | ||
| "questionId": 34, | ||
| "content": "스마트 홈 서비스의 핵심 모듈을 개발하며, 사용자 경험 중심의 설계 중요성을 깨달았습니다." | ||
| "content": "A/B 테스트를 설계하여 마케팅 캠페인의 효과를 분석하고 최적의 전략을 도출했습니다." | ||
| }, | ||
| { | ||
| "questionId": 35, | ||
| "content": "팀원들과의 코드 리뷰를 통해 문제를 다양한 시각으로 바라보는 법을 배웠습니다." | ||
| "content": "데이터 시각화를 통해 인사이트를 제공하며, 의사결정 과정을 지원하는 방법을 배웠습니다." | ||
| }, | ||
| { | ||
| "questionId": 36, | ||
| "content": "Python과 SQL을 활용한 데이터 분석 프로젝트를 진행하며, 데이터 처리 및 분석 역량을 키웠습니다." | ||
| }, | ||
| { | ||
| "questionId": 37, | ||
| "content": "데이터 기반 의사결정의 중요성을 체감하며, 문제 해결력을 향상시킬 수 있었습니다." | ||
| } | ||
| ] | ||
| }, | ||
| { | ||
| "sequence": 2, | ||
| "answerInfoList": [ | ||
| { | ||
| "questionId": 30, | ||
| "content": "LG CNS / 클라우드 플랫폼 개발팀" | ||
| }, | ||
| "answerInfoDTOList": [ | ||
| { | ||
| "questionId": 31, | ||
| "content": "풀스택 개발자" | ||
| "content": "쿠팡 / 물류 최적화 팀" | ||
| }, | ||
| { | ||
| "questionId": 32, | ||
| "content": "2021.09.01~2022.02.01" | ||
| "content": "2022.06.01~2022.12.31" | ||
| }, | ||
| { | ||
| "questionId": 33, | ||
| "content": "클라우드 기반 서비스 배포 자동화를 구현하며 DevOps의 핵심 개념을 익혔습니다." | ||
| "content": "이커머스 기업 / 물류 데이터 분석팀" | ||
| }, | ||
| { | ||
| "questionId": 34, | ||
| "content": "대규모 사용자 트래픽을 처리하며 안정적인 시스템 운영 경험을 쌓았습니다." | ||
| "content": "배송 최적화를 위한 데이터 분석을 진행하며, 머신러닝을 활용한 수요 예측 모델을 개발했습니다." | ||
| }, | ||
| { | ||
| "questionId": 35, | ||
| "content": "다양한 클라우드 서비스를 연동하며 기술적 시야를 넓힐 수 있었습니다." | ||
| "content": "물류 데이터 분석을 통해 비용 절감과 효율성을 높이는 경험을 했습니다." | ||
| }, | ||
| { | ||
| "questionId": 36, | ||
| "content": "팀원들과 협업하여 데이터 기반 개선안을 도출하고, 이를 현업에 적용하는 과정을 배웠습니다." | ||
| }, | ||
| { | ||
| "questionId": 37, | ||
| "content": "데이터를 기반으로 문제를 해결하는 과정에서 논리적 사고력과 커뮤니케이션 능력을 키웠습니다." | ||
| } | ||
| ] | ||
| } | ||
|
|
@@ -208,8 +228,9 @@ public ApiResponse<List<AnswerInfoListDTO>> getAnswerList(@LoginMember Member me | |
| """ | ||
| ) | ||
| public ApiResponse<CommonResultCode> updateAnswerList(@LoginMember Member member, | ||
| @Valid @RequestBody AnswerCreateOrUpdateDTO answerCreateOrUpdateDTO) { | ||
| answerCommandService.updateAnswerList(member, answerCreateOrUpdateDTO); | ||
| @RequestPart("data") @Valid AnswerCreateOrUpdateDTO answerCreateOrUpdateDTO, | ||
| @RequestPart(value = "image", required = false) List<MultipartFile> imageFileList) throws IOException { | ||
| answerCommandService.updateAnswerList(member, answerCreateOrUpdateDTO, imageFileList); | ||
| return ApiResponse.onSuccess(UPDATE_ANSWER_LIST); | ||
|
Comment on lines
+231
to
234
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. updateAnswerList 메소드도 동일한 파일 검증이 필요합니다. saveAnswerList와 동일한 파일 업로드 보안 및 유효성 검증을 적용해야 합니다. |
||
| } | ||
|
|
||
|
|
@@ -225,7 +246,6 @@ public ApiResponse<CommonResultCode> updateAnswerList(@LoginMember Member member | |
| 3. 기타 활동 (OTHER_ACTIVITIES)\s | ||
| 4. 보유 기술 (TECHNICAL_SKILLS)\s | ||
| 5. 최종 정리 (SUMMARY) | ||
|
|
||
| ### Example Response JSON: | ||
| ```json | ||
| { | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,11 +1,10 @@ | ||||||||||||||||||||||||||||||||||||||||||||||
| package UMC.career_mate.domain.answer.converter; | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| import UMC.career_mate.domain.answer.Answer; | ||||||||||||||||||||||||||||||||||||||||||||||
| import UMC.career_mate.domain.answer.dto.request.AnswerCreateOrUpdateDTO; | ||||||||||||||||||||||||||||||||||||||||||||||
| import UMC.career_mate.domain.answer.dto.response.AnswerCompletionStatusInfoDTO; | ||||||||||||||||||||||||||||||||||||||||||||||
| import UMC.career_mate.domain.answer.dto.response.AnswerCompletionStatusInfoListDTO; | ||||||||||||||||||||||||||||||||||||||||||||||
| import UMC.career_mate.domain.answer.dto.response.AnswerInfoListDTO; | ||||||||||||||||||||||||||||||||||||||||||||||
| import UMC.career_mate.domain.answer.dto.response.AnswerInfoDTO; | ||||||||||||||||||||||||||||||||||||||||||||||
| import UMC.career_mate.domain.answer.dto.response.AnswerInfoListDTO; | ||||||||||||||||||||||||||||||||||||||||||||||
| import UMC.career_mate.domain.member.Member; | ||||||||||||||||||||||||||||||||||||||||||||||
| import UMC.career_mate.domain.question.Question; | ||||||||||||||||||||||||||||||||||||||||||||||
| import UMC.career_mate.domain.template.enums.TemplateType; | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -14,17 +13,17 @@ | |||||||||||||||||||||||||||||||||||||||||||||
| import java.util.stream.Collectors; | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| public class AnswerConverter { | ||||||||||||||||||||||||||||||||||||||||||||||
| public static Answer toAnswer(AnswerCreateOrUpdateDTO.AnswerInfo answerInfo, Member member, Question question, long sequence) { | ||||||||||||||||||||||||||||||||||||||||||||||
| public static Answer toAnswer(String content, Member member, Question question, long sequence) { | ||||||||||||||||||||||||||||||||||||||||||||||
| return Answer.builder() | ||||||||||||||||||||||||||||||||||||||||||||||
| .content(answerInfo.content()) | ||||||||||||||||||||||||||||||||||||||||||||||
| .content(content) | ||||||||||||||||||||||||||||||||||||||||||||||
| .member(member) | ||||||||||||||||||||||||||||||||||||||||||||||
| .question(question) | ||||||||||||||||||||||||||||||||||||||||||||||
| .sequence(sequence) | ||||||||||||||||||||||||||||||||||||||||||||||
| .build(); | ||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+16
to
23
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion toAnswer 메소드의 매개변수 유효성 검증이 필요합니다. 매개변수에 대한 null 체크가 없습니다. 다음과 같은 방어적 프로그래밍을 추가하는 것이 좋습니다: public static Answer toAnswer(String content, Member member, Question question, long sequence) {
+ Objects.requireNonNull(member, "Member cannot be null");
+ Objects.requireNonNull(question, "Question cannot be null");
+ if (content == null) {
+ content = "";
+ }
return Answer.builder()
.content(content)
.member(member)
.question(question)
.sequence(sequence)
.build();
}📝 Committable suggestion
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| public static AnswerInfoDTO toAnswerInfoDTO(Answer answer) { | ||||||||||||||||||||||||||||||||||||||||||||||
| return AnswerInfoDTO.builder() | ||||||||||||||||||||||||||||||||||||||||||||||
| return UMC.career_mate.domain.answer.dto.response.AnswerInfoDTO.builder() | ||||||||||||||||||||||||||||||||||||||||||||||
| .questionId(answer.getQuestion().getId()) | ||||||||||||||||||||||||||||||||||||||||||||||
| .questionName(answer.getQuestion().getContent()) | ||||||||||||||||||||||||||||||||||||||||||||||
| .content(answer.getContent()) | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -40,7 +39,7 @@ public static List<AnswerInfoDTO> toAnswerInfoDTOList(List<Answer> answerList) { | |||||||||||||||||||||||||||||||||||||||||||||
| public static AnswerInfoListDTO toAnswerListResponseDTO(Long sequence, List<Answer> answerList) { | ||||||||||||||||||||||||||||||||||||||||||||||
| return AnswerInfoListDTO.builder() | ||||||||||||||||||||||||||||||||||||||||||||||
| .sequence(sequence) | ||||||||||||||||||||||||||||||||||||||||||||||
| .answerList(toAnswerInfoDTOList(answerList)) | ||||||||||||||||||||||||||||||||||||||||||||||
| .answerInfoDTOList(toAnswerInfoDTOList(answerList)) | ||||||||||||||||||||||||||||||||||||||||||||||
| .build(); | ||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
파일 업로드 처리에 대한 보안 및 유효성 검증이 필요합니다.
이미지 파일 업로드 처리 시 다음 사항들을 고려해야 합니다:
다음과 같은 검증 로직을 추가하는 것을 권장합니다:
📝 Committable suggestion