diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 28b48599..a78cf57e 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -83,6 +83,9 @@ jobs: sudo docker rm -f $(docker ps -qa) sudo docker pull ${{ secrets.DOCKER_REPO }}/eatssu-prod sudo docker run -d -p 9000:9000 \ + --log-driver=json-file \ + --log-opt max-size=20m \ + --log-opt max-file=5 \ -e EATSSU_DB_URL_PROD="${{ secrets.EATSSU_DB_URL_PROD }}" \ -e EATSSU_DB_USERNAME="${{ secrets.EATSSU_DB_USERNAME }}" \ -e EATSSU_DB_PASSWORD="${{ secrets.EATSSU_DB_PASSWORD }}" \ @@ -107,6 +110,9 @@ jobs: sudo docker rm -f $(docker ps -qa) sudo docker pull ${{ secrets.DOCKER_REPO }}/eatssu-dev sudo docker run -d -p 9000:9000 \ + --log-driver=json-file \ + --log-opt max-size=20m \ + --log-opt max-file=5 \ -e EATSSU_DB_URL_DEV="${{ secrets.EATSSU_DB_URL_DEV }}" \ -e EATSSU_DB_USERNAME="${{ secrets.EATSSU_DB_USERNAME }}" \ -e EATSSU_DB_PASSWORD="${{ secrets.EATSSU_DB_PASSWORD }}" \ diff --git a/.gitignore b/.gitignore index 633f67dc..21461390 100644 --- a/.gitignore +++ b/.gitignore @@ -45,4 +45,8 @@ out/ ### env file ### .env -/src/main/resources/application-local.yml \ No newline at end of file +/src/main/resources/application-local.yml + +### DS File ### +.DS_Store +*/.DS_Store diff --git a/Dockerfile b/Dockerfile index c68d1129..5bd8e119 100644 --- a/Dockerfile +++ b/Dockerfile @@ -22,4 +22,4 @@ COPY --from=builder /home/gradle/project/build/libs/*.jar app.jar EXPOSE 9000 # 애플리케이션 실행 -ENTRYPOINT ["java", "-jar", "app.jar"] \ No newline at end of file +ENTRYPOINT ["java", "-jar", "app.jar"] diff --git a/build.gradle b/build.gradle index 6cad98bb..9799a10a 100644 --- a/build.gradle +++ b/build.gradle @@ -80,6 +80,7 @@ def generated = 'src/main/generated' tasks.withType(JavaCompile) { options.getGeneratedSourceOutputDirectory().set(file(generated)) + options.compilerArgs += ['-parameters'] } sourceSets { @@ -88,4 +89,4 @@ sourceSets { clean { delete file('src/main/generated') -} \ No newline at end of file +} diff --git a/src/main/java/ssu/eatssu/domain/auth/dto/AppleLoginRequest.java b/src/main/java/ssu/eatssu/domain/auth/dto/AppleLoginRequest.java index 2d5416ad..8752f2d8 100644 --- a/src/main/java/ssu/eatssu/domain/auth/dto/AppleLoginRequest.java +++ b/src/main/java/ssu/eatssu/domain/auth/dto/AppleLoginRequest.java @@ -1,9 +1,11 @@ package ssu.eatssu.domain.auth.dto; import io.swagger.v3.oas.annotations.media.Schema; +import ssu.eatssu.global.log.annotation.LogMask; @Schema(title = "애플 로그인 및 회원가입") public record AppleLoginRequest( + @LogMask @Schema(description = "identityToken", example = "eyJraWQiOiJXNldjT0tCIiwiYWxnIjoi...") String identityToken ) { diff --git a/src/main/java/ssu/eatssu/domain/auth/dto/KakaoLoginRequest.java b/src/main/java/ssu/eatssu/domain/auth/dto/KakaoLoginRequest.java index eacf8e65..140f0191 100644 --- a/src/main/java/ssu/eatssu/domain/auth/dto/KakaoLoginRequest.java +++ b/src/main/java/ssu/eatssu/domain/auth/dto/KakaoLoginRequest.java @@ -3,14 +3,17 @@ import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.Email; import jakarta.validation.constraints.NotBlank; +import ssu.eatssu.global.log.annotation.LogMask; @Schema(title = "카카오 로그인 및 회원가입") public record KakaoLoginRequest( + @LogMask @NotBlank(message = "이메일을 입력해주세요.") @Email(message = "올바른 이메일 주소를 입력해주세요.") @Schema(description = "이메일", example = "test@email.com") String email, + @LogMask @Schema(description = "providerId", example = "10378247832195") String providerId ) { diff --git a/src/main/java/ssu/eatssu/domain/auth/dto/ValidRequest.java b/src/main/java/ssu/eatssu/domain/auth/dto/ValidRequest.java index 748028e6..0a70ed4d 100644 --- a/src/main/java/ssu/eatssu/domain/auth/dto/ValidRequest.java +++ b/src/main/java/ssu/eatssu/domain/auth/dto/ValidRequest.java @@ -2,9 +2,11 @@ import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotBlank; +import ssu.eatssu.global.log.annotation.LogMask; @Schema(title = "유효한 토큰 확인") public record ValidRequest( + @LogMask @NotBlank(message = "토큰을 입력해주세요") @Schema(description = "토큰", example = "eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJ7XCJpZFwiOjcsXCJlbWFpbFwiOlwidGVzdEBlbWFpbC5jb21cIixcInJvbGVcIjpcIlJPTEVfVVNFUlwifSIsImF1dGgiOiJST0xFX1VTRVIiLCJleHAiOjE3NDQzNzQ0MjB9.mhIWYX_Vj3xW1eXuVflbzpH6vLTcC9b1twbIcqovVjDVnS7tjegu3nQHGXUsUa_WG2DIAtJMFZT_Q1XcVq1jPw") String token diff --git a/src/main/java/ssu/eatssu/domain/auth/infrastructure/SecurityConfig.java b/src/main/java/ssu/eatssu/domain/auth/infrastructure/SecurityConfig.java index b8d7fee4..959cfc3a 100644 --- a/src/main/java/ssu/eatssu/domain/auth/infrastructure/SecurityConfig.java +++ b/src/main/java/ssu/eatssu/domain/auth/infrastructure/SecurityConfig.java @@ -14,7 +14,6 @@ import org.springframework.web.cors.UrlBasedCorsConfigurationSource; import ssu.eatssu.domain.auth.security.JwtAuthenticationFilter; import ssu.eatssu.domain.auth.security.JwtTokenProvider; -import ssu.eatssu.domain.slack.service.SlackErrorNotifier; import ssu.eatssu.global.handler.JwtAccessDeniedHandler; import ssu.eatssu.global.handler.JwtAuthenticationEntryPoint; diff --git a/src/main/java/ssu/eatssu/domain/auth/security/JwtAuthenticationFilter.java b/src/main/java/ssu/eatssu/domain/auth/security/JwtAuthenticationFilter.java index 2566ecec..327108b4 100644 --- a/src/main/java/ssu/eatssu/domain/auth/security/JwtAuthenticationFilter.java +++ b/src/main/java/ssu/eatssu/domain/auth/security/JwtAuthenticationFilter.java @@ -15,8 +15,6 @@ import org.springframework.util.StringUtils; import org.springframework.web.filter.GenericFilterBean; - - import java.io.IOException; import java.util.List; diff --git a/src/main/java/ssu/eatssu/domain/inquiry/dto/CreateInquiryRequest.java b/src/main/java/ssu/eatssu/domain/inquiry/dto/CreateInquiryRequest.java index 318664e3..703dcbd7 100644 --- a/src/main/java/ssu/eatssu/domain/inquiry/dto/CreateInquiryRequest.java +++ b/src/main/java/ssu/eatssu/domain/inquiry/dto/CreateInquiryRequest.java @@ -3,6 +3,7 @@ import io.swagger.v3.oas.annotations.media.Schema; import lombok.Getter; import lombok.NoArgsConstructor; +import ssu.eatssu.global.log.annotation.LogMask; @Schema(title = "문의 남기기") @NoArgsConstructor @@ -10,8 +11,10 @@ public class CreateInquiryRequest { @Schema(description = "답장 받을 이메일", example = "sandy1017@gmail.com") + @LogMask private String email; @Schema(description = "문의 내용", example = "어쩌고 저쩌고 문의 남깁니다") + @LogMask private String content; } diff --git a/src/main/java/ssu/eatssu/domain/inquiry/service/InquiryService.java b/src/main/java/ssu/eatssu/domain/inquiry/service/InquiryService.java index 66c7c7c0..7a38ab08 100644 --- a/src/main/java/ssu/eatssu/domain/inquiry/service/InquiryService.java +++ b/src/main/java/ssu/eatssu/domain/inquiry/service/InquiryService.java @@ -1,6 +1,7 @@ package ssu.eatssu.domain.inquiry.service; import lombok.RequiredArgsConstructor; +import org.springframework.context.ApplicationEventPublisher; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import ssu.eatssu.domain.auth.security.CustomUserDetails; @@ -10,6 +11,7 @@ import ssu.eatssu.domain.user.entity.User; import ssu.eatssu.domain.user.repository.UserRepository; import ssu.eatssu.global.handler.response.BaseException; +import ssu.eatssu.global.log.event.LogEvent; import static ssu.eatssu.global.handler.response.BaseResponseStatus.NOT_FOUND_USER; @@ -20,14 +22,23 @@ public class InquiryService { private final UserRepository userRepository; private final InquiryRepository inquiryRepository; + private final ApplicationEventPublisher eventPublisher; public Inquiry createUserInquiry(CustomUserDetails userDetails, CreateInquiryRequest request) { User user = userRepository.findById(userDetails.getId()) - .orElseThrow(() -> new BaseException(NOT_FOUND_USER)); + .orElseThrow(() -> new BaseException(NOT_FOUND_USER)); Inquiry inquiry = new Inquiry(request.getContent(), user, request.getEmail()); + Inquiry saved = inquiryRepository.save(inquiry); - return inquiryRepository.save(inquiry); + eventPublisher.publishEvent(LogEvent.of(String.format( + "Inquiry created: id=%d, userId=%d, status=%s", + saved.getId(), + user.getId(), + saved.getStatus() + ))); + + return saved; } } diff --git a/src/main/java/ssu/eatssu/domain/menu/entity/Meal.java b/src/main/java/ssu/eatssu/domain/menu/entity/Meal.java index 7680ff3a..fa7069bd 100644 --- a/src/main/java/ssu/eatssu/domain/menu/entity/Meal.java +++ b/src/main/java/ssu/eatssu/domain/menu/entity/Meal.java @@ -64,4 +64,10 @@ public List getMenuNames() { public void addMealMenu(MealMenu mealMenu) { mealMenus.add(mealMenu); } + + public List getMenus() { + return mealMenus.stream() + .map(MealMenu::getMenu) + .toList(); + } } diff --git a/src/main/java/ssu/eatssu/domain/menu/entity/Menu.java b/src/main/java/ssu/eatssu/domain/menu/entity/Menu.java index b3346b03..a64d9c56 100644 --- a/src/main/java/ssu/eatssu/domain/menu/entity/Menu.java +++ b/src/main/java/ssu/eatssu/domain/menu/entity/Menu.java @@ -33,6 +33,8 @@ public class Menu { private final Reviews reviews = new Reviews(); @OneToMany(mappedBy = "menu", cascade = CascadeType.ALL) private final List mealMenus = new ArrayList<>(); + @Column(name = "unlike_count") + private Integer unlikeCount = 0; @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "menu_id") @@ -44,15 +46,10 @@ public class Menu { @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "menu_category_id") private MenuCategory category; - private boolean isDiscontinued = false; - @Column(name = "like_count") private Integer likeCount = 0; - @Column(name = "unlike_count") - private final Integer unlikeCount = 0; - private Menu(String name, Restaurant restaurant, Integer price, MenuCategory category) { this.name = name; this.restaurant = restaurant; diff --git a/src/main/java/ssu/eatssu/domain/menu/persistence/QuerydslMenuRatingCalculator.java b/src/main/java/ssu/eatssu/domain/menu/persistence/QuerydslMenuRatingCalculator.java index 0d8b2843..160b9f2b 100644 --- a/src/main/java/ssu/eatssu/domain/menu/persistence/QuerydslMenuRatingCalculator.java +++ b/src/main/java/ssu/eatssu/domain/menu/persistence/QuerydslMenuRatingCalculator.java @@ -27,7 +27,6 @@ public Double getMainRatingAverage(Long menuId) { } - private BooleanExpression menuIdEq(Long menuId) { return menu.id.eq(menuId); } diff --git a/src/main/java/ssu/eatssu/domain/menu/presentation/dto/response/MenusInMealResponse.java b/src/main/java/ssu/eatssu/domain/menu/presentation/dto/response/MenusInMealResponse.java index cc58ea3d..a6222c75 100644 --- a/src/main/java/ssu/eatssu/domain/menu/presentation/dto/response/MenusInMealResponse.java +++ b/src/main/java/ssu/eatssu/domain/menu/presentation/dto/response/MenusInMealResponse.java @@ -6,6 +6,7 @@ import lombok.NoArgsConstructor; import ssu.eatssu.domain.menu.entity.Meal; import ssu.eatssu.domain.menu.entity.MealMenu; +import ssu.eatssu.domain.menu.entity.Menu; import java.util.List; @@ -18,9 +19,8 @@ public class MenusInMealResponse { @Schema(description = "식단 속 메뉴 목록", example = "[]") private List briefMenus; - public static MenusInMealResponse from(Meal meal) { - List menusInformation = meal.getMealMenus().stream() - .map(MealMenu::getMenu) + public static MenusInMealResponse from(List menus) { + List menusInformation = menus.stream() .map(BriefMenuResponse::new) .toList(); diff --git a/src/main/java/ssu/eatssu/domain/menu/service/MealService.java b/src/main/java/ssu/eatssu/domain/menu/service/MealService.java index 4fc0859d..75cd6445 100644 --- a/src/main/java/ssu/eatssu/domain/menu/service/MealService.java +++ b/src/main/java/ssu/eatssu/domain/menu/service/MealService.java @@ -39,8 +39,15 @@ public class MealService { public MenusInMealResponse getMenusInMealByMealId(Long mealId) { Meal meal = mealRepository.findById(mealId) .orElseThrow(() -> new BaseException(BaseResponseStatus.NOT_FOUND_MEAL)); + List menus = meal.getMealMenus().stream() + .map(MealMenu::getMenu) + .toList(); + + if (menus.isEmpty()) { + log.warn("Meal[{}] has no menus.", mealId); + } - return MenusInMealResponse.from(meal); + return MenusInMealResponse.from(menus); } public List getMealDetailsByDateAndRestaurantAndTimePart( diff --git a/src/main/java/ssu/eatssu/domain/partnership/dto/PartnershipInfo.java b/src/main/java/ssu/eatssu/domain/partnership/dto/PartnershipInfo.java index bbf39d36..b50fe3e1 100644 --- a/src/main/java/ssu/eatssu/domain/partnership/dto/PartnershipInfo.java +++ b/src/main/java/ssu/eatssu/domain/partnership/dto/PartnershipInfo.java @@ -28,9 +28,11 @@ public static PartnershipInfo fromEntity(Partnership partnership, .startDate(partnership.getStartDate()) .endDate(partnership.getEndDate()) .collegeName(partnership.getPartnershipCollege() == null && partnership.getPartnershipDepartment() == null - ? "총학생회" - : (partnership.getPartnershipCollege() != null ? partnership.getPartnershipCollege().getName() : null)) - .departmentName(partnership.getPartnershipDepartment() != null ? partnership.getPartnershipDepartment().getName() : null) + ? "총학생회" + : (partnership.getPartnershipCollege() != null ? partnership.getPartnershipCollege() + .getName() : null)) + .departmentName(partnership.getPartnershipDepartment() != null ? partnership.getPartnershipDepartment() + .getName() : null) .likeCount(restaurant.getLikes() != null ? restaurant.getLikes().size() : 0) .isLiked(isLiked) .build(); diff --git a/src/main/java/ssu/eatssu/domain/partnership/entity/Partnership.java b/src/main/java/ssu/eatssu/domain/partnership/entity/Partnership.java index 91763d0f..3f32b6a1 100644 --- a/src/main/java/ssu/eatssu/domain/partnership/entity/Partnership.java +++ b/src/main/java/ssu/eatssu/domain/partnership/entity/Partnership.java @@ -2,8 +2,6 @@ import jakarta.persistence.Column; import jakarta.persistence.Entity; -import jakarta.persistence.EnumType; -import jakarta.persistence.Enumerated; import jakarta.persistence.FetchType; import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; @@ -32,7 +30,7 @@ public class Partnership { @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "partnership_id") private Long id; - + @Column(name = "description", nullable = false) private String description; diff --git a/src/main/java/ssu/eatssu/domain/partnership/persistence/PartnershipRepository.java b/src/main/java/ssu/eatssu/domain/partnership/persistence/PartnershipRepository.java index 052a8ae6..a64fd259 100644 --- a/src/main/java/ssu/eatssu/domain/partnership/persistence/PartnershipRepository.java +++ b/src/main/java/ssu/eatssu/domain/partnership/persistence/PartnershipRepository.java @@ -12,19 +12,19 @@ public interface PartnershipRepository extends JpaRepository { @Query(""" - select distinct pr - from PartnershipRestaurant pr - join fetch pr.partnerships p - left join fetch p.partnershipCollege pc - left join fetch p.partnershipDepartment pd - where - ( - pc = :college - or pd = :department - or (pc is null and pd is null) - ) - and (p.endDate is null or p.endDate >= current_date) - """) + select distinct pr + from PartnershipRestaurant pr + join fetch pr.partnerships p + left join fetch p.partnershipCollege pc + left join fetch p.partnershipDepartment pd + where + ( + pc = :college + or pd = :department + or (pc is null and pd is null) + ) + and (p.endDate is null or p.endDate >= current_date) + """) List findRestaurantsWithMyPartnerships( @Param("college") College college, @Param("department") Department department diff --git a/src/main/java/ssu/eatssu/domain/partnership/persistence/PartnershipRestaurantRepository.java b/src/main/java/ssu/eatssu/domain/partnership/persistence/PartnershipRestaurantRepository.java index 23e92f98..bbe83b6d 100644 --- a/src/main/java/ssu/eatssu/domain/partnership/persistence/PartnershipRestaurantRepository.java +++ b/src/main/java/ssu/eatssu/domain/partnership/persistence/PartnershipRestaurantRepository.java @@ -8,10 +8,11 @@ public interface PartnershipRestaurantRepository extends JpaRepository { @Query(""" - SELECT DISTINCT pr FROM PartnershipRestaurant pr - LEFT JOIN FETCH pr.partnerships p - LEFT JOIN FETCH p.partnershipCollege - LEFT JOIN FETCH p.partnershipDepartment - WHERE p.endDate is null or p.endDate >= CURRENT_DATE""") + SELECT DISTINCT pr FROM PartnershipRestaurant pr + JOIN FETCH pr.partnerships p + LEFT JOIN FETCH p.partnershipCollege + LEFT JOIN FETCH p.partnershipDepartment + WHERE p.endDate IS NULL OR p.endDate >= CURRENT_DATE + """) List findAllWithDetails(); } diff --git a/src/main/java/ssu/eatssu/domain/partnership/service/PartnershipService.java b/src/main/java/ssu/eatssu/domain/partnership/service/PartnershipService.java index b82a94db..63e5dd98 100644 --- a/src/main/java/ssu/eatssu/domain/partnership/service/PartnershipService.java +++ b/src/main/java/ssu/eatssu/domain/partnership/service/PartnershipService.java @@ -1,6 +1,8 @@ package ssu.eatssu.domain.partnership.service; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.context.ApplicationEventPublisher; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import ssu.eatssu.domain.auth.security.CustomUserDetails; @@ -19,6 +21,7 @@ import ssu.eatssu.domain.user.entity.User; import ssu.eatssu.domain.user.repository.UserRepository; import ssu.eatssu.global.handler.response.BaseException; +import ssu.eatssu.global.log.event.LogEvent; import java.util.List; import java.util.Optional; @@ -40,6 +43,7 @@ public class PartnershipService { private final UserRepository userRepository; private final PartnershipLikeRepository partnershipLikeRepository; private final PartnershipRestaurantRepository partnerShipRestaurantRepository; + private final ApplicationEventPublisher eventPublisher; @Transactional public void createPartnership(CreatePartnershipRequest request) { @@ -68,22 +72,30 @@ public List getAllPartnerships(CustomUserDetails customUser @Transactional public void togglePartnershipLike(Long partnershipId, CustomUserDetails userDetails) { Partnership partnership = partnershipRepository.findById(partnershipId) - .orElseThrow(() -> new BaseException(NOT_FOUND_PARTNERSHIP)); + .orElseThrow(() -> new BaseException(NOT_FOUND_PARTNERSHIP)); User user = userRepository.findById(userDetails.getId()) - .orElseThrow(() -> new BaseException(NOT_FOUND_USER)); + .orElseThrow(() -> new BaseException(NOT_FOUND_USER)); PartnershipRestaurant partnershipRestaurant = partnership.getPartnershipRestaurant(); - Optional optionalPartnershipLike = partnershipLikeRepository.findByUserAndPartnershipRestaurant( - user, - partnershipRestaurant); + Optional optionalPartnershipLike = + partnershipLikeRepository.findByUserAndPartnershipRestaurant(user, partnershipRestaurant); + if (optionalPartnershipLike.isPresent()) { PartnershipLike partnershipLike = optionalPartnershipLike.get(); partnershipRestaurant.getLikes().remove(partnershipLike); partnershipLikeRepository.delete(partnershipLike); + + eventPublisher.publishEvent(LogEvent.of( + String.format("User[%d] canceled like on PartnershipRestaurant[%d]", + user.getId(), partnershipRestaurant.getId()))); } else { PartnershipLike partnershipLike = new PartnershipLike(user, partnershipRestaurant); partnershipRestaurant.getLikes().add(partnershipLike); partnershipLikeRepository.save(partnershipLike); + + eventPublisher.publishEvent(LogEvent.of( + String.format("User[%d] liked PartnershipRestaurant[%d]", + user.getId(), partnershipRestaurant.getId()))); } } diff --git a/src/main/java/ssu/eatssu/domain/report/dto/ReportCreateRequest.java b/src/main/java/ssu/eatssu/domain/report/dto/ReportCreateRequest.java index 6ddd6800..6f96af91 100644 --- a/src/main/java/ssu/eatssu/domain/report/dto/ReportCreateRequest.java +++ b/src/main/java/ssu/eatssu/domain/report/dto/ReportCreateRequest.java @@ -9,10 +9,10 @@ public record ReportCreateRequest( @Schema(description = "신고할 리뷰 id", example = "4") Long reviewId, - @Schema(description = "신고 타입", example = "BAD_WORD") + @Schema(description = "신고 타입", example = "NO_ASSOCIATE_CONTENT") ReportType reportType, - @Schema(description = "신고 내용", example = "음란성, 욕설 등 부적절한 내용") + @Schema(description = "신고 내용", example = "리뷰 작성 취지에 맞지 않는 내용") String content) { } diff --git a/src/main/java/ssu/eatssu/domain/report/repository/ReportRepository.java b/src/main/java/ssu/eatssu/domain/report/repository/ReportRepository.java index 58d0a066..51edfeba 100644 --- a/src/main/java/ssu/eatssu/domain/report/repository/ReportRepository.java +++ b/src/main/java/ssu/eatssu/domain/report/repository/ReportRepository.java @@ -1,7 +1,22 @@ package ssu.eatssu.domain.report.repository; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; import ssu.eatssu.domain.review.entity.Report; +import java.time.LocalDateTime; + public interface ReportRepository extends JpaRepository { + + @Query(""" + SELECT count(r) > 0 + FROM Report r + WHERE r.user.id = :userId + AND r.review.id = :reviewId + AND r.createdDate >= :threshold +""") + boolean existsRecentReport(@Param("userId") Long userId, + @Param("reviewId") Long reviewId, + @Param("threshold") LocalDateTime threshold); } diff --git a/src/main/java/ssu/eatssu/domain/report/service/ReportService.java b/src/main/java/ssu/eatssu/domain/report/service/ReportService.java index f0d7050b..929d801c 100644 --- a/src/main/java/ssu/eatssu/domain/report/service/ReportService.java +++ b/src/main/java/ssu/eatssu/domain/report/service/ReportService.java @@ -1,6 +1,7 @@ package ssu.eatssu.domain.report.service; import lombok.RequiredArgsConstructor; +import org.springframework.context.ApplicationEventPublisher; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import ssu.eatssu.domain.auth.security.CustomUserDetails; @@ -14,9 +15,11 @@ import ssu.eatssu.domain.user.entity.User; import ssu.eatssu.domain.user.repository.UserRepository; import ssu.eatssu.global.handler.response.BaseException; +import ssu.eatssu.global.log.event.LogEvent; -import static ssu.eatssu.global.handler.response.BaseResponseStatus.NOT_FOUND_REVIEW; -import static ssu.eatssu.global.handler.response.BaseResponseStatus.NOT_FOUND_USER; +import java.time.LocalDateTime; + +import static ssu.eatssu.global.handler.response.BaseResponseStatus.*; @RequiredArgsConstructor @Service @@ -26,18 +29,37 @@ public class ReportService { private final ReviewRepository reviewRepository; private final UserRepository userRepository; private final ReportRepository reportRepository; + private final ApplicationEventPublisher eventPublisher; public Report reportReview(CustomUserDetails userDetails, ReportCreateRequest request) { User user = userRepository.findById(userDetails.getId()) - .orElseThrow(() -> new BaseException(NOT_FOUND_USER)); + .orElseThrow(() -> new BaseException(NOT_FOUND_USER)); Review review = reviewRepository.findById(request.reviewId()) - .orElseThrow(() -> new BaseException(NOT_FOUND_REVIEW)); + .orElseThrow(() -> new BaseException(NOT_FOUND_REVIEW)); + + if(reportRepository.existsRecentReport(user.getId(), review.getId(), LocalDateTime.now().minusHours(24))){ + throw new BaseException(RECENT_REPORT_ON_REVIEW); + } Report report = Report.create(user, review, request, ReportStatus.PENDING); - return reportRepository.save(report); + reportRepository.save(report); + + eventPublisher.publishEvent(LogEvent.of( + String.format( + "Report created: reportId=%d, reviewId=%d, userId=%d, reportType=%s, status=%s", + report.getId(), + report.getReview().getId(), + report.getUser().getId(), + report.getReportType(), + report.getStatus() + ) + )); + + return report; } + public ReportTypeList getReportType() { return ReportTypeList.get(); } diff --git a/src/main/java/ssu/eatssu/domain/restaurant/entity/Restaurant.java b/src/main/java/ssu/eatssu/domain/restaurant/entity/Restaurant.java index 553e53bd..3bd2554f 100644 --- a/src/main/java/ssu/eatssu/domain/restaurant/entity/Restaurant.java +++ b/src/main/java/ssu/eatssu/domain/restaurant/entity/Restaurant.java @@ -15,7 +15,7 @@ public enum Restaurant { FOOD_COURT("FOOD_COURT", null), SNACK_CORNER("SNACK_CORNER", null), HAKSIK("HAKSIK", 5000), - FACULTY("FACULTY",null); + FACULTY("FACULTY", null); private final String restaurantName; private final Integer restaurantPrice; diff --git a/src/main/java/ssu/eatssu/domain/review/dto/CreateMenuReviewRequest.java b/src/main/java/ssu/eatssu/domain/review/dto/CreateMenuReviewRequest.java index 2a50f46f..b688ff5d 100644 --- a/src/main/java/ssu/eatssu/domain/review/dto/CreateMenuReviewRequest.java +++ b/src/main/java/ssu/eatssu/domain/review/dto/CreateMenuReviewRequest.java @@ -6,7 +6,6 @@ import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; -import org.springframework.util.Assert; import ssu.eatssu.domain.menu.entity.Menu; import ssu.eatssu.domain.rating.entity.Ratings; import ssu.eatssu.domain.review.entity.Review; diff --git a/src/main/java/ssu/eatssu/domain/review/dto/MealReviewResponse.java b/src/main/java/ssu/eatssu/domain/review/dto/MealReviewResponse.java index 633cb7c6..07d3ab40 100644 --- a/src/main/java/ssu/eatssu/domain/review/dto/MealReviewResponse.java +++ b/src/main/java/ssu/eatssu/domain/review/dto/MealReviewResponse.java @@ -10,6 +10,7 @@ import java.time.LocalDate; import java.util.ArrayList; import java.util.List; +import java.util.Set; import java.util.stream.Collectors; @AllArgsConstructor @@ -41,19 +42,41 @@ public class MealReviewResponse { @Schema(description = "리뷰 이미지 url 리스트", example = "[\"imgurl1\", \"imgurl2\"]") private List imageUrls; - @Schema(description = "좋아요한 메뉴명 리스트", example = "[\"메뉴1\", \"메뉴2\"]") - private List likedMenuNames; - @Schema(description = "메뉴명 리스트", example = "['고구마치즈돈까스', '막국수', '미니밥','단무지', '요구르트']") - private List menuNames; - - public static MealReviewResponse from(Review review, Long userId) { + @Schema(description = "메뉴 리스트", example = """ + [ + { + "menuId": 3143, + "name": "생고기제육볶음", + "isLike": true + }, + { + "menuId": 3144, + "name": "오징어초무침", + "isLike": false + } + ] + """) + private List menuList; + + public static MealReviewResponse from(Review review, + Long userId, + List validMenus) { List imageUrls = new ArrayList<>(); review.getReviewImages().forEach(i -> imageUrls.add(i.getImageUrl())); - List likedMenuNames = review.getMenuLikes().stream() - .filter(ReviewMenuLike::getIsLike) - .map(like -> like.getMenu().getName()) - .collect(Collectors.toList()); + // 좋아요한 메뉴 ID 모음 + Set likedMenuIds = review.getMenuLikes().stream() + .filter(ReviewMenuLike::getIsLike) + .map(like -> like.getMenu().getId()) + .collect(Collectors.toSet()); + + List menuNames = validMenus.stream() + .map(valid -> new MenuIdNameLikeDto( + valid.getMenuId(), + valid.getName(), + likedMenuIds.contains(valid.getMenuId()) + )) + .toList(); MealReviewResponseBuilder builder = MealReviewResponse.builder() .reviewId(review.getId()) @@ -61,8 +84,7 @@ public static MealReviewResponse from(Review review, Long userId) { .writtenAt(review.getCreatedDate().toLocalDate()) .content(review.getContent()) .imageUrls(imageUrls) - .menuNames(review.getMeal().getMenuNames()) - .likedMenuNames(likedMenuNames); + .menuList(menuNames); if (review.getUser() == null) { return builder.writerId(null) @@ -82,5 +104,6 @@ public static MealReviewResponse from(Review review, Long userId) { .writerNickname(review.getUser().getNickname()) .isWriter(false) .build(); + } } diff --git a/src/main/java/ssu/eatssu/domain/review/dto/MealReviewsV2Response.java b/src/main/java/ssu/eatssu/domain/review/dto/MealReviewsV2Response.java index 2fa91875..1d98f7c0 100644 --- a/src/main/java/ssu/eatssu/domain/review/dto/MealReviewsV2Response.java +++ b/src/main/java/ssu/eatssu/domain/review/dto/MealReviewsV2Response.java @@ -12,26 +12,26 @@ @AllArgsConstructor @Builder public class MealReviewsV2Response implements ReviewInformationResponse { - @Schema(description = "메뉴명 리스트", example = "['고구마치즈돈까스', '막국수', '미니밥','단무지', '요구르트']") - private List menuNames; + @Schema(description = "메뉴 리스트") + private List menuList; @Schema(description = "리뷰 개수", example = "15") private Long totalReviewCount; - @Schema(description = "평점-메인", example = "4.4") - private Double mainRating; + @Schema(description = "평점", example = "4.4") + private Double rating; @Schema(description = "좋아요 개수", example = "4.4") private Integer likeCount; @Schema(description = "평점 별 갯수") private ReviewRatingCount reviewRatingCount; - public static MealReviewsV2Response of(Long totalReviewCount, List menuNames, + public static MealReviewsV2Response of(Long totalReviewCount, List menuNames, RatingAverages ratingAverages, ReviewRatingCount reviewRatingCount) { return MealReviewsV2Response.builder() - .menuNames(menuNames) - .mainRating(ratingAverages.mainRating()) + .menuList(menuNames) + .rating(ratingAverages.mainRating()) .totalReviewCount(totalReviewCount) .reviewRatingCount(reviewRatingCount) .build(); diff --git a/src/main/java/ssu/eatssu/domain/review/dto/MenuIdNameDto.java b/src/main/java/ssu/eatssu/domain/review/dto/MenuIdNameDto.java new file mode 100644 index 00000000..99fe21ed --- /dev/null +++ b/src/main/java/ssu/eatssu/domain/review/dto/MenuIdNameDto.java @@ -0,0 +1,5 @@ +package ssu.eatssu.domain.review.dto; + +public record MenuIdNameDto(Long id, + String name) { +} diff --git a/src/main/java/ssu/eatssu/domain/review/dto/MenuIdNameLikeDto.java b/src/main/java/ssu/eatssu/domain/review/dto/MenuIdNameLikeDto.java new file mode 100644 index 00000000..cb8b9a27 --- /dev/null +++ b/src/main/java/ssu/eatssu/domain/review/dto/MenuIdNameLikeDto.java @@ -0,0 +1,8 @@ +package ssu.eatssu.domain.review.dto; + +public record MenuIdNameLikeDto( + Long id, + String name, + Boolean isLike +) { +} diff --git a/src/main/java/ssu/eatssu/domain/review/dto/MenuReviewsV2Response.java b/src/main/java/ssu/eatssu/domain/review/dto/MenuReviewsV2Response.java index 7c68fe56..01989b70 100644 --- a/src/main/java/ssu/eatssu/domain/review/dto/MenuReviewsV2Response.java +++ b/src/main/java/ssu/eatssu/domain/review/dto/MenuReviewsV2Response.java @@ -17,8 +17,8 @@ public class MenuReviewsV2Response implements ReviewInformationResponse { @Schema(description = "리뷰 개수", example = "15") private Long totalReviewCount; - @Schema(description = "평점-메인", example = "4.4") - private Double mainRating; + @Schema(description = "평점", example = "4.4") + private Double rating; @Schema(description = "좋아요 개수", example = "4.4") private Integer likeCount; @@ -34,7 +34,7 @@ public static MenuReviewsV2Response of(Long totalReviewCount, return MenuReviewsV2Response.builder() .menuName(menuName) - .mainRating(ratingAverages.mainRating()) + .rating(ratingAverages.mainRating()) .likeCount(menu.getLikeCount()) .totalReviewCount(totalReviewCount) .reviewRatingCount(reviewRatingCount) diff --git a/src/main/java/ssu/eatssu/domain/review/dto/RestaurantReviewResponse.java b/src/main/java/ssu/eatssu/domain/review/dto/RestaurantReviewResponse.java index 197d6f5f..e7a20523 100644 --- a/src/main/java/ssu/eatssu/domain/review/dto/RestaurantReviewResponse.java +++ b/src/main/java/ssu/eatssu/domain/review/dto/RestaurantReviewResponse.java @@ -17,7 +17,7 @@ public class RestaurantReviewResponse { private ReviewRatingCount reviewRatingCount; @Schema(description = "리뷰 평점", example = "4.4") - private Double mainRating; + private Double rating; @Schema(description = "좋아요 개수", example = "15") private Integer likeCount; diff --git a/src/main/java/ssu/eatssu/domain/review/dto/ReviewDetail.java b/src/main/java/ssu/eatssu/domain/review/dto/ReviewDetail.java index 0d196d27..ca5979cd 100644 --- a/src/main/java/ssu/eatssu/domain/review/dto/ReviewDetail.java +++ b/src/main/java/ssu/eatssu/domain/review/dto/ReviewDetail.java @@ -4,11 +4,15 @@ import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Getter; +import ssu.eatssu.domain.menu.entity.Menu; import ssu.eatssu.domain.review.entity.Review; +import ssu.eatssu.domain.review.entity.ReviewMenuLike; import java.time.LocalDate; import java.util.ArrayList; import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; @AllArgsConstructor @Builder @@ -19,8 +23,8 @@ public class ReviewDetail { @Schema(description = "리뷰 식별자", example = "123") Long reviewId; - @Schema(description = "메뉴 이름", example = "콥샐러드") - String menu; + @Schema(description = "메뉴", example = "콥샐러드") + MenuIdNameLikeDto menu; @Schema(description = "작성자 식별자", example = "123") Long writerId; @@ -31,17 +35,11 @@ public class ReviewDetail { @Schema(description = "작성자 닉네임", example = "숭시리시리") private String writerNickname; - @Schema(description = "평점-메인", example = "4") - private Integer mainRating; - - @Schema(description = "평점-양", example = "4") - private Integer amountRating; - - @Schema(description = "평점-맛", example = "4") - private Integer tasteRating; + @Schema(description = "평점", example = "4") + private Integer rating; @Schema(description = "리뷰 작성 날짜(format = yyyy-MM-dd)", example = "2023-04-07") - private LocalDate writedAt; + private LocalDate writtenAt; @Schema(description = "리뷰 내용", example = "맛있습니당") private String content; @@ -49,17 +47,24 @@ public class ReviewDetail { @Schema(description = "리뷰 이미지 url 리스트", example = "[\"imgurl1\", \"imgurl2\"]") private List imageUrls; + public static ReviewDetail from(Review review, Long userId) { List imageUrls = new ArrayList<>(); review.getReviewImages().forEach(i -> imageUrls.add(i.getImageUrl())); + Menu menu = review.getMenu(); + + Set likedMenuIds = review.getMenuLikes().stream() + .filter(ReviewMenuLike::getIsLike) + .map(like -> like.getMenu().getId()) + .collect(Collectors.toSet()); ReviewDetailBuilder builder = ReviewDetail.builder() .reviewId(review.getId()) - .mainRating(review.getRatings().getMainRating()) - .writedAt(review.getCreatedDate().toLocalDate()) + .rating(review.getRatings().getMainRating()) + .writtenAt(review.getCreatedDate().toLocalDate()) .content(review.getContent()) .imageUrls(imageUrls) - .menu(review.getMenu().getName()); + .menu(new MenuIdNameLikeDto(menu.getId(),menu.getName(),likedMenuIds.contains(menu.getId()))); if (review.getUser() == null) { return builder diff --git a/src/main/java/ssu/eatssu/domain/review/dto/ValidMenuForViewResponse.java b/src/main/java/ssu/eatssu/domain/review/dto/ValidMenuForViewResponse.java index 494ba5a0..d787cc07 100644 --- a/src/main/java/ssu/eatssu/domain/review/dto/ValidMenuForViewResponse.java +++ b/src/main/java/ssu/eatssu/domain/review/dto/ValidMenuForViewResponse.java @@ -4,7 +4,6 @@ import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Getter; -import lombok.NoArgsConstructor; import lombok.Setter; import java.util.List; @@ -15,7 +14,7 @@ @Schema(title = "리뷰에 포함되는 메뉴") @AllArgsConstructor public class ValidMenuForViewResponse { - @Schema(description = "리뷰에 포함되는 메뉴 리스트", example = "[김치볶음밥, 고구마치즈돈까스, 김자반]") + @Schema(description = "리뷰에 포함되는 메뉴 리스트") private List menuList; @Getter diff --git a/src/main/java/ssu/eatssu/domain/review/service/ReviewServiceV2.java b/src/main/java/ssu/eatssu/domain/review/service/ReviewServiceV2.java index 3950faf1..89fb03d6 100644 --- a/src/main/java/ssu/eatssu/domain/review/service/ReviewServiceV2.java +++ b/src/main/java/ssu/eatssu/domain/review/service/ReviewServiceV2.java @@ -1,6 +1,8 @@ package ssu.eatssu.domain.review.service; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.context.ApplicationEventPublisher; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Slice; @@ -17,6 +19,7 @@ import ssu.eatssu.domain.review.dto.CreateMenuReviewRequest; import ssu.eatssu.domain.review.dto.MealReviewResponse; import ssu.eatssu.domain.review.dto.MealReviewsV2Response; +import ssu.eatssu.domain.review.dto.MenuIdNameDto; import ssu.eatssu.domain.review.dto.MenuLikeRequest; import ssu.eatssu.domain.review.dto.MenuReviewsV2Response; import ssu.eatssu.domain.review.dto.RestaurantReviewResponse; @@ -34,6 +37,7 @@ import ssu.eatssu.domain.user.entity.User; import ssu.eatssu.domain.user.repository.UserRepository; import ssu.eatssu.global.handler.response.BaseException; +import ssu.eatssu.global.log.event.LogEvent; import java.util.Collections; import java.util.List; @@ -48,8 +52,9 @@ import static ssu.eatssu.global.handler.response.BaseResponseStatus.NOT_FOUND_USER; import static ssu.eatssu.global.handler.response.BaseResponseStatus.REVIEW_PERMISSION_DENIED; -@RequiredArgsConstructor +@Slf4j @Service +@RequiredArgsConstructor public class ReviewServiceV2 { private final UserRepository userRepository; private final ReviewRepository reviewRepository; @@ -57,6 +62,7 @@ public class ReviewServiceV2 { private final MealRepository mealRepository; private final MealMenuRepository mealMenuRepository; private final ReviewImageRepository reviewImageRepository; + private final ApplicationEventPublisher eventPublisher; /** * meal에 대한 리뷰 생성 @@ -64,10 +70,10 @@ public class ReviewServiceV2 { @Transactional public void createMealReview(CustomUserDetails userDetails, CreateMealReviewRequest request) { User user = userRepository.findById(userDetails.getId()) - .orElseThrow(() -> new BaseException(NOT_FOUND_USER)); + .orElseThrow(() -> new BaseException(NOT_FOUND_USER)); Meal meal = mealRepository.findById(request.getMealId()) - .orElseThrow(() -> new BaseException(NOT_FOUND_MEAL)); + .orElseThrow(() -> new BaseException(NOT_FOUND_MEAL)); Review review = request.toReviewEntity(user, meal); @@ -75,11 +81,20 @@ public void createMealReview(CustomUserDetails userDetails, CreateMealReviewRequ for (MenuLikeRequest menuLike : request.getMenuLikes()) { Menu menu = menuRepository.findById(menuLike.getMenuId()) - .orElseThrow(() -> new BaseException(NOT_FOUND_MENU)); + .orElseThrow(() -> new BaseException(NOT_FOUND_MENU)); review.addReviewMenuLike(menu, menuLike.getIsLike()); } reviewRepository.save(review); + + eventPublisher.publishEvent(LogEvent.of( + String.format("MealReview created: reviewId=%d, mealId=%d, userId=%d, images=%d, menuLikes=%d", + review.getId(), + meal.getId(), + user.getId(), + request.getImageUrls().size(), + request.getMenuLikes().size()) + )); } /** @@ -88,10 +103,10 @@ public void createMealReview(CustomUserDetails userDetails, CreateMealReviewRequ @Transactional public void createMenuReview(CustomUserDetails userDetails, CreateMenuReviewRequest request) { User user = userRepository.findById(userDetails.getId()) - .orElseThrow(() -> new BaseException(NOT_FOUND_USER)); + .orElseThrow(() -> new BaseException(NOT_FOUND_USER)); Menu menu = menuRepository.findById(request.getMenuId()) - .orElseThrow(() -> new BaseException(NOT_FOUND_MENU)); + .orElseThrow(() -> new BaseException(NOT_FOUND_MENU)); Review review = request.toReviewEntity(user, menu); review.addReviewMenuLike(menu, request.getMenuLike().getIsLike()); @@ -101,6 +116,15 @@ public void createMenuReview(CustomUserDetails userDetails, CreateMenuReviewRequ reviewImageRepository.save(reviewImage); menu.addReview(review); + + eventPublisher.publishEvent(LogEvent.of( + String.format("MenuReview created: reviewId=%d, menuId=%d, userId=%d, isLike=%s, imageUrl=%s", + review.getId(), + menu.getId(), + user.getId(), + request.getMenuLike().getIsLike(), + request.getImageUrl()) + )); } /** @@ -148,7 +172,7 @@ public RestaurantReviewResponse findRestaurantReviews(Restaurant restaurant) { return RestaurantReviewResponse.builder() .totalReviewCount(reviews.size()) .reviewRatingCount(reviewRatingCount) - .mainRating(Math.round(averageRating * 10) / 10.0) + .rating(Math.round(averageRating * 10) / 10.0) .likeCount(likeCount) .unlikeCount(unlikeCount) .build(); @@ -159,28 +183,46 @@ public RestaurantReviewResponse findRestaurantReviews(Restaurant restaurant) { */ public SliceResponse findMealReviewList(Long mealId, Long lastReviewId, Pageable pageable, CustomUserDetails userDetails) { - if (!mealRepository.existsById(mealId)) { - throw new BaseException(NOT_FOUND_MEAL); + + Meal meal = mealRepository.findById(mealId).orElseThrow(() -> new BaseException(NOT_FOUND_MEAL)); + + List menus = mealMenuRepository.findMenusByMeal(meal); + if (menus.isEmpty()) { + log.warn("No menus found for mealId={}", mealId); } - List menuIds = mealMenuRepository.findMenuIdsByMealId(mealId); - if (menuIds.isEmpty()) { + List validMenus = menus.stream() + .filter(menu -> !MenuFilterUtil.isExcludedFromReview( + menu.getName())) + .map(menu -> ValidMenuForViewResponse.MenuDto.builder() + .menuId(menu.getId()) + .name(menu.getName()) + .build()) + .collect(Collectors.toList()); + + + if (validMenus.isEmpty()) { + log.warn("No valid menus found for mealId={}", mealId); return SliceResponse.empty(); } - List mealIds = mealMenuRepository.findMealIdsByMenuIds(menuIds); + List validMenuIds = validMenus.stream().map(ValidMenuForViewResponse.MenuDto::getMenuId).toList(); + List mealIds = mealMenuRepository.findMealIdsByMenuIds(validMenuIds); if (mealIds.isEmpty()) { + log.warn("No related mealIds found for validMenuIds={} in mealId={}", validMenuIds, mealId); return SliceResponse.empty(); } Page pageReviews = reviewRepository.findReviewsByMealIds(mealIds, lastReviewId, pageable); Long userId = (userDetails != null) ? userDetails.getId() : null; + + List mealReviewResponses = pageReviews.getContent() .stream() .map(review -> MealReviewResponse.from(review, - userId)) + userId, validMenus)) .collect(Collectors.toList()); return SliceResponse.builder() @@ -242,6 +284,10 @@ public MenuReviewsV2Response findMenuReviews(Long menuId) { .average() .orElse(0.0); + if (!reviews.isEmpty() && averageRating == 0.0) { + log.warn("All reviews for menuId={} have null/invalid ratings", menuId); + } + Integer likeCount = menu.getLikeCount(); ReviewRatingCount reviewRatingCount = ReviewRatingCount.from(reviews); @@ -252,7 +298,7 @@ public MenuReviewsV2Response findMenuReviews(Long menuId) { .menuName(menu.getName()) .totalReviewCount((long) reviews.size()) .reviewRatingCount(reviewRatingCount) - .mainRating(Math.round(averageRating * 10) / 10.0) + .rating(Math.round(averageRating * 10) / 10.0) .likeCount(likeCount != null ? likeCount : 0) .build(); } @@ -264,6 +310,22 @@ public MealReviewsV2Response findMealReviews(Long mealId) { Meal meal = mealRepository.findById(mealId).orElseThrow(() -> new BaseException(NOT_FOUND_MEAL)); List reviews = reviewRepository.findAllByMeal(meal); List menus = mealMenuRepository.findMenusByMeal(meal); + if (menus.isEmpty()) { + log.warn("No menus found for mealId={}", meal.getId()); + } + + List validMenus = menus.stream() + .filter(menu -> !MenuFilterUtil.isExcludedFromReview( + menu.getName())) + .map(menu -> ValidMenuForViewResponse.MenuDto.builder() + .menuId(menu.getId()) + .name(menu.getName()) + .build()) + .toList(); + + if (validMenus.isEmpty()) { + log.warn("No valid menus for review found in mealId={}", mealId); + } Double averageRating = Optional.ofNullable(reviews) .orElse(Collections.emptyList()) @@ -278,6 +340,10 @@ public MealReviewsV2Response findMealReviews(Long mealId) { .average() .orElse(0.0); + if (!reviews.isEmpty() && averageRating == 0.0) { + log.warn("All reviews have null/invalid ratings for mealId={}", mealId); + } + Integer likeCount = Optional.ofNullable(menus) .orElse(Collections.emptyList()) .stream() @@ -288,27 +354,20 @@ public MealReviewsV2Response findMealReviews(Long mealId) { .sum(); - Integer unlikeCount = Optional.ofNullable(menus) - .orElse(Collections.emptyList()) - .stream() - .filter(Objects::nonNull) - .map(Menu::getUnlikeCount) - .filter(Objects::nonNull) - .mapToInt(Integer::intValue) - .sum(); - ReviewRatingCount reviewRatingCount = ReviewRatingCount.from(reviews); return MealReviewsV2Response .builder() - .menuNames(menus.stream() - .filter(Objects::nonNull) - .map(Menu::getName) - .filter(Objects::nonNull) - .collect(Collectors.toList())) + .menuList(validMenus.stream() + .filter(Objects::nonNull) + .map(menu -> new MenuIdNameDto( + menu.getMenuId(), + menu.getName() + )) + .collect(Collectors.toList())) .totalReviewCount((long) reviews.size()) .reviewRatingCount(reviewRatingCount) - .mainRating(Math.round(averageRating * 10) / 10.0) + .rating(Math.round(averageRating * 10) / 10.0) .likeCount(likeCount) .build(); } @@ -337,6 +396,11 @@ public void updateReview(CustomUserDetails userDetails, Long reviewId, UpdateMea review.update(request.getContent(), request.getRating(), menuLikes); reviewRepository.save(review); + + eventPublisher.publishEvent(LogEvent.of( + String.format("Review updated: reviewId=%d, userId=%d, newRating=%d", + review.getId(), user.getId(), request.getRating()) + )); } /** @@ -355,6 +419,11 @@ public void deleteReview(CustomUserDetails userDetails, Long reviewId) { } review.resetMenuLikes(); + + eventPublisher.publishEvent(LogEvent.of( + String.format("Review deleted: reviewId=%d, userId=%d", review.getId(), user.getId()) + )); + reviewRepository.delete(review); } diff --git a/src/main/java/ssu/eatssu/domain/review/utils/MenuFilterUtil.java b/src/main/java/ssu/eatssu/domain/review/utils/MenuFilterUtil.java index a587501f..e705a7e4 100644 --- a/src/main/java/ssu/eatssu/domain/review/utils/MenuFilterUtil.java +++ b/src/main/java/ssu/eatssu/domain/review/utils/MenuFilterUtil.java @@ -23,6 +23,7 @@ public static boolean isExcludedFromReview(String menuName) { } if (menuName.contains("밥") + && !menuName.contains("자장밥") && !menuName.contains("볶음밥") && !menuName.contains("비빔밥")) { return true; diff --git a/src/main/java/ssu/eatssu/domain/user/department/entity/College.java b/src/main/java/ssu/eatssu/domain/user/department/entity/College.java index 70ae0260..b8447a40 100644 --- a/src/main/java/ssu/eatssu/domain/user/department/entity/College.java +++ b/src/main/java/ssu/eatssu/domain/user/department/entity/College.java @@ -19,20 +19,17 @@ @Getter @NoArgsConstructor(access = AccessLevel.PROTECTED) public class College { + @OneToMany(mappedBy = "college", cascade = CascadeType.ALL, orphanRemoval = true) + private final List departments = new ArrayList<>(); + @OneToMany(mappedBy = "partnershipCollege", cascade = CascadeType.ALL, orphanRemoval = true) + private final List partnerships = new ArrayList<>(); @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "college_id") private Long id; - @Column(nullable = false, unique = true) private String name; - @OneToMany(mappedBy = "college", cascade = CascadeType.ALL, orphanRemoval = true) - private final List departments = new ArrayList<>(); - - @OneToMany(mappedBy = "partnershipCollege", cascade = CascadeType.ALL, orphanRemoval = true) - private final List partnerships = new ArrayList<>(); - public College(String name) { this.name = name; } diff --git a/src/main/java/ssu/eatssu/domain/user/department/entity/Department.java b/src/main/java/ssu/eatssu/domain/user/department/entity/Department.java index 9c5f227c..51f02653 100644 --- a/src/main/java/ssu/eatssu/domain/user/department/entity/Department.java +++ b/src/main/java/ssu/eatssu/domain/user/department/entity/Department.java @@ -22,21 +22,18 @@ @Getter @NoArgsConstructor(access = AccessLevel.PROTECTED) public class Department { + @OneToMany(mappedBy = "partnershipDepartment", cascade = CascadeType.ALL, orphanRemoval = true) + private final List partnerships = new ArrayList<>(); @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "department_id") private Long id; - @Column(nullable = false) private String name; - @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "college_id") private College college; - @OneToMany(mappedBy = "partnershipDepartment", cascade = CascadeType.ALL, orphanRemoval = true) - private final List partnerships = new ArrayList<>(); - public Department(String name) { this.name = name; } diff --git a/src/main/java/ssu/eatssu/domain/user/dto/GetDepartmentResponse.java b/src/main/java/ssu/eatssu/domain/user/dto/GetDepartmentResponse.java index 128b9d92..73774bc0 100644 --- a/src/main/java/ssu/eatssu/domain/user/dto/GetDepartmentResponse.java +++ b/src/main/java/ssu/eatssu/domain/user/dto/GetDepartmentResponse.java @@ -4,5 +4,5 @@ @Builder public record GetDepartmentResponse(Long id, - String name) { + String name) { } diff --git a/src/main/java/ssu/eatssu/domain/user/dto/MyMealReviewResponse.java b/src/main/java/ssu/eatssu/domain/user/dto/MyMealReviewResponse.java index ff8604c2..c10579af 100644 --- a/src/main/java/ssu/eatssu/domain/user/dto/MyMealReviewResponse.java +++ b/src/main/java/ssu/eatssu/domain/user/dto/MyMealReviewResponse.java @@ -4,13 +4,17 @@ import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Getter; +import ssu.eatssu.domain.review.dto.MenuIdNameLikeDto; import ssu.eatssu.domain.review.entity.Review; import ssu.eatssu.domain.review.entity.ReviewMenuLike; +import ssu.eatssu.domain.review.utils.MenuFilterUtil; import java.time.LocalDate; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; @AllArgsConstructor @Builder @@ -31,24 +35,53 @@ public class MyMealReviewResponse { @Schema(description = "리뷰 이미지 url 리스트", example = "[\"imgurl1\", \"imgurl2\"]") private List imageUrls; - - @Schema(description = "좋아요한 메뉴명 리스트", example = "[\"메뉴1\", \"메뉴2\"]") - private List likedMenuNames; - @Schema(description = "메뉴명 리스트", example = "['고구마치즈돈까스', '막국수', '미니밥','단무지', '요구르트']") - private List menuNames; + @Schema(description = "메뉴 리스트", example = """ + [ + { + "menuId": 3143, + "name": "생고기제육볶음", + "isLike": true + }, + { + "menuId": 3144, + "name": "오징어초무침", + "isLike": false + } + ] + """) + private List menuList; public static MyMealReviewResponse from(Review review) { List imgUrlList = new ArrayList<>(); review.getReviewImages().forEach(i -> imgUrlList.add(i.getImageUrl())); - List likedMenuNames = review.getMenuLikes().stream() - .filter(ReviewMenuLike::getIsLike) - .map(like -> like.getMenu().getName()) - .toList(); + Set likedMenuIds = review.getMenuLikes().stream() + .filter(ReviewMenuLike::getIsLike) + .map(like -> like.getMenu().getId()) + .collect(Collectors.toSet()); + + + List menuNames; + + if (review.getMeal() != null) { + menuNames = review.getMeal().getMenus().stream() + .filter(menu -> !MenuFilterUtil.isExcludedFromReview(menu.getName())) + .map(menu -> new MenuIdNameLikeDto( + menu.getId(), + menu.getName(), + likedMenuIds.contains(menu.getId()) + )) + .toList(); + } else { + menuNames = Collections.singletonList( + new MenuIdNameLikeDto( + review.getMenu().getId(), + review.getMenu().getName(), + likedMenuIds.contains(review.getMenu().getId()) + ) + ); + } - List menuNames = review.getMeal() == null ? Collections.singletonList(review.getMenu() - .getName()) : review.getMeal() - .getMenuNames(); return MyMealReviewResponse .builder() .reviewId(review.getId()) @@ -56,8 +89,7 @@ public static MyMealReviewResponse from(Review review) { .writtenAt(review.getCreatedDate().toLocalDate()) .content(review.getContent()) .imageUrls(imgUrlList) - .likedMenuNames(likedMenuNames) - .menuNames(menuNames) + .menuList(menuNames) .build(); } } diff --git a/src/main/java/ssu/eatssu/domain/user/service/UserService.java b/src/main/java/ssu/eatssu/domain/user/service/UserService.java index 9bd2f1e5..aa5332ca 100644 --- a/src/main/java/ssu/eatssu/domain/user/service/UserService.java +++ b/src/main/java/ssu/eatssu/domain/user/service/UserService.java @@ -3,6 +3,7 @@ import jakarta.transaction.Transactional; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.context.ApplicationEventPublisher; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Component; import org.springframework.stereotype.Service; @@ -23,6 +24,7 @@ import ssu.eatssu.domain.user.entity.User; import ssu.eatssu.domain.user.repository.UserRepository; import ssu.eatssu.global.handler.response.BaseException; +import ssu.eatssu.global.log.event.LogEvent; import java.util.List; import java.util.UUID; @@ -44,6 +46,7 @@ public class UserService { private final DepartmentRepository departmentRepository; private final UserProperties userProperties; private final CollegeRepository collegeRepository; + private final ApplicationEventPublisher eventPublisher; public User join(String email, OAuthProvider provider, String providerId) { String credentials = createCredentials(provider, providerId); @@ -61,6 +64,11 @@ public void updateNickname(CustomUserDetails userDetails, NicknameUpdateRequest } user.updateNickname(request.nickname()); + + eventPublisher.publishEvent(LogEvent.of( + String.format("User nickname updated: userId=%d, newNickname=%s", + user.getId(), request.nickname())) + ); } public MyPageResponse findMyPage(CustomUserDetails userDetails) { @@ -77,6 +85,11 @@ public boolean withdraw(CustomUserDetails userDetails) { user.getUserInquiries().forEach(inquiry -> inquiry.clearUser()); userRepository.delete(user); + eventPublisher.publishEvent(LogEvent.of( + String.format("User withdrawn: userId=%d", + user.getId()) + )); + return true; } @@ -131,14 +144,14 @@ public List getDepartmentList(Long collegeId) { .orElseThrow(() -> new BaseException(VALIDATION_ERROR)); List departments = departmentRepository.findByCollege(college); return departments.stream().map(department -> GetDepartmentResponse.builder() - .id(department.getId()) - .name(department.getName()) - .build()) - .toList(); + .id(department.getId()) + .name(department.getName()) + .build()) + .toList(); } private boolean isForbiddenNickname(String nickname) { return userProperties.getForbiddenNicknames().stream() .anyMatch(forbidden -> forbidden.equalsIgnoreCase(nickname)); } -} \ No newline at end of file +} diff --git a/src/main/java/ssu/eatssu/global/config/AsyncConfig.java b/src/main/java/ssu/eatssu/global/config/AsyncConfig.java new file mode 100644 index 00000000..ca9284c6 --- /dev/null +++ b/src/main/java/ssu/eatssu/global/config/AsyncConfig.java @@ -0,0 +1,9 @@ +package ssu.eatssu.global.config; + +import org.springframework.context.annotation.Configuration; +import org.springframework.scheduling.annotation.EnableAsync; + +@Configuration +@EnableAsync +public class AsyncConfig { +} diff --git a/src/main/java/ssu/eatssu/global/handler/GlobalExceptionHandler.java b/src/main/java/ssu/eatssu/global/handler/GlobalExceptionHandler.java index d05810e1..8507c6af 100644 --- a/src/main/java/ssu/eatssu/global/handler/GlobalExceptionHandler.java +++ b/src/main/java/ssu/eatssu/global/handler/GlobalExceptionHandler.java @@ -46,7 +46,7 @@ public class GlobalExceptionHandler extends ResponseEntityExceptionHandler { */ @ExceptionHandler(BaseException.class) public ResponseEntity> handleBaseException(BaseException e) { - if(BaseResponseStatus.sendSlackNotification(e.getStatus())){ + if (BaseResponseStatus.sendSlackNotification(e.getStatus())) { slackErrorNotifier.notify(e); } return ResponseEntity.status(e.getStatus().getHttpStatus()).body(BaseResponse.fail(e.getStatus())); diff --git a/src/main/java/ssu/eatssu/global/handler/response/BaseResponseStatus.java b/src/main/java/ssu/eatssu/global/handler/response/BaseResponseStatus.java index e7352315..eb418ff1 100644 --- a/src/main/java/ssu/eatssu/global/handler/response/BaseResponseStatus.java +++ b/src/main/java/ssu/eatssu/global/handler/response/BaseResponseStatus.java @@ -31,6 +31,7 @@ public enum BaseResponseStatus { EXISTED_MEAL(false, HttpStatus.BAD_REQUEST, 40011, "이미 존재하는 식단입니다."), INVALID_TARGET_TYPE(false, HttpStatus.BAD_REQUEST, 40012, "잘못된 targetType 입니다."), MISSING_USER_DEPARTMENT(false, HttpStatus.BAD_REQUEST, 40013, "사용자의 학과 정보가 없습니다."), + RECENT_REPORT_ON_REVIEW(false, HttpStatus.BAD_REQUEST, 40014, "24시간 이내에 동일 댓글을 신고했습니다."), /** * 401 UNAUTHORIZED 권한없음(인증 실패) @@ -59,7 +60,7 @@ public enum BaseResponseStatus { NOT_FOUND_DEPARTMENT(false, HttpStatus.NOT_FOUND, 40409, "해당 학과를 찾을 수 없습니다."), NOT_FOUND_PARTNERSHIP(false, HttpStatus.NOT_FOUND, 40410, "해당 제휴를 찾을 수 없습니다."), NOT_FOUND_PARTNERSHIP_RESTAURANT(false, HttpStatus.NOT_FOUND, 40411, "해당 제휴 식당을 찾을 수 없습니다."), - INVALID_NICKNAME(false,HttpStatus.NOT_FOUND,40412,"잘못된 닉네임입니다."), + INVALID_NICKNAME(false, HttpStatus.NOT_FOUND, 40412, "잘못된 닉네임입니다."), /** * 405 METHOD_NOT_ALLOWED 지원하지 않은 method 호출 diff --git a/src/main/java/ssu/eatssu/global/log/ControllerLogAspect.java b/src/main/java/ssu/eatssu/global/log/ControllerLogAspect.java new file mode 100644 index 00000000..2de93aad --- /dev/null +++ b/src/main/java/ssu/eatssu/global/log/ControllerLogAspect.java @@ -0,0 +1,127 @@ +package ssu.eatssu.global.log; + +import com.fasterxml.jackson.databind.ObjectMapper; +import jakarta.servlet.http.HttpServletRequest; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.annotation.Around; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.annotation.Pointcut; +import org.aspectj.lang.reflect.MethodSignature; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.stereotype.Component; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; +import ssu.eatssu.domain.auth.security.CustomUserDetails; +import ssu.eatssu.global.log.annotation.LogMask; + +import java.lang.reflect.Field; +import java.util.HashMap; +import java.util.Map; +import java.util.stream.Collectors; +import java.util.stream.IntStream; + + +@Aspect +@Component +@Slf4j +@RequiredArgsConstructor +public class ControllerLogAspect { + + private final ObjectMapper objectMapper; + + @Pointcut("within(@org.springframework.web.bind.annotation.RestController *)") + public void restController() {} + + @Around("restController()") + public Object logApi(ProceedingJoinPoint joinPoint) throws Throwable { + long start = System.currentTimeMillis(); + + HttpServletRequest request = + ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest(); + String uri = request.getRequestURI(); + String method = request.getMethod(); + + MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature(); + String[] paramNames = methodSignature.getParameterNames(); + Object[] args = joinPoint.getArgs(); + + // 요청자 + String userIdLog = IntStream.range(0, args.length) + .filter(i -> args[i] instanceof CustomUserDetails) + .mapToObj(i -> { + CustomUserDetails user = (CustomUserDetails) args[i]; + return "userId=" + user.getId(); + }) + .findFirst() + .orElse("userId=anonymous"); + + // 나머지 요청 인자 + String otherArgsJson = IntStream.range(0, args.length) + .filter(i -> !(args[i] instanceof HttpServletRequest)) + .filter(i -> !(args[i] instanceof CustomUserDetails)) + .filter(i -> !(args[i] instanceof org.springframework.validation.BindingResult)) + .mapToObj(i -> { + String name = (paramNames != null && i < paramNames.length) ? paramNames[i] : "arg" + i; + Object arg = args[i]; + try { + String value; + if (arg != null) { + Map safeMap = toSafeMap(arg); + value = objectMapper.writeValueAsString(safeMap); + } else { + value = "null"; + } + if (value.length() > 200) value = value.substring(0, 200) + "...(truncated)"; + return name + "=" + value; + } catch (Exception e) { + return name + "=" + String.valueOf(arg); + } + }) + .collect(Collectors.joining(", ")); + + String argsJson = userIdLog + (otherArgsJson.isEmpty() ? "" : ", " + otherArgsJson); + + log.info("REQUEST {} {} args={}", method, uri, argsJson); + + try { + Object result = joinPoint.proceed(); + long time = System.currentTimeMillis() - start; + + String resultJson; + try { + resultJson = objectMapper.writeValueAsString(result); + if (resultJson.length() > 600) { + resultJson = resultJson.substring(0, 600) + "...(truncated)"; + } + } catch (Exception e) { + resultJson = String.valueOf(result); + } + + log.info("RESPONSE {} {} ({} ms) result={}", method, uri, time, resultJson); + return result; + } catch (Throwable e) { + long time = System.currentTimeMillis() - start; + log.error("EXCEPTION {} {} ({} ms) cause={}", method, uri, time, e.getMessage(), e); + throw e; + } + } + + private Map toSafeMap(Object arg) { + Map result = new HashMap<>(); + for (Field field : arg.getClass().getDeclaredFields()) { + field.setAccessible(true); + try { + Object value = field.get(arg); + if (field.isAnnotationPresent(LogMask.class)) { + value = "***"; + } + result.put(field.getName(), value); + } catch (IllegalAccessException e) { + result.put(field.getName(), "ERROR"); + } + } + return result; + } +} diff --git a/src/main/java/ssu/eatssu/global/log/MDCLoggingFilter.java b/src/main/java/ssu/eatssu/global/log/MDCLoggingFilter.java new file mode 100644 index 00000000..e1d6ce9e --- /dev/null +++ b/src/main/java/ssu/eatssu/global/log/MDCLoggingFilter.java @@ -0,0 +1,30 @@ +package ssu.eatssu.global.log; + +import jakarta.servlet.*; +import jakarta.servlet.http.HttpServletRequest; +import org.slf4j.MDC; +import org.springframework.core.Ordered; +import org.springframework.core.annotation.Order; +import org.springframework.stereotype.Component; + +import java.io.IOException; +import java.util.UUID; + +@Component +@Order(Ordered.HIGHEST_PRECEDENCE) +public class MDCLoggingFilter implements Filter { + @Override + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) + throws IOException, ServletException { + String requestId = ((HttpServletRequest) request).getHeader("X-RequestID"); + if (requestId == null) { + requestId = UUID.randomUUID().toString().replace("-", ""); + } + MDC.put("requestId", requestId); + try { + chain.doFilter(request, response); + } finally { + MDC.clear(); + } + } +} diff --git a/src/main/java/ssu/eatssu/global/log/annotation/LogMask.java b/src/main/java/ssu/eatssu/global/log/annotation/LogMask.java new file mode 100644 index 00000000..bec4c56d --- /dev/null +++ b/src/main/java/ssu/eatssu/global/log/annotation/LogMask.java @@ -0,0 +1,11 @@ +package ssu.eatssu.global.log.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target({ElementType.FIELD, ElementType.RECORD_COMPONENT}) +@Retention(RetentionPolicy.RUNTIME) +public @interface LogMask { +} diff --git a/src/main/java/ssu/eatssu/global/log/event/LogEvent.java b/src/main/java/ssu/eatssu/global/log/event/LogEvent.java new file mode 100644 index 00000000..c817d949 --- /dev/null +++ b/src/main/java/ssu/eatssu/global/log/event/LogEvent.java @@ -0,0 +1,7 @@ +package ssu.eatssu.global.log.event; + +public record LogEvent(String message) { + public static LogEvent of(String message) { + return new LogEvent(message); + } +} diff --git a/src/main/java/ssu/eatssu/global/log/event/LogEventListener.java b/src/main/java/ssu/eatssu/global/log/event/LogEventListener.java new file mode 100644 index 00000000..d687a2c0 --- /dev/null +++ b/src/main/java/ssu/eatssu/global/log/event/LogEventListener.java @@ -0,0 +1,20 @@ +package ssu.eatssu.global.log.event; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.scheduling.annotation.Async; +import org.springframework.stereotype.Component; +import org.springframework.transaction.event.TransactionPhase; +import org.springframework.transaction.event.TransactionalEventListener; + +@Component +@RequiredArgsConstructor +@Slf4j +public class LogEventListener { + + @Async + @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT) + public void handleLogEvent(LogEvent event) { + log.info(event.message()); + } +} diff --git a/src/main/resources/application-local.yml b/src/main/resources/application-local.yml deleted file mode 100644 index 2bc506ce..00000000 --- a/src/main/resources/application-local.yml +++ /dev/null @@ -1,84 +0,0 @@ -server: - port: 9000 - env: local - - -spring: - datasource: - driver-class-name: com.mysql.cj.jdbc.Driver - url: jdbc:mysql://eatssu-db.cf0ackgagqzb.ap-northeast-2.rds.amazonaws.com/dev - username: admin - password: ssu2023! - - jpa: - hibernate: - ddl-auto: update - properties: - hibernate: - jdbc: - lob: - non_contextual_creation: true - format_sql: true - show_sql: false - - servlet: - multipart: - max-file-size: 20MB - max-request-size: 20MB - -jwt: - secret: - key: ${EATSSU_JWT_SECRET_LOCAL} - token-validity-in-seconds: 86400 - refresh-token-validity-in-seconds: 604800 - -cloud: - aws: - credentials: - accessKey: ${EATSSU_AWS_ACCESS_KEY_DEV} - secretKey: ${EATSSU_AWS_SECRET_KEY_DEV} - s3: - bucket: eatssu-bucket - region: - static: ap-northeast-2 - stack: - auto: false - -slack: - token: ${EATSSU_SLACK_TOKEN} - -swagger: - url: http://localhost:9000 - description: Test Server Swagger API - -springdoc: - swagger-ui: - path: /swagger-ui.html - groups-order: DESC - operationsSorter: method - disable-swagger-default-url: true - display-request-duration: true - api-docs: - path: /v3/api-docs - show-actuator: true - default-consumes-media-type: application/json - default-produces-media-type: application/json - paths-to-match: - - /** - -logging: - level: - root: INFO - com.zaxxer.hikari: INFO - -management: - endpoint: - metrics: - enabled: true - prometheus: - enabled: true - - endpoints: - web: - exposure: - include: health, info, metrics, prometheus diff --git a/src/main/resources/logback-spring.xml b/src/main/resources/logback-spring.xml new file mode 100644 index 00000000..28f81a9d --- /dev/null +++ b/src/main/resources/logback-spring.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + ${LOG_PATTERN} + + + + + + true + + + + + + + +