Skip to content

Commit 98c1095

Browse files
authored
Merge branch 'dev' into feat/96
2 parents 4ad4219 + 34aac99 commit 98c1095

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

44 files changed

+2302
-155
lines changed

.github/workflows/backend-ci.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,12 @@ jobs:
1212
build-and-test:
1313
runs-on: ubuntu-latest
1414

15+
services:
16+
redis:
17+
image: redis:7
18+
ports:
19+
- 6379:6379
20+
1521
steps:
1622
# 저장소 체크아웃
1723
- name: Checkout repository

build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ dependencies {
2929
implementation("org.springframework.boot:spring-boot-starter-web")
3030
implementation("org.springframework.boot:spring-boot-starter-websocket")
3131
implementation("org.springframework.boot:spring-boot-starter-validation")
32+
implementation("org.springframework.boot:spring-boot-starter-mail")
3233

3334
// Database & JPA
3435
implementation("org.springframework.boot:spring-boot-starter-data-jpa")

src/main/java/com/back/domain/chat/room/controller/RoomChatWebSocketController.java

Lines changed: 6 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import com.back.global.exception.CustomException;
88
import com.back.global.security.user.CustomUserDetails;
99
import com.back.domain.chat.room.service.RoomChatService;
10+
import com.back.global.websocket.util.WebSocketAuthHelper;
1011
import com.back.global.websocket.util.WebSocketErrorHelper;
1112
import io.swagger.v3.oas.annotations.tags.Tag;
1213
import lombok.RequiredArgsConstructor;
@@ -29,6 +30,7 @@ public class RoomChatWebSocketController {
2930

3031
private final RoomChatService roomChatService;
3132
private final SimpMessagingTemplate messagingTemplate;
33+
private final WebSocketAuthHelper authHelper;
3234
private final WebSocketErrorHelper errorHelper;
3335

3436
/**
@@ -43,7 +45,8 @@ public void handleRoomChat(@DestinationVariable Long roomId,
4345

4446
try {
4547
// WebSocket에서 인증된 사용자 정보 추출
46-
CustomUserDetails userDetails = extractUserDetails(principal);
48+
CustomUserDetails userDetails = authHelper.extractUserDetails(principal);
49+
4750
if (userDetails == null) {
4851
errorHelper.sendUnauthorizedError(headerAccessor.getSessionId());
4952
return;
@@ -101,7 +104,8 @@ public void clearRoomChat(@DestinationVariable Long roomId,
101104
log.info("WebSocket 채팅 일괄 삭제 요청 - roomId: {}", roomId);
102105

103106
// 사용자 인증 확인
104-
CustomUserDetails userDetails = extractUserDetails(principal);
107+
CustomUserDetails userDetails = authHelper.extractUserDetails(principal);
108+
105109
if (userDetails == null) {
106110
errorHelper.sendUnauthorizedError(headerAccessor.getSessionId());
107111
return;
@@ -148,15 +152,4 @@ public void clearRoomChat(@DestinationVariable Long roomId,
148152
}
149153
}
150154

151-
// WebSocket Principal에서 CustomUserDetails 추출
152-
private CustomUserDetails extractUserDetails(Principal principal) {
153-
if (principal instanceof Authentication auth) {
154-
Object principalObj = auth.getPrincipal();
155-
if (principalObj instanceof CustomUserDetails userDetails) {
156-
return userDetails;
157-
}
158-
}
159-
return null;
160-
}
161-
162155
}

src/main/java/com/back/domain/study/plan/controller/StudyPlanController.java

Lines changed: 15 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -31,10 +31,11 @@ public class StudyPlanController {
3131
" 이후 반복되는 계획들은 가상 계획으로서 db에는 없지만 조회 시 가상으로 생성됩니다")
3232

3333
public ResponseEntity<RsData<StudyPlanResponse>> createStudyPlan(
34-
// 로그인 유저 정보 받기 @AuthenticationPrincipal CustomUserDetails user,
34+
// 로그인 유저 정보 받기
35+
@AuthenticationPrincipal CustomUserDetails user,
3536
@RequestBody StudyPlanRequest request) {
36-
//커스텀 디테일 구현 시 사용 int userId = user.getId();
37-
Long userId = 1L; // 임시로 userId를 1로 설정
37+
//커스텀 디테일 구현 시 사용
38+
Long userId = user.getUserId();
3839
StudyPlanResponse response = studyPlanService.createStudyPlan(userId, request);
3940
return ResponseEntity.ok(RsData.success("학습 계획이 성공적으로 생성되었습니다.", response));
4041
}
@@ -49,9 +50,8 @@ public ResponseEntity<RsData<StudyPlanResponse>> createStudyPlan(
4950
public ResponseEntity<RsData<StudyPlanListResponse>> getStudyPlansForDate(
5051
@AuthenticationPrincipal CustomUserDetails user,
5152
@PathVariable @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate date) {
52-
// 유저 아이디 추출. 지금은 임시값 적용
53-
// Long userId = user.getId();
54-
Long userId = 1L; // 임시로 userId를 1로 설정
53+
// 유저 아이디 추출.
54+
Long userId = user.getUserId();
5555

5656
List<StudyPlanResponse> plans = studyPlanService.getStudyPlansForDate(userId, date);
5757
StudyPlanListResponse response = new StudyPlanListResponse(date, plans, plans.size());
@@ -66,11 +66,11 @@ public ResponseEntity<RsData<StudyPlanListResponse>> getStudyPlansForDate(
6666
description = "기간에 해당하는 모든 학습 계획을 조회합니다."
6767
)
6868
public ResponseEntity<RsData<List<StudyPlanResponse>>> getStudyPlansForPeriod(
69-
// @AuthenticationPrincipal CustomUserDetails user,
69+
@AuthenticationPrincipal CustomUserDetails user,
7070
@RequestParam("start") @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate startDate,
7171
@RequestParam("end") @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate endDate) {
72-
// Long userId = user.getId();
73-
Long userId = 1L; // 임시
72+
73+
Long userId = user.getUserId();
7474

7575
List<StudyPlanResponse> plans = studyPlanService.getStudyPlansForPeriod(userId, startDate, endDate);
7676

@@ -90,12 +90,11 @@ public ResponseEntity<RsData<List<StudyPlanResponse>>> getStudyPlansForPeriod(
9090
"클라이언트에서는 paln에 repeat_rule이 있으면 반복 계획으로 간주하고 반드시 apply_scope를 쿼리 파라미터로 넘겨야 합니다." +
9191
"repeat_rule이 없으면 단발성 계획으로 간주하여 수정 범위를 설정 할 필요가 없으므로 apply_scope를 넘기지 않아도 됩니다.")
9292
public ResponseEntity<RsData<StudyPlanResponse>> updateStudyPlan(
93-
// @AuthenticationPrincipal CustomUserDetails user,
93+
@AuthenticationPrincipal CustomUserDetails user,
9494
@PathVariable Long planId,
9595
@RequestBody StudyPlanRequest request,
96-
@RequestParam(required = false, defaultValue = "THIS_ONLY") ApplyScope applyScope) {
97-
// Long userId = user.getId();
98-
Long userId = 1L; // 임시
96+
@RequestParam(name = "applyScope", required = true) ApplyScope applyScope) {
97+
Long userId = user.getUserId();
9998

10099
StudyPlanResponse response = studyPlanService.updateStudyPlan(userId, planId, request, applyScope);
101100
return ResponseEntity.ok(RsData.success("학습 계획이 성공적으로 수정되었습니다.", response));
@@ -113,11 +112,11 @@ public ResponseEntity<RsData<StudyPlanResponse>> updateStudyPlan(
113112
"클라이언트에서는 paln에 repeat_rule이 있으면 반복 계획으로 간주하고 반드시 apply_scope를 쿼리 파라미터로 넘겨야 합니다." +
114113
"repeat_rule이 없으면 단발성 계획으로 간주하여 삭제 범위를 설정 할 필요가 없으므로 apply_scope를 넘기지 않아도 됩니다.")
115114
public ResponseEntity<RsData<Void>> deleteStudyPlan(
116-
// @AuthenticationPrincipal CustomUserDetails user,
115+
@AuthenticationPrincipal CustomUserDetails user,
117116
@PathVariable Long planId,
118117
@RequestParam @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate selectedDate,
119-
@RequestParam(required = false) ApplyScope applyScope) {
120-
Long userId = 1L; // 임시
118+
@RequestParam(name = "applyScope", required = true) ApplyScope applyScope) {
119+
Long userId = user.getUserId();
121120

122121
studyPlanService.deleteStudyPlan(userId, planId, selectedDate, applyScope);
123122
return ResponseEntity.ok(RsData.success("학습 계획이 성공적으로 삭제되었습니다."));

src/main/java/com/back/domain/study/plan/dto/StudyPlanRequest.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
import lombok.Setter;
1010

1111
import java.time.LocalDateTime;
12+
import java.util.ArrayList;
13+
import java.util.List;
1214

1315
@Getter
1416
@Setter
@@ -35,7 +37,7 @@ public class StudyPlanRequest {
3537
public static class RepeatRuleRequest {
3638
private Frequency frequency;
3739
private Integer intervalValue;
38-
private String byDay; // "MON" 형태의 문자열
40+
private List<String> byDay = new ArrayList<>(); // 문자열 리스트
3941

4042
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd")
4143
private String untilDate; // "2025-12-31" 형태

src/main/java/com/back/domain/study/plan/dto/StudyPlanResponse.java

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212

1313
import java.time.LocalDate;
1414
import java.time.LocalDateTime;
15+
import java.util.ArrayList;
1516
import java.util.Arrays;
1617
import java.util.List;
1718
import java.util.stream.Collectors;
@@ -42,11 +43,14 @@ public class StudyPlanResponse {
4243
public static class RepeatRuleResponse {
4344
private Frequency frequency;
4445
private Integer repeatInterval;
45-
private String byDay; // "MON" 형태의 문자열
46+
// byDay 필드는 이미 List<String>으로 선언되어 있음.
47+
private List<String> byDay = new ArrayList<>(); // "MON" 형태의 문자열 리스트
4648

4749
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd")
4850
private LocalDate untilDate;
4951

52+
// 엔티티 생성자: 그대로 유지
53+
5054
public RepeatRuleResponse(com.back.domain.study.plan.entity.RepeatRule repeatRule) {
5155
if (repeatRule != null) {
5256
this.frequency = repeatRule.getFrequency();
@@ -56,13 +60,13 @@ public RepeatRuleResponse(com.back.domain.study.plan.entity.RepeatRule repeatRul
5660
}
5761
}
5862

59-
// 요일을 리스트로 접근 ("MON,TUE" -> [MON, TUE])
6063
public List<DayOfWeek> getByDaysList() {
6164
if (byDay == null || byDay.isEmpty()) {
6265
return List.of();
6366
}
64-
return Arrays.stream(byDay.split(","))
65-
.map(String::trim)
67+
68+
// List<String>의 각 요소를 DayOfWeek enum으로 변환하여 반환
69+
return byDay.stream()
6670
.map(com.back.domain.study.plan.entity.DayOfWeek::valueOf)
6771
.collect(Collectors.toList());
6872
}

src/main/java/com/back/domain/study/plan/entity/RepeatRule.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
import lombok.Setter;
99

1010
import java.time.LocalDate;
11+
import java.util.ArrayList;
12+
import java.util.List;
1113

1214
@Entity
1315
@Getter
@@ -25,9 +27,9 @@ public class RepeatRule extends BaseEntity {
2527
@Column(name = "interval_value", nullable = false)
2628
private int repeatInterval;
2729

28-
//요일은 계획 날짜에 따라 자동 설정
30+
//요일은 응답에 들어있는 요일을 그대로 저장 (예: "WED")
2931
@Column(name = "by_day")
30-
private String byDay;
32+
private List<String> byDay = new ArrayList<>();
3133

3234
private LocalDate untilDate;
3335
}

src/main/java/com/back/domain/study/plan/entity/RepeatRuleEmbeddable.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
import lombok.Setter;
1010

1111
import java.time.LocalDate;
12+
import java.util.ArrayList;
13+
import java.util.List;
1214

1315
@Embeddable
1416
@Getter
@@ -20,6 +22,6 @@ public class RepeatRuleEmbeddable {
2022
private Frequency frequency;
2123

2224
private Integer intervalValue;
23-
private String byDay;
25+
private List<String> byDay = new ArrayList<>();
2426
private LocalDate untilDate; // LocalDateTime → LocalDate 변경
2527
}

src/main/java/com/back/domain/study/plan/entity/StudyPlanException.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ public enum ExceptionType {
5858
}
5959

6060
@Embedded
61+
@Column(name = "modified_repeat_rule")
6162
@AttributeOverrides({
6263
@AttributeOverride(name = "frequency", column = @Column(name = "modified_frequency")),
6364
@AttributeOverride(name = "intervalValue", column = @Column(name = "modified_repeat_interval")),

src/main/java/com/back/domain/study/plan/repository/StudyPlanExceptionRepository.java

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import com.back.domain.study.plan.entity.ApplyScope;
44
import com.back.domain.study.plan.entity.StudyPlanException;
55
import org.springframework.data.jpa.repository.JpaRepository;
6+
import org.springframework.data.jpa.repository.Modifying;
67
import org.springframework.data.jpa.repository.Query;
78
import org.springframework.data.repository.query.Param;
89
import org.springframework.stereotype.Repository;
@@ -22,7 +23,7 @@ public interface StudyPlanExceptionRepository extends JpaRepository<StudyPlanExc
2223
List<StudyPlanException> findByStudyPlanIdAndApplyScopeAndExceptionDateBefore(
2324
@Param("planId") Long planId,
2425
@Param("applyScope") ApplyScope applyScope,
25-
@Param("targetDate") LocalDateTime targetDate);
26+
@Param("targetDate") LocalDate targetDate);
2627
// 특정 계획의 특정 기간 동안(start~end)의 예외를 조회
2728
@Query("SELECT spe FROM StudyPlanException spe WHERE spe.studyPlan.id = :planId " +
2829
"AND spe.exceptionDate BETWEEN :startDate AND :endDate " +
@@ -35,4 +36,10 @@ List<StudyPlanException> findByStudyPlanIdAndExceptionDateBetween(@Param("planId
3536
"AND DATE(spe.exceptionDate) = DATE(:targetDate)")
3637
Optional<StudyPlanException> findByPlanIdAndDate(@Param("planId") Long planId,
3738
@Param("targetDate") LocalDate targetDate);
39+
40+
// 특정 계획의 특정 날짜 이후 예외 모두 삭제
41+
@Modifying
42+
@Query("DELETE FROM StudyPlanException spe WHERE spe.studyPlan.id = :planId AND spe.exceptionDate > :date")
43+
void deleteByStudyPlanIdAndExceptionDateAfter(@Param("planId") Long planId, @Param("date") LocalDate date);
44+
3845
}

0 commit comments

Comments
 (0)