Skip to content

Commit 21ace49

Browse files
authored
Merge branch 'develop' into feature/#39-course
2 parents e8b6b4d + 10e92de commit 21ace49

File tree

21 files changed

+445
-306
lines changed

21 files changed

+445
-306
lines changed

src/main/java/org/withtime/be/withtimebe/domain/date/controller/command/DateCommandController.java

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,24 +3,24 @@
33
import io.swagger.v3.oas.annotations.Operation;
44
import io.swagger.v3.oas.annotations.responses.ApiResponse;
55
import io.swagger.v3.oas.annotations.responses.ApiResponses;
6+
import io.swagger.v3.oas.annotations.tags.Tag;
67
import lombok.RequiredArgsConstructor;
78
import org.namul.api.payload.response.DefaultResponse;
89
import org.springframework.web.bind.annotation.*;
910
import org.withtime.be.withtimebe.domain.date.converter.DateConverter;
1011
import org.withtime.be.withtimebe.domain.date.dto.request.DateRequestDTO;
1112
import org.withtime.be.withtimebe.domain.date.dto.response.DateResponseDTO;
1213
import org.withtime.be.withtimebe.domain.date.entity.DateCourseBookmark;
13-
import org.withtime.be.withtimebe.domain.date.entity.DatePlace;
1414
import org.withtime.be.withtimebe.domain.date.service.command.DateCommandService;
15+
import org.withtime.be.withtimebe.domain.date.service.command.dto.RecommendedCourseResult;
1516
import org.withtime.be.withtimebe.domain.member.entity.Member;
1617
import org.withtime.be.withtimebe.global.security.annotation.AuthenticatedMember;
1718

18-
import java.util.List;
19-
2019

