Skip to content

Commit 03d9df3

Browse files
authored
Merge pull request #32 from hackathon-soa/feat/#25
feat : 여행코스 상세 조회 API 1차 구현
2 parents 094ead8 + b9dd34f commit 03d9df3

11 files changed

Lines changed: 274 additions & 11 deletions

File tree

src/main/java/hackathon/soa/common/apiPayload/code/status/ErrorStatus.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,12 @@ public enum ErrorStatus implements BaseErrorCode {
3030
NOT_FOUND_COURSE(HttpStatus.NOT_FOUND, "H001", "코스를 찾을 수 없습니다."),
3131
NOT_FOUND_SEGMENT(HttpStatus.NOT_FOUND, "H002", "세그먼트를 찾을 수 없습니다."),
3232

33+
34+
35+
// 세그먼트 관련
36+
SEGMENT_NOT_FOUND(HttpStatus.NOT_FOUND, "SEGMENT4001", "세그먼트를 찾을 수 없습니다."),
37+
SEGMENT_DESERIALIZATION_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "SEGMENT4002", "세그먼트 데이터 처리 중 오류가 발생했습니다."),
38+
3339
;
3440

3541
private final HttpStatus httpStatus;
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package hackathon.soa.common.apiPayload.exception;
2+
3+
import hackathon.soa.common.apiPayload.code.BaseErrorCode;
4+
5+
6+
public class CourseHandler extends GeneralException {
7+
public CourseHandler(BaseErrorCode errorCode) {
8+
super(errorCode);
9+
}
10+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package hackathon.soa.common.apiPayload.exception;
2+
3+
import hackathon.soa.common.apiPayload.code.BaseErrorCode;
4+
5+
6+
public class SegmentHandler extends GeneralException {
7+
public SegmentHandler(BaseErrorCode errorCode) {
8+
super(errorCode);
9+
}
10+
}

src/main/java/hackathon/soa/domain/participation/ParticipationService.java

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
import hackathon.soa.common.apiPayload.exception.AuthHandler;
55
import hackathon.soa.common.apiPayload.exception.GeneralException;
66
import hackathon.soa.domain.member.MemberRepository;
7-
import hackathon.soa.domain.segment.CourseSegmentRepository;
7+
import hackathon.soa.domain.segment.repository.CourseSegmentRepository;
88
import hackathon.soa.entity.CourseSegment;
99
import hackathon.soa.entity.Member;
1010
import hackathon.soa.entity.SegmentParticipation;
@@ -13,8 +13,6 @@
1313
import lombok.RequiredArgsConstructor;
1414
import org.springframework.stereotype.Service;
1515

16-
import javax.swing.text.Segment;
17-
1816
@Service
1917
@Transactional
2018
@RequiredArgsConstructor
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package hackathon.soa.domain.segment;
2+
3+
import hackathon.soa.common.JwtUser;
4+
import hackathon.soa.common.apiPayload.ApiResponse;
5+
import hackathon.soa.domain.auth.AuthService;
6+
import hackathon.soa.domain.auth.dto.AuthRequestDTO;
7+
import hackathon.soa.domain.auth.dto.AuthResponseDTO;
8+
import hackathon.soa.domain.segment.dto.SegmentResponseDTO;
9+
import io.swagger.v3.oas.annotations.Operation;
10+
import io.swagger.v3.oas.annotations.Parameter;
11+
import jakarta.validation.Valid;
12+
import lombok.RequiredArgsConstructor;
13+
import org.springframework.http.MediaType;
14+
import org.springframework.web.bind.annotation.*;
15+
import org.springframework.web.multipart.MultipartFile;
16+
17+
@RestController
18+
@RequestMapping("/api/segment")
19+
@RequiredArgsConstructor
20+
public class SegmentController {
21+
22+
private final SegmentService segmentService;
23+
24+
@GetMapping("/{courseId}")
25+
@Operation(
26+
summary = "코스 상세 조회",
27+
description = "코스 ID로 특정 코스의 세그먼트들의 정보를 조회합니다."
28+
)
29+
public ApiResponse<SegmentResponseDTO.CourseDetailResponseDTO> getCourseDetail(
30+
@PathVariable Long courseId,
31+
@Parameter(hidden = true) @JwtUser Long memberId
32+
) {
33+
SegmentResponseDTO.CourseDetailResponseDTO result = segmentService.getCourseDetail(courseId, memberId);
34+
return ApiResponse.onSuccess(result);
35+
}
36+
}
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
package hackathon.soa.domain.segment;
2+
3+
import hackathon.soa.common.apiPayload.code.status.ErrorStatus;
4+
import hackathon.soa.common.apiPayload.exception.CourseHandler;
5+
import hackathon.soa.common.apiPayload.exception.SegmentHandler;
6+
import hackathon.soa.domain.course.CourseRepository;
7+
import hackathon.soa.domain.segment.dto.SegmentResponseDTO;
8+
import hackathon.soa.domain.segment.repository.CourseSegmentRepository;
9+
import hackathon.soa.domain.segment.repository.MoveSegmentRepository;
10+
import hackathon.soa.domain.segment.repository.StaySegmentRepository;
11+
import hackathon.soa.entity.Course;
12+
import hackathon.soa.entity.CourseSegment;
13+
import hackathon.soa.entity.MoveSegment;
14+
import hackathon.soa.entity.StaySegment;
15+
import lombok.RequiredArgsConstructor;
16+
import lombok.extern.slf4j.Slf4j;
17+
import org.springframework.stereotype.Service;
18+
import org.springframework.transaction.annotation.Transactional;
19+
20+
import java.time.format.DateTimeFormatter;
21+
import java.util.List;
22+
import java.util.Locale;
23+
import java.util.Optional;
24+
import java.util.stream.Collectors;
25+
26+
@Service
27+
@RequiredArgsConstructor
28+
@Transactional(readOnly = true)
29+
@Slf4j
30+
public class SegmentService {
31+
32+
private final CourseSegmentRepository courseSegmentRepository;
33+
private final StaySegmentRepository staySegmentRepository;
34+
private final MoveSegmentRepository moveSegmentRepository;
35+
private final CourseRepository courseRepository;
36+
37+
public SegmentResponseDTO.CourseDetailResponseDTO getCourseDetail(Long courseId, Long memberId) {
38+
// 1. 코스 조회 및 작성자 확인
39+
Course course = courseRepository.findById(courseId)
40+
.orElseThrow(() -> new CourseHandler(ErrorStatus.NOT_FOUND_COURSE));
41+
42+
boolean isOwner = course.getMember().getId().equals(memberId);
43+
44+
// 2. 코스 세그먼트들을 순서대로 조회
45+
List<CourseSegment> courseSegments = courseSegmentRepository.findByCourse_IdOrderBySegmentOrder(courseId);
46+
47+
if (courseSegments.isEmpty()) {
48+
throw new CourseHandler(ErrorStatus.NOT_FOUND_COURSE);
49+
}
50+
51+
// 3. 각 세그먼트 정보를 조회하여 DTO 생성
52+
List<SegmentResponseDTO.SegmentDetailDTO> segmentDetails = courseSegments.stream()
53+
.map(this::buildSegmentDetail)
54+
.collect(Collectors.toList());
55+
56+
return SegmentResponseDTO.CourseDetailResponseDTO.builder()
57+
.courseId(courseId)
58+
.isOwner(isOwner)
59+
.segments(segmentDetails)
60+
.build();
61+
}
62+
63+
private SegmentResponseDTO.SegmentDetailDTO buildSegmentDetail(CourseSegment courseSegment) {
64+
Long segmentId = courseSegment.getId();
65+
66+
// 시간 포맷터 정의
67+
DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern("MM/dd(E)", Locale.KOREAN);
68+
DateTimeFormatter timeFormatter = DateTimeFormatter.ofPattern("HH:mm");
69+
70+
try {
71+
// 장소 세그먼트 먼저 확인 (@MapsId로 PK가 동일함)
72+
Optional<StaySegment> staySegment = staySegmentRepository.findById(segmentId);
73+
if (staySegment.isPresent()) {
74+
return SegmentResponseDTO.SegmentDetailDTO.builder()
75+
.segmentOrder(courseSegment.getSegmentOrder())
76+
.segmentType("장소")
77+
.date(courseSegment.getStartTime().format(dateFormatter))
78+
.startTime(courseSegment.getStartTime().format(timeFormatter))
79+
.endTime(courseSegment.getEndTime().format(timeFormatter))
80+
.staySegment(SegmentResponseDTO.StaySegmentDTO.from(staySegment.get()))
81+
.moveSegment(null)
82+
.build();
83+
}
84+
85+
// 이동 세그먼트 확인 (@MapsId로 PK가 동일함)
86+
Optional<MoveSegment> moveSegment = moveSegmentRepository.findById(segmentId);
87+
if (moveSegment.isPresent()) {
88+
return SegmentResponseDTO.SegmentDetailDTO.builder()
89+
.segmentOrder(courseSegment.getSegmentOrder())
90+
.segmentType("이동")
91+
.date(courseSegment.getStartTime().format(dateFormatter))
92+
.startTime(courseSegment.getStartTime().format(timeFormatter))
93+
.endTime(courseSegment.getEndTime().format(timeFormatter))
94+
.staySegment(null)
95+
.moveSegment(SegmentResponseDTO.MoveSegmentDTO.from(moveSegment.get()))
96+
.build();
97+
}
98+
99+
throw new SegmentHandler(ErrorStatus.SEGMENT_NOT_FOUND);
100+
101+
} catch (Exception e) {
102+
log.error("Error while building segment detail for segmentId: {}", segmentId, e);
103+
throw new SegmentHandler(ErrorStatus.SEGMENT_DESERIALIZATION_ERROR);
104+
}
105+
}
106+
}
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
package hackathon.soa.domain.segment.dto;
2+
3+
import hackathon.soa.entity.MoveSegment;
4+
import hackathon.soa.entity.MovementType;
5+
import hackathon.soa.entity.StaySegment;
6+
import lombok.AllArgsConstructor;
7+
import lombok.Builder;
8+
import lombok.Getter;
9+
import lombok.NoArgsConstructor;
10+
11+
import java.math.BigDecimal;
12+
import java.util.List;
13+
14+
public class SegmentResponseDTO {
15+
16+
@Builder
17+
@Getter
18+
@NoArgsConstructor
19+
@AllArgsConstructor
20+
public static class CourseDetailResponseDTO {
21+
private Long courseId;
22+
private Boolean isOwner; // 해당 멤버가 코스 작성자인지 여부
23+
private List<SegmentDetailDTO> segments;
24+
}
25+
26+
@Builder
27+
@Getter
28+
@NoArgsConstructor
29+
@AllArgsConstructor
30+
public static class SegmentDetailDTO {
31+
private Integer segmentOrder;
32+
private String segmentType; // "장소" or "이동"
33+
private String date; // "07/05(토)"
34+
private String startTime; // "09:00"
35+
private String endTime; // "09:30"
36+
private StaySegmentDTO staySegment;
37+
private MoveSegmentDTO moveSegment;
38+
}
39+
40+
@Builder
41+
@Getter
42+
@NoArgsConstructor
43+
@AllArgsConstructor
44+
public static class StaySegmentDTO {
45+
private Long segmentId;
46+
private String locationName;
47+
private String locationAddress;
48+
49+
public static StaySegmentDTO from(StaySegment staySegment) {
50+
return StaySegmentDTO.builder()
51+
.segmentId(staySegment.getSegmentId())
52+
.locationName(staySegment.getLocationName())
53+
.locationAddress(staySegment.getLocationAddress())
54+
.build();
55+
}
56+
}
57+
58+
@Builder
59+
@Getter
60+
@NoArgsConstructor
61+
@AllArgsConstructor
62+
public static class MoveSegmentDTO {
63+
private Long segmentId;
64+
private MovementType movementType;
65+
private BigDecimal movementDistanceKm;
66+
67+
public static MoveSegmentDTO from(MoveSegment moveSegment) {
68+
return MoveSegmentDTO.builder()
69+
.segmentId(moveSegment.getSegmentId())
70+
.movementType(moveSegment.getMovementType())
71+
.movementDistanceKm(BigDecimal.valueOf(moveSegment.getMovementDistanceKm()))
72+
.build();
73+
}
74+
}
75+
}

src/main/java/hackathon/soa/domain/segment/CourseSegmentRepository.java renamed to src/main/java/hackathon/soa/domain/segment/repository/CourseSegmentRepository.java

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
1-
package hackathon.soa.domain.segment;
2-
3-
import hackathon.soa.entity.CourseSegment;
4-
import org.springframework.data.jpa.repository.JpaRepository;
5-
6-
public interface CourseSegmentRepository extends JpaRepository<CourseSegment, Long> {
7-
}
1+
package hackathon.soa.domain.segment.repository;
2+
3+
import hackathon.soa.entity.CourseSegment;
4+
import org.springframework.data.jpa.repository.JpaRepository;
5+
6+
import java.util.List;
7+
8+
public interface CourseSegmentRepository extends JpaRepository<CourseSegment, Long> {
9+
List<CourseSegment> findByCourse_IdOrderBySegmentOrder(Long courseId);
10+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package hackathon.soa.domain.segment.repository;
2+
3+
import hackathon.soa.entity.MoveSegment;
4+
import hackathon.soa.entity.StaySegment;
5+
import org.springframework.data.jpa.repository.JpaRepository;
6+
7+
import java.util.Optional;
8+
9+
public interface MoveSegmentRepository extends JpaRepository<MoveSegment, Long> {
10+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package hackathon.soa.domain.segment.repository;
2+
3+
import hackathon.soa.entity.StaySegment;
4+
import org.springframework.data.jpa.repository.JpaRepository;
5+
6+
import java.util.Optional;
7+
8+
public interface StaySegmentRepository extends JpaRepository<StaySegment, Long> {
9+
}

0 commit comments

Comments
 (0)