Skip to content

Commit 390da86

Browse files
committed
[FEAT] #44 오늘, 누적, 목표 공부 시간 저장 및 조회 기능 구현
1 parent 68ccea1 commit 390da86

File tree

10 files changed

+161
-56
lines changed

10 files changed

+161
-56
lines changed

src/main/java/com/example/be/BeApplication.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@
22

33
import org.springframework.boot.SpringApplication;
44
import org.springframework.boot.autoconfigure.SpringBootApplication;
5+
import org.springframework.scheduling.annotation.EnableScheduling;
56

7+
@EnableScheduling
68
@SpringBootApplication
79
public class BeApplication {
810

src/main/java/com/example/be/domain/User.java

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@
33
import com.example.be.domain.enums.LoginType;
44
import jakarta.persistence.*;
55
import lombok.*;
6+
import org.springframework.cglib.core.Local;
7+
8+
import java.time.LocalDate;
69
import java.time.LocalDateTime;
710
import java.util.List;
811
import java.util.UUID;
@@ -34,10 +37,16 @@ public class User {
3437

3538
private String providerId;
3639

37-
private String gptKey;
38-
3940
private LocalDateTime createDate;
4041

42+
@Column(columnDefinition = "int default 0")
43+
private int todayStudyTime;
44+
45+
@Column(columnDefinition = "int default 0")
46+
private int totalStudyTime;
47+
48+
@Column(columnDefinition = "int default 0")
49+
private int goalStudyTime;
4150

4251
//Relationships
4352
@OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true)

src/main/java/com/example/be/repository/UserRepository.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import com.example.be.domain.User;
44
import org.springframework.data.jpa.repository.JpaRepository;
5+
import org.springframework.data.jpa.repository.Modifying;
56
import org.springframework.data.jpa.repository.Query;
67
import java.util.Optional;
78
import java.util.UUID;
@@ -15,4 +16,8 @@ public interface UserRepository extends JpaRepository<User, Long> {
1516
boolean existsByEmail(String email);
1617

1718
Optional<User> findByEmail(String email);
19+
20+
@Modifying
21+
@Query("update User u Set u.todayStudyTime = 0")
22+
void updateTodayStudyTime();
1823
}

src/main/java/com/example/be/service/PostServiceImpl.java

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package com.example.be.service;
22

33

4-
import com.example.be.apiPayload.ApiResponse;
54
import com.example.be.apiPayload.code.status.ErrorStatus;
65
import com.example.be.apiPayload.exception.handler.PostHandler;
76
import com.example.be.apiPayload.exception.handler.UserHandler;
@@ -13,10 +12,8 @@
1312
import com.example.be.web.dto.CommentDTO;
1413
import com.example.be.web.dto.CommonDTO;
1514
import com.example.be.web.dto.PostDTO;
16-
import com.example.be.web.dto.UserDTO;
1715
import jakarta.servlet.http.HttpServletRequest;
1816
import lombok.RequiredArgsConstructor;
19-
import lombok.extern.slf4j.Slf4j;
2017
import org.springframework.data.domain.Page;
2118
import org.springframework.data.domain.PageRequest;
2219
import org.springframework.data.domain.Pageable;
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
package com.example.be.service;
2+
3+
import com.example.be.apiPayload.code.status.ErrorStatus;
4+
import com.example.be.apiPayload.exception.handler.UserHandler;
5+
import com.example.be.domain.User;
6+
import com.example.be.repository.UserRepository;
7+
import com.example.be.web.dto.CommonDTO;
8+
import com.example.be.web.dto.UserDTO;
9+
import jakarta.servlet.http.HttpServletRequest;
10+
import lombok.RequiredArgsConstructor;
11+
import org.springframework.scheduling.annotation.Scheduled;
12+
import org.springframework.stereotype.Service;
13+
import org.springframework.transaction.annotation.Transactional;
14+
15+
import java.util.UUID;
16+
17+
@Service
18+
@RequiredArgsConstructor
19+
public class StudyServiceImpl {
20+
private final JwtUtilServiceImpl jwtUtilService;
21+
private final UserRepository userRepository;
22+
23+
private User getUserFromRequest(HttpServletRequest request) {
24+
try {
25+
String accessToken = jwtUtilService.extractTokenFromCookie(request, "accessToken");
26+
if (accessToken != null) {
27+
String userId = jwtUtilService.getUserIdFromToken(accessToken);
28+
return userRepository.findByUserId(UUID.fromString(userId)).orElse(null);
29+
}
30+
} catch (Exception e) {
31+
throw new UserHandler(ErrorStatus._NOT_FOUND_COOKIE);
32+
}
33+
return null;
34+
}
35+
36+
public CommonDTO.IsSuccessDTO addStudyTime(HttpServletRequest request, int time) {
37+
User user= getUserFromRequest(request);
38+
39+
if (user != null) {
40+
user.setTodayStudyTime(user.getTodayStudyTime()+ time);
41+
user.setTotalStudyTime(user.getTotalStudyTime()+ time);
42+
userRepository.save(user);
43+
}
44+
45+
return CommonDTO.IsSuccessDTO.builder().isSuccess(true).build();
46+
}
47+
48+
public CommonDTO.IsSuccessDTO addStudyGoalTime(HttpServletRequest request, int time) {
49+
User user= getUserFromRequest(request);
50+
51+
if (user != null) {
52+
user.setGoalStudyTime(time);
53+
userRepository.save(user);
54+
}
55+
56+
return CommonDTO.IsSuccessDTO.builder().isSuccess(true).build();
57+
}
58+
59+
public UserDTO.studyTimeResponseDto getStudyTime(HttpServletRequest request) {
60+
User user= getUserFromRequest(request);
61+
62+
int today = user.getTodayStudyTime();
63+
int total = user.getTotalStudyTime();
64+
int goal = user.getGoalStudyTime();
65+
66+
return UserDTO.studyTimeResponseDto.builder()
67+
.userId(user.getId())
68+
.todayStudyTime(today/60 +"시간 " + today%60+"분")
69+
.totalStudyTime(total/60 + "시간 " + total%60 + "분")
70+
.goalStudyTime(goal/60 + "시간 " + goal%60 + "분")
71+
.build();
72+
}
73+
74+
//매일 자정에 오늘 공부 시간 초기화
75+
@Scheduled(cron = "0 0 0 * * *")
76+
@Transactional
77+
public void resetTodayStudyTime() {
78+
userRepository.updateTodayStudyTime();
79+
}
80+
81+
82+
83+
}

src/main/java/com/example/be/service/UserServiceImpl.java

Lines changed: 8 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
import com.example.be.apiPayload.exception.handler.UserHandler;
55
import com.example.be.domain.RefreshToken;
66
import com.example.be.domain.User;
7-
import com.example.be.domain.enums.LoginType;
87
import com.example.be.repository.RefreshTokenRepository;
98
import com.example.be.repository.UserRepository;
109
import com.example.be.web.dto.CommonDTO;
@@ -15,10 +14,11 @@
1514
import lombok.RequiredArgsConstructor;
1615
import lombok.extern.slf4j.Slf4j;
1716
import org.springframework.beans.factory.annotation.Value;
17+
import org.springframework.scheduling.annotation.Scheduled;
1818
import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler;
1919
import org.springframework.stereotype.Service;
20+
import org.springframework.transaction.annotation.Transactional;
2021

21-
import java.net.URLEncoder;
2222
import java.time.LocalDateTime;
2323
import java.util.UUID;
2424

@@ -27,7 +27,7 @@
2727
@RequiredArgsConstructor
2828
public class UserServiceImpl extends SimpleUrlAuthenticationSuccessHandler {
2929
private final UserRepository userRepository;
30-
private final JwtUtilServiceImpl jwtUtil;
30+
private final JwtUtilServiceImpl jwtUtilService;
3131
private final RefreshTokenRepository refreshTokenRepository;
3232

3333
@Value("${jwt.access-token.expiration-time}")
@@ -72,7 +72,7 @@ public CommonDTO.IsSuccessDTO login(UserDTO.LoginRequestDto request, HttpServlet
7272
refreshTokenRepository.deleteByUserId(user.getUserId());
7373

7474
// RefreshToken 재발급
75-
String refreshToken = jwtUtil.generateRefreshToken(user.getUserId(), REFRESH_TOKEN_EXPIRATION_TIME);
75+
String refreshToken = jwtUtilService.generateRefreshToken(user.getUserId(), REFRESH_TOKEN_EXPIRATION_TIME);
7676

7777
RefreshToken newRefreshToken = RefreshToken.builder()
7878
.userId(user.getUserId())
@@ -82,29 +82,14 @@ public CommonDTO.IsSuccessDTO login(UserDTO.LoginRequestDto request, HttpServlet
8282
refreshTokenRepository.save(newRefreshToken);
8383

8484
// AccessToken 발급
85-
String accessToken = jwtUtil.generateAccessToken(user.getUserId(), ACCESS_TOKEN_EXPIRATION_TIME);
86-
87-
String origin = httpRequest.getHeader("Origin");
88-
// boolean isLocalhost = origin != null && origin.contains("localhost");
89-
//
90-
// // 액세스 토큰 쿠키 설정
91-
// if (isLocalhost) {
92-
// // 로컬 개발 환경: SameSite=None, Secure=false
93-
// response.addHeader("Set-Cookie",
94-
// String.format("accessToken=%s; Path=/; Max-Age=%d; HttpOnly; SameSite=None",
95-
// accessToken, (int) (ACCESS_TOKEN_EXPIRATION_TIME / 1000)));
96-
// response.addHeader("Set-Cookie",
97-
// String.format("refreshToken=%s; Path=/; Max-Age=%d; HttpOnly; SameSite=None",
98-
// refreshToken, (int) (REFRESH_TOKEN_EXPIRATION_TIME / 1000)));
99-
// } else {
100-
// 배포 환경: SameSite=None, Secure=true
85+
String accessToken = jwtUtilService.generateAccessToken(user.getUserId(), ACCESS_TOKEN_EXPIRATION_TIME);
86+
10187
response.addHeader("Set-Cookie",
10288
String.format("accessToken=%s; Path=/; Max-Age=%d; HttpOnly; Secure; SameSite=None",
10389
accessToken, (int) (ACCESS_TOKEN_EXPIRATION_TIME / 1000)));
10490
response.addHeader("Set-Cookie",
10591
String.format("refreshToken=%s; Path=/; Max-Age=%d; HttpOnly; Secure; SameSite=None",
10692
refreshToken, (int) (REFRESH_TOKEN_EXPIRATION_TIME / 1000)));
107-
// }
10893

10994
return CommonDTO.IsSuccessDTO.builder().isSuccess(true).build();
11095
}
@@ -116,7 +101,7 @@ public UserDTO.UserResponseDto getUserInfo(String accessToken) {
116101
}
117102

118103
// 토큰에서 사용자 ID 추출
119-
String userId = jwtUtil.getUserIdFromToken(accessToken);
104+
String userId = jwtUtilService.getUserIdFromToken(accessToken);
120105

121106
// 사용자 정보 조회
122107
User user = userRepository.findByUserId(UUID.fromString(userId))
@@ -138,27 +123,12 @@ public CommonDTO.IsSuccessDTO logout(HttpServletResponse response, HttpServletRe
138123
if(cookies == null) {
139124
throw new UserHandler(ErrorStatus._NOT_FOUND_COOKIE);
140125
}
141-
142-
// Origin 헤더로 환경 판단
143-
// String origin = request.getHeader("Origin");
144-
// boolean isSecure = origin == null || !origin.contains("localhost");
145-
146-
// 쿠키 삭제 - addHeader 방식 사용
147-
// if (isSecure) {
148-
// 배포 환경
149126
response.addHeader("Set-Cookie",
150127
"accessToken=; Path=/; Max-Age=0; HttpOnly; Secure; SameSite=None");
151128
response.addHeader("Set-Cookie",
152129
"refreshToken=; Path=/; Max-Age=0; HttpOnly; Secure; SameSite=None");
153-
// } else {
154-
// // 로컬 환경
155-
// response.addHeader("Set-Cookie",
156-
// "accessToken=; Path=/; Max-Age=0; HttpOnly; SameSite=None");
157-
// response.addHeader("Set-Cookie",
158-
// "refreshToken=; Path=/; Max-Age=0; HttpOnly; SameSite=None");
159-
// }
160130

161131
return CommonDTO.IsSuccessDTO.builder().isSuccess(true).build();
162132
}
163-
164133
}
134+

src/main/java/com/example/be/web/controller/PostController.java

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,16 @@
11
package com.example.be.web.controller;
22

33
import com.example.be.apiPayload.ApiResponse;
4-
import com.example.be.apiPayload.code.status.ErrorStatus;
5-
import com.example.be.apiPayload.exception.handler.UserHandler;
6-
import com.example.be.domain.User;
7-
import com.example.be.repository.UserRepository;
8-
import com.example.be.service.JwtUtilServiceImpl;
94
import com.example.be.service.PostLikeServiceImpl;
105
import com.example.be.service.PostServiceImpl;
11-
import com.example.be.service.UserServiceImpl;
126
import com.example.be.web.dto.CommonDTO;
137
import com.example.be.web.dto.PostDTO;
14-
import com.example.be.web.dto.UserDTO;
158
import io.swagger.v3.oas.annotations.Operation;
169
import io.swagger.v3.oas.annotations.Parameter;
1710
import jakarta.servlet.http.HttpServletRequest;
1811
import lombok.RequiredArgsConstructor;
1912
import org.springframework.web.bind.annotation.*;
2013

21-
import java.util.List;
22-
2314
@RestController
2415
@RequestMapping("/post")
2516
@RequiredArgsConstructor
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
package com.example.be.web.controller;
2+
3+
import com.example.be.apiPayload.ApiResponse;
4+
import com.example.be.service.StudyServiceImpl;
5+
import com.example.be.service.UserServiceImpl;
6+
import com.example.be.web.dto.CommonDTO;
7+
import com.example.be.web.dto.UserDTO;
8+
import io.swagger.v3.oas.annotations.Operation;
9+
import jakarta.servlet.http.HttpServletRequest;
10+
import lombok.RequiredArgsConstructor;
11+
import org.springframework.web.bind.annotation.*;
12+
13+
@RestController
14+
@RequestMapping("/study")
15+
@RequiredArgsConstructor
16+
public class StudyController {
17+
18+
private final StudyServiceImpl studyService;
19+
20+
@PostMapping("/{time}")
21+
@Operation(summary = "오늘, 누적 공부 시간 추가 API")
22+
public ApiResponse<CommonDTO.IsSuccessDTO> addStudyTime(HttpServletRequest request, @PathVariable("time")int time) {
23+
return ApiResponse.onSuccess(studyService.addStudyTime(request, time));
24+
}
25+
26+
@PostMapping("/goal/{time}")
27+
@Operation(summary = "목표 공부 시간 수정 API")
28+
public ApiResponse<CommonDTO.IsSuccessDTO> addStudyGoalTime(HttpServletRequest request, @PathVariable("time")int time) {
29+
return ApiResponse.onSuccess(studyService.addStudyGoalTime(request, time));
30+
}
31+
32+
@GetMapping("/time")
33+
@Operation(summary = "오늘, 누적, 목표 공부 시간 조회 API")
34+
public ApiResponse<UserDTO.studyTimeResponseDto> getStudyTime(HttpServletRequest request) {
35+
return ApiResponse.onSuccess(studyService.getStudyTime(request));
36+
}
37+
38+
39+
}

src/main/java/com/example/be/web/controller/UserController.java

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,14 @@
11
package com.example.be.web.controller;
22

33
import com.example.be.apiPayload.ApiResponse;
4-
import com.example.be.apiPayload.code.status.ErrorStatus;
5-
import com.example.be.apiPayload.exception.handler.UserHandler;
64
import com.example.be.service.JwtUtilServiceImpl;
75
import com.example.be.service.UserServiceImpl;
86
import com.example.be.web.dto.CommonDTO;
97
import com.example.be.web.dto.UserDTO;
10-
import io.swagger.v3.oas.annotations.Hidden;
118
import io.swagger.v3.oas.annotations.Operation;
129
import jakarta.servlet.http.HttpServletRequest;
1310
import jakarta.servlet.http.HttpServletResponse;
1411
import lombok.RequiredArgsConstructor;
15-
import org.springframework.http.ResponseEntity;
1612
import org.springframework.web.bind.annotation.*;
1713

1814
@RestController
@@ -49,4 +45,6 @@ public ApiResponse<UserDTO.UserResponseDto> userInfo(HttpServletRequest request)
4945
public ApiResponse<CommonDTO.IsSuccessDTO> logout(HttpServletResponse response, HttpServletRequest request) {
5046
return ApiResponse.onSuccess(userService.logout(response, request));
5147
}
48+
49+
5250
}

src/main/java/com/example/be/web/dto/UserDTO.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,4 +37,15 @@ public static class LoginRequestDto {
3737
private String email;
3838
private String password;
3939
}
40+
41+
@Builder
42+
@Getter
43+
@NoArgsConstructor
44+
@AllArgsConstructor
45+
public static class studyTimeResponseDto {
46+
private Long userId;
47+
private String todayStudyTime;
48+
private String totalStudyTime;
49+
private String goalStudyTime;
50+
}
4051
}

0 commit comments

Comments
 (0)