diff --git a/backend/src/main/java/org/example/config/WebOAuthSecurityConfig.java b/backend/src/main/java/org/example/config/WebOAuthSecurityConfig.java index 90d0fd8c..3a5b3056 100644 --- a/backend/src/main/java/org/example/config/WebOAuthSecurityConfig.java +++ b/backend/src/main/java/org/example/config/WebOAuthSecurityConfig.java @@ -116,21 +116,28 @@ public OAuth2AuthorizationRequestBasedOnCookieRepository oAuth2AuthorizationRequ @Bean public CorsConfigurationSource corsConfigurationSource() { CorsConfiguration corsConfiguration = new CorsConfiguration(); - corsConfiguration.addAllowedOriginPattern("*"); + + corsConfiguration.addAllowedOriginPattern("https://www.lookus.shop"); + corsConfiguration.addAllowedOriginPattern("https://api.lookus.shop"); + corsConfiguration.addAllowedOriginPattern("http://localhost:8080"); // JUST FOR LOCAL DEV + corsConfiguration.addAllowedOriginPattern("http://localhost:3000"); // JUST FOR LOCAL DEV + + corsConfiguration.addAllowedMethod("PATCH"); + corsConfiguration.addAllowedMethod("GET"); + corsConfiguration.addAllowedMethod("POST"); + corsConfiguration.addAllowedMethod("PUT"); + corsConfiguration.addAllowedMethod("DELETE"); + + corsConfiguration.addAllowedHeader("*"); corsConfiguration.addExposedHeader("Authorization"); corsConfiguration.addExposedHeader("refresh_token"); corsConfiguration.addExposedHeader("Set-Cookie"); - corsConfiguration.addAllowedHeader("*"); - corsConfiguration.addAllowedMethod("*"); - corsConfiguration.addAllowedOrigin(System.getenv("WEBSITE_DOMAIN")); - corsConfiguration.addAllowedOrigin(System.getenv("API_DOMAIN")); - corsConfiguration.addAllowedOrigin("http://localhost:8080"); // JUST FOR LOCAL DEV - corsConfiguration.addAllowedOrigin("http://localhost:8081"); // JUST FOR LOCAL DEV - corsConfiguration.addAllowedOrigin("http://localhost:3000"); // JUST FOR LOCAL DEV + corsConfiguration.setAllowCredentials(true); UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); source.registerCorsConfiguration("/**", corsConfiguration); + return source; } } diff --git a/backend/src/main/java/org/example/post/service/PostStatsService.java b/backend/src/main/java/org/example/post/service/PostStatsService.java index 7764ccaa..0bb038b0 100644 --- a/backend/src/main/java/org/example/post/service/PostStatsService.java +++ b/backend/src/main/java/org/example/post/service/PostStatsService.java @@ -1,12 +1,16 @@ package org.example.post.service; +import java.util.HashMap; import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; import org.example.config.log.LogExecution; import org.example.post.domain.entity.PostDailyStats; import org.example.post.domain.entity.PostEntity; import org.example.post.domain.entity.PostTotalStats; import org.example.post.repository.PostDailyStatsRepository; +import org.example.post.repository.PostRepository; import org.example.post.repository.PostTotalStatsRepository; import org.springframework.stereotype.Service; @@ -16,9 +20,9 @@ @RequiredArgsConstructor public class PostStatsService { - private final PostDailyStatsRepository postDailyStatsRepository; private final PostTotalStatsRepository postTotalStatsRepository; + private final PostRepository postRepository; @LogExecution public List getDailyStatsByPost(PostEntity post) { @@ -29,5 +33,52 @@ public List getDailyStatsByPost(PostEntity post) { public List getTotalStatsByPost(PostEntity post) { return postTotalStatsRepository.findByPost(post); } - // 날짜별로 정렬하여 가져오기 + + public Map getPostStats() { + List posts = postRepository.findAll(); + + Map> labelsByPostId = new HashMap<>(); + Map> hitsDataByPostId = new HashMap<>(); + Map> likeDataByPostId = new HashMap<>(); + Map> todayHitsByPostId = new HashMap<>(); + Map> todayLikesByPostId = new HashMap<>(); + + for (PostEntity post : posts) { + List dailyStats = getDailyStatsByPost(post); + List totalStats = getTotalStatsByPost(post); + + List labels = dailyStats.stream() + .map(stat -> stat.getRecordedAt().toString()) + .collect(Collectors.toList()); + + List todayHitsData = dailyStats.stream() + .map(PostDailyStats::getTodayHits) + .collect(Collectors.toList()); + + List todayLikesData = dailyStats.stream() + .map(PostDailyStats::getTodayLikes) + .collect(Collectors.toList()); + + int totalHits = !totalStats.isEmpty() ? totalStats.get(0).getHits() : 0; + int totalLikes = !totalStats.isEmpty() ? totalStats.get(0).getLikeCount() : 0; + + labelsByPostId.put(post.getPostId(), labels); + todayHitsByPostId.put(post.getPostId(), todayHitsData); + todayLikesByPostId.put(post.getPostId(), todayLikesData); + hitsDataByPostId.put(post.getPostId(), List.of(totalHits)); // 전체 조회수 + likeDataByPostId.put(post.getPostId(), List.of(totalLikes)); // 전체 좋아요 수 + } + + // 필요한 데이터를 맵으로 반환 + Map resultMap = new HashMap<>(); + resultMap.put("posts", posts); + resultMap.put("labelsByPostId", labelsByPostId); + resultMap.put("hitsDataByPostId", hitsDataByPostId); + resultMap.put("likeDataByPostId", likeDataByPostId); + resultMap.put("todayHitsByPostId", todayHitsByPostId); + resultMap.put("todayLikesByPostId", todayLikesByPostId); + + return resultMap; + } + } diff --git a/backend/src/main/java/org/example/user/controller/member/AdminStatsController.java b/backend/src/main/java/org/example/user/controller/member/AdminStatsController.java new file mode 100644 index 00000000..e2548951 --- /dev/null +++ b/backend/src/main/java/org/example/user/controller/member/AdminStatsController.java @@ -0,0 +1,35 @@ +package org.example.user.controller.member; + +import java.util.Map; + +import org.example.config.log.LogExecution; +import org.example.post.service.PostStatsService; +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.web.bind.annotation.GetMapping; + +import lombok.RequiredArgsConstructor; + +@Controller +@RequiredArgsConstructor +public class AdminStatsController { + + private final PostStatsService postStatsService; + + @LogExecution + @GetMapping("/admin/stats") + public String getPostStats(Model model) { + // 서비스에서 데이터 조회 + Map statsData = postStatsService.getPostStats(); + + // 모델에 데이터 추가 + model.addAttribute("posts", statsData.get("posts")); + model.addAttribute("labelsByPostId", statsData.get("labelsByPostId")); + model.addAttribute("hitsDataByPostId", statsData.get("hitsDataByPostId")); + model.addAttribute("likeDataByPostId", statsData.get("likeDataByPostId")); + model.addAttribute("todayHitsByPostId", statsData.get("todayHitsByPostId")); + model.addAttribute("todayLikesByPostId", statsData.get("todayLikesByPostId")); + + return "adminStats"; // Thymeleaf 템플릿 이름 + } +} diff --git a/backend/src/main/java/org/example/user/controller/member/UserViewController.java b/backend/src/main/java/org/example/user/controller/member/UserViewController.java index 74fa0d1c..efa47eac 100644 --- a/backend/src/main/java/org/example/user/controller/member/UserViewController.java +++ b/backend/src/main/java/org/example/user/controller/member/UserViewController.java @@ -1,18 +1,9 @@ package org.example.user.controller.member; import java.time.Duration; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.stream.Collectors; import org.example.config.jwt.TokenProvider; import org.example.config.log.LogExecution; -import org.example.post.domain.entity.PostDailyStats; -import org.example.post.domain.entity.PostEntity; -import org.example.post.domain.entity.PostTotalStats; -import org.example.post.repository.PostRepository; -import org.example.post.service.PostStatsService; import org.example.user.domain.dto.UserDto; import org.example.user.domain.entity.member.UserEntity; import org.example.user.service.member.UserService; @@ -29,13 +20,14 @@ import jakarta.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; +/** + * This page is for Admin Page. + */ @Controller @RequiredArgsConstructor public class UserViewController { private final UserService userService; - private final PostStatsService postStatsService; - private final PostRepository postRepository; private final TokenProvider tokenProvider; @GetMapping("/") @@ -71,66 +63,12 @@ public String login(@ModelAttribute UserDto.UserLoginRequest loginRequest, Model jwtCookie.setMaxAge(24 * 60 * 60); // 24시간 response.addCookie(jwtCookie); - // if ("ROLE_ADMIN".equals(userResponse.role())) { - // return "redirect:/admin/stats"; - // } else { - // return "redirect:/home"; - // } return "redirect:/admin/stats"; } else { return "redirect:/login?error=true"; } } - @LogExecution - @GetMapping("/admin/stats") - public String getPostStats(Model model) { - // 데이터 조회 - List posts = postRepository.findAll(); - - Map> labelsByPostId = new HashMap<>(); - Map> hitsDataByPostId = new HashMap<>(); - Map> likeDataByPostId = new HashMap<>(); - Map> todayHitsByPostId = new HashMap<>(); - Map> todayLikesByPostId = new HashMap<>(); - - for (PostEntity post : posts) { - List dailyStats = postStatsService.getDailyStatsByPost(post); - List totalStats = postStatsService.getTotalStatsByPost(post); - - List labels = dailyStats.stream() - .map(stat -> stat.getRecordedAt().toString()) - .collect(Collectors.toList()); - - List todayHitsData = dailyStats.stream() - .map(PostDailyStats::getTodayHits) - .collect(Collectors.toList()); - - List todayLikesData = dailyStats.stream() - .map(PostDailyStats::getTodayLikes) - .collect(Collectors.toList()); - - int totalHits = !totalStats.isEmpty() ? totalStats.get(0).getHits() : 0; - int totalLikes = !totalStats.isEmpty() ? totalStats.get(0).getLikeCount() : 0; - - labelsByPostId.put(post.getPostId(), labels); - todayHitsByPostId.put(post.getPostId(), todayHitsData); - todayLikesByPostId.put(post.getPostId(), todayLikesData); - hitsDataByPostId.put(post.getPostId(), List.of(totalHits)); // 전체 조회수 - likeDataByPostId.put(post.getPostId(), List.of(totalLikes)); // 전체 좋아요 수 - } - - // 모델에 데이터 추가 - model.addAttribute("posts", posts); - model.addAttribute("labelsByPostId", labelsByPostId); - model.addAttribute("hitsDataByPostId", hitsDataByPostId); - model.addAttribute("likeDataByPostId", likeDataByPostId); - model.addAttribute("todayHitsByPostId", todayHitsByPostId); - model.addAttribute("todayLikesByPostId", todayLikesByPostId); - - return "adminStats"; // Thymeleaf 템플릿 이름 - } - @LogExecution @GetMapping("/signup") public String signup() { diff --git a/backend/src/main/java/org/example/user/service/member/UserService.java b/backend/src/main/java/org/example/user/service/member/UserService.java index f5b64a42..b6b7cf00 100644 --- a/backend/src/main/java/org/example/user/service/member/UserService.java +++ b/backend/src/main/java/org/example/user/service/member/UserService.java @@ -86,25 +86,25 @@ public void signupUser(UserDto.UserCreateRequest addUserRequest) { @LogExecution public UserDto.UserResponse loginUser(UserDto.UserLoginRequest loginRequest) { - UserEntity user = getUserByEmail(loginRequest.email()); - - if (user != null && bCryptPasswordEncoder.matches(loginRequest.password(), user.getPassword())) { - // 로그인 성공 시 SecurityContextHolder에 권한 부여 - Collection authorities = List.of(new SimpleGrantedAuthority(user.getRole().name())); - Authentication authentication = new UsernamePasswordAuthenticationToken(user.getEmail(), null, authorities); - SecurityContext context = SecurityContextHolder.getContext(); - - context.setAuthentication(authentication); - - System.out.println("context = " + context); - + UserEntity user = getUserByEmail(loginRequest.email()); + if (user == null || !bCryptPasswordEncoder.matches(loginRequest.password(), user.getPassword())) { + // 로그인 실패 시 예외 발생 + throw ApiUserException.builder() + .category(ApiErrorCategory.RESOURCE_INACCESSIBLE) + .subCategory(ApiUserErrorSubCategory.USER_NOT_FOUND) + .build(); + } - return new UserDto.UserResponse(user.getEmail(), user.getRole().name()); - } + // 로그인 성공 시 SecurityContextHolder에 권한 부여 + Collection authorities = List.of(new SimpleGrantedAuthority(user.getRole().name())); + Authentication authentication = new UsernamePasswordAuthenticationToken(user.getEmail(), null, authorities); + SecurityContext context = SecurityContextHolder.getContext(); + context.setAuthentication(authentication); - return null; // 로그인 실패 시 null 반환 - } + // 성공 시 유저 정보 반환 + return new UserDto.UserResponse(user.getEmail(), user.getRole().name()); + } @LogExecution @Transactional(readOnly = true) diff --git a/backend/src/main/resources/application.yml b/backend/src/main/resources/application.yml index d8223285..75637513 100644 --- a/backend/src/main/resources/application.yml +++ b/backend/src/main/resources/application.yml @@ -11,10 +11,10 @@ spring: properties: hibernate: dialect: org.hibernate.dialect.MySQL8Dialect # MySQL 버전에 맞게 설정하세요 - format_sql: true + format_sql: false hibernate: ddl-auto: update - show-sql: true + show-sql: false datasource: url: ${MY_DATA_SOURCE}