Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
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
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
Expand All @@ -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, "모집 상세 조회 성공"));
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

상세 조회 엔드포인트가 크게 개선됨

다음과 같은 개선사항들이 적용되었습니다:

  • CustomException을 사용한 표준화된 예외 처리
  • 참여자 정보를 안전하게 DTO로 변환
  • 주문 ID 목록 제공으로 추가 정보 노출
  • 일관된 응답 형식 적용

참여자의 getOrderId() 호출 시 null 안전성을 확인해 보겠습니다:


🏁 Script executed:

#!/bin/bash
# Description: RecruitmentParticipant 엔티티의 orderid 필드 null 안전성 확인

# RecruitmentParticipant 클래스에서 orderId 필드와 getter 메서드 확인
ast-grep --pattern 'class RecruitmentParticipant {
  $$$
  orderId
  $$$
}'

# orderId getter 메서드 확인
rg -A 5 "getOrderId" --type java

Length of output: 944


다음 스크립트로 RecruitmentParticipant 엔티티 파일 경로를 찾고, orderId 필드의 선언부와 null 허용 여부를 함께 확인해 보겠습니다:


🏁 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


다음 스크립트로 RecruitmentParticipant 엔티티 파일 위치를 먼저 찾고, orderId 선언부와 nullable 설정, getter 구현을 확인해 보겠습니다:


🏁 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 엔티티에 orderId 필드 및 getter 미정의로 인한 컴파일 오류

현재 p.getOrderId() 호출 시 대응되는 필드나 메서드가 존재하지 않습니다. 아래 위치를 확인하고 수정이 필요합니다:

  • 파일:
    recruitment-service/src/main/java/com/example/recruitment/entity/RecruitmentParticipant.java

수정 예시:

@Entity
@NoArgsConstructor
@AllArgsConstructor
public class RecruitmentParticipant {
    // ... 기존 필드 ...

    @Column(name = "order_id", nullable = true)
    private Long orderId;

    public Long getOrderId() {
        return this.orderId;
    }

    // ... 나머지 메서드 ...
}

필드와 getter를 추가하거나, Lombok의 @Getter 어노테이션을 클래스 레벨에 적용해 주세요.

🤖 Prompt for AI Agents
In
recruitment-service/src/main/java/com/example/recruitment/entity/RecruitmentParticipant.java
around the declaration of the RecruitmentParticipant class, add a private Long
field named orderId annotated with @Column(name = "order_id", nullable = true)
if it does not exist. Also, add a public getter method getOrderId() that returns
this.orderId. Alternatively, you can add Lombok's @Getter annotation at the
class level to generate the getter automatically. This will fix the compilation
error caused by calling p.getOrderId() in RecruitmentController.java lines
72-89.


// 유저가 만든 모집글
@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);

Expand All @@ -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, "모집글이 수정되었습니다."));
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.example.recruitment.dto;

import com.example.recruitment.entity.Recruitment;
import com.example.recruitment.entity.Store;
import com.example.recruitment.entity.User;
import lombok.Getter;
Expand All @@ -8,14 +9,60 @@
import java.time.LocalDateTime;
import java.util.List;

@Getter @Setter
@Getter
@Setter
public class RecruitmentDetailDto {

private Long id;
private String title;
private String description;
private String status;
private LocalDateTime deadlineTime;
private User user;
private Store store;
private List<User> participants;

private UserDto user;
private StoreDto store;
private List<UserDto> participants;
private List<Long> orderIds;

public RecruitmentDetailDto(Recruitment recruitment, List<UserDto> participantUsers, List<Long> orderIds) {
this.id = recruitment.getId();
this.title = recruitment.getTitle();
this.description = recruitment.getDescription();
this.status = recruitment.getStatus();
this.deadlineTime = recruitment.getDeadlineTime();
this.user = new UserDto(recruitment.getUser());
this.store = new StoreDto(recruitment.getStore());
this.participants = participantUsers; // ✅ 이미 변환된 상태로 전달됨
this.orderIds = orderIds;
}

@Getter
@Setter
public static class UserDto {
private Long id;
private String name;
private String email;

public UserDto(User user) {
this.id = user.getId();
this.name = user.getName();
this.email = user.getEmail();
}
}

@Getter
@Setter
public static class StoreDto {
private Long id;
private String name;
private String category;
private String location;

public StoreDto(Store store) {
this.id = store.getId();
this.name = store.getName();
this.category = store.getCategory();
this.location = store.getLocation();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,11 @@ public class RecruitmentRequestDto {
private String category;

// 주문용 메뉴 정보
@NotNull(message = "메뉴 정보는 필수입니다.")
@Size(min = 1, message = "최소 하나 이상의 메뉴를 선택해야 합니다.")
private List<OrderRequestDto.MenuDto> menus;

//OrderRequestDto 변환 메서드
// OrderRequestDto 변환 메서드
public OrderRequestDto toOrderRequestDto() {
OrderRequestDto dto = new OrderRequestDto();
dto.setUserId(this.userId);
Expand Down
Loading