-
Notifications
You must be signed in to change notification settings - Fork 0
refactor: 모집글 예외 처리 및 응답 포맷 통일 #54
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 1 commit
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 |
|---|---|---|
| @@ -1,21 +1,28 @@ | ||
| package com.example.recruitment.controller; | ||
|
|
||
| import com.example.recruitment.common.ApiResponse; | ||
| import com.example.recruitment.dto.RecruitmentDetailDto; | ||
| import com.example.recruitment.dto.RecruitmentRequestDto; | ||
| import com.example.recruitment.dto.RecruitmentResponseDto; | ||
| import com.example.recruitment.dto.order.OrderRequestDto; | ||
| import com.example.recruitment.entity.Recruitment; | ||
| import com.example.recruitment.entity.RecruitmentParticipant; | ||
| import com.example.recruitment.entity.User; | ||
| import com.example.recruitment.entity.Store; | ||
| import com.example.recruitment.exception.CustomException; | ||
| import com.example.recruitment.exception.ErrorCode; | ||
| import com.example.recruitment.repository.RecruitmentParticipantRepository; | ||
| import com.example.recruitment.repository.RecruitmentRepository; | ||
| import com.example.recruitment.repository.StoreRepository; | ||
| import com.example.recruitment.service.RecruitmentService; | ||
| import jakarta.validation.Valid; | ||
| import lombok.RequiredArgsConstructor; | ||
| import org.springframework.http.HttpStatus; | ||
| import org.springframework.http.ResponseEntity; | ||
| import org.springframework.web.bind.annotation.*; | ||
|
|
||
| import java.time.LocalDateTime; | ||
| import java.util.List; | ||
| import java.util.Map; | ||
|
|
||
| @RestController | ||
| @RequiredArgsConstructor | ||
|
|
@@ -25,78 +32,86 @@ public class RecruitmentController { | |
| private final RecruitmentService recruitmentService; | ||
| private final RecruitmentRepository recruitmentRepository; | ||
| private final RecruitmentParticipantRepository participantRepository; | ||
| private final StoreRepository storeRepository; | ||
|
|
||
| //모집글 생성 (Order 서버에 주문 생성 포함) | ||
| @PostMapping | ||
| public ResponseEntity<?> createRecruitment(@Valid @RequestBody RecruitmentRequestDto dto) { | ||
| recruitmentService.createRecruitment(dto); | ||
| return ResponseEntity.ok("모집글 생성 완료"); | ||
| public ResponseEntity<ApiResponse<Map<String, Long>>> createRecruitment(@Valid @RequestBody RecruitmentRequestDto dto) { | ||
| Long recruitmentId = recruitmentService.createRecruitment(dto); | ||
| return ResponseEntity | ||
| .status(HttpStatus.CREATED) | ||
| .body(ApiResponse.created(Map.of("recruitmentId", recruitmentId), "모집글 생성 완료")); | ||
| } | ||
|
|
||
| //모집글 참여 (Order 서버에 주문 생성 포함) | ||
| @PostMapping("/{recruitmentId}/join") | ||
| public ResponseEntity<?> joinRecruitment(@PathVariable Long recruitmentId, | ||
| @RequestParam Long userId, | ||
| @RequestBody OrderRequestDto orderRequestDto) { | ||
| public ResponseEntity<ApiResponse<String>> joinRecruitment(@PathVariable Long recruitmentId, | ||
| @RequestParam Long userId, | ||
| @Valid @RequestBody OrderRequestDto orderRequestDto) { | ||
| recruitmentService.joinRecruitment(recruitmentId, userId, orderRequestDto); | ||
| return ResponseEntity.ok("모집글 참여 완료"); | ||
| return ResponseEntity.ok(ApiResponse.ok(null, "모집글 참여 완료")); | ||
| } | ||
|
|
||
| // 모집글 전체 조회 | ||
| @GetMapping | ||
| public List<Recruitment> getAll() { | ||
| return recruitmentRepository.findAll(); | ||
| public ResponseEntity<ApiResponse<List<RecruitmentResponseDto>>> getAll() { | ||
| List<Recruitment> recruitments = recruitmentRepository.findAll(); | ||
| List<RecruitmentResponseDto> response = recruitments.stream() | ||
| .map(RecruitmentResponseDto::new) | ||
| .toList(); | ||
| return ResponseEntity.ok(ApiResponse.ok(response, "모든 모집글 조회 성공")); | ||
| } | ||
|
|
||
| // 상태별 조회 | ||
| @GetMapping(params = "status") | ||
| public List<Recruitment> getByStatus(@RequestParam String status) { | ||
| return recruitmentRepository.findByStatus(status); | ||
| public ResponseEntity<ApiResponse<List<RecruitmentResponseDto>>> getByStatus(@RequestParam String status) { | ||
| List<Recruitment> recruitments = recruitmentRepository.findByStatus(status); | ||
| List<RecruitmentResponseDto> response = recruitments.stream() | ||
| .map(RecruitmentResponseDto::new) | ||
| .toList(); | ||
| return ResponseEntity.ok(ApiResponse.ok(response, "상태별 모집글 조회 성공")); | ||
| } | ||
|
|
||
| // 모집글 상세 조회 | ||
| @GetMapping("/{recruitmentId}") | ||
| public ResponseEntity<?> getRecruitmentDetail(@PathVariable Long recruitmentId) { | ||
| Recruitment recruitment = recruitmentRepository.findById(recruitmentId).orElseThrow(); | ||
| public ResponseEntity<ApiResponse<RecruitmentDetailDto>> getRecruitmentDetail(@PathVariable Long recruitmentId) { | ||
| Recruitment recruitment = recruitmentRepository.findById(recruitmentId) | ||
| .orElseThrow(() -> new CustomException(ErrorCode.NOT_FOUND)); | ||
|
|
||
| List<RecruitmentParticipant> participants = participantRepository.findByRecruitmentId(recruitmentId); | ||
|
|
||
| List<RecruitmentParticipant> participants = | ||
| participantRepository.findByRecruitmentId(recruitmentId); | ||
| List<User> participantUsers = participants.stream() | ||
| .map(RecruitmentParticipant::getUser) | ||
| List<RecruitmentDetailDto.UserDto> participantUsers = participants.stream() | ||
| .map(p -> new RecruitmentDetailDto.UserDto(p.getUser())) | ||
| .toList(); | ||
|
|
||
| RecruitmentDetailDto dto = new RecruitmentDetailDto(); | ||
| dto.setId(recruitment.getId()); | ||
| dto.setTitle(recruitment.getTitle()); | ||
| dto.setDescription(recruitment.getDescription()); | ||
| dto.setStatus(recruitment.getStatus()); | ||
| dto.setDeadlineTime(recruitment.getDeadlineTime()); | ||
| dto.setUser(recruitment.getUser()); | ||
| dto.setStore(recruitment.getStore()); | ||
| dto.setParticipants(participantUsers); | ||
|
|
||
| return ResponseEntity.ok(dto); | ||
| List<Long> orderIds = participants.stream() | ||
| .map(RecruitmentParticipant::getOrderId) | ||
| .toList(); | ||
|
|
||
| RecruitmentDetailDto dto = new RecruitmentDetailDto(recruitment, participantUsers, orderIds); | ||
|
|
||
| return ResponseEntity.ok(ApiResponse.ok(dto, "모집 상세 조회 성공")); | ||
| } | ||
|
Comment on lines
+72
to
94
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. 💡 Verification agent 🧩 Analysis chain상세 조회 엔드포인트가 크게 개선됨 다음과 같은 개선사항들이 적용되었습니다:
참여자의 🏁 Script executed: #!/bin/bash
# Description: RecruitmentParticipant 엔티티의 orderid 필드 null 안전성 확인
# RecruitmentParticipant 클래스에서 orderId 필드와 getter 메서드 확인
ast-grep --pattern 'class RecruitmentParticipant {
$$$
orderId
$$$
}'
# orderId getter 메서드 확인
rg -A 5 "getOrderId" --type javaLength of output: 944 다음 스크립트로 🏁 Script executed: #!/bin/bash
# 1) 엔티티 클래스 파일 위치 확인
entity_file=$(rg -l "class\s\+RecruitmentParticipant" --type java)
echo ">> RecruitmentParticipant 엔티티 파일: $entity_file"
# 2) orderId 필드 선언부 및 nullable 설정 확인
rg -n "@Column.*nullable" -C2 "$entity_file"
rg -n "private\s\+Long\s\+orderId" -C2 "$entity_file"
# 3) getter 구현부 확인
rg -n "getOrderId" -C3 "$entity_file"Length of output: 376 다음 스크립트로 🏁 Script executed: #!/bin/bash
# 1) 엔티티 클래스 파일 경로 검색
entity_file=$(fd RecruitmentParticipant.java)
echo ">> RecruitmentParticipant 엔티티 파일 경로: $entity_file"
# 2) 클래스 선언부 확인
rg -n "class RecruitmentParticipant" -C2 "$entity_file"
# 3) orderId 필드 선언부 및 @Column nullable 설정 확인
rg -n "private\s\+Long\s\+orderId" -C2 "$entity_file"
rg -n "@Column.*nullable" -C2 "$entity_file"
# 4) getOrderId() 구현 확인
rg -n "getOrderId" -C3 "$entity_file"Length of output: 1142 RecruitmentParticipant 엔티티에 현재
수정 예시: @Entity
@NoArgsConstructor
@AllArgsConstructor
public class RecruitmentParticipant {
// ... 기존 필드 ...
@Column(name = "order_id", nullable = true)
private Long orderId;
public Long getOrderId() {
return this.orderId;
}
// ... 나머지 메서드 ...
}필드와 getter를 추가하거나, Lombok의 🤖 Prompt for AI Agents |
||
|
|
||
| // 유저가 만든 모집글 | ||
| @GetMapping("/user/{userId}/created-recruitments") | ||
| public List<Recruitment> getRecruitmentsCreatedByUser(@PathVariable Long userId) { | ||
| return recruitmentRepository.findByUserId(userId); | ||
| public ResponseEntity<ApiResponse<List<RecruitmentResponseDto>>> getRecruitmentsCreatedByUser(@PathVariable Long userId) { | ||
| List<Recruitment> list = recruitmentRepository.findByUserId(userId); | ||
| List<RecruitmentResponseDto> response = list.stream() | ||
| .map(RecruitmentResponseDto::new) | ||
| .toList(); | ||
| return ResponseEntity.ok(ApiResponse.ok(response, "작성한 모집글 조회 성공")); | ||
| } | ||
|
|
||
| // 유저가 참여한 모집글 | ||
| @GetMapping("/user/{userId}/joined-recruitments") | ||
| public List<Recruitment> getRecruitmentsJoinedByUser(@PathVariable Long userId) { | ||
| public ResponseEntity<ApiResponse<List<RecruitmentResponseDto>>> getRecruitmentsJoinedByUser(@PathVariable Long userId) { | ||
| List<RecruitmentParticipant> participantList = participantRepository.findByUserId(userId); | ||
| return participantList.stream() | ||
| List<RecruitmentResponseDto> response = participantList.stream() | ||
| .map(RecruitmentParticipant::getRecruitment) | ||
| .map(RecruitmentResponseDto::new) | ||
| .toList(); | ||
| return ResponseEntity.ok(ApiResponse.ok(response, "참여한 모집글 조회 성공")); | ||
| } | ||
|
|
||
| // 모집 상태 업데이트 | ||
| @PatchMapping("/{recruitmentId}/status") | ||
| public ResponseEntity<?> updateRecruitmentStatus(@PathVariable Long recruitmentId) { | ||
| Recruitment recruitment = recruitmentRepository.findById(recruitmentId).orElseThrow(); | ||
| public ResponseEntity<ApiResponse<String>> updateRecruitmentStatus(@PathVariable Long recruitmentId) { | ||
| Recruitment recruitment = recruitmentRepository.findById(recruitmentId) | ||
| .orElseThrow(() -> new CustomException(ErrorCode.NOT_FOUND)); | ||
|
|
||
| LocalDateTime now = LocalDateTime.now(); | ||
| long participantCount = participantRepository.countByRecruitmentId(recruitmentId); | ||
|
|
||
|
|
@@ -107,62 +122,70 @@ public ResponseEntity<?> updateRecruitmentStatus(@PathVariable Long recruitmentI | |
| recruitment.setStatus("FAILED"); | ||
| } | ||
| recruitmentRepository.save(recruitment); | ||
| return ResponseEntity.ok("상태가 " + recruitment.getStatus() + "로 변경되었습니다."); | ||
| return ResponseEntity.ok(ApiResponse.ok(null, "상태가 " + recruitment.getStatus() + "로 변경되었습니다.")); | ||
| } else { | ||
| return ResponseEntity.ok("아직 마감 시간이 지나지 않았습니다."); | ||
| return ResponseEntity.ok(ApiResponse.ok(null, "아직 마감 시간이 지나지 않았습니다.")); | ||
| } | ||
| } | ||
|
|
||
| // 주문 수락 상태 변경 | ||
| @PatchMapping("/{recruitmentId}/accept") | ||
| public ResponseEntity<?> acceptRecruitment(@PathVariable Long recruitmentId) { | ||
| Recruitment recruitment = recruitmentRepository.findById(recruitmentId).orElseThrow(); | ||
| public ResponseEntity<ApiResponse<String>> acceptRecruitment(@PathVariable Long recruitmentId) { | ||
| Recruitment recruitment = recruitmentRepository.findById(recruitmentId) | ||
| .orElseThrow(() -> new CustomException(ErrorCode.NOT_FOUND)); | ||
|
|
||
| if (!"CONFIRMED".equals(recruitment.getStatus())) { | ||
| return ResponseEntity.badRequest().body("주문 수락은 CONFIRMED 상태에서만 가능합니다."); | ||
| return ResponseEntity.badRequest() | ||
| .body(ApiResponse.fail(4001, "주문 수락은 CONFIRMED 상태에서만 가능합니다.", HttpStatus.BAD_REQUEST)); | ||
| } | ||
| recruitment.setStatus("ACCEPTED"); | ||
| recruitmentRepository.save(recruitment); | ||
| return ResponseEntity.ok("상태가 ACCEPTED로 변경되었습니다."); | ||
| return ResponseEntity.ok(ApiResponse.ok(null, "상태가 ACCEPTED로 변경되었습니다.")); | ||
| } | ||
|
|
||
| // 배달 완료 상태 변경 | ||
| @PatchMapping("/{recruitmentId}/deliver") | ||
| public ResponseEntity<?> completeDelivery(@PathVariable Long recruitmentId) { | ||
| Recruitment recruitment = recruitmentRepository.findById(recruitmentId).orElseThrow(); | ||
| public ResponseEntity<ApiResponse<String>> completeDelivery(@PathVariable Long recruitmentId) { | ||
| Recruitment recruitment = recruitmentRepository.findById(recruitmentId) | ||
| .orElseThrow(() -> new CustomException(ErrorCode.NOT_FOUND)); | ||
|
|
||
| if (!"ACCEPTED".equals(recruitment.getStatus())) { | ||
| return ResponseEntity.badRequest().body("배달 완료는 ACCEPTED 상태에서만 가능합니다."); | ||
| return ResponseEntity.badRequest() | ||
| .body(ApiResponse.fail(4002, "배달 완료는 ACCEPTED 상태에서만 가능합니다.", HttpStatus.BAD_REQUEST)); | ||
| } | ||
| recruitment.setStatus("DELIVERED"); | ||
| recruitmentRepository.save(recruitment); | ||
| return ResponseEntity.ok("상태가 DELIVERED로 변경되었습니다."); | ||
| return ResponseEntity.ok(ApiResponse.ok(null, "상태가 DELIVERED로 변경되었습니다.")); | ||
| } | ||
|
|
||
| // 모집글 삭제 | ||
| @DeleteMapping("/{recruitmentId}") | ||
| public ResponseEntity<?> deleteRecruitment(@PathVariable Long recruitmentId) { | ||
| Recruitment recruitment = recruitmentRepository.findById(recruitmentId).orElseThrow(); | ||
| public ResponseEntity<ApiResponse<String>> deleteRecruitment(@PathVariable Long recruitmentId) { | ||
| Recruitment recruitment = recruitmentRepository.findById(recruitmentId) | ||
| .orElseThrow(() -> new CustomException(ErrorCode.NOT_FOUND)); | ||
| recruitmentRepository.delete(recruitment); | ||
| return ResponseEntity.ok("모집글이 삭제되었습니다."); | ||
| return ResponseEntity.ok(ApiResponse.ok(null, "모집글이 삭제되었습니다.")); | ||
| } | ||
|
|
||
| // 모집글 수정 | ||
| @PutMapping("/{recruitmentId}") | ||
| public ResponseEntity<?> updateRecruitment(@PathVariable Long recruitmentId, | ||
| @Valid @RequestBody RecruitmentRequestDto dto) { | ||
| Recruitment recruitment = recruitmentRepository.findById(recruitmentId).orElseThrow(); | ||
| public ResponseEntity<ApiResponse<String>> updateRecruitment(@PathVariable Long recruitmentId, | ||
| @Valid @RequestBody RecruitmentRequestDto dto) { | ||
| Recruitment recruitment = recruitmentRepository.findById(recruitmentId) | ||
| .orElseThrow(() -> new CustomException(ErrorCode.NOT_FOUND)); | ||
|
|
||
| if (!recruitment.getUser().getId().equals(dto.getUserId())) { | ||
| return ResponseEntity.status(403).body("작성자만 수정할 수 있습니다."); | ||
| return ResponseEntity.status(HttpStatus.FORBIDDEN) | ||
| .body(ApiResponse.fail(4031, "작성자만 수정할 수 있습니다.", HttpStatus.FORBIDDEN)); | ||
| } | ||
|
|
||
| recruitment.setTitle(dto.getTitle()); | ||
| recruitment.setDescription(dto.getDescription()); | ||
| recruitment.setDeadlineTime(dto.getDeadlineTime()); | ||
|
|
||
| if (dto.getStoreId() != null) { | ||
| recruitment.setStore(recruitment.getStore()); | ||
| Store store = storeRepository.findById(dto.getStoreId()) | ||
| .orElseThrow(() -> new CustomException(ErrorCode.NOT_FOUND)); | ||
| recruitment.setStore(store); | ||
| } | ||
|
|
||
| recruitmentRepository.save(recruitment); | ||
| return ResponseEntity.ok("모집글이 수정되었습니다."); | ||
| return ResponseEntity.ok(ApiResponse.ok(null, "모집글이 수정되었습니다.")); | ||
| } | ||
| } | ||
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.
🛠️ Refactor suggestion
UserClient 호출 시 예외 처리 필요
UserClient의 getUserById 메서드는 외부 서비스 호출이므로 실패할 수 있습니다. 특히 반복문 내에서 호출 시 일부 사용자 정보 조회 실패로 전체 요청이 실패할 수 있습니다.
부분 실패를 허용하는 방식으로 개선하거나, 최소한 적절한 예외 처리를 추가하세요:
📝 Committable suggestion
🤖 Prompt for AI Agents