Skip to content

Commit 62ee836

Browse files
authored
Merge branch 'dev' into Test/155
2 parents 8bcf522 + 87c6e0f commit 62ee836

File tree

11 files changed

+723
-9
lines changed

11 files changed

+723
-9
lines changed

src/main/java/com/back/domain/study/plan/service/StudyPlanService.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -557,7 +557,7 @@ private void validateDateTime(LocalDateTime startDate, LocalDateTime endDate) {
557557
}
558558

559559
if (!startDate.isBefore(endDate)) {
560-
throw new CustomException(ErrorCode.PLAN_INVALID_TIME_RANGE);
560+
throw new CustomException(ErrorCode.INVALID_TIME_RANGE);
561561
}
562562
}
563563
//시간 겹침 검증 메서드 (최적화된 DB 쿼리 + 가상 인스턴스 검증 조합)
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
package com.back.domain.study.record.controller;
2+
3+
import com.back.domain.study.record.dto.StudyRecordRequestDto;
4+
import com.back.domain.study.record.dto.StudyRecordResponseDto;
5+
import com.back.domain.study.record.service.StudyRecordService;
6+
import com.back.global.common.dto.RsData;
7+
import com.back.global.security.user.CustomUserDetails;
8+
import jakarta.validation.Valid;
9+
import lombok.RequiredArgsConstructor;
10+
import org.springframework.format.annotation.DateTimeFormat;
11+
import org.springframework.http.ResponseEntity;
12+
import org.springframework.security.core.annotation.AuthenticationPrincipal;
13+
import org.springframework.web.bind.annotation.*;
14+
15+
import java.time.LocalDate;
16+
import java.util.List;
17+
18+
@RestController
19+
@RequiredArgsConstructor
20+
@RequestMapping("/api/plans/records")
21+
public class StudyRecordController {
22+
private final StudyRecordService studyRecordService;
23+
24+
// ======================= 생성 ======================
25+
// 학습 기록 생성
26+
@PostMapping
27+
public ResponseEntity<RsData<StudyRecordResponseDto>> createStudyRecord(
28+
@AuthenticationPrincipal CustomUserDetails user,
29+
@Valid @RequestBody StudyRecordRequestDto request
30+
) {
31+
Long userId = user.getUserId();
32+
StudyRecordResponseDto response = studyRecordService.createStudyRecord(userId, request);
33+
return ResponseEntity.ok(RsData.success("학습 기록이 생성되었습니다.", response));
34+
}
35+
// ======================= 조회 ======================
36+
// 일별 학습 기록 조회
37+
@GetMapping
38+
public ResponseEntity<RsData<List<StudyRecordResponseDto>>> getDailyStudyRecord(
39+
@AuthenticationPrincipal CustomUserDetails user,
40+
@RequestParam("date") @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate date
41+
) {
42+
Long userId = user.getUserId();
43+
List<StudyRecordResponseDto> response = studyRecordService.getStudyRecordsByDate(userId, date);
44+
return ResponseEntity.ok(RsData.success("일별 학습 기록 조회 성공", response));
45+
}
46+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package com.back.domain.study.record.dto;
2+
3+
import lombok.Getter;
4+
import lombok.NoArgsConstructor;
5+
6+
import java.time.LocalDateTime;
7+
import java.util.ArrayList;
8+
import java.util.List;
9+
10+
@Getter
11+
@NoArgsConstructor
12+
public class StudyRecordRequestDto {
13+
private Long planId;
14+
private Long roomId;
15+
private LocalDateTime startTime;
16+
private LocalDateTime endTime;
17+
private Long duration;
18+
private List<PauseInfoRequestDto> pauseInfos = new ArrayList<>();
19+
20+
@Getter
21+
@NoArgsConstructor
22+
public static class PauseInfoRequestDto {
23+
private LocalDateTime pausedAt;
24+
private LocalDateTime restartAt;
25+
}
26+
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
package com.back.domain.study.record.dto;
2+
3+
import com.back.domain.study.record.entity.PauseInfo;
4+
import com.back.domain.study.record.entity.StudyRecord;
5+
import lombok.Getter;
6+
import lombok.NoArgsConstructor;
7+
8+
import java.time.LocalDateTime;
9+
import java.util.ArrayList;
10+
import java.util.List;
11+
import java.util.stream.Collectors;
12+
13+
@Getter
14+
@NoArgsConstructor
15+
public class StudyRecordResponseDto {
16+
private Long id;
17+
private Long planId;
18+
private Long roomId;
19+
private LocalDateTime startTime;
20+
private List<PauseInfoResponseDto> pauseInfos = new ArrayList<>();
21+
private LocalDateTime endTime;
22+
private Long duration;
23+
24+
public static StudyRecordResponseDto from(StudyRecord studyRecord) {
25+
StudyRecordResponseDto dto = new StudyRecordResponseDto();
26+
dto.id = studyRecord.getId();
27+
dto.planId = studyRecord.getStudyPlan() != null ? studyRecord.getStudyPlan().getId() : null;
28+
dto.roomId = studyRecord.getRoom() != null ? studyRecord.getRoom().getId() : null;
29+
dto.startTime = studyRecord.getStartTime();
30+
dto.endTime = studyRecord.getEndTime();
31+
dto.duration = studyRecord.getDuration();
32+
dto.pauseInfos = studyRecord.getPauseInfos().stream()
33+
.map(PauseInfoResponseDto::from)
34+
.collect(Collectors.toList());
35+
return dto;
36+
}
37+
38+
@Getter
39+
@NoArgsConstructor
40+
public static class PauseInfoResponseDto {
41+
private LocalDateTime pausedAt;
42+
private LocalDateTime restartAt;
43+
44+
public static PauseInfoResponseDto from(PauseInfo pauseInfo) {
45+
PauseInfoResponseDto dto = new PauseInfoResponseDto();
46+
dto.pausedAt = pauseInfo.getPausedAt();
47+
dto.restartAt = pauseInfo.getRestartAt();
48+
return dto;
49+
}
50+
}
51+
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package com.back.domain.study.record.entity;
2+
3+
4+
import com.back.global.entity.BaseEntity;
5+
import jakarta.persistence.*;
6+
import lombok.Getter;
7+
8+
import java.time.LocalDateTime;
9+
10+
@Entity
11+
@Getter
12+
@Table(name = "pause_infos")
13+
public class PauseInfo {
14+
// 일시정지 정보에 생성, 수정일은 필요 없을 것 같아서
15+
// id만 별도로 생성
16+
@Id
17+
@GeneratedValue(strategy = GenerationType.IDENTITY)
18+
private Long id;
19+
20+
@ManyToOne(fetch = FetchType.LAZY)
21+
@JoinColumn(name = "study_record_id", nullable = false)
22+
private StudyRecord studyRecord;
23+
24+
@Column(name = "paused_at", nullable = false)
25+
private LocalDateTime pausedAt;
26+
27+
@Column(name = "restart_at")
28+
private LocalDateTime restartAt;
29+
30+
public static PauseInfo of(LocalDateTime pausedAt, LocalDateTime restartAt) {
31+
PauseInfo pauseInfo = new PauseInfo();
32+
pauseInfo.pausedAt = pausedAt;
33+
pauseInfo.restartAt = restartAt;
34+
return pauseInfo;
35+
}
36+
37+
// 양방향 연관관계 설정
38+
void assignStudyRecord(StudyRecord studyRecord) {
39+
this.studyRecord = studyRecord;
40+
}
41+
42+
}

src/main/java/com/back/domain/study/record/entity/StudyRecord.java

Lines changed: 59 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,31 +2,84 @@
22

33
import com.back.domain.study.plan.entity.StudyPlan;
44
import com.back.domain.studyroom.entity.Room;
5+
import com.back.domain.user.entity.User;
56
import com.back.global.entity.BaseEntity;
6-
import jakarta.persistence.Entity;
7-
import jakarta.persistence.FetchType;
8-
import jakarta.persistence.JoinColumn;
9-
import jakarta.persistence.ManyToOne;
7+
import com.back.global.exception.CustomException;
8+
import com.back.global.exception.ErrorCode;
9+
import jakarta.persistence.*;
1010
import lombok.Getter;
1111
import lombok.NoArgsConstructor;
1212

13+
import java.time.Duration;
1314
import java.time.LocalDateTime;
15+
import java.util.ArrayList;
16+
import java.util.List;
1417

1518
@Entity
1619
@NoArgsConstructor
1720
@Getter
1821
public class StudyRecord extends BaseEntity {
22+
23+
@ManyToOne(fetch = FetchType.LAZY)
24+
@JoinColumn(name = "user_id", nullable = false)
25+
private User user;
26+
1927
@ManyToOne(fetch = FetchType.LAZY)
20-
@JoinColumn(name = "plan_id")
28+
@JoinColumn(name = "plan_id", nullable = false)
2129
private StudyPlan studyPlan;
2230

2331
@ManyToOne(fetch = FetchType.LAZY)
2432
@JoinColumn(name = "room_id")
2533
private Room room;
2634

27-
private int duration;
35+
// 초 단위
36+
@Column(nullable = false)
37+
private Long duration;
2838

2939
private LocalDateTime startTime;
3040

41+
@OneToMany(mappedBy = "studyRecord", cascade = CascadeType.ALL, orphanRemoval = true)
42+
private List<PauseInfo> pauseInfos = new ArrayList<>();
43+
3144
private LocalDateTime endTime;
45+
46+
public static StudyRecord create(User user, StudyPlan studyPlan, Room room,
47+
LocalDateTime startTime, LocalDateTime endTime,
48+
List<PauseInfo> pauseInfos) {
49+
StudyRecord record = new StudyRecord();
50+
record.user = user;
51+
record.studyPlan = studyPlan;
52+
record.room = room;
53+
record.startTime = startTime;
54+
record.endTime = endTime;
55+
56+
// 일시정지 정보 추가
57+
if (pauseInfos != null && !pauseInfos.isEmpty()) {
58+
pauseInfos.forEach(record::addPauseInfo);
59+
}
60+
// 총 학습 시간 계산 (검증은 서비스에서)
61+
record.calculateDuration();
62+
63+
return record;
64+
}
65+
66+
// 일시정지 정보 추가
67+
private void addPauseInfo(PauseInfo pauseInfo) {
68+
this.pauseInfos.add(pauseInfo);
69+
pauseInfo.assignStudyRecord(this);
70+
}
71+
72+
// 실제 학습 시간 계산 (전체 시간 - 일시정지 시간)
73+
private void calculateDuration() {
74+
// 전체 시간 계산 (초)
75+
long totalSeconds = Duration.between(startTime, endTime).getSeconds();
76+
77+
// 일시정지 시간 제외
78+
long pausedSeconds = pauseInfos.stream()
79+
.filter(pause -> pause.getPausedAt() != null && pause.getRestartAt() != null)
80+
.mapToLong(pause -> Duration.between(pause.getPausedAt(), pause.getRestartAt()).getSeconds())
81+
.sum();
82+
83+
this.duration = totalSeconds - pausedSeconds;;
84+
}
3285
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package com.back.domain.study.record.repository;
2+
3+
import com.back.domain.study.record.entity.StudyRecord;
4+
import org.springframework.data.jpa.repository.JpaRepository;
5+
import org.springframework.data.jpa.repository.Query;
6+
import org.springframework.data.repository.query.Param;
7+
import org.springframework.stereotype.Repository;
8+
9+
import java.time.LocalDateTime;
10+
import java.util.List;
11+
12+
@Repository
13+
public interface StudyRecordRepository extends JpaRepository<StudyRecord, Long> {
14+
// 시작 시간을 기준으로만 조회
15+
List<StudyRecord> findByUserIdAndStartTimeBetween(
16+
Long userId, LocalDateTime start, LocalDateTime end);
17+
18+
// 시작 시간과 종료 시간을 둘 다 고려하여 조회
19+
@Query("SELECT sr FROM StudyRecord sr " +
20+
"WHERE sr.user.id = :userId " +
21+
"AND (" +
22+
" (sr.startTime >= :startOfDay AND sr.startTime < :endOfDay) OR " +
23+
" (sr.endTime > :startOfDay AND sr.endTime <= :endOfDay) OR " +
24+
" (sr.startTime < :startOfDay AND sr.endTime > :endOfDay)" +
25+
") " +
26+
"ORDER BY sr.startTime DESC")
27+
List<StudyRecord> findByUserIdAndDateRange(
28+
@Param("userId") Long userId,
29+
@Param("startOfDay") LocalDateTime startOfDay,
30+
@Param("endOfDay") LocalDateTime endOfDay);
31+
}

0 commit comments

Comments
 (0)