From 267f4084706c7b53a1aacca07fad0975e6e3adb0 Mon Sep 17 00:00:00 2001 From: zhy2on <52701529+zhy2on@users.noreply.github.com> Date: Fri, 2 Aug 2024 05:04:11 +0900 Subject: [PATCH 01/28] =?UTF-8?q?refactor:=20=EC=9D=B8=EC=A6=9D=EA=B3=BC?= =?UTF-8?q?=EC=A0=95=20=EC=98=88=EC=99=B8=EC=B2=98=EB=A6=AC=20#55=20=20-?= =?UTF-8?q?=20exceptionHandling=EC=97=90=20authenticationEntryPoint=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=20=20-=20authenticationFilter=EC=97=90?= =?UTF-8?q?=EC=84=9C=20=EC=98=88=EC=99=B8=EB=A5=BC=20=EB=8D=94=20=EA=B5=AC?= =?UTF-8?q?=EC=B2=B4=EC=A0=81=EC=9C=BC=EB=A1=9C=20=EC=B2=98=EB=A6=AC?= =?UTF-8?q?=ED=95=98=EB=8F=84=EB=A1=9D=20=EC=88=98=EC=A0=95=20=20-=20refre?= =?UTF-8?q?sh=20token=EC=9D=B4=20=EC=97=86=EB=8A=94=20=EC=B1=84=EB=A1=9C?= =?UTF-8?q?=20=EC=9A=94=EC=B2=AD=EC=8B=9C=20UNAUTHORIZED=EA=B0=80=20?= =?UTF-8?q?=EC=95=84=EB=8B=8C=20BAD=5FREQUEST=EB=A5=BC=20=EB=B0=98?= =?UTF-8?q?=ED=99=98=ED=95=98=EB=8F=84=EB=A1=9D=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../auth/JwtAuthenticationEntryPoint.java | 25 +++++++++++++++++++ .../auth/JwtAuthenticationFilter.java | 25 +++++++++++++++++-- .../auth/JwtTokenProvider.java | 12 +++------ .../auth/controller/AuthController.java | 2 +- .../common/config/SecurityConfig.java | 11 +++++--- 5 files changed, 60 insertions(+), 15 deletions(-) create mode 100644 src/main/java/com/tutorialsejong/courseregistration/auth/JwtAuthenticationEntryPoint.java diff --git a/src/main/java/com/tutorialsejong/courseregistration/auth/JwtAuthenticationEntryPoint.java b/src/main/java/com/tutorialsejong/courseregistration/auth/JwtAuthenticationEntryPoint.java new file mode 100644 index 0000000..1d9a950 --- /dev/null +++ b/src/main/java/com/tutorialsejong/courseregistration/auth/JwtAuthenticationEntryPoint.java @@ -0,0 +1,25 @@ +package com.tutorialsejong.courseregistration.auth; + +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import java.io.IOException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.web.AuthenticationEntryPoint; +import org.springframework.stereotype.Component; + +@Component +public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint { + + private static final Logger logger = LoggerFactory.getLogger(JwtAuthenticationEntryPoint.class); + + @Override + public void commence(HttpServletRequest request, HttpServletResponse response, + AuthenticationException authException) throws IOException { + logger.error("Unauthorized error: {}", authException.getMessage()); + response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); + response.setContentType("application/json"); + response.getWriter().write(String.format("{\"error\": \"Unauthorized: %s\"}", authException.getMessage())); + } +} diff --git a/src/main/java/com/tutorialsejong/courseregistration/auth/JwtAuthenticationFilter.java b/src/main/java/com/tutorialsejong/courseregistration/auth/JwtAuthenticationFilter.java index 0e699d4..bc3aa6d 100644 --- a/src/main/java/com/tutorialsejong/courseregistration/auth/JwtAuthenticationFilter.java +++ b/src/main/java/com/tutorialsejong/courseregistration/auth/JwtAuthenticationFilter.java @@ -1,5 +1,7 @@ package com.tutorialsejong.courseregistration.auth; +import com.tutorialsejong.courseregistration.common.exception.JwtAuthenticationException; +import io.jsonwebtoken.ExpiredJwtException; import jakarta.servlet.FilterChain; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; @@ -25,9 +27,8 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse throws ServletException, IOException { try { String jwt = getJwtFromRequest(request); - tokenProvider.validateToken(jwt); - if (StringUtils.hasText(jwt)) { + tokenProvider.validateToken(jwt); Authentication authentication = tokenProvider.getAuthentication(jwt); if (authentication instanceof UsernamePasswordAuthenticationToken) { @@ -37,13 +38,27 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse SecurityContextHolder.getContext().setAuthentication(authentication); } + filterChain.doFilter(request, response); + } catch (ExpiredJwtException ex) { + logger.error("Expired JWT token", ex); + sendErrorResponse(response, HttpServletResponse.SC_UNAUTHORIZED, "Access Token has expired"); + } catch (JwtAuthenticationException ex) { + logger.error("Invalid JWT token", ex); + sendErrorResponse(response, HttpServletResponse.SC_UNAUTHORIZED, "Invalid Access Token"); } catch (Exception ex) { logger.error("Could not set user authentication in security context", ex); + sendErrorResponse(response, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "An error occurred while processing your request"); } filterChain.doFilter(request, response); } + private void sendErrorResponse(HttpServletResponse response, int status, String message) throws IOException { + response.setStatus(status); + response.setContentType("application/json"); + response.getWriter().write(String.format("{\"error\": \"%s\"}", message)); + } + private String getJwtFromRequest(HttpServletRequest request) { String bearerToken = request.getHeader("Authorization"); if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) { @@ -51,4 +66,10 @@ private String getJwtFromRequest(HttpServletRequest request) { } return null; } + + @Override + protected boolean shouldNotFilter(HttpServletRequest request) { + String path = request.getRequestURI(); + return path.equals("/api/auth/login") || path.equals("/api/auth/refresh"); + } } diff --git a/src/main/java/com/tutorialsejong/courseregistration/auth/JwtTokenProvider.java b/src/main/java/com/tutorialsejong/courseregistration/auth/JwtTokenProvider.java index ad0147c..2218c0b 100644 --- a/src/main/java/com/tutorialsejong/courseregistration/auth/JwtTokenProvider.java +++ b/src/main/java/com/tutorialsejong/courseregistration/auth/JwtTokenProvider.java @@ -77,12 +77,10 @@ public String getUsernameFromJWT(String token) { public void validateToken(String authToken) { try { Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(authToken); - } catch (MalformedJwtException | UnsupportedJwtException ex) { - throw new JwtAuthenticationException("Invalid JWT token", ex); } catch (ExpiredJwtException ex) { - throw new JwtAuthenticationException("Expired JWT token", ex); - } catch (IllegalArgumentException ex) { - throw new JwtAuthenticationException("JWT claims string is empty", ex); + throw ex; + } catch (MalformedJwtException | UnsupportedJwtException | IllegalArgumentException ex) { + throw new JwtAuthenticationException("Invalid JWT token", ex); } } @@ -91,8 +89,4 @@ public Authentication getAuthentication(String token) { UserDetails userDetails = customUserDetailsService.loadUserByUsername(username); return new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities()); } - - public int getRefreshTokenExpirationInSeconds() { - return refreshTokenExpirationInMs / 1000; - } } diff --git a/src/main/java/com/tutorialsejong/courseregistration/auth/controller/AuthController.java b/src/main/java/com/tutorialsejong/courseregistration/auth/controller/AuthController.java index c6185c5..5c74702 100644 --- a/src/main/java/com/tutorialsejong/courseregistration/auth/controller/AuthController.java +++ b/src/main/java/com/tutorialsejong/courseregistration/auth/controller/AuthController.java @@ -61,7 +61,7 @@ public ResponseEntity login(@RequestBody @Valid LoginRequest loginRequest, Ht @PostMapping("/refresh") public ResponseEntity refreshToken(@CookieValue(name = "refreshToken", required = false) String refreshToken) { if (refreshToken == null) { - return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("Refresh token is missing"); + return ResponseEntity.status(HttpStatus.BAD_REQUEST).body("Refresh token is missing"); } JwtTokens jwtTokens = authService.refreshAccessToken(refreshToken); diff --git a/src/main/java/com/tutorialsejong/courseregistration/common/config/SecurityConfig.java b/src/main/java/com/tutorialsejong/courseregistration/common/config/SecurityConfig.java index 211667f..230c080 100644 --- a/src/main/java/com/tutorialsejong/courseregistration/common/config/SecurityConfig.java +++ b/src/main/java/com/tutorialsejong/courseregistration/common/config/SecurityConfig.java @@ -1,5 +1,6 @@ package com.tutorialsejong.courseregistration.common.config; +import com.tutorialsejong.courseregistration.auth.JwtAuthenticationEntryPoint; import com.tutorialsejong.courseregistration.auth.JwtAuthenticationFilter; import com.tutorialsejong.courseregistration.auth.JwtTokenProvider; import java.util.Arrays; @@ -22,9 +23,11 @@ public class SecurityConfig { private final JwtTokenProvider tokenProvider; + private final JwtAuthenticationEntryPoint unauthorizedHandler; - public SecurityConfig(JwtTokenProvider tokenProvider) { + public SecurityConfig(JwtTokenProvider tokenProvider,JwtAuthenticationEntryPoint unauthorizedHandler ) { this.tokenProvider = tokenProvider; + this.unauthorizedHandler = unauthorizedHandler; } @Bean @@ -38,7 +41,7 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { .cors(cors -> cors .configurationSource(request -> { CorsConfiguration config = new CorsConfiguration(); - config.setAllowedOrigins(Arrays.asList("http://localhost:3000")); // 변경 + config.setAllowedOrigins(Arrays.asList("https://tutorial-sejong.com")); config.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE", "OPTIONS")); config.setAllowedHeaders(Arrays.asList("*")); config.setAllowCredentials(true); @@ -50,11 +53,13 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { .authorizeHttpRequests(auth -> auth .requestMatchers( "/api/auth/login", - "/api/auth/register", "/api/auth/refresh" ).permitAll() .anyRequest().authenticated() ) + .exceptionHandling(exceptionHandling -> exceptionHandling + .authenticationEntryPoint(unauthorizedHandler) + ) .addFilterBefore(new JwtAuthenticationFilter(tokenProvider), UsernamePasswordAuthenticationFilter.class); From 44550528cec825961a169b7f366fe88844fec63c Mon Sep 17 00:00:00 2001 From: zhy2on <52701529+zhy2on@users.noreply.github.com> Date: Fri, 2 Aug 2024 06:08:48 +0900 Subject: [PATCH 02/28] =?UTF-8?q?chore:=20=ED=85=8C=EC=8A=A4=ED=8A=B8?= =?UTF-8?q?=EC=9A=A9=20cors=20=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../courseregistration/common/config/SecurityConfig.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/tutorialsejong/courseregistration/common/config/SecurityConfig.java b/src/main/java/com/tutorialsejong/courseregistration/common/config/SecurityConfig.java index 230c080..65e2f9c 100644 --- a/src/main/java/com/tutorialsejong/courseregistration/common/config/SecurityConfig.java +++ b/src/main/java/com/tutorialsejong/courseregistration/common/config/SecurityConfig.java @@ -41,7 +41,7 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { .cors(cors -> cors .configurationSource(request -> { CorsConfiguration config = new CorsConfiguration(); - config.setAllowedOrigins(Arrays.asList("https://tutorial-sejong.com")); + config.setAllowedOrigins(Arrays.asList("https://tutorial-sejong.com", "https://frontend.local.com:3000", "http://localhost:3000")); config.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE", "OPTIONS")); config.setAllowedHeaders(Arrays.asList("*")); config.setAllowCredentials(true); From c1df516d47f6a9b71243200d8f156048ad3a137c Mon Sep 17 00:00:00 2001 From: zhy2on <52701529+zhy2on@users.noreply.github.com> Date: Fri, 2 Aug 2024 16:39:13 +0900 Subject: [PATCH 03/28] =?UTF-8?q?fix:=20=EC=9D=B8=EC=A6=9D=EA=B3=BC?= =?UTF-8?q?=EC=A0=95=20=EC=98=A4=EB=A5=98=20=EC=88=98=EC=A0=95=20-=20?= =?UTF-8?q?=EC=8B=9C=EA=B0=84=EC=9D=B4=20=EC=97=86=EC=96=B4=20=EC=A0=95?= =?UTF-8?q?=ED=99=95=ED=95=9C=20=EC=9B=90=EC=9D=B8=EC=9D=80=20=ED=8C=8C?= =?UTF-8?q?=EC=95=85=ED=95=9C=20=EB=92=A4=20=EC=9D=B4=EC=8A=88=EC=97=90=20?= =?UTF-8?q?=EB=94=B0=EB=A1=9C=20=EB=82=A8=EA=B8=B0=EA=B3=A0=20=EC=96=B8?= =?UTF-8?q?=EA=B8=89=20=ED=95=98=EA=B2=A0=EC=8A=B5=EB=8B=88=EB=8B=A4..?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../courseregistration/auth/JwtAuthenticationFilter.java | 7 ++++--- .../courseregistration/common/config/SecurityConfig.java | 7 +++++-- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/src/main/java/com/tutorialsejong/courseregistration/auth/JwtAuthenticationFilter.java b/src/main/java/com/tutorialsejong/courseregistration/auth/JwtAuthenticationFilter.java index bc3aa6d..683946e 100644 --- a/src/main/java/com/tutorialsejong/courseregistration/auth/JwtAuthenticationFilter.java +++ b/src/main/java/com/tutorialsejong/courseregistration/auth/JwtAuthenticationFilter.java @@ -1,5 +1,6 @@ package com.tutorialsejong.courseregistration.auth; +import com.tutorialsejong.courseregistration.auth.service.CustomUserDetailsService; import com.tutorialsejong.courseregistration.common.exception.JwtAuthenticationException; import io.jsonwebtoken.ExpiredJwtException; import jakarta.servlet.FilterChain; @@ -17,9 +18,11 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter { private final JwtTokenProvider tokenProvider; + private final CustomUserDetailsService userDetailsService; - public JwtAuthenticationFilter(JwtTokenProvider tokenProvider) { + public JwtAuthenticationFilter(JwtTokenProvider tokenProvider, CustomUserDetailsService userDetailsService) { this.tokenProvider = tokenProvider; + this.userDetailsService = userDetailsService; } @Override @@ -49,8 +52,6 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse logger.error("Could not set user authentication in security context", ex); sendErrorResponse(response, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "An error occurred while processing your request"); } - - filterChain.doFilter(request, response); } private void sendErrorResponse(HttpServletResponse response, int status, String message) throws IOException { diff --git a/src/main/java/com/tutorialsejong/courseregistration/common/config/SecurityConfig.java b/src/main/java/com/tutorialsejong/courseregistration/common/config/SecurityConfig.java index 65e2f9c..07a52c4 100644 --- a/src/main/java/com/tutorialsejong/courseregistration/common/config/SecurityConfig.java +++ b/src/main/java/com/tutorialsejong/courseregistration/common/config/SecurityConfig.java @@ -3,6 +3,7 @@ import com.tutorialsejong.courseregistration.auth.JwtAuthenticationEntryPoint; import com.tutorialsejong.courseregistration.auth.JwtAuthenticationFilter; import com.tutorialsejong.courseregistration.auth.JwtTokenProvider; +import com.tutorialsejong.courseregistration.auth.service.CustomUserDetailsService; import java.util.Arrays; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -23,11 +24,13 @@ public class SecurityConfig { private final JwtTokenProvider tokenProvider; + private final CustomUserDetailsService customUserDetailsService; private final JwtAuthenticationEntryPoint unauthorizedHandler; - public SecurityConfig(JwtTokenProvider tokenProvider,JwtAuthenticationEntryPoint unauthorizedHandler ) { + public SecurityConfig(JwtTokenProvider tokenProvider,JwtAuthenticationEntryPoint unauthorizedHandler, CustomUserDetailsService customUserDetailsService) { this.tokenProvider = tokenProvider; this.unauthorizedHandler = unauthorizedHandler; + this.customUserDetailsService = customUserDetailsService; } @Bean @@ -60,7 +63,7 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { .exceptionHandling(exceptionHandling -> exceptionHandling .authenticationEntryPoint(unauthorizedHandler) ) - .addFilterBefore(new JwtAuthenticationFilter(tokenProvider), + .addFilterBefore(new JwtAuthenticationFilter(tokenProvider, customUserDetailsService), UsernamePasswordAuthenticationFilter.class); return http.build(); From d232a5bca717e8ee3d16348d8bcd8b6162b7a9a5 Mon Sep 17 00:00:00 2001 From: zhy2on <52701529+zhy2on@users.noreply.github.com> Date: Sat, 3 Aug 2024 23:53:46 +0900 Subject: [PATCH 04/28] =?UTF-8?q?refactor:=20=EC=88=98=EA=B0=95=EC=8B=A0?= =?UTF-8?q?=EC=B2=AD=20=EA=B4=80=EB=A0=A8=20api=20=EB=A6=AC=ED=8C=A9?= =?UTF-8?q?=ED=86=A0=EB=A7=81=20(#59)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: 수강신청 목록 초기화 api 구현 * refactor: 수강 신청 과목 제외하고 강좌 목록 반환 --- .../controller/CourseRegistrationController.java | 6 ++++++ .../service/CourseRegistrationService.java | 9 +++++++++ .../schedule/service/ScheduleService.java | 11 ++++++++++- .../wishlist/service/WishListService.java | 15 +++++++++++---- 4 files changed, 36 insertions(+), 5 deletions(-) diff --git a/src/main/java/com/tutorialsejong/courseregistration/registration/controller/CourseRegistrationController.java b/src/main/java/com/tutorialsejong/courseregistration/registration/controller/CourseRegistrationController.java index a3d0588..8376643 100644 --- a/src/main/java/com/tutorialsejong/courseregistration/registration/controller/CourseRegistrationController.java +++ b/src/main/java/com/tutorialsejong/courseregistration/registration/controller/CourseRegistrationController.java @@ -52,4 +52,10 @@ public ResponseEntity cancelCourseRegistration( courseRegistrationService.cancelCourseRegistration(userDetails.getUsername(), scheduleId); return ResponseEntity.ok().build(); } + + @DeleteMapping("/all") + public ResponseEntity cancelAllCourseRegistrations(@AuthenticationPrincipal UserDetails userDetails) { + courseRegistrationService.cancelAllCourseRegistrations(userDetails.getUsername()); + return ResponseEntity.ok().build(); + } } diff --git a/src/main/java/com/tutorialsejong/courseregistration/registration/service/CourseRegistrationService.java b/src/main/java/com/tutorialsejong/courseregistration/registration/service/CourseRegistrationService.java index 12a7765..9eb1831 100644 --- a/src/main/java/com/tutorialsejong/courseregistration/registration/service/CourseRegistrationService.java +++ b/src/main/java/com/tutorialsejong/courseregistration/registration/service/CourseRegistrationService.java @@ -67,6 +67,15 @@ public void cancelCourseRegistration(String studentId, Long scheduleId) { courseRegistrationRepository.delete(registration); } + @Transactional + public void cancelAllCourseRegistrations(String studentId) { + User student = userRepository.findByStudentId(studentId) + .orElseThrow(() -> new NotFoundException("User not found with id: " + studentId)); + + List registrations = courseRegistrationRepository.findAllByStudent(student); + courseRegistrationRepository.deleteAll(registrations); + } + private CourseRegistrationResponse convertToDto(CourseRegistration registration) { return new CourseRegistrationResponse( registration.getStudent().getStudentId(), diff --git a/src/main/java/com/tutorialsejong/courseregistration/schedule/service/ScheduleService.java b/src/main/java/com/tutorialsejong/courseregistration/schedule/service/ScheduleService.java index 16eb874..5430c25 100644 --- a/src/main/java/com/tutorialsejong/courseregistration/schedule/service/ScheduleService.java +++ b/src/main/java/com/tutorialsejong/courseregistration/schedule/service/ScheduleService.java @@ -1,5 +1,7 @@ package com.tutorialsejong.courseregistration.schedule.service; +import com.tutorialsejong.courseregistration.registration.entity.CourseRegistration; +import com.tutorialsejong.courseregistration.registration.repository.CourseRegistrationRepository; import com.tutorialsejong.courseregistration.schedule.dto.ScheduleSearchRequest; import com.tutorialsejong.courseregistration.schedule.entity.Schedule; import com.tutorialsejong.courseregistration.schedule.repository.ScheduleRepository; @@ -19,12 +21,15 @@ public class ScheduleService { private final ScheduleRepository scheduleRepository; private final WishListRepository wishListRepository; private final UserRepository userRepository; + private final CourseRegistrationRepository courseRegistrationRepository; @Autowired - public ScheduleService(ScheduleRepository scheduleRepository, WishListRepository wishListRepository, UserRepository userRepository) { + public ScheduleService(ScheduleRepository scheduleRepository, WishListRepository wishListRepository, + UserRepository userRepository, CourseRegistrationRepository courseRegistrationRepository) { this.scheduleRepository = scheduleRepository; this.wishListRepository = wishListRepository; this.userRepository = userRepository; + this.courseRegistrationRepository = courseRegistrationRepository; } public List getSearchResultSchedules(ScheduleSearchRequest scheduleSearchRequest, String studentId) { @@ -46,6 +51,10 @@ public List getSearchResultSchedules(ScheduleSearchRequest scheduleSea .map(WishList::getScheduleId) .collect(Collectors.toList()); + List registeredSchedules = courseRegistrationRepository.findAllByStudent(user).stream() + .map(CourseRegistration::getSchedule) + .collect(Collectors.toList()); + return findAllByResult.stream() .filter(schedule -> !wishListSchedules.contains(schedule)) .collect(Collectors.toList()); diff --git a/src/main/java/com/tutorialsejong/courseregistration/wishlist/service/WishListService.java b/src/main/java/com/tutorialsejong/courseregistration/wishlist/service/WishListService.java index c485e3a..675775f 100644 --- a/src/main/java/com/tutorialsejong/courseregistration/wishlist/service/WishListService.java +++ b/src/main/java/com/tutorialsejong/courseregistration/wishlist/service/WishListService.java @@ -1,16 +1,16 @@ package com.tutorialsejong.courseregistration.wishlist.service; import com.tutorialsejong.courseregistration.common.exception.CheckUserException; +import com.tutorialsejong.courseregistration.registration.repository.CourseRegistrationRepository; import com.tutorialsejong.courseregistration.schedule.entity.Schedule; import com.tutorialsejong.courseregistration.schedule.repository.ScheduleRepository; import com.tutorialsejong.courseregistration.user.entity.User; import com.tutorialsejong.courseregistration.user.repository.UserRepository; import com.tutorialsejong.courseregistration.wishlist.entity.WishList; import com.tutorialsejong.courseregistration.wishlist.repository.WishListRepository; -import org.springframework.stereotype.Service; - import java.util.List; import java.util.stream.Collectors; +import org.springframework.stereotype.Service; @Service public class WishListService { @@ -18,11 +18,16 @@ public class WishListService { private final WishListRepository wishListRepository; private final UserRepository userRepository; private final ScheduleRepository scheduleRepository; + private final CourseRegistrationRepository courseRegistrationRepository; - public WishListService(WishListRepository wishListRepository, UserRepository userRepository, ScheduleRepository scheduleRepository) { + public WishListService(WishListRepository wishListRepository, + UserRepository userRepository, + ScheduleRepository scheduleRepository, + CourseRegistrationRepository courseRegistrationRepository) { this.wishListRepository = wishListRepository; this.userRepository = userRepository; this.scheduleRepository = scheduleRepository; + this.courseRegistrationRepository = courseRegistrationRepository; } public void saveWishList(String studentId, List wishListIdList) { @@ -31,11 +36,12 @@ public void saveWishList(String studentId, List wishListIdList) { List wishList = wishListIdList.stream() .map(this::checkExistSchedule) .filter(schedule -> !wishListRepository.existsByStudentIdAndScheduleId(user, schedule)) // 이미 등록된 관심과목 제외 + .filter(schedule -> !courseRegistrationRepository.existsByStudentStudentIdAndScheduleScheduleId(studentId, schedule.getScheduleId())) // 수강신청된 과목 제외 .map(schedule -> new WishList(user, schedule)) .collect(Collectors.toList()); if (wishList.isEmpty()) { - new CheckUserException("이미 신청된 관심과목이 포함되어있습니다."); + throw new CheckUserException("이미 신청된 관심과목이거나 수강신청된 과목이 포함되어있습니다."); } wishListRepository.saveAll(wishList); @@ -48,6 +54,7 @@ public List getWishList(String studentId) { return wishListList.stream() .map(WishList::getScheduleId) + .filter(schedule -> !courseRegistrationRepository.existsByStudentStudentIdAndScheduleScheduleId(studentId, schedule.getScheduleId())) // 수강신청된 과목 제외 .collect(Collectors.toList()); } From 60fa1d5d039b535f662725a767a554f99614a293 Mon Sep 17 00:00:00 2001 From: zhy2on <52701529+zhy2on@users.noreply.github.com> Date: Sun, 4 Aug 2024 01:24:13 +0900 Subject: [PATCH 05/28] =?UTF-8?q?refactor:=20=EA=B0=95=EC=A2=8C=20?= =?UTF-8?q?=EC=A1=B0=ED=9A=8C=20api=20=EC=88=98=EC=A0=95=20-=20=EC=9B=90?= =?UTF-8?q?=EB=9E=98=20=EA=B4=80=EC=8B=AC=EA=B3=BC=EB=AA=A9=20=EB=AA=A9?= =?UTF-8?q?=EB=A1=9D=EB=8F=84=20=EC=A0=9C=EC=99=B8=ED=96=88=EC=97=88?= =?UTF-8?q?=EB=8A=94=EB=8D=B0=20=EC=88=98=EA=B0=95=EC=8B=A0=EC=B2=AD?= =?UTF-8?q?=EB=90=9C=20=EB=AA=A9=EB=A1=9D=EB=A7=8C=20=EC=A0=9C=EC=99=B8?= =?UTF-8?q?=ED=95=98=EB=8F=84=EB=A1=9D=20=EB=B3=80=EA=B2=BD=ED=95=98?= =?UTF-8?q?=EC=98=80=EC=8A=B5=EB=8B=88=EB=8B=A4.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../schedule/service/ScheduleService.java | 19 +++++-------------- 1 file changed, 5 insertions(+), 14 deletions(-) diff --git a/src/main/java/com/tutorialsejong/courseregistration/schedule/service/ScheduleService.java b/src/main/java/com/tutorialsejong/courseregistration/schedule/service/ScheduleService.java index 5430c25..978fb91 100644 --- a/src/main/java/com/tutorialsejong/courseregistration/schedule/service/ScheduleService.java +++ b/src/main/java/com/tutorialsejong/courseregistration/schedule/service/ScheduleService.java @@ -7,27 +7,22 @@ import com.tutorialsejong.courseregistration.schedule.repository.ScheduleRepository; import com.tutorialsejong.courseregistration.user.entity.User; import com.tutorialsejong.courseregistration.user.repository.UserRepository; -import com.tutorialsejong.courseregistration.wishlist.entity.WishList; -import com.tutorialsejong.courseregistration.wishlist.repository.WishListRepository; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Service; - import java.util.List; import java.util.stream.Collectors; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; @Service public class ScheduleService { private final ScheduleRepository scheduleRepository; - private final WishListRepository wishListRepository; private final UserRepository userRepository; private final CourseRegistrationRepository courseRegistrationRepository; @Autowired - public ScheduleService(ScheduleRepository scheduleRepository, WishListRepository wishListRepository, - UserRepository userRepository, CourseRegistrationRepository courseRegistrationRepository) { + public ScheduleService(ScheduleRepository scheduleRepository, UserRepository userRepository, + CourseRegistrationRepository courseRegistrationRepository) { this.scheduleRepository = scheduleRepository; - this.wishListRepository = wishListRepository; this.userRepository = userRepository; this.courseRegistrationRepository = courseRegistrationRepository; } @@ -47,16 +42,12 @@ public List getSearchResultSchedules(ScheduleSearchRequest scheduleSea scheduleSearchRequest.lesnEmp() ); - List wishListSchedules = wishListRepository.findAllByStudentId(user).stream() - .map(WishList::getScheduleId) - .collect(Collectors.toList()); - List registeredSchedules = courseRegistrationRepository.findAllByStudent(user).stream() .map(CourseRegistration::getSchedule) .collect(Collectors.toList()); return findAllByResult.stream() - .filter(schedule -> !wishListSchedules.contains(schedule)) + .filter(schedule -> !registeredSchedules.contains(schedule)) .collect(Collectors.toList()); } } From 76953085356898cb8f6fda397735f8d4a195adf6 Mon Sep 17 00:00:00 2001 From: zhy2on <52701529+zhy2on@users.noreply.github.com> Date: Tue, 6 Aug 2024 00:27:48 +0900 Subject: [PATCH 06/28] =?UTF-8?q?fix:=20=EC=88=98=EA=B0=95=20=EC=8B=A0?= =?UTF-8?q?=EC=B2=AD=20=EB=8F=99=EC=8B=9C=EC=84=B1=20=EC=9D=B4=EC=8A=88=20?= =?UTF-8?q?=ED=95=B4=EA=B2=B0=20=EB=B0=8F=20=EC=BD=94=EB=93=9C=20=EA=B0=9C?= =?UTF-8?q?=EC=84=A0=20(#61)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: 수강 신청 동시성 이슈 해결 - CourseRegistration 엔티티에 유니크 제약 조건 추가 - registerCourse 메서드에서 중복 체크 로직 개선 * refactor: 예외처리 수정 - 상황에 맞지 않는 익셉션을 사용하던 것 수정 - BadRequestException 추가 --- .../common/exception/BadRequestException.java | 7 +++++++ .../exception/GlobalExceptionHandler.java | 7 +++++++ .../entity/CourseRegistration.java | 5 ++++- .../service/CourseRegistrationService.java | 19 ++++++++++--------- .../wishlist/service/WishListService.java | 12 +++++++----- 5 files changed, 35 insertions(+), 15 deletions(-) create mode 100644 src/main/java/com/tutorialsejong/courseregistration/common/exception/BadRequestException.java diff --git a/src/main/java/com/tutorialsejong/courseregistration/common/exception/BadRequestException.java b/src/main/java/com/tutorialsejong/courseregistration/common/exception/BadRequestException.java new file mode 100644 index 0000000..5062b9f --- /dev/null +++ b/src/main/java/com/tutorialsejong/courseregistration/common/exception/BadRequestException.java @@ -0,0 +1,7 @@ +package com.tutorialsejong.courseregistration.common.exception; + +public class BadRequestException extends RuntimeException { + public BadRequestException(String message) { + super(message); + } +} diff --git a/src/main/java/com/tutorialsejong/courseregistration/common/exception/GlobalExceptionHandler.java b/src/main/java/com/tutorialsejong/courseregistration/common/exception/GlobalExceptionHandler.java index 31b72fa..22ede41 100644 --- a/src/main/java/com/tutorialsejong/courseregistration/common/exception/GlobalExceptionHandler.java +++ b/src/main/java/com/tutorialsejong/courseregistration/common/exception/GlobalExceptionHandler.java @@ -88,4 +88,11 @@ public ResponseEntity handleNotFoundException(NotFoundException ex) { body.put("message", ex.getMessage()); return ResponseEntity.status(HttpStatus.NOT_FOUND).body(body); } + + @ExceptionHandler(BadRequestException.class) + public ResponseEntity handleBadRequestException(BadRequestException ex) { + Map body = new HashMap<>(); + body.put("message", ex.getMessage()); + return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(body); + } } diff --git a/src/main/java/com/tutorialsejong/courseregistration/registration/entity/CourseRegistration.java b/src/main/java/com/tutorialsejong/courseregistration/registration/entity/CourseRegistration.java index 62e0d6d..08206e9 100644 --- a/src/main/java/com/tutorialsejong/courseregistration/registration/entity/CourseRegistration.java +++ b/src/main/java/com/tutorialsejong/courseregistration/registration/entity/CourseRegistration.java @@ -10,10 +10,13 @@ import jakarta.persistence.JoinColumn; import jakarta.persistence.ManyToOne; import jakarta.persistence.Table; +import jakarta.persistence.UniqueConstraint; import java.time.LocalDateTime; @Entity -@Table(name = "course_registration") +@Table(name = "course_registration", uniqueConstraints = { + @UniqueConstraint(columnNames = {"student_id", "schedule_id"}) +}) public class CourseRegistration { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) diff --git a/src/main/java/com/tutorialsejong/courseregistration/registration/service/CourseRegistrationService.java b/src/main/java/com/tutorialsejong/courseregistration/registration/service/CourseRegistrationService.java index 9eb1831..663779d 100644 --- a/src/main/java/com/tutorialsejong/courseregistration/registration/service/CourseRegistrationService.java +++ b/src/main/java/com/tutorialsejong/courseregistration/registration/service/CourseRegistrationService.java @@ -12,6 +12,7 @@ import java.time.LocalDateTime; import java.util.List; import java.util.stream.Collectors; +import org.springframework.dao.DataIntegrityViolationException; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -32,18 +33,18 @@ public CourseRegistrationService(CourseRegistrationRepository courseRegistration @Transactional public CourseRegistrationResponse registerCourse(String studentId, Long scheduleId) { - User student = userRepository.findByStudentId(studentId) - .orElseThrow(() -> new NotFoundException("User not found with id: " + studentId)); - Schedule schedule = scheduleRepository.findById(scheduleId) - .orElseThrow(() -> new NotFoundException("Schedule not found with id: " + scheduleId)); + try { + User student = userRepository.findByStudentId(studentId) + .orElseThrow(() -> new NotFoundException("User not found with id: " + studentId)); + Schedule schedule = scheduleRepository.findById(scheduleId) + .orElseThrow(() -> new NotFoundException("Schedule not found with id: " + scheduleId)); - if (courseRegistrationRepository.existsByStudentStudentIdAndScheduleScheduleId(studentId, scheduleId)) { + CourseRegistration registration = new CourseRegistration(student, schedule, LocalDateTime.now()); + registration = courseRegistrationRepository.save(registration); + return convertToDto(registration); + } catch (DataIntegrityViolationException e) { throw new AlreadyRegisteredException("Course already registered"); } - - CourseRegistration registration = new CourseRegistration(student, schedule, LocalDateTime.now()); - registration = courseRegistrationRepository.save(registration); - return convertToDto(registration); } @Transactional(readOnly = true) diff --git a/src/main/java/com/tutorialsejong/courseregistration/wishlist/service/WishListService.java b/src/main/java/com/tutorialsejong/courseregistration/wishlist/service/WishListService.java index 675775f..5adc057 100644 --- a/src/main/java/com/tutorialsejong/courseregistration/wishlist/service/WishListService.java +++ b/src/main/java/com/tutorialsejong/courseregistration/wishlist/service/WishListService.java @@ -1,6 +1,8 @@ package com.tutorialsejong.courseregistration.wishlist.service; -import com.tutorialsejong.courseregistration.common.exception.CheckUserException; +import com.tutorialsejong.courseregistration.common.exception.AlreadyRegisteredException; +import com.tutorialsejong.courseregistration.common.exception.BadRequestException; +import com.tutorialsejong.courseregistration.common.exception.NotFoundException; import com.tutorialsejong.courseregistration.registration.repository.CourseRegistrationRepository; import com.tutorialsejong.courseregistration.schedule.entity.Schedule; import com.tutorialsejong.courseregistration.schedule.repository.ScheduleRepository; @@ -41,7 +43,7 @@ public void saveWishList(String studentId, List wishListIdList) { .collect(Collectors.toList()); if (wishList.isEmpty()) { - throw new CheckUserException("이미 신청된 관심과목이거나 수강신청된 과목이 포함되어있습니다."); + throw new AlreadyRegisteredException("이미 신청된 관심과목이거나 수강신청된 과목이 포함되어있습니다."); } wishListRepository.saveAll(wishList); @@ -60,12 +62,12 @@ public List getWishList(String studentId) { public User checkExistUser(String studentId) { return userRepository.findByStudentId(studentId) - .orElseThrow(() -> new CheckUserException(studentId + "회원이 존재하지 않습니다.")); + .orElseThrow(() -> new NotFoundException(studentId + "회원이 존재하지 않습니다.")); } public Schedule checkExistSchedule(Long scheduleId) { return scheduleRepository.findById(scheduleId) - .orElseThrow(() -> new CheckUserException(scheduleId + "과목이 존재하지않습니다.")); + .orElseThrow(() -> new NotFoundException(scheduleId + "과목이 존재하지않습니다.")); } public void deleteWishList(String studentId, Long scheduleId) { @@ -73,7 +75,7 @@ public void deleteWishList(String studentId, Long scheduleId) { Schedule schedule = checkExistSchedule(scheduleId); WishList wishList = wishListRepository.findByStudentIdAndScheduleId(user, schedule) - .orElseThrow(() -> new CheckUserException("신청하지 않은 과목입니다.")); + .orElseThrow(() -> new BadRequestException("신청하지 않은 과목입니다.")); wishListRepository.delete(wishList); } From 13b7cb51c2c2559a96e442e26214a0075bad3f03 Mon Sep 17 00:00:00 2001 From: Anhye0n Date: Tue, 6 Aug 2024 02:00:46 +0900 Subject: [PATCH 07/28] chore: submodule update --- src/main/resources/config | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/resources/config b/src/main/resources/config index a732a59..5fe90fd 160000 --- a/src/main/resources/config +++ b/src/main/resources/config @@ -1 +1 @@ -Subproject commit a732a59a2346f181d34ce4566040e8c6485bfd3a +Subproject commit 5fe90fdd91fd6e9b767ba8e569c7e8636a92d770 From bc42dcb3b47419c6169d26557b0b3061f9600ee3 Mon Sep 17 00:00:00 2001 From: Anhye0n Date: Tue, 6 Aug 2024 02:09:45 +0900 Subject: [PATCH 08/28] chore: submodule update --- src/main/resources/config | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/resources/config b/src/main/resources/config index 5fe90fd..8aada45 160000 --- a/src/main/resources/config +++ b/src/main/resources/config @@ -1 +1 @@ -Subproject commit 5fe90fdd91fd6e9b767ba8e569c7e8636a92d770 +Subproject commit 8aada456da70229b1736078412e7dcb154ad1ff2 From 87353db9a23ac39a5fb905145ad07c2d1173da74 Mon Sep 17 00:00:00 2001 From: Anhye0n <49294599+Anhye0n@users.noreply.github.com> Date: Fri, 9 Aug 2024 06:13:49 +0900 Subject: [PATCH 09/28] =?UTF-8?q?feat:=20=EC=9C=A0=EC=A0=80=EC=A0=95?= =?UTF-8?q?=EB=B3=B4=20=EC=A0=9C=EA=B1=B0=20=EB=A1=9C=EC=A7=81=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20(#65)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../auth/JwtAuthenticationFilter.java | 2 +- .../auth/controller/AuthController.java | 15 +++++++------ .../auth/service/AuthService.java | 21 ++++++++++++++++++- .../common/config/SecurityConfig.java | 3 ++- .../CourseRegistrationRepository.java | 5 +++++ .../service/CourseRegistrationService.java | 4 ++++ .../user/repository/UserRepository.java | 8 ++++++- .../repository/WishListRepository.java | 5 +++++ .../wishlist/service/WishListService.java | 4 ++++ 9 files changed, 57 insertions(+), 10 deletions(-) diff --git a/src/main/java/com/tutorialsejong/courseregistration/auth/JwtAuthenticationFilter.java b/src/main/java/com/tutorialsejong/courseregistration/auth/JwtAuthenticationFilter.java index 683946e..382e558 100644 --- a/src/main/java/com/tutorialsejong/courseregistration/auth/JwtAuthenticationFilter.java +++ b/src/main/java/com/tutorialsejong/courseregistration/auth/JwtAuthenticationFilter.java @@ -71,6 +71,6 @@ private String getJwtFromRequest(HttpServletRequest request) { @Override protected boolean shouldNotFilter(HttpServletRequest request) { String path = request.getRequestURI(); - return path.equals("/api/auth/login") || path.equals("/api/auth/refresh"); + return path.equals("/api/auth/login") || path.equals("/api/auth/refresh") || path.equals("/api/auth/withdraw"); } } diff --git a/src/main/java/com/tutorialsejong/courseregistration/auth/controller/AuthController.java b/src/main/java/com/tutorialsejong/courseregistration/auth/controller/AuthController.java index 5c74702..304ffb2 100644 --- a/src/main/java/com/tutorialsejong/courseregistration/auth/controller/AuthController.java +++ b/src/main/java/com/tutorialsejong/courseregistration/auth/controller/AuthController.java @@ -19,12 +19,7 @@ import org.springframework.http.HttpStatus; import org.springframework.http.ResponseCookie; import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.CookieValue; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.bind.annotation.*; @RestController @RequestMapping("/api/auth") @@ -70,6 +65,14 @@ public ResponseEntity refreshToken(@CookieValue(name = "refreshToken", requir return ResponseEntity.ok().body(body); } + @DeleteMapping("/withdrawal/{studentId}") + public ResponseEntity withdrawal(@PathVariable("studentId") String studentId) { + + authService.withdrawalUser(studentId); + + return ResponseEntity.ok().build(); + } + @GetMapping("/macro") public ResponseEntity verificationCodes() { MacroResponse body = createMacroResponse(); diff --git a/src/main/java/com/tutorialsejong/courseregistration/auth/service/AuthService.java b/src/main/java/com/tutorialsejong/courseregistration/auth/service/AuthService.java index 5fbc1fc..750e1f6 100644 --- a/src/main/java/com/tutorialsejong/courseregistration/auth/service/AuthService.java +++ b/src/main/java/com/tutorialsejong/courseregistration/auth/service/AuthService.java @@ -4,13 +4,17 @@ import com.tutorialsejong.courseregistration.auth.dto.AuthenticationResult; import com.tutorialsejong.courseregistration.auth.dto.JwtTokens; import com.tutorialsejong.courseregistration.auth.dto.LoginRequest; +import com.tutorialsejong.courseregistration.registration.service.CourseRegistrationService; import com.tutorialsejong.courseregistration.user.entity.User; import com.tutorialsejong.courseregistration.user.repository.InvalidRefreshTokenException; import com.tutorialsejong.courseregistration.user.repository.UserRepository; +import com.tutorialsejong.courseregistration.wishlist.service.WishListService; +import jakarta.transaction.Transactional; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.parameters.P; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; @@ -22,14 +26,21 @@ public class AuthService { private final UserRepository userRepository; private final PasswordEncoder passwordEncoder; + private final WishListService wishListService; + + private final CourseRegistrationService courseRegistrationService; + public AuthService(AuthenticationManager authenticationManager, JwtTokenProvider tokenProvider, UserRepository userRepository, - PasswordEncoder passwordEncoder) { + PasswordEncoder passwordEncoder, WishListService wishListService, CourseRegistrationService courseRegistrationService) { this.authenticationManager = authenticationManager; this.tokenProvider = tokenProvider; this.userRepository = userRepository; this.passwordEncoder = passwordEncoder; + + this.wishListService = wishListService; + this.courseRegistrationService = courseRegistrationService; } public AuthenticationResult loginOrSignup(LoginRequest loginRequest) { @@ -87,4 +98,12 @@ public JwtTokens refreshAccessToken(String refreshToken) { String newAccessToken = tokenProvider.generateAccessTokenFromUsername(username); return new JwtTokens(newAccessToken, refreshToken); } + + @Transactional + public void withdrawalUser(String studentId) { + + wishListService.deleteWishListsByStudent(studentId); + courseRegistrationService.deleteCourseRegistrationsByStudent(studentId); + userRepository.deleteByStudentId(studentId); + } } diff --git a/src/main/java/com/tutorialsejong/courseregistration/common/config/SecurityConfig.java b/src/main/java/com/tutorialsejong/courseregistration/common/config/SecurityConfig.java index 07a52c4..4f6e996 100644 --- a/src/main/java/com/tutorialsejong/courseregistration/common/config/SecurityConfig.java +++ b/src/main/java/com/tutorialsejong/courseregistration/common/config/SecurityConfig.java @@ -56,7 +56,8 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { .authorizeHttpRequests(auth -> auth .requestMatchers( "/api/auth/login", - "/api/auth/refresh" + "/api/auth/refresh", + "/api/auth/withdrawal/**" ).permitAll() .anyRequest().authenticated() ) diff --git a/src/main/java/com/tutorialsejong/courseregistration/registration/repository/CourseRegistrationRepository.java b/src/main/java/com/tutorialsejong/courseregistration/registration/repository/CourseRegistrationRepository.java index fa859b4..8d5b057 100644 --- a/src/main/java/com/tutorialsejong/courseregistration/registration/repository/CourseRegistrationRepository.java +++ b/src/main/java/com/tutorialsejong/courseregistration/registration/repository/CourseRegistrationRepository.java @@ -6,6 +6,7 @@ import java.util.List; import java.util.Optional; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Modifying; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; @@ -20,4 +21,8 @@ public interface CourseRegistrationRepository extends JpaRepository findCourseRegistrationResponsesByStudentId(@Param("studentId") String studentId); + + @Modifying + @Query("DELETE FROM CourseRegistration cr WHERE cr.student.studentId = :studentId") + void deleteByStudentId(String studentId); } diff --git a/src/main/java/com/tutorialsejong/courseregistration/registration/service/CourseRegistrationService.java b/src/main/java/com/tutorialsejong/courseregistration/registration/service/CourseRegistrationService.java index 663779d..e5db38c 100644 --- a/src/main/java/com/tutorialsejong/courseregistration/registration/service/CourseRegistrationService.java +++ b/src/main/java/com/tutorialsejong/courseregistration/registration/service/CourseRegistrationService.java @@ -83,4 +83,8 @@ private CourseRegistrationResponse convertToDto(CourseRegistration registration) registration.getSchedule().getScheduleId() ); } + + public void deleteCourseRegistrationsByStudent(String studentId) { + courseRegistrationRepository.deleteByStudentId(studentId); + } } diff --git a/src/main/java/com/tutorialsejong/courseregistration/user/repository/UserRepository.java b/src/main/java/com/tutorialsejong/courseregistration/user/repository/UserRepository.java index 87a945d..4c41545 100644 --- a/src/main/java/com/tutorialsejong/courseregistration/user/repository/UserRepository.java +++ b/src/main/java/com/tutorialsejong/courseregistration/user/repository/UserRepository.java @@ -2,9 +2,15 @@ import com.tutorialsejong.courseregistration.user.entity.User; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Modifying; +import org.springframework.data.jpa.repository.Query; import java.util.Optional; -public interface UserRepository extends JpaRepository { +public interface UserRepository extends JpaRepository { Optional findByStudentId(String studentId); + + @Modifying + @Query("DELETE FROM User u WHERE u.studentId = :studentId") + void deleteByStudentId(String studentId); } diff --git a/src/main/java/com/tutorialsejong/courseregistration/wishlist/repository/WishListRepository.java b/src/main/java/com/tutorialsejong/courseregistration/wishlist/repository/WishListRepository.java index 3a56cd6..8e94cf1 100644 --- a/src/main/java/com/tutorialsejong/courseregistration/wishlist/repository/WishListRepository.java +++ b/src/main/java/com/tutorialsejong/courseregistration/wishlist/repository/WishListRepository.java @@ -6,6 +6,8 @@ import java.util.List; import java.util.Optional; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Modifying; +import org.springframework.data.jpa.repository.Query; public interface WishListRepository extends JpaRepository { @@ -15,4 +17,7 @@ public interface WishListRepository extends JpaRepository { boolean existsByStudentIdAndScheduleId(User studentId, Schedule scheduleId); + @Modifying + @Query("DELETE FROM WishList w WHERE w.studentId.studentId = :studentId") + void deleteByStudentId(String studentId); } diff --git a/src/main/java/com/tutorialsejong/courseregistration/wishlist/service/WishListService.java b/src/main/java/com/tutorialsejong/courseregistration/wishlist/service/WishListService.java index 5adc057..25e7198 100644 --- a/src/main/java/com/tutorialsejong/courseregistration/wishlist/service/WishListService.java +++ b/src/main/java/com/tutorialsejong/courseregistration/wishlist/service/WishListService.java @@ -79,4 +79,8 @@ public void deleteWishList(String studentId, Long scheduleId) { wishListRepository.delete(wishList); } + + public void deleteWishListsByStudent(String studentId) { + wishListRepository.deleteByStudentId(studentId); + } } From f1b4ffe946696279b7cbcb3427c5aff75866fd1c Mon Sep 17 00:00:00 2001 From: zhy2on <52701529+zhy2on@users.noreply.github.com> Date: Fri, 9 Aug 2024 18:47:23 +0900 Subject: [PATCH 10/28] =?UTF-8?q?feat:=20=EB=8F=99=EC=9D=BC=ED=95=9C=20?= =?UTF-8?q?=ED=95=99=EC=88=98=EB=B2=88=ED=98=B8=20=EC=98=88=EC=99=B8?= =?UTF-8?q?=EC=B2=98=EB=A6=AC=20(#70)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: 관심과목 담기 동일한 학수번호 예외처리 및 리팩토링 - 관심과목 목록에 동일한 학수번호로 신청된 정보가 존재하면 예외처리를 하였습니다. - body에 등록할 과목을 리스트로 받던 것을 과목 하나씩만 입력받도록 변경했습니다. * feat: 수강신청 동일한 학수번호 예외 처리 - 동일한 학수번호로 수강신청된 과목이 있는 경우도 예외처리 하였습니다. --- .../service/CourseRegistrationService.java | 18 ++++++++---- .../controller/WishListController.java | 8 ++--- .../wishlist/dto/WishListRequest.java | 6 ++-- .../wishlist/service/WishListService.java | 29 ++++++++++++------- 4 files changed, 37 insertions(+), 24 deletions(-) diff --git a/src/main/java/com/tutorialsejong/courseregistration/registration/service/CourseRegistrationService.java b/src/main/java/com/tutorialsejong/courseregistration/registration/service/CourseRegistrationService.java index e5db38c..6161af5 100644 --- a/src/main/java/com/tutorialsejong/courseregistration/registration/service/CourseRegistrationService.java +++ b/src/main/java/com/tutorialsejong/courseregistration/registration/service/CourseRegistrationService.java @@ -33,12 +33,20 @@ public CourseRegistrationService(CourseRegistrationRepository courseRegistration @Transactional public CourseRegistrationResponse registerCourse(String studentId, Long scheduleId) { - try { - User student = userRepository.findByStudentId(studentId) - .orElseThrow(() -> new NotFoundException("User not found with id: " + studentId)); - Schedule schedule = scheduleRepository.findById(scheduleId) - .orElseThrow(() -> new NotFoundException("Schedule not found with id: " + scheduleId)); + User student = userRepository.findByStudentId(studentId) + .orElseThrow(() -> new NotFoundException("User not found with id: " + studentId)); + Schedule schedule = scheduleRepository.findById(scheduleId) + .orElseThrow(() -> new NotFoundException("Schedule not found with id: " + scheduleId)); + + boolean alreadyRegistered = courseRegistrationRepository.findAllByStudent(student) + .stream() + .anyMatch(registration -> registration.getSchedule().getCuriNo().equals(schedule.getCuriNo())); + if (alreadyRegistered) { + throw new AlreadyRegisteredException("Course already registered"); + } + + try { CourseRegistration registration = new CourseRegistration(student, schedule, LocalDateTime.now()); registration = courseRegistrationRepository.save(registration); return convertToDto(registration); diff --git a/src/main/java/com/tutorialsejong/courseregistration/wishlist/controller/WishListController.java b/src/main/java/com/tutorialsejong/courseregistration/wishlist/controller/WishListController.java index b9cbe31..627fd5f 100644 --- a/src/main/java/com/tutorialsejong/courseregistration/wishlist/controller/WishListController.java +++ b/src/main/java/com/tutorialsejong/courseregistration/wishlist/controller/WishListController.java @@ -23,8 +23,8 @@ public WishListController(WishListService wishListService) { @PostMapping("/save") - public ResponseEntity saveWishList(@RequestBody WishListRequest wishListRequest) { - wishListService.saveWishList(wishListRequest.studentId(), wishListRequest.wishListIdList()); + public ResponseEntity saveWishListItem(@RequestBody WishListRequest wishListRequest) { + wishListService.saveWishListItem(wishListRequest.studentId(), wishListRequest.scheduleId()); return ResponseEntity.status(HttpStatus.CREATED).body("관심과목이 저장되었습니다."); } @@ -37,8 +37,8 @@ public ResponseEntity getWishList(@RequestParam String studentId) { } @DeleteMapping - public ResponseEntity deleteWishList(@RequestParam String studentId, @RequestParam Long scheduleId) { - wishListService.deleteWishList(studentId, scheduleId); + public ResponseEntity deleteWishListItem(@RequestParam String studentId, @RequestParam Long scheduleId) { + wishListService.deleteWishListItem(studentId, scheduleId); return ResponseEntity.status(HttpStatus.OK).body("관심과목이 삭제되었습니다."); } } diff --git a/src/main/java/com/tutorialsejong/courseregistration/wishlist/dto/WishListRequest.java b/src/main/java/com/tutorialsejong/courseregistration/wishlist/dto/WishListRequest.java index 99f83b3..203902a 100644 --- a/src/main/java/com/tutorialsejong/courseregistration/wishlist/dto/WishListRequest.java +++ b/src/main/java/com/tutorialsejong/courseregistration/wishlist/dto/WishListRequest.java @@ -1,10 +1,8 @@ package com.tutorialsejong.courseregistration.wishlist.dto; -import java.util.List; - public record WishListRequest( String studentId, - List wishListIdList + Long scheduleId ) { -} \ No newline at end of file +} diff --git a/src/main/java/com/tutorialsejong/courseregistration/wishlist/service/WishListService.java b/src/main/java/com/tutorialsejong/courseregistration/wishlist/service/WishListService.java index 25e7198..afcf419 100644 --- a/src/main/java/com/tutorialsejong/courseregistration/wishlist/service/WishListService.java +++ b/src/main/java/com/tutorialsejong/courseregistration/wishlist/service/WishListService.java @@ -32,21 +32,28 @@ public WishListService(WishListRepository wishListRepository, this.courseRegistrationRepository = courseRegistrationRepository; } - public void saveWishList(String studentId, List wishListIdList) { + public void saveWishListItem(String studentId, Long scheduleId) { User user = checkExistUser(studentId); + Schedule schedule = checkExistSchedule(scheduleId); - List wishList = wishListIdList.stream() - .map(this::checkExistSchedule) - .filter(schedule -> !wishListRepository.existsByStudentIdAndScheduleId(user, schedule)) // 이미 등록된 관심과목 제외 - .filter(schedule -> !courseRegistrationRepository.existsByStudentStudentIdAndScheduleScheduleId(studentId, schedule.getScheduleId())) // 수강신청된 과목 제외 - .map(schedule -> new WishList(user, schedule)) - .collect(Collectors.toList()); + String curiNo = schedule.getCuriNo(); + + boolean existsInWishList = wishListRepository.findAllByStudentId(user).stream() + .anyMatch(wishList -> wishList.getScheduleId().getCuriNo().equals(curiNo)); + + if (existsInWishList) { + throw new AlreadyRegisteredException("이미 관심과목 담기된 과목입니다."); + } + + boolean existsInRegistration = courseRegistrationRepository + .existsByStudentStudentIdAndScheduleScheduleId(studentId, scheduleId); - if (wishList.isEmpty()) { - throw new AlreadyRegisteredException("이미 신청된 관심과목이거나 수강신청된 과목이 포함되어있습니다."); + if (existsInRegistration) { + throw new AlreadyRegisteredException("이미 수강신청된 과목입니다"); } - wishListRepository.saveAll(wishList); + WishList newWishList = new WishList(user, schedule); + wishListRepository.save(newWishList); } public List getWishList(String studentId) { @@ -70,7 +77,7 @@ public Schedule checkExistSchedule(Long scheduleId) { .orElseThrow(() -> new NotFoundException(scheduleId + "과목이 존재하지않습니다.")); } - public void deleteWishList(String studentId, Long scheduleId) { + public void deleteWishListItem(String studentId, Long scheduleId) { User user = checkExistUser(studentId); Schedule schedule = checkExistSchedule(scheduleId); From 68217a873abd2ed628113b25af43f4fb42941c16 Mon Sep 17 00:00:00 2001 From: Anhye0n <49294599+Anhye0n@users.noreply.github.com> Date: Fri, 9 Aug 2024 19:16:08 +0900 Subject: [PATCH 11/28] =?UTF-8?q?refactor:=20=EB=A7=A4=ED=81=AC=EB=A1=9C?= =?UTF-8?q?=20=EC=BD=94=EB=93=9C=20=EC=97=85=EB=8D=B0=EC=9D=B4=ED=8A=B8=20?= =?UTF-8?q?(#72)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../auth/controller/AuthController.java | 11 ++++++++--- src/main/resources/static/macro/10.jpg | Bin 10511 -> 11548 bytes src/main/resources/static/macro/11.jpg | Bin 10348 -> 11582 bytes src/main/resources/static/macro/12.jpg | Bin 10532 -> 13397 bytes src/main/resources/static/macro/13.jpg | Bin 11893 -> 13459 bytes src/main/resources/static/macro/14.jpg | Bin 10567 -> 13017 bytes src/main/resources/static/macro/15.jpg | Bin 11393 -> 13668 bytes src/main/resources/static/macro/16.jpg | Bin 10550 -> 11356 bytes src/main/resources/static/macro/17.jpg | Bin 10495 -> 12288 bytes src/main/resources/static/macro/18.jpg | Bin 11181 -> 13499 bytes src/main/resources/static/macro/19.jpg | Bin 12271 -> 14045 bytes src/main/resources/static/macro/20.jpg | Bin 10612 -> 12604 bytes src/main/resources/static/macro/21.jpg | Bin 0 -> 12957 bytes src/main/resources/static/macro/22.jpg | Bin 0 -> 13774 bytes src/main/resources/static/macro/23.jpg | Bin 0 -> 11626 bytes src/main/resources/static/macro/24.jpg | Bin 0 -> 16387 bytes src/main/resources/static/macro/25.jpg | Bin 0 -> 16663 bytes src/main/resources/static/macro/26.jpg | Bin 0 -> 14890 bytes src/main/resources/static/macro/27.jpg | Bin 0 -> 15730 bytes src/main/resources/static/macro/28.jpg | Bin 0 -> 16162 bytes src/main/resources/static/macro/29.jpg | Bin 0 -> 14443 bytes src/main/resources/static/macro/30.jpg | Bin 0 -> 17230 bytes src/main/resources/static/macro/6.jpg | Bin 11509 -> 11980 bytes src/main/resources/static/macro/7.jpg | Bin 9954 -> 10525 bytes src/main/resources/static/macro/8.jpg | Bin 12028 -> 13052 bytes 25 files changed, 8 insertions(+), 3 deletions(-) create mode 100644 src/main/resources/static/macro/21.jpg create mode 100644 src/main/resources/static/macro/22.jpg create mode 100644 src/main/resources/static/macro/23.jpg create mode 100644 src/main/resources/static/macro/24.jpg create mode 100644 src/main/resources/static/macro/25.jpg create mode 100644 src/main/resources/static/macro/26.jpg create mode 100644 src/main/resources/static/macro/27.jpg create mode 100644 src/main/resources/static/macro/28.jpg create mode 100644 src/main/resources/static/macro/29.jpg create mode 100644 src/main/resources/static/macro/30.jpg diff --git a/src/main/java/com/tutorialsejong/courseregistration/auth/controller/AuthController.java b/src/main/java/com/tutorialsejong/courseregistration/auth/controller/AuthController.java index 304ffb2..b9cbaaf 100644 --- a/src/main/java/com/tutorialsejong/courseregistration/auth/controller/AuthController.java +++ b/src/main/java/com/tutorialsejong/courseregistration/auth/controller/AuthController.java @@ -8,12 +8,14 @@ import com.tutorialsejong.courseregistration.auth.service.AuthService; import jakarta.servlet.http.HttpServletResponse; import jakarta.validation.Valid; + import java.time.Duration; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Random; + import org.springframework.beans.factory.annotation.Value; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; @@ -27,9 +29,12 @@ public class AuthController { private static final String REFRESH_TOKEN_COOKIE_NAME = "refreshToken"; private static final String COOKIE_PATH = "/"; private static final List MACRO_ANSWERS = Arrays.asList( - 1208, 2154, 2509, 2857, 3086, 3458, 3511, 3803, - 4613, 4139, 5106, 5802, 5648, 6352, 7086, 7414, - 8415, 8594, 9468, 9102 + 1208, 2154, 2509, 2857, 3086, + 3458, 3511, 3803, 4613, 4139, + 5106, 5802, 5648, 6352, 7086, + 7414, 8415, 8594, 9468, 9102, + 1146, 1452, 2117, 3964, 4586, + 5148, 5549, 6180, 7597, 9383 ); private final AuthService authService; diff --git a/src/main/resources/static/macro/10.jpg b/src/main/resources/static/macro/10.jpg index 697b15e7fea5f21b46ff602fa1fc0fdccbb8ca65..7f5a9765dfe35adff949712fa4de4c4cd74e2799 100644 GIT binary patch literal 11548 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 literal 10511 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 E2j%JsivR!s diff --git a/src/main/resources/static/macro/12.jpg b/src/main/resources/static/macro/12.jpg index e2cdf0b5bf63460b40f790208ded95743f703580..025c7499e1f80734ff4d137f79ad83e7ebb795de 100644 GIT binary patch literal 13397 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 literal 10532 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 diff --git a/src/main/resources/static/macro/13.jpg b/src/main/resources/static/macro/13.jpg index 0ee70dd669a5c304f524b89db38a95d7a5f5b34d..e934b8131fcf6caa7325d8a2a197e869512f8b32 100644 GIT binary patch literal 13459 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 X-)}mQ6J>HTaH7ogf3-l7S`7R@ut$Lb literal 11893 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 G|NjFxs6%A{ diff --git a/src/main/resources/static/macro/14.jpg b/src/main/resources/static/macro/14.jpg index 067acc6da404e44a817e716bba1e0685594757a7..7515abbbd1375a45f795d6d3ffc92057ece1fec0 100644 GIT binary patch literal 13017 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

I-{^Tp&PnvMx#ZN5CdLpo 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 ZR~PbmAoyre38Gf|KW2c>9FZBr{{yor=yL!7 literal 10567 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 D{9okg diff --git a/src/main/resources/static/macro/15.jpg b/src/main/resources/static/macro/15.jpg index 2baaa5e7f71885e4340bbd777f93521f97c93174..f2428d953363bdc0b0c526cbf4f56529d8fc760f 100644 GIT binary patch literal 13668 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 literal 11393 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�PC83iu5(%4<$c?kJ8K$EY&4Y`ww+Z! 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 D>Wn|R literal 10495 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 diff --git a/src/main/resources/static/macro/18.jpg b/src/main/resources/static/macro/18.jpg index f960174d66c127ec1d2510da7bf30d63b8d06168..f02a636d6822ce934e802cc1bd062662ee84269f 100644 GIT binary patch literal 13499 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 AQ~&?~ literal 11181 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 diff --git a/src/main/resources/static/macro/19.jpg b/src/main/resources/static/macro/19.jpg index 6df6bd5e8c541319c3584e5c636d171609c731b3..451321d2a540189cc0ffc12f3de6f23165d81dce 100644 GIT binary patch literal 14045 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 literal 12271 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 diff --git a/src/main/resources/static/macro/20.jpg b/src/main/resources/static/macro/20.jpg index 5bc9b76130e2fc58b7655886ea2c68eec7798872..c4e6fecb9b274b03014858d1de25adec8454ddf4 100644 GIT binary patch literal 12604 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 literal 10612 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 literal 0 HcmV?d00001 diff --git a/src/main/resources/static/macro/22.jpg b/src/main/resources/static/macro/22.jpg new file mode 100644 index 0000000000000000000000000000000000000000..49f98210e9a0bfb589d48bdab036a0dfab85745b GIT binary patch literal 13774 zcmeHucRZEh`~Nu^2NjNvlI z;>Z906R=+gLlPas!?(tRqhZI8a6BY@zX3)EV8UT|-~@o;#xXb{f`|Ygk9fZaMudBN z(uRapSrYe5l0+#b`=OxQNuAfY(1UbIFmRT}(E;130~LXiIXRgtYX=7Y*Dnw<)*fIC+<~Ko zOtUg~U=J+#1NE@jaAx~!y3C%QoR!T3C69pPyqF}9qwc|C$b?A2L@QIkArN$ITpXzg zT>mc&2nD$B0n|9)xahz#LeU`}16ic-AB$+2X7;U2L+TyM#0DrXK4>8GIYA}_2Xra~ z4_zm$1rMzq&<+9y-~#-85Mtc(z$n0k146BbqH!#cUm=DR(?efk;{@<^sGfg%<@=mo2PAgF>R-?(UQg?N*hj?E^(aY_ znRz!daa#hC$B)%4LaJC{YiazMIXf11Iv{Qq~`=g^?A-^Pw*V^v~%)62a&3urE;(j*LseRSyIpOXW{Dp=??BOf=RCN*)1sBr zxpiw>O?^kCe3w1T%=L~}^=rqCqKl>#69CmwV)#++ROj$n#MhT@EBLXlJR_sa?JMjQ zA{7$*I$e}m=9x_QdBEmiMbNS-S1TO8!a}z+HQh77--Zi4D0iV88tQi>otRU3E^HSr z61D51yf*W;%-+!_90Y~6qN2b=Y3AQ_D#~i4Gp5U!70S%z_xr0F2x)23l~y|u(=g>u z9V!9lYPQzO2SO4}7frLAhdLLe*8{LIb`Wb0S2OY63ndj6PH+OwvcIW7E6^hLdp9#l zI@aD~S4gapZG4YHNV171*R~?JzCUdO=yM0Cf=xWFQS%(q-)m#)3$b4zRiGy>Q7v19 z5Z1QIe!_PmM52E6?Mg@3Z?0Wn2Q_tRA2B}+^$uq z`vfaM-0va}&NO#U0Hvf&l~OXcwDc&IX$;sT&rTCK)xgEOt__0N-Bor@)6^`{llB}?F z#6B$a)t1%dyS&MJYrL1Sb_J}$S9ipB6TY&8#l0x2#nqOfx5vd(3uMYNRtT={0hvSn z_N0?@27zJIsgA0V$_i0at!pBez={GL16S9A9T8O^J=z< z(i&gGR?5RgL0S8H3%pM!Jtg%<`dmUX*Nm4@wrmhFp3&Rr%%Y;KH_ii@?+lgH&EaGq67N~DqE7V+R zNeJqirk~8r!L~cbP{m9o0Yb2$VmTn-V`G~SYG3*II~0A^7&hSmjFCNR`r_HR%-ry? z{#<^G?9z9g9Z+NXr{FNyamrvpIB5fQa#rpTlc_CF=e(=jj(wM-rd2QKL7UQ(&}H(I z-fDAVa-N~{?Ns>`!v~zWKPO*iYPRWp1MX=;tjbyBM^ z`&wcDOi^q7%8!N}B^cg-_Pe5rPJrTXLxZ~b{LgQJ+(KhzNgB2BKnnVIAj-!Bi}>wK ztb*EB_z<`EELZg$AFDr0aD$P#ELY~NXySZ`H3PT=nR{=(ssmWKFLOnNm`r_nI&*~H zU{e%tV@tZYDD%V4HgNDtxcWdenB?C`vTPAR*sSNEl%+ZrqBcbx%Olm)_vl|tKO4kb zw;gbC`P!4yvN8*UFErmVU6DU>K6c`u&!4q&i`TxQl-1Na6Oi(ALHQs6RvcSAcgQ~O zR^_rzR0eEA=^)5Q%|*Cv7y8&D`s0z_hmHU`AM|`s_!J(Tppa360!MB96}DO) z?*2A-P#hO!LOg&2*a}K}@QP&sor7snNBOuTQ24MeM8Fs*2U z0_-T!p?p=A#Hx-{AJ_r=fV^XNz~5afg8~^ z|GNeL2SeihpCSKu`yEjLL*4-f!-wM&k)6aR#>2-0AHl)bDg-b>BqPNsCXOdo`E8V^ z(bukXiz%zz)(?60eux~!%qgm>cF({)G^tkH!!!9DmxR$%>y)}-%>8yr!^EY$4~qw1 zw!lT;`>=6djk=RHC(2S8%~d$t?%L5R#%o?A9eJ_RB&q-ViLirlwm{&8mUFblR)i=@ zwwkML@8X}nlMKI{bp0)Y{PH3-+eTw(#?_t=*9@X#+ZC+p14guDyh<}dn3W2z3b5XX zMPBlq%Pgk}3ve407G-~>X&;FX!{90~NUe`LKzr1dyy?FCi zaG3H}i(Rv&mF|!6q>Ewve$=y_B#e_*aSBCwnHh{?%0?vO1S?d=#Da0HpH{<<^AfHf z^H_Nq+d}oKz|*>2^qNM@#Q78swe_1GzhIqA%ORZ_@*%1*M81vq;){)eeWx0%GAwDu zZ+^Z{WFJCVwUoW}^Gm^s-+9q1m`GBkeBQ5eX)n1y5?HAg^Il1>7&;d&V?}xUHd`a_ zOXW90(k}FmdR{RPg^$wRbQ+yoiG2HBM^?fzY1pT%qU)E=NF+-s|LMZ|Ij78Y`26w0 zw>_uRT?#BRpYWDkF^xeBX6{&iv1Eg+aK9)#>w4CgOI5KP zS3fFo5<1+lpPHHrjJ)B!?!!{>3B696nx8z*vgs01N8!=@w(U6K{G?dp?{!ka_+%kJ zuKQ0Uy4h+j{}Fz9!$}6ySx`c-HFI50-!4!)dUQhfOFNM{i>x$p_1f)Jj&CX`HV!YC z(6YHNuNdms5)r|8m)9d%mVRQYy8Bf1&Qfue1WBE38Jl&=)tFCN9oXjYz>tDDRytA4W%cakJTrZRmCl#Q*#_?CzW%>#DiR zdPzTca35B5D@#+*$LZq$g}+DYYnR63Vr>xdKG;P{ zMxK=o25U@DV+&)LOP1hHl-TnNY7lndkFp$NJ3VvhMVZl`zA%Ny82wbvBF6Xd^&h|M zD92-s^@>j`klpe+ZYlNWOZW7NK@$rT3s%#%@(rndm<@_jdoY^q^*CKkC|j}pLeQom zZ3^pGnG|9TF4bcw{79Hk<`0~Sf6IJ!%(p7!74?97nG={4bpHI-e1bYP;3=xI(bJRb z!BDO7dnR+$>dV&~?1@&T1q<4cgUoHT%ReyEZa$c&P4Yh~3AGhL}&2SJWt^pRx9e(%B1;FaN)I>%C+g3Sk>*~`kyq%H2fmq z4<%3E)=7Tz*Bl_?J4XTqhR;>vWhmDA^i{89@tp0>%=$slu!jGMW*>HCc8dE%Fw54@ z6@t4g9UmHK%Npi|PIjl(DSevsv9Ns@T3@N^!0E%c?x6jI2BXUuKUVUItTJ<}^R@r4 z3dde=Uxq|37Z=H(In7O(&K&NdGxlR&as;$#cywxZJ&3kP(JV*~?+|3V%Ik495|nQM zjN;zUvs{uTdaR~CJv6)BznrpP1oE>VI>BMe^1IOJH}fa6^WRo?j(6BKi?1Lo!kgRg zO4E^@$cR~P(|pLLPm8JG`g!$hLRxzMZk8VLjgGfP*fO5cJYM@=-pxGIR2o%T_@MWq zQ@)kuPi7}ti`#LC)MddL6Q;{rZ!KDRZ!=1a#dcw}=GvOus$j4R66yrMb<=#=^jECiiA` zMAZBkf=6^~%b%gAa4|GT)9lfe{H9P1=Rs;NozJioDY`F2zP`QzZ5h0GH|pR0;j`y5 z_ZA+zn=u!1=IuVry7s$O_9@e-VNxCaKXXCx3@$c^iN1&Q-q(DtW4=;NQ&M!Jp1+-! zxhSX#U%Vv8H<5Cl^M~JWOdLnB;(6Y?XNGNM_($DlvlX;e;U8qq{vcNOzn&h!FESz* zQ)xW2X2+J%sH!4-iyuXB{|O%9u$!*$-QA0FJX^){4Oii(;Pux3WvA?BpW8@6BL%M_u-P1CN$`i)%Y%Kr?}Y~3)KE>|HPM;o;1albQ} z@^4Rnlw5YtP#EGUXD<=TDVw%b@ctHKBsy~tV!3BzKh6SU{GZxrmXM%{7?Ltb4H{B?krD5F|d}{ zw%PNshBEnx%j@I9lqXk{C3q6+_g=juVGxY{w(j#rvqYJrUpQU(m9N-L(bpI(o>0Ne8`}jvaMc4VUs2Q_<29SmD#Bm1I|JPVKzhrLH#sqEWd?+23OtY zHq~ai`q-qwj7p`pjstH(-k83Fvw^m7WKO%>$M2--2flPo@!;d`-m-ND#+$S18>gGj zMdxe$D2Q^$r18x^AM88Jaj%Eo7yUcTGN4R84AclC!` z+8?2Mc8ugJL|>VNppoak5-^bvuo4a;(%b$mG8e0@PxM?Gv!E4q@cML$$^=O* zU4Go)YG26JZto1Sbwg0R30ResNMO8K3ZY70j#=a=UOVvGC}|AKQrY+Llj6_G=@m4t zyy(r&B(a~0l@*>&MC0Am3!|I&m5AYIpuHuw^F;bS%%c3K{a#0|SDfh&EvVxPpSa${t!gM%5}&?V?^N!p zWcp0`$SEc*o8+YP-${G?HXu}hoZa0xz{W&pXVdCfKcILEP+Qw$J?2q%`Kj$DU zWizZuODTO*j~iyCT0E~iL9}po_0{|&Q=I*-$h$0 zlf4eWhQijL6KjfQ9eYex>e1{yBls6$Z;tLQye+zqdd|^&?^qZ(cREeO{qAS5q;K|y zQ#Z5Bk;|0H$s33c4g9$!Z?eQ}y?y>|!DdLA%c~!n&NusnznJ6;pEFM|Ea*(2;8lFD z`AG2vOcD6t)uYlwEwd5E!lgcY-h_OEb z2^hsna}}@c5+_v;?DhPxG!Dr7#QQBJr@3*rK>qlHXM}h|%?!r&+z)2^R4yg*Ssqiz z(4>?lE+BpyMjPJrl0n07D#6%9ufPx)9M~|d#jOml3hX10(!w1tJY&AxvcG2bqh*b7 z;2Yw$nS5{lTdI*z&Da(HO1H)F3yZ@jcFS#e1#RJj)gEJ$~R*%A6P~hZ=3&8}9b6g))d0Rd7^y$`rRP+_o*xTK zB!DHtc!h5LZ5i&4ebKy3L=_dxp^iTb6Q5Ld)x515cqXa$GCSMnTdi{u;i!?+N!un_ zzbEah0!FEld{d5e@7tqett&~pP2?4B==wY=c$-FdRia2!RW&dYFtTM}8N+@;j9<3J z45p-#ojA^)F;3o8)uiF^U3z~d-1;87yZnn=YpgV?bUn~sAMxq5VHSQbU38{{vFpQMj$!_Tho$we|jd|T0L%UJa zXuo%qXjYaBN+V*jXFe$rr^nDBp1{}xEy5zd#y?r(5h*kqx^VX*sE?ieRt~74UOT;~ z>6D3DD%d1+=yH|RNyn3sHPf)97&FOisbpfKs?E}tRIcHzQi~Thtwrke)QCHOMqEmH z8vae?3%-eD8e)!thWn;A-Kd5ZVZ%?@bO!_Ky_aZr?T;ji?rf&4+x^$Z$y9N5*`uHX z+8SO6QJ^cf*|_1ZNl@g=;kR88Q^4I$%p;EGXEc|*crYrRqB4e6ar!l{ z2RwSEFY&xY@52=bLFcKhy7EG=iLnYQJgejfYznHh5k~MbP2;yW6?sp-LbFQ5AUXTl zOp-O7TpEHWxC+*{UobVBm@I8jNUYpa3@Uj=J06js_XNfN{jzY?FzeH3O_yg4N`P={ z+cyai{@FlE#J2xfnMeEZr;=Gde5p~!FM8trIVI7nrXC5(%-Ch=Kh)oJ5zRT)6LHrw zB5UJ|L{IvdPE&0i&uSdM+VC{r-Jh`|H@^^cJW5iR6k}?#7gvgE_KZR4DZT2--mCp^ zi@-Qc`nq7 z(|NrWM>wb=XRZ*|e~(mAut2-dab5g#yR0f%8ruvIm}KRZf4-uKjukMUS=>5wkH*)f zx`l5ZZ#Yg#4Uq^`bycNM{gz@7Q`Y<4aPolJ-Z@f(sx|(KKs|f%V!Oj?JDo$2T-uXt4CS{UYpB>T}t|QkIWpn}t-~rrf5F{kGN{BNR;yg@?RX)ZJ1igB1UEVey3;8lMx(%G?y!LR6@{j}P8Uvwdtq-H&uawk(qPnniivRz8jN`r+<1=Z$+UqE>3 z<1_x1CQ*ICJ1Yr-|J(XB2pIb5m*bnb@RYWs(HaB;oG?b9;krn_p`lecvuBANp;RoD|=V~Uu-1n`cWtE$->f`7S8 z=;yO5c^E_u@w7yfdWjER4h}GTCjfK-)>PcS`&GCRjb;= zxxLl5IJs}<)OduKWaa6F!33@X`pc8W&+z}GCN5b>mTw}yUq<+ET^^_bLIm~N%=h3c zx5S3`{2dm_qBT#RSygRu144|eljEK7#v?o>cTp>Y3|s}&S0~dFU_eOQGf6>62%-NL z+XN(p>wcS}FnmRpR3eqw`)v|_+C@XRkJpqKc|uIKSMHbE#x2h_}Gu^7`M&>%A@y_psS>Q)k8UdUt1THkEo6==xT&t z9ZezSCFKQf#DKJZ4}kQ)26N@PBT_(hAmhI%xtPTPn?NfDBlQM@6?epA7kdN-=-CS0 zwEDI#Z~#F`hYFD6Sj3QP^;1WT7~{W}cCU|w99{rPfy*I>5^%7nu_mD#`!5x6w*p@w zd4Iv-R7pbibN)jP6v3*3Mqjdfc`y|g^Wn4rIuid#j`3fN#&&?j<4_c{IB+^+E0)!> z>637H0B;C!B|-NGxpBx0mBz&AYS+{hHVc3ElCY@PK^d;=~kg zshzfRqbqQsFe2}1qXPp=ePsXrHH*RFv2hX7iHt=eU*8v;QB)2OgZ$k3)>zO){tr?` zb=}Kd`p%c}rjjqxxsNS+$ZRqkw^jpFUAo*+ehU( zg3)z&!4A&%--_;)nvs-rXO4e*0uXJH{_u?Q1R}!BPM}|5@9XFZa%)Re2}k zeaHk;tZ0^9&0XO@y2$;L<01iZCtH*nFc{_}dNJUN&LChbyDWTxtYZ7 z_WnM3aM5kN_S)bsns!3npt?}#pdb}6gdUN$_o*T@4-~HVFxh@59 zf7G((my=Jq?iIZ#UEeipJar9rL-*jJU{*%)>~s=CtY<}fCvWY;TzU9M9reK*5^~4C zEwqdG-+!61J->-8v9m-p?Za;V0AGsY-9s~Dkf#Kg5!q_X<8F@|44Ft2A2%%^hXw37 zDA3F^?xp8!(fVgs^H>wO2sb}$e|<`_;zVRVJoH_@<7@Q={pMe@1iuFPq~qnsx+6(8 zPqRu@!MLV_-X{F;>Ao=xCSR{cXwdK}HS+K}a|-@xtM#UF8rg@LuGD~7vK-g={yD$2 zjd~0QJLxtGr6TGWzwO*gG5u3#4cZWX=7!mpppJc*GYA@MMiMeY{l=?euIhDX$K~kt zcFD;2Ih{RUFLB#6?&^_M*#KK(oFWqmvlF&i^dVu;pbkMI&+fy#$VgeNv|Z*~eFF!# zd8Hefp2waRm=&JgL#g%I4H`V8DZB%_`_QW1)nKgySw4qP}5c)h__5CTuyTF2# zb$Ft0+O(MZxD%5N_JA%iDWl}0E?WY1a(?&0FJ_W@YkHsM4(U#HK_$gNWa~zA^&|6kVl3b#HAq|Z*h|mt-SnVu5|9aWNZf~|)9d|) zk3T+8H7SE4F0|FA;bDYxr^vXmbQ@Xxx_MBbkJ)<{JdX((^Nwo-FV2uVq>+m3!}0}% z+qwWBUS*&I{=^uiV{WZzBa#*^?eTC(7%6Wr;4Mi{=8#_} zvI+Q~c~nTbaydWUvqCeM2!C%|3R7Yq;kb|qXC&>a0y>@>JaYFrW7ICN=A@eMyAf)Njf!8muXT9{96 z*4Tn@NV4RVWt%k5bvH1yoG*XJYcI3Ao7>{w1BLAQQ^h}+y38{~!hbCp-TT2x;?Sl) zg1-;5Ts;#A=ZQcpoYuZ*8M#p8On>f8>zz%lYk#Kib)V9&0Qx|KKVfHf`^g!+NWpPl zu4FX&v!zgZ%7{sC2t6XMc8rti#|Ni|z58ja!|Q<{!u(lo;K_X*iyUM2T!=bd7N|cL zY)ce>g5*Ap4H7AM=>*<;!B=9RuF#wJ*wMu^QHfo5c(fOSbq=+CJI&kr%;ruzD$Y@Q zc$3!(ykRB5F3+=@z)ck1iiQl?*S2I`|30aBluU!?QsDHMj-G*)RQ-Bw2_qHk9$w-T zn{PYyOHK%F09(>snzj+(;Wn2v(jr4n(A-7zUZrIWIfmxLe3%e%=oa41$IeTB-`RL8 zmRU6Ay$_6Vt_j8P(BeE7vqXvTE|_e5#QzhiVtzU+T%n<)^%|=A_lD6#pds*W_0Ll%pW$g@30mXEcSOCT(fILyerr0*HCNJzrBmf?w& z>zh$K+LU2-!K5s+vFB*BN*Ol!cYbS}QF1W=kp)(GUVG+_ClB9_O%_7YHvY^jUZS{W zz~_y`HQvDz7seB?M^Jl3_kC$3iwe=t5WWNcaRIC4(gX513u&wkR%R~rS`G*nMQtdg z%Z!VH@DxXLp#@?_KKBY4SKV1`!%`uU)vK|?d@k|tmBBrE&ZQCo*5bgkBw67){fN^0 zWAwr!wN#*To`BK10Vrs+aGo=R>*QuUw{mu=udT&Q+J@?O#_rF(>1S#pnF$?=^UxdV zrZw%JK^w!yQ}sk7wh;(2^Hel*^=c3wsCLk(S&^s@`DoP1U_Y*cMxSFjmQjP?zGFsN z)xTwKdF%r$Q^%Vu>^81_>U&0ybR|QrH&+s2d3}9%Pu=@2h|)b*(;x86ln`KYDCZn| z5=%GiK6N#n53wXiK=!e3@k*Tsax`HGrzKt2V$zwxB}Ky-5Ph$`h(@m(uabw-MTJe< zC@g{MjhAS5qS;lK_2oLv_lh%l9)mU4VE4$5aWT0OU|_sD$=+fda~1nAjaBn+c~|;^ z2d^5iy7Q<;!E8RAGyR;nkC(*u z_@y~{&wD(?m(}&WJoPRKFl^K@W(RTb`!-;VTgBCaO6ICZiA#nsjj$7$zk>&-eJ1lbKKcktD(* zeW;C*^xBktl=7j&T%PlH>%`Bi{=eRyiTplE^Pyx8?|UN}ef7I@LMZ@Y=~A9DhGG`q zab;SFdqR%UdMx-*9P z+$Ht&9X|LzjQCuWe(#U#QSl2=kx!qUtIX4VTC++hMDU1OQL>u*ig7}2J%z}x@ad64 zC{hCD`kZ5=JXz#^<=6V7g3*{4q3ntR##H#yR%n5A47Pzc#)U@`cHsnRi+dMrk!~Nv z8ukUv4KnBN;9{QgX>0TiIC-#P_`5*@4K?(xuP^$uHP}cTvS67(T0;X=F{t@wF2g1) zcE^&0-4_5Pf42)6$UZFfKc@qJAOVts7D&bkz?~uJVpv^(Dq!mZ4!X8O=A%smR9e8` z#(uzDhX1bwaDBMsLvKS{hbPnvYZ8OB(!^N7KqN9L37G+PJK&T=O&vl@m_`Qu;0by3 eFHO+jz`@AF{c;Qr7lEOI{s#*1?@(BZ?*9kz#b}KH literal 0 HcmV?d00001 diff --git a/src/main/resources/static/macro/23.jpg b/src/main/resources/static/macro/23.jpg new file mode 100644 index 0000000000000000000000000000000000000000..30253054a1bb16563a0be85ef50bdcb6a8505331 GIT binary patch literal 11626 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 DJe-em literal 0 HcmV?d00001 diff --git a/src/main/resources/static/macro/24.jpg b/src/main/resources/static/macro/24.jpg new file mode 100644 index 0000000000000000000000000000000000000000..e9c998b47099559198d72cb07db032ef618d9bb1 GIT binary patch literal 16387 zcmeHubzD^4*YD66fCz|^I!FitqLj4Kox>>IU6Rrb14<(u3Q|KO-6bK7(k~6Aap`B3_`T4RuCniCK?C>cmotgHFPv=99%3+jB8ipAY9bt z6^H8#ilV4J&7WA+ESTYwC@}+Yswg(3`g^m4SgHQl>h5KnXB?iMWaEhvW#HA{duJkefP09a8362E0M~*%g2r3;^pt z{D62QyK0%6`l`ptwVA~ z$Qj>1L}>62$S5ojYLAb@e{aZ+2s-%wCZNF=6{K;0dm^Q!mBas{j5;wltLXa)fGzBR zWKRzNHzo+?f9*u_FM~KAAoN5<#h<=__T+GeaX?dKClnDtIWbiz5dBAu-?*S4zy7EO zq7DUiATWR(aRALi*zK)2mlyzKnK)@R0Hv*4yjm9KzT@P6$mK=34jpNvC$~Vb0L;m zo5)e4mTL|ug8=1!_dGy`#PByHzYpX2`TJupQ5^pR7C;VQ!;_LMMdx5gwS~%SPNpM9kkvMEVREXj{M_M{dYSM2vaWkrae|NN1{ABH=?(6F(~Z ziwe?5NOda8YL@?|2$Wi=A^l0z2*q1<2!RTUKg2TE+Nt;8H-DszDF;$b5Juz~Rq&&I zo^Ai#D^RLn!?qnu=efNlA`9(m5$+0x4KCDfZ$ngv0DV0OB04+`RW}hFPsdSqp(3P5 zap0E+2w#4chvCxw#gJzs&fzBh+Al@`gaW()w^#Uv6H_^7j6lekXVrE#Xov4ni`cpV zFpck5A%f)(NP)sTHXvJ1sb~**zTNx38b+1^84m)tfru`MS`7lZkXNk%kyqI5&Lfyr zdzox$sXBZ*IrY~l0Ovyf-CH2rZE@mM-ksvJn)OsD+5T}8#mpeIOkIWjW8|V95yo!P?Kr$g4hRoGlT{Ev{?16m(4@DcAdfI{TSH3i!3WdHAE9^mK`sO( zX5}Zbv7Qlqb@T^}6(CozhM@j-%$d#- z)lvpDY2|mA|Avfd_q1WI>eziuT*g7jam0bxfj~OY<0u)LR6XnRJq8h=2*XAecM6xN z3O9Rpx7CtbCWp%E?ND+0H?#J7&9TU?^!-rS@5900*z^f4TnXRwI4hi=Z|L?YG`#{z zA+sak@wJ&pp~748n_5&qjh`F~3_+8=p`sp{_kgIcc7;>R4|*yEGxcaF@b;CeKwlr+ zwd<3H;_TmD)GGg-+w$RieaZyVP8m=yL9}lEK1kW}Q)D0^RYHumSxth}nE{PVf0CsX zM#{x;DW<%$o6oItax1=V8WnYr zMa5P8^YNcrR1_^!obdf%RFET_2k7?b#BFAGW@h^ri)`QJS+IQ|6vxcTpLlIL2w!>~ zZ%><&qUPsC5w+`gzAKQqS#D>Yo_pV$%Wu=AyR%CItK<0qyFbB&aHJC7X-i{)=Txdl zmbN{}jzF5&enbw3;n3=&^ z;3E+B#bXg-FPf@zDB} zGvB7?Ylx+Fs7t>cwWB4(x%Qvw0Xe1$ZY=jN)<-GwKOdrQ#nIc*?%*w~scesWu}NJB zsi>ztMcZI;))JT{7~7l{hLqKl{S=OOEyy+RS^S%=kuy@Tn>;Jd;bFrO!^xC!)5-Rp zryOL$-GAPcDr94tW8tH#@e0He8piGAVqL{eAKEAC^hf!$Dt}*jIB3a*Ew8%0v|6Vd9!mkV(>O~ zX$9NO=JH}D@o3p>BvBNoUJzbJPMAS!TEoAH6;@Mn@CUX@h=D?7hcD-%*M0R{{#q(8 zPG*f@~Im8mZcBBKSV$tO`t!@(KP+UKvu8~2SDsndT@ zn~J)vozYqvCvvm>`la1zZoYdmzl%R};BE=U-n;8)osUT1<+&aN-YxO2?dDEB8Goh( z#HjPoX1(Ox7ie5)$lBTI&AoV8Ghaf?lyTNo>B@Cz=@$BKeQt*Gt2#vU!GQkJInnkI zG|nZ-Q(kW$RTmWc&gv{_?E=-I8jz#_SD_3h8PFKUo{lH>YLA%WIi zlN&|M6mnX)rU9q%Q}?gMvWley1oWtY0hA(XJE0~% zX$U$OSY}?nh!7=qoBq7+nM7Std>*w8R1~U1#tQaM?Py)@cw=Sis)%w2uuEvkV;`8& zWs##FFzZhw14XUmi6G7170CL_H^F*TaiU@)K-lhyb8wcFUR0HjoN0hm>?VI3hvg{4 z(mLVm-xx+FCIn82i0Twop#th7DxnG~_!I(G;BCB+JsL4_|H(S2gu#ehZUD6$;8BE6 zQ7>~dU-CO@&76X zDGDjb5>k$!94~wZIEOsrx1)d4B$}ZLMbvZ#NC8A3ibi11F9XV}lz#`uZ*hoK3oso+ zEY;xUg^qwNNHIub9COW`x(`rVp-Oi|WQh_Z3%TEAAV7+Iscs@n0c;r_9&%1XSpGXB z{ys&_`A9*is#+Cr3xW>lji4H`I!yv9%zk8YN0I_k8I*x($_$A$0%zb9@u>nhm;>q! zm6g>j*#i0qJBY0Tc1`_8Td|@HSakr!tSjJ501pJnfNHAa02QzvGHU=<*2Mp;sQ;|2 zkqc_{TYok-BO(Srd7-GJ`^Gln775K$4iz0c@5rc6#Z3cu9tyow*3;6Ggs0z!mky$u9T-Sn-k{olsn5w*v%;>3aqXh|*&R)>?BXc<;!X)rvE@>@0OisC_%H zvo-6Z{AgH>9%NA+3^!5Fd%75CN;|N?Aoq?jd=D=Z=PvR3i851xZXoA4sf^0w|iO>GP??0c+jc=VVx{t#S}R8iR6=|4m{Ok+>l z))c@>zkA>Bf(EOf*1X=Q_2@+}tsz6z=6Ryur{ST-{9LiG>! z`4cf#6%ux3*~O>tNGFVDbZ6%HaC%g<(Vs+kcu>fMl{ubq+5fbwn|Kk&`OxY%ZkiUn zJX`l3^QKUDs##8IN6a%WN!b=9+Cg|l!QjJ}*)QS22_($2&0K0<%^bG4TFyGV8jO-H zcSzOP?=xRd)Rbzto$4w)racQ@l7p0vx1JuS_i0cY-3v4c)XHhDLVp=&Lor7Can6Ek z>zY>{>5cLm?CaL(+~0k4s)J&ADc?V*eUDW-T;va>qUAdY`Ednmw`d$D)|U>i*yuqMWb`=Ky4Nwp__$rP zyGwh)c~3+ICZco*sk+@i|5+j;oh~5p)`~nD(>r%1`8)Khvh^@b2ZqtK++cWar=t@5 z`lH*EK^c~>a-xrmUqID;Hi$g84NAXnj=KR1N5RsEe?|XBU(-AX}p2Hb+ z-KHRW@J13_w`wZdn(Ny4ssXzoIv<$?W?9p{TN>Jll@(;x?(1}JwKRDQ`pWZny3RhQ zX3a;DJ&ZTbX){P-d06U37-#Ahgjd#@$Z`D2hDUHX;b-Bkv{yUqf*JhENmG~S5=~Nx z&|I~|&STZZ!?Ba?Ha^LliH5b3POARYYJ;D!#(uDkMDK4pnDXppaEhJ=4Sik8G&nSv zos@NY1!GLSGXi1lOr4lz*0j!%UUn^Cn&5g*W})&vW*>_^#b#vbM2i=4i{(*cIlkB}?l-$B9Y%RXO42UE@uK(nh(`yE0hT~y zmMb}IKNiVaC7SVdnZ@96QHPH=F_tf?T*c%$5zomDCY+fF+T+7U@Vy*O+lhsiUwbnz z{||@O&@<~#mV0{Q?p*!lrz}^X|L&NrTs{2}=l;#EwalLm_TxcP!@333a=oVMUh!gj zqeKA}xM`7vx79R!Vrf^gK7YFc(Jje1stRuu@yPVMSb&(lIIr0RgTZg;Aetf8iqYeA z7H$R$k5Ad1U3y*dXNn-L0;Yzl#`!Nz;|lfgeBFX-7CVCOE=d|a)BNJ^1jM?S84X^$ zffYzPYUQmdld?YP0Pe%J<1k78S`0qn0O_^Y7E!nv@6*qYMuD(HRm%e1!Ymi z(HxV7if`kj?y+ft{`IQ;H5+w=7tDLrzB7g^VRI%Yvf|O_ildS6c|V^=Ig!RY;7|OO zH<>b$MxNlGjV&jIuhy<=8C1XzP1qB6jXS7`na=9(y?sHWkaah0**=^v0m_7_f z7x#|}2c8sYRpnM*8!uuQw_Vk?lDpY7GGxRSRs3a4esAUQ&}ORJEXna0oyVoqmVt6S z^JI62@_6F?K<%yudi7)HKMi-k^p5akP9nGd{b;63Gqea{e@pc8W zw*|^GTJVN$IxGTDG=#%<#B`VY$}EUSI*pU*lO5(alQ=jQHBPyy<%dfbqfN&i7H}=d zQfe?esT4@bfy%Ef%|r-o4RN)Hd40=%kw?y__T^Cv5jEs;tAJ22TPa0)zcPE=+5W7=BxJ4Z70pSu zmX&9ku3wKJ0W`y~~;Y~GrSZmY-p+tkrbPj!HDk2BBn9!~_@umThvla@X+)=sAJz@{D-s0V}r<28~QJJ9s#h|e``YgKju)F*7%T1zcc`JxCkwiOUG#&QSbyz~ z;`#3f1rSQlLkR&6o=Ul`90=PvXQSZW@xm-@M#;e`J$6l6@&gsQ*+#5r(fm~D1q*HM zqm9W_lciL!n#^hBkVAJYK@gNn?0vwL!-qE0W~>>K>Vcxje6sB%v-H|EeV2oAe9~5z z*@VF&jfZ0dZ9P2SXIV;S<5+x1>Nt~F6Z2PdD4@poi#-{ZHIi;*>Txd04)nT*6quQ6 z1fQXicwjGx5O_b(rcwi7KP% zWnP`CH7?_e32vNT;$uytC+ROG-z{3YcbSwiC_j68rf(z7W_ed>_;FmH&4l7U^!Qs7 zlqHI`fXGD4?DF)jO2f1FsUch6(vOu0La6dz_o!xY1G7+6PfwIoPX6Q1Jb3e!mOt^% zhy*q>q$4G9%om>@{lq25#8o!pU8?C_*~5V*}?ZE zwUO#=@gW_W?D%nZZSc~!PlDM$=BKMdk4E-Q4j1RtSKVH9X0UP`(Luhdi3 zM?laQ*=Feyp`LV7a#|viIuiDbB9BTAu`<#Rx0Ovx&O4iB;C{uv*JVmB;~2BZw&-Qw zcec>uS%V=EEsl=HHPwpu5K{3Hfmj?O&w4qEjYQvX@C0I1$6rNxXhB{%rBxH#Q0xt%A1=YWr)yzfU#OORHCx(3uxl z$9HU$iU*5e(lOblO~?2@G!ios`t_zav8yI>-mA-K-QVUqBYfPgB$0WQ_Apj2u{q&s zwxqinDZhKNp_v1epoo7o%aVGK6+sfo?6#&vzuthbS7p zjwIcYUcbJpmX0=Vp9QX|N|N^DeHV2{x@g8q8q3bBA|vO!T6U|Ey7ud5S(p{d^mxX4 zer7hTmSY+VGMy13nIuZCidUe_L@wwSeOoga{XTMzx2BV$dY%#od|hIu)tyAWHT4Xb z2OP{^^_kyg5pUvTYG^H&*d`jEB4dVwx$~7G&h&YBdGbzs8WSjUCVMZ3>nbk`8*7t9}tP5G9>WDIi6C5WMh)yTSb0HCSB!&m0#PIl}K{ zLXtiB9k-W>wzf~7W|vu2ROZ*Xv~%eg_-A#=l7~J+a0sGA12;UBCajBouAOueemy( zSb6Y7*{I2QiFEse_aUCx>qd@If!Hv9fk)w=eS$?Qz_e>M!|)z8ztgA3xaAZ-y5sM1 zNUa-SLUEP}CavCBK9Bqst{uEgsYu(M>{~pv?PsrH}(F)z<_SE zS$L+qM3wlQLsGL^d`5GA;N7scb3qoFw6^fwgx!JxAMX%F`^v|p`N}y^^&GnkNy9;< zHABI3$19MW$}mUwbGFNYG^*%tp}TGF@pwl)agolCzx(XVR^KRQdnqTnHfJdO`a8s1 ztBYRuRG?h!`k=$bll&@#s6* zZ2j!P9aTKR+<{Nv5-(@CL>c@ ztb>Tk2TVnXQ{+JQhey&0JYG9VqvNT*#d?$TzmlifN3$Zkib*sVO7hp>Rosljv>Y1e z;*xGbi8-(Pq!Fevx{V9Iqc+lf`^KE;`>yqB-v@7TcOD@d1J*p{96txD3S~_NrWB{* zip9ZtnfLgg^@4}ADc0W>d*+q@Q$<*JHeGIh=X-lX^nev8{mZqwtDW_Q{2IJwzL+WA z-i#oP`HKJGXyCP$g7ib&M`=XYu0R$R_tD1D3BZ~|2`u?owK*%VRFXvr;~ETxpP7Ad&}CuLNU$X|XO~OCLh*kZD!eDpQ-0rxskmDQv66OZOP$$8G9vTzY(Gff z{i5`<{?&U))8^`PuL0Fz__1jYfU9bebL6gKnC}0%fjT? zi0k&H>8u6fFb`_;%S$2)REy=JUMp96K2o>egBi<^SF1^erD#UcCLFia&&yL z8Icz16pG8Fn-PlU3aM-bH`<+OPfKz#!sNWvcgKG{e=Afu6u*8FYfq<&iPLRFb$)D* zmud~sU8>X(9!wQ&6xiVZ_384{4q5G1hK#4%7Uo`o3Y|l1C6IM1VYP`3FPiVEEqz|Q zzM&>7%a`Ep%x%dP%w`HmQI`ww{V;#i7>^tbquY! z0{p8?5d3CRwsq{{rDuak=)fIIKzagHT1|}WQAvq*OlXGcD(wnX$vu2 zuRsmDH-E{N`V4M_unJfm$L3(C)1l3?tCMVc+cSTJ{Oqk%D5jhxt?^_@S1?NX7Qg() zRcJ&~7Qxz^bCQ3-ArR%Ms0R|dZVo=7FA_SM=_ zzFEGr$5CeshSH0U=B$0HUGmh7Ykp;XVZ#~QD-ZF>m*tyhYv``5pKxfy@hD!s-K&}t zVj9iVQ(G7F!Ee>}J*VByb4%oP}@H1sUMuQjJ)m4 zESu6J#UN?uJYP*)7v&ETNo3F~ftGwdE|{a*7Lx);rh&C`EvPdN3qWXD#&$;AEXPq+ z@MFe|U%4}%Z2y2Xq;{J>1WX2g{@K^2ihJXwkQ5!GUxK3J0QyrQG$q>ppv)h^WMW^V zmfKjMpM$J{akOW?fBxTO{z|H${_<-0-*y-pe7A?dF;bxE!zjK zmV1%8N1v8xlk*`|(xJLj$5zze9X_ahkoW+BdeZs6e0`mW4o7zo)Sw*}W%2|^DA7j2 zD?(AxNzqd6^K%SH(8lIY#Nhq*vAdYEOw^q(HqnG z(e`^iBRAx}LtqChR!_QM4|6>{^BoscLl2BevL+PRCpEJ{Q*78x60t9@Yn6YV9mIYd z91M<$Hh=Sc1`jS`WLRP$fNL2OeFfUu%q4zA+zdrOreRzXAmFw9s8dz{>FMXY`KH*h zlKYi;FrdYkpZ#ZO3xGO5H^#bDHb1cO)hqYJ>f-qt`L6Y+>tL_Knk6ucw#n|Os1rZ( zo)B>s1dpjXu$N0NlyLrBv54)|^?!0M5ypC-IpLGa)D=h`mbjE1qmT9O>9@$FkgPh( zJ5lJg-@fB!weUx7M(T;jktGF$f46gg&dh~1b)%OkZncr`>v!Xy9q%hk?mf|B|7OIy zcY`oM_UklpegBS{as_dVn6NXasENv`{cbg{gnzYpQfyZ_!zXYMn2{w~b3$cDHnuo9 z>o&Wmk!Dtu%iyfE&#TC|`_qvA+IOXmX+O-Q5~loH&xJ<=WplXM@RNi0r&3>eZ@|2Su z6B$5SBuaFWghc5;eYFgHPsMZ#`05l59RmmJ&(D(4K!guo+`7;9L{ayRcSMtd&ZpuX zp@H4Gzu!OII-5I02ZQe;V(d=?gWIS4L~OwSG@2knYR3q|e_A7K_5}s*c@aP|EAmgX z0n@BzjWqv52<9fFz=grWvQk$CU?Ts}6E-4*&AOL*B98yr!m2xf3rMyv{!$!8T>AWr zEsZydZ#4`Siiwafj*k1A%dBWvH7% z1k{@qp|(yA`YZqMNn=g}_&oG6>?|ow)&eYTIy>(k}=xQ75Q(_YUCrS0X_Ao9;aP-|SK6fY#!Z z2LIXpkL3iPQ$+;7{pBo<{^thZ!WL?q-xg5d{=Ezc0#I_-na>@0u-8lO>`1=Nx8ElS zRPbDg{pi2$PjfdEC^cj5{OCw(c#vxu*iB#Jypt{X{Hc82mt+3uS~2M3ny-(o_n~6O znB*k;ng#>P4&nR0hAvd*R!V>xgeLdwug!f+_`JoB@g#+Z+AGX``)rh6@|`&mCgfsYB;`0DIaPq=Gr4WMM~ zuX7x;#YPMVW9jm>6sOIY<3$5EXt9_DgNSVFhm}U^iw?^rXv9whGHtN~w@QB!e5Wt4 zULVyBajJh+9T;_gdo23&esmQ{7$!0*UU6fF3=5XdGAsn5hxlJ`f-YqTNA>%D#@p9+ z_z36LKSMxCt=`OA*k^s}Ig{kGK)QlAl(d*uF;S<{vXB!0OOlU$GJSGsXf|Gx)41x! z-4fo=pB51Sm3>k(wo_e%+c$VYT|$WWRB^lnLM@wibjXgV2gZk|H}QrV?|gRVa(TG} z_=zU^tmG79q9O*?c!Ud$1EDE4qJ2EqJ;$Os%eE!Q`40+iOB7+f$bf14#6e!*soRyWe?cQWgNq2q zvnA6>c^$`g8R4}&zPTo=4Jw>xvu@oJ)=yfbGsVU2+M`vJodQN;#h)452v=W$n!#W? zZrj|+TCk)kKEweUk&cUV;##Brru5Kt!~q7YuH4yNnKr&J?+&A%PE8#`xyBH=X~mMv@k_JGR`{~^DI%j z_6&7??;v3(F9Z#@y0$nuxDF$&e+)=e)2-}jiAKkQf`mgboQ#*JvEMX)<1*C`y@^TA ziYqnoh>-Azj^2(-@UAsqKtyXbYj$1i^CGtBG2?z=ine3<(ES=eY9%*%&I(_vi=MRz zLOL#4yqLoN@6l(3FbPyfvf(B)5^$<)?As1xT7w=32owcI$HMy}8Y#~wdGr5FxeVYVbF|8u*e%HLRXF=a`zu&P5bmEgI z94jvRh4FR#=7SshiyOSz$HF~?HOsniyjD~ z^MP~_(SdAV;;08bW*2)-s8}gi3HSL*L7=yoqE{d<@hcEtIhbbS@swEG9A{?Rg`K}9 zH1nt94cXx=KB*1pzCB<2nT2y-R(HecHy&`?E0>dwLjs0|ilT;oIUEigs$SHD`LnJ66!yBH_6y7=CrI@ zn@nEs>Oj zuG{(w^eC{MXQ_fTyjA4JzVA1>-acotc_%n02IHyZy*!=11SpoqQecOU?c0i@U!A+3 z*+RAlZtn(NfqL$7bT9)dG?sEuzmuMSuAZBs8MR+bfz@7^EqL7h%$r*KSR<_ld#&5* z*~co*mQZw190g58^!E|cjcT4_wV{0i;2CUP2;>U17dT(EMKmP1*iD=4dDpJk>3v=5 zlUF{pJ1q;vziflOtLd8*Yl{m-O?U&^^_;YBm|;(Zdq3+5carVH1rQH=kW@Zdcd*cP zLW3i(EmU0oV^nwu26JLH;~VUNo$V5s;)8C&Yp`I=X{#|S+pcH|!8ANOTVEEe4x^oq z)q}ot#|K$AEMa?%kkxdlxO|Pf54T#?+MFV%?J5pG^<;w;c zaEjCDZi519Xa*L!Wy>2yeeQm5#J3=?;!h;Ke5Nt0qq~=@cTHr9H*`lt)Sf2ro_gi; zuFquj!m(KI+-pT9ok}HmrWFRjU=}#*kBEgvKR3$i>Wu@!xd-n-IvAC>u%IH&2b<3W zMBKf7Z}1ks1{<~UsB3Bqt?f)7Sd2E_r$Q6hYV!TqG%Gqw{U0SjZogHXHY%wygY zvIQH6S1o!ZzNlQ?w_~AunCu!)DBIDo%e)`qRUP7};WfocPVuXiW9w8TgIkn;a%m^9 z{(#E0RYWus^X82BZnvdtz=n+t_@sm#WT*|+VtGqhI%6fvRpy;@lEzKQz>>b+BPJp~ zid{?eK9Ggj0DIA zPTw*cV;Qz^$BH9f(Rq_iH=m$gYWMaQFgpG7-!(PT!}huFj`M3-w50a5ih*wmm%_waDIQ( zrcvL9ofmfdCJ>drX3%mwlH)wSY+-nyDOGG|=OD+eiLCs7pwQHMY#=DOPY2-q`3fX- zo}L^rq?$zLu!l36T7_r&zLzQrIq1M|?AGf>8;Yd?>R`wk@trZ2VuCI-Ia znFOfyS~yEpi<>s@*T4;M0*p7s@8t=@9niD>AT%9royJ%1h}>9cAs~v}?)G1IF&y-M zs3inEl)0s%c*NMuX>IkU@uYrn6ISwIfR#f3WYJxMAZGDQ2!w}zUs`c)=OHXj_jx$y z`j#FT{JbY;24fU1gtcaF3f90n-yQN`eflyoVl%8{eKZrOW${rVT(VZ|coH|C!_+Ti=9DxULPE6|LP z5L@S2=q9vkaj?`)|L0vA?*}H`j!miSae)(4*6)Eis>X;q+&BJp$Bc$`QBYq>$xXr= z=dAC}3lXnV>UXCE?@m(Rou$4zok6_LwS$~+P4I@;@%0V;B^)sz-qWY^`?tm$js;(k z8?|GWqL+uZo&}w}76)NNEWb=X!43@L4Y!rawf5VP=f*)Ro)vh|Jw86bSU7#d0Z&&4}*B!OBNpZ56yW%tVchX>k}?o&1Ne_8S7mY3KkK^iS4u^S7`mM%$Yd_1OQ7@y0!BY9#Y+cau zthN%jdTO6*6Rgk`rqvz+fW&XTz{zUeTN2FrYD9@1*O@#RigWWr2M)u4vu*fO5C&s8 zttk1M1n1wyiQ^qAag24D>itjTPC8km*^j{%VP?~Q*g1~qo^n>cKWq4XZ)H)VRlJ9< zO*Lv_G|m@Yc6lu5yqKO|EM;4h@`-f<9_T-YL-w5^9~CT}a4(BMXkNftP|V({<0jbY z;heZ&PkOA@T~MFRoum6imo^IeMS><{o`$SBek$;|srM$%MObcBbqkIUkc+ojah?^tLdQMS4fBj^~l4!oRW^a?U5^Ed4>aanoEar8u^eP+#j#@9WYE<%?Lb{nqK zOk(y1SJ!pt&jRuX5k6p=_qza_VUv~`8FuM@8Lv`oGOeYt#^0d>f$oC(LCENVg;Gy% z+ttpBKR-0O<5FihB}>{1M*D(ux|8hAMop9VWu1Q4M8K_UXJ_oo5AD|C9~R2p>ugvv zaa~|p`8TA(-%DNVvqYTKpNei4FH+*=errmiruVSgO)Gy}nYjJp#?I?@Z2mQM0Bc}* z$hzfqiwDd@!MP~Cb$+otC(U)EyoN&Z?7P02NOVtQ-0@FOs+7dJY zKMLsOA2hwJD!!2w?ss^ZmDWjl^1ZGge=czEjm|JdHr?E39S}B#5GMjHWRnMetwao( za*hCtbcTcZC#PPCh>!arBIRGJTR>6-1ug*JPsI9r^XVUAg#174_c9#uz#CpA;F(3(PO454LL8zYpK<($E(o)!O(OmX{>=p; z4=D*))l%SwV!EdxHTVxoZlEvfQ4DaIW|Ih5{~{aGHq;Fy5lUlj%ABKn$orVctA7vy z1fYyXP&gVJ*-_644m;wYqx^y36^i%(rU?fY#NrqTA&s*lHi#da6aM`l{O_Mm7omQ& VkA@})A`bh%^Z@=tkL)q_zW}{mg|Gks literal 0 HcmV?d00001 diff --git a/src/main/resources/static/macro/25.jpg b/src/main/resources/static/macro/25.jpg new file mode 100644 index 0000000000000000000000000000000000000000..0154648bd126dc41a69b8b3477b20cb4c5c3fc9d GIT binary patch literal 16663 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 literal 0 HcmV?d00001 diff --git a/src/main/resources/static/macro/27.jpg b/src/main/resources/static/macro/27.jpg new file mode 100644 index 0000000000000000000000000000000000000000..2f8cfda5dae3b34507734a0164f8a751f7cbefb7 GIT binary patch literal 15730 zcmeIZby!qg_%1wjh;)ebAl=e}pfU&wC^evx3Mk#(AuzNE(jg!8k3cbC-J zGXwAY{?7GX=UmtK_gS0SvtvDVKlggp+B3uD$mKMM`03*(k3nc?AP^d`fiA~E??IUV zG;C}vY;0T{py3kWAsPW00U-hKM@B+UOhimhLQh3OK}8RyqoD(HvaoOp-4%jBw9)>N z$Nc}N;PM-Y1Q#6x(;fp23_>SC!yrMsYz5H)ZlZxOfF0l{s-dG{A~#R z{SeYhWAL#8IBG*4j!pWd0eTR9{#6Z}A}ol&E8rD*rid5vpaNcl4JZXnBh90{x%LE( zQVb|XYz4sI^6hJt2w&L$_ih)0ub08at*ihCfcDxc_4V52Wg37NFgy{RPD+sqNPcBdpUd76>$dCfM zFL-2(f(K#bIs&9UVCQlOxQ=ii1vuAUVhTsc=Hb_{311rZor+zphf`9V6I+R3MBE9^Kd@IEk z5s6;xs|hoQzZGHd|2YH@XJg}gy+|p9IRw(0NzQ9O-BW-QSuch=ouGrG<*bcsY9Z5) z;(&!_N*mJ0E3$AbY&Gp~4Iu=kK`bKm0S?brihK#^lV z&$QBg7wIh0VL+75;}@aanD(cRn#xcd@V?V(dU)jO5%1~4jLjpm>*N<)(*{V2R=KVd zP*jiw0SRz8^$yQ7m6zT{D}Zom?NH&P6lSR^H`sGzmv`VZld8iJ+X z#d&?WXvp{*O#gQ7x#I38a*lF<0w_-WR}`x2gaZ01>@-=5C3;S}h0&*6-!DP;SEuqG z1#=<`U6JE;Aut6}gn0EW{I@UySl37ww3JDF4cytickW`ZoHg{nW%r5|R>@u0W8;cZ z_FL+K(pzI(GcGo#r~MHZdRzO|RGjBR2u^4WzPcJL$XW#WGN|i-7>K~@S`Ej*gfi_V zsIkp@O3d5K(`{TE;UOFlfCF0rv=Tvnpg2Ocw3eX%)XA*RuZX|=+8w?GZU zn^P+QNLpv9MZf?>j#FR<*aX70>a*t(xuPXfl3aOj3y*MneKuKcz^ZYKHsoYSx9}uw zRLT!g=GPd+&!5*Gd2U&quU=LCyorK5sD*`Q((-@v4OEH?PtCJ8=W>VS4w}&ODTt?U z!@GnFkkgUz(4d88YE86enN&ThfN3tSdEV$~a5_4z8z>X!vCtSg>H7x;1S$@C0`q0c zYf@2!&0D{MjiRknMFU3;iuM7QcI9re?A1+}-j(l!k($u=&Z`AP)u| zN6WFgMdauKz(K^9(fh~wRC{h?s%J)0Ut06?Nf28`&M)L z`3d%*#sv|bjm@D4O~0p;tON0R%D|FC?b2P8CS=hHX1MYBf*&-mv-2GvC_It}!#;mJ z=E|YE=4&e5=MD{at7Orqf~FN`)xZ5t-6RsBlKz7&@ouqy2VL4w=|YS!ps0B2=B|0Q zV{9Ep7PtU=A)ouXO#u#K+W4xOaDQ3tm=nf8wodP7hx9lJT@gCnb< zlQz^iy(&#yd)s|nyN#lUf3*W+ZRrrfXY~C6NipBQTya}H_xos zgPT5c^Ho7n|AogTh-vJiV|>r!YSbeO6%sT=ZCvORqW`%5w4g}x;K7#V9(;$YUtF`) z%(U)F2gcZ+p$d2%O^4j(B}hiP+@%4Pd<5PIQBmTLn2g9lg>{Upl^U1l$$tp7rhpFt##!0r+O;;Sn#h$(RIPi$U7xE%E`#7QI^pBA*`k2wa7Ga zegJK+vjp5Y%nLh?W>_X#9~au{)AtKjRsssjiIP!v+m#@=fj-M?Jd=It~ zfB!p$AZG*1XqDl5qLU_^sr~0X@Ug;-y_KVS|G#-=={4lGjm8?ko!O3Ys$3i_G>%if zadPhC=~`R`gP;v+Rv_n!uusKnV$iHOwb}>fj1#tyl!W!aV@0C-#|xf6#|m~Tv5B-C zfkEr61;fcX$I!JvQulglKsutL?NwA&*>!da%2>12wd$2F1SS=0FYctZ!NGFq!n11H zqA@LKJ;f67W~1t$g^l!$CfDpn(cZG_^q^RbggU(fKBxtSE=tvTS?Sm&jKOz0$N_Pv zmR?A!9=T_c*&a_($DD2>PN7>x-PjfXuUUeqpK6Xkfy^_l>iC%X=fVe=?6Gkz@mMonC>Q7Tkx2iYnKMCd#3Wu`Oub_p^Zl zGo-6|qEFi&fK%@hb5ah70{$bJcEwW_V7(0#5cu(hMC|@W0?y)%i_P%^>_Mxp<-(M? zneQIpUHOZt<&M=N;T!~t{|Os71XlLtKpoSIYV!tRRNYqs3VfYi$l27-?kqV5J;+nf8$~bK0Z-UKQ^|z^@#B7z(_N}Sf%FL z7$64>EkI7>YFT)^0jvNl-1R5}xev>cs|H|Fb51RkD?8OP>lOOf7)|CCRusUri`)=p zw~?yVr~7^p08hD=X+crjg}bN0me=RTDW~2yD0HqZBLJ12f^vjWER7 zSix9f*lyhZQ3i#sF|d>c!u-$h2b{QG#32A4J5FIH)Y@=`kBwdEr2`Y$(y>~2Qtad< zsG_3o*Hy`2&mRN)MWU?o_4lt9HC}|}&AU()5Up)U{nFS< zP)FECMhys~4mp7t6S1LY?FKfOOw8$9z^u1(^?t2F%q7T@`S+BhYs8OQE$E^HAPlT) zkT~O`&`U%Bh1gK2?MObNtsDo=boG{}aaIo4JS>2v8nF1h;$SbzD!>m4q?iW4DrDFQ z)C%BJGe2~Qc74v>^ado`D=I48`>#sW6%8j4M#qpSqmV|zjl{U%g3hi*N^bG|tfRD= zaA;^OuV?n3Bq2r*vg-biXb~#~)Ow+!09bB3g-`5>1F-=~MSycvML24K;7qSGi?Row z0Fqje2h8U~LtF7(>?$=#-T^YQZ*ve4W=(EDfgB)S8k0l74)r%Q2+sw`R9>zJJT8;} z`V?+6)NX{WWb3e8g3PUy?yc~h8n^;dPUAFyRCmJ&AT^|O z*bYd2-Kr4H3X8jkK4&Dh1>F`aNh`moDZ7m$St^dHOFT2`NgADIb z&c0*n)HlB)|GkyN6MF}vj}6rZwT^sLe$(TrvJzD1m4ChNIr0H(jSKsAy~C}Qvz3v? zjj4f?ffBFr#_YZ9H&c~f&zGLHHCkSz_9cgSPMk|8OHk?|E)Ns8_e{1yB3o~F_jyykCrmax!%iqLn&)IJIsb9 zkc`-E3D3Sr1Jjx@4FRRcvp3GfB>qQio1?P)K{yWXX4b_tf|J1oE&0|P_!tD#Bd~;p z=BIQADbTa*zAuTV{hS`(geQiAKmB!wL-Wz`umCqMZrf?P5=Y6mo{ZPrtqoNY zzq*8c^A+#w%M!95P2zOHa^!=#OnHq)@&u-vKwBx#9csDjc_MtDH(KpE4@+`W8_Ln+ zyE(qJ9|MCvShTz|r%M0uQ?Xr$k6$BrMRvqYWsPxsf?0=#JB=s4*%hKgFFf?sKcW9)FWz)Mq-;kt{Y<&uWE>o%4 zyilSd9ZR5$+|9`$$cj_4xV3mN8LLPaTNh5Kth+9%8d~>9=E0t*%}S>BJ??eAC+Qg)aI5NF93QT& z4eubX`V(2zHrW!@m4@T`74q-E}bpHrXWpQ+5WK~hczbhrk62(-x=E$`y3?_88( zMTVxFL?8037CJ&&%NxvYBs5C&QDRuq??de!R4AY%BFh}U>>On&(nAqn{PgZ@LCY_G z>WT#KcNVkkWFE05sP^TH5atG|Y2n-wDWWKI{i=}Id8@i8*J2PxuaTB z286ahxn5f|WuK*TVww7%8w%zw!uh_1Jzub#AG~#AAw84EAv+K+8?(b+V~XDFxf4#O z2NitG%lHyhrkTCu#|4{fW>_3av}q5ynT>nHUZd>`i(&YiX3vh62qu#+1e5qut>)X> zlk~DXlcljM{LRR6;hN`PqC#l(s`Tkuk%nEfx4Bb4TM3n+th&0ocG)KlW-Bb%!8d}= z%`w{_^76|5Dk`eK)kl(SDjsS(G=mi972c>+#@j7Vpzmo+-Ok>eV||wHjLtML;F>S! zhkttL!8E$ca5h;WSKw5h%(Cadsb2|NMM$<47R;IP%8SgMz4sn~_lq4F1vh^Q#`*xJ3z$ z?k2opYwwwDjDPvkRa02>R5*9OOdNkY=L&c`>JjHxe-iYu`grgvZZ$vy@7G2cYiX#I)=f(SF!5NK%YNIg)yKBtKBWqLOORrtczZD^ zm}~2cu#2ArDd)}hs%GVZ^bcw#<}IgRUQk;JQu=q#l26*s34mIu&u@>J)zpqDEGqV3 zXcah+X*1jmD3QysFlFu5^_U_)+kzg`sZqdhN@$6@D44C1E#C?87KChsu60CN?qjMv zlr15ZLNnTPT;_bw{-`27Gq+f2R#>r1U?Trbur1-UkgxIHhiJuh#`d;MuvF^Hs;H}LQE+e-l-{BxhViJj z&lJ{+<9{42BOlB+J*XydQ9HY^5s;_kf9U2Gj}a0c49_!N%CMwirc4W4o;VQ_paq*b8KI`Glt2kD4UEm}!D7G8u_M#sxx3OHmW zbrw)@$0GG*?GhV!e7>>ATB#x{@vjkESdavniO6>+(kPSP+N0w-+LS5|VXTJ7h2L+I zpV2gv5UhwyFREwi<74+^SQ@w+#NH9ZZEpK#Ts~35Mmr!P#P2T- zOwBFHUIpH_`y!$R&n8I!%vd;u?Az=4`4W>+A<6;1Wri@i0$KvL{O&Xk3eoV8GHoYJ zQJ+XYp@E1h9wGe43X!BAH=S@evN%Emn}9gg?_Fq8vJC<+XJ$ z8tP2azG1ywt!sB8$Fm~DnjZ7*Yi?0}1nF=MK|0UBzByNw69UMHl< zPM^GTS}sAI(QB=F=Z_gKAQ^T~?6P>uX`88eV7|ui%Gud?-#|xJ#$qkZakO5rtUiN(ft2C1k`EpmZhkdu7CTZ4U*nS?T(kOWN{BvoCsE-E=0bJr zZShDjP3QK`<}TmKx_f=x;Wllj$)A)l_grf9vm0@hfgdVDjC^9+ zba8ep`)1>op?wKb%l~fJH8B)$prSiUn<8&NRUja?62n3Ds*~~WFy};M^Xf{p#M6Xk z?bjz6gN3|W41|*hA@=Y;ADzbZRszn=61fdE(hX~6Zp3!kS;_t+y-SQ)_DO|A6lwyF z7Ul3GE)iIS6;aVIfprS*+Q&SeyG6s4TIM5~Ss`j@CllRs8&+)GR$iGxt2e=qn^N|BE0@$`GWSMd+@FSd?6vOYnVwB zW1G)z`w}E=lQl_;qeVvdO~LqF*V~E9*@Ew5MRr9vG;m-!-MemH<@g7DzD#K9-4I-Aj?F{G}TF(T={$53fFs21%;vp{U7Rwd0*zp6` z&KGJjN^APbqHu3B=A;Q_I?BoXl2<7j)8MrfbqjI9f)O!;(BQWe4J+RuCqH9+0uqhN zib2Y1k@rd~1xk%~PZ#Jd6+TLxcAxW47TUyhwllUl`-%tHc z5SwbOXEdZxZB0%!6r=3_0EWk{u0^@=EcAD2z!-E0f?rWrJbxDoAn{XpA0u%W`BOTJ=^wnV|}y@LtS#opPhD9R(S_D_FDy4e8O8%Bw&B(mq< zDt_P*Ksu`L$UfH#i6kNrlIV}o$KoLetAW8gb4`L!e;=W_ua!`&XH%k7A&hp zXyHNZ14;(sIQ@G5tKC$&nE|*pN5hm!iB6`Wjkrm1RmpuNg`cX$CGpgo8m2pvVss*p zCV?@zK4Wbp$TyR8n0YAW6TOQw;aPrk@%m!w+{87rI*{4eSyY?BlInS|;P;d?1M;7a zM`Uat6B`M?5V%7ys@iY#QS_+C_A$N1_4W5Y95?z|>uDzW77s6mf;Q-oi0|8fW|T@t zQi_p|@jxRUunw)gpT8wObK3%jhu^SVX%1N-A4NgL)o5QHCn3-TQTyX*nlQ*Q*xVTirHTu43`@k;biK z!-sd}{JQ(ILWklWmD09&*J+U2t8fwIdbM(TqNfj0t9rqpXIOH1inqptj5??yUL^~K z=9BDbc*n4dVn`+S3e9a|D4|Dw9DzkgJw<=foB0}Y+pOE}yTVVtP;QBzIDabeU$L*f z-wVHIR(Mxc7h0{JIfT}eM4Op!7G+^MOx7IBPqX~~5Ik{}mEQs%zk9Pf1UU9t9Fw;A zy}!T5QyAU&>hu!CzhHGyRTnS3_N8Z|Ztk&{2WQ>*j*PJNNQ?%G#iwAb@b2(+MM^~mRDg`=0vROtNp7`&Ug zf;V^P*RN9_ddp`a2Wb|mitaOKL9U;@oZ|jIIC9Qp;P#KZ8F4cpK1|}v7xRF7q`ycy zL>n->*OY>9<8$&Czm8*`S4=QLgL<2b9q~7;3%U-435_%Il;MKnBS~9F*hmr@^iP*- zx~*k;X+FJqnKL7-;5VKM(S0yhubosOMP0B)xdKHDuiAl|s~tCT-;wyeE$hB59l_Ud zpyWlv_PAoyzT;VK*4fEVS33b~JA)wcYCd(M%_yR$Qd<<@%nt%Fc|Yd-8x;LB+0WG# zw@Y-h$uij&v=eY_N5mHs>_;C(Dd9+xrR0~bX?L&l*-`qjCQur)-FLaaZ&}bwRAY?M zC$wAvmB$({7&FuL&EA!?&MdzKpPR@LPydKyinuxE+jZoPNV# zt$)3mF~*@?n17&5xX>=b+QVs0EzT~s!x<#i?P}S1y2ZQS;#$)*Q%=N>*+o~-FKp*# zrJghY_Wm-nj`;&K_w5tilMvfGY@ubGOY5VLz_wb{Z^}6~8XF%b@FP}(5C`^qSw!lXq( zb&qGnxC+MRf2{eWlFWhwM>m=LVYr{n?l?EYn}O2YccX90h}t8CcevI4yt^ChYe=K0 z>)d~AXE3NjHXq&v?Xotj$zVUkubp%7O6ZHAHWO-rw$IY=4iXvfrc7z*Va|W!$k8}s z>8kxX{z|6!k01|!=Lm>B$dhqLd8@h}sN=ekPO~~If%>V?inlo>1TX%)V)e+_YY+;4 zALQd47i^elBNChO<|Vh9ZAL;XD^ETXWt*C|vzB`-{Pacqonq;Cq)TQs?uK#q=J*Ay zgeUx173s7;6FsJuCLhZHRQW6Z3XPzqw^lfKn#Vt9j0Uu)3aqQ)WJNZjF z2ox5|T9-}As%xp3guD4m*$&Vi;kz$nDq44$u9AGr%Hm#IxVhYk`^4`>Tm@G1Smixi z9##s*Ujs3%Ol;VjvP-JGn*2VM(B`T!tNb$iEQKGW@$Wk$8E0Svp4Djg?W5F>oS&HM)}C^NnfACx@lG`Iu} z`KW%$m@w$?llEm{g3o`X;f#dFf#`G!oVnlo5|(l@T^E5T zcPET>FcmSoZbne2YqddIg%J-X=TBg`BZnTh$b!0(FE2+NyVIL=BCM?T!BDS}%A&7ZVfsX&WA4j9eH6n?@>%5~`J%KVEL zNt7~4Rjul;oPLITs2L6A7$NW2_;3m8-TthOGraOvDI#Gk_NnUa^sX`W7lA2mAyo`U zj0?6at}k(L$ITib!zS8|r&Yi=CS1h8mw0IC7}(etSHF2h1CcO2BxV%gS9xvY6RE79 zR?@uv;OETFKi`57pA^G@7LEydNP=ulFxc^vT_)7YKl>)wNg1Nwe_A-E`xpdh*5Lm% zDRVGb43MHF#6;d>_8}o*5RL>c;QmJx1`A<8lX`-<`ELUZ1DAz?3rJCfII4Nko~Zv1 zVHj*00&#ayuz3MCjxar{n@c$o2sQBDgOk-=xcsZ|`iOykD59sby!mwM55v;+Mc|$C z%5UbmFA>kpDWOm|4XxbsM_yw8o3Rj}?^)@zsoHzNW@X1o{8WRJFG$bi&~X9Wu9_Ui zTv7PiL3TM7S}h2z7-vr&iRu+Rs8EUJI+W=eKPMLjC`_HC{|7cOF2d$C#9B%sJ&$qu~;+wNKyCo8Fir9XA2v9Y$ho4PvP+mtJ0KSGyM7dsImS8ZAj9f>=!yHmT? zRFX_BRi!7vwSHzF;_D=dkucCPPxU6f=DuJVu~!)I>N@;HGOX^P2~lr?F-QLS{Y({* zV!T#+^&XDXAzz>R)l<^_*^$?FYu3s94NiEq?(jF!bz20U|H&;154jG-*xj|ZQGTDj zj$>oH_ZZDD$8vNY9O;8Zgyyf^y0S8h^i4`k$=K~yT|#LmcKc~>gS+13@3l971E&K! zmWy6`-#wbP125l;uqH9k{TUN!9CU*-X%Yl4O2fB?{OOx>kldP$&-!ug#q za#vPF93y>m-xH$~LY&Bo3~O`sm>i2pFUCJyMF*@I0Xm_YHJEv>@ZsR;!p0Mh2`9zP z!#7;R7FA^LN}9Y@^*v6t1;)2%-vegW%sPuArmCz;myy^F`Q=`Ctad@NSfq!hU>X?_2swXn=P=Vr*eucWL> zw}oDmyvfGCF%;HMdTj?~1m*8E#NGNf#tSH@9owRrXJB8&?=J5=O;8<0C^5(!hpSH+ z9S0%@@%F9yb97`g9H^mKl&P%m^3|e*lPXO(ii~q|5rP?CBSs1h;^{21L<>SIFvlj z{JzE27PUGY=k^E;#`Og=B0#>PDL@$DOs;dH9b@pMPUwD6534qPyDnplI_7Hp*Jsb7Smn&*QRmntv+%IoMsP( z#?x3iI4~!=lWp2Y5+gzd@`n3g*MR~ST6Wq{+hfGqe?Vv`wt7CDZ77D3q3}EMoNW5y z@i4g^jJz0Ay#ckxS80x6b_E_i93Mgh;^D60U|eVSmiz}vi+; z-aXRSIqN-%<+=G>Z~Y6P`vqO+T61Gs7e1(K;sP$pTBLE6Ovb*N*tMtl(Wk3DZnaZi zLO!0$HImyhpq=oo^l!K5svR{e^6;JK;ol&b&t5?c&f`&UIw7_R>_gX{sG5}fb0t(s zZ~B3{A1=;oCN%2mu>OF8(A~G!;)mhiq%N7Xpqd}9A3CXTwsuAWlzB7E)oQ<$+vWI+ z&o#m~4gn3&N=)8>L^WD{s*kviLXt}JnjCLN@HS&GLac+pCHnR_D?@GDfpol6ADM%8 z3Vmd%lFudNBURzm&=kf6=f|R!O$NIpKe>Iu5<$a>I+AzAM+YY)zFc6HYsNm7y%|9< z!sFzZe6-r=?Tz+`xoU&8`pY{Tv_%c>6zmCp0GgN6b{jiQz^}ykcs(%5PLENUkoCz( zn;8%L;22$Xs8>BEiqUlu-nscAE#A*>YWI;C1NHE2B^mj5$w6D4c7?VD&()twyZ*fu zHO}C2Cj!n@Ql32>DNY5k3-4Q5+%o{78NH+@!^)BNH{QdhO6zfJ{Iv=Yzj@k7)Im>u zaUkB~4Fhiy9hlrt#!le0f9k#T=L`Mgw&Hb~5)u+(+3$(9Aj^uBx60b}#=nq{%b33CKy9={yUg&QpvS49o~B-E_+y)Ruk^j>oo8`66;UJMIPJxIbnQku zbsF&w`l)()a}ISNM4SJ{?E5tLxtYZ|v3&tV1BaVB9z-62BGp+f)iI zcG1I;L{-DXIbqa_^D2?_^KYa?2SwAOnng*qItXxl75hXCN?wZ3$*kQG;H9%UYuOjO zg;so%f>aGs2|=w<$&X~WD4R>p3~pXzpW1Ph(DY<)0m~16T@PuzaNTf!x3FPvGlN5~ zss}J{C#JuRylNyQWcFn(%dQx4Ff8nOv;$pC$Rp=~d$3OPA92Z%-)w7^Jt|LoAnp|p z1{mw35;P(E527EEP*#lsKg?Sfx0mSH`blz&@Bzd91sf8Qdp>AO7bAAmtp1zDKoxX< zi=JYblqKTcg2p%cbPHQzEem@!3ci)>++=tUfxME#S@n#ft*6@UvE0A-NkZ1I zr<|H92n#3lvpITZ6F#l`0~oXheSc3PFF_8|?w`dT%r(}`!t?BHtW9B~;PMD%qrLSd zC*pP--pwQV)Ye1w&D~`u^L5c@&GdAfp%Ni&z?$-v%&E~SZG1e=(8TslEj%T-3cPAU zyH$3wI%U!T1`7_l1o7H-9~ISs#gY!*&itI4Ul*b!kzsu2_()~8A{npP1|1>?hE!YR zr;sz%mo?U`J>NMq6iHFr`;cK-)i{AJv%EwyH{9cpBmyV1@~D zWWPUh&VNVj&OA0B&nwhmXSnp3mCBw5C~K5mgCZ*_b)nk<&!r#gvsVycLLk%0BZo<2 zU@(0d--pN4+gyL*T;3PdR{+zDuB!Cw#q&#$r<4kguRyrh>*=1#udN=c5bHzn&mA|s zPx3LdTqfCKW%Y~1ZUO6d$_LOU)BI}2b4j*T-Df`KPXY@isqz3aS_H0LREhu^150}Y z8b3$Ho%%Nrq=Uh1n%qT+fA{Eg5>q*raDlOW7Dd5SxpiLT@>K6N#zuJw+X(2iZOhtGlgA}96cFT+*syK6?HP<#Sn{j@E>fV1zo|4#b7onAS9^?C@1#m#FPw&&v zNb1S7jDs7##P^h|q3?Uurl7}BmlTIU2(Q@^PHl#?=ds-l=Ka9emmu-j-B!dh1Po>x zPI*{;X0u9@XfCXP^UM=_t)^9ulw6yBM{*XpeW$V|N+hiK+j1h8&8P%RXWtyNWy?EH z3vjzf3Q=_59n?ZfFl_>F^=T$lGlW{Rp4n?^>QMAt3HajB>-W?F)j7mRQ4#!8+=o#P zd@srPvv7g%Q!tn{A#3_uZ2-w`?08?uypUm&(v%yok(rLn(0y*~{FF(e=Si?#a?73Xf(4tKtzd;$FBF4li9X>Xdm(qeVDR~oSIT>YQ`W%JlrofG zL|4UH*~m!`zS27Fo{qTjPm?{p0KQ-(#6bt&`NZAygWd85B0ewvE&ZE=Kgxov*Np?C z_?ltV9DXH7ubT_kA?~!tuMzA58qz~p$dSi|Cnm5^Ch$qWBmH8F3q6uSsHhl6} z%&j0H!B;;{yH3l2%n{q)cBPsQPj83!AzN{&UPJt}*qMQz zMu3`Wxvo3^BnN_o`5I{C1E4injDa-?vD`-y=sH+tAEft)M{LC7$apHQ^hab5alnIc z%&X3bTeP^d5Qq&}>=K0d88FE||8@-gauv~vQNO1~Lz4!PNBkdp0ROU#>@oVk09S$H AQ2+n{ literal 0 HcmV?d00001 diff --git a/src/main/resources/static/macro/28.jpg b/src/main/resources/static/macro/28.jpg new file mode 100644 index 0000000000000000000000000000000000000000..bed725b74b7e5506d985853b90e98f1a5bd35ed5 GIT binary patch literal 16162 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 literal 0 HcmV?d00001 diff --git a/src/main/resources/static/macro/29.jpg b/src/main/resources/static/macro/29.jpg new file mode 100644 index 0000000000000000000000000000000000000000..21970b09769157411846da2bc504ec25d1dfc440 GIT binary patch literal 14443 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 qaRW&^Le^~ z-LP3h+rY@g#6&^e+=^^$rMJ<<2s*(K2n3=4QBp`q(nv{G$>{&vezhPXyjU*oqg)sf z1S^8!62bgxLnI+oEEhofw_$L2EH@7q=p?}~{%yZT5dkn7E6gPfIzE_D2?UIp3J!;0 z=~Vc4GT97au~;N%2!Kq$P|$uHY6#j6yQsAA@Nhbf4n_!)SR`~d22LfBu%LqqCIJJJ z5b%f^7z%um!2&qg1AfDWP=j9kz}hGf7J}k{^TBfHY~T*ggVPYs3>FLR0FEI70)a|J zhu8}P09dd*0l~qca6bL_T3kOdiXtcsdH$Po^Z~|;W()z3 zsG-5Z0MQ=U2@vC%9!Kg^(fc`K56(=Y-6|xSK3Jv)i5?B;9 z3U07i#`#={c`w$b18#U+-tOs=gij7X=NY7W<4iT&yK~z+yqdO77H(juD&}_np zIMM_&;l%%lm7@}$2!qON)0*hNPWCv|S1k)()<{Jg)0`j3Iz<5rK&@F{S z9i$(!2>~rjY&oT+VrGcGlOMu1wa!8SV^!~*EKqgBf(Deru$HGLv!N5n<#JGHa$(TR z+X5SD%i4*E%b&*KG&_~dto%b|EsRC|I|DXunH(=XmV^Z7GhASLKPI~K!k40 zS|4Hc{fjc7b`U891i}JXFbl@xV1uC(h*oACoE)j3>;iFo&@GfgOsA*3k}j2r31dbK z(v%2-!eBNXR5c)o1)YK1E>{73E|zKm^@kv)#j~hl5j4e+5;la$vtp0lz!b9a-p;Q` zh)&1wF^n0gcfi~K+)@!X&xBSJa2tYY0Ha`}FeuIzsA;7FePI?2%t2;3LW3{?N>#8I*g0O2mc@M0W|+(&7^cAr zS%RPdaQC-NaL95wp;G_|{M`d)(4q9l0E7*CA&Jls4vzuFHgB5cEA|=@f&*1Z} zGI9Y>38*6k_(jvOtQc4s&>jnfL5FKX0!^D(zyN;KV7h=)_XJIZI5DfNNey5WAUtlL zLf&x_0wTcS5HJC(jYb6Gfsd?`rb*C;muio=RFiX)3H$+9C?m@|)HdZjX*WcFz(bY1 zO*t!17}0O_$HzbhK|F9c7>s(y;SdfU&_bYMdi7FCPwXxMnf%_B*=P(b7OO)-6X80m zZN_5tcG)1}PQBy;EUFm}Mvi7s(Qq)bz#hX@;a}1vK41vJbCLyQ4aP&ma}Sy!GBL+N zkDn4RLhO4*M~~6J0m2DzAc6t}yCJ|##9s+LpQcY^LRvWq0rLUfz$k8lmSBU5J^}=r z>PAQ2Ll6JaAq%x)(g1?U&^^J*z6>dZ_;vQB zt{IpE5CJF9Ek}}&0oVX`gmAiBmx^h}$eDjQMA&FIT=$jCSp*pqm{n0Rc?RT|W5DvI z|1E78>vC@Xal+ztAM*;vbmqCi{Ql_og2w=%^ zwn7{N5Ti6eAEEX;#}S^9`mLG#yaS^Osgfeiq?@OUig4;x#`eB0MZsaH}bf>kxpwk^8n&jZR@}SSzst$3BJQg znXtqjNM3rOqMFbAH&~eee}Y9ANLNns>2B(4glP5bILJF*Y}gEWCIEyFG+^w_fW9{J z6h=(Cbr~g^x*<+=UUmx^K(sOc+|)SKK|)|nL4#q~IbNek0+=z*#`_42W zp509>NOJ_<-wpWWT6?%1z`)_)I>ijmR>UcE4AeDDF<6LWaBnC0&Ilt0^0ia~F`rGx zR9`AUuMkeIdX>N(Cf%Tbi4F!BKn?wk4r7J`3jq6X=u~vmrV;$8r-F^-uBPXy^)Cx50CiUB?u4%jKd+oCwe1+iQ?QYQJ2p=L7GdX5zHn1 zf~@=Jk?E7-AfBSkri0N3LGFo9nDEd=>1FjHAVlCDBpXt;>~>l8U~dBx$!&d2h-CgK z(5q6s8Rm}kxWAGD*QHr6klDzi&5b#|zwZT{O~qc|e>ruQFTV&I#|_RdGPz$*7BTI3 z2bdxR$Z{zhk}YN)MT~MmmZ>N1H_0TPp`aOrW8ns)YR?NYfZ?2F9N|EDavDG=h7vcj zv;iRGv7I3z-s*v{@vH-M{G?}-uKYNr>ysI{$0lv$>HQSU$;9la=Fd{ur7h)*Aq7>ojeM+1MS0C?M#9)OIAv8e% z3E2c*ZHg88n@6>y?IuGu@`OFOBnnW$?GkJbG-fY)2SjC$a)U}1EQz*|xTXV&O`wL3 zC9vKBMfyeC0GbtXTGLFxa}1Rhfkms}oupPSzy=OU2+xSri>d&+;hdism;v~R8a6`0 zz2_3Botxfv_q#wT75UMn5_;b;S*=;g$Tyi7@N(!m+ys`zyNx-N0>`kp*s7f+d8owT zhGl~uEB{6d0G{5K^4QdD`q;KtPvI>Nf79Je$Eu%7brbYN&Inlo4U~%Mq*O{`o*Ig^ zx`1ult9zEsDPC5|TIR=jSoo+QfTB^_!#Qs@{|JBtsy|Q1%8+aBFwY2!trvj+E@FWQ z@gDJ)DzWqMPu@!rfC-JkY zWP7tVgy;kJ0&tpRGb5}KuG*7EWD1-@-nK>6!4ytIV46VQ&{otp3pKz5$1vn=O39}& zV+Qct?kCbg8EZm5RgfZ+UT_o@h$CoyQfS$+PLJrzKH+`)$L$I9#lg>e8t04Z1Pgn4P=%+hXztga#lif5za<1<%l6tf*H=v9WR&h@L8FNawoL zAU%)?9kRJixm6aC8#Lq!CWO=QuUKIhh|pvf_?sPPgAqVU zRTc=B>`6I5E+|YGY>9kN)3QH~KHwbS4u`Pe3@isHnt0FyvS}m>ahW(l1O!;Y4VJW z+AUBBr|m79>EKRZK!4%4 zP(HJnFka4~`A?YP$y4TNJn+o`CLNj?PIZPGk$PkUZWLB^ZbC5#a>nk-&76632wHzI zQJd2y20x3XSOayH3AU~j} zQz$1oIe`N%I91J2HlX64^|I_6z7ziaUI ze*WD8ye)$T3STZdkdBgUY&d+I|LtHoDAX;g78n9l1bp?tK=zElBMK2i?u0$dQUPZI z_+Uv9Mle_`7au=A7RQa{0@=cVFP=C=L>4bBDyOMs=15zya;*-T>f|1jl1f@7uk99m zKL18@pZRtLj{^l0x_Xu&7HQ!TH^FC4Uhwe~vl$tdLg#vxy3K! zt-XjezR_7VVr0BvRn)gw${nHVKV{YVTgma6EzLsPK9HY?Jqlf0QDVr5Ub?+RIvc8W z&V-VxCm&%g9W<~zQQgP&fV7&=BA0pk%48?{6)1OZM$F=TR-{naw>}JF(vHkH*1sBwFl+bqZ+@f`eZb z?ZN)RA6b}QbHKK!I7CQGOltR!Yk9YBv|6lUglq6Srr47!OLFuSPIoIgtu7W?S?6!I zNY&w|ruxoSk{A@n>IU-*w|DlqWJ-TJIbHaayCnE{*}5NnpQYVfqA$}{mzh5x6{)By zur0*aUfi_tL$6$cjnMgK=NDf;BO$)MB~>e=N#;VV!=lFD-tPI7&g&-6Y*G7Aiw%G`*L+|wyj>cszN4Tyl{w4*?67t*Yc3M zUi`M%f-be&WM#&CD-g1O!jkY>c;k~@gdQq;mUN^xT89Rk9T<2eRV}j=~`(vt4y-^7{L+KGop8kk_%Jpa1=_5QRO@4}psj9l>F8t+ZNV%0AC!@7Q#j_MU$GBq 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 literal 9954 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


E`0UU1+1GXLeIKihk&Ow!YN%_V3osY}Fz_GP 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 N2mi+iEM>&#{{vP$ef$6b literal 12028 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 WMTr0jz9^Yv`TwwhzZ@qly8jnIuDWUf From 23b9e636d1ad81eb7ac927371c1634dc083c4eb8 Mon Sep 17 00:00:00 2001 From: Anhye0n <49294599+Anhye0n@users.noreply.github.com> Date: Mon, 12 Aug 2024 22:33:12 +0900 Subject: [PATCH 12/28] Refactor/71 anti macro image update and scale (#74) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * refactor: 매크로 코드 업데이트 * refactor: random 변수 수정 --- .../courseregistration/auth/controller/AuthController.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/tutorialsejong/courseregistration/auth/controller/AuthController.java b/src/main/java/com/tutorialsejong/courseregistration/auth/controller/AuthController.java index b9cbaaf..ae55862 100644 --- a/src/main/java/com/tutorialsejong/courseregistration/auth/controller/AuthController.java +++ b/src/main/java/com/tutorialsejong/courseregistration/auth/controller/AuthController.java @@ -96,7 +96,7 @@ private ResponseCookie createRefreshTokenCookie(String refreshToken) { private MacroResponse createMacroResponse() { Random random = new Random(); - int randomNumber = random.nextInt(20) + 1; + int randomNumber = random.nextInt(30) + 1; MacroResponse.MacroData data = new MacroResponse.MacroData( MACRO_ANSWERS.get(randomNumber - 1).toString(), From 7b28a4561159a1eb703147de9416a362cfbe5033 Mon Sep 17 00:00:00 2001 From: zhy2on <52701529+zhy2on@users.noreply.github.com> Date: Sun, 8 Sep 2024 20:00:36 +0900 Subject: [PATCH 13/28] =?UTF-8?q?TS-18:=20exception,=20response=20?= =?UTF-8?q?=EB=A6=AC=ED=8C=A9=ED=86=A0=EB=A7=81=20(#76)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * refactor(TS-16): 프로젝트 구조 개선 - common과 domain 패키지로 구조 재분류 * refactor(TS-16): JWT 관련 파일을 common/security 패키지로 이동 - JWT 인증 관련 파일들을 common/security 패키지로 통합 - 보안 로직의 중앙화 및 재사용성 개선 * refactor(TS-18): 예외 처리 로직 중앙화 및 표준화 - BaseException과 ErrorCode, ErrorResponse 도입으로 예외 처리 구조화 - GlobalExceptionHandler를 ResponseEntityExceptionHandler 확장하도록 변경 - AlreadyRegisteredException 리팩토링 * refactor(TS-18): ErrorCode 인터페이스 도입 및 도메인별 에러 코드 구현 - ErrorCode 인터페이스 생성으로 에러 코드 구조 표준화 - 도메인별 ErrorCode enum 구현 (예: GlobalErrorCode, CourseRegistrationErrorCode) * refactor(TS-18): wishlist, schedule, user 패키지의 예외 처리 개선 - 도메인별 커스텀 예외 도입 및 적용 - BusinessException을 상속한 필요 예외 클래스 구현 * chore(TS-18): build.gradle 수정 --- build.gradle | 3 + .../common/config/SecurityConfig.java | 18 +++-- .../exception/AlreadyRegisteredException.java | 7 -- .../common/exception/BadRequestException.java | 7 -- .../common/exception/BusinessException.java | 19 +++++ .../common/exception/CheckUserException.java | 7 -- .../CourseNotRegisteredException.java | 7 -- .../common/exception/ErrorCode.java | 16 ++++ .../common/exception/ErrorResponse.java | 36 +++++++++ .../common/exception/GlobalErrorCode.java | 21 +++++ .../exception/GlobalExceptionHandler.java | 79 +++++++------------ .../common/exception/NotFoundException.java | 7 -- .../JwtAuthenticationEntryPoint.java | 2 +- .../security}/JwtAuthenticationFilter.java | 9 ++- .../security}/JwtTokenProvider.java | 6 +- .../exception/JwtAuthenticationException.java | 3 +- .../auth/controller/AuthController.java | 16 ++-- .../auth/dto/AuthenticationResult.java | 2 +- .../{ => domain}/auth/dto/JwtTokens.java | 2 +- .../{ => domain}/auth/dto/LoginRequest.java | 2 +- .../{ => domain}/auth/dto/LoginResponse.java | 2 +- .../{ => domain}/auth/dto/MacroResponse.java | 2 +- .../auth/service/AuthService.java | 30 +++---- .../service/CustomUserDetailsService.java | 6 +- .../CourseRegistrationController.java | 8 +- .../dto/CourseRegistrationResponse.java | 2 +- .../entity/CourseRegistration.java | 6 +- .../CourseAlreadyRegisteredException.java | 10 +++ .../CourseRegistrationErrorCode.java | 18 +++++ .../CourseRegistrationRepository.java | 10 +-- .../service/CourseRegistrationService.java | 37 ++++----- .../controller/ScheduleController.java | 10 +-- .../{ => domain}/schedule/dto/ErrorDto.java | 2 +- .../schedule/dto/ScheduleSearchRequest.java | 2 +- .../schedule/entity/Schedule.java | 2 +- .../schedule/exception/ScheduleErrorCode.java | 18 +++++ .../exception/ScheduleNotFoundException.java | 10 +++ .../repository/ScheduleRepository.java | 4 +- .../schedule/service/ScheduleService.java | 21 ++--- .../{ => domain}/user/entity/User.java | 2 +- .../domain/user/exception/UserErrorCode.java | 18 +++++ .../user/exception/UserNotFoundException.java | 10 +++ .../InvalidRefreshTokenException.java | 2 +- .../user/repository/UserRepository.java | 4 +- .../controller/WishListController.java | 8 +- .../wishlist/dto/CourseInformation.java | 2 +- .../wishlist/dto/WishListRequest.java | 2 +- .../wishlist/entity/WishList.java | 8 +- .../exception/AlreadyInWishlistException.java | 10 +++ ...hlistCourseAlreadyRegisteredException.java | 10 +++ .../wishlist/exception/WishlistErrorCode.java | 20 +++++ .../exception/WishlistNotFoundException.java | 10 +++ .../repository/WishListRepository.java | 8 +- .../wishlist/service/WishListService.java | 39 ++++----- .../schedule/dto/ScheduleRequest.java | 4 - .../schedule/dto/ScheduleResponse.java | 4 - .../CourseRegistrationControllerTest.java | 4 +- 57 files changed, 404 insertions(+), 230 deletions(-) delete mode 100644 src/main/java/com/tutorialsejong/courseregistration/common/exception/AlreadyRegisteredException.java delete mode 100644 src/main/java/com/tutorialsejong/courseregistration/common/exception/BadRequestException.java create mode 100644 src/main/java/com/tutorialsejong/courseregistration/common/exception/BusinessException.java delete mode 100644 src/main/java/com/tutorialsejong/courseregistration/common/exception/CheckUserException.java delete mode 100644 src/main/java/com/tutorialsejong/courseregistration/common/exception/CourseNotRegisteredException.java create mode 100644 src/main/java/com/tutorialsejong/courseregistration/common/exception/ErrorCode.java create mode 100644 src/main/java/com/tutorialsejong/courseregistration/common/exception/ErrorResponse.java create mode 100644 src/main/java/com/tutorialsejong/courseregistration/common/exception/GlobalErrorCode.java delete mode 100644 src/main/java/com/tutorialsejong/courseregistration/common/exception/NotFoundException.java rename src/main/java/com/tutorialsejong/courseregistration/{auth => common/security}/JwtAuthenticationEntryPoint.java (94%) rename src/main/java/com/tutorialsejong/courseregistration/{auth => common/security}/JwtAuthenticationFilter.java (90%) rename src/main/java/com/tutorialsejong/courseregistration/{auth => common/security}/JwtTokenProvider.java (93%) rename src/main/java/com/tutorialsejong/courseregistration/common/{ => security}/exception/JwtAuthenticationException.java (69%) rename src/main/java/com/tutorialsejong/courseregistration/{ => domain}/auth/controller/AuthController.java (87%) rename src/main/java/com/tutorialsejong/courseregistration/{ => domain}/auth/dto/AuthenticationResult.java (66%) rename src/main/java/com/tutorialsejong/courseregistration/{ => domain}/auth/dto/JwtTokens.java (58%) rename src/main/java/com/tutorialsejong/courseregistration/{ => domain}/auth/dto/LoginRequest.java (80%) rename src/main/java/com/tutorialsejong/courseregistration/{ => domain}/auth/dto/LoginResponse.java (58%) rename src/main/java/com/tutorialsejong/courseregistration/{ => domain}/auth/dto/MacroResponse.java (94%) rename src/main/java/com/tutorialsejong/courseregistration/{ => domain}/auth/service/AuthService.java (78%) rename src/main/java/com/tutorialsejong/courseregistration/{ => domain}/auth/service/CustomUserDetailsService.java (82%) rename src/main/java/com/tutorialsejong/courseregistration/{ => domain}/registration/controller/CourseRegistrationController.java (87%) rename src/main/java/com/tutorialsejong/courseregistration/{ => domain}/registration/dto/CourseRegistrationResponse.java (58%) rename src/main/java/com/tutorialsejong/courseregistration/{ => domain}/registration/entity/CourseRegistration.java (87%) create mode 100644 src/main/java/com/tutorialsejong/courseregistration/domain/registration/exception/CourseAlreadyRegisteredException.java create mode 100644 src/main/java/com/tutorialsejong/courseregistration/domain/registration/exception/CourseRegistrationErrorCode.java rename src/main/java/com/tutorialsejong/courseregistration/{ => domain}/registration/repository/CourseRegistrationRepository.java (70%) rename src/main/java/com/tutorialsejong/courseregistration/{ => domain}/registration/service/CourseRegistrationService.java (68%) rename src/main/java/com/tutorialsejong/courseregistration/{ => domain}/schedule/controller/ScheduleController.java (88%) rename src/main/java/com/tutorialsejong/courseregistration/{ => domain}/schedule/dto/ErrorDto.java (64%) rename src/main/java/com/tutorialsejong/courseregistration/{ => domain}/schedule/dto/ScheduleSearchRequest.java (84%) rename src/main/java/com/tutorialsejong/courseregistration/{ => domain}/schedule/entity/Schedule.java (97%) create mode 100644 src/main/java/com/tutorialsejong/courseregistration/domain/schedule/exception/ScheduleErrorCode.java create mode 100644 src/main/java/com/tutorialsejong/courseregistration/domain/schedule/exception/ScheduleNotFoundException.java rename src/main/java/com/tutorialsejong/courseregistration/{ => domain}/schedule/repository/ScheduleRepository.java (91%) rename src/main/java/com/tutorialsejong/courseregistration/{ => domain}/schedule/service/ScheduleService.java (68%) rename src/main/java/com/tutorialsejong/courseregistration/{ => domain}/user/entity/User.java (94%) create mode 100644 src/main/java/com/tutorialsejong/courseregistration/domain/user/exception/UserErrorCode.java create mode 100644 src/main/java/com/tutorialsejong/courseregistration/domain/user/exception/UserNotFoundException.java rename src/main/java/com/tutorialsejong/courseregistration/{ => domain}/user/repository/InvalidRefreshTokenException.java (69%) rename src/main/java/com/tutorialsejong/courseregistration/{ => domain}/user/repository/UserRepository.java (76%) rename src/main/java/com/tutorialsejong/courseregistration/{ => domain}/wishlist/controller/WishListController.java (81%) rename src/main/java/com/tutorialsejong/courseregistration/{ => domain}/wishlist/dto/CourseInformation.java (61%) rename src/main/java/com/tutorialsejong/courseregistration/{ => domain}/wishlist/dto/WishListRequest.java (57%) rename src/main/java/com/tutorialsejong/courseregistration/{ => domain}/wishlist/entity/WishList.java (80%) create mode 100644 src/main/java/com/tutorialsejong/courseregistration/domain/wishlist/exception/AlreadyInWishlistException.java create mode 100644 src/main/java/com/tutorialsejong/courseregistration/domain/wishlist/exception/WishlistCourseAlreadyRegisteredException.java create mode 100644 src/main/java/com/tutorialsejong/courseregistration/domain/wishlist/exception/WishlistErrorCode.java create mode 100644 src/main/java/com/tutorialsejong/courseregistration/domain/wishlist/exception/WishlistNotFoundException.java rename src/main/java/com/tutorialsejong/courseregistration/{ => domain}/wishlist/repository/WishListRepository.java (69%) rename src/main/java/com/tutorialsejong/courseregistration/{ => domain}/wishlist/service/WishListService.java (62%) delete mode 100644 src/main/java/com/tutorialsejong/courseregistration/schedule/dto/ScheduleRequest.java delete mode 100644 src/main/java/com/tutorialsejong/courseregistration/schedule/dto/ScheduleResponse.java diff --git a/build.gradle b/build.gradle index 0c61490..d35a42f 100644 --- a/build.gradle +++ b/build.gradle @@ -23,6 +23,9 @@ dependencies { implementation 'org.springframework.boot:spring-boot-starter-security' implementation 'org.springframework.boot:spring-boot-starter-validation' implementation 'io.jsonwebtoken:jjwt-api:0.11.5' + implementation 'org.projectlombok:lombok' + + annotationProcessor 'org.projectlombok:lombok' runtimeOnly 'org.mariadb.jdbc:mariadb-java-client' runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.11.5' diff --git a/src/main/java/com/tutorialsejong/courseregistration/common/config/SecurityConfig.java b/src/main/java/com/tutorialsejong/courseregistration/common/config/SecurityConfig.java index 4f6e996..be004d6 100644 --- a/src/main/java/com/tutorialsejong/courseregistration/common/config/SecurityConfig.java +++ b/src/main/java/com/tutorialsejong/courseregistration/common/config/SecurityConfig.java @@ -1,9 +1,9 @@ package com.tutorialsejong.courseregistration.common.config; -import com.tutorialsejong.courseregistration.auth.JwtAuthenticationEntryPoint; -import com.tutorialsejong.courseregistration.auth.JwtAuthenticationFilter; -import com.tutorialsejong.courseregistration.auth.JwtTokenProvider; -import com.tutorialsejong.courseregistration.auth.service.CustomUserDetailsService; +import com.tutorialsejong.courseregistration.common.security.JwtAuthenticationEntryPoint; +import com.tutorialsejong.courseregistration.common.security.JwtAuthenticationFilter; +import com.tutorialsejong.courseregistration.common.security.JwtTokenProvider; +import com.tutorialsejong.courseregistration.domain.auth.service.CustomUserDetailsService; import java.util.Arrays; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -27,7 +27,8 @@ public class SecurityConfig { private final CustomUserDetailsService customUserDetailsService; private final JwtAuthenticationEntryPoint unauthorizedHandler; - public SecurityConfig(JwtTokenProvider tokenProvider,JwtAuthenticationEntryPoint unauthorizedHandler, CustomUserDetailsService customUserDetailsService) { + public SecurityConfig(JwtTokenProvider tokenProvider, JwtAuthenticationEntryPoint unauthorizedHandler, + CustomUserDetailsService customUserDetailsService) { this.tokenProvider = tokenProvider; this.unauthorizedHandler = unauthorizedHandler; this.customUserDetailsService = customUserDetailsService; @@ -44,7 +45,9 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { .cors(cors -> cors .configurationSource(request -> { CorsConfiguration config = new CorsConfiguration(); - config.setAllowedOrigins(Arrays.asList("https://tutorial-sejong.com", "https://frontend.local.com:3000", "http://localhost:3000")); + config.setAllowedOrigins( + Arrays.asList("https://tutorial-sejong.com", "https://frontend.local.com:3000", + "http://localhost:3000")); config.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE", "OPTIONS")); config.setAllowedHeaders(Arrays.asList("*")); config.setAllowCredentials(true); @@ -71,7 +74,8 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { } @Bean - public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception { + public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) + throws Exception { return authenticationConfiguration.getAuthenticationManager(); } diff --git a/src/main/java/com/tutorialsejong/courseregistration/common/exception/AlreadyRegisteredException.java b/src/main/java/com/tutorialsejong/courseregistration/common/exception/AlreadyRegisteredException.java deleted file mode 100644 index 365edfa..0000000 --- a/src/main/java/com/tutorialsejong/courseregistration/common/exception/AlreadyRegisteredException.java +++ /dev/null @@ -1,7 +0,0 @@ -package com.tutorialsejong.courseregistration.common.exception; - -public class AlreadyRegisteredException extends RuntimeException { - public AlreadyRegisteredException(String message) { - super(message); - } -} diff --git a/src/main/java/com/tutorialsejong/courseregistration/common/exception/BadRequestException.java b/src/main/java/com/tutorialsejong/courseregistration/common/exception/BadRequestException.java deleted file mode 100644 index 5062b9f..0000000 --- a/src/main/java/com/tutorialsejong/courseregistration/common/exception/BadRequestException.java +++ /dev/null @@ -1,7 +0,0 @@ -package com.tutorialsejong.courseregistration.common.exception; - -public class BadRequestException extends RuntimeException { - public BadRequestException(String message) { - super(message); - } -} diff --git a/src/main/java/com/tutorialsejong/courseregistration/common/exception/BusinessException.java b/src/main/java/com/tutorialsejong/courseregistration/common/exception/BusinessException.java new file mode 100644 index 0000000..b0d0201 --- /dev/null +++ b/src/main/java/com/tutorialsejong/courseregistration/common/exception/BusinessException.java @@ -0,0 +1,19 @@ +package com.tutorialsejong.courseregistration.common.exception; + +import lombok.Getter; + +@Getter +public class BusinessException extends RuntimeException { + + private final ErrorCode errorCode; + + public BusinessException(ErrorCode errorCode) { + super(errorCode.getMessage()); + this.errorCode = errorCode; + } + + public BusinessException(ErrorCode errorCode, Throwable cause) { + super(errorCode.getMessage(), cause); + this.errorCode = errorCode; + } +} diff --git a/src/main/java/com/tutorialsejong/courseregistration/common/exception/CheckUserException.java b/src/main/java/com/tutorialsejong/courseregistration/common/exception/CheckUserException.java deleted file mode 100644 index ba66a9c..0000000 --- a/src/main/java/com/tutorialsejong/courseregistration/common/exception/CheckUserException.java +++ /dev/null @@ -1,7 +0,0 @@ -package com.tutorialsejong.courseregistration.common.exception; - -public class CheckUserException extends RuntimeException{ - public CheckUserException(String message) { - super(message); - } -} diff --git a/src/main/java/com/tutorialsejong/courseregistration/common/exception/CourseNotRegisteredException.java b/src/main/java/com/tutorialsejong/courseregistration/common/exception/CourseNotRegisteredException.java deleted file mode 100644 index dc3310e..0000000 --- a/src/main/java/com/tutorialsejong/courseregistration/common/exception/CourseNotRegisteredException.java +++ /dev/null @@ -1,7 +0,0 @@ -package com.tutorialsejong.courseregistration.common.exception; - -public class CourseNotRegisteredException extends RuntimeException { - public CourseNotRegisteredException(String message) { - super(message); - } -} diff --git a/src/main/java/com/tutorialsejong/courseregistration/common/exception/ErrorCode.java b/src/main/java/com/tutorialsejong/courseregistration/common/exception/ErrorCode.java new file mode 100644 index 0000000..baa80c1 --- /dev/null +++ b/src/main/java/com/tutorialsejong/courseregistration/common/exception/ErrorCode.java @@ -0,0 +1,16 @@ +package com.tutorialsejong.courseregistration.common.exception; + +import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.annotation.JsonIgnore; +import org.springframework.http.HttpStatus; + +@JsonFormat(shape = JsonFormat.Shape.OBJECT) +public interface ErrorCode { + + String getCode(); + + String getMessage(); + + @JsonIgnore + HttpStatus getStatus(); +} diff --git a/src/main/java/com/tutorialsejong/courseregistration/common/exception/ErrorResponse.java b/src/main/java/com/tutorialsejong/courseregistration/common/exception/ErrorResponse.java new file mode 100644 index 0000000..54fb9ef --- /dev/null +++ b/src/main/java/com/tutorialsejong/courseregistration/common/exception/ErrorResponse.java @@ -0,0 +1,36 @@ +package com.tutorialsejong.courseregistration.common.exception; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonInclude.Include; +import com.fasterxml.jackson.annotation.JsonUnwrapped; +import java.util.List; +import org.springframework.http.ResponseEntity; +import org.springframework.validation.FieldError; + +public record ErrorResponse( + @JsonUnwrapped + ErrorCode errorCode, + + @JsonInclude(Include.NON_EMPTY) + List invalidParams +) { + + public static ErrorResponse from(ErrorCode errorCode) { + return new ErrorResponse(errorCode, null); + } + + public static ErrorResponse of(ErrorCode errorCode, List invalidParams) { + return new ErrorResponse(errorCode, invalidParams); + } + + public ResponseEntity asHttp() { + return ResponseEntity.status(errorCode.getStatus()).body(this); + } + + public record InvalidParam(String name, String reason) { + + public static InvalidParam from(FieldError fieldError) { + return new InvalidParam(fieldError.getField(), fieldError.getDefaultMessage()); + } + } +} diff --git a/src/main/java/com/tutorialsejong/courseregistration/common/exception/GlobalErrorCode.java b/src/main/java/com/tutorialsejong/courseregistration/common/exception/GlobalErrorCode.java new file mode 100644 index 0000000..fc12243 --- /dev/null +++ b/src/main/java/com/tutorialsejong/courseregistration/common/exception/GlobalErrorCode.java @@ -0,0 +1,21 @@ +package com.tutorialsejong.courseregistration.common.exception; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; + +@Getter +@RequiredArgsConstructor +public enum GlobalErrorCode implements ErrorCode { + + INTERNAL_SERVER_ERROR("G001", "서버 내부 오류가 발생했습니다.", HttpStatus.INTERNAL_SERVER_ERROR), + INVALID_INPUT_VALUE("G002", "잘못된 입력값입니다.", HttpStatus.BAD_REQUEST), + RESOURCE_NOT_FOUND("G003", "요청한 리소스를 찾을 수 없습니다.", HttpStatus.NOT_FOUND), + METHOD_NOT_ALLOWED("G004", "허용되지 않는 메서드입니다.", HttpStatus.METHOD_NOT_ALLOWED), + HANDLE_ACCESS_DENIED("G005", "접근이 거부되었습니다.", HttpStatus.FORBIDDEN), + TOO_MANY_REQUESTS("G006", "과도한 요청을 보내셨습니다. 잠시 기다려 주세요.", HttpStatus.TOO_MANY_REQUESTS); + + private final String code; + private final String message; + private final HttpStatus status; +} diff --git a/src/main/java/com/tutorialsejong/courseregistration/common/exception/GlobalExceptionHandler.java b/src/main/java/com/tutorialsejong/courseregistration/common/exception/GlobalExceptionHandler.java index 22ede41..7d69679 100644 --- a/src/main/java/com/tutorialsejong/courseregistration/common/exception/GlobalExceptionHandler.java +++ b/src/main/java/com/tutorialsejong/courseregistration/common/exception/GlobalExceptionHandler.java @@ -1,36 +1,45 @@ package com.tutorialsejong.courseregistration.common.exception; +import com.tutorialsejong.courseregistration.common.security.exception.JwtAuthenticationException; import io.jsonwebtoken.ExpiredJwtException; - import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.stream.Collectors; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.context.support.DefaultMessageSourceResolvable; +import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; +import org.springframework.http.HttpStatusCode; import org.springframework.http.ResponseEntity; import org.springframework.security.authentication.BadCredentialsException; import org.springframework.web.bind.MethodArgumentNotValidException; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice; +import org.springframework.web.context.request.WebRequest; +import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler; @RestControllerAdvice -public class GlobalExceptionHandler { +public class GlobalExceptionHandler extends ResponseEntityExceptionHandler { - private static final Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class); + @ExceptionHandler(BusinessException.class) + public ResponseEntity handleBusinessException(BusinessException ex) { + ErrorCode errorCode = ex.getErrorCode(); + return ErrorResponse.from(errorCode).asHttp(); + } - @ExceptionHandler(MethodArgumentNotValidException.class) - public ResponseEntity handleValidationExceptions(MethodArgumentNotValidException ex) { - List errors = ex.getBindingResult().getAllErrors().stream() - .map(DefaultMessageSourceResolvable::getDefaultMessage) - .collect(Collectors.toList()); - Map body = new HashMap<>(); - body.put("message", errors); - return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(body); + @Override + protected ResponseEntity handleMethodArgumentNotValid( + MethodArgumentNotValidException ex, + HttpHeaders headers, + HttpStatusCode status, + WebRequest request) { + List invalidParams = ex.getBindingResult() + .getFieldErrors() + .stream() + .map(ErrorResponse.InvalidParam::from) + .toList(); + + ErrorCode errorCode = GlobalErrorCode.INVALID_INPUT_VALUE; + return ErrorResponse.of(errorCode, invalidParams).asHttp(); } @ExceptionHandler(BadCredentialsException.class) @@ -49,7 +58,6 @@ public ResponseEntity handleIllegalArgumentException(IllegalArgumentException @ExceptionHandler(JwtAuthenticationException.class) public ResponseEntity handleJwtAuthenticationException(JwtAuthenticationException ex) { - logger.error("JWT authentication error: {}", ex.getMessage()); Map body = new HashMap<>(); if (ex.getCause() instanceof ExpiredJwtException) { body.put("message", Collections.singletonList("토큰이 만료되었습니다.")); @@ -59,40 +67,9 @@ public ResponseEntity handleJwtAuthenticationException(JwtAuthenticationExcep return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(body); } - @ExceptionHandler(CheckUserException.class) - public ResponseEntity handleCheckUserException(CheckUserException ex) { - - return ResponseEntity.status(HttpStatus.NOT_FOUND).body(ex.getMessage()); - } - - @ExceptionHandler(AlreadyRegisteredException.class) - public ResponseEntity handleAlreadyRegisteredException(AlreadyRegisteredException ex) { - return ResponseEntity.status(HttpStatus.CONFLICT).body(ex.getMessage()); - } - - @ExceptionHandler(CourseNotRegisteredException.class) - public ResponseEntity handleCourseNotRegisteredException(CourseNotRegisteredException ex) { - return ResponseEntity.status(HttpStatus.NOT_FOUND).body(ex.getMessage()); - } - @ExceptionHandler(Exception.class) - public ResponseEntity handleGenericException(Exception ex) { - Map body = new HashMap<>(); - body.put("message", "An unexpected error occurred"); - return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(body); - } - - @ExceptionHandler(NotFoundException.class) - public ResponseEntity handleNotFoundException(NotFoundException ex) { - Map body = new HashMap<>(); - body.put("message", ex.getMessage()); - return ResponseEntity.status(HttpStatus.NOT_FOUND).body(body); - } - - @ExceptionHandler(BadRequestException.class) - public ResponseEntity handleBadRequestException(BadRequestException ex) { - Map body = new HashMap<>(); - body.put("message", ex.getMessage()); - return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(body); + public ResponseEntity handleGenericException(Exception ex) { + ErrorCode errorCode = GlobalErrorCode.INTERNAL_SERVER_ERROR; + return ErrorResponse.from(errorCode).asHttp(); } } diff --git a/src/main/java/com/tutorialsejong/courseregistration/common/exception/NotFoundException.java b/src/main/java/com/tutorialsejong/courseregistration/common/exception/NotFoundException.java deleted file mode 100644 index 2a76861..0000000 --- a/src/main/java/com/tutorialsejong/courseregistration/common/exception/NotFoundException.java +++ /dev/null @@ -1,7 +0,0 @@ -package com.tutorialsejong.courseregistration.common.exception; - -public class NotFoundException extends RuntimeException { - public NotFoundException(String message) { - super(message); - } -} diff --git a/src/main/java/com/tutorialsejong/courseregistration/auth/JwtAuthenticationEntryPoint.java b/src/main/java/com/tutorialsejong/courseregistration/common/security/JwtAuthenticationEntryPoint.java similarity index 94% rename from src/main/java/com/tutorialsejong/courseregistration/auth/JwtAuthenticationEntryPoint.java rename to src/main/java/com/tutorialsejong/courseregistration/common/security/JwtAuthenticationEntryPoint.java index 1d9a950..d90edf2 100644 --- a/src/main/java/com/tutorialsejong/courseregistration/auth/JwtAuthenticationEntryPoint.java +++ b/src/main/java/com/tutorialsejong/courseregistration/common/security/JwtAuthenticationEntryPoint.java @@ -1,4 +1,4 @@ -package com.tutorialsejong.courseregistration.auth; +package com.tutorialsejong.courseregistration.common.security; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; diff --git a/src/main/java/com/tutorialsejong/courseregistration/auth/JwtAuthenticationFilter.java b/src/main/java/com/tutorialsejong/courseregistration/common/security/JwtAuthenticationFilter.java similarity index 90% rename from src/main/java/com/tutorialsejong/courseregistration/auth/JwtAuthenticationFilter.java rename to src/main/java/com/tutorialsejong/courseregistration/common/security/JwtAuthenticationFilter.java index 382e558..c84ce86 100644 --- a/src/main/java/com/tutorialsejong/courseregistration/auth/JwtAuthenticationFilter.java +++ b/src/main/java/com/tutorialsejong/courseregistration/common/security/JwtAuthenticationFilter.java @@ -1,7 +1,7 @@ -package com.tutorialsejong.courseregistration.auth; +package com.tutorialsejong.courseregistration.common.security; -import com.tutorialsejong.courseregistration.auth.service.CustomUserDetailsService; -import com.tutorialsejong.courseregistration.common.exception.JwtAuthenticationException; +import com.tutorialsejong.courseregistration.common.security.exception.JwtAuthenticationException; +import com.tutorialsejong.courseregistration.domain.auth.service.CustomUserDetailsService; import io.jsonwebtoken.ExpiredJwtException; import jakarta.servlet.FilterChain; import jakarta.servlet.ServletException; @@ -50,7 +50,8 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse sendErrorResponse(response, HttpServletResponse.SC_UNAUTHORIZED, "Invalid Access Token"); } catch (Exception ex) { logger.error("Could not set user authentication in security context", ex); - sendErrorResponse(response, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "An error occurred while processing your request"); + sendErrorResponse(response, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, + "An error occurred while processing your request"); } } diff --git a/src/main/java/com/tutorialsejong/courseregistration/auth/JwtTokenProvider.java b/src/main/java/com/tutorialsejong/courseregistration/common/security/JwtTokenProvider.java similarity index 93% rename from src/main/java/com/tutorialsejong/courseregistration/auth/JwtTokenProvider.java rename to src/main/java/com/tutorialsejong/courseregistration/common/security/JwtTokenProvider.java index 2218c0b..f21e9cf 100644 --- a/src/main/java/com/tutorialsejong/courseregistration/auth/JwtTokenProvider.java +++ b/src/main/java/com/tutorialsejong/courseregistration/common/security/JwtTokenProvider.java @@ -1,7 +1,7 @@ -package com.tutorialsejong.courseregistration.auth; +package com.tutorialsejong.courseregistration.common.security; -import com.tutorialsejong.courseregistration.auth.service.CustomUserDetailsService; -import com.tutorialsejong.courseregistration.common.exception.JwtAuthenticationException; +import com.tutorialsejong.courseregistration.common.security.exception.JwtAuthenticationException; +import com.tutorialsejong.courseregistration.domain.auth.service.CustomUserDetailsService; import io.jsonwebtoken.Claims; import io.jsonwebtoken.ExpiredJwtException; import io.jsonwebtoken.Jwts; diff --git a/src/main/java/com/tutorialsejong/courseregistration/common/exception/JwtAuthenticationException.java b/src/main/java/com/tutorialsejong/courseregistration/common/security/exception/JwtAuthenticationException.java similarity index 69% rename from src/main/java/com/tutorialsejong/courseregistration/common/exception/JwtAuthenticationException.java rename to src/main/java/com/tutorialsejong/courseregistration/common/security/exception/JwtAuthenticationException.java index e734ea1..02b5a59 100644 --- a/src/main/java/com/tutorialsejong/courseregistration/common/exception/JwtAuthenticationException.java +++ b/src/main/java/com/tutorialsejong/courseregistration/common/security/exception/JwtAuthenticationException.java @@ -1,6 +1,7 @@ -package com.tutorialsejong.courseregistration.common.exception; +package com.tutorialsejong.courseregistration.common.security.exception; public class JwtAuthenticationException extends RuntimeException { + public JwtAuthenticationException(String message, Throwable cause) { super(message, cause); } diff --git a/src/main/java/com/tutorialsejong/courseregistration/auth/controller/AuthController.java b/src/main/java/com/tutorialsejong/courseregistration/domain/auth/controller/AuthController.java similarity index 87% rename from src/main/java/com/tutorialsejong/courseregistration/auth/controller/AuthController.java rename to src/main/java/com/tutorialsejong/courseregistration/domain/auth/controller/AuthController.java index ae55862..5f2a5dc 100644 --- a/src/main/java/com/tutorialsejong/courseregistration/auth/controller/AuthController.java +++ b/src/main/java/com/tutorialsejong/courseregistration/domain/auth/controller/AuthController.java @@ -1,11 +1,11 @@ -package com.tutorialsejong.courseregistration.auth.controller; - -import com.tutorialsejong.courseregistration.auth.dto.AuthenticationResult; -import com.tutorialsejong.courseregistration.auth.dto.JwtTokens; -import com.tutorialsejong.courseregistration.auth.dto.LoginRequest; -import com.tutorialsejong.courseregistration.auth.dto.LoginResponse; -import com.tutorialsejong.courseregistration.auth.dto.MacroResponse; -import com.tutorialsejong.courseregistration.auth.service.AuthService; +package com.tutorialsejong.courseregistration.domain.auth.controller; + +import com.tutorialsejong.courseregistration.domain.auth.dto.AuthenticationResult; +import com.tutorialsejong.courseregistration.domain.auth.dto.JwtTokens; +import com.tutorialsejong.courseregistration.domain.auth.dto.LoginRequest; +import com.tutorialsejong.courseregistration.domain.auth.dto.LoginResponse; +import com.tutorialsejong.courseregistration.domain.auth.dto.MacroResponse; +import com.tutorialsejong.courseregistration.domain.auth.service.AuthService; import jakarta.servlet.http.HttpServletResponse; import jakarta.validation.Valid; diff --git a/src/main/java/com/tutorialsejong/courseregistration/auth/dto/AuthenticationResult.java b/src/main/java/com/tutorialsejong/courseregistration/domain/auth/dto/AuthenticationResult.java similarity index 66% rename from src/main/java/com/tutorialsejong/courseregistration/auth/dto/AuthenticationResult.java rename to src/main/java/com/tutorialsejong/courseregistration/domain/auth/dto/AuthenticationResult.java index ee29a1b..a156410 100644 --- a/src/main/java/com/tutorialsejong/courseregistration/auth/dto/AuthenticationResult.java +++ b/src/main/java/com/tutorialsejong/courseregistration/domain/auth/dto/AuthenticationResult.java @@ -1,4 +1,4 @@ -package com.tutorialsejong.courseregistration.auth.dto; +package com.tutorialsejong.courseregistration.domain.auth.dto; public record AuthenticationResult( String accessToken, diff --git a/src/main/java/com/tutorialsejong/courseregistration/auth/dto/JwtTokens.java b/src/main/java/com/tutorialsejong/courseregistration/domain/auth/dto/JwtTokens.java similarity index 58% rename from src/main/java/com/tutorialsejong/courseregistration/auth/dto/JwtTokens.java rename to src/main/java/com/tutorialsejong/courseregistration/domain/auth/dto/JwtTokens.java index 7dcca89..f2f6f67 100644 --- a/src/main/java/com/tutorialsejong/courseregistration/auth/dto/JwtTokens.java +++ b/src/main/java/com/tutorialsejong/courseregistration/domain/auth/dto/JwtTokens.java @@ -1,4 +1,4 @@ -package com.tutorialsejong.courseregistration.auth.dto; +package com.tutorialsejong.courseregistration.domain.auth.dto; public record JwtTokens( String accessToken, diff --git a/src/main/java/com/tutorialsejong/courseregistration/auth/dto/LoginRequest.java b/src/main/java/com/tutorialsejong/courseregistration/domain/auth/dto/LoginRequest.java similarity index 80% rename from src/main/java/com/tutorialsejong/courseregistration/auth/dto/LoginRequest.java rename to src/main/java/com/tutorialsejong/courseregistration/domain/auth/dto/LoginRequest.java index 1ded328..6aa0f77 100644 --- a/src/main/java/com/tutorialsejong/courseregistration/auth/dto/LoginRequest.java +++ b/src/main/java/com/tutorialsejong/courseregistration/domain/auth/dto/LoginRequest.java @@ -1,4 +1,4 @@ -package com.tutorialsejong.courseregistration.auth.dto; +package com.tutorialsejong.courseregistration.domain.auth.dto; import jakarta.validation.constraints.NotBlank; diff --git a/src/main/java/com/tutorialsejong/courseregistration/auth/dto/LoginResponse.java b/src/main/java/com/tutorialsejong/courseregistration/domain/auth/dto/LoginResponse.java similarity index 58% rename from src/main/java/com/tutorialsejong/courseregistration/auth/dto/LoginResponse.java rename to src/main/java/com/tutorialsejong/courseregistration/domain/auth/dto/LoginResponse.java index f68c197..f71bbdc 100644 --- a/src/main/java/com/tutorialsejong/courseregistration/auth/dto/LoginResponse.java +++ b/src/main/java/com/tutorialsejong/courseregistration/domain/auth/dto/LoginResponse.java @@ -1,4 +1,4 @@ -package com.tutorialsejong.courseregistration.auth.dto; +package com.tutorialsejong.courseregistration.domain.auth.dto; public record LoginResponse( String accessToken, diff --git a/src/main/java/com/tutorialsejong/courseregistration/auth/dto/MacroResponse.java b/src/main/java/com/tutorialsejong/courseregistration/domain/auth/dto/MacroResponse.java similarity index 94% rename from src/main/java/com/tutorialsejong/courseregistration/auth/dto/MacroResponse.java rename to src/main/java/com/tutorialsejong/courseregistration/domain/auth/dto/MacroResponse.java index febb752..3d8b7f7 100644 --- a/src/main/java/com/tutorialsejong/courseregistration/auth/dto/MacroResponse.java +++ b/src/main/java/com/tutorialsejong/courseregistration/domain/auth/dto/MacroResponse.java @@ -1,4 +1,4 @@ -package com.tutorialsejong.courseregistration.auth.dto; +package com.tutorialsejong.courseregistration.domain.auth.dto; public class MacroResponse { private Integer statusCode; diff --git a/src/main/java/com/tutorialsejong/courseregistration/auth/service/AuthService.java b/src/main/java/com/tutorialsejong/courseregistration/domain/auth/service/AuthService.java similarity index 78% rename from src/main/java/com/tutorialsejong/courseregistration/auth/service/AuthService.java rename to src/main/java/com/tutorialsejong/courseregistration/domain/auth/service/AuthService.java index 750e1f6..d657eee 100644 --- a/src/main/java/com/tutorialsejong/courseregistration/auth/service/AuthService.java +++ b/src/main/java/com/tutorialsejong/courseregistration/domain/auth/service/AuthService.java @@ -1,21 +1,20 @@ -package com.tutorialsejong.courseregistration.auth.service; - -import com.tutorialsejong.courseregistration.auth.JwtTokenProvider; -import com.tutorialsejong.courseregistration.auth.dto.AuthenticationResult; -import com.tutorialsejong.courseregistration.auth.dto.JwtTokens; -import com.tutorialsejong.courseregistration.auth.dto.LoginRequest; -import com.tutorialsejong.courseregistration.registration.service.CourseRegistrationService; -import com.tutorialsejong.courseregistration.user.entity.User; -import com.tutorialsejong.courseregistration.user.repository.InvalidRefreshTokenException; -import com.tutorialsejong.courseregistration.user.repository.UserRepository; -import com.tutorialsejong.courseregistration.wishlist.service.WishListService; +package com.tutorialsejong.courseregistration.domain.auth.service; + +import com.tutorialsejong.courseregistration.common.security.JwtTokenProvider; +import com.tutorialsejong.courseregistration.domain.auth.dto.AuthenticationResult; +import com.tutorialsejong.courseregistration.domain.auth.dto.JwtTokens; +import com.tutorialsejong.courseregistration.domain.auth.dto.LoginRequest; +import com.tutorialsejong.courseregistration.domain.registration.service.CourseRegistrationService; +import com.tutorialsejong.courseregistration.domain.user.entity.User; +import com.tutorialsejong.courseregistration.domain.user.exception.UserNotFoundException; +import com.tutorialsejong.courseregistration.domain.user.repository.InvalidRefreshTokenException; +import com.tutorialsejong.courseregistration.domain.user.repository.UserRepository; +import com.tutorialsejong.courseregistration.domain.wishlist.service.WishListService; import jakarta.transaction.Transactional; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.security.core.parameters.P; -import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; @@ -33,7 +32,8 @@ public class AuthService { public AuthService(AuthenticationManager authenticationManager, JwtTokenProvider tokenProvider, UserRepository userRepository, - PasswordEncoder passwordEncoder, WishListService wishListService, CourseRegistrationService courseRegistrationService) { + PasswordEncoder passwordEncoder, WishListService wishListService, + CourseRegistrationService courseRegistrationService) { this.authenticationManager = authenticationManager; this.tokenProvider = tokenProvider; this.userRepository = userRepository; @@ -89,7 +89,7 @@ public JwtTokens refreshAccessToken(String refreshToken) { String username = tokenProvider.getUsernameFromJWT(refreshToken); User user = userRepository.findByStudentId(username) - .orElseThrow(() -> new UsernameNotFoundException("User not found with username: " + username)); + .orElseThrow(() -> new UserNotFoundException()); if (!user.getRefreshToken().equals(refreshToken)) { throw new InvalidRefreshTokenException("Invalid refresh token"); diff --git a/src/main/java/com/tutorialsejong/courseregistration/auth/service/CustomUserDetailsService.java b/src/main/java/com/tutorialsejong/courseregistration/domain/auth/service/CustomUserDetailsService.java similarity index 82% rename from src/main/java/com/tutorialsejong/courseregistration/auth/service/CustomUserDetailsService.java rename to src/main/java/com/tutorialsejong/courseregistration/domain/auth/service/CustomUserDetailsService.java index 5197103..e273511 100644 --- a/src/main/java/com/tutorialsejong/courseregistration/auth/service/CustomUserDetailsService.java +++ b/src/main/java/com/tutorialsejong/courseregistration/domain/auth/service/CustomUserDetailsService.java @@ -1,7 +1,7 @@ -package com.tutorialsejong.courseregistration.auth.service; +package com.tutorialsejong.courseregistration.domain.auth.service; -import com.tutorialsejong.courseregistration.user.entity.User; -import com.tutorialsejong.courseregistration.user.repository.UserRepository; +import com.tutorialsejong.courseregistration.domain.user.entity.User; +import com.tutorialsejong.courseregistration.domain.user.repository.UserRepository; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UsernameNotFoundException; diff --git a/src/main/java/com/tutorialsejong/courseregistration/registration/controller/CourseRegistrationController.java b/src/main/java/com/tutorialsejong/courseregistration/domain/registration/controller/CourseRegistrationController.java similarity index 87% rename from src/main/java/com/tutorialsejong/courseregistration/registration/controller/CourseRegistrationController.java rename to src/main/java/com/tutorialsejong/courseregistration/domain/registration/controller/CourseRegistrationController.java index 8376643..73ce140 100644 --- a/src/main/java/com/tutorialsejong/courseregistration/registration/controller/CourseRegistrationController.java +++ b/src/main/java/com/tutorialsejong/courseregistration/domain/registration/controller/CourseRegistrationController.java @@ -1,9 +1,9 @@ -package com.tutorialsejong.courseregistration.registration.controller; +package com.tutorialsejong.courseregistration.domain.registration.controller; -import com.tutorialsejong.courseregistration.registration.dto.CourseRegistrationResponse; -import com.tutorialsejong.courseregistration.registration.service.CourseRegistrationService; -import com.tutorialsejong.courseregistration.schedule.entity.Schedule; +import com.tutorialsejong.courseregistration.domain.registration.dto.CourseRegistrationResponse; +import com.tutorialsejong.courseregistration.domain.registration.service.CourseRegistrationService; +import com.tutorialsejong.courseregistration.domain.schedule.entity.Schedule; import java.util.List; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; diff --git a/src/main/java/com/tutorialsejong/courseregistration/registration/dto/CourseRegistrationResponse.java b/src/main/java/com/tutorialsejong/courseregistration/domain/registration/dto/CourseRegistrationResponse.java similarity index 58% rename from src/main/java/com/tutorialsejong/courseregistration/registration/dto/CourseRegistrationResponse.java rename to src/main/java/com/tutorialsejong/courseregistration/domain/registration/dto/CourseRegistrationResponse.java index 7b22b36..8a09879 100644 --- a/src/main/java/com/tutorialsejong/courseregistration/registration/dto/CourseRegistrationResponse.java +++ b/src/main/java/com/tutorialsejong/courseregistration/domain/registration/dto/CourseRegistrationResponse.java @@ -1,4 +1,4 @@ -package com.tutorialsejong.courseregistration.registration.dto; +package com.tutorialsejong.courseregistration.domain.registration.dto; public record CourseRegistrationResponse( String studentId, diff --git a/src/main/java/com/tutorialsejong/courseregistration/registration/entity/CourseRegistration.java b/src/main/java/com/tutorialsejong/courseregistration/domain/registration/entity/CourseRegistration.java similarity index 87% rename from src/main/java/com/tutorialsejong/courseregistration/registration/entity/CourseRegistration.java rename to src/main/java/com/tutorialsejong/courseregistration/domain/registration/entity/CourseRegistration.java index 08206e9..3adcce4 100644 --- a/src/main/java/com/tutorialsejong/courseregistration/registration/entity/CourseRegistration.java +++ b/src/main/java/com/tutorialsejong/courseregistration/domain/registration/entity/CourseRegistration.java @@ -1,7 +1,7 @@ -package com.tutorialsejong.courseregistration.registration.entity; +package com.tutorialsejong.courseregistration.domain.registration.entity; -import com.tutorialsejong.courseregistration.schedule.entity.Schedule; -import com.tutorialsejong.courseregistration.user.entity.User; +import com.tutorialsejong.courseregistration.domain.schedule.entity.Schedule; +import com.tutorialsejong.courseregistration.domain.user.entity.User; import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.GeneratedValue; diff --git a/src/main/java/com/tutorialsejong/courseregistration/domain/registration/exception/CourseAlreadyRegisteredException.java b/src/main/java/com/tutorialsejong/courseregistration/domain/registration/exception/CourseAlreadyRegisteredException.java new file mode 100644 index 0000000..64fc3fa --- /dev/null +++ b/src/main/java/com/tutorialsejong/courseregistration/domain/registration/exception/CourseAlreadyRegisteredException.java @@ -0,0 +1,10 @@ +package com.tutorialsejong.courseregistration.domain.registration.exception; + +import com.tutorialsejong.courseregistration.common.exception.BusinessException; + +public class CourseAlreadyRegisteredException extends BusinessException { + + public CourseAlreadyRegisteredException() { + super(CourseRegistrationErrorCode.COURSE_ALREADY_REGISTERED); + } +} diff --git a/src/main/java/com/tutorialsejong/courseregistration/domain/registration/exception/CourseRegistrationErrorCode.java b/src/main/java/com/tutorialsejong/courseregistration/domain/registration/exception/CourseRegistrationErrorCode.java new file mode 100644 index 0000000..eaa6121 --- /dev/null +++ b/src/main/java/com/tutorialsejong/courseregistration/domain/registration/exception/CourseRegistrationErrorCode.java @@ -0,0 +1,18 @@ +package com.tutorialsejong.courseregistration.domain.registration.exception; + +import com.tutorialsejong.courseregistration.common.exception.ErrorCode; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; + +@Getter +@RequiredArgsConstructor +public enum CourseRegistrationErrorCode implements ErrorCode { + + COURSE_ALREADY_REGISTERED("C001", "이미 수강신청된 과목입니다.", HttpStatus.CONFLICT), + ; + + private final String code; + private final String message; + private final HttpStatus status; +} diff --git a/src/main/java/com/tutorialsejong/courseregistration/registration/repository/CourseRegistrationRepository.java b/src/main/java/com/tutorialsejong/courseregistration/domain/registration/repository/CourseRegistrationRepository.java similarity index 70% rename from src/main/java/com/tutorialsejong/courseregistration/registration/repository/CourseRegistrationRepository.java rename to src/main/java/com/tutorialsejong/courseregistration/domain/registration/repository/CourseRegistrationRepository.java index 8d5b057..e5aa91f 100644 --- a/src/main/java/com/tutorialsejong/courseregistration/registration/repository/CourseRegistrationRepository.java +++ b/src/main/java/com/tutorialsejong/courseregistration/domain/registration/repository/CourseRegistrationRepository.java @@ -1,8 +1,8 @@ -package com.tutorialsejong.courseregistration.registration.repository; +package com.tutorialsejong.courseregistration.domain.registration.repository; -import com.tutorialsejong.courseregistration.registration.dto.CourseRegistrationResponse; -import com.tutorialsejong.courseregistration.registration.entity.CourseRegistration; -import com.tutorialsejong.courseregistration.user.entity.User; +import com.tutorialsejong.courseregistration.domain.registration.dto.CourseRegistrationResponse; +import com.tutorialsejong.courseregistration.domain.registration.entity.CourseRegistration; +import com.tutorialsejong.courseregistration.domain.user.entity.User; import java.util.List; import java.util.Optional; import org.springframework.data.jpa.repository.JpaRepository; @@ -17,7 +17,7 @@ public interface CourseRegistrationRepository extends JpaRepository findAllByStudent(User student); - @Query("SELECT new com.tutorialsejong.courseregistration.registration.dto.CourseRegistrationResponse(" + + @Query("SELECT new com.tutorialsejong.courseregistration.domain.registration.dto.CourseRegistrationResponse(" + "cr.student.studentId, cr.schedule.scheduleId) " + "FROM CourseRegistration cr WHERE cr.student.studentId = :studentId") List findCourseRegistrationResponsesByStudentId(@Param("studentId") String studentId); diff --git a/src/main/java/com/tutorialsejong/courseregistration/registration/service/CourseRegistrationService.java b/src/main/java/com/tutorialsejong/courseregistration/domain/registration/service/CourseRegistrationService.java similarity index 68% rename from src/main/java/com/tutorialsejong/courseregistration/registration/service/CourseRegistrationService.java rename to src/main/java/com/tutorialsejong/courseregistration/domain/registration/service/CourseRegistrationService.java index 6161af5..5ecad51 100644 --- a/src/main/java/com/tutorialsejong/courseregistration/registration/service/CourseRegistrationService.java +++ b/src/main/java/com/tutorialsejong/courseregistration/domain/registration/service/CourseRegistrationService.java @@ -1,14 +1,15 @@ -package com.tutorialsejong.courseregistration.registration.service; - -import com.tutorialsejong.courseregistration.common.exception.AlreadyRegisteredException; -import com.tutorialsejong.courseregistration.common.exception.NotFoundException; -import com.tutorialsejong.courseregistration.registration.dto.CourseRegistrationResponse; -import com.tutorialsejong.courseregistration.registration.entity.CourseRegistration; -import com.tutorialsejong.courseregistration.registration.repository.CourseRegistrationRepository; -import com.tutorialsejong.courseregistration.schedule.entity.Schedule; -import com.tutorialsejong.courseregistration.schedule.repository.ScheduleRepository; -import com.tutorialsejong.courseregistration.user.entity.User; -import com.tutorialsejong.courseregistration.user.repository.UserRepository; +package com.tutorialsejong.courseregistration.domain.registration.service; + +import com.tutorialsejong.courseregistration.domain.registration.dto.CourseRegistrationResponse; +import com.tutorialsejong.courseregistration.domain.registration.entity.CourseRegistration; +import com.tutorialsejong.courseregistration.domain.registration.exception.CourseAlreadyRegisteredException; +import com.tutorialsejong.courseregistration.domain.registration.repository.CourseRegistrationRepository; +import com.tutorialsejong.courseregistration.domain.schedule.entity.Schedule; +import com.tutorialsejong.courseregistration.domain.schedule.exception.ScheduleNotFoundException; +import com.tutorialsejong.courseregistration.domain.schedule.repository.ScheduleRepository; +import com.tutorialsejong.courseregistration.domain.user.entity.User; +import com.tutorialsejong.courseregistration.domain.user.exception.UserNotFoundException; +import com.tutorialsejong.courseregistration.domain.user.repository.UserRepository; import java.time.LocalDateTime; import java.util.List; import java.util.stream.Collectors; @@ -34,16 +35,16 @@ public CourseRegistrationService(CourseRegistrationRepository courseRegistration @Transactional public CourseRegistrationResponse registerCourse(String studentId, Long scheduleId) { User student = userRepository.findByStudentId(studentId) - .orElseThrow(() -> new NotFoundException("User not found with id: " + studentId)); + .orElseThrow(() -> new UserNotFoundException()); Schedule schedule = scheduleRepository.findById(scheduleId) - .orElseThrow(() -> new NotFoundException("Schedule not found with id: " + scheduleId)); + .orElseThrow(() -> new ScheduleNotFoundException()); boolean alreadyRegistered = courseRegistrationRepository.findAllByStudent(student) .stream() .anyMatch(registration -> registration.getSchedule().getCuriNo().equals(schedule.getCuriNo())); if (alreadyRegistered) { - throw new AlreadyRegisteredException("Course already registered"); + throw new CourseAlreadyRegisteredException(); } try { @@ -51,14 +52,14 @@ public CourseRegistrationResponse registerCourse(String studentId, Long schedule registration = courseRegistrationRepository.save(registration); return convertToDto(registration); } catch (DataIntegrityViolationException e) { - throw new AlreadyRegisteredException("Course already registered"); + throw new CourseAlreadyRegisteredException(); } } @Transactional(readOnly = true) public List getRegisteredCourses(String studentId) { User student = userRepository.findByStudentId(studentId) - .orElseThrow(() -> new NotFoundException("User not found with id: " + studentId)); + .orElseThrow(() -> new UserNotFoundException()); List registrations = courseRegistrationRepository.findAllByStudent(student); @@ -71,7 +72,7 @@ public List getRegisteredCourses(String studentId) { public void cancelCourseRegistration(String studentId, Long scheduleId) { CourseRegistration registration = courseRegistrationRepository .findByStudentStudentIdAndScheduleScheduleId(studentId, scheduleId) - .orElseThrow(() -> new NotFoundException("Course registration not found")); + .orElseThrow(() -> new ScheduleNotFoundException()); courseRegistrationRepository.delete(registration); } @@ -79,7 +80,7 @@ public void cancelCourseRegistration(String studentId, Long scheduleId) { @Transactional public void cancelAllCourseRegistrations(String studentId) { User student = userRepository.findByStudentId(studentId) - .orElseThrow(() -> new NotFoundException("User not found with id: " + studentId)); + .orElseThrow(() -> new UserNotFoundException()); List registrations = courseRegistrationRepository.findAllByStudent(student); courseRegistrationRepository.deleteAll(registrations); diff --git a/src/main/java/com/tutorialsejong/courseregistration/schedule/controller/ScheduleController.java b/src/main/java/com/tutorialsejong/courseregistration/domain/schedule/controller/ScheduleController.java similarity index 88% rename from src/main/java/com/tutorialsejong/courseregistration/schedule/controller/ScheduleController.java rename to src/main/java/com/tutorialsejong/courseregistration/domain/schedule/controller/ScheduleController.java index 759710d..f2fd6fc 100644 --- a/src/main/java/com/tutorialsejong/courseregistration/schedule/controller/ScheduleController.java +++ b/src/main/java/com/tutorialsejong/courseregistration/domain/schedule/controller/ScheduleController.java @@ -1,9 +1,9 @@ -package com.tutorialsejong.courseregistration.schedule.controller; +package com.tutorialsejong.courseregistration.domain.schedule.controller; -import com.tutorialsejong.courseregistration.schedule.dto.ErrorDto; -import com.tutorialsejong.courseregistration.schedule.dto.ScheduleSearchRequest; -import com.tutorialsejong.courseregistration.schedule.entity.Schedule; -import com.tutorialsejong.courseregistration.schedule.service.ScheduleService; +import com.tutorialsejong.courseregistration.domain.schedule.dto.ErrorDto; +import com.tutorialsejong.courseregistration.domain.schedule.dto.ScheduleSearchRequest; +import com.tutorialsejong.courseregistration.domain.schedule.entity.Schedule; +import com.tutorialsejong.courseregistration.domain.schedule.service.ScheduleService; import java.util.Date; import java.util.List; import java.util.Set; diff --git a/src/main/java/com/tutorialsejong/courseregistration/schedule/dto/ErrorDto.java b/src/main/java/com/tutorialsejong/courseregistration/domain/schedule/dto/ErrorDto.java similarity index 64% rename from src/main/java/com/tutorialsejong/courseregistration/schedule/dto/ErrorDto.java rename to src/main/java/com/tutorialsejong/courseregistration/domain/schedule/dto/ErrorDto.java index 2552483..69b0569 100644 --- a/src/main/java/com/tutorialsejong/courseregistration/schedule/dto/ErrorDto.java +++ b/src/main/java/com/tutorialsejong/courseregistration/domain/schedule/dto/ErrorDto.java @@ -1,4 +1,4 @@ -package com.tutorialsejong.courseregistration.schedule.dto; +package com.tutorialsejong.courseregistration.domain.schedule.dto; import java.util.Date; diff --git a/src/main/java/com/tutorialsejong/courseregistration/schedule/dto/ScheduleSearchRequest.java b/src/main/java/com/tutorialsejong/courseregistration/domain/schedule/dto/ScheduleSearchRequest.java similarity index 84% rename from src/main/java/com/tutorialsejong/courseregistration/schedule/dto/ScheduleSearchRequest.java rename to src/main/java/com/tutorialsejong/courseregistration/domain/schedule/dto/ScheduleSearchRequest.java index 065ad80..6cb44cf 100644 --- a/src/main/java/com/tutorialsejong/courseregistration/schedule/dto/ScheduleSearchRequest.java +++ b/src/main/java/com/tutorialsejong/courseregistration/domain/schedule/dto/ScheduleSearchRequest.java @@ -1,4 +1,4 @@ -package com.tutorialsejong.courseregistration.schedule.dto; +package com.tutorialsejong.courseregistration.domain.schedule.dto; import com.fasterxml.jackson.annotation.JsonInclude; diff --git a/src/main/java/com/tutorialsejong/courseregistration/schedule/entity/Schedule.java b/src/main/java/com/tutorialsejong/courseregistration/domain/schedule/entity/Schedule.java similarity index 97% rename from src/main/java/com/tutorialsejong/courseregistration/schedule/entity/Schedule.java rename to src/main/java/com/tutorialsejong/courseregistration/domain/schedule/entity/Schedule.java index 36ce4f2..11794dd 100644 --- a/src/main/java/com/tutorialsejong/courseregistration/schedule/entity/Schedule.java +++ b/src/main/java/com/tutorialsejong/courseregistration/domain/schedule/entity/Schedule.java @@ -1,4 +1,4 @@ -package com.tutorialsejong.courseregistration.schedule.entity; +package com.tutorialsejong.courseregistration.domain.schedule.entity; import jakarta.persistence.*; diff --git a/src/main/java/com/tutorialsejong/courseregistration/domain/schedule/exception/ScheduleErrorCode.java b/src/main/java/com/tutorialsejong/courseregistration/domain/schedule/exception/ScheduleErrorCode.java new file mode 100644 index 0000000..4042485 --- /dev/null +++ b/src/main/java/com/tutorialsejong/courseregistration/domain/schedule/exception/ScheduleErrorCode.java @@ -0,0 +1,18 @@ +package com.tutorialsejong.courseregistration.domain.schedule.exception; + +import com.tutorialsejong.courseregistration.common.exception.ErrorCode; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; + +@Getter +@RequiredArgsConstructor +public enum ScheduleErrorCode implements ErrorCode { + + SCHEDULE_NOT_FOUND("S001", "존재하지 않는 강의입니다.", HttpStatus.NOT_FOUND), + ; + + private final String code; + private final String message; + private final HttpStatus status; +} diff --git a/src/main/java/com/tutorialsejong/courseregistration/domain/schedule/exception/ScheduleNotFoundException.java b/src/main/java/com/tutorialsejong/courseregistration/domain/schedule/exception/ScheduleNotFoundException.java new file mode 100644 index 0000000..276dc5f --- /dev/null +++ b/src/main/java/com/tutorialsejong/courseregistration/domain/schedule/exception/ScheduleNotFoundException.java @@ -0,0 +1,10 @@ +package com.tutorialsejong.courseregistration.domain.schedule.exception; + +import com.tutorialsejong.courseregistration.common.exception.BusinessException; + +public class ScheduleNotFoundException extends BusinessException { + + public ScheduleNotFoundException() { + super(ScheduleErrorCode.SCHEDULE_NOT_FOUND); + } +} diff --git a/src/main/java/com/tutorialsejong/courseregistration/schedule/repository/ScheduleRepository.java b/src/main/java/com/tutorialsejong/courseregistration/domain/schedule/repository/ScheduleRepository.java similarity index 91% rename from src/main/java/com/tutorialsejong/courseregistration/schedule/repository/ScheduleRepository.java rename to src/main/java/com/tutorialsejong/courseregistration/domain/schedule/repository/ScheduleRepository.java index 50713ba..24baeb3 100644 --- a/src/main/java/com/tutorialsejong/courseregistration/schedule/repository/ScheduleRepository.java +++ b/src/main/java/com/tutorialsejong/courseregistration/domain/schedule/repository/ScheduleRepository.java @@ -1,6 +1,6 @@ -package com.tutorialsejong.courseregistration.schedule.repository; +package com.tutorialsejong.courseregistration.domain.schedule.repository; -import com.tutorialsejong.courseregistration.schedule.entity.Schedule; +import com.tutorialsejong.courseregistration.domain.schedule.entity.Schedule; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; diff --git a/src/main/java/com/tutorialsejong/courseregistration/schedule/service/ScheduleService.java b/src/main/java/com/tutorialsejong/courseregistration/domain/schedule/service/ScheduleService.java similarity index 68% rename from src/main/java/com/tutorialsejong/courseregistration/schedule/service/ScheduleService.java rename to src/main/java/com/tutorialsejong/courseregistration/domain/schedule/service/ScheduleService.java index 978fb91..0991583 100644 --- a/src/main/java/com/tutorialsejong/courseregistration/schedule/service/ScheduleService.java +++ b/src/main/java/com/tutorialsejong/courseregistration/domain/schedule/service/ScheduleService.java @@ -1,12 +1,13 @@ -package com.tutorialsejong.courseregistration.schedule.service; - -import com.tutorialsejong.courseregistration.registration.entity.CourseRegistration; -import com.tutorialsejong.courseregistration.registration.repository.CourseRegistrationRepository; -import com.tutorialsejong.courseregistration.schedule.dto.ScheduleSearchRequest; -import com.tutorialsejong.courseregistration.schedule.entity.Schedule; -import com.tutorialsejong.courseregistration.schedule.repository.ScheduleRepository; -import com.tutorialsejong.courseregistration.user.entity.User; -import com.tutorialsejong.courseregistration.user.repository.UserRepository; +package com.tutorialsejong.courseregistration.domain.schedule.service; + +import com.tutorialsejong.courseregistration.domain.registration.entity.CourseRegistration; +import com.tutorialsejong.courseregistration.domain.registration.repository.CourseRegistrationRepository; +import com.tutorialsejong.courseregistration.domain.schedule.dto.ScheduleSearchRequest; +import com.tutorialsejong.courseregistration.domain.schedule.entity.Schedule; +import com.tutorialsejong.courseregistration.domain.schedule.repository.ScheduleRepository; +import com.tutorialsejong.courseregistration.domain.user.entity.User; +import com.tutorialsejong.courseregistration.domain.user.exception.UserNotFoundException; +import com.tutorialsejong.courseregistration.domain.user.repository.UserRepository; import java.util.List; import java.util.stream.Collectors; import org.springframework.beans.factory.annotation.Autowired; @@ -29,7 +30,7 @@ public ScheduleService(ScheduleRepository scheduleRepository, UserRepository use public List getSearchResultSchedules(ScheduleSearchRequest scheduleSearchRequest, String studentId) { User user = userRepository.findByStudentId(studentId) - .orElseThrow(() -> new IllegalArgumentException("존재하지 않는 사용자입니다.")); + .orElseThrow(() -> new UserNotFoundException()); List findAllByResult = scheduleRepository.findAllBy( scheduleSearchRequest.curiNo(), diff --git a/src/main/java/com/tutorialsejong/courseregistration/user/entity/User.java b/src/main/java/com/tutorialsejong/courseregistration/domain/user/entity/User.java similarity index 94% rename from src/main/java/com/tutorialsejong/courseregistration/user/entity/User.java rename to src/main/java/com/tutorialsejong/courseregistration/domain/user/entity/User.java index 699ee3e..3c13a54 100644 --- a/src/main/java/com/tutorialsejong/courseregistration/user/entity/User.java +++ b/src/main/java/com/tutorialsejong/courseregistration/domain/user/entity/User.java @@ -1,4 +1,4 @@ -package com.tutorialsejong.courseregistration.user.entity; +package com.tutorialsejong.courseregistration.domain.user.entity; import jakarta.persistence.Column; import jakarta.persistence.Entity; diff --git a/src/main/java/com/tutorialsejong/courseregistration/domain/user/exception/UserErrorCode.java b/src/main/java/com/tutorialsejong/courseregistration/domain/user/exception/UserErrorCode.java new file mode 100644 index 0000000..e91089c --- /dev/null +++ b/src/main/java/com/tutorialsejong/courseregistration/domain/user/exception/UserErrorCode.java @@ -0,0 +1,18 @@ +package com.tutorialsejong.courseregistration.domain.user.exception; + +import com.tutorialsejong.courseregistration.common.exception.ErrorCode; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; + +@Getter +@RequiredArgsConstructor +public enum UserErrorCode implements ErrorCode { + + USER_NOT_FOUND("U001", "존재하지 않는 사용자입니다.", HttpStatus.NOT_FOUND), + ; + + private final String code; + private final String message; + private final HttpStatus status; +} diff --git a/src/main/java/com/tutorialsejong/courseregistration/domain/user/exception/UserNotFoundException.java b/src/main/java/com/tutorialsejong/courseregistration/domain/user/exception/UserNotFoundException.java new file mode 100644 index 0000000..bc2bcbc --- /dev/null +++ b/src/main/java/com/tutorialsejong/courseregistration/domain/user/exception/UserNotFoundException.java @@ -0,0 +1,10 @@ +package com.tutorialsejong.courseregistration.domain.user.exception; + +import com.tutorialsejong.courseregistration.common.exception.BusinessException; + +public class UserNotFoundException extends BusinessException { + + public UserNotFoundException() { + super(UserErrorCode.USER_NOT_FOUND); + } +} diff --git a/src/main/java/com/tutorialsejong/courseregistration/user/repository/InvalidRefreshTokenException.java b/src/main/java/com/tutorialsejong/courseregistration/domain/user/repository/InvalidRefreshTokenException.java similarity index 69% rename from src/main/java/com/tutorialsejong/courseregistration/user/repository/InvalidRefreshTokenException.java rename to src/main/java/com/tutorialsejong/courseregistration/domain/user/repository/InvalidRefreshTokenException.java index a109043..f7eba06 100644 --- a/src/main/java/com/tutorialsejong/courseregistration/user/repository/InvalidRefreshTokenException.java +++ b/src/main/java/com/tutorialsejong/courseregistration/domain/user/repository/InvalidRefreshTokenException.java @@ -1,4 +1,4 @@ -package com.tutorialsejong.courseregistration.user.repository; +package com.tutorialsejong.courseregistration.domain.user.repository; public class InvalidRefreshTokenException extends RuntimeException { public InvalidRefreshTokenException(String message) { diff --git a/src/main/java/com/tutorialsejong/courseregistration/user/repository/UserRepository.java b/src/main/java/com/tutorialsejong/courseregistration/domain/user/repository/UserRepository.java similarity index 76% rename from src/main/java/com/tutorialsejong/courseregistration/user/repository/UserRepository.java rename to src/main/java/com/tutorialsejong/courseregistration/domain/user/repository/UserRepository.java index 4c41545..2a50617 100644 --- a/src/main/java/com/tutorialsejong/courseregistration/user/repository/UserRepository.java +++ b/src/main/java/com/tutorialsejong/courseregistration/domain/user/repository/UserRepository.java @@ -1,6 +1,6 @@ -package com.tutorialsejong.courseregistration.user.repository; +package com.tutorialsejong.courseregistration.domain.user.repository; -import com.tutorialsejong.courseregistration.user.entity.User; +import com.tutorialsejong.courseregistration.domain.user.entity.User; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Modifying; import org.springframework.data.jpa.repository.Query; diff --git a/src/main/java/com/tutorialsejong/courseregistration/wishlist/controller/WishListController.java b/src/main/java/com/tutorialsejong/courseregistration/domain/wishlist/controller/WishListController.java similarity index 81% rename from src/main/java/com/tutorialsejong/courseregistration/wishlist/controller/WishListController.java rename to src/main/java/com/tutorialsejong/courseregistration/domain/wishlist/controller/WishListController.java index 627fd5f..c401a7e 100644 --- a/src/main/java/com/tutorialsejong/courseregistration/wishlist/controller/WishListController.java +++ b/src/main/java/com/tutorialsejong/courseregistration/domain/wishlist/controller/WishListController.java @@ -1,8 +1,8 @@ -package com.tutorialsejong.courseregistration.wishlist.controller; +package com.tutorialsejong.courseregistration.domain.wishlist.controller; -import com.tutorialsejong.courseregistration.wishlist.dto.WishListRequest; -import com.tutorialsejong.courseregistration.wishlist.service.WishListService; -import com.tutorialsejong.courseregistration.schedule.entity.Schedule; +import com.tutorialsejong.courseregistration.domain.wishlist.dto.WishListRequest; +import com.tutorialsejong.courseregistration.domain.wishlist.service.WishListService; +import com.tutorialsejong.courseregistration.domain.schedule.entity.Schedule; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; diff --git a/src/main/java/com/tutorialsejong/courseregistration/wishlist/dto/CourseInformation.java b/src/main/java/com/tutorialsejong/courseregistration/domain/wishlist/dto/CourseInformation.java similarity index 61% rename from src/main/java/com/tutorialsejong/courseregistration/wishlist/dto/CourseInformation.java rename to src/main/java/com/tutorialsejong/courseregistration/domain/wishlist/dto/CourseInformation.java index e7c1b54..b758e21 100644 --- a/src/main/java/com/tutorialsejong/courseregistration/wishlist/dto/CourseInformation.java +++ b/src/main/java/com/tutorialsejong/courseregistration/domain/wishlist/dto/CourseInformation.java @@ -1,4 +1,4 @@ -package com.tutorialsejong.courseregistration.wishlist.dto; +package com.tutorialsejong.courseregistration.domain.wishlist.dto; public record CourseInformation( String curiNo, diff --git a/src/main/java/com/tutorialsejong/courseregistration/wishlist/dto/WishListRequest.java b/src/main/java/com/tutorialsejong/courseregistration/domain/wishlist/dto/WishListRequest.java similarity index 57% rename from src/main/java/com/tutorialsejong/courseregistration/wishlist/dto/WishListRequest.java rename to src/main/java/com/tutorialsejong/courseregistration/domain/wishlist/dto/WishListRequest.java index 203902a..6d35a41 100644 --- a/src/main/java/com/tutorialsejong/courseregistration/wishlist/dto/WishListRequest.java +++ b/src/main/java/com/tutorialsejong/courseregistration/domain/wishlist/dto/WishListRequest.java @@ -1,4 +1,4 @@ -package com.tutorialsejong.courseregistration.wishlist.dto; +package com.tutorialsejong.courseregistration.domain.wishlist.dto; public record WishListRequest( diff --git a/src/main/java/com/tutorialsejong/courseregistration/wishlist/entity/WishList.java b/src/main/java/com/tutorialsejong/courseregistration/domain/wishlist/entity/WishList.java similarity index 80% rename from src/main/java/com/tutorialsejong/courseregistration/wishlist/entity/WishList.java rename to src/main/java/com/tutorialsejong/courseregistration/domain/wishlist/entity/WishList.java index 354b90e..6963f87 100644 --- a/src/main/java/com/tutorialsejong/courseregistration/wishlist/entity/WishList.java +++ b/src/main/java/com/tutorialsejong/courseregistration/domain/wishlist/entity/WishList.java @@ -1,7 +1,7 @@ -package com.tutorialsejong.courseregistration.wishlist.entity; +package com.tutorialsejong.courseregistration.domain.wishlist.entity; -import com.tutorialsejong.courseregistration.schedule.entity.Schedule; -import com.tutorialsejong.courseregistration.user.entity.User; +import com.tutorialsejong.courseregistration.domain.schedule.entity.Schedule; +import com.tutorialsejong.courseregistration.domain.user.entity.User; import jakarta.persistence.*; @Entity @@ -45,4 +45,4 @@ public Schedule getScheduleId() { public void setScheduleId(Schedule scheduleId) { this.scheduleId = scheduleId; } -} \ No newline at end of file +} diff --git a/src/main/java/com/tutorialsejong/courseregistration/domain/wishlist/exception/AlreadyInWishlistException.java b/src/main/java/com/tutorialsejong/courseregistration/domain/wishlist/exception/AlreadyInWishlistException.java new file mode 100644 index 0000000..5bdc790 --- /dev/null +++ b/src/main/java/com/tutorialsejong/courseregistration/domain/wishlist/exception/AlreadyInWishlistException.java @@ -0,0 +1,10 @@ +package com.tutorialsejong.courseregistration.domain.wishlist.exception; + +import com.tutorialsejong.courseregistration.common.exception.BusinessException; + +public class AlreadyInWishlistException extends BusinessException { + + public AlreadyInWishlistException() { + super(WishlistErrorCode.ALREADY_IN_WISHLIST); + } +} diff --git a/src/main/java/com/tutorialsejong/courseregistration/domain/wishlist/exception/WishlistCourseAlreadyRegisteredException.java b/src/main/java/com/tutorialsejong/courseregistration/domain/wishlist/exception/WishlistCourseAlreadyRegisteredException.java new file mode 100644 index 0000000..14a5082 --- /dev/null +++ b/src/main/java/com/tutorialsejong/courseregistration/domain/wishlist/exception/WishlistCourseAlreadyRegisteredException.java @@ -0,0 +1,10 @@ +package com.tutorialsejong.courseregistration.domain.wishlist.exception; + +import com.tutorialsejong.courseregistration.common.exception.BusinessException; + +public class WishlistCourseAlreadyRegisteredException extends BusinessException { + + public WishlistCourseAlreadyRegisteredException() { + super(WishlistErrorCode.WISHLIST_COURSE_ALREADY_REGISTERED); + } +} diff --git a/src/main/java/com/tutorialsejong/courseregistration/domain/wishlist/exception/WishlistErrorCode.java b/src/main/java/com/tutorialsejong/courseregistration/domain/wishlist/exception/WishlistErrorCode.java new file mode 100644 index 0000000..f556428 --- /dev/null +++ b/src/main/java/com/tutorialsejong/courseregistration/domain/wishlist/exception/WishlistErrorCode.java @@ -0,0 +1,20 @@ +package com.tutorialsejong.courseregistration.domain.wishlist.exception; + +import com.tutorialsejong.courseregistration.common.exception.ErrorCode; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; + +@Getter +@RequiredArgsConstructor +public enum WishlistErrorCode implements ErrorCode { + + ALREADY_IN_WISHLIST("W001", "이미 관심과목 담기된 과목입니다.", HttpStatus.CONFLICT), + WISHLIST_COURSE_ALREADY_REGISTERED("W002", "이미 수강신청된 과목은 관심과목으로 담을 수 없습니다.", HttpStatus.CONFLICT), + WISHLIST_NOT_FOUND("W003", "존재하지 않는 과목입니다.", HttpStatus.NOT_FOUND), + ; + + private final String code; + private final String message; + private final HttpStatus status; +} diff --git a/src/main/java/com/tutorialsejong/courseregistration/domain/wishlist/exception/WishlistNotFoundException.java b/src/main/java/com/tutorialsejong/courseregistration/domain/wishlist/exception/WishlistNotFoundException.java new file mode 100644 index 0000000..6e7ae8a --- /dev/null +++ b/src/main/java/com/tutorialsejong/courseregistration/domain/wishlist/exception/WishlistNotFoundException.java @@ -0,0 +1,10 @@ +package com.tutorialsejong.courseregistration.domain.wishlist.exception; + +import com.tutorialsejong.courseregistration.common.exception.BusinessException; + +public class WishlistNotFoundException extends BusinessException { + + public WishlistNotFoundException() { + super(WishlistErrorCode.WISHLIST_NOT_FOUND); + } +} diff --git a/src/main/java/com/tutorialsejong/courseregistration/wishlist/repository/WishListRepository.java b/src/main/java/com/tutorialsejong/courseregistration/domain/wishlist/repository/WishListRepository.java similarity index 69% rename from src/main/java/com/tutorialsejong/courseregistration/wishlist/repository/WishListRepository.java rename to src/main/java/com/tutorialsejong/courseregistration/domain/wishlist/repository/WishListRepository.java index 8e94cf1..aa86338 100644 --- a/src/main/java/com/tutorialsejong/courseregistration/wishlist/repository/WishListRepository.java +++ b/src/main/java/com/tutorialsejong/courseregistration/domain/wishlist/repository/WishListRepository.java @@ -1,8 +1,8 @@ -package com.tutorialsejong.courseregistration.wishlist.repository; +package com.tutorialsejong.courseregistration.domain.wishlist.repository; -import com.tutorialsejong.courseregistration.schedule.entity.Schedule; -import com.tutorialsejong.courseregistration.user.entity.User; -import com.tutorialsejong.courseregistration.wishlist.entity.WishList; +import com.tutorialsejong.courseregistration.domain.schedule.entity.Schedule; +import com.tutorialsejong.courseregistration.domain.user.entity.User; +import com.tutorialsejong.courseregistration.domain.wishlist.entity.WishList; import java.util.List; import java.util.Optional; import org.springframework.data.jpa.repository.JpaRepository; diff --git a/src/main/java/com/tutorialsejong/courseregistration/wishlist/service/WishListService.java b/src/main/java/com/tutorialsejong/courseregistration/domain/wishlist/service/WishListService.java similarity index 62% rename from src/main/java/com/tutorialsejong/courseregistration/wishlist/service/WishListService.java rename to src/main/java/com/tutorialsejong/courseregistration/domain/wishlist/service/WishListService.java index afcf419..d2b10ca 100644 --- a/src/main/java/com/tutorialsejong/courseregistration/wishlist/service/WishListService.java +++ b/src/main/java/com/tutorialsejong/courseregistration/domain/wishlist/service/WishListService.java @@ -1,15 +1,17 @@ -package com.tutorialsejong.courseregistration.wishlist.service; - -import com.tutorialsejong.courseregistration.common.exception.AlreadyRegisteredException; -import com.tutorialsejong.courseregistration.common.exception.BadRequestException; -import com.tutorialsejong.courseregistration.common.exception.NotFoundException; -import com.tutorialsejong.courseregistration.registration.repository.CourseRegistrationRepository; -import com.tutorialsejong.courseregistration.schedule.entity.Schedule; -import com.tutorialsejong.courseregistration.schedule.repository.ScheduleRepository; -import com.tutorialsejong.courseregistration.user.entity.User; -import com.tutorialsejong.courseregistration.user.repository.UserRepository; -import com.tutorialsejong.courseregistration.wishlist.entity.WishList; -import com.tutorialsejong.courseregistration.wishlist.repository.WishListRepository; +package com.tutorialsejong.courseregistration.domain.wishlist.service; + +import com.tutorialsejong.courseregistration.domain.registration.repository.CourseRegistrationRepository; +import com.tutorialsejong.courseregistration.domain.schedule.entity.Schedule; +import com.tutorialsejong.courseregistration.domain.schedule.exception.ScheduleNotFoundException; +import com.tutorialsejong.courseregistration.domain.schedule.repository.ScheduleRepository; +import com.tutorialsejong.courseregistration.domain.user.entity.User; +import com.tutorialsejong.courseregistration.domain.user.exception.UserNotFoundException; +import com.tutorialsejong.courseregistration.domain.user.repository.UserRepository; +import com.tutorialsejong.courseregistration.domain.wishlist.entity.WishList; +import com.tutorialsejong.courseregistration.domain.wishlist.exception.AlreadyInWishlistException; +import com.tutorialsejong.courseregistration.domain.wishlist.exception.WishlistCourseAlreadyRegisteredException; +import com.tutorialsejong.courseregistration.domain.wishlist.exception.WishlistNotFoundException; +import com.tutorialsejong.courseregistration.domain.wishlist.repository.WishListRepository; import java.util.List; import java.util.stream.Collectors; import org.springframework.stereotype.Service; @@ -42,14 +44,14 @@ public void saveWishListItem(String studentId, Long scheduleId) { .anyMatch(wishList -> wishList.getScheduleId().getCuriNo().equals(curiNo)); if (existsInWishList) { - throw new AlreadyRegisteredException("이미 관심과목 담기된 과목입니다."); + throw new AlreadyInWishlistException(); } boolean existsInRegistration = courseRegistrationRepository .existsByStudentStudentIdAndScheduleScheduleId(studentId, scheduleId); if (existsInRegistration) { - throw new AlreadyRegisteredException("이미 수강신청된 과목입니다"); + throw new WishlistCourseAlreadyRegisteredException(); } WishList newWishList = new WishList(user, schedule); @@ -63,18 +65,19 @@ public List getWishList(String studentId) { return wishListList.stream() .map(WishList::getScheduleId) - .filter(schedule -> !courseRegistrationRepository.existsByStudentStudentIdAndScheduleScheduleId(studentId, schedule.getScheduleId())) // 수강신청된 과목 제외 + .filter(schedule -> !courseRegistrationRepository.existsByStudentStudentIdAndScheduleScheduleId( + studentId, schedule.getScheduleId())) // 수강신청된 과목 제외 .collect(Collectors.toList()); } public User checkExistUser(String studentId) { return userRepository.findByStudentId(studentId) - .orElseThrow(() -> new NotFoundException(studentId + "회원이 존재하지 않습니다.")); + .orElseThrow(() -> new UserNotFoundException()); } public Schedule checkExistSchedule(Long scheduleId) { return scheduleRepository.findById(scheduleId) - .orElseThrow(() -> new NotFoundException(scheduleId + "과목이 존재하지않습니다.")); + .orElseThrow(() -> new ScheduleNotFoundException()); } public void deleteWishListItem(String studentId, Long scheduleId) { @@ -82,7 +85,7 @@ public void deleteWishListItem(String studentId, Long scheduleId) { Schedule schedule = checkExistSchedule(scheduleId); WishList wishList = wishListRepository.findByStudentIdAndScheduleId(user, schedule) - .orElseThrow(() -> new BadRequestException("신청하지 않은 과목입니다.")); + .orElseThrow(() -> new WishlistNotFoundException()); wishListRepository.delete(wishList); } diff --git a/src/main/java/com/tutorialsejong/courseregistration/schedule/dto/ScheduleRequest.java b/src/main/java/com/tutorialsejong/courseregistration/schedule/dto/ScheduleRequest.java deleted file mode 100644 index eb5e535..0000000 --- a/src/main/java/com/tutorialsejong/courseregistration/schedule/dto/ScheduleRequest.java +++ /dev/null @@ -1,4 +0,0 @@ -package com.tutorialsejong.courseregistration.schedule.dto; - -public class ScheduleRequest { -} diff --git a/src/main/java/com/tutorialsejong/courseregistration/schedule/dto/ScheduleResponse.java b/src/main/java/com/tutorialsejong/courseregistration/schedule/dto/ScheduleResponse.java deleted file mode 100644 index 3c60306..0000000 --- a/src/main/java/com/tutorialsejong/courseregistration/schedule/dto/ScheduleResponse.java +++ /dev/null @@ -1,4 +0,0 @@ -package com.tutorialsejong.courseregistration.schedule.dto; - -public class ScheduleResponse { -} diff --git a/src/test/java/com/tutorialsejong/courseregistration/CourseRegistrationControllerTest.java b/src/test/java/com/tutorialsejong/courseregistration/CourseRegistrationControllerTest.java index 34e098c..9242435 100644 --- a/src/test/java/com/tutorialsejong/courseregistration/CourseRegistrationControllerTest.java +++ b/src/test/java/com/tutorialsejong/courseregistration/CourseRegistrationControllerTest.java @@ -3,8 +3,8 @@ import static io.restassured.RestAssured.given; import static org.assertj.core.api.Assertions.assertThat; -import com.tutorialsejong.courseregistration.registration.repository.CourseRegistrationRepository; -import com.tutorialsejong.courseregistration.schedule.entity.Schedule; +import com.tutorialsejong.courseregistration.domain.registration.repository.CourseRegistrationRepository; +import com.tutorialsejong.courseregistration.domain.schedule.entity.Schedule; import io.restassured.RestAssured; import io.restassured.http.ContentType; import io.restassured.response.Response; From 4636cd3c43f50bd2d8bbbf5cdef9c724cb8a29df Mon Sep 17 00:00:00 2001 From: zhy2on <52701529+zhy2on@users.noreply.github.com> Date: Wed, 11 Sep 2024 11:32:40 +0900 Subject: [PATCH 14/28] =?UTF-8?q?refactor(TS-21):=20JWT=20=EC=9D=B8?= =?UTF-8?q?=EC=A6=9D=20=EC=98=88=EC=99=B8=20=EC=B2=98=EB=A6=AC=20=EA=B0=9C?= =?UTF-8?q?=EC=84=A0=20(#77)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - JWT 관련 예외를 집중 처리하는 JwtExceptionFilter 구현 - Spring Security 인증 예외를 관리하는 JwtAuthenticationEntryPoint 구현 - 세분화된 오류 보고를 위해 SecurityErrorCode enum 확장 - 새로운 예외 처리 컴포넌트를 적용하도록 SecurityConfig 수정 --- .../common/config/JacksonConfig.java | 14 ++++ .../common/config/SecurityConfig.java | 20 ++---- .../common/exception/ErrorResponse.java | 13 +++- .../exception/GlobalExceptionHandler.java | 30 +-------- .../security/JwtAuthenticationEntryPoint.java | 12 ++-- .../security/JwtAuthenticationFilter.java | 65 +++++++------------ .../common/security/JwtExceptionFilter.java | 24 +++++++ .../common/security/JwtTokenProvider.java | 14 ++-- .../exception/JwtAuthenticationException.java | 8 --- .../security/exception/JwtException.java | 21 ++++++ .../exception/JwtTokenExpiredException.java | 8 +++ .../exception/JwtTokenInvalidException.java | 8 +++ .../security/exception/SecurityErrorCode.java | 20 ++++++ .../common/utils/JsonUtils.java | 21 ++++++ 14 files changed, 173 insertions(+), 105 deletions(-) create mode 100644 src/main/java/com/tutorialsejong/courseregistration/common/config/JacksonConfig.java create mode 100644 src/main/java/com/tutorialsejong/courseregistration/common/security/JwtExceptionFilter.java delete mode 100644 src/main/java/com/tutorialsejong/courseregistration/common/security/exception/JwtAuthenticationException.java create mode 100644 src/main/java/com/tutorialsejong/courseregistration/common/security/exception/JwtException.java create mode 100644 src/main/java/com/tutorialsejong/courseregistration/common/security/exception/JwtTokenExpiredException.java create mode 100644 src/main/java/com/tutorialsejong/courseregistration/common/security/exception/JwtTokenInvalidException.java create mode 100644 src/main/java/com/tutorialsejong/courseregistration/common/security/exception/SecurityErrorCode.java create mode 100644 src/main/java/com/tutorialsejong/courseregistration/common/utils/JsonUtils.java diff --git a/src/main/java/com/tutorialsejong/courseregistration/common/config/JacksonConfig.java b/src/main/java/com/tutorialsejong/courseregistration/common/config/JacksonConfig.java new file mode 100644 index 0000000..0667ca5 --- /dev/null +++ b/src/main/java/com/tutorialsejong/courseregistration/common/config/JacksonConfig.java @@ -0,0 +1,14 @@ +package com.tutorialsejong.courseregistration.common.config; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class JacksonConfig { + + @Bean + public ObjectMapper objectMapper() { + return new ObjectMapper(); + } +} diff --git a/src/main/java/com/tutorialsejong/courseregistration/common/config/SecurityConfig.java b/src/main/java/com/tutorialsejong/courseregistration/common/config/SecurityConfig.java index be004d6..1db58e4 100644 --- a/src/main/java/com/tutorialsejong/courseregistration/common/config/SecurityConfig.java +++ b/src/main/java/com/tutorialsejong/courseregistration/common/config/SecurityConfig.java @@ -2,9 +2,10 @@ import com.tutorialsejong.courseregistration.common.security.JwtAuthenticationEntryPoint; import com.tutorialsejong.courseregistration.common.security.JwtAuthenticationFilter; +import com.tutorialsejong.courseregistration.common.security.JwtExceptionFilter; import com.tutorialsejong.courseregistration.common.security.JwtTokenProvider; -import com.tutorialsejong.courseregistration.domain.auth.service.CustomUserDetailsService; import java.util.Arrays; +import lombok.RequiredArgsConstructor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.authentication.AuthenticationManager; @@ -19,20 +20,12 @@ import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; import org.springframework.web.cors.CorsConfiguration; +@RequiredArgsConstructor @Configuration @EnableWebSecurity public class SecurityConfig { private final JwtTokenProvider tokenProvider; - private final CustomUserDetailsService customUserDetailsService; - private final JwtAuthenticationEntryPoint unauthorizedHandler; - - public SecurityConfig(JwtTokenProvider tokenProvider, JwtAuthenticationEntryPoint unauthorizedHandler, - CustomUserDetailsService customUserDetailsService) { - this.tokenProvider = tokenProvider; - this.unauthorizedHandler = unauthorizedHandler; - this.customUserDetailsService = customUserDetailsService; - } @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { @@ -65,10 +58,11 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { .anyRequest().authenticated() ) .exceptionHandling(exceptionHandling -> exceptionHandling - .authenticationEntryPoint(unauthorizedHandler) + .authenticationEntryPoint(new JwtAuthenticationEntryPoint()) ) - .addFilterBefore(new JwtAuthenticationFilter(tokenProvider, customUserDetailsService), - UsernamePasswordAuthenticationFilter.class); + .addFilterBefore(new JwtAuthenticationFilter(tokenProvider), + UsernamePasswordAuthenticationFilter.class) + .addFilterBefore(new JwtExceptionFilter(), JwtAuthenticationFilter.class); return http.build(); } diff --git a/src/main/java/com/tutorialsejong/courseregistration/common/exception/ErrorResponse.java b/src/main/java/com/tutorialsejong/courseregistration/common/exception/ErrorResponse.java index 54fb9ef..07c8697 100644 --- a/src/main/java/com/tutorialsejong/courseregistration/common/exception/ErrorResponse.java +++ b/src/main/java/com/tutorialsejong/courseregistration/common/exception/ErrorResponse.java @@ -3,7 +3,11 @@ import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonInclude.Include; import com.fasterxml.jackson.annotation.JsonUnwrapped; +import com.tutorialsejong.courseregistration.common.utils.JsonUtils; +import jakarta.servlet.http.HttpServletResponse; +import java.io.IOException; import java.util.List; +import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.validation.FieldError; @@ -23,10 +27,17 @@ public static ErrorResponse of(ErrorCode errorCode, List invalidPa return new ErrorResponse(errorCode, invalidParams); } - public ResponseEntity asHttp() { + public ResponseEntity toResponseEntity() { return ResponseEntity.status(errorCode.getStatus()).body(this); } + public void writeTo(HttpServletResponse response) throws IOException { + response.setStatus(errorCode.getStatus().value()); + response.setContentType(MediaType.APPLICATION_JSON_VALUE); + response.setCharacterEncoding("UTF-8"); + response.getWriter().write(JsonUtils.toJson(this)); + } + public record InvalidParam(String name, String reason) { public static InvalidParam from(FieldError fieldError) { diff --git a/src/main/java/com/tutorialsejong/courseregistration/common/exception/GlobalExceptionHandler.java b/src/main/java/com/tutorialsejong/courseregistration/common/exception/GlobalExceptionHandler.java index 7d69679..3137336 100644 --- a/src/main/java/com/tutorialsejong/courseregistration/common/exception/GlobalExceptionHandler.java +++ b/src/main/java/com/tutorialsejong/courseregistration/common/exception/GlobalExceptionHandler.java @@ -1,7 +1,5 @@ package com.tutorialsejong.courseregistration.common.exception; -import com.tutorialsejong.courseregistration.common.security.exception.JwtAuthenticationException; -import io.jsonwebtoken.ExpiredJwtException; import java.util.Collections; import java.util.HashMap; import java.util.List; @@ -10,7 +8,6 @@ import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatusCode; import org.springframework.http.ResponseEntity; -import org.springframework.security.authentication.BadCredentialsException; import org.springframework.web.bind.MethodArgumentNotValidException; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice; @@ -22,8 +19,7 @@ public class GlobalExceptionHandler extends ResponseEntityExceptionHandler { @ExceptionHandler(BusinessException.class) public ResponseEntity handleBusinessException(BusinessException ex) { - ErrorCode errorCode = ex.getErrorCode(); - return ErrorResponse.from(errorCode).asHttp(); + return ErrorResponse.from(ex.getErrorCode()).toResponseEntity(); } @Override @@ -38,15 +34,7 @@ protected ResponseEntity handleMethodArgumentNotValid( .map(ErrorResponse.InvalidParam::from) .toList(); - ErrorCode errorCode = GlobalErrorCode.INVALID_INPUT_VALUE; - return ErrorResponse.of(errorCode, invalidParams).asHttp(); - } - - @ExceptionHandler(BadCredentialsException.class) - public ResponseEntity handleBadCredentialsException(BadCredentialsException ex) { - Map body = new HashMap<>(); - body.put("message", "올바르지 않은 비밀번호입니다!"); - return ResponseEntity.status(HttpStatus.NOT_ACCEPTABLE).body(body); + return ErrorResponse.of(GlobalErrorCode.INVALID_INPUT_VALUE, invalidParams).toResponseEntity(); } @ExceptionHandler(IllegalArgumentException.class) @@ -56,20 +44,8 @@ public ResponseEntity handleIllegalArgumentException(IllegalArgumentException return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(body); } - @ExceptionHandler(JwtAuthenticationException.class) - public ResponseEntity handleJwtAuthenticationException(JwtAuthenticationException ex) { - Map body = new HashMap<>(); - if (ex.getCause() instanceof ExpiredJwtException) { - body.put("message", Collections.singletonList("토큰이 만료되었습니다.")); - } else { - body.put("message", Collections.singletonList("유효하지 않은 토큰입니다.")); - } - return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(body); - } - @ExceptionHandler(Exception.class) public ResponseEntity handleGenericException(Exception ex) { - ErrorCode errorCode = GlobalErrorCode.INTERNAL_SERVER_ERROR; - return ErrorResponse.from(errorCode).asHttp(); + return ErrorResponse.from(GlobalErrorCode.INTERNAL_SERVER_ERROR).toResponseEntity(); } } diff --git a/src/main/java/com/tutorialsejong/courseregistration/common/security/JwtAuthenticationEntryPoint.java b/src/main/java/com/tutorialsejong/courseregistration/common/security/JwtAuthenticationEntryPoint.java index d90edf2..b474003 100644 --- a/src/main/java/com/tutorialsejong/courseregistration/common/security/JwtAuthenticationEntryPoint.java +++ b/src/main/java/com/tutorialsejong/courseregistration/common/security/JwtAuthenticationEntryPoint.java @@ -1,10 +1,10 @@ package com.tutorialsejong.courseregistration.common.security; +import com.tutorialsejong.courseregistration.common.exception.ErrorResponse; +import com.tutorialsejong.courseregistration.common.security.exception.SecurityErrorCode; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import java.io.IOException; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.springframework.security.core.AuthenticationException; import org.springframework.security.web.AuthenticationEntryPoint; import org.springframework.stereotype.Component; @@ -12,14 +12,10 @@ @Component public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint { - private static final Logger logger = LoggerFactory.getLogger(JwtAuthenticationEntryPoint.class); - @Override public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException { - logger.error("Unauthorized error: {}", authException.getMessage()); - response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); - response.setContentType("application/json"); - response.getWriter().write(String.format("{\"error\": \"Unauthorized: %s\"}", authException.getMessage())); + ErrorResponse.from(SecurityErrorCode.AUTHENTICATION_FAILED) + .writeTo(response); } } diff --git a/src/main/java/com/tutorialsejong/courseregistration/common/security/JwtAuthenticationFilter.java b/src/main/java/com/tutorialsejong/courseregistration/common/security/JwtAuthenticationFilter.java index c84ce86..39a4fac 100644 --- a/src/main/java/com/tutorialsejong/courseregistration/common/security/JwtAuthenticationFilter.java +++ b/src/main/java/com/tutorialsejong/courseregistration/common/security/JwtAuthenticationFilter.java @@ -1,13 +1,12 @@ package com.tutorialsejong.courseregistration.common.security; -import com.tutorialsejong.courseregistration.common.security.exception.JwtAuthenticationException; -import com.tutorialsejong.courseregistration.domain.auth.service.CustomUserDetailsService; -import io.jsonwebtoken.ExpiredJwtException; import jakarta.servlet.FilterChain; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import java.io.IOException; +import java.util.Optional; +import lombok.RequiredArgsConstructor; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; @@ -15,58 +14,44 @@ import org.springframework.util.StringUtils; import org.springframework.web.filter.OncePerRequestFilter; +@RequiredArgsConstructor public class JwtAuthenticationFilter extends OncePerRequestFilter { - private final JwtTokenProvider tokenProvider; - private final CustomUserDetailsService userDetailsService; + private static final String AUTHORIZATION_HEADER = "Authorization"; + private static final String BEARER_PREFIX = "Bearer "; + private static final int BEARER_PREFIX_LENGTH = BEARER_PREFIX.length(); - public JwtAuthenticationFilter(JwtTokenProvider tokenProvider, CustomUserDetailsService userDetailsService) { - this.tokenProvider = tokenProvider; - this.userDetailsService = userDetailsService; - } + private final JwtTokenProvider tokenProvider; @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { - try { - String jwt = getJwtFromRequest(request); - if (StringUtils.hasText(jwt)) { - tokenProvider.validateToken(jwt); - Authentication authentication = tokenProvider.getAuthentication(jwt); + extractJwtFromRequest(request) + .ifPresent(jwt -> processJwtAuthentication(jwt, request)); - if (authentication instanceof UsernamePasswordAuthenticationToken) { - ((UsernamePasswordAuthenticationToken) authentication) - .setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); - } + filterChain.doFilter(request, response); + } - SecurityContextHolder.getContext().setAuthentication(authentication); - } - filterChain.doFilter(request, response); - } catch (ExpiredJwtException ex) { - logger.error("Expired JWT token", ex); - sendErrorResponse(response, HttpServletResponse.SC_UNAUTHORIZED, "Access Token has expired"); - } catch (JwtAuthenticationException ex) { - logger.error("Invalid JWT token", ex); - sendErrorResponse(response, HttpServletResponse.SC_UNAUTHORIZED, "Invalid Access Token"); - } catch (Exception ex) { - logger.error("Could not set user authentication in security context", ex); - sendErrorResponse(response, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, - "An error occurred while processing your request"); + private Optional extractJwtFromRequest(HttpServletRequest request) { + String bearerToken = request.getHeader(AUTHORIZATION_HEADER); + if (StringUtils.hasText(bearerToken) && bearerToken.startsWith(BEARER_PREFIX)) { + return Optional.of(bearerToken.substring(BEARER_PREFIX_LENGTH)); } + return Optional.empty(); } - private void sendErrorResponse(HttpServletResponse response, int status, String message) throws IOException { - response.setStatus(status); - response.setContentType("application/json"); - response.getWriter().write(String.format("{\"error\": \"%s\"}", message)); + private void processJwtAuthentication(String jwt, HttpServletRequest request) { + tokenProvider.validateToken(jwt); + Authentication authentication = tokenProvider.getAuthentication(jwt); + enhanceAuthenticationWithRequestDetails(authentication, request); + SecurityContextHolder.getContext().setAuthentication(authentication); } - private String getJwtFromRequest(HttpServletRequest request) { - String bearerToken = request.getHeader("Authorization"); - if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) { - return bearerToken.substring(7); + private void enhanceAuthenticationWithRequestDetails(Authentication authentication, HttpServletRequest request) { + if (authentication instanceof UsernamePasswordAuthenticationToken) { + ((UsernamePasswordAuthenticationToken) authentication) + .setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); } - return null; } @Override diff --git a/src/main/java/com/tutorialsejong/courseregistration/common/security/JwtExceptionFilter.java b/src/main/java/com/tutorialsejong/courseregistration/common/security/JwtExceptionFilter.java new file mode 100644 index 0000000..4c1c97f --- /dev/null +++ b/src/main/java/com/tutorialsejong/courseregistration/common/security/JwtExceptionFilter.java @@ -0,0 +1,24 @@ +package com.tutorialsejong.courseregistration.common.security; + +import com.tutorialsejong.courseregistration.common.exception.ErrorResponse; +import com.tutorialsejong.courseregistration.common.security.exception.JwtException; +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import java.io.IOException; +import org.springframework.web.filter.OncePerRequestFilter; + +public class JwtExceptionFilter extends OncePerRequestFilter { + + @Override + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, + FilterChain filterChain) throws ServletException, IOException { + try { + filterChain.doFilter(request, response); + } catch (JwtException e) { + ErrorResponse.from(e.getErrorCode()) + .writeTo(response); + } + } +} diff --git a/src/main/java/com/tutorialsejong/courseregistration/common/security/JwtTokenProvider.java b/src/main/java/com/tutorialsejong/courseregistration/common/security/JwtTokenProvider.java index f21e9cf..3efc34d 100644 --- a/src/main/java/com/tutorialsejong/courseregistration/common/security/JwtTokenProvider.java +++ b/src/main/java/com/tutorialsejong/courseregistration/common/security/JwtTokenProvider.java @@ -1,6 +1,7 @@ package com.tutorialsejong.courseregistration.common.security; -import com.tutorialsejong.courseregistration.common.security.exception.JwtAuthenticationException; +import com.tutorialsejong.courseregistration.common.security.exception.JwtTokenExpiredException; +import com.tutorialsejong.courseregistration.common.security.exception.JwtTokenInvalidException; import com.tutorialsejong.courseregistration.domain.auth.service.CustomUserDetailsService; import io.jsonwebtoken.Claims; import io.jsonwebtoken.ExpiredJwtException; @@ -57,9 +58,6 @@ private String generateToken(String username, int expirationInMs) { } public String generateAccessTokenFromUsername(String username) { - Date now = new Date(); - Date expiryDate = new Date(now.getTime() + accessTokenExpirationInMs); - return generateToken(username, accessTokenExpirationInMs); } @@ -74,13 +72,13 @@ public String getUsernameFromJWT(String token) { return claims.getSubject(); } - public void validateToken(String authToken) { + public void validateToken(String token) { try { - Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(authToken); + Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token); } catch (ExpiredJwtException ex) { - throw ex; + throw new JwtTokenExpiredException(); } catch (MalformedJwtException | UnsupportedJwtException | IllegalArgumentException ex) { - throw new JwtAuthenticationException("Invalid JWT token", ex); + throw new JwtTokenInvalidException(); } } diff --git a/src/main/java/com/tutorialsejong/courseregistration/common/security/exception/JwtAuthenticationException.java b/src/main/java/com/tutorialsejong/courseregistration/common/security/exception/JwtAuthenticationException.java deleted file mode 100644 index 02b5a59..0000000 --- a/src/main/java/com/tutorialsejong/courseregistration/common/security/exception/JwtAuthenticationException.java +++ /dev/null @@ -1,8 +0,0 @@ -package com.tutorialsejong.courseregistration.common.security.exception; - -public class JwtAuthenticationException extends RuntimeException { - - public JwtAuthenticationException(String message, Throwable cause) { - super(message, cause); - } -} diff --git a/src/main/java/com/tutorialsejong/courseregistration/common/security/exception/JwtException.java b/src/main/java/com/tutorialsejong/courseregistration/common/security/exception/JwtException.java new file mode 100644 index 0000000..4eb7588 --- /dev/null +++ b/src/main/java/com/tutorialsejong/courseregistration/common/security/exception/JwtException.java @@ -0,0 +1,21 @@ +package com.tutorialsejong.courseregistration.common.security.exception; + +import com.tutorialsejong.courseregistration.common.exception.ErrorCode; +import lombok.Getter; +import org.springframework.security.core.AuthenticationException; + +@Getter +public class JwtException extends AuthenticationException { + + private final ErrorCode errorCode; + + public JwtException(ErrorCode errorCode) { + super(errorCode.getMessage()); + this.errorCode = errorCode; + } + + public JwtException(ErrorCode errorCode, Throwable cause) { + super(errorCode.getMessage(), cause); + this.errorCode = errorCode; + } +} diff --git a/src/main/java/com/tutorialsejong/courseregistration/common/security/exception/JwtTokenExpiredException.java b/src/main/java/com/tutorialsejong/courseregistration/common/security/exception/JwtTokenExpiredException.java new file mode 100644 index 0000000..17273ba --- /dev/null +++ b/src/main/java/com/tutorialsejong/courseregistration/common/security/exception/JwtTokenExpiredException.java @@ -0,0 +1,8 @@ +package com.tutorialsejong.courseregistration.common.security.exception; + +public class JwtTokenExpiredException extends JwtException { + + public JwtTokenExpiredException() { + super(SecurityErrorCode.JWT_TOKEN_EXPIRED); + } +} diff --git a/src/main/java/com/tutorialsejong/courseregistration/common/security/exception/JwtTokenInvalidException.java b/src/main/java/com/tutorialsejong/courseregistration/common/security/exception/JwtTokenInvalidException.java new file mode 100644 index 0000000..83d3553 --- /dev/null +++ b/src/main/java/com/tutorialsejong/courseregistration/common/security/exception/JwtTokenInvalidException.java @@ -0,0 +1,8 @@ +package com.tutorialsejong.courseregistration.common.security.exception; + +public class JwtTokenInvalidException extends JwtException { + + public JwtTokenInvalidException() { + super(SecurityErrorCode.JWT_TOKEN_INVALID); + } +} diff --git a/src/main/java/com/tutorialsejong/courseregistration/common/security/exception/SecurityErrorCode.java b/src/main/java/com/tutorialsejong/courseregistration/common/security/exception/SecurityErrorCode.java new file mode 100644 index 0000000..34b977f --- /dev/null +++ b/src/main/java/com/tutorialsejong/courseregistration/common/security/exception/SecurityErrorCode.java @@ -0,0 +1,20 @@ +package com.tutorialsejong.courseregistration.common.security.exception; + +import com.tutorialsejong.courseregistration.common.exception.ErrorCode; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; + +@Getter +@RequiredArgsConstructor +public enum SecurityErrorCode implements ErrorCode { + + AUTHENTICATION_FAILED("S001", "인증에 실패했습니다.", HttpStatus.UNAUTHORIZED), + JWT_TOKEN_EXPIRED("S002", "토큰이 만료되었습니다.", HttpStatus.UNAUTHORIZED), + JWT_TOKEN_INVALID("S003", "유효하지 않은 토큰입니다.", HttpStatus.UNAUTHORIZED), + ; + + private final String code; + private final String message; + private final HttpStatus status; +} diff --git a/src/main/java/com/tutorialsejong/courseregistration/common/utils/JsonUtils.java b/src/main/java/com/tutorialsejong/courseregistration/common/utils/JsonUtils.java new file mode 100644 index 0000000..b809ef0 --- /dev/null +++ b/src/main/java/com/tutorialsejong/courseregistration/common/utils/JsonUtils.java @@ -0,0 +1,21 @@ +package com.tutorialsejong.courseregistration.common.utils; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +@Component +public class JsonUtils { + + private static ObjectMapper objectMapper; + + @Autowired + public void setObjectMapper(ObjectMapper objectMapper) { + JsonUtils.objectMapper = objectMapper; + } + + public static String toJson(Object object) throws JsonProcessingException { + return objectMapper.writeValueAsString(object); + } +} From 07216bc4d9ab90d61412d8a56f7518312d82a8c0 Mon Sep 17 00:00:00 2001 From: zhy2on <52701529+zhy2on@users.noreply.github.com> Date: Mon, 6 Jan 2025 15:34:03 +0900 Subject: [PATCH 15/28] =?UTF-8?q?feat:=20config=20=EC=88=98=EC=A0=95=20(#8?= =?UTF-8?q?2)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/resources/config | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/resources/config b/src/main/resources/config index 8aada45..6daab50 160000 --- a/src/main/resources/config +++ b/src/main/resources/config @@ -1 +1 @@ -Subproject commit 8aada456da70229b1736078412e7dcb154ad1ff2 +Subproject commit 6daab505382bce99ad679d5c8f09881c212d9638 From ddf1dd1f90ddda82b9a2610a56f04bae87fd8e91 Mon Sep 17 00:00:00 2001 From: Anhye0n <49294599+Anhye0n@users.noreply.github.com> Date: Tue, 7 Jan 2025 20:48:43 +0900 Subject: [PATCH 16/28] =?UTF-8?q?feat(TS-5):=20Logback=20=EC=84=A4?= =?UTF-8?q?=EC=A0=95=20=EC=B6=94=EA=B0=80=20=EB=B0=8F=20=EA=B8=B0=EB=B3=B8?= =?UTF-8?q?=20=EB=A1=9C=EA=B9=85=20=EA=B5=AC=EC=84=B1=20=EC=99=84=EB=A3=8C?= =?UTF-8?q?=20(#83)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: Logback 설정 추가 및 기본 로깅 구성 완료 * feat: Logback 설정 추가 및 기본 로깅 구성 완료 --- .gitignore | 2 + .../common/exception/BusinessException.java | 3 + .../common/security/JwtTokenProvider.java | 23 +++ .../common/utils/log/LogAction.java | 15 ++ .../common/utils/log/LogMessage.java | 101 +++++++++++++ .../common/utils/log/LogReason.java | 10 ++ .../common/utils/log/LogResult.java | 7 + .../auth/controller/AuthController.java | 1 + .../domain/auth/service/AuthService.java | 33 ++++- .../service/CourseRegistrationService.java | 139 ++++++++++++++++-- .../wishlist/service/WishListService.java | 130 ++++++++++++++-- src/main/resources/config | 2 +- src/main/resources/logback-spring.xml | 62 ++++++++ 13 files changed, 504 insertions(+), 24 deletions(-) create mode 100644 src/main/java/com/tutorialsejong/courseregistration/common/utils/log/LogAction.java create mode 100644 src/main/java/com/tutorialsejong/courseregistration/common/utils/log/LogMessage.java create mode 100644 src/main/java/com/tutorialsejong/courseregistration/common/utils/log/LogReason.java create mode 100644 src/main/java/com/tutorialsejong/courseregistration/common/utils/log/LogResult.java create mode 100644 src/main/resources/logback-spring.xml diff --git a/.gitignore b/.gitignore index 17d2209..66b7e79 100644 --- a/.gitignore +++ b/.gitignore @@ -38,3 +38,5 @@ out/ application*.properties src/main/resources/config + +logs/* \ No newline at end of file diff --git a/src/main/java/com/tutorialsejong/courseregistration/common/exception/BusinessException.java b/src/main/java/com/tutorialsejong/courseregistration/common/exception/BusinessException.java index b0d0201..9be2936 100644 --- a/src/main/java/com/tutorialsejong/courseregistration/common/exception/BusinessException.java +++ b/src/main/java/com/tutorialsejong/courseregistration/common/exception/BusinessException.java @@ -1,6 +1,9 @@ package com.tutorialsejong.courseregistration.common.exception; +import com.tutorialsejong.courseregistration.domain.auth.controller.AuthController; import lombok.Getter; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; @Getter public class BusinessException extends RuntimeException { diff --git a/src/main/java/com/tutorialsejong/courseregistration/common/security/JwtTokenProvider.java b/src/main/java/com/tutorialsejong/courseregistration/common/security/JwtTokenProvider.java index 3efc34d..67a761a 100644 --- a/src/main/java/com/tutorialsejong/courseregistration/common/security/JwtTokenProvider.java +++ b/src/main/java/com/tutorialsejong/courseregistration/common/security/JwtTokenProvider.java @@ -2,6 +2,10 @@ import com.tutorialsejong.courseregistration.common.security.exception.JwtTokenExpiredException; import com.tutorialsejong.courseregistration.common.security.exception.JwtTokenInvalidException; +import com.tutorialsejong.courseregistration.common.utils.log.LogAction; +import com.tutorialsejong.courseregistration.common.utils.log.LogMessage; +import com.tutorialsejong.courseregistration.common.utils.log.LogReason; +import com.tutorialsejong.courseregistration.common.utils.log.LogResult; import com.tutorialsejong.courseregistration.domain.auth.service.CustomUserDetailsService; import io.jsonwebtoken.Claims; import io.jsonwebtoken.ExpiredJwtException; @@ -11,6 +15,9 @@ import io.jsonwebtoken.security.Keys; import java.security.Key; import java.util.Date; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Value; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; @@ -19,6 +26,8 @@ @Component public class JwtTokenProvider { + private static final Logger logger = LoggerFactory.getLogger(JwtTokenProvider.class); + private final Key key; private final int accessTokenExpirationInMs; private final int refreshTokenExpirationInMs; @@ -76,8 +85,22 @@ public void validateToken(String token) { try { Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token); } catch (ExpiredJwtException ex) { + String username = getUsernameFromJWT(token); + logger.warn(LogMessage.builder() + .action(LogAction.VALIDATE_TOKEN) + .subject("s"+username) + .result(LogResult.FAIL) + .reason(LogReason.EXPIRED) + .build().toString()); throw new JwtTokenExpiredException(); } catch (MalformedJwtException | UnsupportedJwtException | IllegalArgumentException ex) { + String username = getUsernameFromJWT(token); + logger.warn(LogMessage.builder() + .action(LogAction.VALIDATE_TOKEN) + .subject("s"+username) + .result(LogResult.FAIL) + .reason(LogReason.INVALID_CREDENTIAL) + .build().toString()); throw new JwtTokenInvalidException(); } } diff --git a/src/main/java/com/tutorialsejong/courseregistration/common/utils/log/LogAction.java b/src/main/java/com/tutorialsejong/courseregistration/common/utils/log/LogAction.java new file mode 100644 index 0000000..311239f --- /dev/null +++ b/src/main/java/com/tutorialsejong/courseregistration/common/utils/log/LogAction.java @@ -0,0 +1,15 @@ +package com.tutorialsejong.courseregistration.common.utils.log; + +public enum LogAction { + LOGIN, + LOGOUT, + VALIDATE_USER, + VALIDATE_COURSE, + VALIDATE_TOKEN, + REFRESH_TOKEN, + WITHDRAWAL, + FETCH_REGISTERED_COURSES, + REGISTER_COURSE, + ADD_WISHLIST, + REMOVE_WISHLIST +} diff --git a/src/main/java/com/tutorialsejong/courseregistration/common/utils/log/LogMessage.java b/src/main/java/com/tutorialsejong/courseregistration/common/utils/log/LogMessage.java new file mode 100644 index 0000000..09daa71 --- /dev/null +++ b/src/main/java/com/tutorialsejong/courseregistration/common/utils/log/LogMessage.java @@ -0,0 +1,101 @@ +package com.tutorialsejong.courseregistration.common.utils.log; + +public class LogMessage { + private LogAction action; // 예: LOGIN, LOGOUT + private String subject; // 예: "Student(20230001)" + private String objectName; // 예: "Course(CS101)" 등 + private LogResult result; // 예: SUCCESS, FAIL + private LogReason reason; // 예: INVALID_CREDENTIAL + private String extras; // 예: "IP:192.168.0.10, UA:Mozilla/5.0" 등 + + // private 생성자 (Builder를 통해서만 객체 생성) + private LogMessage(LogAction action, + String subject, + String objectName, + LogResult result, + LogReason reason, + String extras) { + this.action = action; + this.subject = subject; + this.objectName = objectName; + this.result = result; + this.reason = reason; + this.extras = extras; + } + + // 빌더로 LogMessage 생성 + public static LogMessageBuilder builder() { + return new LogMessageBuilder(); + } + + // 최종적으로 로그 문자열로 출력될 형태 + // ACTION=액션 | SUBJECT=주체 | OBJECT=대상 | RESULT=결과 | REASON=원인 | EXTRAS=추가정보 + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + + if (action != null) { + sb.append("").append(action.name()); + } + if (subject != null) { + sb.append(" | ").append(subject); + } + if (objectName != null) { + sb.append(" | ").append(objectName); + } + if (result != null) { + sb.append(" | ").append(result.name()); + } + if (reason != null) { + sb.append(" | ").append(reason.name()); + } + if (extras != null && !extras.isEmpty()) { + sb.append(" | ").append(extras); + } + return sb.toString(); + } + + // 빌더 내부 클래스 + public static class LogMessageBuilder { + private LogAction action; + private String subject; + private String objectName; + private LogResult result; + private LogReason reason; + private String extras; + + public LogMessageBuilder action(LogAction action) { + this.action = action; + return this; + } + + public LogMessageBuilder subject(String subject) { + this.subject = subject; + return this; + } + + public LogMessageBuilder objectName(String objectName) { + this.objectName = objectName; + return this; + } + + public LogMessageBuilder result(LogResult result) { + this.result = result; + return this; + } + + public LogMessageBuilder reason(LogReason reason) { + this.reason = reason; + return this; + } + + public LogMessageBuilder extras(String extras) { + this.extras = extras; + return this; + } + + public LogMessage build() { + return new LogMessage(action, subject, objectName, result, reason, extras); + } + } +} diff --git a/src/main/java/com/tutorialsejong/courseregistration/common/utils/log/LogReason.java b/src/main/java/com/tutorialsejong/courseregistration/common/utils/log/LogReason.java new file mode 100644 index 0000000..64a2d5e --- /dev/null +++ b/src/main/java/com/tutorialsejong/courseregistration/common/utils/log/LogReason.java @@ -0,0 +1,10 @@ +package com.tutorialsejong.courseregistration.common.utils.log; + +public enum LogReason { + INVALID_CREDENTIAL, + NOT_FOUND, + TIMEOUT, + UNKNOWN, + EXPIRED, + ALREADY_EXIST +} \ No newline at end of file diff --git a/src/main/java/com/tutorialsejong/courseregistration/common/utils/log/LogResult.java b/src/main/java/com/tutorialsejong/courseregistration/common/utils/log/LogResult.java new file mode 100644 index 0000000..76eb74e --- /dev/null +++ b/src/main/java/com/tutorialsejong/courseregistration/common/utils/log/LogResult.java @@ -0,0 +1,7 @@ +package com.tutorialsejong.courseregistration.common.utils.log; + +public enum LogResult { + SUCCESS, + FAIL, + ERROR +} \ No newline at end of file diff --git a/src/main/java/com/tutorialsejong/courseregistration/domain/auth/controller/AuthController.java b/src/main/java/com/tutorialsejong/courseregistration/domain/auth/controller/AuthController.java index 5f2a5dc..2070659 100644 --- a/src/main/java/com/tutorialsejong/courseregistration/domain/auth/controller/AuthController.java +++ b/src/main/java/com/tutorialsejong/courseregistration/domain/auth/controller/AuthController.java @@ -26,6 +26,7 @@ @RestController @RequestMapping("/api/auth") public class AuthController { + private static final String REFRESH_TOKEN_COOKIE_NAME = "refreshToken"; private static final String COOKIE_PATH = "/"; private static final List MACRO_ANSWERS = Arrays.asList( diff --git a/src/main/java/com/tutorialsejong/courseregistration/domain/auth/service/AuthService.java b/src/main/java/com/tutorialsejong/courseregistration/domain/auth/service/AuthService.java index d657eee..4502da8 100644 --- a/src/main/java/com/tutorialsejong/courseregistration/domain/auth/service/AuthService.java +++ b/src/main/java/com/tutorialsejong/courseregistration/domain/auth/service/AuthService.java @@ -1,6 +1,10 @@ package com.tutorialsejong.courseregistration.domain.auth.service; import com.tutorialsejong.courseregistration.common.security.JwtTokenProvider; +import com.tutorialsejong.courseregistration.common.utils.log.LogAction; +import com.tutorialsejong.courseregistration.common.utils.log.LogMessage; +import com.tutorialsejong.courseregistration.common.utils.log.LogReason; +import com.tutorialsejong.courseregistration.common.utils.log.LogResult; import com.tutorialsejong.courseregistration.domain.auth.dto.AuthenticationResult; import com.tutorialsejong.courseregistration.domain.auth.dto.JwtTokens; import com.tutorialsejong.courseregistration.domain.auth.dto.LoginRequest; @@ -11,6 +15,8 @@ import com.tutorialsejong.courseregistration.domain.user.repository.UserRepository; import com.tutorialsejong.courseregistration.domain.wishlist.service.WishListService; import jakarta.transaction.Transactional; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; @@ -20,6 +26,9 @@ @Service public class AuthService { + + private static final Logger logger = LoggerFactory.getLogger(AuthService.class); + private final AuthenticationManager authenticationManager; private final JwtTokenProvider tokenProvider; private final UserRepository userRepository; @@ -47,6 +56,15 @@ public AuthenticationResult loginOrSignup(LoginRequest loginRequest) { User user = findOrCreateUser(loginRequest); Authentication authentication = authenticate(loginRequest); JwtTokens jwtTokens = generateTokens(authentication, user); + + logger.info(LogMessage.builder() + .action(LogAction.LOGIN) + .subject("s"+user.getStudentId()) + .result(LogResult.SUCCESS) + .build() + .toString() + ); + return new AuthenticationResult(jwtTokens.accessToken(), jwtTokens.refreshToken(), user.getStudentId()); } @@ -56,6 +74,7 @@ private User findOrCreateUser(LoginRequest loginRequest) { } private User createNewUser(LoginRequest loginRequest) { + User newUser = new User(loginRequest.studentId(), encodePassword(loginRequest.password())); return userRepository.save(newUser); } @@ -92,6 +111,12 @@ public JwtTokens refreshAccessToken(String refreshToken) { .orElseThrow(() -> new UserNotFoundException()); if (!user.getRefreshToken().equals(refreshToken)) { + logger.warn(LogMessage.builder() + .action(LogAction.REFRESH_TOKEN) + .subject("s"+username) + .result(LogResult.FAIL) + .reason(LogReason.INVALID_CREDENTIAL) + .build().toString()); throw new InvalidRefreshTokenException("Invalid refresh token"); } @@ -101,9 +126,15 @@ public JwtTokens refreshAccessToken(String refreshToken) { @Transactional public void withdrawalUser(String studentId) { - wishListService.deleteWishListsByStudent(studentId); courseRegistrationService.deleteCourseRegistrationsByStudent(studentId); userRepository.deleteByStudentId(studentId); + + logger.info(LogMessage.builder() + .action(LogAction.WITHDRAWAL) + .subject("s"+studentId) + .result(LogResult.SUCCESS) + .build() + .toString()); } } diff --git a/src/main/java/com/tutorialsejong/courseregistration/domain/registration/service/CourseRegistrationService.java b/src/main/java/com/tutorialsejong/courseregistration/domain/registration/service/CourseRegistrationService.java index 5ecad51..9492d6d 100644 --- a/src/main/java/com/tutorialsejong/courseregistration/domain/registration/service/CourseRegistrationService.java +++ b/src/main/java/com/tutorialsejong/courseregistration/domain/registration/service/CourseRegistrationService.java @@ -1,5 +1,10 @@ package com.tutorialsejong.courseregistration.domain.registration.service; +import com.tutorialsejong.courseregistration.common.utils.log.LogAction; +import com.tutorialsejong.courseregistration.common.utils.log.LogMessage; +import com.tutorialsejong.courseregistration.common.utils.log.LogReason; +import com.tutorialsejong.courseregistration.common.utils.log.LogResult; +import com.tutorialsejong.courseregistration.domain.auth.controller.AuthController; import com.tutorialsejong.courseregistration.domain.registration.dto.CourseRegistrationResponse; import com.tutorialsejong.courseregistration.domain.registration.entity.CourseRegistration; import com.tutorialsejong.courseregistration.domain.registration.exception.CourseAlreadyRegisteredException; @@ -10,15 +15,20 @@ import com.tutorialsejong.courseregistration.domain.user.entity.User; import com.tutorialsejong.courseregistration.domain.user.exception.UserNotFoundException; import com.tutorialsejong.courseregistration.domain.user.repository.UserRepository; + import java.time.LocalDateTime; import java.util.List; import java.util.stream.Collectors; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.dao.DataIntegrityViolationException; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @Service public class CourseRegistrationService { + private static final Logger logger = LoggerFactory.getLogger(CourseRegistrationService.class); private final CourseRegistrationRepository courseRegistrationRepository; private final UserRepository userRepository; @@ -34,56 +44,161 @@ public CourseRegistrationService(CourseRegistrationRepository courseRegistration @Transactional public CourseRegistrationResponse registerCourse(String studentId, Long scheduleId) { + logger.info(LogMessage.builder() + .action(LogAction.REGISTER_COURSE) + .subject("s"+studentId) + .objectName("c"+scheduleId) + .result(LogResult.SUCCESS) + .extras("Starting course registration") + .build().toString()); + User student = userRepository.findByStudentId(studentId) - .orElseThrow(() -> new UserNotFoundException()); - Schedule schedule = scheduleRepository.findById(scheduleId) - .orElseThrow(() -> new ScheduleNotFoundException()); + .orElseThrow(() -> { + logger.warn(LogMessage.builder() + .action(LogAction.VALIDATE_USER) + .subject("s"+studentId) + .result(LogResult.FAIL) + .reason(LogReason.NOT_FOUND) + .build().toString()); + throw new UserNotFoundException(); + }); - boolean alreadyRegistered = courseRegistrationRepository.findAllByStudent(student) - .stream() + Schedule schedule = scheduleRepository.findById(scheduleId) + .orElseThrow(() -> { + logger.warn(LogMessage.builder() + .action(LogAction.VALIDATE_COURSE) + .subject("s"+studentId) + .objectName("c"+scheduleId) + .result(LogResult.FAIL) + .reason(LogReason.NOT_FOUND) + .build().toString()); + throw new ScheduleNotFoundException(); + }); + + boolean alreadyRegistered = courseRegistrationRepository.findAllByStudent(student).stream() .anyMatch(registration -> registration.getSchedule().getCuriNo().equals(schedule.getCuriNo())); if (alreadyRegistered) { + logger.warn(LogMessage.builder() + .action(LogAction.REGISTER_COURSE) + .subject("s"+studentId) + .objectName("c"+scheduleId) + .result(LogResult.FAIL) + .reason(LogReason.ALREADY_EXIST) + .build().toString()); throw new CourseAlreadyRegisteredException(); } try { CourseRegistration registration = new CourseRegistration(student, schedule, LocalDateTime.now()); registration = courseRegistrationRepository.save(registration); + logger.info(LogMessage.builder() + .action(LogAction.REGISTER_COURSE) + .subject("s"+studentId) + .objectName("c"+scheduleId) + .result(LogResult.SUCCESS) + .extras("Registration completed") + .build().toString()); return convertToDto(registration); } catch (DataIntegrityViolationException e) { + logger.error(LogMessage.builder() + .action(LogAction.REGISTER_COURSE) + .subject("s"+studentId) + .objectName("c"+scheduleId) + .result(LogResult.ERROR) + .reason(LogReason.ALREADY_EXIST) + .extras("Data integrity violation") + .build().toString()); throw new CourseAlreadyRegisteredException(); } } @Transactional(readOnly = true) public List getRegisteredCourses(String studentId) { + logger.info(LogMessage.builder() + .action(LogAction.FETCH_REGISTERED_COURSES) + .subject("s"+studentId) + .result(LogResult.SUCCESS) + .extras("Fetching registered courses") + .build().toString()); + User student = userRepository.findByStudentId(studentId) - .orElseThrow(() -> new UserNotFoundException()); + .orElseThrow(() -> { + logger.warn(LogMessage.builder() + .action(LogAction.VALIDATE_USER) + .subject("s"+studentId) + .result(LogResult.FAIL) + .reason(LogReason.NOT_FOUND) + .build().toString()); + throw new UserNotFoundException(); + }); List registrations = courseRegistrationRepository.findAllByStudent(student); - - return registrations.stream() - .map(CourseRegistration::getSchedule) - .collect(Collectors.toList()); + return registrations.stream().map(CourseRegistration::getSchedule).collect(Collectors.toList()); } @Transactional public void cancelCourseRegistration(String studentId, Long scheduleId) { + logger.info(LogMessage.builder() + .action(LogAction.WITHDRAWAL) + .subject("s"+studentId) + .objectName("c"+scheduleId) + .result(LogResult.SUCCESS) + .extras("Canceling course registration") + .build().toString()); + CourseRegistration registration = courseRegistrationRepository .findByStudentStudentIdAndScheduleScheduleId(studentId, scheduleId) - .orElseThrow(() -> new ScheduleNotFoundException()); + .orElseThrow(() -> { + logger.warn(LogMessage.builder() + .action(LogAction.VALIDATE_COURSE) + .subject("s"+studentId) + .objectName("c"+scheduleId) + .result(LogResult.FAIL) + .reason(LogReason.NOT_FOUND) + .build().toString()); + throw new ScheduleNotFoundException(); + }); courseRegistrationRepository.delete(registration); + logger.info(LogMessage.builder() + .action(LogAction.WITHDRAWAL) + .subject("s"+studentId) + .objectName("c"+scheduleId) + .result(LogResult.SUCCESS) + .extras("Successfully canceled") + .build().toString()); } @Transactional public void cancelAllCourseRegistrations(String studentId) { + logger.info(LogMessage.builder() + .action(LogAction.WITHDRAWAL) + .subject("s"+studentId) + .result(LogResult.SUCCESS) + .extras("Starting cancellation of all registrations") + .build().toString()); + User student = userRepository.findByStudentId(studentId) - .orElseThrow(() -> new UserNotFoundException()); + .orElseThrow(() -> { + logger.warn(LogMessage.builder() + .action(LogAction.VALIDATE_USER) + .subject("s"+studentId) + .result(LogResult.FAIL) + .reason(LogReason.NOT_FOUND) + .build().toString()); + throw new UserNotFoundException(); + }); List registrations = courseRegistrationRepository.findAllByStudent(student); courseRegistrationRepository.deleteAll(registrations); + + logger.info(LogMessage.builder() + .action(LogAction.WITHDRAWAL) + .subject("s"+studentId) + .result(LogResult.SUCCESS) + .extras("All registrations canceled successfully") + .build().toString()); } private CourseRegistrationResponse convertToDto(CourseRegistration registration) { diff --git a/src/main/java/com/tutorialsejong/courseregistration/domain/wishlist/service/WishListService.java b/src/main/java/com/tutorialsejong/courseregistration/domain/wishlist/service/WishListService.java index d2b10ca..807a925 100644 --- a/src/main/java/com/tutorialsejong/courseregistration/domain/wishlist/service/WishListService.java +++ b/src/main/java/com/tutorialsejong/courseregistration/domain/wishlist/service/WishListService.java @@ -1,5 +1,10 @@ package com.tutorialsejong.courseregistration.domain.wishlist.service; +import com.tutorialsejong.courseregistration.common.utils.log.LogAction; +import com.tutorialsejong.courseregistration.common.utils.log.LogMessage; +import com.tutorialsejong.courseregistration.common.utils.log.LogReason; +import com.tutorialsejong.courseregistration.common.utils.log.LogResult; +import com.tutorialsejong.courseregistration.domain.auth.controller.AuthController; import com.tutorialsejong.courseregistration.domain.registration.repository.CourseRegistrationRepository; import com.tutorialsejong.courseregistration.domain.schedule.entity.Schedule; import com.tutorialsejong.courseregistration.domain.schedule.exception.ScheduleNotFoundException; @@ -14,11 +19,16 @@ import com.tutorialsejong.courseregistration.domain.wishlist.repository.WishListRepository; import java.util.List; import java.util.stream.Collectors; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.stereotype.Service; @Service public class WishListService { + private static final Logger logger = LoggerFactory.getLogger(AuthController.class); + private final WishListRepository wishListRepository; private final UserRepository userRepository; private final ScheduleRepository scheduleRepository; @@ -35,6 +45,14 @@ public WishListService(WishListRepository wishListRepository, } public void saveWishListItem(String studentId, Long scheduleId) { + logger.info(LogMessage.builder() + .action(LogAction.ADD_WISHLIST) + .subject("s"+studentId) + .objectName("c"+scheduleId) + .result(LogResult.SUCCESS) + .extras("Starting wishlist addition") + .build().toString()); + User user = checkExistUser(studentId); Schedule schedule = checkExistSchedule(scheduleId); @@ -44,25 +62,46 @@ public void saveWishListItem(String studentId, Long scheduleId) { .anyMatch(wishList -> wishList.getScheduleId().getCuriNo().equals(curiNo)); if (existsInWishList) { + logger.warn(LogMessage.builder() + .action(LogAction.ADD_WISHLIST) + .subject("s"+studentId) + .objectName("c"+scheduleId) + .result(LogResult.FAIL) + .reason(LogReason.ALREADY_EXIST) + .build().toString()); throw new AlreadyInWishlistException(); } - boolean existsInRegistration = courseRegistrationRepository - .existsByStudentStudentIdAndScheduleScheduleId(studentId, scheduleId); - - if (existsInRegistration) { - throw new WishlistCourseAlreadyRegisteredException(); - } - WishList newWishList = new WishList(user, schedule); wishListRepository.save(newWishList); + logger.info(LogMessage.builder() + .action(LogAction.ADD_WISHLIST) + .subject("s"+studentId) + .objectName("c"+scheduleId) + .result(LogResult.SUCCESS) + .extras("Successfully added to wishlist") + .build().toString()); } public List getWishList(String studentId) { + logger.info(LogMessage.builder() + .action(LogAction.FETCH_REGISTERED_COURSES) + .subject("s"+studentId) + .result(LogResult.SUCCESS) + .extras("Fetching wishlist") + .build().toString()); + User user = checkExistUser(studentId); List wishListList = wishListRepository.findAllByStudentId(user); + logger.info(LogMessage.builder() + .action(LogAction.FETCH_REGISTERED_COURSES) + .subject("s"+studentId) + .result(LogResult.SUCCESS) + .extras("Wishlist fetched successfully") + .build().toString()); + return wishListList.stream() .map(WishList::getScheduleId) .filter(schedule -> !courseRegistrationRepository.existsByStudentStudentIdAndScheduleScheduleId( @@ -71,26 +110,97 @@ public List getWishList(String studentId) { } public User checkExistUser(String studentId) { + logger.info(LogMessage.builder() + .action(LogAction.VALIDATE_USER) + .subject("s"+studentId) + .result(LogResult.SUCCESS) + .extras("Validating user existence") + .build().toString()); + return userRepository.findByStudentId(studentId) - .orElseThrow(() -> new UserNotFoundException()); + .orElseThrow(() -> { + logger.warn(LogMessage.builder() + .action(LogAction.VALIDATE_USER) + .subject("s"+studentId) + .result(LogResult.FAIL) + .reason(LogReason.NOT_FOUND) + .extras("User not found") + .build().toString()); + return new UserNotFoundException(); + }); } public Schedule checkExistSchedule(Long scheduleId) { + logger.info(LogMessage.builder() + .action(LogAction.VALIDATE_COURSE) + .objectName("c"+scheduleId) + .result(LogResult.SUCCESS) + .extras("Validating schedule existence") + .build().toString()); + return scheduleRepository.findById(scheduleId) - .orElseThrow(() -> new ScheduleNotFoundException()); + .orElseThrow(() -> { + logger.warn(LogMessage.builder() + .action(LogAction.VALIDATE_COURSE) + .objectName("c"+scheduleId) + .result(LogResult.FAIL) + .reason(LogReason.NOT_FOUND) + .extras("Schedule not found") + .build().toString()); + return new ScheduleNotFoundException(); + }); } public void deleteWishListItem(String studentId, Long scheduleId) { + logger.info(LogMessage.builder() + .action(LogAction.REMOVE_WISHLIST) // 추가된 LogAction + .subject("s"+studentId) + .objectName("c"+scheduleId) + .result(LogResult.SUCCESS) + .extras("Starting removal of wishlist item") + .build().toString()); + User user = checkExistUser(studentId); Schedule schedule = checkExistSchedule(scheduleId); WishList wishList = wishListRepository.findByStudentIdAndScheduleId(user, schedule) - .orElseThrow(() -> new WishlistNotFoundException()); + .orElseThrow(() -> { + logger.warn(LogMessage.builder() + .action(LogAction.REMOVE_WISHLIST) + .subject("s"+studentId) + .objectName("c"+scheduleId) + .result(LogResult.FAIL) + .reason(LogReason.NOT_FOUND) + .extras("Wishlist item not found") + .build().toString()); + return new WishlistNotFoundException(); + }); wishListRepository.delete(wishList); + logger.info(LogMessage.builder() + .action(LogAction.REMOVE_WISHLIST) + .subject("s"+studentId) + .objectName("c"+scheduleId) + .result(LogResult.SUCCESS) + .extras("Removed wishlist item") + .build().toString()); } public void deleteWishListsByStudent(String studentId) { + logger.info(LogMessage.builder() + .action(LogAction.REMOVE_WISHLIST) + .subject("s"+studentId) + .result(LogResult.SUCCESS) + .extras("Starting removal of all wishlist items for the user") + .build().toString()); + wishListRepository.deleteByStudentId(studentId); + + logger.info(LogMessage.builder() + .action(LogAction.REMOVE_WISHLIST) + .subject("s"+studentId) + .result(LogResult.SUCCESS) + .extras("Removed all wishlist items for the user") + .build().toString()); } } diff --git a/src/main/resources/config b/src/main/resources/config index 6daab50..f392ec2 160000 --- a/src/main/resources/config +++ b/src/main/resources/config @@ -1 +1 @@ -Subproject commit 6daab505382bce99ad679d5c8f09881c212d9638 +Subproject commit f392ec2da50564e27ec3a1638574e2bc54e9680f diff --git a/src/main/resources/logback-spring.xml b/src/main/resources/logback-spring.xml new file mode 100644 index 0000000..49666d1 --- /dev/null +++ b/src/main/resources/logback-spring.xml @@ -0,0 +1,62 @@ + + + + + + + + %d{yyyy-MM-dd HH:mm:ss.SSS} [%highlight(%-5level)] [%cyan(%logger{36}.%method:line%line)] - %msg%n + + + + + + + + + + logs/tutorial-sejong-log.log + + + %d{yyyy-MM-dd HH:mm:ss.SSS} [%-5level] [%logger{36}.%method:line%line] - %msg%n + + + + + + logs/backup/tutorial-sejong-log-%d{yyyy-MM-dd}.log + + 30 + + + + + + + + /home/anhye0n/web/tutorial_sejong/backend/logs/tutorial-sejong-log.log + + + %d{yyyy-MM-dd HH:mm:ss.SSS} [%-5level] [%logger{36}.%method:line%line] - %msg%n + + + + /home/anhye0n/web/tutorial_sejong/backend/logs/backup/tutorial-sejong-log-%d{yyyy-MM-dd}.log + 50 + + + + + + + + + + + + + + + + + From 37e9730c0192a2b92de9167ba188ed0c094b2bbb Mon Sep 17 00:00:00 2001 From: anhye0n Date: Tue, 7 Jan 2025 21:09:12 +0900 Subject: [PATCH 17/28] chore: submodule Update --- src/main/resources/config | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/resources/config b/src/main/resources/config index f392ec2..773b787 160000 --- a/src/main/resources/config +++ b/src/main/resources/config @@ -1 +1 @@ -Subproject commit f392ec2da50564e27ec3a1638574e2bc54e9680f +Subproject commit 773b7876084a9f8013ab81020ec3772900dcc388 From 0abce1c6d902d3e8ecefc6ce728c643f3617f4eb Mon Sep 17 00:00:00 2001 From: Anhye0n <49294599+Anhye0n@users.noreply.github.com> Date: Tue, 7 Jan 2025 21:09:36 +0900 Subject: [PATCH 18/28] chore: submodule Update (#84) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: Logback 설정 추가 및 기본 로깅 구성 완료 * feat: Logback 설정 추가 및 기본 로깅 구성 완료 * chore: submodule Update From 0cfd236b63e3ef952876216bc4d4631b014f26c1 Mon Sep 17 00:00:00 2001 From: Anhye0n <49294599+Anhye0n@users.noreply.github.com> Date: Tue, 7 Jan 2025 22:08:02 +0900 Subject: [PATCH 19/28] Ts 5/chore/logback setup (#85) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: Logback 설정 추가 및 기본 로깅 구성 완료 * feat: Logback 설정 추가 및 기본 로깅 구성 완료 * chore: submodule Update * chore: github action 코드 수정 --- .github/workflows/deploy.yml | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 13c85c8..0be114b 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -32,30 +32,39 @@ jobs: - name: Grant execute permission for gradlew run: chmod +x gradlew + # --- (1) build 시 테스트 무시: -x test --- - name: Build with Gradle run: ./gradlew build -x test - name: Install sshpass run: sudo apt-get install -y sshpass + # 빌드 산출물(JAR 파일)을 원격 서버로 복사 - name: Copy build artifacts run: | JAR_FILE=$(ls build/libs/*.jar | sort -r | head -n 1) echo "JAR_FILE=$JAR_FILE" >> $GITHUB_ENV - sshpass -p ${{ secrets.SSH_PASSWORD }} scp -o StrictHostKeyChecking=no -r $JAR_FILE ${{ secrets.SSH_USER }}@${{ secrets.SSH_HOST }}:/home/anhye0n/web/tutorial_sejong/backend/ + sshpass -p ${{ secrets.SSH_PASSWORD }} scp -o StrictHostKeyChecking=no -r "$JAR_FILE" ${{ secrets.SSH_USER }}@${{ secrets.SSH_HOST }}:/home/anhye0n/web/tutorial_sejong/backend/ + # --- (2) systemd daemon-reload 추가 후 서비스 재시작 --- - name: Restart backend service run: | sshpass -p ${{ secrets.SSH_PASSWORD }} ssh -o StrictHostKeyChecking=no ${{ secrets.SSH_USER }}@${{ secrets.SSH_HOST }} << EOF cd /home/anhye0n/web/tutorial_sejong/backend/ git submodule update --init --recursive + CURRENT_PID=\$(lsof -t -i:8080) if [ -n "\$CURRENT_PID" ]; then echo "Stopping process using port 8080 with PID \$CURRENT_PID" echo ${{ secrets.SSH_PASSWORD }} | sudo -S kill -9 \$CURRENT_PID fi + + # systemd 설정 변경/추가 후 재로딩 + echo ${{ secrets.SSH_PASSWORD }} | sudo -S systemctl daemon-reload echo ${{ secrets.SSH_PASSWORD }} | sudo -S systemctl restart tutorial_sejong_backend echo ${{ secrets.SSH_PASSWORD }} | sudo -S systemctl status tutorial_sejong_backend + + # DB 스크립트 실행 (15초 대기 후) sleep 15 mysql -u ${{ secrets.MARIADB_ID }} -p${{ secrets.MARIADB_PASSWORD }} < /home/anhye0n/web/tutorial_sejong/backend/tutorial_sejong.sql EOF From f6d8bac90b7842131fb14a3adcc1de61b8903dd6 Mon Sep 17 00:00:00 2001 From: zhy2on <52701529+zhy2on@users.noreply.github.com> Date: Wed, 8 Jan 2025 23:37:28 +0900 Subject: [PATCH 20/28] =?UTF-8?q?refactor(TS-35):=20=EB=A7=A4=ED=81=AC?= =?UTF-8?q?=EB=A1=9C=20=EB=B0=A9=EC=A7=80=20=EC=9D=B4=EB=AF=B8=EC=A7=80=20?= =?UTF-8?q?=EC=83=9D=EC=84=B1=EC=9D=84=20Simple=20Captcha=EB=A5=BC=20?= =?UTF-8?q?=EC=9D=B4=EC=9A=A9=ED=95=98=EB=8F=84=EB=A1=9D=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD=20(#86)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * chore: SimpleCaptcha 라이브러리 의존성 추가 * refactor: Captcha 구현을 SimpleCaptcha로 변경 및 응답 형식 개선 - 기존 Captcha 구현을 SimpleCaptcha 라이브러리로 대체 - Captcha 이미지 응답 방식을 파일 경로에서 Base64 인코딩된 이미지로 변경 - 이미지 생성을 위한 CaptchaConfig 설정 추가 - 메모리 효율성 개선 (이미지 파일 저장/관리 불필요) --- build.gradle | 3 +- .../common/config/CaptchaConfig.java | 31 +++++++++++ .../auth/controller/AuthController.java | 50 ++++++----------- .../domain/auth/dto/CaptchaResult.java | 7 +++ .../domain/auth/dto/MacroResponse.java | 54 ++----------------- .../domain/auth/service/CaptchaService.java | 48 +++++++++++++++++ 6 files changed, 108 insertions(+), 85 deletions(-) create mode 100644 src/main/java/com/tutorialsejong/courseregistration/common/config/CaptchaConfig.java create mode 100644 src/main/java/com/tutorialsejong/courseregistration/domain/auth/dto/CaptchaResult.java create mode 100644 src/main/java/com/tutorialsejong/courseregistration/domain/auth/service/CaptchaService.java diff --git a/build.gradle b/build.gradle index d35a42f..2f1aa9f 100644 --- a/build.gradle +++ b/build.gradle @@ -24,7 +24,8 @@ dependencies { implementation 'org.springframework.boot:spring-boot-starter-validation' implementation 'io.jsonwebtoken:jjwt-api:0.11.5' implementation 'org.projectlombok:lombok' - + implementation 'cn.apiclub.tool:simplecaptcha:1.2.2' + annotationProcessor 'org.projectlombok:lombok' runtimeOnly 'org.mariadb.jdbc:mariadb-java-client' diff --git a/src/main/java/com/tutorialsejong/courseregistration/common/config/CaptchaConfig.java b/src/main/java/com/tutorialsejong/courseregistration/common/config/CaptchaConfig.java new file mode 100644 index 0000000..224c291 --- /dev/null +++ b/src/main/java/com/tutorialsejong/courseregistration/common/config/CaptchaConfig.java @@ -0,0 +1,31 @@ +package com.tutorialsejong.courseregistration.common.config; + +import cn.apiclub.captcha.backgrounds.BackgroundProducer; +import cn.apiclub.captcha.backgrounds.GradiatedBackgroundProducer; +import cn.apiclub.captcha.text.renderer.DefaultWordRenderer; +import cn.apiclub.captcha.text.renderer.WordRenderer; +import java.awt.Color; +import java.awt.Font; +import java.util.List; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class CaptchaConfig { + + @Bean + public WordRenderer wordRenderer() { + return new DefaultWordRenderer( + List.of(new Color(0, 0, 0)), + List.of(new Font("Helvetica", Font.PLAIN, 60)) + ); + } + + @Bean + public BackgroundProducer backgroundProducer() { + GradiatedBackgroundProducer producer = new GradiatedBackgroundProducer(); + producer.setFromColor(new Color(100, 100, 100)); + producer.setToColor(new Color(180, 180, 180)); + return producer; + } +} diff --git a/src/main/java/com/tutorialsejong/courseregistration/domain/auth/controller/AuthController.java b/src/main/java/com/tutorialsejong/courseregistration/domain/auth/controller/AuthController.java index 2070659..ac5142d 100644 --- a/src/main/java/com/tutorialsejong/courseregistration/domain/auth/controller/AuthController.java +++ b/src/main/java/com/tutorialsejong/courseregistration/domain/auth/controller/AuthController.java @@ -1,52 +1,47 @@ package com.tutorialsejong.courseregistration.domain.auth.controller; import com.tutorialsejong.courseregistration.domain.auth.dto.AuthenticationResult; +import com.tutorialsejong.courseregistration.domain.auth.dto.CaptchaResult; import com.tutorialsejong.courseregistration.domain.auth.dto.JwtTokens; import com.tutorialsejong.courseregistration.domain.auth.dto.LoginRequest; import com.tutorialsejong.courseregistration.domain.auth.dto.LoginResponse; import com.tutorialsejong.courseregistration.domain.auth.dto.MacroResponse; import com.tutorialsejong.courseregistration.domain.auth.service.AuthService; +import com.tutorialsejong.courseregistration.domain.auth.service.CaptchaService; import jakarta.servlet.http.HttpServletResponse; import jakarta.validation.Valid; - import java.time.Duration; -import java.util.Arrays; import java.util.HashMap; -import java.util.List; import java.util.Map; -import java.util.Random; - +import lombok.RequiredArgsConstructor; import org.springframework.beans.factory.annotation.Value; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseCookie; import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.*; +import org.springframework.web.bind.annotation.CookieValue; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; @RestController +@RequiredArgsConstructor @RequestMapping("/api/auth") public class AuthController { private static final String REFRESH_TOKEN_COOKIE_NAME = "refreshToken"; private static final String COOKIE_PATH = "/"; - private static final List MACRO_ANSWERS = Arrays.asList( - 1208, 2154, 2509, 2857, 3086, - 3458, 3511, 3803, 4613, 4139, - 5106, 5802, 5648, 6352, 7086, - 7414, 8415, 8594, 9468, 9102, - 1146, 1452, 2117, 3964, 4586, - 5148, 5549, 6180, 7597, 9383 - ); private final AuthService authService; + private final CaptchaService captchaService; @Value("${app.jwt.refreshTokenExpirationInMs}") private int refreshTokenExpirationInMs; - public AuthController(AuthService authService) { - this.authService = authService; - } - @PostMapping("/login") public ResponseEntity login(@RequestBody @Valid LoginRequest loginRequest, HttpServletResponse response) { AuthenticationResult authResult = authService.loginOrSignup(loginRequest); @@ -58,7 +53,6 @@ public ResponseEntity login(@RequestBody @Valid LoginRequest loginRequest, Ht return ResponseEntity.ok(loginResponse); } - @PostMapping("/refresh") public ResponseEntity refreshToken(@CookieValue(name = "refreshToken", required = false) String refreshToken) { if (refreshToken == null) { @@ -80,9 +74,9 @@ public ResponseEntity withdrawal(@PathVariable("studentId") String studentId) } @GetMapping("/macro") - public ResponseEntity verificationCodes() { - MacroResponse body = createMacroResponse(); - return ResponseEntity.ok(body); + public ResponseEntity getMacro() { + CaptchaResult captchaData = captchaService.generateCaptcha(); + return ResponseEntity.ok(new MacroResponse(200, captchaData)); } private ResponseCookie createRefreshTokenCookie(String refreshToken) { @@ -94,16 +88,4 @@ private ResponseCookie createRefreshTokenCookie(String refreshToken) { .path(COOKIE_PATH) .build(); } - - private MacroResponse createMacroResponse() { - Random random = new Random(); - int randomNumber = random.nextInt(30) + 1; - - MacroResponse.MacroData data = new MacroResponse.MacroData( - MACRO_ANSWERS.get(randomNumber - 1).toString(), - "/macro/" + randomNumber + ".jpg" - ); - - return new MacroResponse(200, data); - } } diff --git a/src/main/java/com/tutorialsejong/courseregistration/domain/auth/dto/CaptchaResult.java b/src/main/java/com/tutorialsejong/courseregistration/domain/auth/dto/CaptchaResult.java new file mode 100644 index 0000000..c260340 --- /dev/null +++ b/src/main/java/com/tutorialsejong/courseregistration/domain/auth/dto/CaptchaResult.java @@ -0,0 +1,7 @@ +package com.tutorialsejong.courseregistration.domain.auth.dto; + +public record CaptchaResult( + String answer, + String url +) { +} diff --git a/src/main/java/com/tutorialsejong/courseregistration/domain/auth/dto/MacroResponse.java b/src/main/java/com/tutorialsejong/courseregistration/domain/auth/dto/MacroResponse.java index 3d8b7f7..6373dd5 100644 --- a/src/main/java/com/tutorialsejong/courseregistration/domain/auth/dto/MacroResponse.java +++ b/src/main/java/com/tutorialsejong/courseregistration/domain/auth/dto/MacroResponse.java @@ -1,53 +1,7 @@ package com.tutorialsejong.courseregistration.domain.auth.dto; -public class MacroResponse { - private Integer statusCode; - private MacroData data; - - public MacroResponse(Integer statusCode, MacroData data) { - this.statusCode = statusCode; - this.data = data; - } - - public Integer getStatusCode() { - return statusCode; - } - - public void setStatusCode(Integer statusCode) { - this.statusCode = statusCode; - } - - public MacroData getData() { - return data; - } - - public void setData(MacroData data) { - this.data = data; - } - - public static class MacroData { - private String answer; - private String url; - - public MacroData(String answer, String url) { - this.answer = answer; - this.url = url; - } - - public String getAnswer() { - return answer; - } - - public void setAnswer(String answer) { - this.answer = answer; - } - - public String getUrl() { - return url; - } - - public void setUrl(String url) { - this.url = url; - } - } +public record MacroResponse( + int statusCode, + CaptchaResult data +) { } diff --git a/src/main/java/com/tutorialsejong/courseregistration/domain/auth/service/CaptchaService.java b/src/main/java/com/tutorialsejong/courseregistration/domain/auth/service/CaptchaService.java new file mode 100644 index 0000000..2f73dc7 --- /dev/null +++ b/src/main/java/com/tutorialsejong/courseregistration/domain/auth/service/CaptchaService.java @@ -0,0 +1,48 @@ +package com.tutorialsejong.courseregistration.domain.auth.service; + +import cn.apiclub.captcha.Captcha; +import cn.apiclub.captcha.backgrounds.BackgroundProducer; +import cn.apiclub.captcha.text.producer.NumbersAnswerProducer; +import cn.apiclub.captcha.text.renderer.WordRenderer; +import com.tutorialsejong.courseregistration.domain.auth.dto.CaptchaResult; +import java.awt.image.BufferedImage; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.Base64; +import javax.imageio.ImageIO; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class CaptchaService { + + private static final int CAPTCHA_WIDTH = 200; + private static final int CAPTCHA_HEIGHT = 80; + private static final int CAPTCHA_LENGTH = 4; + + private final WordRenderer wordRenderer; + private final BackgroundProducer backgroundProducer; + + public CaptchaResult generateCaptcha() { + Captcha captcha = new Captcha.Builder(CAPTCHA_WIDTH, CAPTCHA_HEIGHT) + .addBackground(backgroundProducer) + .addText(new NumbersAnswerProducer(CAPTCHA_LENGTH), wordRenderer) + .build(); + + String base64Image = convertToBase64(captcha.getImage()); + String imageUrl = "data:image/png;base64," + base64Image; + + return new CaptchaResult(captcha.getAnswer(), imageUrl); + } + + private String convertToBase64(BufferedImage image) { + try { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + ImageIO.write(image, "png", baos); + return Base64.getEncoder().encodeToString(baos.toByteArray()); + } catch (IOException e) { + throw new RuntimeException("Failed to convert image to Base64", e); + } + } +} From 836d09f9cdc87a92fdd9037a65091158769bf3db Mon Sep 17 00:00:00 2001 From: zhy2on <52701529+zhy2on@users.noreply.github.com> Date: Mon, 20 Jan 2025 00:29:24 +0900 Subject: [PATCH 21/28] =?UTF-8?q?chore:=20allowed-origins=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/resources/config | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/resources/config b/src/main/resources/config index 773b787..4478578 160000 --- a/src/main/resources/config +++ b/src/main/resources/config @@ -1 +1 @@ -Subproject commit 773b7876084a9f8013ab81020ec3772900dcc388 +Subproject commit 44785789c4540ecfb403043177236fd746058277 From d8ef5ef75e8c3f776c55ab4aca03151b7bb45cf7 Mon Sep 17 00:00:00 2001 From: zhy2on <52701529+zhy2on@users.noreply.github.com> Date: Mon, 20 Jan 2025 01:07:12 +0900 Subject: [PATCH 22/28] =?UTF-8?q?feat(TS-48):=20=EC=9D=B8=EA=B8=B0=20?= =?UTF-8?q?=EA=B0=95=EC=A2=8C=20=EB=AA=A9=EB=A1=9D=20=EC=A1=B0=ED=9A=8C=20?= =?UTF-8?q?=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=ED=98=84=20(#87)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: Schedule 엔티티에 관심과목 수 관리 기능 추가 - wish_count 필드 추가 (기본값 0, null 불가) - 관심과목 증가/감소 메서드 추가 (incrementWishCount, decrementWishCount) - decrementWishCount 메서드에 음수 방지 로직 포함 - lombok 어노테이션 활용하여 코드 개선 * feat: 관심과목 등록/삭제 시 wishCount 업데이트 로직 추가 - 관심과목 등록 시 해당 과목의 wishCount 증가 - 관심과목 삭제 시 해당 과목의 wishCount 감소 * feat: 인기 과목 조회를 위한 Repository 메서드 추가 - wish_count 기준 내림차순 정렬 쿼리 메서드 추가 - Pageable을 통한 조회 개수 제한 지원 * feat: 인기 과목 조회 API 구현 - 인기 과목 순위 계산 로직 구현 (동일 wish_count는 같은 순위 부여) - 인기 과목 조회 API 엔드포인트 추가 (/api/schedules/popular) - limit 파라미터를 통한 조회 개수 제어 기능 추가 * refactor: 인기 과목 순위 계산 로직 개선 - 순위 계산 로직을 RankInfo 클래스로 분리하여 캡슐화 - calculatePopularSchedulesWithRank 메서드 추출로 책임 분리 --- .../controller/ScheduleController.java | 12 ++- .../domain/schedule/dto/ScheduleResponse.java | 36 ++++++++ .../domain/schedule/entity/Schedule.java | 92 ++++--------------- .../repository/ScheduleRepository.java | 6 +- .../schedule/service/ScheduleService.java | 48 +++++++++- .../wishlist/service/WishListService.java | 50 +++++----- 6 files changed, 141 insertions(+), 103 deletions(-) create mode 100644 src/main/java/com/tutorialsejong/courseregistration/domain/schedule/dto/ScheduleResponse.java diff --git a/src/main/java/com/tutorialsejong/courseregistration/domain/schedule/controller/ScheduleController.java b/src/main/java/com/tutorialsejong/courseregistration/domain/schedule/controller/ScheduleController.java index f2fd6fc..d650fe4 100644 --- a/src/main/java/com/tutorialsejong/courseregistration/domain/schedule/controller/ScheduleController.java +++ b/src/main/java/com/tutorialsejong/courseregistration/domain/schedule/controller/ScheduleController.java @@ -1,6 +1,7 @@ package com.tutorialsejong.courseregistration.domain.schedule.controller; import com.tutorialsejong.courseregistration.domain.schedule.dto.ErrorDto; +import com.tutorialsejong.courseregistration.domain.schedule.dto.ScheduleResponse; import com.tutorialsejong.courseregistration.domain.schedule.dto.ScheduleSearchRequest; import com.tutorialsejong.courseregistration.domain.schedule.entity.Schedule; import com.tutorialsejong.courseregistration.domain.schedule.service.ScheduleService; @@ -19,6 +20,7 @@ import org.springframework.security.core.userdetails.UserDetails; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.context.request.WebRequest; @@ -27,7 +29,8 @@ public class ScheduleController { private static final Set ALLOWED_PARAMS = Set.of( - "curiNo", "classNo", "schCollegeAlias", "schDeptAlias", "curiTypeCdNm", "sltDomainCdNm", "curiNm", "lesnEmp", "studentId" + "curiNo", "classNo", "schCollegeAlias", "schDeptAlias", "curiTypeCdNm", "sltDomainCdNm", "curiNm", + "lesnEmp", "studentId" ); private final ScheduleService scheduleService; @@ -69,4 +72,11 @@ private ResponseEntity createErrorResponse(HttpStatus status, String m return ResponseEntity.status(status) .body(new ErrorDto(new Date(), status.value(), message, request.getDescription(false))); } + + @GetMapping("/popular") + public ResponseEntity> getPopularSchedules( + @RequestParam(defaultValue = "10") int limit) { + List popularSchedules = scheduleService.findPopularSchedules(limit); + return ResponseEntity.ok(popularSchedules); + } } diff --git a/src/main/java/com/tutorialsejong/courseregistration/domain/schedule/dto/ScheduleResponse.java b/src/main/java/com/tutorialsejong/courseregistration/domain/schedule/dto/ScheduleResponse.java new file mode 100644 index 0000000..cc03381 --- /dev/null +++ b/src/main/java/com/tutorialsejong/courseregistration/domain/schedule/dto/ScheduleResponse.java @@ -0,0 +1,36 @@ +package com.tutorialsejong.courseregistration.domain.schedule.dto; + +import com.tutorialsejong.courseregistration.domain.schedule.entity.Schedule; + +public record ScheduleResponse( + Long scheduleId, + String curiNm, // 과목명 + String curiNo, // 과목번호 + String classNo, // 분반 + String manageDeptNm, // 개설학과 + String lesnEmp, // 담당교수 + String lesnTime, // 강의시간 + String lesnRoom, // 강의실 + Long wishCount, // 관심과목 수 + Integer rank // 순위 +) { + public static ScheduleResponse from(Schedule schedule, Integer rank) { + return new ScheduleResponse( + schedule.getScheduleId(), + schedule.getCuriNm(), + schedule.getCuriNo(), + schedule.getClassNo(), + schedule.getManageDeptNm(), + schedule.getLesnEmp(), + schedule.getLesnTime(), + schedule.getLesnRoom(), + schedule.getWishCount(), + rank + ); + } + + // rank 없는 버전의 from 메서드 - 일반 과목 조회시 사용 + public static ScheduleResponse from(Schedule schedule) { + return from(schedule, null); + } +} diff --git a/src/main/java/com/tutorialsejong/courseregistration/domain/schedule/entity/Schedule.java b/src/main/java/com/tutorialsejong/courseregistration/domain/schedule/entity/Schedule.java index 11794dd..3f28328 100644 --- a/src/main/java/com/tutorialsejong/courseregistration/domain/schedule/entity/Schedule.java +++ b/src/main/java/com/tutorialsejong/courseregistration/domain/schedule/entity/Schedule.java @@ -1,9 +1,18 @@ package com.tutorialsejong.courseregistration.domain.schedule.entity; -import jakarta.persistence.*; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import lombok.Getter; +import lombok.NoArgsConstructor; @Entity @Table(name = "course_schedule") +@Getter +@NoArgsConstructor public class Schedule { @Id @@ -67,83 +76,14 @@ public class Schedule { @Column(name = "remark") private String remark; - public Long getScheduleId() { - return scheduleId; - } - - public String getSchDeptAlias() { - return schDeptAlias; - } - - public String getCuriNo() { - return curiNo; - } - - public String getClassNo() { - return classNo; - } - - public String getSchCollegeAlias() { - return schCollegeAlias; - } - - public String getCuriNm() { - return curiNm; - } - - public String getCuriLangNm() { - return curiLangNm; - } - - public String getCuriTypeCdNm() { - return curiTypeCdNm; - } - - public String getSltDomainCdNm() { - return sltDomainCdNm; - } - - public String getTmNum() { - return tmNum; - } - - public String getStudentYear() { - return studentYear; - } - - public String getCorsUnitGrpCdNm() { - return corsUnitGrpCdNm; - } - - public String getManageDeptNm() { - return manageDeptNm; - } - - public String getLesnEmp() { - return lesnEmp; - } - - public String getLesnTime() { - return lesnTime; - } - - public String getLesnRoom() { - return lesnRoom; - } - - public String getCyberTypeCdNm() { - return cyberTypeCdNm; - } - - public String getInternshipTypeCdNm() { - return internshipTypeCdNm; - } + @Column(name = "wish_count", nullable = false) + private Long wishCount = 0L; - public String getInoutSubCdtExchangeYn() { - return inoutSubCdtExchangeYn; + public void incrementWishCount() { + this.wishCount++; } - public String getRemark() { - return remark; + public void decrementWishCount() { + this.wishCount = Math.max(0, this.wishCount - 1); } } diff --git a/src/main/java/com/tutorialsejong/courseregistration/domain/schedule/repository/ScheduleRepository.java b/src/main/java/com/tutorialsejong/courseregistration/domain/schedule/repository/ScheduleRepository.java index 24baeb3..55a6ab2 100644 --- a/src/main/java/com/tutorialsejong/courseregistration/domain/schedule/repository/ScheduleRepository.java +++ b/src/main/java/com/tutorialsejong/courseregistration/domain/schedule/repository/ScheduleRepository.java @@ -1,12 +1,12 @@ package com.tutorialsejong.courseregistration.domain.schedule.repository; import com.tutorialsejong.courseregistration.domain.schedule.entity.Schedule; +import java.util.List; +import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; -import java.util.List; - public interface ScheduleRepository extends JpaRepository { @Query("SELECT s FROM Schedule s WHERE " + @@ -36,4 +36,6 @@ List findAllBy( @Param("curi_nm") String curiNm, @Param("lesn_emp") String lesnEmp ); + + List findAllByOrderByWishCountDesc(Pageable pageable); } diff --git a/src/main/java/com/tutorialsejong/courseregistration/domain/schedule/service/ScheduleService.java b/src/main/java/com/tutorialsejong/courseregistration/domain/schedule/service/ScheduleService.java index 0991583..15ebce8 100644 --- a/src/main/java/com/tutorialsejong/courseregistration/domain/schedule/service/ScheduleService.java +++ b/src/main/java/com/tutorialsejong/courseregistration/domain/schedule/service/ScheduleService.java @@ -2,15 +2,20 @@ import com.tutorialsejong.courseregistration.domain.registration.entity.CourseRegistration; import com.tutorialsejong.courseregistration.domain.registration.repository.CourseRegistrationRepository; +import com.tutorialsejong.courseregistration.domain.schedule.dto.ScheduleResponse; import com.tutorialsejong.courseregistration.domain.schedule.dto.ScheduleSearchRequest; import com.tutorialsejong.courseregistration.domain.schedule.entity.Schedule; import com.tutorialsejong.courseregistration.domain.schedule.repository.ScheduleRepository; import com.tutorialsejong.courseregistration.domain.user.entity.User; import com.tutorialsejong.courseregistration.domain.user.exception.UserNotFoundException; import com.tutorialsejong.courseregistration.domain.user.repository.UserRepository; +import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.stream.Collectors; +import lombok.Getter; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.PageRequest; import org.springframework.stereotype.Service; @Service @@ -30,7 +35,7 @@ public ScheduleService(ScheduleRepository scheduleRepository, UserRepository use public List getSearchResultSchedules(ScheduleSearchRequest scheduleSearchRequest, String studentId) { User user = userRepository.findByStudentId(studentId) - .orElseThrow(() -> new UserNotFoundException()); + .orElseThrow(UserNotFoundException::new); List findAllByResult = scheduleRepository.findAllBy( scheduleSearchRequest.curiNo(), @@ -45,10 +50,49 @@ public List getSearchResultSchedules(ScheduleSearchRequest scheduleSea List registeredSchedules = courseRegistrationRepository.findAllByStudent(user).stream() .map(CourseRegistration::getSchedule) - .collect(Collectors.toList()); + .toList(); return findAllByResult.stream() .filter(schedule -> !registeredSchedules.contains(schedule)) .collect(Collectors.toList()); } + + public List findPopularSchedules(int limit) { + List schedules = scheduleRepository.findAllByOrderByWishCountDesc(PageRequest.of(0, limit)); + + if (schedules.isEmpty()) { + return Collections.emptyList(); + } + + return calculatePopularSchedulesWithRank(schedules); + } + + private List calculatePopularSchedulesWithRank(List schedules) { + List responses = new ArrayList<>(schedules.size()); + RankInfo rankInfo = new RankInfo(); + + for (Schedule schedule : schedules) { + rankInfo.updateRankIfWishCountChanged(schedule.getWishCount()); + responses.add(ScheduleResponse.from(schedule, rankInfo.getCurrentRank())); + } + + return responses; + } + + private static class RankInfo { + @Getter + private int currentRank = 1; + private Long previousWishCount = null; + + public void updateRankIfWishCountChanged(Long newWishCount) { + if (shouldIncrementRank(newWishCount)) { + currentRank++; + } + previousWishCount = newWishCount; + } + + private boolean shouldIncrementRank(Long newWishCount) { + return previousWishCount != null && !newWishCount.equals(previousWishCount); + } + } } diff --git a/src/main/java/com/tutorialsejong/courseregistration/domain/wishlist/service/WishListService.java b/src/main/java/com/tutorialsejong/courseregistration/domain/wishlist/service/WishListService.java index 807a925..0efa58e 100644 --- a/src/main/java/com/tutorialsejong/courseregistration/domain/wishlist/service/WishListService.java +++ b/src/main/java/com/tutorialsejong/courseregistration/domain/wishlist/service/WishListService.java @@ -14,12 +14,10 @@ import com.tutorialsejong.courseregistration.domain.user.repository.UserRepository; import com.tutorialsejong.courseregistration.domain.wishlist.entity.WishList; import com.tutorialsejong.courseregistration.domain.wishlist.exception.AlreadyInWishlistException; -import com.tutorialsejong.courseregistration.domain.wishlist.exception.WishlistCourseAlreadyRegisteredException; import com.tutorialsejong.courseregistration.domain.wishlist.exception.WishlistNotFoundException; import com.tutorialsejong.courseregistration.domain.wishlist.repository.WishListRepository; import java.util.List; import java.util.stream.Collectors; - import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Service; @@ -47,8 +45,8 @@ public WishListService(WishListRepository wishListRepository, public void saveWishListItem(String studentId, Long scheduleId) { logger.info(LogMessage.builder() .action(LogAction.ADD_WISHLIST) - .subject("s"+studentId) - .objectName("c"+scheduleId) + .subject("s" + studentId) + .objectName("c" + scheduleId) .result(LogResult.SUCCESS) .extras("Starting wishlist addition") .build().toString()); @@ -64,8 +62,8 @@ public void saveWishListItem(String studentId, Long scheduleId) { if (existsInWishList) { logger.warn(LogMessage.builder() .action(LogAction.ADD_WISHLIST) - .subject("s"+studentId) - .objectName("c"+scheduleId) + .subject("s" + studentId) + .objectName("c" + scheduleId) .result(LogResult.FAIL) .reason(LogReason.ALREADY_EXIST) .build().toString()); @@ -74,10 +72,14 @@ public void saveWishListItem(String studentId, Long scheduleId) { WishList newWishList = new WishList(user, schedule); wishListRepository.save(newWishList); + + schedule.incrementWishCount(); + scheduleRepository.save(schedule); + logger.info(LogMessage.builder() .action(LogAction.ADD_WISHLIST) - .subject("s"+studentId) - .objectName("c"+scheduleId) + .subject("s" + studentId) + .objectName("c" + scheduleId) .result(LogResult.SUCCESS) .extras("Successfully added to wishlist") .build().toString()); @@ -86,7 +88,7 @@ public void saveWishListItem(String studentId, Long scheduleId) { public List getWishList(String studentId) { logger.info(LogMessage.builder() .action(LogAction.FETCH_REGISTERED_COURSES) - .subject("s"+studentId) + .subject("s" + studentId) .result(LogResult.SUCCESS) .extras("Fetching wishlist") .build().toString()); @@ -97,7 +99,7 @@ public List getWishList(String studentId) { logger.info(LogMessage.builder() .action(LogAction.FETCH_REGISTERED_COURSES) - .subject("s"+studentId) + .subject("s" + studentId) .result(LogResult.SUCCESS) .extras("Wishlist fetched successfully") .build().toString()); @@ -112,7 +114,7 @@ public List getWishList(String studentId) { public User checkExistUser(String studentId) { logger.info(LogMessage.builder() .action(LogAction.VALIDATE_USER) - .subject("s"+studentId) + .subject("s" + studentId) .result(LogResult.SUCCESS) .extras("Validating user existence") .build().toString()); @@ -121,7 +123,7 @@ public User checkExistUser(String studentId) { .orElseThrow(() -> { logger.warn(LogMessage.builder() .action(LogAction.VALIDATE_USER) - .subject("s"+studentId) + .subject("s" + studentId) .result(LogResult.FAIL) .reason(LogReason.NOT_FOUND) .extras("User not found") @@ -133,7 +135,7 @@ public User checkExistUser(String studentId) { public Schedule checkExistSchedule(Long scheduleId) { logger.info(LogMessage.builder() .action(LogAction.VALIDATE_COURSE) - .objectName("c"+scheduleId) + .objectName("c" + scheduleId) .result(LogResult.SUCCESS) .extras("Validating schedule existence") .build().toString()); @@ -142,7 +144,7 @@ public Schedule checkExistSchedule(Long scheduleId) { .orElseThrow(() -> { logger.warn(LogMessage.builder() .action(LogAction.VALIDATE_COURSE) - .objectName("c"+scheduleId) + .objectName("c" + scheduleId) .result(LogResult.FAIL) .reason(LogReason.NOT_FOUND) .extras("Schedule not found") @@ -154,8 +156,8 @@ public Schedule checkExistSchedule(Long scheduleId) { public void deleteWishListItem(String studentId, Long scheduleId) { logger.info(LogMessage.builder() .action(LogAction.REMOVE_WISHLIST) // 추가된 LogAction - .subject("s"+studentId) - .objectName("c"+scheduleId) + .subject("s" + studentId) + .objectName("c" + scheduleId) .result(LogResult.SUCCESS) .extras("Starting removal of wishlist item") .build().toString()); @@ -167,8 +169,8 @@ public void deleteWishListItem(String studentId, Long scheduleId) { .orElseThrow(() -> { logger.warn(LogMessage.builder() .action(LogAction.REMOVE_WISHLIST) - .subject("s"+studentId) - .objectName("c"+scheduleId) + .subject("s" + studentId) + .objectName("c" + scheduleId) .result(LogResult.FAIL) .reason(LogReason.NOT_FOUND) .extras("Wishlist item not found") @@ -177,10 +179,14 @@ public void deleteWishListItem(String studentId, Long scheduleId) { }); wishListRepository.delete(wishList); + + schedule.decrementWishCount(); + scheduleRepository.save(schedule); + logger.info(LogMessage.builder() .action(LogAction.REMOVE_WISHLIST) - .subject("s"+studentId) - .objectName("c"+scheduleId) + .subject("s" + studentId) + .objectName("c" + scheduleId) .result(LogResult.SUCCESS) .extras("Removed wishlist item") .build().toString()); @@ -189,7 +195,7 @@ public void deleteWishListItem(String studentId, Long scheduleId) { public void deleteWishListsByStudent(String studentId) { logger.info(LogMessage.builder() .action(LogAction.REMOVE_WISHLIST) - .subject("s"+studentId) + .subject("s" + studentId) .result(LogResult.SUCCESS) .extras("Starting removal of all wishlist items for the user") .build().toString()); @@ -198,7 +204,7 @@ public void deleteWishListsByStudent(String studentId) { logger.info(LogMessage.builder() .action(LogAction.REMOVE_WISHLIST) - .subject("s"+studentId) + .subject("s" + studentId) .result(LogResult.SUCCESS) .extras("Removed all wishlist items for the user") .build().toString()); From a86ad3ffaecd871b7be08381f55b76ce6bf85398 Mon Sep 17 00:00:00 2001 From: zhy2on <52701529+zhy2on@users.noreply.github.com> Date: Mon, 20 Jan 2025 01:08:36 +0900 Subject: [PATCH 23/28] =?UTF-8?q?refactor:=20CORS=20=EC=84=A4=EC=A0=95?= =?UTF-8?q?=EC=9D=84=20application.properties=EB=A1=9C=20=EB=B6=84?= =?UTF-8?q?=EB=A6=AC=20(#88)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - SecurityConfig의 하드코딩된 CORS 설정을 외부 설정 파일로 이동 - allowed-origins와 allow-credentials 설정을 프로퍼티로 분리 --- .../common/config/SecurityConfig.java | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/tutorialsejong/courseregistration/common/config/SecurityConfig.java b/src/main/java/com/tutorialsejong/courseregistration/common/config/SecurityConfig.java index 1db58e4..e113334 100644 --- a/src/main/java/com/tutorialsejong/courseregistration/common/config/SecurityConfig.java +++ b/src/main/java/com/tutorialsejong/courseregistration/common/config/SecurityConfig.java @@ -6,6 +6,7 @@ import com.tutorialsejong.courseregistration.common.security.JwtTokenProvider; import java.util.Arrays; import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.authentication.AuthenticationManager; @@ -25,6 +26,12 @@ @EnableWebSecurity public class SecurityConfig { + @Value("${security.cors.allowed-origins}") + private String[] allowedOrigins; + + @Value("${security.cors.allow-credentials}") + private boolean allowCredentials; + private final JwtTokenProvider tokenProvider; @Bean @@ -38,12 +45,10 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { .cors(cors -> cors .configurationSource(request -> { CorsConfiguration config = new CorsConfiguration(); - config.setAllowedOrigins( - Arrays.asList("https://tutorial-sejong.com", "https://frontend.local.com:3000", - "http://localhost:3000")); + config.setAllowedOrigins(Arrays.asList(allowedOrigins)); + config.setAllowCredentials(allowCredentials); config.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE", "OPTIONS")); config.setAllowedHeaders(Arrays.asList("*")); - config.setAllowCredentials(true); return config; }) ) From e857b1367c59a79e4ded0d727b3bc66445ba4a07 Mon Sep 17 00:00:00 2001 From: Anhye0n <49294599+Anhye0n@users.noreply.github.com> Date: Mon, 20 Jan 2025 23:22:42 +0900 Subject: [PATCH 24/28] =?UTF-8?q?chore(TS-52):=20swagger=20=EC=84=A4?= =?UTF-8?q?=EC=A0=95=20(#89)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * chore: swagger 초기 세팅 * chore: submodule 업데이트 * chore: swagger 로그인 설정 * chore: Auth 도메인 swagger 설정 * chore: registraion 도메인 swagger 설정 * chore: schedule 도메인 swagger 설정 * chore: wishlist 도메인 swagger 설정 * refactor: shouldNotFilter 메서드 수정 * refactor: 에러 응답 일관성 수정 * refactor: Swagger SecurityConfig 구조 수정 * refactor: Swagger 관련 컴포넌트 개선 - SwaggerAccessDeniedHandler를 common/security 패키지로 이동 - SwaggerAuthenticationEntryPoint에서 불필요한 setStatus 제거 (ErrorResponse에서 처리) - 불필요한 ServletException throws 제거 * chore: 파일 끝 개행 삭제 * chore: redundant parameter 제거 - required=false 제거 - 스프링독(SpringDoc) OpenAPI 문서의 @Parameter 어노테이션에서 required 속성의 기본값이 이미 false이므로 불필요. * chore: 파일 끝 개행 추가 --------- Co-authored-by: zhy2on <52701529+zhy2on@users.noreply.github.com> --- build.gradle | 2 + .../common/config/SecurityConfig.java | 55 ++++++- .../common/config/SpringDocConfig.java | 36 +++++ .../common/exception/GlobalErrorCode.java | 1 + .../security/SwaggerAccessDeniedHandler.java | 19 +++ .../SwaggerAuthenticationEntryPoint.java | 20 +++ .../auth/controller/AuthController.java | 11 ++ .../domain/auth/dto/CaptchaResult.java | 6 + .../domain/auth/dto/LoginRequest.java | 3 + .../domain/auth/dto/LoginResponse.java | 5 + .../domain/auth/dto/MacroResponse.java | 5 + .../domain/auth/swagger/LoginOperation.java | 27 ++++ .../domain/auth/swagger/MacroOperation.java | 30 ++++ .../domain/auth/swagger/RefreshOperation.java | 41 +++++ .../auth/swagger/WithdrawalOperation.java | 35 ++++ .../CourseRegistrationController.java | 31 +++- .../dto/CourseRegistrationResponse.java | 5 + .../CourseRegistrationScheduleResponse.java | 151 ++++++++++++++++++ .../service/CourseRegistrationService.java | 15 +- ...CancelAllCourseRegistrationsOperation.java | 25 +++ .../CancelCourseRegistrationOperation.java | 28 ++++ .../GetRegisteredCoursesOperation.java | 31 ++++ .../swagger/RegisterCourseOperation.java | 30 ++++ .../controller/ScheduleController.java | 27 +++- .../schedule/dto/ScheduleSearchRequest.java | 39 +++++ .../swagger/GetPopularSchedulesOperation.java | 38 +++++ .../swagger/SearchSchedulesOperation.java | 50 ++++++ .../controller/WishListController.java | 41 +++-- .../domain/wishlist/dto/WishListRequest.java | 4 + .../swagger/DeleteWishListOperation.java | 30 ++++ .../swagger/GetWishListOperation.java | 30 ++++ .../swagger/SaveWishListOperation.java | 30 ++++ src/main/resources/config | 2 +- 33 files changed, 861 insertions(+), 42 deletions(-) create mode 100644 src/main/java/com/tutorialsejong/courseregistration/common/config/SpringDocConfig.java create mode 100644 src/main/java/com/tutorialsejong/courseregistration/common/security/SwaggerAccessDeniedHandler.java create mode 100644 src/main/java/com/tutorialsejong/courseregistration/common/security/SwaggerAuthenticationEntryPoint.java create mode 100644 src/main/java/com/tutorialsejong/courseregistration/domain/auth/swagger/LoginOperation.java create mode 100644 src/main/java/com/tutorialsejong/courseregistration/domain/auth/swagger/MacroOperation.java create mode 100644 src/main/java/com/tutorialsejong/courseregistration/domain/auth/swagger/RefreshOperation.java create mode 100644 src/main/java/com/tutorialsejong/courseregistration/domain/auth/swagger/WithdrawalOperation.java create mode 100644 src/main/java/com/tutorialsejong/courseregistration/domain/registration/dto/CourseRegistrationScheduleResponse.java create mode 100644 src/main/java/com/tutorialsejong/courseregistration/domain/registration/swagger/CancelAllCourseRegistrationsOperation.java create mode 100644 src/main/java/com/tutorialsejong/courseregistration/domain/registration/swagger/CancelCourseRegistrationOperation.java create mode 100644 src/main/java/com/tutorialsejong/courseregistration/domain/registration/swagger/GetRegisteredCoursesOperation.java create mode 100644 src/main/java/com/tutorialsejong/courseregistration/domain/registration/swagger/RegisterCourseOperation.java create mode 100644 src/main/java/com/tutorialsejong/courseregistration/domain/schedule/swagger/GetPopularSchedulesOperation.java create mode 100644 src/main/java/com/tutorialsejong/courseregistration/domain/schedule/swagger/SearchSchedulesOperation.java create mode 100644 src/main/java/com/tutorialsejong/courseregistration/domain/wishlist/swagger/DeleteWishListOperation.java create mode 100644 src/main/java/com/tutorialsejong/courseregistration/domain/wishlist/swagger/GetWishListOperation.java create mode 100644 src/main/java/com/tutorialsejong/courseregistration/domain/wishlist/swagger/SaveWishListOperation.java diff --git a/build.gradle b/build.gradle index 2f1aa9f..3618269 100644 --- a/build.gradle +++ b/build.gradle @@ -32,6 +32,8 @@ dependencies { runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.11.5' runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.11.5' + implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.2.0' + testImplementation 'org.springframework.boot:spring-boot-starter-test' testImplementation 'org.springframework.security:spring-security-test' testImplementation 'io.rest-assured:rest-assured:5.4.0' diff --git a/src/main/java/com/tutorialsejong/courseregistration/common/config/SecurityConfig.java b/src/main/java/com/tutorialsejong/courseregistration/common/config/SecurityConfig.java index e113334..4ed2687 100644 --- a/src/main/java/com/tutorialsejong/courseregistration/common/config/SecurityConfig.java +++ b/src/main/java/com/tutorialsejong/courseregistration/common/config/SecurityConfig.java @@ -4,19 +4,26 @@ import com.tutorialsejong.courseregistration.common.security.JwtAuthenticationFilter; import com.tutorialsejong.courseregistration.common.security.JwtExceptionFilter; import com.tutorialsejong.courseregistration.common.security.JwtTokenProvider; +import com.tutorialsejong.courseregistration.common.security.SwaggerAccessDeniedHandler; +import com.tutorialsejong.courseregistration.common.security.SwaggerAuthenticationEntryPoint; import java.util.Arrays; import lombok.RequiredArgsConstructor; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.core.annotation.Order; import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.config.Customizer; import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; import org.springframework.security.config.http.SessionCreationPolicy; +import org.springframework.security.core.userdetails.User; +import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.security.provisioning.InMemoryUserDetailsManager; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; import org.springframework.web.cors.CorsConfiguration; @@ -32,15 +39,49 @@ public class SecurityConfig { @Value("${security.cors.allow-credentials}") private boolean allowCredentials; + @Value("${app.swagger.user.name}") + private String swaggerUsername; + + @Value("${app.swagger.user.password}") + private String swaggerPassword; + private final JwtTokenProvider tokenProvider; @Bean - public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { + @Order(1) + public SecurityFilterChain swaggerFilterChain(HttpSecurity http, PasswordEncoder passwordEncoder) throws Exception { + // 1) Swagger용 인메모리 사용자 생성 + UserDetails admin = User.withUsername(swaggerUsername) + .password(passwordEncoder.encode(swaggerPassword)) + .roles("ADMIN") + .build(); + + InMemoryUserDetailsManager swaggerUserManager = new InMemoryUserDetailsManager(admin); + + // 2) Swagger 경로에 대해서만 Basic Auth 설정 + http + .securityMatcher("/docs/swagger-ui/**", "/v3/api-docs/**") + .csrf(AbstractHttpConfigurer::disable) + .httpBasic(Customizer.withDefaults()) + .userDetailsService(swaggerUserManager) + .authorizeHttpRequests(authorize -> authorize + .anyRequest().authenticated() + ) + .exceptionHandling(exceptionHandling -> exceptionHandling + .authenticationEntryPoint(new SwaggerAuthenticationEntryPoint()) // 401 + .accessDeniedHandler(new SwaggerAccessDeniedHandler()) // 403 + ); + + return http.build(); + } + + @Bean + @Order(2) + public SecurityFilterChain apiFilterChain(HttpSecurity http) throws Exception { http .csrf(AbstractHttpConfigurer::disable) .headers(headers -> headers - .contentSecurityPolicy(csp -> csp - .policyDirectives("frame-ancestors 'self'")) + .contentSecurityPolicy(csp -> csp.policyDirectives("frame-ancestors 'self'")) ) .cors(cors -> cors .configurationSource(request -> { @@ -52,8 +93,7 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { return config; }) ) - .sessionManagement(session -> session - .sessionCreationPolicy(SessionCreationPolicy.STATELESS)) + .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) .authorizeHttpRequests(auth -> auth .requestMatchers( "/api/auth/login", @@ -62,13 +102,12 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { ).permitAll() .anyRequest().authenticated() ) + // JWT 기반 인증 필터 적용 .exceptionHandling(exceptionHandling -> exceptionHandling .authenticationEntryPoint(new JwtAuthenticationEntryPoint()) ) - .addFilterBefore(new JwtAuthenticationFilter(tokenProvider), - UsernamePasswordAuthenticationFilter.class) + .addFilterBefore(new JwtAuthenticationFilter(tokenProvider), UsernamePasswordAuthenticationFilter.class) .addFilterBefore(new JwtExceptionFilter(), JwtAuthenticationFilter.class); - return http.build(); } diff --git a/src/main/java/com/tutorialsejong/courseregistration/common/config/SpringDocConfig.java b/src/main/java/com/tutorialsejong/courseregistration/common/config/SpringDocConfig.java new file mode 100644 index 0000000..9f3ce2f --- /dev/null +++ b/src/main/java/com/tutorialsejong/courseregistration/common/config/SpringDocConfig.java @@ -0,0 +1,36 @@ +package com.tutorialsejong.courseregistration.common.config; + +import io.swagger.v3.oas.annotations.enums.SecuritySchemeType; +import io.swagger.v3.oas.annotations.security.SecurityScheme; +import io.swagger.v3.oas.models.OpenAPI; +import io.swagger.v3.oas.models.info.Info; +import io.swagger.v3.oas.models.servers.Server; +import java.util.List; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +@SecurityScheme( + name = "BearerAuth", + type = SecuritySchemeType.HTTP, + scheme = "bearer", + bearerFormat = "JWT" +) +public class SpringDocConfig { + + @Bean + public OpenAPI customOpenAPI() { + return new OpenAPI() + .servers(List.of( + new Server().url("https://tutorial-sejong.com").description("Production Server"), + new Server().url("http://dev-tutorial-sejong.o-r.kr:8090").description("Test Server"), + new Server().url("http://localhost:8080").description("Local Server") + )) + // API 정보 + .info(new Info() + .title("Tutorial Sejong API") + .version("v2.0.0") + .description("Tutorial Sejong API API") + ); + } +} diff --git a/src/main/java/com/tutorialsejong/courseregistration/common/exception/GlobalErrorCode.java b/src/main/java/com/tutorialsejong/courseregistration/common/exception/GlobalErrorCode.java index fc12243..0205e1b 100644 --- a/src/main/java/com/tutorialsejong/courseregistration/common/exception/GlobalErrorCode.java +++ b/src/main/java/com/tutorialsejong/courseregistration/common/exception/GlobalErrorCode.java @@ -13,6 +13,7 @@ public enum GlobalErrorCode implements ErrorCode { RESOURCE_NOT_FOUND("G003", "요청한 리소스를 찾을 수 없습니다.", HttpStatus.NOT_FOUND), METHOD_NOT_ALLOWED("G004", "허용되지 않는 메서드입니다.", HttpStatus.METHOD_NOT_ALLOWED), HANDLE_ACCESS_DENIED("G005", "접근이 거부되었습니다.", HttpStatus.FORBIDDEN), + UNAUTHORIZED("G006", "인증되지 않은 사용자입니다.", HttpStatus.UNAUTHORIZED), TOO_MANY_REQUESTS("G006", "과도한 요청을 보내셨습니다. 잠시 기다려 주세요.", HttpStatus.TOO_MANY_REQUESTS); private final String code; diff --git a/src/main/java/com/tutorialsejong/courseregistration/common/security/SwaggerAccessDeniedHandler.java b/src/main/java/com/tutorialsejong/courseregistration/common/security/SwaggerAccessDeniedHandler.java new file mode 100644 index 0000000..db0e2b6 --- /dev/null +++ b/src/main/java/com/tutorialsejong/courseregistration/common/security/SwaggerAccessDeniedHandler.java @@ -0,0 +1,19 @@ +package com.tutorialsejong.courseregistration.common.security; + +import com.tutorialsejong.courseregistration.common.exception.ErrorResponse; +import com.tutorialsejong.courseregistration.common.exception.GlobalErrorCode; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import java.io.IOException; +import org.springframework.security.access.AccessDeniedException; +import org.springframework.security.web.access.AccessDeniedHandler; + +public class SwaggerAccessDeniedHandler implements AccessDeniedHandler { + + @Override + public void handle(HttpServletRequest request, HttpServletResponse response, + AccessDeniedException accessDeniedException) throws IOException { + ErrorResponse.from(GlobalErrorCode.HANDLE_ACCESS_DENIED) + .writeTo(response); + } +} diff --git a/src/main/java/com/tutorialsejong/courseregistration/common/security/SwaggerAuthenticationEntryPoint.java b/src/main/java/com/tutorialsejong/courseregistration/common/security/SwaggerAuthenticationEntryPoint.java new file mode 100644 index 0000000..542d09f --- /dev/null +++ b/src/main/java/com/tutorialsejong/courseregistration/common/security/SwaggerAuthenticationEntryPoint.java @@ -0,0 +1,20 @@ +package com.tutorialsejong.courseregistration.common.security; + +import com.tutorialsejong.courseregistration.common.exception.ErrorResponse; +import com.tutorialsejong.courseregistration.common.exception.GlobalErrorCode; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import java.io.IOException; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.web.AuthenticationEntryPoint; + +public class SwaggerAuthenticationEntryPoint implements AuthenticationEntryPoint { + + @Override + public void commence(HttpServletRequest request, HttpServletResponse response, + AuthenticationException authException) throws IOException { + response.addHeader("WWW-Authenticate", "Basic realm=\"Tutorial Sejong Swagger\""); + ErrorResponse.from(GlobalErrorCode.UNAUTHORIZED) + .writeTo(response); + } +} diff --git a/src/main/java/com/tutorialsejong/courseregistration/domain/auth/controller/AuthController.java b/src/main/java/com/tutorialsejong/courseregistration/domain/auth/controller/AuthController.java index ac5142d..7e9df7a 100644 --- a/src/main/java/com/tutorialsejong/courseregistration/domain/auth/controller/AuthController.java +++ b/src/main/java/com/tutorialsejong/courseregistration/domain/auth/controller/AuthController.java @@ -8,6 +8,12 @@ import com.tutorialsejong.courseregistration.domain.auth.dto.MacroResponse; import com.tutorialsejong.courseregistration.domain.auth.service.AuthService; import com.tutorialsejong.courseregistration.domain.auth.service.CaptchaService; +import com.tutorialsejong.courseregistration.domain.auth.swagger.LoginOperation; +import com.tutorialsejong.courseregistration.domain.auth.swagger.MacroOperation; +import com.tutorialsejong.courseregistration.domain.auth.swagger.RefreshOperation; +import com.tutorialsejong.courseregistration.domain.auth.swagger.WithdrawalOperation; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.servlet.http.HttpServletResponse; import jakarta.validation.Valid; import java.time.Duration; @@ -31,6 +37,7 @@ @RestController @RequiredArgsConstructor @RequestMapping("/api/auth") +@Tag(name = "인증 API", description = "인증 관련 API") public class AuthController { private static final String REFRESH_TOKEN_COOKIE_NAME = "refreshToken"; @@ -42,6 +49,7 @@ public class AuthController { @Value("${app.jwt.refreshTokenExpirationInMs}") private int refreshTokenExpirationInMs; + @LoginOperation @PostMapping("/login") public ResponseEntity login(@RequestBody @Valid LoginRequest loginRequest, HttpServletResponse response) { AuthenticationResult authResult = authService.loginOrSignup(loginRequest); @@ -53,6 +61,7 @@ public ResponseEntity login(@RequestBody @Valid LoginRequest loginRequest, Ht return ResponseEntity.ok(loginResponse); } + @RefreshOperation @PostMapping("/refresh") public ResponseEntity refreshToken(@CookieValue(name = "refreshToken", required = false) String refreshToken) { if (refreshToken == null) { @@ -65,6 +74,7 @@ public ResponseEntity refreshToken(@CookieValue(name = "refreshToken", requir return ResponseEntity.ok().body(body); } + @WithdrawalOperation @DeleteMapping("/withdrawal/{studentId}") public ResponseEntity withdrawal(@PathVariable("studentId") String studentId) { @@ -73,6 +83,7 @@ public ResponseEntity withdrawal(@PathVariable("studentId") String studentId) return ResponseEntity.ok().build(); } + @MacroOperation @GetMapping("/macro") public ResponseEntity getMacro() { CaptchaResult captchaData = captchaService.generateCaptcha(); diff --git a/src/main/java/com/tutorialsejong/courseregistration/domain/auth/dto/CaptchaResult.java b/src/main/java/com/tutorialsejong/courseregistration/domain/auth/dto/CaptchaResult.java index c260340..e9c77a9 100644 --- a/src/main/java/com/tutorialsejong/courseregistration/domain/auth/dto/CaptchaResult.java +++ b/src/main/java/com/tutorialsejong/courseregistration/domain/auth/dto/CaptchaResult.java @@ -1,7 +1,13 @@ package com.tutorialsejong.courseregistration.domain.auth.dto; +import io.swagger.v3.oas.annotations.media.Schema; + public record CaptchaResult( + @Schema(description = "캡차 정답(문자열)", example = "3427") String answer, + + @Schema(description = "캡차 이미지 주소 (Base64 인코딩 포함 Data URL 형태)", + example = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAABQCAIAAADTD63nAAAA...") String url ) { } diff --git a/src/main/java/com/tutorialsejong/courseregistration/domain/auth/dto/LoginRequest.java b/src/main/java/com/tutorialsejong/courseregistration/domain/auth/dto/LoginRequest.java index 6aa0f77..ee8d96e 100644 --- a/src/main/java/com/tutorialsejong/courseregistration/domain/auth/dto/LoginRequest.java +++ b/src/main/java/com/tutorialsejong/courseregistration/domain/auth/dto/LoginRequest.java @@ -1,11 +1,14 @@ package com.tutorialsejong.courseregistration.domain.auth.dto; +import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotBlank; public record LoginRequest( + @Schema(description = "11자리 이상 학번", example = "12345678911") @NotBlank(message = "studentId should not be empty") String studentId, + @Schema(description = "비밀번호", example = "12345678911") @NotBlank(message = "password should not be empty") String password ) { diff --git a/src/main/java/com/tutorialsejong/courseregistration/domain/auth/dto/LoginResponse.java b/src/main/java/com/tutorialsejong/courseregistration/domain/auth/dto/LoginResponse.java index f71bbdc..30eef12 100644 --- a/src/main/java/com/tutorialsejong/courseregistration/domain/auth/dto/LoginResponse.java +++ b/src/main/java/com/tutorialsejong/courseregistration/domain/auth/dto/LoginResponse.java @@ -1,6 +1,11 @@ package com.tutorialsejong.courseregistration.domain.auth.dto; +import io.swagger.v3.oas.annotations.media.Schema; + public record LoginResponse( + @Schema(description = "엑세스 토큰", example = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c") String accessToken, + + @Schema(description = "사용자 이름", example = "12345678911") String username) { } diff --git a/src/main/java/com/tutorialsejong/courseregistration/domain/auth/dto/MacroResponse.java b/src/main/java/com/tutorialsejong/courseregistration/domain/auth/dto/MacroResponse.java index 6373dd5..2577cb0 100644 --- a/src/main/java/com/tutorialsejong/courseregistration/domain/auth/dto/MacroResponse.java +++ b/src/main/java/com/tutorialsejong/courseregistration/domain/auth/dto/MacroResponse.java @@ -1,7 +1,12 @@ package com.tutorialsejong.courseregistration.domain.auth.dto; +import io.swagger.v3.oas.annotations.media.Schema; + public record MacroResponse( + @Schema(description = "상태 코드", example = "200") int statusCode, + + @Schema(description = "캡차 데이터") CaptchaResult data ) { } diff --git a/src/main/java/com/tutorialsejong/courseregistration/domain/auth/swagger/LoginOperation.java b/src/main/java/com/tutorialsejong/courseregistration/domain/auth/swagger/LoginOperation.java new file mode 100644 index 0000000..080afc0 --- /dev/null +++ b/src/main/java/com/tutorialsejong/courseregistration/domain/auth/swagger/LoginOperation.java @@ -0,0 +1,27 @@ +package com.tutorialsejong.courseregistration.domain.auth.swagger; + +import com.tutorialsejong.courseregistration.domain.auth.dto.LoginResponse; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import org.springframework.http.MediaType; + +@Target({ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +@Operation(summary = "로그인", description = "로그인 요청") +@ApiResponses(value = { + @ApiResponse( + responseCode = "200", + description = "성공", + content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE, + schema = @Schema(implementation = LoginResponse.class)) + ) +}) +public @interface LoginOperation { +} diff --git a/src/main/java/com/tutorialsejong/courseregistration/domain/auth/swagger/MacroOperation.java b/src/main/java/com/tutorialsejong/courseregistration/domain/auth/swagger/MacroOperation.java new file mode 100644 index 0000000..445faf1 --- /dev/null +++ b/src/main/java/com/tutorialsejong/courseregistration/domain/auth/swagger/MacroOperation.java @@ -0,0 +1,30 @@ +package com.tutorialsejong.courseregistration.domain.auth.swagger; + +import com.tutorialsejong.courseregistration.domain.auth.dto.MacroResponse; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.security.SecurityRequirement; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import org.springframework.http.MediaType; + +@Target({ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +@Operation(summary = "매크로 코드", description = "매크로 코드 발급", security = @SecurityRequirement(name = "BearerAuth")) +@ApiResponses(value = { + @ApiResponse( + responseCode = "200", + description = "성공", + content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE, + schema = @Schema(implementation = MacroResponse.class) + ) + ) +}) +@SecurityRequirement(name = "jwt") +public @interface MacroOperation { +} diff --git a/src/main/java/com/tutorialsejong/courseregistration/domain/auth/swagger/RefreshOperation.java b/src/main/java/com/tutorialsejong/courseregistration/domain/auth/swagger/RefreshOperation.java new file mode 100644 index 0000000..8d2b1d7 --- /dev/null +++ b/src/main/java/com/tutorialsejong/courseregistration/domain/auth/swagger/RefreshOperation.java @@ -0,0 +1,41 @@ +package com.tutorialsejong.courseregistration.domain.auth.swagger; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.enums.ParameterIn; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.ExampleObject; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.security.SecurityRequirement; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.util.Map; + +@Target({ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +@Operation(summary = "토큰 재발급", description = "Refresh Token 유효할 시, Access Token 재발급", security = @SecurityRequirement(name = "BearerAuth")) +@Parameter(name = "refreshToken", description = "발급 받은 Refresh Token", in = ParameterIn.COOKIE, required = true) +@ApiResponses(value = { + @ApiResponse( + responseCode = "200", + description = "성공", + content = @Content( + mediaType = "application/json", + schema = @Schema(implementation = Map.class), + examples = @ExampleObject( + value = """ + { + "accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c" + } + """ + ) + ) + ) +}) +public @interface RefreshOperation { +} diff --git a/src/main/java/com/tutorialsejong/courseregistration/domain/auth/swagger/WithdrawalOperation.java b/src/main/java/com/tutorialsejong/courseregistration/domain/auth/swagger/WithdrawalOperation.java new file mode 100644 index 0000000..c727afa --- /dev/null +++ b/src/main/java/com/tutorialsejong/courseregistration/domain/auth/swagger/WithdrawalOperation.java @@ -0,0 +1,35 @@ +package com.tutorialsejong.courseregistration.domain.auth.swagger; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.Parameters; +import io.swagger.v3.oas.annotations.enums.ParameterIn; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target({ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +@Operation(summary = "회원 탈퇴", description = "회원 탈퇴 api") +@Parameters({ + @Parameter( + name = "studentId", + description = "탈퇴할 유저의 학번", + required = true, + example = "12345678911", + in = ParameterIn.PATH + ) +}) +@ApiResponses(value = { + @ApiResponse( + responseCode = "200", + description = "성공", + content = @Content() + ) +}) +public @interface WithdrawalOperation { +} diff --git a/src/main/java/com/tutorialsejong/courseregistration/domain/registration/controller/CourseRegistrationController.java b/src/main/java/com/tutorialsejong/courseregistration/domain/registration/controller/CourseRegistrationController.java index 73ce140..f1d6f59 100644 --- a/src/main/java/com/tutorialsejong/courseregistration/domain/registration/controller/CourseRegistrationController.java +++ b/src/main/java/com/tutorialsejong/courseregistration/domain/registration/controller/CourseRegistrationController.java @@ -1,9 +1,14 @@ package com.tutorialsejong.courseregistration.domain.registration.controller; - import com.tutorialsejong.courseregistration.domain.registration.dto.CourseRegistrationResponse; +import com.tutorialsejong.courseregistration.domain.registration.dto.CourseRegistrationScheduleResponse; import com.tutorialsejong.courseregistration.domain.registration.service.CourseRegistrationService; -import com.tutorialsejong.courseregistration.domain.schedule.entity.Schedule; +import com.tutorialsejong.courseregistration.domain.registration.swagger.CancelAllCourseRegistrationsOperation; +import com.tutorialsejong.courseregistration.domain.registration.swagger.CancelCourseRegistrationOperation; +import com.tutorialsejong.courseregistration.domain.registration.swagger.GetRegisteredCoursesOperation; +import com.tutorialsejong.courseregistration.domain.registration.swagger.RegisterCourseOperation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; import java.util.List; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; @@ -18,6 +23,7 @@ @RestController @RequestMapping("/registrations") +@Tag(name = "수강신청 API", description = "수강신청 관련 API") public class CourseRegistrationController { private final CourseRegistrationService courseRegistrationService; @@ -26,35 +32,44 @@ public CourseRegistrationController(CourseRegistrationService courseRegistration this.courseRegistrationService = courseRegistrationService; } + @RegisterCourseOperation @PostMapping("/{scheduleId}") public ResponseEntity registerCourse( + @Parameter(description = "강의 ID", required = true, example = "1") @PathVariable Long scheduleId, @AuthenticationPrincipal UserDetails userDetails) { - CourseRegistrationResponse registration = courseRegistrationService.registerCourse(userDetails.getUsername(), - scheduleId); + CourseRegistrationResponse registration = courseRegistrationService.registerCourse( + userDetails.getUsername(), scheduleId); return ResponseEntity.status(HttpStatus.CREATED).body(registration); } + @GetRegisteredCoursesOperation @GetMapping - public ResponseEntity> getRegisteredCourses( - @AuthenticationPrincipal UserDetails userDetails) { - List registrations = courseRegistrationService.getRegisteredCourses(userDetails.getUsername()); + public ResponseEntity> getRegisteredCourses( + @AuthenticationPrincipal UserDetails userDetails + ) { + List registrations = courseRegistrationService.getRegisteredCourses( + userDetails.getUsername()); if (registrations.isEmpty()) { return ResponseEntity.noContent().build(); } return ResponseEntity.ok(registrations); } + @CancelCourseRegistrationOperation @DeleteMapping("/{scheduleId}") public ResponseEntity cancelCourseRegistration( + @Parameter(description = "수강신청할 강의의 ID", required = true, example = "1") @PathVariable Long scheduleId, @AuthenticationPrincipal UserDetails userDetails) { courseRegistrationService.cancelCourseRegistration(userDetails.getUsername(), scheduleId); return ResponseEntity.ok().build(); } + @CancelAllCourseRegistrationsOperation @DeleteMapping("/all") - public ResponseEntity cancelAllCourseRegistrations(@AuthenticationPrincipal UserDetails userDetails) { + public ResponseEntity cancelAllCourseRegistrations( + @AuthenticationPrincipal UserDetails userDetails) { courseRegistrationService.cancelAllCourseRegistrations(userDetails.getUsername()); return ResponseEntity.ok().build(); } diff --git a/src/main/java/com/tutorialsejong/courseregistration/domain/registration/dto/CourseRegistrationResponse.java b/src/main/java/com/tutorialsejong/courseregistration/domain/registration/dto/CourseRegistrationResponse.java index 8a09879..3cd9520 100644 --- a/src/main/java/com/tutorialsejong/courseregistration/domain/registration/dto/CourseRegistrationResponse.java +++ b/src/main/java/com/tutorialsejong/courseregistration/domain/registration/dto/CourseRegistrationResponse.java @@ -1,7 +1,12 @@ package com.tutorialsejong.courseregistration.domain.registration.dto; +import io.swagger.v3.oas.annotations.media.Schema; + public record CourseRegistrationResponse( + @Schema(description = "11자리 이상 학번", example = "12345678911") String studentId, + + @Schema(description = "강의 ID", example = "1") Long scheduleId ) { } diff --git a/src/main/java/com/tutorialsejong/courseregistration/domain/registration/dto/CourseRegistrationScheduleResponse.java b/src/main/java/com/tutorialsejong/courseregistration/domain/registration/dto/CourseRegistrationScheduleResponse.java new file mode 100644 index 0000000..07683c4 --- /dev/null +++ b/src/main/java/com/tutorialsejong/courseregistration/domain/registration/dto/CourseRegistrationScheduleResponse.java @@ -0,0 +1,151 @@ +package com.tutorialsejong.courseregistration.domain.registration.dto; + +import com.tutorialsejong.courseregistration.domain.schedule.entity.Schedule; +import io.swagger.v3.oas.annotations.media.Schema; + +@Schema(description = "수강 일정 응답 DTO") +public record CourseRegistrationScheduleResponse( + @Schema(description = "수강 일정 ID", example = "1") + Long scheduleId, + + @Schema(description = "학과 별칭", example = "대양휴머니티칼리지") + String schDeptAlias, + + @Schema(description = "교과목 번호", example = "009352") + String curiNo, + + @Schema(description = "분반 번호", example = "001") + String classNo, + + @Schema(description = "단과 대학 별칭", example = "대양휴머니티칼리지") + String schCollegeAlias, + + @Schema(description = "교과목 명", example = "사고와표현1") + String curiNm, + + @Schema(description = "교과목 수업 언어", example = "영어") + String curiLangNm, + + @Schema(description = "교과목 유형명", example = "공통교양필수") + String curiTypeCdNm, + + @Schema(description = "선택영역 코드명", example = "학문기초") + String sltDomainCdNm, + + @Schema(description = "시간 관련 정보", example = "3.0 / 3 / 0") + String tmNum, + + @Schema(description = "학년", example = "1") + String studentYear, + + @Schema(description = "이수 단위 그룹 코드명", example = "학사") + String corsUnitGrpCdNm, + + @Schema(description = "운영 부서 명", example = "국어국문학과") + String manageDeptNm, + + @Schema(description = "강사", example = "노지현") + String lesnEmp, + + @Schema(description = "수업 시간", example = "화 목 09:00~10:30") + String lesnTime, + + @Schema(description = "강의실", example = "세101") + String lesnRoom, + + @Schema(description = "사이버 유형 코드명", example = "null", nullable = true) + String cyberTypeCdNm, + + @Schema(description = "인턴십 유형 코드명", example = "null", nullable = true) + String internshipTypeCdNm, + + @Schema(description = "교과목 편입/교환 여부", example = "null", nullable = true) + String inoutSubCdtExchangeYn, + + @Schema(description = "비고", example = "외국인대상과목, 기초(Beginner)") + String remark, + + @Schema(description = "희망 수 강 인원수", example = "1") + Long wishCount +) { + + /** + * 엔티티 {@link Schedule}로부터 DTO 객체를 생성합니다. + */ + public static CourseRegistrationScheduleResponse from(Schedule schedule) { + return new CourseRegistrationScheduleResponse( + schedule.getScheduleId(), + schedule.getSchDeptAlias(), + schedule.getCuriNo(), + schedule.getClassNo(), + schedule.getSchCollegeAlias(), + schedule.getCuriNm(), + schedule.getCuriLangNm(), + schedule.getCuriTypeCdNm(), + schedule.getSltDomainCdNm(), + schedule.getTmNum(), + schedule.getStudentYear(), + schedule.getCorsUnitGrpCdNm(), + schedule.getManageDeptNm(), + schedule.getLesnEmp(), + schedule.getLesnTime(), + schedule.getLesnRoom(), + schedule.getCyberTypeCdNm(), + schedule.getInternshipTypeCdNm(), + schedule.getInoutSubCdtExchangeYn(), + schedule.getRemark(), + schedule.getWishCount() + ); + } + + /** + * 모든 필드를 인자로 받아 DTO 객체를 생성합니다. + */ + public static CourseRegistrationScheduleResponse of( + Long scheduleId, + String schDeptAlias, + String curiNo, + String classNo, + String schCollegeAlias, + String curiNm, + String curiLangNm, + String curiTypeCdNm, + String sltDomainCdNm, + String tmNum, + String studentYear, + String corsUnitGrpCdNm, + String manageDeptNm, + String lesnEmp, + String lesnTime, + String lesnRoom, + String cyberTypeCdNm, + String internshipTypeCdNm, + String inoutSubCdtExchangeYn, + String remark, + Long wishCount + ) { + return new CourseRegistrationScheduleResponse( + scheduleId, + schDeptAlias, + curiNo, + classNo, + schCollegeAlias, + curiNm, + curiLangNm, + curiTypeCdNm, + sltDomainCdNm, + tmNum, + studentYear, + corsUnitGrpCdNm, + manageDeptNm, + lesnEmp, + lesnTime, + lesnRoom, + cyberTypeCdNm, + internshipTypeCdNm, + inoutSubCdtExchangeYn, + remark, + wishCount + ); + } +} \ No newline at end of file diff --git a/src/main/java/com/tutorialsejong/courseregistration/domain/registration/service/CourseRegistrationService.java b/src/main/java/com/tutorialsejong/courseregistration/domain/registration/service/CourseRegistrationService.java index 9492d6d..3f65320 100644 --- a/src/main/java/com/tutorialsejong/courseregistration/domain/registration/service/CourseRegistrationService.java +++ b/src/main/java/com/tutorialsejong/courseregistration/domain/registration/service/CourseRegistrationService.java @@ -6,6 +6,7 @@ import com.tutorialsejong.courseregistration.common.utils.log.LogResult; import com.tutorialsejong.courseregistration.domain.auth.controller.AuthController; import com.tutorialsejong.courseregistration.domain.registration.dto.CourseRegistrationResponse; +import com.tutorialsejong.courseregistration.domain.registration.dto.CourseRegistrationScheduleResponse; import com.tutorialsejong.courseregistration.domain.registration.entity.CourseRegistration; import com.tutorialsejong.courseregistration.domain.registration.exception.CourseAlreadyRegisteredException; import com.tutorialsejong.courseregistration.domain.registration.repository.CourseRegistrationRepository; @@ -114,10 +115,10 @@ public CourseRegistrationResponse registerCourse(String studentId, Long schedule } @Transactional(readOnly = true) - public List getRegisteredCourses(String studentId) { + public List getRegisteredCourses(String studentId) { logger.info(LogMessage.builder() .action(LogAction.FETCH_REGISTERED_COURSES) - .subject("s"+studentId) + .subject("s" + studentId) .result(LogResult.SUCCESS) .extras("Fetching registered courses") .build().toString()); @@ -126,7 +127,7 @@ public List getRegisteredCourses(String studentId) { .orElseThrow(() -> { logger.warn(LogMessage.builder() .action(LogAction.VALIDATE_USER) - .subject("s"+studentId) + .subject("s" + studentId) .result(LogResult.FAIL) .reason(LogReason.NOT_FOUND) .build().toString()); @@ -134,7 +135,13 @@ public List getRegisteredCourses(String studentId) { }); List registrations = courseRegistrationRepository.findAllByStudent(student); - return registrations.stream().map(CourseRegistration::getSchedule).collect(Collectors.toList()); + + List response = registrations.stream() + .map(CourseRegistration::getSchedule) + .map(CourseRegistrationScheduleResponse::from) + .collect(Collectors.toList()); + + return response; } @Transactional diff --git a/src/main/java/com/tutorialsejong/courseregistration/domain/registration/swagger/CancelAllCourseRegistrationsOperation.java b/src/main/java/com/tutorialsejong/courseregistration/domain/registration/swagger/CancelAllCourseRegistrationsOperation.java new file mode 100644 index 0000000..c2fd3ca --- /dev/null +++ b/src/main/java/com/tutorialsejong/courseregistration/domain/registration/swagger/CancelAllCourseRegistrationsOperation.java @@ -0,0 +1,25 @@ +package com.tutorialsejong.courseregistration.domain.registration.swagger; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.security.SecurityRequirement; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import org.springframework.http.MediaType; + +@Target({ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +@Operation(summary = "전체 수강 취소", description = "로그인한 사용자의 전체 수강 등록을 취소한다.", security = @SecurityRequirement(name = "bearerAuth")) +@ApiResponses(value = { + @ApiResponse( + responseCode = "200", + description = "전체 수강 취소 성공", + content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE) + ) +}) +public @interface CancelAllCourseRegistrationsOperation { +} diff --git a/src/main/java/com/tutorialsejong/courseregistration/domain/registration/swagger/CancelCourseRegistrationOperation.java b/src/main/java/com/tutorialsejong/courseregistration/domain/registration/swagger/CancelCourseRegistrationOperation.java new file mode 100644 index 0000000..097eabe --- /dev/null +++ b/src/main/java/com/tutorialsejong/courseregistration/domain/registration/swagger/CancelCourseRegistrationOperation.java @@ -0,0 +1,28 @@ +package com.tutorialsejong.courseregistration.domain.registration.swagger; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.enums.ParameterIn; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.security.SecurityRequirement; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import org.springframework.http.MediaType; + +@Target({ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +@Operation(summary = "단건 수강 취소", description = "해당 스케줄에 대해 등록된 수강을 취소한다.", security = @SecurityRequirement(name = "bearerAuth")) +@Parameter(name = "scheduleId", description = "취소할 수강의 스케줄 ID", required = true, in = ParameterIn.PATH, example = "1") +@ApiResponses(value = { + @ApiResponse( + responseCode = "200", + description = "수강 취소 성공", + content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE) + ) +}) +public @interface CancelCourseRegistrationOperation { +} diff --git a/src/main/java/com/tutorialsejong/courseregistration/domain/registration/swagger/GetRegisteredCoursesOperation.java b/src/main/java/com/tutorialsejong/courseregistration/domain/registration/swagger/GetRegisteredCoursesOperation.java new file mode 100644 index 0000000..6d773ef --- /dev/null +++ b/src/main/java/com/tutorialsejong/courseregistration/domain/registration/swagger/GetRegisteredCoursesOperation.java @@ -0,0 +1,31 @@ +package com.tutorialsejong.courseregistration.domain.registration.swagger; + +import com.tutorialsejong.courseregistration.domain.registration.dto.CourseRegistrationScheduleResponse; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.ArraySchema; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.security.SecurityRequirement; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import org.springframework.http.MediaType; + +@Target({ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +@Operation(summary = "등록 내역 조회", description = "로그인한 사용자의 수강 등록 내역을 조회", security = @SecurityRequirement(name = "bearerAuth")) +@ApiResponses(value = { + @ApiResponse( + responseCode = "200", + description = "조회 성공", + content = @Content( + mediaType = MediaType.APPLICATION_JSON_VALUE, + array = @ArraySchema(schema = @Schema(implementation = CourseRegistrationScheduleResponse.class)) + ) + ), +}) +public @interface GetRegisteredCoursesOperation { +} diff --git a/src/main/java/com/tutorialsejong/courseregistration/domain/registration/swagger/RegisterCourseOperation.java b/src/main/java/com/tutorialsejong/courseregistration/domain/registration/swagger/RegisterCourseOperation.java new file mode 100644 index 0000000..4cff312 --- /dev/null +++ b/src/main/java/com/tutorialsejong/courseregistration/domain/registration/swagger/RegisterCourseOperation.java @@ -0,0 +1,30 @@ +package com.tutorialsejong.courseregistration.domain.registration.swagger; + +import com.tutorialsejong.courseregistration.domain.registration.dto.CourseRegistrationResponse; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.security.SecurityRequirement; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import org.springframework.http.MediaType; + +@Target({ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +@Operation(summary = "수강 등록", description = "해당 스케줄에 수강 신청을 진행한다.", security = @SecurityRequirement(name = "bearerAuth")) +@ApiResponses(value = { + @ApiResponse( + responseCode = "201", + description = "수강 등록 성공", + content = @Content( + mediaType = MediaType.APPLICATION_JSON_VALUE, + schema = @Schema(implementation = CourseRegistrationResponse.class) + ) + ) +}) +public @interface RegisterCourseOperation { +} diff --git a/src/main/java/com/tutorialsejong/courseregistration/domain/schedule/controller/ScheduleController.java b/src/main/java/com/tutorialsejong/courseregistration/domain/schedule/controller/ScheduleController.java index d650fe4..f7a5cb0 100644 --- a/src/main/java/com/tutorialsejong/courseregistration/domain/schedule/controller/ScheduleController.java +++ b/src/main/java/com/tutorialsejong/courseregistration/domain/schedule/controller/ScheduleController.java @@ -5,6 +5,8 @@ import com.tutorialsejong.courseregistration.domain.schedule.dto.ScheduleSearchRequest; import com.tutorialsejong.courseregistration.domain.schedule.entity.Schedule; import com.tutorialsejong.courseregistration.domain.schedule.service.ScheduleService; +import com.tutorialsejong.courseregistration.domain.schedule.swagger.GetPopularSchedulesOperation; +import com.tutorialsejong.courseregistration.domain.schedule.swagger.SearchSchedulesOperation; import java.util.Date; import java.util.List; import java.util.Set; @@ -12,6 +14,7 @@ import java.util.Spliterators; import java.util.stream.Collectors; import java.util.stream.StreamSupport; +import org.springdoc.core.annotations.ParameterObject; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; @@ -29,8 +32,8 @@ public class ScheduleController { private static final Set ALLOWED_PARAMS = Set.of( - "curiNo", "classNo", "schCollegeAlias", "schDeptAlias", "curiTypeCdNm", "sltDomainCdNm", "curiNm", - "lesnEmp", "studentId" + "curiNo", "classNo", "schCollegeAlias", "schDeptAlias", "curiTypeCdNm", + "sltDomainCdNm", "curiNm", "lesnEmp", "studentId" ); private final ScheduleService scheduleService; @@ -40,10 +43,13 @@ public ScheduleController(ScheduleService scheduleService) { this.scheduleService = scheduleService; } + @SearchSchedulesOperation @GetMapping(value = "/search", produces = MediaType.APPLICATION_JSON_VALUE) - public ResponseEntity getSearchSchedules(ScheduleSearchRequest searchRequest, - WebRequest request, - @AuthenticationPrincipal UserDetails userDetails) { + public ResponseEntity getSearchSchedules( + @ParameterObject ScheduleSearchRequest searchRequest, + WebRequest request, + @AuthenticationPrincipal UserDetails userDetails + ) { Set invalidParams = validateParameters(request); if (!invalidParams.isEmpty()) { String message = "유효하지않은 Parameter. (" + String.join(", ", invalidParams) + ")"; @@ -58,12 +64,15 @@ public ResponseEntity getSearchSchedules(ScheduleSearchRequest searchRequest, return createErrorResponse(HttpStatus.NOT_FOUND, "검색된 값 없음", request); } - return ResponseEntity.ok().body(searchResult); + // 엔티티 그대로 반환 (권장: DTO 변환) + return ResponseEntity.ok(searchResult); } private Set validateParameters(WebRequest request) { return StreamSupport.stream( - Spliterators.spliteratorUnknownSize(request.getParameterNames(), Spliterator.ORDERED), false) + Spliterators.spliteratorUnknownSize(request.getParameterNames(), Spliterator.ORDERED), + false + ) .filter(param -> !ALLOWED_PARAMS.contains(param)) .collect(Collectors.toSet()); } @@ -73,9 +82,11 @@ private ResponseEntity createErrorResponse(HttpStatus status, String m .body(new ErrorDto(new Date(), status.value(), message, request.getDescription(false))); } + @GetPopularSchedulesOperation @GetMapping("/popular") public ResponseEntity> getPopularSchedules( - @RequestParam(defaultValue = "10") int limit) { + @RequestParam(defaultValue = "10") int limit + ) { List popularSchedules = scheduleService.findPopularSchedules(limit); return ResponseEntity.ok(popularSchedules); } diff --git a/src/main/java/com/tutorialsejong/courseregistration/domain/schedule/dto/ScheduleSearchRequest.java b/src/main/java/com/tutorialsejong/courseregistration/domain/schedule/dto/ScheduleSearchRequest.java index 6cb44cf..f8c03cd 100644 --- a/src/main/java/com/tutorialsejong/courseregistration/domain/schedule/dto/ScheduleSearchRequest.java +++ b/src/main/java/com/tutorialsejong/courseregistration/domain/schedule/dto/ScheduleSearchRequest.java @@ -1,16 +1,55 @@ package com.tutorialsejong.courseregistration.domain.schedule.dto; import com.fasterxml.jackson.annotation.JsonInclude; +import io.swagger.v3.oas.annotations.media.Schema; @JsonInclude(JsonInclude.Include.NON_NULL) +@Schema(description = "스케줄 검색 요청 DTO") public record ScheduleSearchRequest( + @Schema(description = "교과목 번호", example = "009352") String curiNo, + + @Schema(description = "분반 번호", example = "001") String classNo, + + @Schema(description = "단과대학 별칭", example = "대양휴머니티칼리지") String schCollegeAlias, + + @Schema(description = "학과 별칭", example = "대양휴머니티칼리지") String schDeptAlias, + + @Schema(description = "교과목 유형명", example = "공통교양필수") String curiTypeCdNm, + + @Schema(description = "선택영역 코드명", example = "학문기초") String sltDomainCdNm, + + @Schema(description = "교과목 명", example = "사고와표현1") String curiNm, + + @Schema(description = "강사명", example = "노지현") String lesnEmp ) { + + public static ScheduleSearchRequest of( + String curiNo, + String classNo, + String schCollegeAlias, + String schDeptAlias, + String curiTypeCdNm, + String sltDomainCdNm, + String curiNm, + String lesnEmp + ) { + return new ScheduleSearchRequest( + curiNo, + classNo, + schCollegeAlias, + schDeptAlias, + curiTypeCdNm, + sltDomainCdNm, + curiNm, + lesnEmp + ); + } } diff --git a/src/main/java/com/tutorialsejong/courseregistration/domain/schedule/swagger/GetPopularSchedulesOperation.java b/src/main/java/com/tutorialsejong/courseregistration/domain/schedule/swagger/GetPopularSchedulesOperation.java new file mode 100644 index 0000000..d02f5c2 --- /dev/null +++ b/src/main/java/com/tutorialsejong/courseregistration/domain/schedule/swagger/GetPopularSchedulesOperation.java @@ -0,0 +1,38 @@ +package com.tutorialsejong.courseregistration.domain.schedule.swagger; + +import com.tutorialsejong.courseregistration.domain.registration.dto.CourseRegistrationScheduleResponse; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.enums.ParameterIn; +import io.swagger.v3.oas.annotations.media.ArraySchema; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import org.springframework.http.MediaType; + +@Target({ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +@Operation(summary = "인기 스케줄 조회", description = "위시카운트가 높은 스케줄을 조회합니다.") +@ApiResponses(value = { + @ApiResponse( + responseCode = "200", + description = "조회 성공", + content = @Content( + mediaType = MediaType.APPLICATION_JSON_VALUE, + array = @ArraySchema(schema = @Schema(implementation = CourseRegistrationScheduleResponse.class)) + ) + ) +}) +@Parameter( + name = "limit", + description = "가져올 인기 스케줄 개수 (기본값=10)", + in = ParameterIn.QUERY, + example = "10" +) +public @interface GetPopularSchedulesOperation { +} diff --git a/src/main/java/com/tutorialsejong/courseregistration/domain/schedule/swagger/SearchSchedulesOperation.java b/src/main/java/com/tutorialsejong/courseregistration/domain/schedule/swagger/SearchSchedulesOperation.java new file mode 100644 index 0000000..3f0cdf3 --- /dev/null +++ b/src/main/java/com/tutorialsejong/courseregistration/domain/schedule/swagger/SearchSchedulesOperation.java @@ -0,0 +1,50 @@ +package com.tutorialsejong.courseregistration.domain.schedule.swagger; + +import com.tutorialsejong.courseregistration.domain.registration.dto.CourseRegistrationScheduleResponse; +import com.tutorialsejong.courseregistration.domain.schedule.dto.ErrorDto; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.ArraySchema; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import org.springframework.http.MediaType; + +@Target({ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +@Operation( + summary = "스케줄 검색", + description = "검색 조건에 맞는 스케줄을 조회합니다." +) +@ApiResponses(value = { + @ApiResponse( + responseCode = "200", + description = "검색 결과 성공", + content = @Content( + mediaType = MediaType.APPLICATION_JSON_VALUE, + array = @ArraySchema(schema = @Schema(implementation = CourseRegistrationScheduleResponse.class)) + ) + ), + @ApiResponse( + responseCode = "400", + description = "유효하지 않은 파라미터", + content = @Content( + mediaType = MediaType.APPLICATION_JSON_VALUE, + schema = @Schema(implementation = ErrorDto.class) + ) + ), + @ApiResponse( + responseCode = "404", + description = "검색된 값 없음", + content = @Content( + mediaType = MediaType.APPLICATION_JSON_VALUE, + schema = @Schema(implementation = ErrorDto.class) + ) + ) +}) +public @interface SearchSchedulesOperation { +} diff --git a/src/main/java/com/tutorialsejong/courseregistration/domain/wishlist/controller/WishListController.java b/src/main/java/com/tutorialsejong/courseregistration/domain/wishlist/controller/WishListController.java index c401a7e..b68a860 100644 --- a/src/main/java/com/tutorialsejong/courseregistration/domain/wishlist/controller/WishListController.java +++ b/src/main/java/com/tutorialsejong/courseregistration/domain/wishlist/controller/WishListController.java @@ -1,44 +1,59 @@ package com.tutorialsejong.courseregistration.domain.wishlist.controller; +import com.tutorialsejong.courseregistration.domain.schedule.entity.Schedule; import com.tutorialsejong.courseregistration.domain.wishlist.dto.WishListRequest; import com.tutorialsejong.courseregistration.domain.wishlist.service.WishListService; -import com.tutorialsejong.courseregistration.domain.schedule.entity.Schedule; -import org.springframework.beans.factory.annotation.Autowired; +import com.tutorialsejong.courseregistration.domain.wishlist.swagger.DeleteWishListOperation; +import com.tutorialsejong.courseregistration.domain.wishlist.swagger.GetWishListOperation; +import com.tutorialsejong.courseregistration.domain.wishlist.swagger.SaveWishListOperation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import java.util.List; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.*; - -import java.util.List; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; @RestController @RequestMapping("/wishlist") +@Tag(name = "관심과목 API", description = "관심과목 관련 API") public class WishListController { private final WishListService wishListService; - @Autowired public WishListController(WishListService wishListService) { this.wishListService = wishListService; } - + @SaveWishListOperation @PostMapping("/save") public ResponseEntity saveWishListItem(@RequestBody WishListRequest wishListRequest) { wishListService.saveWishListItem(wishListRequest.studentId(), wishListRequest.scheduleId()); - return ResponseEntity.status(HttpStatus.CREATED).body("관심과목이 저장되었습니다."); } - @GetMapping() + @GetWishListOperation + @GetMapping public ResponseEntity getWishList(@RequestParam String studentId) { List wishList = wishListService.getWishList(studentId); - - return ResponseEntity.status(HttpStatus.OK).body(wishList); + return ResponseEntity.ok(wishList); } + @DeleteWishListOperation @DeleteMapping - public ResponseEntity deleteWishListItem(@RequestParam String studentId, @RequestParam Long scheduleId) { + public ResponseEntity deleteWishListItem( + @Parameter(description = "11자리 이상 학번", example = "12345678911") + @RequestParam String studentId, + + @Parameter(description = "강의 ID", example = "1") + @RequestParam Long scheduleId + ) { wishListService.deleteWishListItem(studentId, scheduleId); - return ResponseEntity.status(HttpStatus.OK).body("관심과목이 삭제되었습니다."); + return ResponseEntity.ok("관심과목이 삭제되었습니다."); } } diff --git a/src/main/java/com/tutorialsejong/courseregistration/domain/wishlist/dto/WishListRequest.java b/src/main/java/com/tutorialsejong/courseregistration/domain/wishlist/dto/WishListRequest.java index 6d35a41..7f7b346 100644 --- a/src/main/java/com/tutorialsejong/courseregistration/domain/wishlist/dto/WishListRequest.java +++ b/src/main/java/com/tutorialsejong/courseregistration/domain/wishlist/dto/WishListRequest.java @@ -1,8 +1,12 @@ package com.tutorialsejong.courseregistration.domain.wishlist.dto; +import io.swagger.v3.oas.annotations.media.Schema; public record WishListRequest( + @Schema(description = "11자리 이상 학번", example = "12345678911") String studentId, + + @Schema(description = "강의 ID", example = "1") Long scheduleId ) { } diff --git a/src/main/java/com/tutorialsejong/courseregistration/domain/wishlist/swagger/DeleteWishListOperation.java b/src/main/java/com/tutorialsejong/courseregistration/domain/wishlist/swagger/DeleteWishListOperation.java new file mode 100644 index 0000000..b9e2e4a --- /dev/null +++ b/src/main/java/com/tutorialsejong/courseregistration/domain/wishlist/swagger/DeleteWishListOperation.java @@ -0,0 +1,30 @@ +package com.tutorialsejong.courseregistration.domain.wishlist.swagger; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.ExampleObject; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import org.springframework.http.MediaType; + +@Target({ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +@Operation(summary = "관심과목 삭제", description = "특정 학생의 관심 과목을 삭제한다.") +@ApiResponses(value = { + @ApiResponse( + responseCode = "200", + description = "삭제 성공", + content = @Content( + mediaType = MediaType.APPLICATION_JSON_VALUE, + schema = @Schema(type = "string"), + examples = @ExampleObject(value = "관심과목이 삭제되었습니다.") + ) + ) +}) +public @interface DeleteWishListOperation { +} diff --git a/src/main/java/com/tutorialsejong/courseregistration/domain/wishlist/swagger/GetWishListOperation.java b/src/main/java/com/tutorialsejong/courseregistration/domain/wishlist/swagger/GetWishListOperation.java new file mode 100644 index 0000000..b519410 --- /dev/null +++ b/src/main/java/com/tutorialsejong/courseregistration/domain/wishlist/swagger/GetWishListOperation.java @@ -0,0 +1,30 @@ +package com.tutorialsejong.courseregistration.domain.wishlist.swagger; + +import com.tutorialsejong.courseregistration.domain.registration.dto.CourseRegistrationScheduleResponse; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.ArraySchema; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import org.springframework.http.MediaType; + +@Target({ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +@Operation(summary = "관심과목 조회", description = "특정 학생의 관심 과목 목록을 조회한다.") +@ApiResponses(value = { + @ApiResponse( + responseCode = "200", + description = "조회 성공", + content = @Content( + mediaType = MediaType.APPLICATION_JSON_VALUE, + array = @ArraySchema(schema = @Schema(implementation = CourseRegistrationScheduleResponse.class)) + ) + ) +}) +public @interface GetWishListOperation { +} diff --git a/src/main/java/com/tutorialsejong/courseregistration/domain/wishlist/swagger/SaveWishListOperation.java b/src/main/java/com/tutorialsejong/courseregistration/domain/wishlist/swagger/SaveWishListOperation.java new file mode 100644 index 0000000..440026e --- /dev/null +++ b/src/main/java/com/tutorialsejong/courseregistration/domain/wishlist/swagger/SaveWishListOperation.java @@ -0,0 +1,30 @@ +package com.tutorialsejong.courseregistration.domain.wishlist.swagger; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.ExampleObject; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import org.springframework.http.MediaType; + +@Target({ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +@Operation(summary = "관심과목 저장", description = "특정 학생의 관심 과목을 저장한다.") +@ApiResponses(value = { + @ApiResponse( + responseCode = "201", + description = "관심과목 저장 성공", + content = @Content( + mediaType = MediaType.APPLICATION_JSON_VALUE, + schema = @Schema(type = "string"), + examples = @ExampleObject(value = "관심과목이 저장되었습니다.") + ) + ) +}) +public @interface SaveWishListOperation { +} diff --git a/src/main/resources/config b/src/main/resources/config index 4478578..51623cf 160000 --- a/src/main/resources/config +++ b/src/main/resources/config @@ -1 +1 @@ -Subproject commit 44785789c4540ecfb403043177236fd746058277 +Subproject commit 51623cf381dbde12349f6e9716f142e210fa1f3b From eb375d9ce83a36ed2c65f6f7dd16b9a770b5fb6b Mon Sep 17 00:00:00 2001 From: zhy2on <52701529+zhy2on@users.noreply.github.com> Date: Fri, 24 Jan 2025 16:24:51 +0900 Subject: [PATCH 25/28] =?UTF-8?q?refactor:=20=EC=97=90=EB=9F=AC=EC=BD=94?= =?UTF-8?q?=EB=93=9C=20=EB=84=A4=EC=9D=B4=EB=B0=8D=20=EC=88=98=EC=A0=95=20?= =?UTF-8?q?(#92)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - SecurityErrorCode와 ScheduleErrorCode의 중복을 피하기 위해 SecurityErrorCode의 접두사를 Auth의 A로 바꿈 - GlobalErrorCode에 G006이 중복돼 있던 것 수정 --- .../common/exception/GlobalErrorCode.java | 2 +- .../common/security/exception/SecurityErrorCode.java | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/tutorialsejong/courseregistration/common/exception/GlobalErrorCode.java b/src/main/java/com/tutorialsejong/courseregistration/common/exception/GlobalErrorCode.java index 0205e1b..e6b1191 100644 --- a/src/main/java/com/tutorialsejong/courseregistration/common/exception/GlobalErrorCode.java +++ b/src/main/java/com/tutorialsejong/courseregistration/common/exception/GlobalErrorCode.java @@ -14,7 +14,7 @@ public enum GlobalErrorCode implements ErrorCode { METHOD_NOT_ALLOWED("G004", "허용되지 않는 메서드입니다.", HttpStatus.METHOD_NOT_ALLOWED), HANDLE_ACCESS_DENIED("G005", "접근이 거부되었습니다.", HttpStatus.FORBIDDEN), UNAUTHORIZED("G006", "인증되지 않은 사용자입니다.", HttpStatus.UNAUTHORIZED), - TOO_MANY_REQUESTS("G006", "과도한 요청을 보내셨습니다. 잠시 기다려 주세요.", HttpStatus.TOO_MANY_REQUESTS); + TOO_MANY_REQUESTS("G007", "과도한 요청을 보내셨습니다. 잠시 기다려 주세요.", HttpStatus.TOO_MANY_REQUESTS); private final String code; private final String message; diff --git a/src/main/java/com/tutorialsejong/courseregistration/common/security/exception/SecurityErrorCode.java b/src/main/java/com/tutorialsejong/courseregistration/common/security/exception/SecurityErrorCode.java index 34b977f..cb61ee5 100644 --- a/src/main/java/com/tutorialsejong/courseregistration/common/security/exception/SecurityErrorCode.java +++ b/src/main/java/com/tutorialsejong/courseregistration/common/security/exception/SecurityErrorCode.java @@ -9,9 +9,9 @@ @RequiredArgsConstructor public enum SecurityErrorCode implements ErrorCode { - AUTHENTICATION_FAILED("S001", "인증에 실패했습니다.", HttpStatus.UNAUTHORIZED), - JWT_TOKEN_EXPIRED("S002", "토큰이 만료되었습니다.", HttpStatus.UNAUTHORIZED), - JWT_TOKEN_INVALID("S003", "유효하지 않은 토큰입니다.", HttpStatus.UNAUTHORIZED), + AUTHENTICATION_FAILED("A001", "인증에 실패했습니다.", HttpStatus.UNAUTHORIZED), + JWT_TOKEN_EXPIRED("A002", "토큰이 만료되었습니다.", HttpStatus.UNAUTHORIZED), + JWT_TOKEN_INVALID("A003", "유효하지 않은 토큰입니다.", HttpStatus.UNAUTHORIZED), ; private final String code; From c190e9a32dfeb9dcb0c69fbc37477db04e1d518b Mon Sep 17 00:00:00 2001 From: zhy2on <52701529+zhy2on@users.noreply.github.com> Date: Fri, 24 Jan 2025 16:28:34 +0900 Subject: [PATCH 26/28] =?UTF-8?q?fix:=20=ED=86=A0=ED=81=B0=20=EB=A7=8C?= =?UTF-8?q?=EB=A3=8C=20=EC=97=90=EB=9F=AC=20=EC=B2=98=EB=A6=AC=20=EC=8B=9C?= =?UTF-8?q?=EC=9D=98=20=EB=A1=9C=EA=B9=85=20=EB=A1=9C=EC=A7=81=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20(#90)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 토큰 만료 시 로깅을 위한 username 추출 방식 변경 - ExpiredJwtException에서 직접 username을 추출하도록 수정하여 무한 재귀 문제 해결 --- .../common/security/JwtTokenProvider.java | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/main/java/com/tutorialsejong/courseregistration/common/security/JwtTokenProvider.java b/src/main/java/com/tutorialsejong/courseregistration/common/security/JwtTokenProvider.java index 67a761a..22b4b51 100644 --- a/src/main/java/com/tutorialsejong/courseregistration/common/security/JwtTokenProvider.java +++ b/src/main/java/com/tutorialsejong/courseregistration/common/security/JwtTokenProvider.java @@ -15,7 +15,6 @@ import io.jsonwebtoken.security.Keys; import java.security.Key; import java.util.Date; - import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Value; @@ -85,19 +84,19 @@ public void validateToken(String token) { try { Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token); } catch (ExpiredJwtException ex) { - String username = getUsernameFromJWT(token); + String username = ex.getClaims().getSubject(); logger.warn(LogMessage.builder() - .action(LogAction.VALIDATE_TOKEN) - .subject("s"+username) - .result(LogResult.FAIL) - .reason(LogReason.EXPIRED) + .action(LogAction.VALIDATE_TOKEN) + .subject("s" + username) + .result(LogResult.FAIL) + .reason(LogReason.EXPIRED) .build().toString()); throw new JwtTokenExpiredException(); } catch (MalformedJwtException | UnsupportedJwtException | IllegalArgumentException ex) { String username = getUsernameFromJWT(token); logger.warn(LogMessage.builder() .action(LogAction.VALIDATE_TOKEN) - .subject("s"+username) + .subject("s" + username) .result(LogResult.FAIL) .reason(LogReason.INVALID_CREDENTIAL) .build().toString()); From 2319614938cc0dd4a26975a8af8f0a28497f3945 Mon Sep 17 00:00:00 2001 From: zhy2on <52701529+zhy2on@users.noreply.github.com> Date: Sat, 25 Jan 2025 01:42:37 +0900 Subject: [PATCH 27/28] =?UTF-8?q?chore:=20=ED=85=8C=EC=8A=A4=ED=8A=B8?= =?UTF-8?q?=EC=9A=A9=20=ED=86=A0=ED=81=B0=20=EB=A7=8C=EB=A3=8C=20=EC=8B=9C?= =?UTF-8?q?=EA=B0=84=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/resources/config | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/resources/config b/src/main/resources/config index 51623cf..a608bf4 160000 --- a/src/main/resources/config +++ b/src/main/resources/config @@ -1 +1 @@ -Subproject commit 51623cf381dbde12349f6e9716f142e210fa1f3b +Subproject commit a608bf4163738d1c21f69822c6de4791c54203f6 From 4c32110419666fcc2900b8f9c7d5c98465181790 Mon Sep 17 00:00:00 2001 From: zhy2on <52701529+zhy2on@users.noreply.github.com> Date: Sat, 25 Jan 2025 12:58:21 +0900 Subject: [PATCH 28/28] =?UTF-8?q?chore:=20=ED=86=A0=ED=81=B0=20=EB=A7=8C?= =?UTF-8?q?=EB=A3=8C=20=EC=8B=9C=EA=B0=84=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/resources/config | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/resources/config b/src/main/resources/config index a608bf4..7891b40 160000 --- a/src/main/resources/config +++ b/src/main/resources/config @@ -1 +1 @@ -Subproject commit a608bf4163738d1c21f69822c6de4791c54203f6 +Subproject commit 7891b409b1463e12e1718f63eb73cede36ec35d5