2120
@RestController
2221
@RequiredArgsConstructor
2322
@RequestMapping("/api/v1/date-courses")
23+
@Tag(name = "데이트 생성 API")
2424
public class DateCommandController {
2525

2626
private final DateCommandService dateCommandService;
@@ -32,10 +32,9 @@ public class DateCommandController {
3232
@PostMapping("/")
3333
public DefaultResponse<DateResponseDTO.DateCourse> createDateCourse(
3434
@RequestBody DateRequestDTO.CreateDateCourse request
35-
// @AuthenticatedMember Member member
3635
){
37-
List<DatePlace> datePlaces = dateCommandService.createDateCourse(request);
38-
DateResponseDTO.DateCourse dateCourse = DateConverter.createDateCourseInfo(datePlaces);
36+
RecommendedCourseResult datePlaces = dateCommandService.createDateCourse(request);
37+
DateResponseDTO.DateCourse dateCourse = DateConverter.createDateCourseInfo(datePlaces.places(), datePlaces.signature());
3938
return DefaultResponse.created(dateCourse);
4039
}
4140

src/main/java/org/withtime/be/withtimebe/domain/date/controller/query/DateQueryController.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import io.swagger.v3.oas.annotations.Operation;
44
import io.swagger.v3.oas.annotations.responses.ApiResponse;
55
import io.swagger.v3.oas.annotations.responses.ApiResponses;
6+
import io.swagger.v3.oas.annotations.tags.Tag;
67
import lombok.RequiredArgsConstructor;
78
import org.namul.api.payload.response.DefaultResponse;
89
import org.springdoc.core.annotations.ParameterObject;
@@ -22,6 +23,7 @@
2223
@RestController
2324
@RequiredArgsConstructor
2425
@RequestMapping("/api/v1/date-courses")
26+
@Tag(name = "데이트 조회 API")
2527
public class DateQueryController {
2628

2729
private final DateQueryService dateQueryService;
@@ -49,7 +51,7 @@ public DefaultResponse<DateResponseDTO.DateCourseList> findDateCourses(
4951
description = "해당 코스를 찾을 수 없습니다")
5052
})
5153
@SwaggerPageable
52-
@PostMapping("/bookmarks")
54+
@PostMapping("/bookmarks/search")
5355
public DefaultResponse<DateResponseDTO.DateCourseList> findDateCourseBookmark(
5456
@PageableDefault(page = 0, size = 10) Pageable pageable,
5557
@ModelAttribute DateRequestDTO.DateCourseSearchCond dateCourseSearchCond,

src/main/java/org/withtime/be/withtimebe/domain/date/converter/DateConverter.java

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,13 +39,16 @@ public static DateCourse createDateCourse(DateRequestDTO.SaveDateCourse dateCour
3939
}
4040

4141
// List<DatePlace> -> DateResponseDTO.DateCourseInfo
42-
public static DateResponseDTO.DateCourse createDateCourseInfo(List<DatePlace> datePlaces){
42+
// 단일 추천 코스를 응답으로 구성(시그니처 포함)
43+
public static DateResponseDTO.DateCourse createDateCourseInfo(List<DatePlace> datePlaces, String signature){
4344
List<DateResponseDTO.DatePlace> datePlaceDtos = datePlaces.stream()
4445
.map(dp -> DateConverter.createDatePlace(dp, null, null))
4546
.toList();
4647

4748
return DateResponseDTO.DateCourse.builder()
4849
.name(LocalDateTime.now().toLocalDate().toString())
50+
.datePlaces(datePlaceDtos)
51+
.signature(signature) // ← 추가
4952
.build();
5053
}
5154

@@ -54,6 +57,7 @@ public static DateResponseDTO.DatePlace createDatePlace(DatePlace datePlace,
5457
LocalDateTime startTime,
5558
LocalDateTime endTime) {
5659
return DateResponseDTO.DatePlace.builder()
60+
.datePlaceId(datePlace.getId())
5761
.name(datePlace.getName())
5862
.image(datePlace.getImage())
5963
.tel(datePlace.getTel())
@@ -80,7 +84,6 @@ public static DateResponseDTO.DateCourse createDateCourse(DateCourse dateCourse,
8084
.toList();
8185

8286
return DateResponseDTO.DateCourse.builder()
83-
.dateCourseId(dateCourse.getId())
8487
.name(dateCourse.getName())
8588
.datePlaces(datePlaces)
8689
.isBookmarked(bookmarked)

src/main/java/org/withtime/be/withtimebe/domain/date/dto/request/DateRequestDTO.java

Lines changed: 35 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
package org.withtime.be.withtimebe.domain.date.dto.request;
22

33
import com.fasterxml.jackson.annotation.JsonFormat;
4+
import com.fasterxml.jackson.annotation.JsonProperty;
5+
import io.swagger.v3.oas.annotations.media.Schema;
46
import jakarta.validation.constraints.NotBlank;
57
import jakarta.validation.constraints.NotNull;
8+
import jakarta.validation.constraints.Pattern;
69
import jakarta.validation.constraints.Size;
710
import org.withtime.be.withtimebe.domain.date.entity.enums.DatePriceRange;
811
import org.withtime.be.withtimebe.domain.date.entity.enums.DateTime;
@@ -11,37 +14,50 @@
1114

1215
import java.time.LocalDateTime;
1316
import java.util.List;
17+
import java.util.Set;
1418

1519
public record DateRequestDTO() {
1620

1721

1822
public record CreateDateCourse(
19-
@NotNull(message = "예산을 선택해주세요")
20-
DatePriceRange budget,
23+
@NotNull(message = "예산을 선택해주세요")
24+
@Schema(example = "UNDER_10K")
25+
DatePriceRange budget,
2126

22-
@Size(min = 1, message = "최소 하나 이상의 값을 선택해주세요")
23-
@NotNull(message = "값이 비어있을 수 없습니다")
24-
List<String> datePlaces,
27+
@Size(min = 1, message = "최소 하나 이상의 값을 선택해주세요")
28+
@NotNull(message = "값이 비어있을 수 없습니다")
29+
@Schema(example = "[\"서울 종로구\"]")
30+
List<String> datePlaces,
2531

26-
@NotNull(message = "데이트 시간을 선택해주세요")
27-
DateTime dateDurationTime,
32+
@NotNull(message = "데이트 시간을 선택해주세요")
33+
@Schema(example = "ONETOTWO")
34+
DateTime dateDurationTime,
2835

29-
List<MealType> mealPlan,
36+
@Schema(example = "[\"BREAKFAST\"]")
37+
List<MealType> mealPlan,
3038

31-
@NotBlank(message = "이동 수단을 선택해주세요")
32-
Transportation transportation,
39+
@NotNull(message = "이동 수단을 선택해주세요")
40+
@Schema(example = "WALK")
41+
Transportation transportation,
3342

34-
@Size(min = 1, max = 3)
35-
@NotNull(message = "사용자 취향을 선택해주세요")
36-
List<String> userPreferredKeywords,
43+
@Size(min = 1, max = 3)
44+
@NotNull(message = "사용자 취향을 선택해주세요")
45+
@Schema(example = "[\"레트로 골목\", \"카페\"]")
46+
List<String> userPreferredKeywords,
3747

38-
@JsonFormat(shape = JsonFormat.Shape.STRING,
39-
pattern = "yyyy-MM-dd'T'HH:mm:ss"
40-
)
41-
LocalDateTime startTime,
48+
@NotBlank(message = "startTime은 필수입니다")
49+
@Pattern(
50+
regexp = "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}$",
51+
message = "startTime 형식은 yyyy-MM-dd'T'HH:mm 이어야 합니다"
52+
)
53+
@JsonProperty("startTime")
54+
@Schema(example = "2025-08-14T07:45")
55+
LocalDateTime startTime,
4256

43-
int attemptCount
44-
){}
57+
// 이미 보여줬던 코스의 시그니처(예: "12-45-33")
58+
@Schema(example = "[]")
59+
Set<String> excludedCourseSignatures
60+
) {}
4561

4662
public record SaveDateCourse(
4763
List<Long> datePlaceIds,

src/main/java/org/withtime/be/withtimebe/domain/date/dto/response/DateResponseDTO.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ public record DateCourseBookmark(
1515

1616
@Builder
1717
public record DatePlace(
18+
Long datePlaceId,
1819
String name,
1920
String image,
2021
String tel,
@@ -31,11 +32,11 @@ public record DatePlace(
3132

3233
@Builder
3334
public record DateCourse(
34-
Long dateCourseId,
3535
String name,
3636
List<DateResponseDTO.DatePlace> datePlaces,
3737
DateCourseSearchCondInfo dateCourseSearchCondInfo,
3838
Boolean isBookmarked
39+
String signature
3940
){}
4041

4142
@Builder
Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,32 @@
1+
// ScheduledDateCourse
12
package org.withtime.be.withtimebe.domain.date.entity.model;
23

34
import lombok.Builder;
45
import lombok.Getter;
56

7+
import java.util.Comparator;
68
import java.util.List;
9+
import java.util.Objects;
710

811
@Builder
912
@Getter
1013
public class ScheduledDateCourse implements Comparable<ScheduledDateCourse> {
11-
private List<ScheduledDatePlace> scheduledDatePlaces;
12-
private double weight;
14+
15+
private final List<ScheduledDatePlace> scheduledDatePlaces;
16+
private final double weight; // 코스 총점
1317

1418
@Override
1519
public int compareTo(ScheduledDateCourse o) {
16-
return (int)(this.weight - o.weight);
20+
return Double.compare(this.weight, o.weight);
21+
}
22+
23+
public static final Comparator<ScheduledDateCourse> BY_WEIGHT_ASC =
24+
Comparator.comparingDouble(ScheduledDateCourse::getWeight);
25+
public static final Comparator<ScheduledDateCourse> BY_WEIGHT_DESC =
26+
BY_WEIGHT_ASC.reversed();
27+
28+
public ScheduledDateCourse(List<ScheduledDatePlace> scheduledDatePlaces, double weight) {
29+
this.scheduledDatePlaces = Objects.requireNonNull(scheduledDatePlaces, "scheduledDatePlaces must not be null");
30+
this.weight = weight;
1731
}
1832
}
Lines changed: 44 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,63 @@
1+
// ScheduledDatePlace
12
package org.withtime.be.withtimebe.domain.date.entity.model;
23

34
import lombok.Builder;
45
import lombok.Getter;
6+
import lombok.ToString;
57
import org.withtime.be.withtimebe.domain.date.entity.DatePlace;
8+
import org.withtime.be.withtimebe.domain.date.entity.enums.PlaceType;
69

710
import java.time.Duration;
8-
import java.time.LocalTime;
11+
import java.time.LocalDateTime;
12+
import java.util.Objects;
913

1014
@Getter
15+
@ToString
1116
public class ScheduledDatePlace {
12-
private DatePlace datePlace;
13-
private LocalTime startTime;
14-
private LocalTime endTime;
15-
private double score;
1617

17-
// 그리고 여기서 그냥 comparator 쓰면 되지 않나?
18+
private final DatePlace datePlace;
1819

19-
@Builder
20-
public ScheduledDatePlace(DatePlace datePlace, LocalTime startTime, Duration duration, double score) {
21-
this.datePlace = datePlace;
20+
// 시간은 점수 산정 단계에선 없어도 됨(선택)
21+
private final LocalDateTime startTime; // nullable
22+
private final LocalDateTime endTime; // nullable
23+
24+
private final double score;
25+
26+
@Builder(toBuilder = true)
27+
private ScheduledDatePlace(DatePlace datePlace,
28+
LocalDateTime startTime,
29+
LocalDateTime endTime,
30+
double score) {
31+
this.datePlace = Objects.requireNonNull(datePlace, "datePlace must not be null");
2232
this.startTime = startTime;
23-
this.endTime = startTime.plus(duration);
33+
this.endTime = endTime;
2434
this.score = score;
2535
}
2636

37+
// 시간 없이 점수만 설정
38+
public static ScheduledDatePlace ofScoreOnly(DatePlace place, double score) {
39+
return ScheduledDatePlace.builder()
40+
.datePlace(place)
41+
.score(score)
42+
.build();
43+
}
44+
45+
// 시작시각 주면 placeType duration으로 종료시각 계산
46+
public ScheduledDatePlace withScheduleFrom(LocalDateTime start) {
47+
PlaceType type = datePlace.getPlaceType();
48+
Duration dur = (type != null) ? type.getDuration() : Duration.ZERO;
49+
LocalDateTime end = (start != null) ? start.plus(dur) : null;
50+
return this.toBuilder()
51+
.startTime(start)
52+
.endTime(end)
53+
.build();
54+
}
55+
2756
public Duration getDuration() {
28-
return Duration.between(startTime, endTime);
57+
if (startTime != null && endTime != null) {
58+
return Duration.between(startTime, endTime);
59+
}
60+
PlaceType type = datePlace.getPlaceType();
61+
return (type != null) ? type.getDuration() : Duration.ZERO;
2962
}
3063
}

src/main/java/org/withtime/be/withtimebe/domain/date/preference/service/command/DatePreferenceTestCommandServiceImpl.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414
import org.withtime.be.withtimebe.domain.date.preference.repository.DatePreferenceQuestionRepository;
1515
import org.withtime.be.withtimebe.domain.date.preference.repository.DatePreferenceTestResultRepository;
1616
import org.withtime.be.withtimebe.domain.date.preference.util.DatePreferenceTestScoreCalculator;
17+
import org.withtime.be.withtimebe.domain.member.annotation.GetPoint;
18+
import org.withtime.be.withtimebe.domain.member.annotation.enums.PointAction;
1719
import org.withtime.be.withtimebe.domain.member.entity.Member;
1820
import org.withtime.be.withtimebe.global.error.code.DatePreferenceErrorCode;
1921
import org.withtime.be.withtimebe.global.error.exception.DatePreferenceException;
@@ -33,6 +35,7 @@ public class DatePreferenceTestCommandServiceImpl implements DatePreferenceTestC
3335
private final DatePreferenceTestScoreCalculator datePreferenceTestScoreCalculator;
3436

3537
@Override
38+
@GetPoint(action = PointAction.COMPLETE_TEST)
3639
public DatePreferenceResponseDTO.TestResult test(Member member, DatePreferenceRequestDTO.Test request) {
3740
// valid 판단
3841
if (!validateRequest(request)) {

src/main/java/org/withtime/be/withtimebe/domain/date/service/command/DateCommandService.java

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,13 @@
33
import org.withtime.be.withtimebe.domain.date.dto.request.DateRequestDTO;
44
import org.withtime.be.withtimebe.domain.date.entity.DateCourse;
55
import org.withtime.be.withtimebe.domain.date.entity.DateCourseBookmark;
6-
import org.withtime.be.withtimebe.domain.date.entity.DatePlace;
6+
import org.withtime.be.withtimebe.domain.date.service.command.dto.RecommendedCourseResult;
77
import org.withtime.be.withtimebe.domain.member.entity.Member;
88

9-
import java.util.List;
10-
119

1210
public interface DateCommandService {
13-
public DateCourseBookmark createDateCourseBookmark(Long dateCourseId, Member member);
14-
public DateCourse deleteDateCourseBookmark(Long dateCourseId, Member member);
15-
public DateCourseBookmark createDateCourseBookmarkWithGeneratedCourse(DateRequestDTO.SaveDateCourse request, Member members);
16-
public List<DatePlace> createDateCourse(DateRequestDTO.CreateDateCourse request);
11+
DateCourseBookmark createDateCourseBookmark(Long dateCourseId, Member member);
12+
DateCourse deleteDateCourseBookmark(Long dateCourseId, Member member);
13+
DateCourseBookmark createDateCourseBookmarkWithGeneratedCourse(DateRequestDTO.SaveDateCourse request, Member members);
14+
RecommendedCourseResult createDateCourse(DateRequestDTO.CreateDateCourse request);
1715
}

0 commit comments

Comments
 (0)