diff --git a/build.gradle b/build.gradle index 6cb75c6..bbef773 100644 --- a/build.gradle +++ b/build.gradle @@ -74,7 +74,7 @@ dependencies { implementation 'org.hibernate.orm:hibernate-core:6.2.5.Final' //QueryDSL - implementation 'com.github.gavlyukovskiy:p6spy-spring-boot-starter:1.9.0' +// implementation 'com.github.gavlyukovskiy:p6spy-spring-boot-starter:1.9.0' implementation 'com.querydsl:querydsl-jpa:5.0.0:jakarta' annotationProcessor "com.querydsl:querydsl-apt:5.0.0:jakarta" annotationProcessor "jakarta.annotation:jakarta.annotation-api" diff --git a/src/main/java/everymeal/server/global/exception/ExceptionList.java b/src/main/java/everymeal/server/global/exception/ExceptionList.java index 91efef9..bc15ffa 100644 --- a/src/main/java/everymeal/server/global/exception/ExceptionList.java +++ b/src/main/java/everymeal/server/global/exception/ExceptionList.java @@ -8,27 +8,33 @@ @Getter @RequiredArgsConstructor public enum ExceptionList { - MEAL_NOT_FOUND("M0001", HttpStatus.NOT_FOUND, "Meal Not Found"), - RESTAURANT_NOT_FOUND("M0002", HttpStatus.NOT_FOUND, "등록된 식당이 아닙니다."), - UNIVERSITY_NOT_FOUND("M0003", HttpStatus.NOT_FOUND, "등록된 학교가 아닙니다."), + MEAL_NOT_FOUND("MEL0001", HttpStatus.NOT_FOUND, "Meal Not Found"), + RESTAURANT_NOT_FOUND("MEL0002", HttpStatus.NOT_FOUND, "등록된 식당이 아닙니다."), + UNIVERSITY_NOT_FOUND("MEL0003", HttpStatus.NOT_FOUND, "등록된 학교가 아닙니다."), INVALID_MEAL_OFFEREDAT_REQUEST( - "M0004", HttpStatus.BAD_REQUEST, "동일한 데이터를 갖는 식단 데이터가 이미 존재합니다."), - INVALID_REQUEST("R0001", HttpStatus.BAD_REQUEST, "Request의 Data Type이 올바르지 않습니다."), + "MEL0004", HttpStatus.BAD_REQUEST, "동일한 데이터를 갖는 식단 데이터가 이미 존재합니다."), + INVALID_REQUEST("COM0001", HttpStatus.BAD_REQUEST, "Request의 Data Type이 올바르지 않습니다."), - USER_NOT_FOUND("U0001", HttpStatus.NOT_FOUND, "등록된 유저가 아닙니다."), - USER_AUTH_TIME_OUT("U0002", HttpStatus.FORBIDDEN, "인증 시간이 만료되었습니다."), - USER_AUTH_FAIL("U0003", HttpStatus.FORBIDDEN, "인증에 실패하였습니다."), - USER_ALREADY_EXIST("U0004", HttpStatus.CONFLICT, "이미 등록된 이메일입니다."), - NICKNAME_ALREADY_EXIST("U0005", HttpStatus.CONFLICT, "이미 등록된 닉네임입니다."), + USER_NOT_FOUND("USR0001", HttpStatus.NOT_FOUND, "등록된 유저가 아닙니다."), + USER_AUTH_TIME_OUT("USR0002", HttpStatus.FORBIDDEN, "인증 시간이 만료되었습니다."), + USER_AUTH_FAIL("USR0003", HttpStatus.FORBIDDEN, "인증에 실패하였습니다."), + USER_ALREADY_EXIST("USR0004", HttpStatus.CONFLICT, "이미 등록된 이메일입니다."), + NICKNAME_ALREADY_EXIST("USR0005", HttpStatus.CONFLICT, "이미 등록된 닉네임입니다."), - TOKEN_NOT_VALID("T0001", HttpStatus.NOT_ACCEPTABLE, "해당 토큰은 유효하지 않습니다."), - TOKEN_EXPIRATION("T0002", HttpStatus.FORBIDDEN, "토큰이 만료되었습니다."), + TOKEN_NOT_VALID("TKN0001", HttpStatus.NOT_ACCEPTABLE, "해당 토큰은 유효하지 않습니다."), + TOKEN_EXPIRATION("TKN0002", HttpStatus.FORBIDDEN, "토큰이 만료되었습니다."), REVIEW_NOT_FOUND("RV0001", HttpStatus.NOT_FOUND, "등록된 리뷰가 아닙니다."), REVIEW_UNAUTHORIZED("RV0002", HttpStatus.FORBIDDEN, "해당 리뷰에 대한 권한이 없습니다."), REVIEW_ALREADY_MARKED("RV0003", HttpStatus.CONFLICT, "이미 해당 리뷰에 대한 평가를 하였습니다."), REVIEW_MARK_NOT_FOUND("RV0004", HttpStatus.NOT_FOUND, "등록된 리뷰 평가가 아닙니다."), - STORE_NOT_FOUND("S0001", HttpStatus.NOT_FOUND, "등록된 가게가 아닙니다."), + STORE_NOT_FOUND("STR0001", HttpStatus.NOT_FOUND, "등록된 가게가 아닙니다."), + + // report + REPORT_NOT_FOUND("RPT0001", HttpStatus.NOT_FOUND, "등록된 신고가 아닙니다."), + REPORT_ALREADY_EXIST("RPT0002", HttpStatus.CONFLICT, "이미 신고한 리뷰입니다."), + REPORT_REVIEW_SELF("RPT0003", HttpStatus.FORBIDDEN, "자신의 리뷰를 신고할 수 없습니다."), + REPORT_REVIEW_ALREADY("RPT0004", HttpStatus.CONFLICT, "이미 신고한 리뷰입니다."), ; public final String CODE; diff --git a/src/main/java/everymeal/server/global/util/aws/S3Util.java b/src/main/java/everymeal/server/global/util/aws/S3Util.java index e6df463..4a2b32f 100644 --- a/src/main/java/everymeal/server/global/util/aws/S3Util.java +++ b/src/main/java/everymeal/server/global/util/aws/S3Util.java @@ -19,13 +19,19 @@ @Component public class S3Util { - private final AmazonS3 amazonS3; + public static AmazonS3 amazonS3; + public static String bucket; + public static String runningName; @Value("${cloud.aws.s3.bucket}") - private String bucket; + public void setBucket(String bucket) { + this.bucket = bucket; + } @Value("${running.name}") - private String runningName; + public void setRunningName(String runningName) { + this.runningName = runningName; + } public S3Util( @Value("${cloud.aws.credentials.access-key}") String accessKey, @@ -46,7 +52,7 @@ public URL getPresignedUrl(String fileName) { return amazonS3.generatePresignedUrl(request); } - public String getImgUrl(String fileName) { + public static String getImgUrl(String fileName) { URL url = amazonS3.getUrl(bucket, runningName + File.separator + fileName); return url.toString(); } diff --git a/src/main/java/everymeal/server/meal/repository/MealMapper.java b/src/main/java/everymeal/server/meal/repository/MealMapper.java index 58b43b9..b05f245 100644 --- a/src/main/java/everymeal/server/meal/repository/MealMapper.java +++ b/src/main/java/everymeal/server/meal/repository/MealMapper.java @@ -16,11 +16,6 @@ List> findDayList( @Param("universityName") String universityName, @Param("campusName") String campusName); - List> findWeekList( - @Param("weeklyDates") List weeklyDates, - @Param("universityName") String universityName, - @Param("campusName") String campusName); - List findAllByOfferedAtOnDateAndMealType( @Param("offeredAt") String offeredAt, @Param("mealType") String mealType, diff --git a/src/main/java/everymeal/server/meal/service/MealCommServiceImpl.java b/src/main/java/everymeal/server/meal/service/MealCommServiceImpl.java new file mode 100644 index 0000000..463e1ae --- /dev/null +++ b/src/main/java/everymeal/server/meal/service/MealCommServiceImpl.java @@ -0,0 +1,35 @@ +package everymeal.server.meal.service; + + +import everymeal.server.meal.entity.Meal; +import everymeal.server.meal.repository.MealDao; +import everymeal.server.meal.repository.MealMapper; +import java.util.List; +import java.util.Map; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@RequiredArgsConstructor +@Transactional(readOnly = true) +@Service +public class MealCommServiceImpl { + + private final MealDao mealDao; // JPQL DAO + private final MealMapper mealMapper; // MyBatis DAO + + public List getMealAllByOfferedAtOnDateAndMealType( + String offeredAt, String mealType, Long restaurantIdx) { + return mealMapper.findAllByOfferedAtOnDateAndMealType(offeredAt, mealType, restaurantIdx); + } + + @Transactional + public void saveAll(List mealList) { + mealDao.saveAll(mealList); + } + + public List> getDayList( + String offeredAt, String universityName, String campusName) { + return mealMapper.findDayList(offeredAt, universityName, campusName); + } +} diff --git a/src/main/java/everymeal/server/meal/service/MealServiceImpl.java b/src/main/java/everymeal/server/meal/service/MealServiceImpl.java index 6bc2373..d069e23 100644 --- a/src/main/java/everymeal/server/meal/service/MealServiceImpl.java +++ b/src/main/java/everymeal/server/meal/service/MealServiceImpl.java @@ -10,11 +10,6 @@ import everymeal.server.meal.controller.dto.response.RestaurantListGetRes; import everymeal.server.meal.entity.Meal; import everymeal.server.meal.entity.Restaurant; -import everymeal.server.meal.repository.MealDao; -import everymeal.server.meal.repository.MealMapper; -import everymeal.server.meal.repository.MealRepository; -import everymeal.server.university.entity.University; -import everymeal.server.university.service.UniversityService; import java.time.DayOfWeek; import java.time.LocalDate; import java.util.ArrayList; @@ -22,7 +17,6 @@ import java.util.List; import java.util.Map; import lombok.RequiredArgsConstructor; -import lombok.val; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -31,30 +25,9 @@ @Service public class MealServiceImpl implements MealService { - /** - * ============================================================================================ - * DI - * ============================================================================================ - */ - private final MealRepository mealRepository; // 기본 JPA 제공 DAO - - private final MealDao mealDao; // JPQL DAO - private final MealMapper mealMapper; // MyBatis DAO + private final MealCommServiceImpl mealCommServiceImpl; private final RestaurantService restaurantServiceImpl; - private final UniversityService universityServiceImpl; - /** - * ============================================================================================ - * 학식 식단 등록 ( 주간, 하루 모두 등록 가능 ) - * - * @param request 식단 등록 요청 DTO - * @return true - *

식당이 없는 경우, - * @throws ApplicationException 404 존재하지 않는 식당입니다.
- * REQ 데이터 중 offeredAt, Restaurant, MealType 이 동일한 데이터가 존재한다면, - * @throws ApplicationException 400 등록되어 있는 식단 데이터 보다 과거의 날짜로 등록할 수 없습니다.
- * ========================================================================================= - */ @Override @Transactional public Boolean createWeekMeal(WeekMealRegisterReq request) { @@ -66,8 +39,8 @@ public Boolean createWeekMeal(WeekMealRegisterReq request) { List mealList = new ArrayList<>(); for (MealRegisterReq req : request.registerReqList()) { // 제공날짜, 학생식당, 식사분류가 동일한 데이터가 이미 존재하면, 덮어쓰기 불가능 오류 - if (!mealMapper - .findAllByOfferedAtOnDateAndMealType( + if (!mealCommServiceImpl + .getMealAllByOfferedAtOnDateAndMealType( req.offeredAt().toString(), req.mealType(), request.restaurantIdx()) .isEmpty()) { throw new ApplicationException(ExceptionList.INVALID_MEAL_OFFEREDAT_REQUEST); @@ -77,34 +50,23 @@ public Boolean createWeekMeal(WeekMealRegisterReq request) { mealList.add(meal); } } - mealDao.saveAll(mealList); + mealCommServiceImpl.saveAll(mealList); return true; } - /** - * ============================================================================================ - * 학생식당 리스트 조회 - * - * @param universityName 학교 한글명 - * @param campusName 캠퍼스 이름 - * @return List - *

학교가 없는 경우, - * @throws ApplicationException 404 존재하지 않는 학교입니다.
- * ========================================================================================= - */ + @Override public List getRestaurantList(String universityName, String campusName) { - // 학교 등록 여부 판단 - University university = universityServiceImpl.getUniversity(universityName, campusName); // 학교와 식당 폐업 여부를 키로 조회 List restaurants = - restaurantServiceImpl.getAllByUniversityAndIsDeletedFalse(university); + restaurantServiceImpl.getAllByUniversityAndIsDeletedFalse( + universityName, campusName); return RestaurantListGetRes.of(restaurants); } @Override public Map>> getDayMealListV2( String universityName, String campusName, String offeredAt) { - var result = mealMapper.findDayList(offeredAt, universityName, campusName); + var result = mealCommServiceImpl.getDayList(offeredAt, universityName, campusName); return Map.of(offeredAt, DayMealGetRes.of(result)); } @@ -135,9 +97,10 @@ public List>>> getWeekMealList( } List>>> result = new ArrayList<>(); for (LocalDate i = monday; i.isBefore(sunday); i = i.plusDays(1)) { - val row = + Map> row = DayMealGetRes.of( - mealMapper.findDayList(i.toString(), universityName, campusName)); + mealCommServiceImpl.getDayList( + i.toString(), universityName, campusName)); result.add(Map.of(i.toString(), row)); } return result; diff --git a/src/main/java/everymeal/server/meal/service/RestaurantCommServiceImpl.java b/src/main/java/everymeal/server/meal/service/RestaurantCommServiceImpl.java new file mode 100644 index 0000000..ab06b88 --- /dev/null +++ b/src/main/java/everymeal/server/meal/service/RestaurantCommServiceImpl.java @@ -0,0 +1,40 @@ +package everymeal.server.meal.service; + +import static everymeal.server.global.exception.ExceptionList.RESTAURANT_NOT_FOUND; + +import everymeal.server.global.exception.ApplicationException; +import everymeal.server.meal.entity.Restaurant; +import everymeal.server.meal.repository.RestaurantRepository; +import everymeal.server.university.entity.University; +import java.util.List; +import java.util.Optional; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@RequiredArgsConstructor +@Transactional(readOnly = true) +@Service +public class RestaurantCommServiceImpl { + + private final RestaurantRepository restaurantRepository; + + public Optional getRestaurantOptionalEntity(Long restaurantIdx) { + return restaurantRepository.findById(restaurantIdx); + } + + public Restaurant getRestaurantEntity(Long restaurantIdx) { + return restaurantRepository + .findById(restaurantIdx) + .orElseThrow(() -> new ApplicationException(RESTAURANT_NOT_FOUND)); + } + + @Transactional + public Restaurant save(Restaurant restaurant) { + return restaurantRepository.save(restaurant); + } + + public List getAllByUniversityAndIsDeletedFalse(University university) { + return restaurantRepository.findAllByUniversityAndIsDeletedFalse(university); + } +} diff --git a/src/main/java/everymeal/server/meal/service/RestaurantService.java b/src/main/java/everymeal/server/meal/service/RestaurantService.java index 79a6bed..232463d 100644 --- a/src/main/java/everymeal/server/meal/service/RestaurantService.java +++ b/src/main/java/everymeal/server/meal/service/RestaurantService.java @@ -3,7 +3,6 @@ import everymeal.server.meal.controller.dto.request.RestaurantRegisterReq; import everymeal.server.meal.entity.Restaurant; -import everymeal.server.university.entity.University; import java.util.List; public interface RestaurantService { @@ -12,5 +11,5 @@ public interface RestaurantService { Boolean createRestaurant(RestaurantRegisterReq restaurantRegisterReq); - List getAllByUniversityAndIsDeletedFalse(University university); + List getAllByUniversityAndIsDeletedFalse(String universityName, String campusName); } diff --git a/src/main/java/everymeal/server/meal/service/RestaurantServiceImpl.java b/src/main/java/everymeal/server/meal/service/RestaurantServiceImpl.java index 4467be2..9776114 100644 --- a/src/main/java/everymeal/server/meal/service/RestaurantServiceImpl.java +++ b/src/main/java/everymeal/server/meal/service/RestaurantServiceImpl.java @@ -5,7 +5,6 @@ import everymeal.server.global.exception.ExceptionList; import everymeal.server.meal.controller.dto.request.RestaurantRegisterReq; import everymeal.server.meal.entity.Restaurant; -import everymeal.server.meal.repository.RestaurantRepository; import everymeal.server.university.entity.University; import everymeal.server.university.service.UniversityService; import java.util.List; @@ -18,13 +17,13 @@ @Service public class RestaurantServiceImpl implements RestaurantService { - private final RestaurantRepository restaurantRepository; + private final RestaurantCommServiceImpl restaurantCommServiceImpl; private final UniversityService universityServiceImpl; @Override public Restaurant getRestaurant(Long restaurantIdx) { - return restaurantRepository - .findById(restaurantIdx) + return restaurantCommServiceImpl + .getRestaurantOptionalEntity(restaurantIdx) .orElseThrow(() -> new ApplicationException(ExceptionList.RESTAURANT_NOT_FOUND)); } @@ -41,11 +40,15 @@ public Boolean createRestaurant(RestaurantRegisterReq restaurantRegisterReq) { .restaurantRegisterReq(restaurantRegisterReq) .university(university) .build(); - return restaurantRepository.save(restaurant).getIdx() != null; + return restaurantCommServiceImpl.save(restaurant).getIdx() != null; } @Override - public List getAllByUniversityAndIsDeletedFalse(University university) { - return restaurantRepository.findAllByUniversityAndIsDeletedFalse(university); + public List getAllByUniversityAndIsDeletedFalse( + String universityName, String campusName) { + // 학교 등록 여부 판단 + University university = universityServiceImpl.getUniversity(universityName, campusName); + + return restaurantCommServiceImpl.getAllByUniversityAndIsDeletedFalse(university); } } diff --git a/src/main/java/everymeal/server/report/controller/ReportController.java b/src/main/java/everymeal/server/report/controller/ReportController.java new file mode 100644 index 0000000..853f13b --- /dev/null +++ b/src/main/java/everymeal/server/report/controller/ReportController.java @@ -0,0 +1,57 @@ +package everymeal.server.report.controller; + + +import everymeal.server.global.dto.response.ApplicationResponse; +import everymeal.server.global.util.authresolver.Auth; +import everymeal.server.global.util.authresolver.AuthUser; +import everymeal.server.global.util.authresolver.entity.AuthenticatedUser; +import everymeal.server.report.dto.ReportDto; +import everymeal.server.report.service.ReportService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +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 io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +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; + +@RequestMapping("/api/v1/reports") +@RequiredArgsConstructor +@RestController +@Tag(name = "Report API", description = "리뷰 신고 관련 API입니다.") +public class ReportController { + + private final ReportService reportService; + + @PostMapping("/review/{reviewIdx}") + @Operation( + summary = "리뷰 신고", + description = + """ + 리뷰 신고를 진행합니다.
+ 로그인이 필요한 기능입니다. + """) + @ApiResponses({ + @ApiResponse( + responseCode = "200", + description = "리뷰 신고 성공", + content = @Content(schema = @Schema())), + }) + @Auth(require = true) + @SecurityRequirement(name = "jwt-user-auth") + public ApplicationResponse reportReview( + @Schema(description = "리뷰 아이디") @PathVariable Long reviewIdx, + @RequestBody @Valid ReportDto.ReportReviewReq request, + @Parameter(hidden = true) @AuthUser AuthenticatedUser user) { + return ApplicationResponse.ok( + reportService.reportReview(reviewIdx, request, user.getIdx())); + } +} diff --git a/src/main/java/everymeal/server/report/dto/ReportDto.java b/src/main/java/everymeal/server/report/dto/ReportDto.java new file mode 100644 index 0000000..1958c55 --- /dev/null +++ b/src/main/java/everymeal/server/report/dto/ReportDto.java @@ -0,0 +1,13 @@ +package everymeal.server.report.dto; + + +import everymeal.server.report.entity.ReportReason; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; + +public class ReportDto { + + public record ReportReviewReq( + @Schema(description = "리뷰 신고 사유를 입력해주세요.") @NotNull(message = "리뷰 신고 사유를 입력해주세요.") + ReportReason reason) {} +} diff --git a/src/main/java/everymeal/server/report/entity/Report.java b/src/main/java/everymeal/server/report/entity/Report.java new file mode 100644 index 0000000..480b5f5 --- /dev/null +++ b/src/main/java/everymeal/server/report/entity/Report.java @@ -0,0 +1,48 @@ +package everymeal.server.report.entity; + + +import everymeal.server.global.entity.BaseEntity; +import everymeal.server.review.entity.Review; +import everymeal.server.user.entity.User; +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.FetchType; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.Table; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@Entity(name = "reports") +@Table +@NoArgsConstructor +public class Report extends BaseEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long idx; + + @Enumerated(value = EnumType.STRING) + private ReportReason reason; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "reporter_idx") + private User reporter; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "review_idx") + private Review review; + + @Builder + public Report(ReportReason reason, User reporter, Review review) { + this.reason = reason; + this.reporter = reporter; + this.review = review; + } +} diff --git a/src/main/java/everymeal/server/report/entity/ReportReason.java b/src/main/java/everymeal/server/report/entity/ReportReason.java new file mode 100644 index 0000000..ee5f0c9 --- /dev/null +++ b/src/main/java/everymeal/server/report/entity/ReportReason.java @@ -0,0 +1,20 @@ +package everymeal.server.report.entity; + + +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor +public enum ReportReason { + UNRELATED("해당 식당과 무관한 리뷰"), + PROFANITY("비속어 및 혐오 발언"), + PORNOGRAPHY("음란성 게시물"), + ETC("기타"); + + private String value; + + ReportReason(String value) { + this.value = value; + } +} diff --git a/src/main/java/everymeal/server/report/repository/ReportRepository.java b/src/main/java/everymeal/server/report/repository/ReportRepository.java new file mode 100644 index 0000000..b11fe72 --- /dev/null +++ b/src/main/java/everymeal/server/report/repository/ReportRepository.java @@ -0,0 +1,17 @@ +package everymeal.server.report.repository; + + +import everymeal.server.report.entity.Report; +import java.util.Optional; +import org.apache.ibatis.annotations.Param; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; + +public interface ReportRepository extends JpaRepository { + + @Query( + value = + "select r from reports r where r.review.idx = :reviewIdx and r.reporter.idx = :userIdx") + Optional findByReviewIdxAndUserIdx( + @Param("reviewIdx") Long reviewIdx, @Param("userIdx") Long userIdx); +} diff --git a/src/main/java/everymeal/server/report/service/ReportCommServiceImpl.java b/src/main/java/everymeal/server/report/service/ReportCommServiceImpl.java new file mode 100644 index 0000000..84b748e --- /dev/null +++ b/src/main/java/everymeal/server/report/service/ReportCommServiceImpl.java @@ -0,0 +1,26 @@ +package everymeal.server.report.service; + + +import everymeal.server.report.entity.Report; +import everymeal.server.report.repository.ReportRepository; +import java.util.Optional; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@RequiredArgsConstructor +@Transactional(readOnly = true) +public class ReportCommServiceImpl { + + private final ReportRepository reportRepository; + + @Transactional + public Report save(Report report) { + return reportRepository.save(report); + } + + public Optional getReportOptionalEntity(Long reviewIdx, Long userIdx) { + return reportRepository.findByReviewIdxAndUserIdx(reviewIdx, userIdx); + } +} diff --git a/src/main/java/everymeal/server/report/service/ReportService.java b/src/main/java/everymeal/server/report/service/ReportService.java new file mode 100644 index 0000000..8bbc776 --- /dev/null +++ b/src/main/java/everymeal/server/report/service/ReportService.java @@ -0,0 +1,9 @@ +package everymeal.server.report.service; + + +import everymeal.server.report.dto.ReportDto.ReportReviewReq; + +public interface ReportService { + + Boolean reportReview(Long reviewIdx, ReportReviewReq request, Long userIdx); +} diff --git a/src/main/java/everymeal/server/report/service/ReportServiceImpl.java b/src/main/java/everymeal/server/report/service/ReportServiceImpl.java new file mode 100644 index 0000000..d5ac0f4 --- /dev/null +++ b/src/main/java/everymeal/server/report/service/ReportServiceImpl.java @@ -0,0 +1,53 @@ +package everymeal.server.report.service; + + +import everymeal.server.global.exception.ApplicationException; +import everymeal.server.global.exception.ExceptionList; +import everymeal.server.report.dto.ReportDto.ReportReviewReq; +import everymeal.server.report.entity.Report; +import everymeal.server.review.entity.Review; +import everymeal.server.review.service.ReviewCommServiceImpl; +import everymeal.server.user.entity.User; +import everymeal.server.user.service.UserCommServiceImpl; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@Transactional(readOnly = true) +@RequiredArgsConstructor +public class ReportServiceImpl implements ReportService { + + private final ReviewCommServiceImpl reviewCommServiceImpl; + private final UserCommServiceImpl userCommServiceImpl; + private final ReportCommServiceImpl reportCommServiceImpl; + + @Override + @Transactional + public Boolean reportReview(Long reviewIdx, ReportReviewReq request, Long userIdx) { + // 리뷰 조회 + Review review = reviewCommServiceImpl.getReviewEntity(reviewIdx); + // 신고 유저 조회 + User reportUser = userCommServiceImpl.getUserEntity(userIdx); + // 리뷰 작성 유저 조회 + User reviewUser = review.getUser(); + // 리뷰 작성 유저와 신고 유저가 같은지 확인 + if (reviewUser.equals(reportUser)) { + throw new ApplicationException(ExceptionList.REPORT_REVIEW_SELF); + } + // 리뷰 신고 여부 확인 + if (reportCommServiceImpl.getReportOptionalEntity(reviewIdx, userIdx).isPresent()) { + throw new ApplicationException(ExceptionList.REPORT_REVIEW_ALREADY); + } + // 리뷰 신고 저장 + Report report = + Report.builder() + .reporter(reportUser) + .review(review) + .reason(request.reason()) + .build(); + reportCommServiceImpl.save(report); + + return true; + } +} diff --git a/src/main/java/everymeal/server/review/dto/response/ReviewDto.java b/src/main/java/everymeal/server/review/dto/response/ReviewDto.java index 2cac7dd..5aa7c29 100644 --- a/src/main/java/everymeal/server/review/dto/response/ReviewDto.java +++ b/src/main/java/everymeal/server/review/dto/response/ReviewDto.java @@ -12,6 +12,7 @@ public class ReviewDto { public record ReviewTodayGetRes(Long reviewIdx, String content) {} public static ReviewTodayGetRes of(Map resultMap) { + if (resultMap == null) return null; return new ReviewTodayGetRes( (Long) resultMap.get("reviewIdx"), (String) resultMap.get("content")); } diff --git a/src/main/java/everymeal/server/review/entity/Image.java b/src/main/java/everymeal/server/review/entity/Image.java index ceae563..3fdf73d 100644 --- a/src/main/java/everymeal/server/review/entity/Image.java +++ b/src/main/java/everymeal/server/review/entity/Image.java @@ -2,7 +2,6 @@ import everymeal.server.global.entity.BaseEntity; -import everymeal.server.store.entity.Store; import jakarta.persistence.Entity; import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; @@ -26,13 +25,14 @@ public class Image extends BaseEntity { private String imageUrl; - @ManyToOne private Store store; - private Boolean isDeleted; + @ManyToOne private Review review; + @Builder - public Image(String imageUrl) { + public Image(String imageUrl, Review review) { this.imageUrl = imageUrl; - isDeleted = false; + this.isDeleted = false; + this.review = review; } } diff --git a/src/main/java/everymeal/server/review/entity/Review.java b/src/main/java/everymeal/server/review/entity/Review.java index cb4e994..c0bbf49 100644 --- a/src/main/java/everymeal/server/review/entity/Review.java +++ b/src/main/java/everymeal/server/review/entity/Review.java @@ -3,6 +3,7 @@ import everymeal.server.global.entity.BaseEntity; import everymeal.server.meal.entity.Restaurant; +import everymeal.server.report.entity.Report; import everymeal.server.store.entity.Store; import everymeal.server.user.entity.User; import jakarta.persistence.CascadeType; @@ -65,6 +66,9 @@ public class Review extends BaseEntity { @JoinColumn(name = "review_idx") private List images = new ArrayList<>(); + @OneToMany(cascade = CascadeType.ALL) + private Set reports = new HashSet<>(); + @Builder public Review( String content, diff --git a/src/main/java/everymeal/server/review/repository/ImageRepository.java b/src/main/java/everymeal/server/review/repository/ImageRepository.java new file mode 100644 index 0000000..b9a8ffa --- /dev/null +++ b/src/main/java/everymeal/server/review/repository/ImageRepository.java @@ -0,0 +1,25 @@ +package everymeal.server.review.repository; + + +import everymeal.server.review.entity.Image; +import java.util.List; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; +import org.springframework.stereotype.Repository; + +@Repository +public interface ImageRepository extends JpaRepository { + + @Query( + value = + """ + SELECT i + FROM images i + LEFT JOIN reviews r ON i.review.idx = r.idx and r.store.idx = :storeIdx AND r.isDeleted = false + WHERE i.isDeleted = false + ORDER BY i.createdAt DESC + LIMIT 5 + """) + List getStoreImages(@Param(value = "storeIdx") Long storeIdx); +} diff --git a/src/main/java/everymeal/server/review/service/ReviewCommServiceImpl.java b/src/main/java/everymeal/server/review/service/ReviewCommServiceImpl.java new file mode 100644 index 0000000..fcb3dd0 --- /dev/null +++ b/src/main/java/everymeal/server/review/service/ReviewCommServiceImpl.java @@ -0,0 +1,72 @@ +package everymeal.server.review.service; + + +import everymeal.server.global.exception.ApplicationException; +import everymeal.server.global.exception.ExceptionList; +import everymeal.server.review.dto.response.ReviewDto; +import everymeal.server.review.dto.response.ReviewDto.ReviewPagingVOWithCnt; +import everymeal.server.review.entity.Review; +import everymeal.server.review.entity.ReviewMark; +import everymeal.server.review.repository.ReviewMapper; +import everymeal.server.review.repository.ReviewMarkRepository; +import everymeal.server.review.repository.ReviewRepository; +import everymeal.server.user.entity.User; +import java.util.Map; +import java.util.Optional; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@RequiredArgsConstructor +@Transactional(readOnly = true) +public class ReviewCommServiceImpl { + + private final ReviewRepository reviewRepository; + private final ReviewMarkRepository reviewMarkRepository; + private final ReviewMapper reviewMapper; + + @Transactional + public Review save(Review review) { + return reviewRepository.save(review); + } + + public Review getReviewEntity(Long reviewIdx) { + return reviewRepository + .findById(reviewIdx) + .orElseThrow(() -> new ApplicationException(ExceptionList.REVIEW_NOT_FOUND)); + } + + public Review getReviewEntity(Long reviewIdx, User user) { + return reviewRepository + .findByIdxAndUser(reviewIdx, user) + .orElseThrow(() -> new ApplicationException(ExceptionList.REVIEW_NOT_FOUND)); + } + + public Optional getReviewOptionalEntity(Long reviewIdx) { + return reviewRepository.findById(reviewIdx); + } + + public ReviewPagingVOWithCnt getReviewPagingVOWithCnt(ReviewDto.ReviewQueryParam queryParam) { + return reviewRepository.getReview(queryParam); + } + + public Optional getReviewMarkOptionalEntity(Long reviewIdx, Long userIdx) { + return reviewMarkRepository.findByReviewIdxAndUserIdx(reviewIdx, userIdx); + } + + @Transactional + public void deleteReviewMark(ReviewMark reviewMark) { + reviewMarkRepository.delete(reviewMark); + } + + public Map getTodayReviewEntityFromMapper( + Long restaurantIdx, String offeredAt) { + return reviewMapper.findTodayReview(restaurantIdx, offeredAt); + } + + @Transactional + public Review saveAndFlush(Review review) { + return reviewRepository.saveAndFlush(review); + } +} diff --git a/src/main/java/everymeal/server/review/service/ReviewServiceImpl.java b/src/main/java/everymeal/server/review/service/ReviewServiceImpl.java index d3c4391..8a5c484 100644 --- a/src/main/java/everymeal/server/review/service/ReviewServiceImpl.java +++ b/src/main/java/everymeal/server/review/service/ReviewServiceImpl.java @@ -1,16 +1,13 @@ package everymeal.server.review.service; -import static everymeal.server.global.exception.ExceptionList.RESTAURANT_NOT_FOUND; import static everymeal.server.global.exception.ExceptionList.REVIEW_ALREADY_MARKED; import static everymeal.server.global.exception.ExceptionList.REVIEW_MARK_NOT_FOUND; -import static everymeal.server.global.exception.ExceptionList.REVIEW_NOT_FOUND; import static everymeal.server.global.exception.ExceptionList.REVIEW_UNAUTHORIZED; -import static everymeal.server.global.exception.ExceptionList.USER_NOT_FOUND; import everymeal.server.global.exception.ApplicationException; import everymeal.server.global.util.TimeFormatUtil; import everymeal.server.meal.entity.Restaurant; -import everymeal.server.meal.repository.RestaurantRepository; +import everymeal.server.meal.service.RestaurantCommServiceImpl; import everymeal.server.review.dto.request.ReviewCreateReq; import everymeal.server.review.dto.response.ReviewDto; import everymeal.server.review.dto.response.ReviewDto.ReviewGetRes; @@ -18,16 +15,11 @@ import everymeal.server.review.dto.response.ReviewDto.ReviewTodayGetRes; import everymeal.server.review.entity.Image; import everymeal.server.review.entity.Review; -import everymeal.server.review.repository.ReviewMapper; -import everymeal.server.review.repository.ReviewMarkRepository; -import everymeal.server.review.repository.ReviewRepository; import everymeal.server.user.entity.User; -import everymeal.server.user.repository.UserRepository; +import everymeal.server.user.service.UserCommServiceImpl; import java.util.ArrayList; import java.util.List; import lombok.RequiredArgsConstructor; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -36,29 +28,20 @@ @Transactional(readOnly = true) public class ReviewServiceImpl implements ReviewService { - private final RestaurantRepository restaurantRepository; - private final ReviewRepository reviewRepository; - private final UserRepository userRepository; - private final Logger logger = LoggerFactory.getLogger(ReviewServiceImpl.class); - - private final ReviewMarkRepository reviewMarkRepository; - private final ReviewMapper reviewMapper; + private final RestaurantCommServiceImpl restaurantCommServiceImpl; + private final UserCommServiceImpl userCommServiceImpl; + private final ReviewCommServiceImpl reviewCommServiceImpl; @Override @Transactional public Long createReview(ReviewCreateReq request, Long userIdx) { // (1) restaurant 객체 조회 Restaurant restaurant = - restaurantRepository - .findById(request.restaurantIdx()) - .orElseThrow(() -> new ApplicationException(RESTAURANT_NOT_FOUND)); + restaurantCommServiceImpl.getRestaurantEntity(request.restaurantIdx()); // (2) 이미지 주소 <> 이미지 객체 치환 List imageList = getImageFromString(request.imageList()); - User user = - userRepository - .findById(userIdx) - .orElseThrow(() -> new ApplicationException(USER_NOT_FOUND)); + User user = userCommServiceImpl.getUserEntity(userIdx); // (3) Entity 생성 ( 사진리뷰인지 분기 처리 ) Review review = @@ -80,7 +63,7 @@ public Long createReview(ReviewCreateReq request, Long userIdx) { review.updateTodayReview(true); } // (4) 저장 - Review savedReview = reviewRepository.save(review); + Review savedReview = reviewCommServiceImpl.save(review); restaurant.addGrade(request.grade()); return savedReview.getIdx(); @@ -90,16 +73,11 @@ public Long createReview(ReviewCreateReq request, Long userIdx) { @Transactional public Long updateReview(ReviewCreateReq request, Long userIdx, Long reviewIdx) { // (1) 기존 리뷰 조회 - Review review = - reviewRepository - .findById(reviewIdx) - .orElseThrow(() -> new ApplicationException(REVIEW_NOT_FOUND)); + Review review = reviewCommServiceImpl.getReviewEntity(reviewIdx); + // (2) 이미지 주소 <> 이미지 객체 치환 List imageList = getImageFromString(request.imageList()); - User user = - userRepository - .findById(userIdx) - .orElseThrow(() -> new ApplicationException(USER_NOT_FOUND)); + User user = userCommServiceImpl.getUserEntity(userIdx); if (review.getUser() != user) { throw new ApplicationException(REVIEW_UNAUTHORIZED); } @@ -115,22 +93,25 @@ public Long updateReview(ReviewCreateReq request, Long userIdx, Long reviewIdx) @Transactional public Boolean deleteReview(Long userIdx, Long reviewIdx) { // (1) 기존 리뷰 조회 - User user = - userRepository - .findById(userIdx) - .orElseThrow(() -> new ApplicationException(USER_NOT_FOUND)); - Review review = - reviewRepository - .findByIdxAndUser(reviewIdx, user) - .orElseThrow(() -> new ApplicationException(REVIEW_NOT_FOUND)); + User user = userCommServiceImpl.getUserEntity(userIdx); + Review review = reviewCommServiceImpl.getReviewEntity(reviewIdx, user); // (2) 기존 데이터 삭제 review.getRestaurant().removeGrade(review.getGrade()); review.deleteEntity(); + + reviewCommServiceImpl + .getReviewMarkOptionalEntity(reviewIdx, userIdx) + .ifPresent( + reviewMark -> { + review.removeMark(user); + reviewCommServiceImpl.deleteReviewMark(reviewMark); + }); + return true; } public ReviewGetRes getReviewWithNoOffSetPaging(ReviewDto.ReviewQueryParam queryParam) { - var result = reviewRepository.getReview(queryParam); + var result = reviewCommServiceImpl.getReviewPagingVOWithCnt(queryParam); List reviewPagingList = new ArrayList<>(); for (Review vo : result.reviewList()) { List strImgList = new ArrayList<>(); @@ -156,33 +137,27 @@ public ReviewGetRes getReviewWithNoOffSetPaging(ReviewDto.ReviewQueryParam query @Transactional public Boolean markReview(Long reviewIdx, boolean isLike, Long userIdx) { // (1) 기존 리뷰 조회 - Review review = - reviewRepository - .findById(reviewIdx) - .orElseThrow(() -> new ApplicationException(REVIEW_NOT_FOUND)); - User user = - userRepository - .findById(userIdx) - .orElseThrow(() -> new ApplicationException(USER_NOT_FOUND)); + Review review = reviewCommServiceImpl.getReviewEntity(reviewIdx); + User user = userCommServiceImpl.getUserEntity(userIdx); // (2) 기존 데이터 수정 if (isLike) { - reviewMarkRepository - .findByReviewIdxAndUserIdx(reviewIdx, userIdx) + reviewCommServiceImpl + .getReviewMarkOptionalEntity(reviewIdx, userIdx) .ifPresentOrElse( reviewMark -> { throw new ApplicationException(REVIEW_ALREADY_MARKED); }, () -> { review.addMark(user); - reviewRepository.saveAndFlush(review); + reviewCommServiceImpl.saveAndFlush(review); }); } else { - reviewMarkRepository - .findByReviewIdxAndUserIdx(reviewIdx, userIdx) + reviewCommServiceImpl + .getReviewMarkOptionalEntity(reviewIdx, userIdx) .ifPresentOrElse( reviewMark -> { review.removeMark(user); - reviewMarkRepository.delete(reviewMark); + reviewCommServiceImpl.deleteReviewMark(reviewMark); }, () -> { throw new ApplicationException(REVIEW_MARK_NOT_FOUND); @@ -193,7 +168,8 @@ public Boolean markReview(Long reviewIdx, boolean isLike, Long userIdx) { @Override public ReviewTodayGetRes getTodayReview(Long restaurantIdx, String offeredAt) { - return ReviewDto.of(reviewMapper.findTodayReview(restaurantIdx, offeredAt)); + return ReviewDto.of( + reviewCommServiceImpl.getTodayReviewEntityFromMapper(restaurantIdx, offeredAt)); } /** diff --git a/src/main/java/everymeal/server/store/controller/StoreController.java b/src/main/java/everymeal/server/store/controller/StoreController.java index 5cea1dd..b02c7fc 100644 --- a/src/main/java/everymeal/server/store/controller/StoreController.java +++ b/src/main/java/everymeal/server/store/controller/StoreController.java @@ -40,7 +40,7 @@ public class StoreController { private final StoreService storeService; @Auth(require = false) - @GetMapping("/{campusIdx}") + @GetMapping("/campus/{campusIdx}") @SecurityRequirement(name = "jwt-user-auth") @Operation(summary = "학교 주변 식당 조회", description = "학교 주변 식당을 조회합니다") public ApplicationResponse> getStores( @@ -79,7 +79,11 @@ public ApplicationResponse> getStores( "recommend", "restaurant", "cafe", - "bar" + "bar", + "korean", + "chinese", + "japanese", + "western", }) String group, @Parameter(hidden = true) @AuthUser AuthenticatedUser authenticatedUser, @@ -187,4 +191,18 @@ public ApplicationResponse> getStoresKeyword( authenticatedUser.getIdx(), PageRequest.of(offset, limit))); } + + @Auth(require = false) + @GetMapping("/{index}") + @SecurityRequirement(name = "jwt-user-auth") + @Operation(summary = "식당 상세 조회", description = "식당 상세 정보를 조회합니다") + public ApplicationResponse getStore( + @PathVariable(value = "index") + @Schema(title = "식당 키 값", description = "식당 키 값", example = "1") + Long storeIdx, + @Parameter(hidden = true) @AuthUser AuthenticatedUser authenticatedUser) { + return ApplicationResponse.ok( + storeService.getStore( + storeIdx, authenticatedUser == null ? null : authenticatedUser.getIdx())); + } } diff --git a/src/main/java/everymeal/server/store/controller/dto/response/StoreGetDetailRes.java b/src/main/java/everymeal/server/store/controller/dto/response/StoreGetDetailRes.java new file mode 100644 index 0000000..3e61645 --- /dev/null +++ b/src/main/java/everymeal/server/store/controller/dto/response/StoreGetDetailRes.java @@ -0,0 +1,19 @@ +package everymeal.server.store.controller.dto.response; + + +import java.util.List; + +public record StoreGetDetailRes( + Long idx, + String name, + String address, + String phoneNumber, + String categoryDetail, + Integer distance, + Integer x, + Integer y, + Double grade, + Integer reviewCount, + Integer recommendedCount, + List images, + Boolean isLiked) {} diff --git a/src/main/java/everymeal/server/store/controller/dto/response/StoreGetRes.java b/src/main/java/everymeal/server/store/controller/dto/response/StoreGetRes.java index 0c086a8..a788244 100644 --- a/src/main/java/everymeal/server/store/controller/dto/response/StoreGetRes.java +++ b/src/main/java/everymeal/server/store/controller/dto/response/StoreGetRes.java @@ -1,6 +1,9 @@ package everymeal.server.store.controller.dto.response; +import everymeal.server.global.util.aws.S3Util; +import everymeal.server.review.entity.Image; +import everymeal.server.store.entity.Store; import java.util.Arrays; import java.util.List; import java.util.Map; @@ -24,6 +27,7 @@ public static List of(List> stores) { List images = null; if (store.get("images") != null) { images = Arrays.asList(((String) store.get("images")).split(",")); + images.replaceAll(S3Util::getImgUrl); } boolean isLiked = false; Integer isLikedInt = (Integer) store.get("isLiked"); @@ -45,4 +49,19 @@ public static List of(List> stores) { }) .toList(); } + + public static StoreGetRes of(Store store, boolean isLike, List images) { + return new StoreGetRes( + store.getIdx(), + store.getName(), + store.getAddress(), + store.getPhone(), + store.getCategoryDetail(), + store.getDistance(), + store.getGradeStatistics().getGrade(), + store.getGradeStatistics().getReviewCount(), + store.getGradeStatistics().getRecommendedCount(), + images.stream().map(image -> S3Util.getImgUrl(image.getImageUrl())).toList(), + isLike); + } } diff --git a/src/main/java/everymeal/server/store/service/StoreService.java b/src/main/java/everymeal/server/store/service/StoreService.java index 4d726d9..aaa15ae 100644 --- a/src/main/java/everymeal/server/store/service/StoreService.java +++ b/src/main/java/everymeal/server/store/service/StoreService.java @@ -12,6 +12,8 @@ public interface StoreService { Page getStores( Long campusIdx, Pageable of, String group, Long userIdx, String order, Integer grade); + StoreGetRes getStore(Long storeIdx, Long userIdx); + Page getUserLikesStore( Long campusIdx, Pageable of, String group, Long userIdx); diff --git a/src/main/java/everymeal/server/store/service/StoreServiceImpl.java b/src/main/java/everymeal/server/store/service/StoreServiceImpl.java index dd51de8..9ff186f 100644 --- a/src/main/java/everymeal/server/store/service/StoreServiceImpl.java +++ b/src/main/java/everymeal/server/store/service/StoreServiceImpl.java @@ -4,6 +4,8 @@ import everymeal.server.global.exception.ApplicationException; import everymeal.server.global.exception.ExceptionList; +import everymeal.server.review.entity.Image; +import everymeal.server.review.repository.ImageRepository; import everymeal.server.store.controller.dto.response.LikedStoreGetRes; import everymeal.server.store.controller.dto.response.StoreGetRes; import everymeal.server.store.entity.Store; @@ -31,9 +33,10 @@ public class StoreServiceImpl implements StoreService { private final StoreMapper storeMapper; + private final StoreRepository storeRepository; private final LikeRepository likeRepository; + private final ImageRepository imageRepository; private final UserRepository userRepository; - private final StoreRepository storeRepository; private final StoreRepositoryCustom storeRepositoryCustom; @Override @@ -66,6 +69,20 @@ public Page getStores( return new PageImpl<>(result, pageable, count); } + @Override + public StoreGetRes getStore(Long storeIdx, Long userIdx) { + Store store = + storeRepository + .findById(storeIdx) + .orElseThrow(() -> new ApplicationException(STORE_NOT_FOUND)); + boolean isLike = false; + if (userIdx != null) { + isLike = likeRepository.findByUserIdxAndStoreIdx(userIdx, storeIdx).isPresent(); + } + List images = imageRepository.getStoreImages(storeIdx); + return StoreGetRes.of(store, isLike, images); + } + @Override public Page getUserLikesStore( Long campusIdx, Pageable pageable, String group, Long userIdx) { diff --git a/src/main/java/everymeal/server/user/entity/User.java b/src/main/java/everymeal/server/user/entity/User.java index d80c62e..a0310ab 100644 --- a/src/main/java/everymeal/server/user/entity/User.java +++ b/src/main/java/everymeal/server/user/entity/User.java @@ -2,6 +2,7 @@ import everymeal.server.global.entity.BaseEntity; +import everymeal.server.report.entity.Report; import everymeal.server.review.entity.Review; import everymeal.server.review.entity.ReviewMark; import everymeal.server.university.entity.University; @@ -57,6 +58,9 @@ public class User extends BaseEntity { @OneToOne(mappedBy = "user") private Withdrawal withdrawal; + @OneToMany(mappedBy = "reporter") + private Set reports = new HashSet<>(); + @Builder public User(String nickname, String email, String profileImgUrl, University university) { this.nickname = nickname; diff --git a/src/main/java/everymeal/server/user/repository/LikeRepository.java b/src/main/java/everymeal/server/user/repository/LikeRepository.java index c9eaac7..e74b604 100644 --- a/src/main/java/everymeal/server/user/repository/LikeRepository.java +++ b/src/main/java/everymeal/server/user/repository/LikeRepository.java @@ -8,5 +8,8 @@ import org.springframework.data.jpa.repository.JpaRepository; public interface LikeRepository extends JpaRepository { + + Optional findByUserIdxAndStoreIdx(Long userIdx, Long storeIdx); + Optional findByUserAndStore(User user, Store store); } diff --git a/src/main/java/everymeal/server/user/service/UserCommServiceImpl.java b/src/main/java/everymeal/server/user/service/UserCommServiceImpl.java new file mode 100644 index 0000000..6901307 --- /dev/null +++ b/src/main/java/everymeal/server/user/service/UserCommServiceImpl.java @@ -0,0 +1,44 @@ +package everymeal.server.user.service; + + +import everymeal.server.global.exception.ApplicationException; +import everymeal.server.global.exception.ExceptionList; +import everymeal.server.user.entity.User; +import everymeal.server.user.repository.UserRepository; +import java.util.Optional; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@RequiredArgsConstructor +@Transactional(readOnly = true) +public class UserCommServiceImpl { + + private final UserRepository userRepository; + + public User getUserEntity(Long userIdx) { + return userRepository + .findById(userIdx) + .orElseThrow(() -> new ApplicationException(ExceptionList.USER_NOT_FOUND)); + } + + public Optional getUserOptionalEntityByEmail(String email) { + return userRepository.findByEmail(email); + } + + public User getUserEntityByEmail(String email) { + return userRepository + .findByEmail(email) + .orElseThrow(() -> new ApplicationException(ExceptionList.USER_NOT_FOUND)); + } + + public Optional getUserOptionalEntityByNickname(String nickname) { + return userRepository.findByNickname(nickname); + } + + @Transactional + public User save(User user) { + return userRepository.save(user); + } +} diff --git a/src/main/java/everymeal/server/user/service/UserServiceImpl.java b/src/main/java/everymeal/server/user/service/UserServiceImpl.java index 6f97fe9..7cc71ed 100644 --- a/src/main/java/everymeal/server/user/service/UserServiceImpl.java +++ b/src/main/java/everymeal/server/user/service/UserServiceImpl.java @@ -21,8 +21,6 @@ import everymeal.server.user.entity.Withdrawal; import everymeal.server.user.entity.WithdrawalReason; import everymeal.server.user.repository.UserMapper; -import everymeal.server.user.repository.UserRepository; -import everymeal.server.user.repository.WithdrawalRepository; import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; import java.util.Map; @@ -38,12 +36,13 @@ public class UserServiceImpl implements UserService { private final UniversityRepository universityRepository; - private final UserRepository userRepository; private final JwtUtil jwtUtil; private final MailUtil mailUtil; private final S3Util s3Util; private final UserMapper userMapper; - private final WithdrawalRepository withdrawalRepository; + private final WithdrawalServiceImpl withdrawalServiceImpl; + + private final UserCommServiceImpl userCommServiceImpl; @Override @Transactional @@ -53,10 +52,10 @@ public UserLoginRes signUp(UserEmailSingReq request) { throw new ApplicationException(ExceptionList.USER_AUTH_FAIL); } String email = jwtUtil.getEmailTokenFromEmail(request.emailAuthToken()); - if (userRepository.findByEmail(email).isPresent()) { + if (userCommServiceImpl.getUserOptionalEntityByEmail(email).isPresent()) { throw new ApplicationException(ExceptionList.USER_ALREADY_EXIST); } - if (userRepository.findByNickname(request.nickname()).isPresent()) { + if (userCommServiceImpl.getUserOptionalEntityByNickname(request.nickname()).isPresent()) { throw new ApplicationException(ExceptionList.NICKNAME_ALREADY_EXIST); } @@ -72,7 +71,7 @@ public UserLoginRes signUp(UserEmailSingReq request) { .profileImgUrl(request.profileImgKey()) .university(university) .build(); - userRepository.save(user); + userCommServiceImpl.save(user); String accessToken = jwtUtil.generateAccessToken(user.getIdx()); String refreshToken = jwtUtil.generateRefreshToken(user.getIdx(), accessToken); @@ -90,10 +89,7 @@ public UserLoginRes login(UserEmailLoginReq request) { throw new ApplicationException(ExceptionList.USER_AUTH_FAIL); } String email = jwtUtil.getEmailTokenFromEmail(request.emailAuthToken()); - User user = - userRepository - .findByEmail(email) - .orElseThrow(() -> new ApplicationException(ExceptionList.USER_NOT_FOUND)); + User user = userCommServiceImpl.getUserEntityByEmail(email); String accessToken = jwtUtil.generateAccessToken(user.getIdx()); String refreshToken = jwtUtil.generateRefreshToken(user.getIdx(), accessToken); return new UserLoginRes( @@ -154,7 +150,7 @@ public Boolean verifyEmailAuth(String emailAuthToken, String emailAuthValue) { @Override public Boolean checkUser(String email) { - return userRepository.findByEmail(email).isPresent(); + return userCommServiceImpl.getUserOptionalEntityByEmail(email).isPresent(); } @Override @@ -168,12 +164,10 @@ public UserProfileRes getUserProfile(AuthenticatedUser authenticatedUser) { @Transactional public Boolean updateUserProfile( AuthenticatedUser authenticatedUser, UserProfileUpdateReq request) { - User user = - userRepository - .findById(authenticatedUser.getIdx()) - .orElseThrow(() -> new ApplicationException(ExceptionList.USER_NOT_FOUND)); + User user = userCommServiceImpl.getUserEntity(authenticatedUser.getIdx()); // 닉네임 중복 검사 - Optional duplicatedNickName = userRepository.findByNickname(request.nickName()); + Optional duplicatedNickName = + userCommServiceImpl.getUserOptionalEntityByNickname(request.nickName()); if (duplicatedNickName.isPresent() && user != duplicatedNickName.get()) { throw new ApplicationException(ExceptionList.NICKNAME_ALREADY_EXIST); } @@ -184,10 +178,7 @@ public Boolean updateUserProfile( @Override @Transactional public Boolean withdrawal(AuthenticatedUser authenticatedUser, WithdrawalReq request) { - User user = - userRepository - .findById(authenticatedUser.getIdx()) - .orElseThrow(() -> new ApplicationException(ExceptionList.USER_NOT_FOUND)); + User user = userCommServiceImpl.getUserEntity(authenticatedUser.getIdx()); Withdrawal withdrawal; if (request.withdrawalReason() != WithdrawalReason.ETC) { // 기타를 제외한 경우 withdrawal = @@ -195,14 +186,15 @@ public Boolean withdrawal(AuthenticatedUser authenticatedUser, WithdrawalReq req .withdrawalReason(request.withdrawalReason()) .user(user) .build(); - } else // 기타를 선택한 경우 - withdrawal = + } else { // 기타를 선택한 경우 + withdrawal = Withdrawal.builder() .withdrawalReason(request.withdrawalReason()) .etcReason(request.etcReason()) .user(user) .build(); - withdrawalRepository.save(withdrawal); // 탈퇴 관련 정보 저장 + } + withdrawalServiceImpl.save(withdrawal); // 탈퇴 관련 정보 저장 user.setIsDeleted(); // 논리 삭제 return true; } diff --git a/src/main/java/everymeal/server/user/service/WithdrawalServiceImpl.java b/src/main/java/everymeal/server/user/service/WithdrawalServiceImpl.java new file mode 100644 index 0000000..8cd9980 --- /dev/null +++ b/src/main/java/everymeal/server/user/service/WithdrawalServiceImpl.java @@ -0,0 +1,21 @@ +package everymeal.server.user.service; + + +import everymeal.server.user.entity.Withdrawal; +import everymeal.server.user.repository.WithdrawalRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@RequiredArgsConstructor +@Transactional(readOnly = true) +public class WithdrawalServiceImpl { + + private final WithdrawalRepository withdrawalRepository; + + @Transactional + public void save(Withdrawal withdrawal) { + withdrawalRepository.save(withdrawal); + } +} diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 2cf1026..845e287 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -41,7 +41,7 @@ springdoc: api-docs: groups: enabled: true - enabled: true +# enabled: true mybatis: type-aliases-package: everymeal.server diff --git a/src/main/resources/mybatis/mappers/MealMapper.xml b/src/main/resources/mybatis/mappers/MealMapper.xml index 3161aab..7049077 100644 --- a/src/main/resources/mybatis/mappers/MealMapper.xml +++ b/src/main/resources/mybatis/mappers/MealMapper.xml @@ -34,40 +34,6 @@ ORDER BY r.name ASC, operatingTime ASC -