diff --git a/.cursor/rules/eatssu-server-rule.mdc b/.cursor/rules/eatssu-server-rule.mdc new file mode 100644 index 00000000..c83a97fc --- /dev/null +++ b/.cursor/rules/eatssu-server-rule.mdc @@ -0,0 +1,27 @@ +--- +description: +globs: +alwaysApply: true +--- +# Copilot instructions + +- You must respond in Korean for all answers, regardless of the input language. +- Do not write any comments in code blocks, even if asked to do so. +- Do not use wildcard when importing libraries +- When you write reviews for pull request, specific review will very helpful for my team. +- Suggest based on logical reasons. + +## Commenting Convention + +- Comments should be written in a single line whenever possible. +- Each comment must include the author's name and the date. +- Use IntelliJ's default `FIX ME` comment format. + - Use this for parts of the code where there may be a potential issue but immediate exception handling is unnecessary. + - Also use this for parts that are not yet finalized due to unclear business requirements. + +## Test Code Convention + +- Test method names must be written in **English**. +- But if the codes is related to test, write the comment for "given, when, then". + +Please follow these conventions when reviewing or suggesting changes to the code. \ No newline at end of file diff --git a/.gemini/config.yaml b/.gemini/config.yaml new file mode 100644 index 00000000..e8560858 --- /dev/null +++ b/.gemini/config.yaml @@ -0,0 +1,10 @@ +have_fun: true +code_review: + disable: false + comment_severity_threshold: MEDIUM + max_review_comments: -1 + pull_request_opened: + help: false + summary: true + code_review: true +ignore_patterns: [] \ No newline at end of file diff --git a/.gemini/styleguide.md b/.gemini/styleguide.md new file mode 100644 index 00000000..2cde6b6e --- /dev/null +++ b/.gemini/styleguide.md @@ -0,0 +1,14 @@ +- You must respond in Korean for all answers, regardless of the input language. +- Do not write any comments in code blocks, even if asked to do so. +- Do not use wildcard when importing libraries +- When you write reviews for pull request, specific review will very helpful for my team. +- Suggest based on logical reasons. + +- Comments should be written in a single line whenever possible. +- Each comment must include the author's name and the date. +- Use IntelliJ's default `FIX ME` comment format. + - Use this for parts of the code where there may be a potential issue but immediate exception handling is unnecessary. + - Also use this for parts that are not yet finalized due to unclear business requirements. + +- Test method names must be written in **English**. +- But if the codes is related to test, write the comment for "given, when, then". diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md new file mode 100644 index 00000000..e74ab612 --- /dev/null +++ b/.github/copilot-instructions.md @@ -0,0 +1,22 @@ +# Copilot instructions + +- You must respond in Korean for all answers, regardless of the input language. +- Do not write any comments in code blocks, even if asked to do so. +- Do not use wildcard when importing libraries +- When you write reviews for pull request, specific review will very helpful for my team. +- Suggest based on logical reasons. + +## Commenting Convention + +- Comments should be written in a single line whenever possible. +- Each comment must include the author's name and the date. +- Use IntelliJ's default `FIX ME` comment format. + - Use this for parts of the code where there may be a potential issue but immediate exception handling is unnecessary. + - Also use this for parts that are not yet finalized due to unclear business requirements. + +## Test Code Convention + +- Test method names must be written in **English**. +- But if the codes is related to test, write the comment for "given, when, then". + +Please follow these conventions when reviewing or suggesting changes to the code. \ No newline at end of file diff --git a/.gitignore b/.gitignore index 4ec9ead9..78ce332e 100644 --- a/.gitignore +++ b/.gitignore @@ -42,14 +42,5 @@ out/ ### Docker Tar files ### /eat-ssu.tar -### SDKMAN ### -.sdkmanrc - -### Junie guideline ### -.junie - -### Cursor guideline ### -eatssu-server-rule.mdc - -### Copilot guildline ### -copilot-instructions.md \ No newline at end of file +### env file ### +.env \ No newline at end of file diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 00000000..e74ab612 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,22 @@ +# Copilot instructions + +- You must respond in Korean for all answers, regardless of the input language. +- Do not write any comments in code blocks, even if asked to do so. +- Do not use wildcard when importing libraries +- When you write reviews for pull request, specific review will very helpful for my team. +- Suggest based on logical reasons. + +## Commenting Convention + +- Comments should be written in a single line whenever possible. +- Each comment must include the author's name and the date. +- Use IntelliJ's default `FIX ME` comment format. + - Use this for parts of the code where there may be a potential issue but immediate exception handling is unnecessary. + - Also use this for parts that are not yet finalized due to unclear business requirements. + +## Test Code Convention + +- Test method names must be written in **English**. +- But if the codes is related to test, write the comment for "given, when, then". + +Please follow these conventions when reviewing or suggesting changes to the code. \ No newline at end of file diff --git a/GEMINI.md b/GEMINI.md new file mode 100644 index 00000000..e74ab612 --- /dev/null +++ b/GEMINI.md @@ -0,0 +1,22 @@ +# Copilot instructions + +- You must respond in Korean for all answers, regardless of the input language. +- Do not write any comments in code blocks, even if asked to do so. +- Do not use wildcard when importing libraries +- When you write reviews for pull request, specific review will very helpful for my team. +- Suggest based on logical reasons. + +## Commenting Convention + +- Comments should be written in a single line whenever possible. +- Each comment must include the author's name and the date. +- Use IntelliJ's default `FIX ME` comment format. + - Use this for parts of the code where there may be a potential issue but immediate exception handling is unnecessary. + - Also use this for parts that are not yet finalized due to unclear business requirements. + +## Test Code Convention + +- Test method names must be written in **English**. +- But if the codes is related to test, write the comment for "given, when, then". + +Please follow these conventions when reviewing or suggesting changes to the code. \ No newline at end of file diff --git a/build.gradle b/build.gradle index 5094e40a..6cad98bb 100644 --- a/build.gradle +++ b/build.gradle @@ -4,9 +4,14 @@ plugins { id 'io.spring.dependency-management' version '1.1.0' } +java { + toolchain { + languageVersion.set(JavaLanguageVersion.of(17)) + } +} + group = 'ssu' version = '0.0.1-SNAPSHOT' -sourceCompatibility = '17' bootJar { archivesBaseName = 'EatSsu' @@ -31,49 +36,39 @@ dependencies { implementation 'org.springframework.boot:spring-boot-starter-security' implementation 'org.hibernate.validator:hibernate-validator:8.0.0.Final' - // Lombok annotationProcessor 'org.projectlombok:lombok' compileOnly 'org.projectlombok:lombok' developmentOnly 'org.springframework.boot:spring-boot-devtools' runtimeOnly 'com.mysql:mysql-connector-j' + testRuntimeOnly 'com.mysql:mysql-connector-j' testImplementation 'org.springframework.boot:spring-boot-starter-test' testImplementation 'org.springframework.security:spring-security-test' - - //openApi implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.2.0' - //jwt implementation group: 'io.jsonwebtoken', name: 'jjwt-api', version: '0.11.2' runtimeOnly group: 'io.jsonwebtoken', name: 'jjwt-impl', version: '0.11.2' runtimeOnly group: 'io.jsonwebtoken', name: 'jjwt-jackson', version: '0.11.2' - //httpClient implementation 'org.apache.httpcomponents.client5:httpclient5:5.3-alpha1' - // Querydsl 추가 implementation 'com.querydsl:querydsl-jpa:5.0.0:jakarta' annotationProcessor "com.querydsl:querydsl-apt:${dependencyManagement.importedProperties['querydsl.version']}:jakarta" annotationProcessor "jakarta.annotation:jakarta.annotation-api" annotationProcessor "jakarta.persistence:jakarta.persistence-api" - //S3 implementation 'org.springframework.cloud:spring-cloud-starter-aws:2.2.6.RELEASE' - //slack implementation("com.slack.api:bolt:1.35.0") implementation("com.slack.api:bolt-servlet:1.35.0") implementation("com.slack.api:bolt-jetty:1.35.0") implementation("org.slf4j:slf4j-simple:1.7.36") - - // Spring Actuator implementation 'org.springframework.boot:spring-boot-starter-actuator' - // Prometheus runtimeOnly 'io.micrometer:micrometer-registry-prometheus' } diff --git a/src/main/java/ssu/eatssu/EatSsuApplication.java b/src/main/java/ssu/eatssu/EatSsuApplication.java index 36cb653b..aa937ee8 100644 --- a/src/main/java/ssu/eatssu/EatSsuApplication.java +++ b/src/main/java/ssu/eatssu/EatSsuApplication.java @@ -8,8 +8,8 @@ @SpringBootApplication public class EatSsuApplication { - public static void main(String[] args) { - SpringApplication.run(EatSsuApplication.class, args); - } + public static void main(String[] args) { + SpringApplication.run(EatSsuApplication.class, args); + } } diff --git a/src/main/java/ssu/eatssu/domain/admin/controller/AuthenticationController.java b/src/main/java/ssu/eatssu/domain/admin/controller/AuthenticationController.java index 20c8b38a..8291506c 100644 --- a/src/main/java/ssu/eatssu/domain/admin/controller/AuthenticationController.java +++ b/src/main/java/ssu/eatssu/domain/admin/controller/AuthenticationController.java @@ -1,13 +1,12 @@ package ssu.eatssu.domain.admin.controller; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Controller; 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.ResponseBody; - -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; import ssu.eatssu.domain.admin.dto.LoginRequest; import ssu.eatssu.domain.admin.service.AuthenticationService; import ssu.eatssu.domain.user.dto.Tokens; @@ -19,12 +18,12 @@ @RequiredArgsConstructor public class AuthenticationController { - private final AuthenticationService authenticationService; + private final AuthenticationService authenticationService; - @ResponseBody - @PostMapping("/login") - public BaseResponse login(@RequestBody LoginRequest request) { - return BaseResponse.success(authenticationService.login(request)); - } + @ResponseBody + @PostMapping("/login") + public BaseResponse login(@RequestBody LoginRequest request) { + return BaseResponse.success(authenticationService.login(request)); + } } diff --git a/src/main/java/ssu/eatssu/domain/admin/controller/ManageCategoryController.java b/src/main/java/ssu/eatssu/domain/admin/controller/ManageCategoryController.java index c8b99234..66836d02 100644 --- a/src/main/java/ssu/eatssu/domain/admin/controller/ManageCategoryController.java +++ b/src/main/java/ssu/eatssu/domain/admin/controller/ManageCategoryController.java @@ -1,13 +1,12 @@ package ssu.eatssu.domain.admin.controller; +import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.ResponseBody; - -import lombok.RequiredArgsConstructor; import ssu.eatssu.domain.admin.dto.RegisterCategoryRequest; import ssu.eatssu.domain.admin.service.ManageCategoryService; import ssu.eatssu.domain.restaurant.entity.Restaurant; @@ -18,13 +17,13 @@ @RequiredArgsConstructor public class ManageCategoryController { - private final ManageCategoryService manageCategoryService; + private final ManageCategoryService manageCategoryService; - @ResponseBody - @PostMapping("/") - public BaseResponse register(@RequestParam Restaurant restaurant, - @RequestBody RegisterCategoryRequest request) { - manageCategoryService.register(restaurant, request); - return BaseResponse.success(); - } + @ResponseBody + @PostMapping("/") + public BaseResponse register(@RequestParam Restaurant restaurant, + @RequestBody RegisterCategoryRequest request) { + manageCategoryService.register(restaurant, request); + return BaseResponse.success(); + } } diff --git a/src/main/java/ssu/eatssu/domain/admin/controller/ManageFixMenuController.java b/src/main/java/ssu/eatssu/domain/admin/controller/ManageFixMenuController.java index 5acefb1d..8b2ac2aa 100644 --- a/src/main/java/ssu/eatssu/domain/admin/controller/ManageFixMenuController.java +++ b/src/main/java/ssu/eatssu/domain/admin/controller/ManageFixMenuController.java @@ -1,5 +1,6 @@ package ssu.eatssu.domain.admin.controller; +import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; @@ -10,8 +11,6 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.ResponseBody; - -import lombok.RequiredArgsConstructor; import ssu.eatssu.domain.admin.dto.MenuBoards; import ssu.eatssu.domain.admin.dto.RegisterFixMenuRequest; import ssu.eatssu.domain.admin.dto.UpdateFixMenuRequest; @@ -24,42 +23,42 @@ @RequiredArgsConstructor public class ManageFixMenuController { - private final ManageFixMenuService manageFixMenuService; + private final ManageFixMenuService manageFixMenuService; - @ResponseBody - @GetMapping("") - public BaseResponse fixMenuPage() { - MenuBoards menuBoards = manageFixMenuService.getMenuBoards(); - return BaseResponse.success(menuBoards); - } + @ResponseBody + @GetMapping("") + public BaseResponse fixMenuPage() { + MenuBoards menuBoards = manageFixMenuService.getMenuBoards(); + return BaseResponse.success(menuBoards); + } - @ResponseBody - @PostMapping("") - public BaseResponse register(@RequestParam Restaurant restaurant, - @RequestBody RegisterFixMenuRequest request) { - manageFixMenuService.register(restaurant, request); - return BaseResponse.success(); - } + @ResponseBody + @PostMapping("") + public BaseResponse register(@RequestParam Restaurant restaurant, + @RequestBody RegisterFixMenuRequest request) { + manageFixMenuService.register(restaurant, request); + return BaseResponse.success(); + } - @ResponseBody - @PatchMapping("/{menuId}") - public BaseResponse update(@PathVariable Long menuId, - @RequestBody UpdateFixMenuRequest request) { - manageFixMenuService.updateMenu(menuId, request); - return BaseResponse.success(); - } + @ResponseBody + @PatchMapping("/{menuId}") + public BaseResponse update(@PathVariable Long menuId, + @RequestBody UpdateFixMenuRequest request) { + manageFixMenuService.updateMenu(menuId, request); + return BaseResponse.success(); + } - @ResponseBody - @DeleteMapping("/{menuId}") - public BaseResponse delete(@PathVariable Long menuId) { - manageFixMenuService.delete(menuId); - return BaseResponse.success(); - } + @ResponseBody + @DeleteMapping("/{menuId}") + public BaseResponse delete(@PathVariable Long menuId) { + manageFixMenuService.delete(menuId); + return BaseResponse.success(); + } - @ResponseBody - @PatchMapping("/{menuId}/discontinued-status") - public BaseResponse toggleDiscontinuedStatus(@PathVariable Long menuId) { - return BaseResponse.success(manageFixMenuService.changeDiscontinuedStatus(menuId)); - } + @ResponseBody + @PatchMapping("/{menuId}/discontinued-status") + public BaseResponse toggleDiscontinuedStatus(@PathVariable Long menuId) { + return BaseResponse.success(manageFixMenuService.changeDiscontinuedStatus(menuId)); + } } diff --git a/src/main/java/ssu/eatssu/domain/admin/controller/ManageInquiryController.java b/src/main/java/ssu/eatssu/domain/admin/controller/ManageInquiryController.java index fe15516d..f12c4be3 100644 --- a/src/main/java/ssu/eatssu/domain/admin/controller/ManageInquiryController.java +++ b/src/main/java/ssu/eatssu/domain/admin/controller/ManageInquiryController.java @@ -1,5 +1,6 @@ package ssu.eatssu.domain.admin.controller; +import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Pageable; import org.springframework.data.web.PageableDefault; import org.springframework.stereotype.Controller; @@ -11,8 +12,6 @@ import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody; - -import lombok.RequiredArgsConstructor; import ssu.eatssu.domain.admin.dto.InquiryLine; import ssu.eatssu.domain.admin.dto.PageWrapper; import ssu.eatssu.domain.admin.dto.UpdateStatusRequest; @@ -23,27 +22,27 @@ @RequestMapping("/admin/inquiries") @RequiredArgsConstructor public class ManageInquiryController { - private final ManageInquiryService manageInquiryService; + private final ManageInquiryService manageInquiryService; - @ResponseBody - @GetMapping("") - public BaseResponse> inquiryPage(@PageableDefault(size = 20) Pageable pageable, - Model model) { - PageWrapper inquiryLinePage = manageInquiryService.getInquiryBoard(pageable); - return BaseResponse.success(inquiryLinePage); - } + @ResponseBody + @GetMapping("") + public BaseResponse> inquiryPage(@PageableDefault(size = 20) Pageable pageable, + Model model) { + PageWrapper inquiryLinePage = manageInquiryService.getInquiryBoard(pageable); + return BaseResponse.success(inquiryLinePage); + } - @ResponseBody - @PatchMapping("{inquiryId}/status") - public BaseResponse updateStatus(@PathVariable Long inquiryId, @RequestBody UpdateStatusRequest request) { - manageInquiryService.updateStatus(inquiryId, request); - return BaseResponse.success(); - } + @ResponseBody + @PatchMapping("{inquiryId}/status") + public BaseResponse updateStatus(@PathVariable Long inquiryId, @RequestBody UpdateStatusRequest request) { + manageInquiryService.updateStatus(inquiryId, request); + return BaseResponse.success(); + } - @ResponseBody - @DeleteMapping("{inquiryId}") - public BaseResponse delete(@PathVariable Long inquiryId) { - manageInquiryService.delete(inquiryId); - return BaseResponse.success(); - } + @ResponseBody + @DeleteMapping("{inquiryId}") + public BaseResponse delete(@PathVariable Long inquiryId) { + manageInquiryService.delete(inquiryId); + return BaseResponse.success(); + } } diff --git a/src/main/java/ssu/eatssu/domain/admin/controller/ManageMealController.java b/src/main/java/ssu/eatssu/domain/admin/controller/ManageMealController.java index e2b57669..943929eb 100644 --- a/src/main/java/ssu/eatssu/domain/admin/controller/ManageMealController.java +++ b/src/main/java/ssu/eatssu/domain/admin/controller/ManageMealController.java @@ -1,7 +1,6 @@ package ssu.eatssu.domain.admin.controller; -import java.util.Date; - +import lombok.RequiredArgsConstructor; import org.springframework.format.annotation.DateTimeFormat; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; @@ -13,8 +12,6 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.ResponseBody; - -import lombok.RequiredArgsConstructor; import ssu.eatssu.domain.admin.dto.MealInfo; import ssu.eatssu.domain.admin.dto.MenuBoards; import ssu.eatssu.domain.admin.dto.RegisterMealRequest; @@ -23,39 +20,41 @@ import ssu.eatssu.domain.restaurant.entity.Restaurant; import ssu.eatssu.global.handler.response.BaseResponse; +import java.util.Date; + @Controller @RequestMapping("/admin/meals") @RequiredArgsConstructor public class ManageMealController { - private final ManageMealService manageMealService; - - @ResponseBody - @GetMapping("") - public BaseResponse mealPage(@RequestParam @DateTimeFormat(pattern = "yyyyMMdd") Date date, - @RequestParam TimePart timePart, - Model model) { - MenuBoards menuBoards = manageMealService.getMenuBoards(date, timePart); - return BaseResponse.success(menuBoards); - } - - @ResponseBody - @PostMapping("") - public BaseResponse register(@RequestParam Restaurant restaurant, - @RequestParam @DateTimeFormat(pattern = "yyyyMMdd") Date date, - @RequestParam TimePart timePart, - @RequestBody RegisterMealRequest request, - Model model) { - MealInfo mealInfo = new MealInfo(restaurant, date, timePart); - manageMealService.register(mealInfo, request); - return BaseResponse.success(); - } - - @ResponseBody - @DeleteMapping("/{mealId}") - public BaseResponse delete(@PathVariable Long mealId) { - manageMealService.delete(mealId); - return BaseResponse.success(); - } + private final ManageMealService manageMealService; + + @ResponseBody + @GetMapping("") + public BaseResponse mealPage(@RequestParam @DateTimeFormat(pattern = "yyyyMMdd") Date date, + @RequestParam TimePart timePart, + Model model) { + MenuBoards menuBoards = manageMealService.getMenuBoards(date, timePart); + return BaseResponse.success(menuBoards); + } + + @ResponseBody + @PostMapping("") + public BaseResponse register(@RequestParam Restaurant restaurant, + @RequestParam @DateTimeFormat(pattern = "yyyyMMdd") Date date, + @RequestParam TimePart timePart, + @RequestBody RegisterMealRequest request, + Model model) { + MealInfo mealInfo = new MealInfo(restaurant, date, timePart); + manageMealService.register(mealInfo, request); + return BaseResponse.success(); + } + + @ResponseBody + @DeleteMapping("/{mealId}") + public BaseResponse delete(@PathVariable Long mealId) { + manageMealService.delete(mealId); + return BaseResponse.success(); + } } \ No newline at end of file diff --git a/src/main/java/ssu/eatssu/domain/admin/controller/ManageReportController.java b/src/main/java/ssu/eatssu/domain/admin/controller/ManageReportController.java index 4e04532e..5b870491 100644 --- a/src/main/java/ssu/eatssu/domain/admin/controller/ManageReportController.java +++ b/src/main/java/ssu/eatssu/domain/admin/controller/ManageReportController.java @@ -1,5 +1,6 @@ package ssu.eatssu.domain.admin.controller; +import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Pageable; import org.springframework.data.web.PageableDefault; import org.springframework.stereotype.Controller; @@ -9,8 +10,6 @@ import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody; - -import lombok.RequiredArgsConstructor; import ssu.eatssu.domain.admin.dto.PageWrapper; import ssu.eatssu.domain.admin.dto.ReportLine; import ssu.eatssu.domain.admin.service.ManageReportService; @@ -20,21 +19,21 @@ @RequestMapping("/admin/reports") @RequiredArgsConstructor public class ManageReportController { - private final ManageReportService manageReportService; + private final ManageReportService manageReportService; - @ResponseBody - @GetMapping("") - public BaseResponse> reportPage(@PageableDefault(size = 20) Pageable pageable, - Model model) { - PageWrapper reportLinePage = manageReportService.getReportBoard(pageable); - return BaseResponse.success(reportLinePage); - } + @ResponseBody + @GetMapping("") + public BaseResponse> reportPage(@PageableDefault(size = 20) Pageable pageable, + Model model) { + PageWrapper reportLinePage = manageReportService.getReportBoard(pageable); + return BaseResponse.success(reportLinePage); + } - @ResponseBody - @DeleteMapping("/{reportId}") - public BaseResponse delete(@PathVariable Long reportId) { - manageReportService.delete(reportId); - return BaseResponse.success(); - } + @ResponseBody + @DeleteMapping("/{reportId}") + public BaseResponse delete(@PathVariable Long reportId) { + manageReportService.delete(reportId); + return BaseResponse.success(); + } } diff --git a/src/main/java/ssu/eatssu/domain/admin/controller/ManageReviewController.java b/src/main/java/ssu/eatssu/domain/admin/controller/ManageReviewController.java index 612fcc3d..b0aa1c05 100644 --- a/src/main/java/ssu/eatssu/domain/admin/controller/ManageReviewController.java +++ b/src/main/java/ssu/eatssu/domain/admin/controller/ManageReviewController.java @@ -1,12 +1,11 @@ package ssu.eatssu.domain.admin.controller; +import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody; - -import lombok.RequiredArgsConstructor; import ssu.eatssu.domain.admin.service.ManageReviewService; import ssu.eatssu.global.handler.response.BaseResponse; @@ -14,12 +13,12 @@ @RequestMapping("/admin/reviews") @RequiredArgsConstructor public class ManageReviewController { - private final ManageReviewService manageReviewService; + private final ManageReviewService manageReviewService; - @ResponseBody - @DeleteMapping("/{reviewId}") - public BaseResponse delete(@PathVariable Long reviewId) { - manageReviewService.delete(reviewId); - return BaseResponse.success(); - } + @ResponseBody + @DeleteMapping("/{reviewId}") + public BaseResponse delete(@PathVariable Long reviewId) { + manageReviewService.delete(reviewId); + return BaseResponse.success(); + } } diff --git a/src/main/java/ssu/eatssu/domain/admin/dto/InquiryLine.java b/src/main/java/ssu/eatssu/domain/admin/dto/InquiryLine.java index 93bbc9d6..34158f29 100644 --- a/src/main/java/ssu/eatssu/domain/admin/dto/InquiryLine.java +++ b/src/main/java/ssu/eatssu/domain/admin/dto/InquiryLine.java @@ -1,12 +1,12 @@ package ssu.eatssu.domain.admin.dto; -import java.time.LocalDateTime; - import ssu.eatssu.domain.inquiry.entity.Inquiry; import ssu.eatssu.domain.inquiry.entity.InquiryStatus; +import java.time.LocalDateTime; + public record InquiryLine(Long inquiryId, LocalDateTime date, String email, String content, InquiryStatus status) { - public InquiryLine(Inquiry inquiry) { - this(inquiry.getId(), inquiry.getCreatedDate(), inquiry.getEmail(), inquiry.getContent(), inquiry.getStatus()); - } + public InquiryLine(Inquiry inquiry) { + this(inquiry.getId(), inquiry.getCreatedDate(), inquiry.getEmail(), inquiry.getContent(), inquiry.getStatus()); + } } diff --git a/src/main/java/ssu/eatssu/domain/admin/dto/MealInfo.java b/src/main/java/ssu/eatssu/domain/admin/dto/MealInfo.java index d6a015fc..56cc82b3 100644 --- a/src/main/java/ssu/eatssu/domain/admin/dto/MealInfo.java +++ b/src/main/java/ssu/eatssu/domain/admin/dto/MealInfo.java @@ -1,9 +1,9 @@ package ssu.eatssu.domain.admin.dto; -import java.util.Date; - import ssu.eatssu.domain.menu.entity.constants.TimePart; import ssu.eatssu.domain.restaurant.entity.Restaurant; +import java.util.Date; + public record MealInfo(Restaurant restaurant, Date date, TimePart timePart) { } diff --git a/src/main/java/ssu/eatssu/domain/admin/dto/MealSection.java b/src/main/java/ssu/eatssu/domain/admin/dto/MealSection.java index 44647533..b0095afd 100644 --- a/src/main/java/ssu/eatssu/domain/admin/dto/MealSection.java +++ b/src/main/java/ssu/eatssu/domain/admin/dto/MealSection.java @@ -5,16 +5,16 @@ public record MealSection(Long mealId, String title, List menuLines) implements SectionInBoard { - public MealSection(Long mealId, String title) { - this(mealId, title, new ArrayList<>()); - } + public MealSection(Long mealId, String title) { + this(mealId, title, new ArrayList<>()); + } - public MealSection(Long mealId) { - this(mealId, null, new ArrayList<>()); - } + public MealSection(Long mealId) { + this(mealId, null, new ArrayList<>()); + } - @Override - public void addMenuLine(MenuLine menuLine) { - menuLines.add(menuLine); - } + @Override + public void addMenuLine(MenuLine menuLine) { + menuLines.add(menuLine); + } } diff --git a/src/main/java/ssu/eatssu/domain/admin/dto/MenuBoard.java b/src/main/java/ssu/eatssu/domain/admin/dto/MenuBoard.java index 2d25bc3d..af033a63 100644 --- a/src/main/java/ssu/eatssu/domain/admin/dto/MenuBoard.java +++ b/src/main/java/ssu/eatssu/domain/admin/dto/MenuBoard.java @@ -4,11 +4,11 @@ import java.util.List; public record MenuBoard(String restaurantName, List sections) { - public MenuBoard(String restaurantName) { - this(restaurantName, new ArrayList<>()); - } + public MenuBoard(String restaurantName) { + this(restaurantName, new ArrayList<>()); + } - public void addSection(SectionInBoard section) { - sections.add(section); - } + public void addSection(SectionInBoard section) { + sections.add(section); + } } \ No newline at end of file diff --git a/src/main/java/ssu/eatssu/domain/admin/dto/MenuBoards.java b/src/main/java/ssu/eatssu/domain/admin/dto/MenuBoards.java index b4f95f19..8a65cecc 100644 --- a/src/main/java/ssu/eatssu/domain/admin/dto/MenuBoards.java +++ b/src/main/java/ssu/eatssu/domain/admin/dto/MenuBoards.java @@ -4,11 +4,11 @@ import java.util.List; public record MenuBoards(List menuBoards) { - public MenuBoards() { - this(new ArrayList<>()); - } + public MenuBoards() { + this(new ArrayList<>()); + } - public void add(MenuBoard menuBoard) { - menuBoards.add(menuBoard); - } + public void add(MenuBoard menuBoard) { + menuBoards.add(menuBoard); + } } \ No newline at end of file diff --git a/src/main/java/ssu/eatssu/domain/admin/dto/MenuSection.java b/src/main/java/ssu/eatssu/domain/admin/dto/MenuSection.java index c080b7b7..48aeaad0 100644 --- a/src/main/java/ssu/eatssu/domain/admin/dto/MenuSection.java +++ b/src/main/java/ssu/eatssu/domain/admin/dto/MenuSection.java @@ -1,17 +1,17 @@ package ssu.eatssu.domain.admin.dto; +import ssu.eatssu.domain.menu.entity.MenuCategory; + import java.util.ArrayList; import java.util.List; -import ssu.eatssu.domain.menu.entity.MenuCategory; - public record MenuSection(Long categoryId, String categoryName, List menuLines) implements SectionInBoard { - public MenuSection(MenuCategory category) { - this(category.getId(), category.getName(), new ArrayList<>()); - } + public MenuSection(MenuCategory category) { + this(category.getId(), category.getName(), new ArrayList<>()); + } - @Override - public void addMenuLine(MenuLine menuLine) { - menuLines.add(menuLine); - } + @Override + public void addMenuLine(MenuLine menuLine) { + menuLines.add(menuLine); + } } \ No newline at end of file diff --git a/src/main/java/ssu/eatssu/domain/admin/dto/PageWrapper.java b/src/main/java/ssu/eatssu/domain/admin/dto/PageWrapper.java index 5fa7d3a7..b4898ef6 100644 --- a/src/main/java/ssu/eatssu/domain/admin/dto/PageWrapper.java +++ b/src/main/java/ssu/eatssu/domain/admin/dto/PageWrapper.java @@ -1,14 +1,14 @@ package ssu.eatssu.domain.admin.dto; -import java.util.List; - import org.springframework.data.domain.Page; +import java.util.List; + public record PageWrapper(List content, int totalPages, long totalElements, int number, int size, - int numberOfElements, - boolean isFirst, boolean isLast) { - public PageWrapper(Page page) { - this(page.getContent(), page.getTotalPages(), page.getTotalElements(), page.getNumber(), page.getSize(), - page.getNumberOfElements(), page.isFirst(), page.isLast()); - } + int numberOfElements, + boolean isFirst, boolean isLast) { + public PageWrapper(Page page) { + this(page.getContent(), page.getTotalPages(), page.getTotalElements(), page.getNumber(), page.getSize(), + page.getNumberOfElements(), page.isFirst(), page.isLast()); + } } diff --git a/src/main/java/ssu/eatssu/domain/admin/dto/RegisterMealRequest.java b/src/main/java/ssu/eatssu/domain/admin/dto/RegisterMealRequest.java index 4147cc02..efd33a83 100644 --- a/src/main/java/ssu/eatssu/domain/admin/dto/RegisterMealRequest.java +++ b/src/main/java/ssu/eatssu/domain/admin/dto/RegisterMealRequest.java @@ -3,7 +3,7 @@ import java.util.List; public record RegisterMealRequest(List menuNames) { - public RegisterMealRequest { - menuNames.sort(String::compareTo); - } + public RegisterMealRequest { + menuNames.sort(String::compareTo); + } } diff --git a/src/main/java/ssu/eatssu/domain/admin/dto/ReportLine.java b/src/main/java/ssu/eatssu/domain/admin/dto/ReportLine.java index 8948d13d..5fcd7831 100644 --- a/src/main/java/ssu/eatssu/domain/admin/dto/ReportLine.java +++ b/src/main/java/ssu/eatssu/domain/admin/dto/ReportLine.java @@ -1,13 +1,13 @@ package ssu.eatssu.domain.admin.dto; -import java.time.LocalDateTime; - import ssu.eatssu.domain.review.entity.Report; import ssu.eatssu.domain.review.entity.Review; +import java.time.LocalDateTime; + public record ReportLine(Long reportId, LocalDateTime date, String type, Long reviewId, String reviewText) { - public ReportLine(Report report, Review review) { - this(report.getId(), report.getCreatedDate(), report.getReportType().getDescription(), - review.getId(), review.getContent()); - } + public ReportLine(Report report, Review review) { + this(report.getId(), report.getCreatedDate(), report.getReportType().getDescription(), + review.getId(), review.getContent()); + } } \ No newline at end of file diff --git a/src/main/java/ssu/eatssu/domain/admin/dto/SectionInBoard.java b/src/main/java/ssu/eatssu/domain/admin/dto/SectionInBoard.java index aa3f566c..54eb1600 100644 --- a/src/main/java/ssu/eatssu/domain/admin/dto/SectionInBoard.java +++ b/src/main/java/ssu/eatssu/domain/admin/dto/SectionInBoard.java @@ -1,5 +1,5 @@ package ssu.eatssu.domain.admin.dto; public interface SectionInBoard { - void addMenuLine(MenuLine menuLine); + void addMenuLine(MenuLine menuLine); } diff --git a/src/main/java/ssu/eatssu/domain/admin/infrastructure/AdminAuthConfigurationProperties.java b/src/main/java/ssu/eatssu/domain/admin/infrastructure/AdminAuthConfigurationProperties.java index 4c2f7ea0..29cca058 100644 --- a/src/main/java/ssu/eatssu/domain/admin/infrastructure/AdminAuthConfigurationProperties.java +++ b/src/main/java/ssu/eatssu/domain/admin/infrastructure/AdminAuthConfigurationProperties.java @@ -1,11 +1,10 @@ package ssu.eatssu.domain.admin.infrastructure; -import org.springframework.boot.context.properties.ConfigurationProperties; - import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; @Data @ConfigurationProperties(prefix = "admin") public class AdminAuthConfigurationProperties { - private String loginId; + private String loginId; } \ No newline at end of file diff --git a/src/main/java/ssu/eatssu/domain/admin/infrastructure/AdminConfiguration.java b/src/main/java/ssu/eatssu/domain/admin/infrastructure/AdminConfiguration.java index 0eb3ea08..01309549 100644 --- a/src/main/java/ssu/eatssu/domain/admin/infrastructure/AdminConfiguration.java +++ b/src/main/java/ssu/eatssu/domain/admin/infrastructure/AdminConfiguration.java @@ -3,15 +3,14 @@ import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; - import ssu.eatssu.domain.admin.controller.AdminAuth; @Configuration @EnableConfigurationProperties(AdminAuthConfigurationProperties.class) public class AdminConfiguration { - @Bean - public AdminAuth adminAuthConfigurationProperties(AdminAuthConfigurationProperties properties) { - return new AdminAuth(properties.getLoginId()); - } + @Bean + public AdminAuth adminAuthConfigurationProperties(AdminAuthConfigurationProperties properties) { + return new AdminAuth(properties.getLoginId()); + } } diff --git a/src/main/java/ssu/eatssu/domain/admin/listener/ReportEventListener.java b/src/main/java/ssu/eatssu/domain/admin/listener/ReportEventListener.java index 5219f6ae..d357cdc9 100644 --- a/src/main/java/ssu/eatssu/domain/admin/listener/ReportEventListener.java +++ b/src/main/java/ssu/eatssu/domain/admin/listener/ReportEventListener.java @@ -1,9 +1,8 @@ package ssu.eatssu.domain.admin.listener; +import lombok.RequiredArgsConstructor; import org.springframework.context.event.EventListener; import org.springframework.stereotype.Component; - -import lombok.RequiredArgsConstructor; import ssu.eatssu.domain.admin.event.ReviewDeleteEvent; import ssu.eatssu.domain.admin.service.ManageReportService; @@ -11,10 +10,10 @@ @RequiredArgsConstructor public class ReportEventListener { - private final ManageReportService manageReportService; + private final ManageReportService manageReportService; - @EventListener - public void deleteReport(ReviewDeleteEvent event) { - manageReportService.deleteAllByReviewId(event.reviewId()); - } + @EventListener + public void deleteReport(ReviewDeleteEvent event) { + manageReportService.deleteAllByReviewId(event.reviewId()); + } } diff --git a/src/main/java/ssu/eatssu/domain/admin/persistence/LoadCategoryRepository.java b/src/main/java/ssu/eatssu/domain/admin/persistence/LoadCategoryRepository.java index 7ae755f2..102f5114 100644 --- a/src/main/java/ssu/eatssu/domain/admin/persistence/LoadCategoryRepository.java +++ b/src/main/java/ssu/eatssu/domain/admin/persistence/LoadCategoryRepository.java @@ -1,10 +1,8 @@ package ssu.eatssu.domain.admin.persistence; -import org.springframework.stereotype.Repository; - import com.querydsl.jpa.impl.JPAQueryFactory; - import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Repository; import ssu.eatssu.domain.menu.entity.QMenuCategory; import ssu.eatssu.domain.restaurant.entity.Restaurant; @@ -12,14 +10,14 @@ @RequiredArgsConstructor public class LoadCategoryRepository { - private final JPAQueryFactory queryFactory; - private final QMenuCategory category = QMenuCategory.menuCategory; + private final JPAQueryFactory queryFactory; + private final QMenuCategory category = QMenuCategory.menuCategory; - public boolean existsCategory(Restaurant restaurant, String name) { - return queryFactory.select(category.id) - .from(category) - .where(category.name.eq(name), - category.restaurant.eq(restaurant)) - .fetchFirst() != null; - } + public boolean existsCategory(Restaurant restaurant, String name) { + return queryFactory.select(category.id) + .from(category) + .where(category.name.eq(name), + category.restaurant.eq(restaurant)) + .fetchFirst() != null; + } } diff --git a/src/main/java/ssu/eatssu/domain/admin/persistence/LoadFixMenuRepository.java b/src/main/java/ssu/eatssu/domain/admin/persistence/LoadFixMenuRepository.java index 470d76f4..217f7578 100644 --- a/src/main/java/ssu/eatssu/domain/admin/persistence/LoadFixMenuRepository.java +++ b/src/main/java/ssu/eatssu/domain/admin/persistence/LoadFixMenuRepository.java @@ -1,85 +1,83 @@ package ssu.eatssu.domain.admin.persistence; -import java.util.List; - -import org.springframework.stereotype.Repository; - import com.querydsl.core.types.Projections; import com.querydsl.core.types.dsl.BooleanExpression; import com.querydsl.jpa.impl.JPAQueryFactory; - import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Repository; import ssu.eatssu.domain.admin.dto.BriefMenu; import ssu.eatssu.domain.menu.entity.MenuCategory; import ssu.eatssu.domain.menu.entity.QMenu; import ssu.eatssu.domain.menu.entity.QMenuCategory; import ssu.eatssu.domain.restaurant.entity.Restaurant; +import java.util.List; + @Repository @RequiredArgsConstructor public class LoadFixMenuRepository { - private final JPAQueryFactory queryFactory; - private final QMenu menu = QMenu.menu; - private final QMenuCategory category = QMenuCategory.menuCategory; + private final JPAQueryFactory queryFactory; + private final QMenu menu = QMenu.menu; + private final QMenuCategory category = QMenuCategory.menuCategory; - public List findBriefMenusByCategoryId(Long categoryId) { - return queryFactory - .select(Projections.constructor(BriefMenu.class, - menu.id, - menu.name, - menu.price)) - .from(menu) - .join(menu.category, category) - .where( - menuCategoryIdEq(categoryId) - ) - .orderBy(menu.name.asc()) - .fetch(); - } + public List findBriefMenusByCategoryId(Long categoryId) { + return queryFactory + .select(Projections.constructor(BriefMenu.class, + menu.id, + menu.name, + menu.price)) + .from(menu) + .join(menu.category, category) + .where( + menuCategoryIdEq(categoryId) + ) + .orderBy(menu.name.asc()) + .fetch(); + } - public boolean existsMenu(String name, Restaurant restaurant) { - return queryFactory - .select(menu.id) - .from(menu) - .where( - menuNameEq(name), - restaurantNameEq(restaurant) - ) - .fetchFirst() != null; - } + public boolean existsMenu(String name, Restaurant restaurant) { + return queryFactory + .select(menu.id) + .from(menu) + .where( + menuNameEq(name), + restaurantNameEq(restaurant) + ) + .fetchFirst() != null; + } - public Restaurant getRestaurant(Long menuId) { - return queryFactory - .select(menu.restaurant) - .from(menu) - .where( - menu.id.eq(menuId) - ) - .fetchFirst(); - } + public Restaurant getRestaurant(Long menuId) { + return queryFactory + .select(menu.restaurant) + .from(menu) + .where( + menu.id.eq(menuId) + ) + .fetchFirst(); + } - public List findMenuCategoriesByRestaurant(Restaurant restaurant) { - return queryFactory - .selectFrom(category) - .where( - categoryRestaurantNameEq(restaurant) - ).fetch(); - } + public List findMenuCategoriesByRestaurant(Restaurant restaurant) { + return queryFactory + .selectFrom(category) + .where( + categoryRestaurantNameEq(restaurant) + ).fetch(); + } - private BooleanExpression menuNameEq(String menuName) { - return menu.name.eq(menuName); - } + private BooleanExpression menuNameEq(String menuName) { + return menu.name.eq(menuName); + } - private BooleanExpression restaurantNameEq(Restaurant restaurant) { - return menu.restaurant.eq(restaurant); - } + private BooleanExpression restaurantNameEq(Restaurant restaurant) { + return menu.restaurant.eq(restaurant); + } - private BooleanExpression categoryRestaurantNameEq(Restaurant restaurant) { - return category.restaurant.eq(restaurant); - } + private BooleanExpression categoryRestaurantNameEq(Restaurant restaurant) { + return category.restaurant.eq(restaurant); + } - private BooleanExpression menuCategoryIdEq(Long categoryId) { - return category.id.eq(categoryId); - } + private BooleanExpression menuCategoryIdEq(Long categoryId) { + return category.id.eq(categoryId); + } } diff --git a/src/main/java/ssu/eatssu/domain/admin/persistence/LoadInquiryRepository.java b/src/main/java/ssu/eatssu/domain/admin/persistence/LoadInquiryRepository.java index ccc0b3c2..361ff218 100644 --- a/src/main/java/ssu/eatssu/domain/admin/persistence/LoadInquiryRepository.java +++ b/src/main/java/ssu/eatssu/domain/admin/persistence/LoadInquiryRepository.java @@ -1,58 +1,56 @@ package ssu.eatssu.domain.admin.persistence; -import java.util.List; - -import org.springframework.data.domain.Page; -import org.springframework.data.domain.Pageable; -import org.springframework.data.support.PageableExecutionUtils; -import org.springframework.stereotype.Repository; - import com.querydsl.core.types.dsl.CaseBuilder; import com.querydsl.core.types.dsl.NumberExpression; import com.querydsl.jpa.impl.JPAQuery; import com.querydsl.jpa.impl.JPAQueryFactory; - import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.support.PageableExecutionUtils; +import org.springframework.stereotype.Repository; import ssu.eatssu.domain.inquiry.entity.Inquiry; import ssu.eatssu.domain.inquiry.entity.InquiryStatus; import ssu.eatssu.domain.inquiry.entity.QInquiry; +import java.util.List; + @RequiredArgsConstructor @Repository public class LoadInquiryRepository { - private final JPAQueryFactory queryFactory; - - private final QInquiry inquiry = QInquiry.inquiry; - - public Page findAllInquiries(Pageable pageable) { - return PageableExecutionUtils.getPage(fetchInquiries(pageable), pageable, () -> countInquiries().fetchOne()); - } - - private JPAQuery countInquiries() { - return queryFactory - .select(inquiry.count()) - .from(inquiry); - } - - private List fetchInquiries(Pageable pageable) { - return queryFactory - .selectFrom(inquiry) - .orderBy(statusPath().asc(), inquiry.createdDate.desc()) - .offset(pageable.getOffset()) - .limit(pageable.getPageSize()) - .fetch(); - } - - private NumberExpression statusPath() { - - /** - * Inquiry Status 에 따라 정렬 - * WAITING -> HOLD -> ANSWERED - */ - return new CaseBuilder() - .when(inquiry.status.eq(InquiryStatus.WAITING)).then(1) - .when(inquiry.status.eq(InquiryStatus.HOLD)).then(2) - .when(inquiry.status.eq(InquiryStatus.ANSWERED)).then(3) - .otherwise(4); - } + private final JPAQueryFactory queryFactory; + + private final QInquiry inquiry = QInquiry.inquiry; + + public Page findAllInquiries(Pageable pageable) { + return PageableExecutionUtils.getPage(fetchInquiries(pageable), pageable, () -> countInquiries().fetchOne()); + } + + private JPAQuery countInquiries() { + return queryFactory + .select(inquiry.count()) + .from(inquiry); + } + + private List fetchInquiries(Pageable pageable) { + return queryFactory + .selectFrom(inquiry) + .orderBy(statusPath().asc(), inquiry.createdDate.desc()) + .offset(pageable.getOffset()) + .limit(pageable.getPageSize()) + .fetch(); + } + + private NumberExpression statusPath() { + + /** + * Inquiry Status 에 따라 정렬 + * WAITING -> HOLD -> ANSWERED + */ + return new CaseBuilder() + .when(inquiry.status.eq(InquiryStatus.WAITING)).then(1) + .when(inquiry.status.eq(InquiryStatus.HOLD)).then(2) + .when(inquiry.status.eq(InquiryStatus.ANSWERED)).then(3) + .otherwise(4); + } } diff --git a/src/main/java/ssu/eatssu/domain/admin/persistence/LoadMealRepository.java b/src/main/java/ssu/eatssu/domain/admin/persistence/LoadMealRepository.java index 73f6e7d0..feb3e9fe 100644 --- a/src/main/java/ssu/eatssu/domain/admin/persistence/LoadMealRepository.java +++ b/src/main/java/ssu/eatssu/domain/admin/persistence/LoadMealRepository.java @@ -1,15 +1,10 @@ package ssu.eatssu.domain.admin.persistence; -import java.util.Date; -import java.util.List; - -import org.springframework.stereotype.Repository; - import com.querydsl.core.types.Projections; import com.querydsl.core.types.dsl.BooleanExpression; import com.querydsl.jpa.impl.JPAQueryFactory; - import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Repository; import ssu.eatssu.domain.admin.dto.BriefMenu; import ssu.eatssu.domain.admin.dto.MealInfo; import ssu.eatssu.domain.menu.entity.Menu; @@ -19,104 +14,107 @@ import ssu.eatssu.domain.menu.entity.constants.TimePart; import ssu.eatssu.domain.restaurant.entity.Restaurant; +import java.util.Date; +import java.util.List; + @Repository @RequiredArgsConstructor public class LoadMealRepository { - private final JPAQueryFactory queryFactory; - private final QMenu menu = QMenu.menu; - private final QMeal meal = QMeal.meal; - private final QMealMenu mealMenu = QMealMenu.mealMenu; - - public List existsMeal(MealInfo query) { - return queryFactory.select(meal.id) - .from(meal) - .where( - mealRestaurantEq(query.restaurant()), - mealDateEq(query.date()), - mealTimePartEq(query.timePart()) - ) - .fetch(); - } - - public List findMenuNamesByMealId(Long mealId) { - return queryFactory - .select(menu.name) - .from(mealMenu) - .join(mealMenu.meal, meal) - .on(mealIdEq(mealId)) - .join(mealMenu.menu, menu) - .orderBy(menu.name.asc()) - .fetch(); - } - - public List getAllMealIdsByInfo(MealInfo query) { - return queryFactory.select(meal.id) - .from(meal) - .where( - mealRestaurantEq(query.restaurant()), - mealDateEq(query.date()), - mealTimePartEq(query.timePart()) - ) - .fetch(); - } - - public List findBriefMenusByMealId(Long mealId) { - return queryFactory - .select(Projections.constructor(BriefMenu.class, - menu.id, - menu.name, - menu.price)) - .from(mealMenu) - .join(mealMenu.meal, meal) - .on(mealIdEq(mealId)) - .join(mealMenu.menu, menu) - .orderBy(menu.name.asc()) - .fetch(); - } - - public Menu findMenu(String menuName, Restaurant restaurant) { - return queryFactory.selectFrom(menu) - .where( - menuNameEq(menuName), - menuRestaurantEq(restaurant) - ) - .fetchFirst(); - } - - public List getAllMenuIds(Long mealId) { - return queryFactory.select(mealMenu.menu.id) - .from(mealMenu) - .where(mealIdEq(mealId)) - .fetch(); - } - - private BooleanExpression mealIdEq(Long mealId) { - return meal.id.eq(mealId); - } - - private BooleanExpression mealTimePartEq(TimePart timePart) { - return meal.timePart.eq(timePart); - } - - private BooleanExpression mealDateEq(Date date) { - return meal.date.eq(date); - } - - private BooleanExpression mealRestaurantEq(Restaurant restaurant) { - return meal.restaurant.eq(restaurant); - } - - private BooleanExpression menuRestaurantEq(Restaurant restaurant) { - return menu.restaurant.eq(restaurant); - } - - private BooleanExpression menuNameEq(String menuName) { - return menu.name.eq(menuName); - } - - public int countMealMenuByMenuId(Long menuId) { - return (int)queryFactory.selectFrom(mealMenu) - .where(mealMenu.menu.id.eq(menuId)) - .fetchCount(); - } + private final JPAQueryFactory queryFactory; + private final QMenu menu = QMenu.menu; + private final QMeal meal = QMeal.meal; + private final QMealMenu mealMenu = QMealMenu.mealMenu; + + public List existsMeal(MealInfo query) { + return queryFactory.select(meal.id) + .from(meal) + .where( + mealRestaurantEq(query.restaurant()), + mealDateEq(query.date()), + mealTimePartEq(query.timePart()) + ) + .fetch(); + } + + public List findMenuNamesByMealId(Long mealId) { + return queryFactory + .select(menu.name) + .from(mealMenu) + .join(mealMenu.meal, meal) + .on(mealIdEq(mealId)) + .join(mealMenu.menu, menu) + .orderBy(menu.name.asc()) + .fetch(); + } + + public List getAllMealIdsByInfo(MealInfo query) { + return queryFactory.select(meal.id) + .from(meal) + .where( + mealRestaurantEq(query.restaurant()), + mealDateEq(query.date()), + mealTimePartEq(query.timePart()) + ) + .fetch(); + } + + public List findBriefMenusByMealId(Long mealId) { + return queryFactory + .select(Projections.constructor(BriefMenu.class, + menu.id, + menu.name, + menu.price)) + .from(mealMenu) + .join(mealMenu.meal, meal) + .on(mealIdEq(mealId)) + .join(mealMenu.menu, menu) + .orderBy(menu.name.asc()) + .fetch(); + } + + public Menu findMenu(String menuName, Restaurant restaurant) { + return queryFactory.selectFrom(menu) + .where( + menuNameEq(menuName), + menuRestaurantEq(restaurant) + ) + .fetchFirst(); + } + + public List getAllMenuIds(Long mealId) { + return queryFactory.select(mealMenu.menu.id) + .from(mealMenu) + .where(mealIdEq(mealId)) + .fetch(); + } + + private BooleanExpression mealIdEq(Long mealId) { + return meal.id.eq(mealId); + } + + private BooleanExpression mealTimePartEq(TimePart timePart) { + return meal.timePart.eq(timePart); + } + + private BooleanExpression mealDateEq(Date date) { + return meal.date.eq(date); + } + + private BooleanExpression mealRestaurantEq(Restaurant restaurant) { + return meal.restaurant.eq(restaurant); + } + + private BooleanExpression menuRestaurantEq(Restaurant restaurant) { + return menu.restaurant.eq(restaurant); + } + + private BooleanExpression menuNameEq(String menuName) { + return menu.name.eq(menuName); + } + + public int countMealMenuByMenuId(Long menuId) { + return (int) queryFactory.selectFrom(mealMenu) + .where(mealMenu.menu.id.eq(menuId)) + .fetchCount(); + } } diff --git a/src/main/java/ssu/eatssu/domain/admin/persistence/LoadReportRepository.java b/src/main/java/ssu/eatssu/domain/admin/persistence/LoadReportRepository.java index 6bfde01f..0e3c3491 100644 --- a/src/main/java/ssu/eatssu/domain/admin/persistence/LoadReportRepository.java +++ b/src/main/java/ssu/eatssu/domain/admin/persistence/LoadReportRepository.java @@ -1,54 +1,52 @@ package ssu.eatssu.domain.admin.persistence; -import java.util.List; - -import org.springframework.data.domain.Page; -import org.springframework.data.domain.Pageable; -import org.springframework.data.support.PageableExecutionUtils; -import org.springframework.stereotype.Repository; - import com.querydsl.core.types.Projections; import com.querydsl.jpa.impl.JPAQuery; import com.querydsl.jpa.impl.JPAQueryFactory; - import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.support.PageableExecutionUtils; +import org.springframework.stereotype.Repository; import ssu.eatssu.domain.admin.dto.ReportLine; import ssu.eatssu.domain.review.entity.QReport; import ssu.eatssu.domain.review.entity.QReview; +import java.util.List; + @Repository @RequiredArgsConstructor public class LoadReportRepository { - private final JPAQueryFactory queryFactory; - private final QReport report = QReport.report; - private final QReview review = QReview.review; - - public Page findAll(Pageable pageable) { - return PageableExecutionUtils.getPage(fetchReportLines(pageable), pageable, () -> countReports().fetchOne()); - } - - private JPAQuery countReports() { - return queryFactory - .select(report.count()) - .from(report); - } - - private List fetchReportLines(Pageable pageable) { - return queryFactory - .select(Projections.constructor(ReportLine.class, report, review)) - .from(report) - .join(report.review, review) - .orderBy(report.createdDate.desc()) - .offset(pageable.getOffset()) - .limit(pageable.getPageSize()) - .fetch(); - } - - public List findAllByReviewId(Long reviewId) { - return queryFactory - .select(report.id) - .from(report) - .where(report.review.id.eq(reviewId)) - .fetch(); - } + private final JPAQueryFactory queryFactory; + private final QReport report = QReport.report; + private final QReview review = QReview.review; + + public Page findAll(Pageable pageable) { + return PageableExecutionUtils.getPage(fetchReportLines(pageable), pageable, () -> countReports().fetchOne()); + } + + private JPAQuery countReports() { + return queryFactory + .select(report.count()) + .from(report); + } + + private List fetchReportLines(Pageable pageable) { + return queryFactory + .select(Projections.constructor(ReportLine.class, report, review)) + .from(report) + .join(report.review, review) + .orderBy(report.createdDate.desc()) + .offset(pageable.getOffset()) + .limit(pageable.getPageSize()) + .fetch(); + } + + public List findAllByReviewId(Long reviewId) { + return queryFactory + .select(report.id) + .from(report) + .where(report.review.id.eq(reviewId)) + .fetch(); + } } diff --git a/src/main/java/ssu/eatssu/domain/admin/persistence/ManageCategoryRepository.java b/src/main/java/ssu/eatssu/domain/admin/persistence/ManageCategoryRepository.java index c25686a9..21847438 100644 --- a/src/main/java/ssu/eatssu/domain/admin/persistence/ManageCategoryRepository.java +++ b/src/main/java/ssu/eatssu/domain/admin/persistence/ManageCategoryRepository.java @@ -2,7 +2,6 @@ import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; - import ssu.eatssu.domain.menu.entity.MenuCategory; @Repository diff --git a/src/main/java/ssu/eatssu/domain/admin/persistence/ManageInquiryRepository.java b/src/main/java/ssu/eatssu/domain/admin/persistence/ManageInquiryRepository.java index 1a9adc10..590e2657 100644 --- a/src/main/java/ssu/eatssu/domain/admin/persistence/ManageInquiryRepository.java +++ b/src/main/java/ssu/eatssu/domain/admin/persistence/ManageInquiryRepository.java @@ -1,7 +1,6 @@ package ssu.eatssu.domain.admin.persistence; import org.springframework.data.jpa.repository.JpaRepository; - import ssu.eatssu.domain.inquiry.entity.Inquiry; public interface ManageInquiryRepository extends JpaRepository { diff --git a/src/main/java/ssu/eatssu/domain/admin/persistence/ManageMealMenuRepository.java b/src/main/java/ssu/eatssu/domain/admin/persistence/ManageMealMenuRepository.java index 3b136e24..4478ea9f 100644 --- a/src/main/java/ssu/eatssu/domain/admin/persistence/ManageMealMenuRepository.java +++ b/src/main/java/ssu/eatssu/domain/admin/persistence/ManageMealMenuRepository.java @@ -1,7 +1,6 @@ package ssu.eatssu.domain.admin.persistence; import org.springframework.data.jpa.repository.JpaRepository; - import ssu.eatssu.domain.menu.entity.MealMenu; public interface ManageMealMenuRepository extends JpaRepository { diff --git a/src/main/java/ssu/eatssu/domain/admin/persistence/ManageMealRepository.java b/src/main/java/ssu/eatssu/domain/admin/persistence/ManageMealRepository.java index 831a89d2..09dd9f84 100644 --- a/src/main/java/ssu/eatssu/domain/admin/persistence/ManageMealRepository.java +++ b/src/main/java/ssu/eatssu/domain/admin/persistence/ManageMealRepository.java @@ -1,7 +1,6 @@ package ssu.eatssu.domain.admin.persistence; import org.springframework.data.jpa.repository.JpaRepository; - import ssu.eatssu.domain.menu.entity.Meal; public interface ManageMealRepository extends JpaRepository { diff --git a/src/main/java/ssu/eatssu/domain/admin/persistence/ManageMenuRepository.java b/src/main/java/ssu/eatssu/domain/admin/persistence/ManageMenuRepository.java index 6e7cb30e..25be2987 100644 --- a/src/main/java/ssu/eatssu/domain/admin/persistence/ManageMenuRepository.java +++ b/src/main/java/ssu/eatssu/domain/admin/persistence/ManageMenuRepository.java @@ -1,13 +1,12 @@ package ssu.eatssu.domain.admin.persistence; -import java.util.Optional; - import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; - import ssu.eatssu.domain.menu.entity.Menu; +import java.util.Optional; + @Repository public interface ManageMenuRepository extends JpaRepository { - Optional findById(Long id); + Optional findById(Long id); } diff --git a/src/main/java/ssu/eatssu/domain/admin/persistence/ManageReportRepository.java b/src/main/java/ssu/eatssu/domain/admin/persistence/ManageReportRepository.java index f3dce349..9acce379 100644 --- a/src/main/java/ssu/eatssu/domain/admin/persistence/ManageReportRepository.java +++ b/src/main/java/ssu/eatssu/domain/admin/persistence/ManageReportRepository.java @@ -1,7 +1,6 @@ package ssu.eatssu.domain.admin.persistence; import org.springframework.data.jpa.repository.JpaRepository; - import ssu.eatssu.domain.review.entity.Report; public interface ManageReportRepository extends JpaRepository { diff --git a/src/main/java/ssu/eatssu/domain/admin/persistence/ManageReviewRepository.java b/src/main/java/ssu/eatssu/domain/admin/persistence/ManageReviewRepository.java index ad3b21f2..598c4876 100644 --- a/src/main/java/ssu/eatssu/domain/admin/persistence/ManageReviewRepository.java +++ b/src/main/java/ssu/eatssu/domain/admin/persistence/ManageReviewRepository.java @@ -1,7 +1,6 @@ package ssu.eatssu.domain.admin.persistence; import org.springframework.data.jpa.repository.JpaRepository; - import ssu.eatssu.domain.review.entity.Review; public interface ManageReviewRepository extends JpaRepository { diff --git a/src/main/java/ssu/eatssu/domain/admin/persistence/MenuRatingRepository.java b/src/main/java/ssu/eatssu/domain/admin/persistence/MenuRatingRepository.java index e90ff3fb..17c8c020 100644 --- a/src/main/java/ssu/eatssu/domain/admin/persistence/MenuRatingRepository.java +++ b/src/main/java/ssu/eatssu/domain/admin/persistence/MenuRatingRepository.java @@ -1,34 +1,32 @@ package ssu.eatssu.domain.admin.persistence; -import org.springframework.stereotype.Repository; - import com.querydsl.core.types.dsl.BooleanExpression; import com.querydsl.jpa.impl.JPAQueryFactory; - import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Repository; import ssu.eatssu.domain.menu.entity.QMenu; import ssu.eatssu.domain.review.entity.QReview; @Repository @RequiredArgsConstructor public class MenuRatingRepository { - private final JPAQueryFactory queryFactory; - private final QMenu menu = QMenu.menu; - private final QReview review = QReview.review; + private final JPAQueryFactory queryFactory; + private final QMenu menu = QMenu.menu; + private final QReview review = QReview.review; - public Double getMainRatingAverage(Long menuId) { - return queryFactory - .select(review.ratings.mainRating.avg()) - .from(review) - .join(review.menu, menu) - .where( - menuIdEq(menuId) - ) - .fetchOne(); - } + public Double getMainRatingAverage(Long menuId) { + return queryFactory + .select(review.ratings.mainRating.avg()) + .from(review) + .join(review.menu, menu) + .where( + menuIdEq(menuId) + ) + .fetchOne(); + } - private BooleanExpression menuIdEq(Long menuId) { - return menu.id.eq(menuId); - } + private BooleanExpression menuIdEq(Long menuId) { + return menu.id.eq(menuId); + } } diff --git a/src/main/java/ssu/eatssu/domain/admin/service/AuthenticationService.java b/src/main/java/ssu/eatssu/domain/admin/service/AuthenticationService.java index f16bc8c6..a5372fdd 100644 --- a/src/main/java/ssu/eatssu/domain/admin/service/AuthenticationService.java +++ b/src/main/java/ssu/eatssu/domain/admin/service/AuthenticationService.java @@ -1,9 +1,8 @@ package ssu.eatssu.domain.admin.service; +import lombok.RequiredArgsConstructor; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; - -import lombok.RequiredArgsConstructor; import ssu.eatssu.domain.admin.controller.AdminAuth; import ssu.eatssu.domain.admin.dto.LoginRequest; import ssu.eatssu.domain.auth.security.JwtTokenProvider; @@ -14,24 +13,24 @@ @RequiredArgsConstructor @Service public class AuthenticationService { - private final JwtTokenProvider tokenProvider; - private final PasswordEncoder passwordEncoder; - private final UserRepository userRepository; - private final AdminAuth adminAuth; + private final JwtTokenProvider tokenProvider; + private final PasswordEncoder passwordEncoder; + private final UserRepository userRepository; + private final AdminAuth adminAuth; - public Tokens login(LoginRequest request) { - return tokenProvider.generateTokens(request.loginId(), request.password()); - } + public Tokens login(LoginRequest request) { + return tokenProvider.generateTokens(request.loginId(), request.password()); + } - private void join(String loginId, String password) { - String credentials = createCredentials(password); + private void join(String loginId, String password) { + String credentials = createCredentials(password); - //회원가입 - User user = User.adminJoin(loginId, credentials); - userRepository.save(user); - } + //회원가입 + User user = User.adminJoin(loginId, credentials); + userRepository.save(user); + } - private String createCredentials(String password) { - return passwordEncoder.encode(password); - } + private String createCredentials(String password) { + return passwordEncoder.encode(password); + } } diff --git a/src/main/java/ssu/eatssu/domain/admin/service/ManageCategoryService.java b/src/main/java/ssu/eatssu/domain/admin/service/ManageCategoryService.java index 249b1a87..a4e5a7f7 100644 --- a/src/main/java/ssu/eatssu/domain/admin/service/ManageCategoryService.java +++ b/src/main/java/ssu/eatssu/domain/admin/service/ManageCategoryService.java @@ -1,9 +1,8 @@ package ssu.eatssu.domain.admin.service; +import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; - -import lombok.RequiredArgsConstructor; import ssu.eatssu.domain.admin.dto.RegisterCategoryRequest; import ssu.eatssu.domain.admin.persistence.LoadCategoryRepository; import ssu.eatssu.domain.admin.persistence.ManageCategoryRepository; @@ -17,20 +16,20 @@ @RequiredArgsConstructor public class ManageCategoryService { - private final LoadCategoryRepository loadCategoryRepository; - private final ManageCategoryRepository manageCategoryRepository; + private final LoadCategoryRepository loadCategoryRepository; + private final ManageCategoryRepository manageCategoryRepository; - public void register(Restaurant restaurant, RegisterCategoryRequest request) { - // 해당 식당에 이미 존재하는 카테고리인지 확인 - if (loadCategoryRepository.existsCategory(restaurant, request.name())) { - throw new BaseException(BaseResponseStatus.CONFLICT); - } + public void register(Restaurant restaurant, RegisterCategoryRequest request) { + // 해당 식당에 이미 존재하는 카테고리인지 확인 + if (loadCategoryRepository.existsCategory(restaurant, request.name())) { + throw new BaseException(BaseResponseStatus.CONFLICT); + } - MenuCategory category = MenuCategory.builder() - .restaurant(restaurant) - .name(request.name()) - .build(); + MenuCategory category = MenuCategory.builder() + .restaurant(restaurant) + .name(request.name()) + .build(); - manageCategoryRepository.save(category); - } + manageCategoryRepository.save(category); + } } diff --git a/src/main/java/ssu/eatssu/domain/admin/service/ManageFixMenuService.java b/src/main/java/ssu/eatssu/domain/admin/service/ManageFixMenuService.java index fdfa44aa..542361dc 100644 --- a/src/main/java/ssu/eatssu/domain/admin/service/ManageFixMenuService.java +++ b/src/main/java/ssu/eatssu/domain/admin/service/ManageFixMenuService.java @@ -1,11 +1,8 @@ package ssu.eatssu.domain.admin.service; -import java.util.List; - +import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; - -import lombok.RequiredArgsConstructor; import ssu.eatssu.domain.admin.dto.BriefMenu; import ssu.eatssu.domain.admin.dto.MenuBoard; import ssu.eatssu.domain.admin.dto.MenuBoards; @@ -23,114 +20,116 @@ import ssu.eatssu.global.handler.response.BaseException; import ssu.eatssu.global.handler.response.BaseResponseStatus; +import java.util.List; + @Service @Transactional @RequiredArgsConstructor public class ManageFixMenuService { - private final LoadFixMenuRepository loadMenuRepository; - private final ManageMenuRepository manageMenuRepository; - private final MenuRatingRepository menuRatingRepository; + private final LoadFixMenuRepository loadMenuRepository; + private final ManageMenuRepository manageMenuRepository; + private final MenuRatingRepository menuRatingRepository; - public MenuBoards getMenuBoards() { - MenuBoards menuBoards = new MenuBoards(); + public MenuBoards getMenuBoards() { + MenuBoards menuBoards = new MenuBoards(); - RestaurantType.FIXED.getRestaurants() - .forEach(restaurant -> menuBoards.add(getMenuBoard(restaurant))); + RestaurantType.FIXED.getRestaurants() + .forEach(restaurant -> menuBoards.add(getMenuBoard(restaurant))); - return menuBoards; - } + return menuBoards; + } - private MenuBoard getMenuBoard(Restaurant restaurant) { - MenuBoard menuBoard = new MenuBoard(restaurant.getRestaurantName()); + private MenuBoard getMenuBoard(Restaurant restaurant) { + MenuBoard menuBoard = new MenuBoard(restaurant.getRestaurantName()); - getMenuSections(restaurant).forEach(menuBoard::addSection); + getMenuSections(restaurant).forEach(menuBoard::addSection); - return menuBoard; - } + return menuBoard; + } - private List getMenuSections(Restaurant restaurant) { - List menuCategories = loadMenuRepository.findMenuCategoriesByRestaurant(restaurant); - return menuCategories.stream() - .map(this::getMenuSection) - .toList(); - } + private List getMenuSections(Restaurant restaurant) { + List menuCategories = loadMenuRepository.findMenuCategoriesByRestaurant(restaurant); + return menuCategories.stream() + .map(this::getMenuSection) + .toList(); + } - private MenuSection getMenuSection(MenuCategory category) { - MenuSection menuSection = new MenuSection(category); + private MenuSection getMenuSection(MenuCategory category) { + MenuSection menuSection = new MenuSection(category); - List briefMenus = loadMenuRepository.findBriefMenusByCategoryId(category.getId()); + List briefMenus = loadMenuRepository.findBriefMenusByCategoryId(category.getId()); - briefMenus.forEach(briefMenu -> - menuSection.addMenuLine(new MenuLine( - briefMenu.id(), - briefMenu.name(), - briefMenu.price(), - menuRatingRepository.getMainRatingAverage(briefMenu.id()) - )) - ); - return menuSection; - } + briefMenus.forEach(briefMenu -> + menuSection.addMenuLine(new MenuLine( + briefMenu.id(), + briefMenu.name(), + briefMenu.price(), + menuRatingRepository.getMainRatingAverage(briefMenu.id()) + )) + ); + return menuSection; + } - public void register(Restaurant restaurant, RegisterFixMenuRequest request) { + public void register(Restaurant restaurant, RegisterFixMenuRequest request) { - // 해당 식당에 이미 존재하는 메뉴인지 확인 - if (loadMenuRepository.existsMenu(request.name(), restaurant)) { - throw new BaseException(BaseResponseStatus.CONFLICT); - } + // 해당 식당에 이미 존재하는 메뉴인지 확인 + if (loadMenuRepository.existsMenu(request.name(), restaurant)) { + throw new BaseException(BaseResponseStatus.CONFLICT); + } - // 해당 식당의 메뉴 카테고리들 중에 요청받은 카테고리가 존재하는지 확인 - List menuCategories = loadMenuRepository.findMenuCategoriesByRestaurant(restaurant); - MenuCategory category = menuCategories.stream() - .filter(menuCategory -> menuCategory.getId().equals(request.categoryId())) - .findFirst() - .orElseThrow(() -> new BaseException(BaseResponseStatus.BAD_REQUEST)); + // 해당 식당의 메뉴 카테고리들 중에 요청받은 카테고리가 존재하는지 확인 + List menuCategories = loadMenuRepository.findMenuCategoriesByRestaurant(restaurant); + MenuCategory category = menuCategories.stream() + .filter(menuCategory -> menuCategory.getId().equals(request.categoryId())) + .findFirst() + .orElseThrow(() -> new BaseException(BaseResponseStatus.BAD_REQUEST)); - manageMenuRepository.save(Menu.createFixed(request.name(), restaurant, request.price(), category)); - } + manageMenuRepository.save(Menu.createFixed(request.name(), restaurant, request.price(), category)); + } - public void updateMenu(Long menuId, UpdateFixMenuRequest request) { - if (isFixedMenu(menuId)) { - throw new BaseException(BaseResponseStatus.NOT_SUPPORT_RESTAURANT); - } + public void updateMenu(Long menuId, UpdateFixMenuRequest request) { + if (isFixedMenu(menuId)) { + throw new BaseException(BaseResponseStatus.NOT_SUPPORT_RESTAURANT); + } - Menu menu = manageMenuRepository.findById(menuId) - .orElseThrow(() -> new BaseException(BaseResponseStatus.NOT_FOUND_MENU)); + Menu menu = manageMenuRepository.findById(menuId) + .orElseThrow(() -> new BaseException(BaseResponseStatus.NOT_FOUND_MENU)); - // 식당에 같은 이름의 메뉴가 있는 지 확인 - Restaurant restaurant = loadMenuRepository.getRestaurant(menuId); - duplicateCheck(request.name(), restaurant); - menu.update(request.name(), request.price()); + // 식당에 같은 이름의 메뉴가 있는 지 확인 + Restaurant restaurant = loadMenuRepository.getRestaurant(menuId); + duplicateCheck(request.name(), restaurant); + menu.update(request.name(), request.price()); - manageMenuRepository.save(menu); - } + manageMenuRepository.save(menu); + } - private void duplicateCheck(String name, Restaurant restaurant) { - if (loadMenuRepository.existsMenu(name, restaurant)) { - throw new BaseException(BaseResponseStatus.CONFLICT); - } - } + private void duplicateCheck(String name, Restaurant restaurant) { + if (loadMenuRepository.existsMenu(name, restaurant)) { + throw new BaseException(BaseResponseStatus.CONFLICT); + } + } - private boolean isFixedMenu(Long menuId) { - Restaurant restaurant = loadMenuRepository.getRestaurant(menuId); - return RestaurantType.isVariableType(restaurant); - } + private boolean isFixedMenu(Long menuId) { + Restaurant restaurant = loadMenuRepository.getRestaurant(menuId); + return RestaurantType.isVariableType(restaurant); + } - public void delete(Long menuId) { - manageMenuRepository.deleteById(menuId); - } + public void delete(Long menuId) { + manageMenuRepository.deleteById(menuId); + } - public Boolean changeDiscontinuedStatus(Long menuId) { - if (isFixedMenu(menuId)) { - throw new BaseException(BaseResponseStatus.NOT_SUPPORT_RESTAURANT); - } + public Boolean changeDiscontinuedStatus(Long menuId) { + if (isFixedMenu(menuId)) { + throw new BaseException(BaseResponseStatus.NOT_SUPPORT_RESTAURANT); + } - Menu menu = manageMenuRepository.findById(menuId) - .orElseThrow(() -> new BaseException(BaseResponseStatus.NOT_FOUND_MENU)); + Menu menu = manageMenuRepository.findById(menuId) + .orElseThrow(() -> new BaseException(BaseResponseStatus.NOT_FOUND_MENU)); - menu.changeDiscontinuedStatus(); + menu.changeDiscontinuedStatus(); - return menu.isDiscontinued(); - } + return menu.isDiscontinued(); + } } diff --git a/src/main/java/ssu/eatssu/domain/admin/service/ManageInquiryService.java b/src/main/java/ssu/eatssu/domain/admin/service/ManageInquiryService.java index cf0981da..e9c765b1 100644 --- a/src/main/java/ssu/eatssu/domain/admin/service/ManageInquiryService.java +++ b/src/main/java/ssu/eatssu/domain/admin/service/ManageInquiryService.java @@ -1,10 +1,9 @@ package ssu.eatssu.domain.admin.service; +import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; - -import lombok.RequiredArgsConstructor; import ssu.eatssu.domain.admin.dto.InquiryLine; import ssu.eatssu.domain.admin.dto.PageWrapper; import ssu.eatssu.domain.admin.dto.UpdateStatusRequest; @@ -18,23 +17,23 @@ @RequiredArgsConstructor public class ManageInquiryService { - private final LoadInquiryRepository loadInquiryRepository; - private final ManageInquiryRepository manageInquiryRepository; + private final LoadInquiryRepository loadInquiryRepository; + private final ManageInquiryRepository manageInquiryRepository; - public PageWrapper getInquiryBoard(Pageable pageable) { - Page inquiries = loadInquiryRepository.findAllInquiries(pageable); - return new PageWrapper<>(inquiries.map(InquiryLine::new)); - } + public PageWrapper getInquiryBoard(Pageable pageable) { + Page inquiries = loadInquiryRepository.findAllInquiries(pageable); + return new PageWrapper<>(inquiries.map(InquiryLine::new)); + } - public void updateStatus(Long inquiryId, UpdateStatusRequest request) { - Inquiry inquiry = manageInquiryRepository.findById(inquiryId) - .orElseThrow(() -> new BaseException( - BaseResponseStatus.NOT_FOUND_USER_INQUIRY)); - inquiry.updateStatus(request.status()); - manageInquiryRepository.save(inquiry); - } + public void updateStatus(Long inquiryId, UpdateStatusRequest request) { + Inquiry inquiry = manageInquiryRepository.findById(inquiryId) + .orElseThrow(() -> new BaseException( + BaseResponseStatus.NOT_FOUND_USER_INQUIRY)); + inquiry.updateStatus(request.status()); + manageInquiryRepository.save(inquiry); + } - public void delete(Long inquiryId) { - manageInquiryRepository.deleteById(inquiryId); - } + public void delete(Long inquiryId) { + manageInquiryRepository.deleteById(inquiryId); + } } diff --git a/src/main/java/ssu/eatssu/domain/admin/service/ManageMealService.java b/src/main/java/ssu/eatssu/domain/admin/service/ManageMealService.java index 3a27dc8b..24397d23 100644 --- a/src/main/java/ssu/eatssu/domain/admin/service/ManageMealService.java +++ b/src/main/java/ssu/eatssu/domain/admin/service/ManageMealService.java @@ -1,11 +1,7 @@ package ssu.eatssu.domain.admin.service; -import java.util.Date; -import java.util.List; - -import org.springframework.stereotype.Service; - import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; import ssu.eatssu.domain.admin.dto.BriefMenu; import ssu.eatssu.domain.admin.dto.MealInfo; import ssu.eatssu.domain.admin.dto.MealSection; @@ -26,112 +22,115 @@ import ssu.eatssu.global.handler.response.BaseException; import ssu.eatssu.global.handler.response.BaseResponseStatus; +import java.util.Date; +import java.util.List; + @RequiredArgsConstructor @Service public class ManageMealService { - private final LoadMealRepository loadMealRepository; - private final MenuRatingRepository menuRatingRepository; - private final ManageMealRepository manageMealRepository; - private final ManageMealMenuRepository manageMealMenuRepository; - private final ManageMenuRepository manageMenuRepository; - - public MenuBoards getMenuBoards(Date date, TimePart timePart) { - MenuBoards menuBoards = new MenuBoards(); - - RestaurantType.VARIABLE.getRestaurants() - .forEach(restaurant -> menuBoards.add( - getMealBoard(new MealInfo(restaurant, date, timePart)))); - - return menuBoards; - } - - private MenuBoard getMealBoard(MealInfo mealInfo) { - MenuBoard menuBoard = new MenuBoard(mealInfo.restaurant().getRestaurantName()); - - getMenuSections(mealInfo).forEach(menuBoard::addSection); - - return menuBoard; - } - - private List getMenuSections(MealInfo mealInfo) { - List mealIds = loadMealRepository.getAllMealIdsByInfo(mealInfo); - - return mealIds.stream().map(this::getMenuSection).toList(); - } - - private MealSection getMenuSection(Long mealId) { - MealSection mealSection = new MealSection(mealId); - - List briefMenus = loadMealRepository.findBriefMenusByMealId(mealId); - - briefMenus.forEach(briefMenu -> - mealSection.addMenuLine(new MenuLine( - briefMenu.id(), - briefMenu.name(), - briefMenu.price(), - menuRatingRepository.getMainRatingAverage(briefMenu.id()) - )) - ); - - return mealSection; - } - - public void register(MealInfo mealInfo, RegisterMealRequest request) { - // 중복 체크 - duplicateCheck(mealInfo, request); - - // 식단 등록 - Meal meal = new Meal(mealInfo.date(), mealInfo.timePart(), mealInfo.restaurant()); - manageMealRepository.save(meal); - - //식단에 메뉴 추가 - addMenusToMeal(meal, request.menuNames()); - - } - - private void addMenusToMeal(Meal meal, List menuNames) { - menuNames.forEach(menuName -> { - Menu menu = loadMealRepository.findMenu(menuName, meal.getRestaurant()); - if (menu == null) { - menu = Menu.createVariable(menuName, meal.getRestaurant()); - manageMenuRepository.save(menu); - } - manageMealMenuRepository.save(MealMenu.builder().meal(meal).menu(menu).build()); - }); - } - - private void duplicateCheck(MealInfo mealInfo, RegisterMealRequest request) { - List mealIds = loadMealRepository.existsMeal(mealInfo); - mealIds.forEach(mealId -> { - if (hasMenus(mealId, request.menuNames())) { - throw new BaseException(BaseResponseStatus.CONFLICT); - } - }); - } - - private boolean hasMenus(Long mealId, List menuNames) { - List menuNamesInMeal = loadMealRepository.findMenuNamesByMealId(mealId); - - /** - * menuNamesInMeal 과 menuNames 는 오름차순 정렬된 상태 - * @see LoadMealRepository#findMenuNamesByMealId(Long) - * @see RegisterMealRequest#RegisterMealRequest - */ - return menuNamesInMeal.equals(menuNames); - } - - public void delete(Long mealId) { - List menuIds = loadMealRepository.getAllMenuIds(mealId); - manageMealRepository.deleteById(mealId); - deleteUnusedMenus(menuIds); - } - - private void deleteUnusedMenus(List menuIds) { - menuIds.forEach(menuId -> { - if (loadMealRepository.countMealMenuByMenuId(menuId) == 0) { - manageMenuRepository.deleteById(menuId); - } - }); - } + private final LoadMealRepository loadMealRepository; + private final MenuRatingRepository menuRatingRepository; + private final ManageMealRepository manageMealRepository; + private final ManageMealMenuRepository manageMealMenuRepository; + private final ManageMenuRepository manageMenuRepository; + + public MenuBoards getMenuBoards(Date date, TimePart timePart) { + MenuBoards menuBoards = new MenuBoards(); + + RestaurantType.VARIABLE.getRestaurants() + .forEach(restaurant -> menuBoards.add( + getMealBoard(new MealInfo(restaurant, date, timePart)))); + + return menuBoards; + } + + private MenuBoard getMealBoard(MealInfo mealInfo) { + MenuBoard menuBoard = new MenuBoard(mealInfo.restaurant().getRestaurantName()); + + getMenuSections(mealInfo).forEach(menuBoard::addSection); + + return menuBoard; + } + + private List getMenuSections(MealInfo mealInfo) { + List mealIds = loadMealRepository.getAllMealIdsByInfo(mealInfo); + + return mealIds.stream().map(this::getMenuSection).toList(); + } + + private MealSection getMenuSection(Long mealId) { + MealSection mealSection = new MealSection(mealId); + + List briefMenus = loadMealRepository.findBriefMenusByMealId(mealId); + + briefMenus.forEach(briefMenu -> + mealSection.addMenuLine(new MenuLine( + briefMenu.id(), + briefMenu.name(), + briefMenu.price(), + menuRatingRepository.getMainRatingAverage(briefMenu.id()) + )) + ); + + return mealSection; + } + + public void register(MealInfo mealInfo, RegisterMealRequest request) { + // 중복 체크 + duplicateCheck(mealInfo, request); + + // 식단 등록 + Meal meal = new Meal(mealInfo.date(), mealInfo.timePart(), mealInfo.restaurant()); + manageMealRepository.save(meal); + + //식단에 메뉴 추가 + addMenusToMeal(meal, request.menuNames()); + + } + + private void addMenusToMeal(Meal meal, List menuNames) { + menuNames.forEach(menuName -> { + Menu menu = loadMealRepository.findMenu(menuName, meal.getRestaurant()); + if (menu == null) { + menu = Menu.createVariable(menuName, meal.getRestaurant()); + manageMenuRepository.save(menu); + } + manageMealMenuRepository.save(MealMenu.builder().meal(meal).menu(menu).build()); + }); + } + + private void duplicateCheck(MealInfo mealInfo, RegisterMealRequest request) { + List mealIds = loadMealRepository.existsMeal(mealInfo); + mealIds.forEach(mealId -> { + if (hasMenus(mealId, request.menuNames())) { + throw new BaseException(BaseResponseStatus.CONFLICT); + } + }); + } + + private boolean hasMenus(Long mealId, List menuNames) { + List menuNamesInMeal = loadMealRepository.findMenuNamesByMealId(mealId); + + /** + * menuNamesInMeal 과 menuNames 는 오름차순 정렬된 상태 + * @see LoadMealRepository#findMenuNamesByMealId(Long) + * @see RegisterMealRequest#RegisterMealRequest + */ + return menuNamesInMeal.equals(menuNames); + } + + public void delete(Long mealId) { + List menuIds = loadMealRepository.getAllMenuIds(mealId); + manageMealRepository.deleteById(mealId); + deleteUnusedMenus(menuIds); + } + + private void deleteUnusedMenus(List menuIds) { + menuIds.forEach(menuId -> { + if (loadMealRepository.countMealMenuByMenuId(menuId) == 0) { + manageMenuRepository.deleteById(menuId); + } + }); + } } diff --git a/src/main/java/ssu/eatssu/domain/admin/service/ManageReportService.java b/src/main/java/ssu/eatssu/domain/admin/service/ManageReportService.java index 1495698f..dff28a17 100644 --- a/src/main/java/ssu/eatssu/domain/admin/service/ManageReportService.java +++ b/src/main/java/ssu/eatssu/domain/admin/service/ManageReportService.java @@ -1,35 +1,34 @@ package ssu.eatssu.domain.admin.service; -import java.util.List; - +import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; - -import lombok.RequiredArgsConstructor; import ssu.eatssu.domain.admin.dto.PageWrapper; import ssu.eatssu.domain.admin.dto.ReportLine; import ssu.eatssu.domain.admin.persistence.LoadReportRepository; import ssu.eatssu.domain.admin.persistence.ManageReportRepository; +import java.util.List; + @Service @RequiredArgsConstructor public class ManageReportService { - private final LoadReportRepository loadReportRepository; - private final ManageReportRepository manageReportRepository; + private final LoadReportRepository loadReportRepository; + private final ManageReportRepository manageReportRepository; - public PageWrapper getReportBoard(Pageable pageable) { - Page reports = loadReportRepository.findAll(pageable); - return new PageWrapper<>(reports); - } + public PageWrapper getReportBoard(Pageable pageable) { + Page reports = loadReportRepository.findAll(pageable); + return new PageWrapper<>(reports); + } - public void delete(Long reportId) { - manageReportRepository.deleteById(reportId); - } + public void delete(Long reportId) { + manageReportRepository.deleteById(reportId); + } - public void deleteAllByReviewId(Long reviewId) { - List reportIds = loadReportRepository.findAllByReviewId(reviewId); - manageReportRepository.deleteAllByIdInBatch(reportIds); - } + public void deleteAllByReviewId(Long reviewId) { + List reportIds = loadReportRepository.findAllByReviewId(reviewId); + manageReportRepository.deleteAllByIdInBatch(reportIds); + } } diff --git a/src/main/java/ssu/eatssu/domain/admin/service/ManageReviewService.java b/src/main/java/ssu/eatssu/domain/admin/service/ManageReviewService.java index 2e158888..fc3b92fb 100644 --- a/src/main/java/ssu/eatssu/domain/admin/service/ManageReviewService.java +++ b/src/main/java/ssu/eatssu/domain/admin/service/ManageReviewService.java @@ -1,10 +1,9 @@ package ssu.eatssu.domain.admin.service; -import org.springframework.context.ApplicationEventPublisher; -import org.springframework.stereotype.Service; - import jakarta.transaction.Transactional; import lombok.RequiredArgsConstructor; +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.stereotype.Service; import ssu.eatssu.domain.admin.event.ReviewDeleteEvent; import ssu.eatssu.domain.admin.persistence.ManageReviewRepository; @@ -12,13 +11,13 @@ @Transactional @RequiredArgsConstructor public class ManageReviewService { - private final ManageReviewRepository manageReviewRepository; - private final ApplicationEventPublisher publisher; + private final ManageReviewRepository manageReviewRepository; + private final ApplicationEventPublisher publisher; - public void delete(Long reviewId) { - // 리뷰 삭제하기 전에 해당 리뷰를 참조하는 신고들을 모두 삭제한다. - publisher.publishEvent(new ReviewDeleteEvent(reviewId)); + public void delete(Long reviewId) { + // 리뷰 삭제하기 전에 해당 리뷰를 참조하는 신고들을 모두 삭제한다. + publisher.publishEvent(new ReviewDeleteEvent(reviewId)); - manageReviewRepository.deleteById(reviewId); - } + manageReviewRepository.deleteById(reviewId); + } } diff --git a/src/main/java/ssu/eatssu/domain/auth/dto/AppleKeys.java b/src/main/java/ssu/eatssu/domain/auth/dto/AppleKeys.java index a4a9b42b..57dc09b2 100644 --- a/src/main/java/ssu/eatssu/domain/auth/dto/AppleKeys.java +++ b/src/main/java/ssu/eatssu/domain/auth/dto/AppleKeys.java @@ -1,31 +1,31 @@ package ssu.eatssu.domain.auth.dto; -import java.util.List; -import java.util.Optional; - import lombok.Getter; import lombok.NoArgsConstructor; +import java.util.List; +import java.util.Optional; + @Getter @NoArgsConstructor public class AppleKeys { - private List keys; + private List keys; - public Optional findKeyBy(String kid, String alg) { - return this.keys.stream() - .filter(key -> key.getKid().equals(kid) && key.getAlg().equals(alg)) - .findFirst(); - } + public Optional findKeyBy(String kid, String alg) { + return this.keys.stream() + .filter(key -> key.getKid().equals(kid) && key.getAlg().equals(alg)) + .findFirst(); + } - @Getter - @NoArgsConstructor - public static class Key { - private String kty; - private String kid; - private String use; - private String alg; - private String n; - private String e; + @Getter + @NoArgsConstructor + public static class Key { + private String kty; + private String kid; + private String use; + private String alg; + private String n; + private String e; - } + } } 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 78beecd1..2d5416ad 100644 --- a/src/main/java/ssu/eatssu/domain/auth/dto/AppleLoginRequest.java +++ b/src/main/java/ssu/eatssu/domain/auth/dto/AppleLoginRequest.java @@ -4,8 +4,8 @@ @Schema(title = "애플 로그인 및 회원가입") public record AppleLoginRequest( - @Schema(description = "identityToken", example = "eyJraWQiOiJXNldjT0tCIiwiYWxnIjoi...") - String identityToken + @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 211677b1..eacf8e65 100644 --- a/src/main/java/ssu/eatssu/domain/auth/dto/KakaoLoginRequest.java +++ b/src/main/java/ssu/eatssu/domain/auth/dto/KakaoLoginRequest.java @@ -6,13 +6,13 @@ @Schema(title = "카카오 로그인 및 회원가입") public record KakaoLoginRequest( - @NotBlank(message = "이메일을 입력해주세요.") - @Email(message = "올바른 이메일 주소를 입력해주세요.") - @Schema(description = "이메일", example = "test@email.com") - String email, + @NotBlank(message = "이메일을 입력해주세요.") + @Email(message = "올바른 이메일 주소를 입력해주세요.") + @Schema(description = "이메일", example = "test@email.com") + String email, - @Schema(description = "providerId", example = "10378247832195") - String providerId + @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 e2f85ce2..748028e6 100644 --- a/src/main/java/ssu/eatssu/domain/auth/dto/ValidRequest.java +++ b/src/main/java/ssu/eatssu/domain/auth/dto/ValidRequest.java @@ -5,9 +5,9 @@ @Schema(title = "유효한 토큰 확인") public record ValidRequest( - @NotBlank(message = "토큰을 입력해주세요") - @Schema(description = "토큰", example = "eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJ7XCJpZFwiOjcsXCJlbWFpbFwiOlwidGVzdEBlbWFpbC5jb21cIixcInJvbGVcIjpcIlJPTEVfVVNFUlwifSIsImF1dGgiOiJST0xFX1VTRVIiLCJleHAiOjE3NDQzNzQ0MjB9.mhIWYX_Vj3xW1eXuVflbzpH6vLTcC9b1twbIcqovVjDVnS7tjegu3nQHGXUsUa_WG2DIAtJMFZT_Q1XcVq1jPw") - String token + @NotBlank(message = "토큰을 입력해주세요") + @Schema(description = "토큰", example = "eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJ7XCJpZFwiOjcsXCJlbWFpbFwiOlwidGVzdEBlbWFpbC5jb21cIixcInJvbGVcIjpcIlJPTEVfVVNFUlwifSIsImF1dGgiOiJST0xFX1VTRVIiLCJleHAiOjE3NDQzNzQ0MjB9.mhIWYX_Vj3xW1eXuVflbzpH6vLTcC9b1twbIcqovVjDVnS7tjegu3nQHGXUsUa_WG2DIAtJMFZT_Q1XcVq1jPw") + String token ) { } diff --git a/src/main/java/ssu/eatssu/domain/auth/entity/AppleAuthenticator.java b/src/main/java/ssu/eatssu/domain/auth/entity/AppleAuthenticator.java index e56acb4f..f150fb37 100644 --- a/src/main/java/ssu/eatssu/domain/auth/entity/AppleAuthenticator.java +++ b/src/main/java/ssu/eatssu/domain/auth/entity/AppleAuthenticator.java @@ -4,5 +4,5 @@ public interface AppleAuthenticator { - OAuthInfo getOAuthInfoByIdentityToken(String identityToken); + OAuthInfo getOAuthInfoByIdentityToken(String identityToken); } diff --git a/src/main/java/ssu/eatssu/domain/auth/entity/OAuthProvider.java b/src/main/java/ssu/eatssu/domain/auth/entity/OAuthProvider.java index 641d1bb3..5c8a22fc 100644 --- a/src/main/java/ssu/eatssu/domain/auth/entity/OAuthProvider.java +++ b/src/main/java/ssu/eatssu/domain/auth/entity/OAuthProvider.java @@ -1,5 +1,5 @@ package ssu.eatssu.domain.auth.entity; public enum OAuthProvider { - EATSSU, KAKAO, APPLE; + EATSSU, KAKAO, APPLE } diff --git a/src/main/java/ssu/eatssu/domain/auth/entity/SystemAppleAuthenticator.java b/src/main/java/ssu/eatssu/domain/auth/entity/SystemAppleAuthenticator.java index 34300152..904c99b6 100644 --- a/src/main/java/ssu/eatssu/domain/auth/entity/SystemAppleAuthenticator.java +++ b/src/main/java/ssu/eatssu/domain/auth/entity/SystemAppleAuthenticator.java @@ -1,6 +1,19 @@ package ssu.eatssu.domain.auth.entity; -import static ssu.eatssu.global.handler.response.BaseResponseStatus.*; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.ExpiredJwtException; +import io.jsonwebtoken.Jwts; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Component; +import org.springframework.web.client.RestTemplate; +import org.springframework.web.util.UriComponentsBuilder; +import ssu.eatssu.domain.auth.dto.AppleKeys; +import ssu.eatssu.domain.auth.dto.OAuthInfo; +import ssu.eatssu.global.handler.response.BaseException; import java.math.BigInteger; import java.net.URI; @@ -12,126 +25,111 @@ import java.util.Base64; import java.util.Map; -import org.springframework.http.ResponseEntity; -import org.springframework.stereotype.Component; -import org.springframework.web.client.RestTemplate; -import org.springframework.web.util.UriComponentsBuilder; - -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.core.type.TypeReference; -import com.fasterxml.jackson.databind.ObjectMapper; - -import io.jsonwebtoken.Claims; -import io.jsonwebtoken.ExpiredJwtException; -import io.jsonwebtoken.Jwts; -import lombok.RequiredArgsConstructor; -import ssu.eatssu.domain.auth.dto.AppleKeys; -import ssu.eatssu.domain.auth.dto.OAuthInfo; -import ssu.eatssu.global.handler.response.BaseException; +import static ssu.eatssu.global.handler.response.BaseResponseStatus.INVALID_IDENTITY_TOKEN; @Component @RequiredArgsConstructor public class SystemAppleAuthenticator implements AppleAuthenticator { - private final RestTemplate restTemplate; - - public OAuthInfo getOAuthInfoByIdentityToken(String identityToken) { - PublicKey publicKey = generatePublicKey(identityToken); - return getOAuthInfoByPublicKey(identityToken, publicKey); - } - - /** - * 애플 로그인 - PublicKey 를 통해 유저 정보(providerId, email) 조회 - */ - private OAuthInfo getOAuthInfoByPublicKey(String identityToken, PublicKey publicKey) { - // identityToken 에서 publicKey 서명을 통해 Claims 를 추출한다. - Claims claims = Jwts.parserBuilder() - .setSigningKey(publicKey) - .build() - .parseClaimsJws(identityToken) - .getBody(); - - //Claims 에서 email, providerId(사용자 식별값) 를 추출한다. - try { - String email = claims.get("email").toString(); - String providerId = claims.get("sub").toString(); - return new OAuthInfo(email, providerId); - } catch (ExpiredJwtException exception) { - throw new BaseException(INVALID_IDENTITY_TOKEN); - } - } - - private PublicKey generatePublicKey(String identityToken) { - //PublicKey 를 만들기 위한 재료가 되는 후보 Key 목록을 가져온다. - AppleKeys keys = getAppleKeys(); - - //후보 Key 에서 정답 Key 를 찾는다. - AppleKeys.Key matchedKey = selectMatchedKey(identityToken, keys); - - //정답 Key 를 통해 PublicKey 를 생성한다. - return generatePublicKeyWithApplePublicKey(matchedKey); - } - - /** - * 애플 로그인 - 헤더에서 뽑은 정보를 통해 후보 Key 에서 정답 Key 를 찾아서 반환한다. - */ - private AppleKeys.Key selectMatchedKey(String identityToken, AppleKeys candidateKeys) { - - //identity token 에서 header 를 뽑아서 decode 한다. - String header = identityToken.split("\\.")[0]; - String decodedHeader = new String(Base64.getDecoder().decode(header)); - - //decode 된 header 정보를 통해 정답키의 key id, algorithm 정보를 가져온다. - Map headerMap; - try { - headerMap = new ObjectMapper().readValue(decodedHeader, - new TypeReference>() { - }); - } catch (JsonProcessingException e) { - throw new BaseException(INVALID_IDENTITY_TOKEN); - } - - //후보키 중에서 정답키를 찾아서 반환한다. - return candidateKeys.findKeyBy(headerMap.get("kid"), headerMap.get("alg")) - .orElseThrow(() -> new BaseException(INVALID_IDENTITY_TOKEN)); - } - - /** - * 애플 로그인 - 정답 Key 를 통해 PublicKey 를 생성한다. - */ - private PublicKey generatePublicKeyWithApplePublicKey(AppleKeys.Key matchedKey) { - - //정답 키에서 PublicKey 의 재료가 될 n, e 값을 가져온다. - byte[] nBytes = Base64.getUrlDecoder().decode(matchedKey.getN()); - byte[] eBytes = Base64.getUrlDecoder().decode(matchedKey.getE()); - - BigInteger n = new BigInteger(1, nBytes); - BigInteger e = new BigInteger(1, eBytes); - - // n, e 값을 통해 PublicKeySpec 을 세팅한다. - RSAPublicKeySpec publicKeySpec = new RSAPublicKeySpec(n, e); - - //PublicKeySpec 을 통해 PublicKey 를 생성한다. - try { - KeyFactory keyFactory = KeyFactory.getInstance(matchedKey.getKty()); - return keyFactory.generatePublic(publicKeySpec); - } catch (NoSuchAlgorithmException | InvalidKeySpecException ex) { - throw new BaseException(INVALID_IDENTITY_TOKEN); - } - } - - /** - * 애플 로그인 - Apple api 호출을 통해 apple 후보 key 리스트를 받아온다. - */ - private AppleKeys getAppleKeys() { - URI uri = UriComponentsBuilder - .fromUriString("https://appleid.apple.com") - .path("/auth/keys") - .encode() - .build() - .toUri(); - - ResponseEntity response = restTemplate.getForEntity(uri, AppleKeys.class); - return response.getBody(); - } + private final RestTemplate restTemplate; + + public OAuthInfo getOAuthInfoByIdentityToken(String identityToken) { + PublicKey publicKey = generatePublicKey(identityToken); + return getOAuthInfoByPublicKey(identityToken, publicKey); + } + + /** + * 애플 로그인 - PublicKey 를 통해 유저 정보(providerId, email) 조회 + */ + private OAuthInfo getOAuthInfoByPublicKey(String identityToken, PublicKey publicKey) { + // identityToken 에서 publicKey 서명을 통해 Claims 를 추출한다. + Claims claims = Jwts.parserBuilder() + .setSigningKey(publicKey) + .build() + .parseClaimsJws(identityToken) + .getBody(); + + //Claims 에서 email, providerId(사용자 식별값) 를 추출한다. + try { + String email = claims.get("email").toString(); + String providerId = claims.get("sub").toString(); + return new OAuthInfo(email, providerId); + } catch (ExpiredJwtException exception) { + throw new BaseException(INVALID_IDENTITY_TOKEN); + } + } + + private PublicKey generatePublicKey(String identityToken) { + //PublicKey 를 만들기 위한 재료가 되는 후보 Key 목록을 가져온다. + AppleKeys keys = getAppleKeys(); + + //후보 Key 에서 정답 Key 를 찾는다. + AppleKeys.Key matchedKey = selectMatchedKey(identityToken, keys); + + //정답 Key 를 통해 PublicKey 를 생성한다. + return generatePublicKeyWithApplePublicKey(matchedKey); + } + + /** + * 애플 로그인 - 헤더에서 뽑은 정보를 통해 후보 Key 에서 정답 Key 를 찾아서 반환한다. + */ + private AppleKeys.Key selectMatchedKey(String identityToken, AppleKeys candidateKeys) { + + //identity token 에서 header 를 뽑아서 decode 한다. + String header = identityToken.split("\\.")[0]; + String decodedHeader = new String(Base64.getDecoder().decode(header)); + + //decode 된 header 정보를 통해 정답키의 key id, algorithm 정보를 가져온다. + Map headerMap; + try { + headerMap = new ObjectMapper().readValue(decodedHeader, + new TypeReference>() { + }); + } catch (JsonProcessingException e) { + throw new BaseException(INVALID_IDENTITY_TOKEN); + } + + //후보키 중에서 정답키를 찾아서 반환한다. + return candidateKeys.findKeyBy(headerMap.get("kid"), headerMap.get("alg")) + .orElseThrow(() -> new BaseException(INVALID_IDENTITY_TOKEN)); + } + + /** + * 애플 로그인 - 정답 Key 를 통해 PublicKey 를 생성한다. + */ + private PublicKey generatePublicKeyWithApplePublicKey(AppleKeys.Key matchedKey) { + + //정답 키에서 PublicKey 의 재료가 될 n, e 값을 가져온다. + byte[] nBytes = Base64.getUrlDecoder().decode(matchedKey.getN()); + byte[] eBytes = Base64.getUrlDecoder().decode(matchedKey.getE()); + + BigInteger n = new BigInteger(1, nBytes); + BigInteger e = new BigInteger(1, eBytes); + + // n, e 값을 통해 PublicKeySpec 을 세팅한다. + RSAPublicKeySpec publicKeySpec = new RSAPublicKeySpec(n, e); + + //PublicKeySpec 을 통해 PublicKey 를 생성한다. + try { + KeyFactory keyFactory = KeyFactory.getInstance(matchedKey.getKty()); + return keyFactory.generatePublic(publicKeySpec); + } catch (NoSuchAlgorithmException | InvalidKeySpecException ex) { + throw new BaseException(INVALID_IDENTITY_TOKEN); + } + } + + /** + * 애플 로그인 - Apple api 호출을 통해 apple 후보 key 리스트를 받아온다. + */ + private AppleKeys getAppleKeys() { + URI uri = UriComponentsBuilder + .fromUriString("https://appleid.apple.com") + .path("/auth/keys") + .encode() + .build() + .toUri(); + + ResponseEntity response = restTemplate.getForEntity(uri, AppleKeys.class); + return response.getBody(); + } } diff --git a/src/main/java/ssu/eatssu/domain/auth/infrastructure/OAuthConfig.java b/src/main/java/ssu/eatssu/domain/auth/infrastructure/OAuthConfig.java index 56674dc6..7e70e0b6 100644 --- a/src/main/java/ssu/eatssu/domain/auth/infrastructure/OAuthConfig.java +++ b/src/main/java/ssu/eatssu/domain/auth/infrastructure/OAuthConfig.java @@ -10,12 +10,12 @@ @Configuration public class OAuthConfig { - @Bean - public RestTemplate restTemplate() { - HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory(); - CloseableHttpClient client = HttpClientBuilder.create().build(); - factory.setConnectTimeout(3000); //3s - factory.setHttpClient(client); - return new RestTemplate(factory); - } + @Bean + public RestTemplate restTemplate() { + HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory(); + CloseableHttpClient client = HttpClientBuilder.create().build(); + factory.setConnectTimeout(3000); //3s + factory.setHttpClient(client); + return new RestTemplate(factory); + } } 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 3804b923..77c3adfd 100644 --- a/src/main/java/ssu/eatssu/domain/auth/infrastructure/SecurityConfig.java +++ b/src/main/java/ssu/eatssu/domain/auth/infrastructure/SecurityConfig.java @@ -1,5 +1,6 @@ package ssu.eatssu.domain.auth.infrastructure; +import lombok.RequiredArgsConstructor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.web.builders.HttpSecurity; @@ -11,10 +12,9 @@ import org.springframework.web.cors.CorsConfiguration; import org.springframework.web.cors.CorsConfigurationSource; import org.springframework.web.cors.UrlBasedCorsConfigurationSource; - -import lombok.RequiredArgsConstructor; 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; @@ -22,64 +22,65 @@ @Configuration @RequiredArgsConstructor public class SecurityConfig { - private static final String[] RESOURCE_LIST = { - "/swagger-ui.html", "/swagger-ui/**", "/v3/api-docs/**", "/swagger-resources/**","/oauths/valid/token", "/admin/img/**", "/css/**", "/js/**", - "/favicon.ico", "/error/**", "/webjars/**", "/h2-console/**" - }; + private static final String[] RESOURCE_LIST = { + "/swagger-ui.html", "/swagger-ui/**", "/v3/api-docs/**", "/swagger-resources/**", "/oauths/valid/token", "/admin/img/**", "/css/**", "/js/**", + "/favicon.ico", "/error/**", "/webjars/**", "/h2-console/**" + }; - private static final String[] AUTH_WHITELIST = { - "/", "/oauths/kakao", "/oauths/apple", "/menus/**", "/meals/**", "/admin/login", - "/reviews", "/reviews/menus/**", "/reviews/meals/**", "/v2/reviews/statistics", "/v2/reviews", - "/partnerships/**","/v2/reviews/menus/**","/v2/reviews/meals/**","/actuator/**","/error-test/**" - }; + private static final String[] AUTH_WHITELIST = { + "/", "/oauths/kakao", "/oauths/apple", "/menus/**", "/meals/**", "/admin/login", + "/reviews", "/reviews/menus/**", "/reviews/meals/**", "/v2/reviews/statistics/**", + "/v2/reviews/menus/**", "/v2/reviews/meals/**", "/actuator/**", "/error-test/**" + }; - private static final String[] ADMIN_PAGE_LIST = { - "/admin/**" - }; + private static final String[] ADMIN_PAGE_LIST = { + "/admin/**" + }; - private final JwtTokenProvider jwtTokenProvider; - private final JwtAccessDeniedHandler jwtAccessDeniedHandler; - private final JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint; + private final JwtTokenProvider jwtTokenProvider; + private final JwtAccessDeniedHandler jwtAccessDeniedHandler; + private final JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint; + private final SlackErrorNotifier slackErrorNotifier; - @Bean - public PasswordEncoder passwordEncoder() { - return new BCryptPasswordEncoder(); - } + @Bean + public PasswordEncoder passwordEncoder() { + return new BCryptPasswordEncoder(); + } - @Bean - protected SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - http - .csrf().disable() - .authorizeHttpRequests(authorize -> authorize - .shouldFilterAllDispatcherTypes(false) - .requestMatchers("/partnerships/*/like").authenticated() - .requestMatchers(AUTH_WHITELIST).permitAll() - .requestMatchers(RESOURCE_LIST).permitAll() - .requestMatchers(ADMIN_PAGE_LIST).hasRole("ADMIN") - .anyRequest().authenticated() - .and().addFilterBefore(new JwtAuthenticationFilter(jwtTokenProvider), - UsernamePasswordAuthenticationFilter.class)) - .exceptionHandling() - .accessDeniedHandler(jwtAccessDeniedHandler) - .authenticationEntryPoint(jwtAuthenticationEntryPoint); + @Bean + protected SecurityFilterChain filterChain(HttpSecurity http) throws Exception { + http + .csrf().disable() + .authorizeHttpRequests(authorize -> authorize + .shouldFilterAllDispatcherTypes(false) + .requestMatchers("/partnerships/*/like").authenticated() + .requestMatchers(AUTH_WHITELIST).permitAll() + .requestMatchers(RESOURCE_LIST).permitAll() + .requestMatchers(ADMIN_PAGE_LIST).hasRole("ADMIN") + .anyRequest().authenticated() + .and().addFilterBefore(new JwtAuthenticationFilter(jwtTokenProvider, slackErrorNotifier), + UsernamePasswordAuthenticationFilter.class)) + .exceptionHandling() + .accessDeniedHandler(jwtAccessDeniedHandler) + .authenticationEntryPoint(jwtAuthenticationEntryPoint); - http - .cors().configurationSource(corsConfigurationSource()); - return http.build(); - } + http + .cors().configurationSource(corsConfigurationSource()); + return http.build(); + } - @Bean - public CorsConfigurationSource corsConfigurationSource() { - CorsConfiguration configuration = new CorsConfiguration(); + @Bean + public CorsConfigurationSource corsConfigurationSource() { + CorsConfiguration configuration = new CorsConfiguration(); - configuration.addAllowedOriginPattern("*"); - configuration.addAllowedHeader("*"); - configuration.addAllowedMethod("*"); - configuration.setAllowCredentials(true); + configuration.addAllowedOriginPattern("*"); + configuration.addAllowedHeader("*"); + configuration.addAllowedMethod("*"); + configuration.setAllowCredentials(true); - UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); - source.registerCorsConfiguration("/**", configuration); - return source; - } + UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); + source.registerCorsConfiguration("/**", configuration); + return source; + } } diff --git a/src/main/java/ssu/eatssu/domain/auth/infrastructure/SecurityUtil.java b/src/main/java/ssu/eatssu/domain/auth/infrastructure/SecurityUtil.java index 31f6a34f..62e3f845 100644 --- a/src/main/java/ssu/eatssu/domain/auth/infrastructure/SecurityUtil.java +++ b/src/main/java/ssu/eatssu/domain/auth/infrastructure/SecurityUtil.java @@ -4,36 +4,35 @@ import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.stereotype.Component; - import ssu.eatssu.domain.auth.security.CustomUserDetails; import ssu.eatssu.domain.auth.security.UserPrincipalDto; @Component public class SecurityUtil { - public static UserPrincipalDto getLoginUserPrincipal() { - Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal(); - if (principal instanceof UserDetails) { - return toDto(principal); - } else { - return null; - } - } + public static UserPrincipalDto getLoginUserPrincipal() { + Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal(); + if (principal instanceof UserDetails) { + return toDto(principal); + } else { + return null; + } + } - public static Authentication getLoginUser() { - return SecurityContextHolder.getContext().getAuthentication(); - } + public static Authentication getLoginUser() { + return SecurityContextHolder.getContext().getAuthentication(); + } - public static Long getLoginUserId() { - UserPrincipalDto loginUser = getLoginUserPrincipal(); - return loginUser != null ? loginUser.getId() : null; - } + public static Long getLoginUserId() { + UserPrincipalDto loginUser = getLoginUserPrincipal(); + return loginUser != null ? loginUser.getId() : null; + } - private static UserPrincipalDto toDto(Object principal) { - CustomUserDetails user = (CustomUserDetails)principal; + private static UserPrincipalDto toDto(Object principal) { + CustomUserDetails user = (CustomUserDetails) principal; - return UserPrincipalDto.from(user); + return UserPrincipalDto.from(user); - } + } } diff --git a/src/main/java/ssu/eatssu/domain/auth/presentation/OAuthController.java b/src/main/java/ssu/eatssu/domain/auth/presentation/OAuthController.java index c7b19817..d80b88fd 100644 --- a/src/main/java/ssu/eatssu/domain/auth/presentation/OAuthController.java +++ b/src/main/java/ssu/eatssu/domain/auth/presentation/OAuthController.java @@ -1,17 +1,5 @@ package ssu.eatssu.domain.auth.presentation; -import static ssu.eatssu.domain.auth.infrastructure.SecurityUtil.*; - -import java.net.http.HttpRequest; - -import javax.servlet.http.HttpServletRequest; - -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.media.Content; import io.swagger.v3.oas.annotations.media.Schema; @@ -21,6 +9,10 @@ import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; import ssu.eatssu.domain.auth.dto.AppleLoginRequest; import ssu.eatssu.domain.auth.dto.KakaoLoginRequest; import ssu.eatssu.domain.auth.dto.ValidRequest; @@ -28,6 +20,8 @@ import ssu.eatssu.domain.user.dto.Tokens; import ssu.eatssu.global.handler.response.BaseResponse; +import static ssu.eatssu.domain.auth.infrastructure.SecurityUtil.getLoginUser; + @Slf4j @RestController @RequestMapping("/oauths") @@ -35,58 +29,58 @@ @Tag(name = "Oauth", description = "Oauth API") public class OAuthController { - private final OAuthService oauthService; + private final OAuthService oauthService; - @Operation(summary = "카카오 회원가입, 로그인 [인증 토큰 필요 X]", description = """ - 카카오 회원가입, 로그인 API 입니다.

- 가입된 회원일 경우 카카오 로그인, 미가입 회원일 경우 회원가입 후 자동 로그인됩니다. - """) - @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "카카오 회원가입/로그인 성공") - }) - @PostMapping("/kakao") - public BaseResponse kakaoLogin(@Valid @RequestBody KakaoLoginRequest request) { - long startTime = System.currentTimeMillis(); - Tokens tokens = oauthService.kakaoLogin(request); - long endTime = System.currentTimeMillis(); - long duration = endTime - startTime; - log.info("OAuthWarmupRunner 완료 - 소요 시간: {} ms", duration); + @Operation(summary = "카카오 회원가입, 로그인 [인증 토큰 필요 X]", description = """ + 카카오 회원가입, 로그인 API 입니다.

+ 가입된 회원일 경우 카카오 로그인, 미가입 회원일 경우 회원가입 후 자동 로그인됩니다. + """) + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "카카오 회원가입/로그인 성공") + }) + @PostMapping("/kakao") + public BaseResponse kakaoLogin(@Valid @RequestBody KakaoLoginRequest request) { + long startTime = System.currentTimeMillis(); + Tokens tokens = oauthService.kakaoLogin(request); + long endTime = System.currentTimeMillis(); + long duration = endTime - startTime; + log.info("OAuthWarmupRunner 완료 - 소요 시간: {} ms", duration); - return BaseResponse.success(tokens); - } + return BaseResponse.success(tokens); + } - @Operation(summary = "애플 회원가입, 로그인 [인증 토큰 필요 X]", description = """ - 애플 로그인, 회원가입 API 입니다.

- 가입된 회원일 경우 카카오 로그인, 미가입 회원일 경우 회원가입 후 자동 로그인됩니다. - """) - @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "애플 회원가입/로그인 성공"), - @ApiResponse(responseCode = "404", description = "존재하지 않는 유저", content = @Content(schema = @Schema(implementation = BaseResponse.class))) - }) - @PostMapping("/apple") - public BaseResponse appleLogin(@Valid @RequestBody AppleLoginRequest request) { - Tokens tokens = oauthService.appleLogin(request); - return BaseResponse.success(tokens); - } + @Operation(summary = "애플 회원가입, 로그인 [인증 토큰 필요 X]", description = """ + 애플 로그인, 회원가입 API 입니다.

+ 가입된 회원일 경우 카카오 로그인, 미가입 회원일 경우 회원가입 후 자동 로그인됩니다. + """) + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "애플 회원가입/로그인 성공"), + @ApiResponse(responseCode = "404", description = "존재하지 않는 유저", content = @Content(schema = @Schema(implementation = BaseResponse.class))) + }) + @PostMapping("/apple") + public BaseResponse appleLogin(@Valid @RequestBody AppleLoginRequest request) { + Tokens tokens = oauthService.appleLogin(request); + return BaseResponse.success(tokens); + } - @Operation(summary = "토큰 재발급", description = "accessToken, refreshToken 재발급 API 입니다.") - @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "토큰 재발급 성공") - }) - @PostMapping("/reissue/token") - public BaseResponse refreshToken() { - Tokens tokens = oauthService.refreshTokens(getLoginUser()); - return BaseResponse.success(tokens); - } + @Operation(summary = "토큰 재발급", description = "accessToken, refreshToken 재발급 API 입니다.") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "토큰 재발급 성공") + }) + @PostMapping("/reissue/token") + public BaseResponse refreshToken() { + Tokens tokens = oauthService.refreshTokens(getLoginUser()); + return BaseResponse.success(tokens); + } - @Operation(summary = "유효한 토큰 확인 [인증 토큰 필요 X]", description = "해당 토큰이 유효하면 true 반환하는, 유효하지 않은 false 반환하는 API 입니다") - @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "유효한 토큰인지 확인 성공") - }) - @PostMapping("/valid/token") - public BaseResponse validToken(@Valid @RequestBody ValidRequest request) { - Boolean response = oauthService.validToken(request); - return BaseResponse.success(response); - } + @Operation(summary = "유효한 토큰 확인 [인증 토큰 필요 X]", description = "해당 토큰이 유효하면 true 반환하는, 유효하지 않은 false 반환하는 API 입니다") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "유효한 토큰인지 확인 성공") + }) + @PostMapping("/valid/token") + public BaseResponse validToken(@Valid @RequestBody ValidRequest request) { + Boolean response = oauthService.validToken(request); + return BaseResponse.success(response); + } } diff --git a/src/main/java/ssu/eatssu/domain/auth/security/CustomUserDetails.java b/src/main/java/ssu/eatssu/domain/auth/security/CustomUserDetails.java index 285b26a2..eb05c2a5 100644 --- a/src/main/java/ssu/eatssu/domain/auth/security/CustomUserDetails.java +++ b/src/main/java/ssu/eatssu/domain/auth/security/CustomUserDetails.java @@ -1,66 +1,65 @@ package ssu.eatssu.domain.auth.security; -import java.util.ArrayList; -import java.util.Collection; - +import lombok.Getter; +import lombok.RequiredArgsConstructor; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.userdetails.UserDetails; - -import lombok.Getter; -import lombok.RequiredArgsConstructor; import ssu.eatssu.domain.user.entity.User; +import java.util.ArrayList; +import java.util.Collection; + @RequiredArgsConstructor @Getter public class CustomUserDetails implements UserDetails { - private final Long id; - private final String email; - private final String credentials; - private final GrantedAuthority role; + private final Long id; + private final String email; + private final String credentials; + private final GrantedAuthority role; - public CustomUserDetails(User user) { - this.id = user.getId(); - this.email = user.getEmail(); - this.credentials = user.getCredentials(); - this.role = user.getRole(); - } + public CustomUserDetails(User user) { + this.id = user.getId(); + this.email = user.getEmail(); + this.credentials = user.getCredentials(); + this.role = user.getRole(); + } - @Override - public Collection getAuthorities() { - ArrayList authorities = new ArrayList<>(); - authorities.add(new SimpleGrantedAuthority(role.getAuthority())); - return authorities; - } + @Override + public Collection getAuthorities() { + ArrayList authorities = new ArrayList<>(); + authorities.add(new SimpleGrantedAuthority(role.getAuthority())); + return authorities; + } - @Override - public String getPassword() { - return credentials; - } + @Override + public String getPassword() { + return credentials; + } - @Override - public String getUsername() { - return email; - } + @Override + public String getUsername() { + return email; + } - @Override - public boolean isAccountNonExpired() { - return true; - } + @Override + public boolean isAccountNonExpired() { + return true; + } - @Override - public boolean isAccountNonLocked() { - return true; - } + @Override + public boolean isAccountNonLocked() { + return true; + } - @Override - public boolean isCredentialsNonExpired() { - return true; - } + @Override + public boolean isCredentialsNonExpired() { + return true; + } - @Override - public boolean isEnabled() { - return true; - } + @Override + public boolean isEnabled() { + return true; + } } diff --git a/src/main/java/ssu/eatssu/domain/auth/security/CustomUserDetailsService.java b/src/main/java/ssu/eatssu/domain/auth/security/CustomUserDetailsService.java index 0bf9f65e..3e528821 100644 --- a/src/main/java/ssu/eatssu/domain/auth/security/CustomUserDetailsService.java +++ b/src/main/java/ssu/eatssu/domain/auth/security/CustomUserDetailsService.java @@ -1,11 +1,10 @@ package ssu.eatssu.domain.auth.security; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.stereotype.Component; - -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; import ssu.eatssu.domain.user.entity.User; import ssu.eatssu.domain.user.repository.UserRepository; import ssu.eatssu.global.handler.response.BaseException; @@ -16,13 +15,13 @@ @RequiredArgsConstructor public class CustomUserDetailsService implements UserDetailsService { - private final UserRepository userRepository; + private final UserRepository userRepository; - @Override - public UserDetails loadUserByUsername(String username) { - User user = userRepository.findByEmail(username) - .orElseThrow(() -> new BaseException(BaseResponseStatus.NOT_FOUND_USER)); - return new CustomUserDetails(user); - } + @Override + public UserDetails loadUserByUsername(String username) { + User user = userRepository.findByEmail(username) + .orElseThrow(() -> new BaseException(BaseResponseStatus.NOT_FOUND_USER)); + return new CustomUserDetails(user); + } } 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 fcbf573e..4c577027 100644 --- a/src/main/java/ssu/eatssu/domain/auth/security/JwtAuthenticationFilter.java +++ b/src/main/java/ssu/eatssu/domain/auth/security/JwtAuthenticationFilter.java @@ -1,52 +1,91 @@ package ssu.eatssu.domain.auth.security; -import java.io.IOException; - -import org.springframework.security.core.Authentication; -import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.util.StringUtils; -import org.springframework.web.filter.GenericFilterBean; - import jakarta.servlet.FilterChain; import jakarta.servlet.ServletException; import jakarta.servlet.ServletRequest; import jakarta.servlet.ServletResponse; import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpStatus; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.util.AntPathMatcher; +import org.springframework.util.StringUtils; +import org.springframework.web.filter.GenericFilterBean; +import ssu.eatssu.domain.slack.service.SlackErrorNotifier; +import ssu.eatssu.global.handler.response.BaseException; +import ssu.eatssu.global.handler.response.BaseResponseStatus; + +import java.io.IOException; +import java.util.List; -@Slf4j @RequiredArgsConstructor +@Slf4j public class JwtAuthenticationFilter extends GenericFilterBean { - private static final String AUTHORIZATION_HEADER = "Authorization"; - private static final String BEARER_TYPE = "Bearer"; - - private final JwtTokenProvider jwtTokenProvider; - - @Override - public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws - IOException, - ServletException { - - // 1. Request Header 에서 JWT 토큰 추출 - String token = resolveToken((HttpServletRequest)request); - - // 2. validateToken 으로 토큰 유효성 검사 - if (token != null && jwtTokenProvider.validateToken(token)) { - // 토큰이 유효할 경우 토큰에서 Authentication 객체를 가지고 와서 SecurityContext 에 저장 - Authentication authentication = jwtTokenProvider.getAuthentication(token); - SecurityContextHolder.getContext().setAuthentication(authentication); - } - - chain.doFilter(request, response); - } - - // Request Header 에서 토큰 정보 추출 - private String resolveToken(HttpServletRequest request) { - String bearerToken = request.getHeader(AUTHORIZATION_HEADER); - if (StringUtils.hasText(bearerToken) && bearerToken.startsWith(BEARER_TYPE)) { - return bearerToken.substring(7); - } - return null; - } + private static final String AUTHORIZATION_HEADER = "Authorization"; + private static final String BEARER_TYPE = "Bearer"; + private static final AntPathMatcher pathMatcher = new AntPathMatcher(); + + private static final List AUTH_WHITELIST = List.of( + "/", "/oauths/kakao", "/oauths/apple", "/menus/**", "/meals/**", "/admin/login", + "/reviews", "/reviews/menus/**", "/reviews/meals/**", "/v2/reviews/statistics/**", + "/v2/reviews/menus/**", "/v2/reviews/meals/**", "/actuator/**", "/error-test/**", + "/swagger-ui.html", "/swagger-ui/**", "/v3/api-docs/**", "/swagger-resources/**", + "/oauths/valid/token", "/admin/img/**", "/css/**", "/js/**", + "/favicon.ico", "/error/**", "/webjars/**", "/h2-console/**" + ); + private final JwtTokenProvider jwtTokenProvider; + private final SlackErrorNotifier slackErrorNotifier; + + @Override + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) + throws IOException, ServletException { + HttpServletRequest httpRequest = (HttpServletRequest) request; + HttpServletResponse httpResponse = (HttpServletResponse) response; + String requestURI = httpRequest.getRequestURI(); + + if (isWhiteListed(requestURI)) { + chain.doFilter(request, response); + return; + } + + String token = resolveToken(httpRequest); + + if (token == null) { + httpResponse.setStatus(HttpStatus.UNAUTHORIZED.value()); + httpResponse.setContentType("application/json;charset=UTF-8"); + httpResponse.setCharacterEncoding("UTF-8"); + httpResponse.getWriter().write("{\"success\":false,\"code\":401,\"message\":\"유효하지 않은 토큰입니다.\"}"); + return; + } + + if (!jwtTokenProvider.validateToken(token)) { + log.warn("토큰 유효하지 않음: {}", requestURI); + httpResponse.setStatus(HttpStatus.UNAUTHORIZED.value()); + httpResponse.setContentType("application/json;charset=UTF-8"); + httpResponse.setCharacterEncoding("UTF-8"); + httpResponse.getWriter().write("{\"success\":false,\"code\":401,\"message\":\"유효하지 않은 토큰입니다.\"}"); + slackErrorNotifier.notify(new BaseException(BaseResponseStatus.INVALID_TOKEN)); + return; + } + + Authentication authentication = jwtTokenProvider.getAuthentication(token); + SecurityContextHolder.getContext().setAuthentication(authentication); + + chain.doFilter(request, response); + } + + private boolean isWhiteListed(String requestURI) { + return AUTH_WHITELIST.stream().anyMatch(pattern -> pathMatcher.match(pattern, requestURI)); + } + + private String resolveToken(HttpServletRequest request) { + String bearerToken = request.getHeader(AUTHORIZATION_HEADER); + if (StringUtils.hasText(bearerToken) && bearerToken.startsWith(BEARER_TYPE)) { + return bearerToken.substring(7); + } + return null; + } } diff --git a/src/main/java/ssu/eatssu/domain/auth/security/JwtTokenProvider.java b/src/main/java/ssu/eatssu/domain/auth/security/JwtTokenProvider.java index 924b9cc1..e13ffaa1 100644 --- a/src/main/java/ssu/eatssu/domain/auth/security/JwtTokenProvider.java +++ b/src/main/java/ssu/eatssu/domain/auth/security/JwtTokenProvider.java @@ -1,172 +1,181 @@ package ssu.eatssu.domain.auth.security; -import java.security.Key; -import java.util.Arrays; -import java.util.Date; -import java.util.List; -import java.util.stream.Collectors; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.GrantedAuthority; -import org.springframework.security.core.authority.SimpleGrantedAuthority; -import org.springframework.stereotype.Component; - import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; - import io.jsonwebtoken.Claims; +import io.jsonwebtoken.ExpiredJwtException; +import io.jsonwebtoken.JwtException; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; import io.jsonwebtoken.io.Decoders; import io.jsonwebtoken.security.Keys; import jakarta.annotation.PostConstruct; import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.stereotype.Component; import ssu.eatssu.domain.user.dto.Tokens; import ssu.eatssu.global.handler.response.BaseException; import ssu.eatssu.global.handler.response.BaseResponseStatus; +import java.security.Key; +import java.util.Arrays; +import java.util.Date; +import java.util.List; +import java.util.stream.Collectors; + @Slf4j @Component public class JwtTokenProvider { - private static final String AUTHORITIES_KEY = "auth"; - private final String secret; - private final long accessTokenValidityInMilliseconds; - private final long refreshTokenValidityInMilliseconds; - private final ObjectMapper objectMapper = new ObjectMapper(); - private final UserAuthenticationProvider authenticationProvider; - - private Key key; - - @Autowired - public JwtTokenProvider(@Value("${jwt.secret.key}") String secret, - @Value("${jwt.token-validity-in-seconds}") long accessTokenValidityInSeconds, - @Value("${jwt.refresh-token-validity-in-seconds}") long refreshTokenValidityInMilliseconds, - UserAuthenticationProvider authenticationProvider - //RedisTemplate redisTemplate* - ) { - this.secret = secret; - this.accessTokenValidityInMilliseconds = accessTokenValidityInSeconds * 1000; - this.refreshTokenValidityInMilliseconds = refreshTokenValidityInMilliseconds * 1000; - this.authenticationProvider = authenticationProvider; - } - - @PostConstruct - public void setKey() { //jwt.secret을 이요하여 key생성 - byte[] keyBytes = Decoders.BASE64.decode(secret); - this.key = Keys.hmacShaKeyFor(keyBytes); - } - - //토큰 발급 - public Tokens generateTokens(String providerId, String password) { - - Authentication authentication = authenticationProvider.getAuthentication(providerId, password); - - // 권한 가져오기 - String authorities = authentication.getAuthorities().stream() - .map(GrantedAuthority::getAuthority) - .collect(Collectors.joining(",")); - - UserPrincipalDto userPrincipalDto = UserPrincipalDto.from((CustomUserDetails)authentication.getPrincipal()); - - Claims claims = createClaims(userPrincipalDto, authorities); - - return new Tokens(createAccessToken(claims), createRefreshToken(claims)); - } - - //토큰 발급 - public Tokens generateTokens(Authentication authentication) { - - // 권한 가져오기 - String authorities = authentication.getAuthorities().stream() - .map(GrantedAuthority::getAuthority) - .collect(Collectors.joining(",")); - - UserPrincipalDto userPrincipalDto = UserPrincipalDto.from((CustomUserDetails)authentication.getPrincipal()); - - Claims claims = createClaims(userPrincipalDto, authorities); - - return new Tokens(createAccessToken(claims), createRefreshToken(claims)); - } - - private Claims createClaims(UserPrincipalDto userPrincipalDto, String authorities) { - - String subject; - try { - subject = objectMapper.writeValueAsString(userPrincipalDto); - } catch (JsonProcessingException e) { - log.error("Cannot generate JWT Tokens because an error occurred during json parsing."); - log.error("errorTrackStace: {}", (Object)e.getStackTrace()); - throw new BaseException(BaseResponseStatus.INTERNAL_SERVER_ERROR); - } - - Claims claims = Jwts.claims() - .setSubject(subject); - claims.put(AUTHORITIES_KEY, authorities); - - return claims; - } - - //accessToken 생성 - private String createAccessToken(Claims claims) { - long now = new Date().getTime(); //밀리세컨드 - Date accessTokenValidity = new Date(now + this.accessTokenValidityInMilliseconds); - claims.setExpiration(accessTokenValidity); - - return Jwts.builder() - .setClaims(claims) - .signWith(key, SignatureAlgorithm.HS512) - .compact(); - } - - //refreshToken 생성 - private String createRefreshToken(Claims claims) { - long now = new Date().getTime(); //밀리세컨드 - Date refreshTokenValidity = new Date(now + this.refreshTokenValidityInMilliseconds); - claims.setExpiration(refreshTokenValidity); - - return Jwts.builder() - .setClaims(claims) - .signWith(key, SignatureAlgorithm.HS512) - .compact(); - } - - //token 에서 Authentication 추출 - public Authentication getAuthentication(String token) throws JsonProcessingException { - Claims claims = getClaims(token); - - List authorities = - Arrays.stream(claims.get(AUTHORITIES_KEY).toString().split(",")) - .map(SimpleGrantedAuthority::new) - .toList(); - - UserPrincipalDto userPrincipalDto = objectMapper.readValue(claims.getSubject(), UserPrincipalDto.class); - - CustomUserDetails principal = new CustomUserDetails(userPrincipalDto.getId(), userPrincipalDto.getEmail(), "", - authorities.get(0)); - - return new UsernamePasswordAuthenticationToken(principal, token, authorities); - } - - private Claims getClaims(String token) { - return Jwts.parserBuilder() - .setSigningKey(key) - .build() - .parseClaimsJws(token) - .getBody(); - } - - //토큰 유효성 검증 - public boolean validateToken(String token) throws BaseException { - try { - Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token); - return true; - } catch (Exception e) { - log.warn("토큰 유효성 검증 실패: {}", e.getMessage()); - return false; - } - } + private static final String AUTHORITIES_KEY = "auth"; + private final String secret; + private final long accessTokenValidityInMilliseconds; + private final long refreshTokenValidityInMilliseconds; + private final ObjectMapper objectMapper = new ObjectMapper(); + private final UserAuthenticationProvider authenticationProvider; + + private Key key; + + @Autowired + public JwtTokenProvider(@Value("${jwt.secret.key}") String secret, + @Value("${jwt.token-validity-in-seconds}") long accessTokenValidityInSeconds, + @Value("${jwt.refresh-token-validity-in-seconds}") long refreshTokenValidityInMilliseconds, + UserAuthenticationProvider authenticationProvider + //RedisTemplate redisTemplate* + ) { + this.secret = secret; + this.accessTokenValidityInMilliseconds = accessTokenValidityInSeconds * 1000; + this.refreshTokenValidityInMilliseconds = refreshTokenValidityInMilliseconds * 1000; + this.authenticationProvider = authenticationProvider; + } + + @PostConstruct + public void setKey() { //jwt.secret을 이요하여 key생성 + byte[] keyBytes = Decoders.BASE64.decode(secret); + this.key = Keys.hmacShaKeyFor(keyBytes); + } + + //토큰 발급 + public Tokens generateTokens(String providerId, String password) { + + Authentication authentication = authenticationProvider.getAuthentication(providerId, password); + + // 권한 가져오기 + String authorities = authentication.getAuthorities().stream() + .map(GrantedAuthority::getAuthority) + .collect(Collectors.joining(",")); + + UserPrincipalDto userPrincipalDto = UserPrincipalDto.from((CustomUserDetails) authentication.getPrincipal()); + + Claims claims = createClaims(userPrincipalDto, authorities); + + return new Tokens(createAccessToken(claims), createRefreshToken(claims)); + } + + //토큰 발급 + public Tokens generateTokens(Authentication authentication) { + + // 권한 가져오기 + String authorities = authentication.getAuthorities().stream() + .map(GrantedAuthority::getAuthority) + .collect(Collectors.joining(",")); + + UserPrincipalDto userPrincipalDto = UserPrincipalDto.from((CustomUserDetails) authentication.getPrincipal()); + + Claims claims = createClaims(userPrincipalDto, authorities); + + return new Tokens(createAccessToken(claims), createRefreshToken(claims)); + } + + private Claims createClaims(UserPrincipalDto userPrincipalDto, String authorities) { + + String subject; + try { + subject = objectMapper.writeValueAsString(userPrincipalDto); + } catch (JsonProcessingException e) { + log.error("Cannot generate JWT Tokens because an error occurred during json parsing."); + log.error("errorTrackStace: {}", (Object) e.getStackTrace()); + throw new BaseException(BaseResponseStatus.INTERNAL_SERVER_ERROR); + } + + Claims claims = Jwts.claims() + .setSubject(subject); + claims.put(AUTHORITIES_KEY, authorities); + + return claims; + } + + //accessToken 생성 + private String createAccessToken(Claims claims) { + long now = new Date().getTime(); //밀리세컨드 + Date accessTokenValidity = new Date(now + this.accessTokenValidityInMilliseconds); + claims.setExpiration(accessTokenValidity); + + return Jwts.builder() + .setClaims(claims) + .signWith(key, SignatureAlgorithm.HS512) + .compact(); + } + + //refreshToken 생성 + private String createRefreshToken(Claims claims) { + long now = new Date().getTime(); //밀리세컨드 + Date refreshTokenValidity = new Date(now + this.refreshTokenValidityInMilliseconds); + claims.setExpiration(refreshTokenValidity); + + return Jwts.builder() + .setClaims(claims) + .signWith(key, SignatureAlgorithm.HS512) + .compact(); + } + + //token 에서 Authentication 추출 + public Authentication getAuthentication(String token) throws JsonProcessingException { + Claims claims = getClaims(token); + + List authorities = + Arrays.stream(claims.get(AUTHORITIES_KEY).toString().split(",")) + .map(SimpleGrantedAuthority::new) + .toList(); + + UserPrincipalDto userPrincipalDto = objectMapper.readValue(claims.getSubject(), UserPrincipalDto.class); + + CustomUserDetails principal = new CustomUserDetails(userPrincipalDto.getId(), userPrincipalDto.getEmail(), "", + authorities.get(0)); + + return new UsernamePasswordAuthenticationToken(principal, token, authorities); + } + + private Claims getClaims(String token) { + return Jwts.parserBuilder() + .setSigningKey(key) + .build() + .parseClaimsJws(token) + .getBody(); + } + + //토큰 유효성 검증 + public boolean validateToken(String token) { + try { + Claims claims = Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token).getBody(); + Date expiration = claims.getExpiration(); + if (expiration.before(new Date())) { + log.warn("토큰 만료됨"); + return false; + } + return true; + } catch (ExpiredJwtException e) { + log.warn("ExpiredJwtException: {}", e.getMessage()); + return false; + } catch (JwtException | IllegalArgumentException e) { + log.warn("JWT 검증 실패: {}", e.getMessage()); + return false; + } + } + } diff --git a/src/main/java/ssu/eatssu/domain/auth/security/UserAuthenticationProvider.java b/src/main/java/ssu/eatssu/domain/auth/security/UserAuthenticationProvider.java index 5db0db61..b1bc1832 100644 --- a/src/main/java/ssu/eatssu/domain/auth/security/UserAuthenticationProvider.java +++ b/src/main/java/ssu/eatssu/domain/auth/security/UserAuthenticationProvider.java @@ -1,12 +1,11 @@ package ssu.eatssu.domain.auth.security; +import lombok.RequiredArgsConstructor; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.core.Authentication; import org.springframework.stereotype.Component; -import lombok.RequiredArgsConstructor; - /** * 인증 정보를 바탕으로 Authentication 객체 생성 */ @@ -14,16 +13,16 @@ @Component public class UserAuthenticationProvider { - private final AuthenticationManagerBuilder authenticationManagerBuilder; + private final AuthenticationManagerBuilder authenticationManagerBuilder; - public Authentication getAuthentication(String providerId, String rawPassword) { + public Authentication getAuthentication(String providerId, String rawPassword) { - UsernamePasswordAuthenticationToken authenticationToken = - new UsernamePasswordAuthenticationToken(providerId, rawPassword); + UsernamePasswordAuthenticationToken authenticationToken = + new UsernamePasswordAuthenticationToken(providerId, rawPassword); - return authenticationManagerBuilder - .eraseCredentials(true) - .getObject() - .authenticate(authenticationToken); - } + return authenticationManagerBuilder + .eraseCredentials(true) + .getObject() + .authenticate(authenticationToken); + } } diff --git a/src/main/java/ssu/eatssu/domain/auth/security/UserPrincipalDto.java b/src/main/java/ssu/eatssu/domain/auth/security/UserPrincipalDto.java index 95adeac5..d2d189e6 100644 --- a/src/main/java/ssu/eatssu/domain/auth/security/UserPrincipalDto.java +++ b/src/main/java/ssu/eatssu/domain/auth/security/UserPrincipalDto.java @@ -10,15 +10,15 @@ @NoArgsConstructor @AllArgsConstructor public class UserPrincipalDto { - private Long id; - private String email; - private String role; + private Long id; + private String email; + private String role; - public static UserPrincipalDto from(CustomUserDetails userDetails) { - return UserPrincipalDto.builder() - .id(userDetails.getId()) - .email(userDetails.getEmail()) - .role(userDetails.getRole().getAuthority()) - .build(); - } + public static UserPrincipalDto from(CustomUserDetails userDetails) { + return UserPrincipalDto.builder() + .id(userDetails.getId()) + .email(userDetails.getEmail()) + .role(userDetails.getRole().getAuthority()) + .build(); + } } \ No newline at end of file diff --git a/src/main/java/ssu/eatssu/domain/auth/service/OAuthService.java b/src/main/java/ssu/eatssu/domain/auth/service/OAuthService.java index fda3e301..96338a6e 100644 --- a/src/main/java/ssu/eatssu/domain/auth/service/OAuthService.java +++ b/src/main/java/ssu/eatssu/domain/auth/service/OAuthService.java @@ -1,21 +1,11 @@ package ssu.eatssu.domain.auth.service; -import static ssu.eatssu.domain.auth.entity.OAuthProvider.*; -import static ssu.eatssu.domain.auth.security.JwtAuthenticationFilter.*; - -import java.net.http.HttpRequest; - -import javax.servlet.http.HttpServletRequest; - +import lombok.RequiredArgsConstructor; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.core.Authentication; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import org.springframework.util.StringUtils; - -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; import ssu.eatssu.domain.auth.dto.AppleLoginRequest; import ssu.eatssu.domain.auth.dto.KakaoLoginRequest; import ssu.eatssu.domain.auth.dto.OAuthInfo; @@ -29,105 +19,84 @@ import ssu.eatssu.domain.user.service.UserService; import ssu.eatssu.global.handler.response.BaseException; -@Slf4j +import static ssu.eatssu.domain.auth.entity.OAuthProvider.APPLE; +import static ssu.eatssu.domain.auth.entity.OAuthProvider.KAKAO; + @Service @Transactional @RequiredArgsConstructor public class OAuthService { - private final UserService userService; - private final UserRepository userRepository; - private final AppleAuthenticator appleAuthenticator; - private final AuthenticationManagerBuilder authenticationManagerBuilder; - private final JwtTokenProvider jwtTokenProvider; - - private static final String AUTHORIZATION_HEADER = "Authorization"; - - private static final String BEARER_TYPE = "Bearer"; - - public Tokens kakaoLogin(KakaoLoginRequest request) { - User user = userRepository.findByProviderId(request.providerId()) - .orElseGet( - () -> userService.join(request.email(), KAKAO, request.providerId())); - - return generateOauthJwtTokens(user.getEmail(), KAKAO, - request.providerId()); - } - - public Tokens appleLogin(AppleLoginRequest request) { - OAuthInfo oAuthInfo = appleAuthenticator.getOAuthInfoByIdentityToken( - request.identityToken()); - - User user = userRepository.findByProviderId(oAuthInfo.providerId()) - .orElseGet(() -> userService.join(oAuthInfo.email(), APPLE, - oAuthInfo.providerId())); - - //todo 이메일 갱신의 이유? - updateAppleUserEmail(user, oAuthInfo.email()); - - return generateOauthJwtTokens(user.getEmail(), APPLE, - oAuthInfo.providerId()); - } - - /** - * JWT 토큰 재발급 - */ - public Tokens refreshTokens(Authentication authentication) { - return jwtTokenProvider.generateTokens(authentication); - } - - private void updateAppleUserEmail(User user, String email) { - if (isHideEmail(user.getEmail()) && !isHideEmail(email)) { - user.updateEmail(email); - userRepository.save(user); - } - } - - /** - * 유효한 토큰인지 확인 - */ - - public Boolean validToken(ValidRequest request){ - String token = request.token(); - - try { - return jwtTokenProvider.validateToken(token); - } catch (BaseException e) { - log.warn("토큰 유효성 검사 중 예외 발생: {}", e.getMessage()); - return false; - } - } - - private boolean isHideEmail(String email) { - if (email.length() > 25) { - return email.startsWith("@privaterelay.appleid.com", email.length() - 25); - } else { - return false; - } - } - - /** - * Oauth 회원 - email, providerId 를 통해 JwtToken 을 생성 - * todo: 같은 이메일로 카카오, 애플 등 여러 회원가입을 한 회원 처리 필요 - */ - private Tokens generateOauthJwtTokens(String email, OAuthProvider provider, String providerId) { - // email, credentials 를 기반으로 Authentication 객체 생성 - // 이때 authentication 은 인증 여부를 확인하는 authenticated 값이 false - UsernamePasswordAuthenticationToken authenticationToken = - new UsernamePasswordAuthenticationToken(email, - makeOauthCredentials(provider, providerId)); - - // 실제 검증 (사용자 비밀번호 체크) - // authenticate 메서드 실행 => CustomUserDetailsService 에서 만든 loadUserByUsername 메서드 실행 - Authentication authentication = authenticationManagerBuilder.getObject() - .authenticate(authenticationToken); - - // 인증 정보를 바탕으로 JWT 토큰 생성 - return jwtTokenProvider.generateTokens(authentication); - } - - private String makeOauthCredentials(OAuthProvider provider, String providerId) { - return provider + providerId; - } + private static final String AUTHORIZATION_HEADER = "Authorization"; + private static final String BEARER_TYPE = "Bearer"; + private final UserService userService; + private final UserRepository userRepository; + private final AppleAuthenticator appleAuthenticator; + private final AuthenticationManagerBuilder authenticationManagerBuilder; + private final JwtTokenProvider jwtTokenProvider; + + public Tokens kakaoLogin(KakaoLoginRequest request) { + User user = userRepository.findByProviderId(request.providerId()) + .orElseGet(() -> userService.join(request.email(), KAKAO, request.providerId())); + + return generateOauthJwtTokens(user.getEmail(), KAKAO, request.providerId()); + } + + public Tokens appleLogin(AppleLoginRequest request) { + OAuthInfo oAuthInfo = appleAuthenticator.getOAuthInfoByIdentityToken(request.identityToken()); + + User user = userRepository.findByProviderId(oAuthInfo.providerId()) + .orElseGet(() -> userService.join(oAuthInfo.email(), APPLE, oAuthInfo.providerId())); + + updateAppleUserEmail(user, oAuthInfo.email()); + + return generateOauthJwtTokens(user.getEmail(), APPLE, oAuthInfo.providerId()); + } + + public Tokens refreshTokens(Authentication authentication) { + return jwtTokenProvider.generateTokens(authentication); + } + + private void updateAppleUserEmail(User user, String email) { + if (isHideEmail(user.getEmail()) && !isHideEmail(email)) { + user.updateEmail(email); + userRepository.save(user); + } + } + + public Boolean validToken(ValidRequest request) { + String token = request.token(); + + try { + return jwtTokenProvider.validateToken(token); + } catch (BaseException e) { + return false; + } + } + + private boolean isHideEmail(String email) { + if (email.length() > 25) { + return email.startsWith("@privaterelay.appleid.com", email.length() - 25); + } else { + return false; + } + } + + // FIXME: 같은 이메일로 카카오, 애플 등 여러 회원가입을 한 회원 처리 필요. + // 사용자는 같은 이메일로 로그인을 진행했을 시 같은 계정에 접근하기를 원함. + // iPhone을 사용하는 사용자들에서만 한함. + // 하지만 private relay를 사용하는 경우도 있는데, 이럴 때는 사용자의 계정을 묶기 다소 복잡하다. + private Tokens generateOauthJwtTokens(String email, OAuthProvider provider, String providerId) { + UsernamePasswordAuthenticationToken authenticationToken = + new UsernamePasswordAuthenticationToken(email, makeOauthCredentials(provider, providerId)); + + Authentication authentication = authenticationManagerBuilder.getObject().authenticate(authenticationToken); + + return jwtTokenProvider.generateTokens(authentication); + } + + private String makeOauthCredentials(OAuthProvider provider, String providerId) { + return provider + providerId; + } } 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 06ecc032..318664e3 100644 --- a/src/main/java/ssu/eatssu/domain/inquiry/dto/CreateInquiryRequest.java +++ b/src/main/java/ssu/eatssu/domain/inquiry/dto/CreateInquiryRequest.java @@ -9,9 +9,9 @@ @Getter public class CreateInquiryRequest { - @Schema(description = "답장 받을 이메일", example = "sandy1017@gmail.com") - private String email; + @Schema(description = "답장 받을 이메일", example = "sandy1017@gmail.com") + private String email; - @Schema(description = "문의 내용", example = "어쩌고 저쩌고 문의 남깁니다") - private String content; + @Schema(description = "문의 내용", example = "어쩌고 저쩌고 문의 남깁니다") + private String content; } diff --git a/src/main/java/ssu/eatssu/domain/inquiry/dto/InquiryDetailResponse.java b/src/main/java/ssu/eatssu/domain/inquiry/dto/InquiryDetailResponse.java index 9c3b8fd4..ec56d7a7 100644 --- a/src/main/java/ssu/eatssu/domain/inquiry/dto/InquiryDetailResponse.java +++ b/src/main/java/ssu/eatssu/domain/inquiry/dto/InquiryDetailResponse.java @@ -12,24 +12,24 @@ @Getter public class InquiryDetailResponse { - @Schema(description = "문의 작성자 Id", example = "123") - private Long writerId; + @Schema(description = "문의 작성자 Id", example = "123") + private Long writerId; - @Schema(description = "문의 작성자 닉네임", example = "먹짱맨") - private String writerNickName; + @Schema(description = "문의 작성자 닉네임", example = "먹짱맨") + private String writerNickName; - @Schema(description = "문의 작성자 이메일", example = "test@gmail.com") - private String writerEmail; + @Schema(description = "문의 작성자 이메일", example = "test@gmail.com") + private String writerEmail; - @Schema(description = "문의 내용", example = "어쩌고 저쩌고 문의드립니다") - private String content; + @Schema(description = "문의 내용", example = "어쩌고 저쩌고 문의드립니다") + private String content; - public static InquiryDetailResponse from(Inquiry userInquiry) { - return InquiryDetailResponse.builder() - .writerId(userInquiry.getUser().getId()) - .writerNickName(userInquiry.getUser().getNickname()) - .writerEmail(userInquiry.getUser().getEmail()) - .content(userInquiry.getContent()) - .build(); - } + public static InquiryDetailResponse from(Inquiry userInquiry) { + return InquiryDetailResponse.builder() + .writerId(userInquiry.getUser().getId()) + .writerNickName(userInquiry.getUser().getNickname()) + .writerEmail(userInquiry.getUser().getEmail()) + .content(userInquiry.getContent()) + .build(); + } } diff --git a/src/main/java/ssu/eatssu/domain/inquiry/entity/Inquiry.java b/src/main/java/ssu/eatssu/domain/inquiry/entity/Inquiry.java index 9fc8c2a5..aa6643bb 100644 --- a/src/main/java/ssu/eatssu/domain/inquiry/entity/Inquiry.java +++ b/src/main/java/ssu/eatssu/domain/inquiry/entity/Inquiry.java @@ -25,34 +25,34 @@ @AllArgsConstructor public class Inquiry extends BaseTimeEntity { - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - @Column(name = "user_inquiry_id") - private Long id; + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "user_inquiry_id") + private Long id; - private String content; + private String content; - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "user_id") - private User user; + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "user_id") + private User user; - private String email; + private String email; - @Enumerated(EnumType.STRING) - private InquiryStatus status; + @Enumerated(EnumType.STRING) + private InquiryStatus status; - public Inquiry(String content, User user, String email) { - this.content = content; - this.user = user; - this.email = email; - this.status = InquiryStatus.WAITING; - } + public Inquiry(String content, User user, String email) { + this.content = content; + this.user = user; + this.email = email; + this.status = InquiryStatus.WAITING; + } - public void updateStatus(InquiryStatus status) { - this.status = status; - } + public void updateStatus(InquiryStatus status) { + this.status = status; + } - public void clearUser() { - this.user = null; - } + public void clearUser() { + this.user = null; + } } diff --git a/src/main/java/ssu/eatssu/domain/inquiry/entity/InquiryStatus.java b/src/main/java/ssu/eatssu/domain/inquiry/entity/InquiryStatus.java index 6b8b4bc9..4156adfb 100644 --- a/src/main/java/ssu/eatssu/domain/inquiry/entity/InquiryStatus.java +++ b/src/main/java/ssu/eatssu/domain/inquiry/entity/InquiryStatus.java @@ -1,5 +1,5 @@ package ssu.eatssu.domain.inquiry.entity; public enum InquiryStatus { - WAITING, ANSWERED, HOLD; + WAITING, ANSWERED, HOLD } diff --git a/src/main/java/ssu/eatssu/domain/inquiry/presentation/InquiryController.java b/src/main/java/ssu/eatssu/domain/inquiry/presentation/InquiryController.java index 9f09bbc1..b5e346b9 100644 --- a/src/main/java/ssu/eatssu/domain/inquiry/presentation/InquiryController.java +++ b/src/main/java/ssu/eatssu/domain/inquiry/presentation/InquiryController.java @@ -1,11 +1,5 @@ package ssu.eatssu.domain.inquiry.presentation; -import org.springframework.security.core.annotation.AuthenticationPrincipal; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.media.Content; import io.swagger.v3.oas.annotations.media.Schema; @@ -13,6 +7,11 @@ import io.swagger.v3.oas.annotations.responses.ApiResponses; import io.swagger.v3.oas.annotations.tags.Tag; import lombok.RequiredArgsConstructor; +import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; import ssu.eatssu.domain.auth.security.CustomUserDetails; import ssu.eatssu.domain.inquiry.dto.CreateInquiryRequest; import ssu.eatssu.domain.inquiry.entity.Inquiry; @@ -28,21 +27,21 @@ @Tag(name = "Inquiry", description = "문의 API") public class InquiryController { - private final SlackService slackService; - private final InquiryService inquiryService; + private final SlackService slackService; + private final InquiryService inquiryService; - @Operation(summary = "문의 작성", description = "문의를 작성하는 API 입니다.") - @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "문의 작성 성공"), - @ApiResponse(responseCode = "404", description = "존재하지 않는 유저", content = @Content(schema = @Schema(implementation = BaseResponse.class))) - }) - @PostMapping("/") - public BaseResponse writeInquiry(@RequestBody CreateInquiryRequest createInquiryRequest, - @AuthenticationPrincipal CustomUserDetails customUserDetails) { - Inquiry inquiry = inquiryService.createUserInquiry(customUserDetails, createInquiryRequest); - slackService.sendSlackMessage(SlackMessageFormat.sendUserInquiry(inquiry) - , SlackChannel.USER_INQUIRY_CHANNEL); - return BaseResponse.success(); - } + @Operation(summary = "문의 작성", description = "문의를 작성하는 API 입니다.") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "문의 작성 성공"), + @ApiResponse(responseCode = "404", description = "존재하지 않는 유저", content = @Content(schema = @Schema(implementation = BaseResponse.class))) + }) + @PostMapping("/") + public BaseResponse writeInquiry(@RequestBody CreateInquiryRequest createInquiryRequest, + @AuthenticationPrincipal CustomUserDetails customUserDetails) { + Inquiry inquiry = inquiryService.createUserInquiry(customUserDetails, createInquiryRequest); + slackService.sendSlackMessage(SlackMessageFormat.sendUserInquiry(inquiry) + , SlackChannel.USER_INQUIRY_CHANNEL); + return BaseResponse.success(); + } } diff --git a/src/main/java/ssu/eatssu/domain/inquiry/repository/InquiryRepository.java b/src/main/java/ssu/eatssu/domain/inquiry/repository/InquiryRepository.java index 2e92023b..8dd6d21f 100644 --- a/src/main/java/ssu/eatssu/domain/inquiry/repository/InquiryRepository.java +++ b/src/main/java/ssu/eatssu/domain/inquiry/repository/InquiryRepository.java @@ -1,7 +1,6 @@ package ssu.eatssu.domain.inquiry.repository; import org.springframework.data.jpa.repository.JpaRepository; - import ssu.eatssu.domain.inquiry.entity.Inquiry; public interface InquiryRepository extends JpaRepository { 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 f87b123e..66c7c7c0 100644 --- a/src/main/java/ssu/eatssu/domain/inquiry/service/InquiryService.java +++ b/src/main/java/ssu/eatssu/domain/inquiry/service/InquiryService.java @@ -1,11 +1,8 @@ package ssu.eatssu.domain.inquiry.service; -import static ssu.eatssu.global.handler.response.BaseResponseStatus.*; - +import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; - -import lombok.RequiredArgsConstructor; import ssu.eatssu.domain.auth.security.CustomUserDetails; import ssu.eatssu.domain.inquiry.dto.CreateInquiryRequest; import ssu.eatssu.domain.inquiry.entity.Inquiry; @@ -14,21 +11,23 @@ import ssu.eatssu.domain.user.repository.UserRepository; import ssu.eatssu.global.handler.response.BaseException; +import static ssu.eatssu.global.handler.response.BaseResponseStatus.NOT_FOUND_USER; + @RequiredArgsConstructor @Service @Transactional public class InquiryService { - private final UserRepository userRepository; - private final InquiryRepository inquiryRepository; + private final UserRepository userRepository; + private final InquiryRepository inquiryRepository; - public Inquiry createUserInquiry(CustomUserDetails userDetails, CreateInquiryRequest request) { - User user = userRepository.findById(userDetails.getId()) - .orElseThrow(() -> new BaseException(NOT_FOUND_USER)); + public Inquiry createUserInquiry(CustomUserDetails userDetails, CreateInquiryRequest request) { + User user = userRepository.findById(userDetails.getId()) + .orElseThrow(() -> new BaseException(NOT_FOUND_USER)); - Inquiry inquiry = new Inquiry(request.getContent(), user, request.getEmail()); + Inquiry inquiry = new Inquiry(request.getContent(), user, request.getEmail()); - return inquiryRepository.save(inquiry); - } + return inquiryRepository.save(inquiry); + } } 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 953d640f..7680ff3a 100644 --- a/src/main/java/ssu/eatssu/domain/menu/entity/Meal.java +++ b/src/main/java/ssu/eatssu/domain/menu/entity/Meal.java @@ -1,11 +1,5 @@ package ssu.eatssu.domain.menu.entity; -import java.util.ArrayList; -import java.util.Date; -import java.util.List; - -import org.springframework.format.annotation.DateTimeFormat; - import jakarta.persistence.CascadeType; import jakarta.persistence.Column; import jakarta.persistence.Entity; @@ -21,53 +15,53 @@ import lombok.AccessLevel; import lombok.Getter; import lombok.NoArgsConstructor; +import org.springframework.format.annotation.DateTimeFormat; import ssu.eatssu.domain.menu.entity.constants.TimePart; import ssu.eatssu.domain.restaurant.entity.Restaurant; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + @Entity @Getter @NoArgsConstructor(access = AccessLevel.PROTECTED) public class Meal { - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - @Column(name = "meal_id") - private Long id; - - @DateTimeFormat(pattern = "yyyyMMdd") - @Temporal(TemporalType.DATE) - private Date date; - - @Enumerated(EnumType.STRING) - private Restaurant restaurant; - - @Enumerated(EnumType.STRING) - private TimePart timePart; - - private Integer price; - - @OneToMany(mappedBy = "meal", cascade = CascadeType.ALL, fetch = FetchType.LAZY) - private List mealMenus = new ArrayList<>(); + @OneToMany(mappedBy = "meal", cascade = CascadeType.ALL, fetch = FetchType.LAZY) + private final List mealMenus = new ArrayList<>(); + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "meal_id") + private Long id; + @DateTimeFormat(pattern = "yyyyMMdd") + @Temporal(TemporalType.DATE) + private Date date; + @Enumerated(EnumType.STRING) + private Restaurant restaurant; + @Enumerated(EnumType.STRING) + private TimePart timePart; + private Integer price; - public Meal(Date date, TimePart timePart, Restaurant restaurant) { - this.date = date; - this.timePart = timePart; - this.restaurant = restaurant; - this.price = restaurant.getRestaurantPrice(); - } + public Meal(Date date, TimePart timePart, Restaurant restaurant) { + this.date = date; + this.timePart = timePart; + this.restaurant = restaurant; + this.price = restaurant.getRestaurantPrice(); + } - public Meal(Date date, TimePart timePart, Restaurant restaurant, Integer price) { - this.date = date; - this.timePart = timePart; - this.restaurant = restaurant; - this.price = price; - } + public Meal(Date date, TimePart timePart, Restaurant restaurant, Integer price) { + this.date = date; + this.timePart = timePart; + this.restaurant = restaurant; + this.price = price; + } - public List getMenuNames() { - return mealMenus.stream().map(mealMenu -> mealMenu.getMenu().getName()).toList(); - } + public List getMenuNames() { + return mealMenus.stream().map(mealMenu -> mealMenu.getMenu().getName()).toList(); + } - public void addMealMenu(MealMenu mealMenu) { - mealMenus.add(mealMenu); - } + public void addMealMenu(MealMenu mealMenu) { + mealMenus.add(mealMenu); + } } diff --git a/src/main/java/ssu/eatssu/domain/menu/entity/MealMenu.java b/src/main/java/ssu/eatssu/domain/menu/entity/MealMenu.java index f6518088..2391a332 100644 --- a/src/main/java/ssu/eatssu/domain/menu/entity/MealMenu.java +++ b/src/main/java/ssu/eatssu/domain/menu/entity/MealMenu.java @@ -21,17 +21,17 @@ @Builder public class MealMenu { - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - @Column(name = "meal_menu_id") - private Long id; + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "meal_menu_id") + private Long id; - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "menu_id") - private Menu menu; + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "menu_id") + private Menu menu; - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "meal_id") - private Meal meal; + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "meal_id") + private Meal meal; } 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 d02ed5be..b3346b03 100644 --- a/src/main/java/ssu/eatssu/domain/menu/entity/Menu.java +++ b/src/main/java/ssu/eatssu/domain/menu/entity/Menu.java @@ -1,8 +1,5 @@ package ssu.eatssu.domain.menu.entity; -import java.util.ArrayList; -import java.util.List; - import jakarta.persistence.CascadeType; import jakarta.persistence.Column; import jakarta.persistence.Embedded; @@ -23,124 +20,102 @@ import ssu.eatssu.domain.review.entity.Review; import ssu.eatssu.domain.review.entity.Reviews; +import java.util.ArrayList; +import java.util.List; + @Entity @Getter @NoArgsConstructor(access = AccessLevel.PROTECTED) public class Menu { - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - @Column(name = "menu_id") - private Long id; - - private String name; - - private Integer price; - - @Enumerated(EnumType.STRING) - private Restaurant restaurant; - - // TODO : 삭제되어야 함 - @Embedded - private Reviews reviews = new Reviews(); - - @OneToMany(mappedBy = "menu", cascade = CascadeType.ALL) - private List mealMenus = new ArrayList<>(); - - @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 Integer unlikeCount = 0; - - private Menu(String name, Restaurant restaurant, Integer price, MenuCategory category) { - this.name = name; - this.restaurant = restaurant; - this.price = price; - this.category = category; - } - - public static Menu createVariable(String name, Restaurant restaurant) { - int price = 0; - return new Menu(name, restaurant, price, null); - } - - public static Menu createFixed(String name, Restaurant restaurant, Integer price, - MenuCategory category) { - return new Menu(name, restaurant, price, category); - } - - public void addReview(Review review) { - reviews.add(review); - } - - public int getTotalReviewCount() { - return reviews.size(); - } - - public void update(String name, Integer price) { - this.name = name; - this.price = price; - } - - public void changeDiscontinuedStatus() { - this.isDiscontinued = !this.isDiscontinued; - } - - public boolean isContinued() { - return !this.isDiscontinued; - } - - public void increaseLikeCount() - { - if(this.likeCount==null){ - this.likeCount=0; - } - this.likeCount++; - } - - public void increaseUnlikeCount() { - if(this.unlikeCount==null){ - this.unlikeCount=0; - } - this.unlikeCount++; - } - - public void decreaseLikeCount() { - if(this.likeCount==null){ - this.likeCount=0; - } - this.likeCount--; - } - - public void decreaseUnlikeCount() { - if(this.unlikeCount==null){ - this.unlikeCount=0; - } - unlikeCount--; - } - - public void changeLikeStatus(Boolean isLike) { - if (isLike) { - decreaseUnlikeCount(); - increaseLikeCount(); - } else { - decreaseLikeCount(); - increaseUnlikeCount(); - } - } - - public void cancelLike(Boolean isLike) { - if (isLike) { - decreaseLikeCount(); - } else { - decreaseUnlikeCount(); - } - } + // TODO : 삭제되어야 함 + @Embedded + private final Reviews reviews = new Reviews(); + @OneToMany(mappedBy = "menu", cascade = CascadeType.ALL) + private final List mealMenus = new ArrayList<>(); + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "menu_id") + private Long id; + private String name; + private Integer price; + @Enumerated(EnumType.STRING) + private Restaurant restaurant; + @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; + this.price = price; + this.category = category; + } + + public static Menu createVariable(String name, Restaurant restaurant) { + int price = 0; + return new Menu(name, restaurant, price, null); + } + + public static Menu createFixed(String name, Restaurant restaurant, Integer price, + MenuCategory category) { + return new Menu(name, restaurant, price, category); + } + + public void addReview(Review review) { + reviews.add(review); + } + + public int getTotalReviewCount() { + return reviews.size(); + } + + public void update(String name, Integer price) { + this.name = name; + this.price = price; + } + + public void changeDiscontinuedStatus() { + this.isDiscontinued = !this.isDiscontinued; + } + + public boolean isContinued() { + return !this.isDiscontinued; + } + + public void increaseLikeCount() { + if (this.likeCount == null) { + this.likeCount = 0; + } + this.likeCount++; + } + + public void decreaseLikeCount() { + if (this.likeCount == null) { + this.likeCount = 0; + } + this.likeCount--; + } + + public void changeLikeStatus(Boolean isLike) { + if (isLike) { + increaseLikeCount(); + } else { + decreaseLikeCount(); + } + } + + public void cancelLike(Boolean isLike) { + if (isLike) { + decreaseLikeCount(); + } + } } \ No newline at end of file diff --git a/src/main/java/ssu/eatssu/domain/menu/entity/MenuCategory.java b/src/main/java/ssu/eatssu/domain/menu/entity/MenuCategory.java index 400dd8ef..9f1a4ec7 100644 --- a/src/main/java/ssu/eatssu/domain/menu/entity/MenuCategory.java +++ b/src/main/java/ssu/eatssu/domain/menu/entity/MenuCategory.java @@ -21,14 +21,14 @@ @Builder public class MenuCategory { - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - @Column(name = "menu_category_id") - private Long id; + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "menu_category_id") + private Long id; - private String name; + private String name; - @Enumerated(EnumType.STRING) - private Restaurant restaurant; + @Enumerated(EnumType.STRING) + private Restaurant restaurant; } diff --git a/src/main/java/ssu/eatssu/domain/menu/entity/constants/MenuType.java b/src/main/java/ssu/eatssu/domain/menu/entity/constants/MenuType.java index edc97d96..e53a0845 100644 --- a/src/main/java/ssu/eatssu/domain/menu/entity/constants/MenuType.java +++ b/src/main/java/ssu/eatssu/domain/menu/entity/constants/MenuType.java @@ -1,33 +1,36 @@ package ssu.eatssu.domain.menu.entity.constants; -import static ssu.eatssu.domain.restaurant.entity.Restaurant.*; +import com.fasterxml.jackson.annotation.JsonCreator; +import lombok.Getter; +import ssu.eatssu.domain.restaurant.entity.Restaurant; import java.util.Arrays; import java.util.List; -import com.fasterxml.jackson.annotation.JsonCreator; - -import lombok.Getter; -import ssu.eatssu.domain.restaurant.entity.Restaurant; +import static ssu.eatssu.domain.restaurant.entity.Restaurant.DODAM; +import static ssu.eatssu.domain.restaurant.entity.Restaurant.DORMITORY; +import static ssu.eatssu.domain.restaurant.entity.Restaurant.FOOD_COURT; +import static ssu.eatssu.domain.restaurant.entity.Restaurant.HAKSIK; +import static ssu.eatssu.domain.restaurant.entity.Restaurant.SNACK_CORNER; @Getter public enum MenuType { - FIXED("고정 메뉴", Arrays.asList(FOOD_COURT, SNACK_CORNER)), - VARIABLE("변동 메뉴", Arrays.asList(DODAM, DORMITORY, HAKSIK)); - - private final String description; - private final List restaurants; - - MenuType(String description, List restaurants) { - this.description = description; - this.restaurants = restaurants; - } - - @JsonCreator - public static MenuType from(String description) { - return Arrays.stream(MenuType.values()) - .filter(d -> d.getDescription().equals(description)) - .findAny() - .orElseThrow(IllegalArgumentException::new); - } + FIXED("고정 메뉴", Arrays.asList(FOOD_COURT, SNACK_CORNER)), + VARIABLE("변동 메뉴", Arrays.asList(DODAM, DORMITORY, HAKSIK)); + + private final String description; + private final List restaurants; + + MenuType(String description, List restaurants) { + this.description = description; + this.restaurants = restaurants; + } + + @JsonCreator + public static MenuType from(String description) { + return Arrays.stream(MenuType.values()) + .filter(d -> d.getDescription().equals(description)) + .findAny() + .orElseThrow(IllegalArgumentException::new); + } } diff --git a/src/main/java/ssu/eatssu/domain/menu/entity/constants/TimePart.java b/src/main/java/ssu/eatssu/domain/menu/entity/constants/TimePart.java index 2866bffd..c8326e76 100644 --- a/src/main/java/ssu/eatssu/domain/menu/entity/constants/TimePart.java +++ b/src/main/java/ssu/eatssu/domain/menu/entity/constants/TimePart.java @@ -1,28 +1,27 @@ package ssu.eatssu.domain.menu.entity.constants; -import java.util.Arrays; - import com.fasterxml.jackson.annotation.JsonCreator; - import lombok.Getter; +import java.util.Arrays; + @Getter public enum TimePart { - MORNING("조식"), - LUNCH("중식"), - DINNER("석식"); + MORNING("조식"), + LUNCH("중식"), + DINNER("석식"); - private final String description; + private final String description; - TimePart(String description) { - this.description = description; - } + TimePart(String description) { + this.description = description; + } - @JsonCreator - public static TimePart from(String description) { - return Arrays.stream(TimePart.values()) - .filter(d -> d.getDescription().equals(description)) - .findAny() - .orElseThrow(IllegalArgumentException::new); - } + @JsonCreator + public static TimePart from(String description) { + return Arrays.stream(TimePart.values()) + .filter(d -> d.getDescription().equals(description)) + .findAny() + .orElseThrow(IllegalArgumentException::new); + } } \ No newline at end of file diff --git a/src/main/java/ssu/eatssu/domain/menu/persistence/MealMenuQueryRepository.java b/src/main/java/ssu/eatssu/domain/menu/persistence/MealMenuQueryRepository.java index ffe34ee4..470e16e4 100644 --- a/src/main/java/ssu/eatssu/domain/menu/persistence/MealMenuQueryRepository.java +++ b/src/main/java/ssu/eatssu/domain/menu/persistence/MealMenuQueryRepository.java @@ -1,34 +1,32 @@ package ssu.eatssu.domain.menu.persistence; -import java.util.List; - -import org.springframework.stereotype.Component; - import com.querydsl.jpa.impl.JPAQueryFactory; - import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; import ssu.eatssu.domain.menu.entity.QMeal; import ssu.eatssu.domain.menu.entity.QMealMenu; import ssu.eatssu.domain.menu.entity.QMenu; +import java.util.List; + @Component @RequiredArgsConstructor public class MealMenuQueryRepository { - private final JPAQueryFactory queryFactory; - private final QMenu menu = QMenu.menu; + private final JPAQueryFactory queryFactory; + private final QMenu menu = QMenu.menu; - private final QMeal meal = QMeal.meal; + private final QMeal meal = QMeal.meal; - private final QMealMenu mealMenu = QMealMenu.mealMenu; + private final QMealMenu mealMenu = QMealMenu.mealMenu; - public List getMenuIds(Long mealId) { - return queryFactory - .select(menu.id) - .from(meal) - .join(mealMenu).on(meal.id.eq(mealMenu.meal.id)) - .join(menu).on(mealMenu.menu.id.eq(menu.id)) - .where(meal.id.eq(mealId)) - .fetch(); - } + public List getMenuIds(Long mealId) { + return queryFactory + .select(menu.id) + .from(meal) + .join(mealMenu).on(meal.id.eq(mealMenu.meal.id)) + .join(menu).on(mealMenu.menu.id.eq(menu.id)) + .where(meal.id.eq(mealId)) + .fetch(); + } } diff --git a/src/main/java/ssu/eatssu/domain/menu/persistence/MealMenuRepository.java b/src/main/java/ssu/eatssu/domain/menu/persistence/MealMenuRepository.java index dc2999b2..277c073d 100644 --- a/src/main/java/ssu/eatssu/domain/menu/persistence/MealMenuRepository.java +++ b/src/main/java/ssu/eatssu/domain/menu/persistence/MealMenuRepository.java @@ -1,25 +1,24 @@ package ssu.eatssu.domain.menu.persistence; -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 ssu.eatssu.domain.menu.entity.Meal; import ssu.eatssu.domain.menu.entity.MealMenu; import ssu.eatssu.domain.menu.entity.Menu; +import java.util.List; + public interface MealMenuRepository extends JpaRepository { - @Query("SELECT mm.menu FROM MealMenu mm WHERE mm.meal IN :meals") - List findMenusByMeals(List meals); + @Query("SELECT mm.menu FROM MealMenu mm WHERE mm.meal IN :meals") + List findMenusByMeals(List meals); - @Query("SELECT mm.menu.id FROM MealMenu mm WHERE mm.meal.id = :mealId") - List findMenuIdsByMealId(@Param("mealId") Long mealId); + @Query("SELECT mm.menu.id FROM MealMenu mm WHERE mm.meal.id = :mealId") + List findMenuIdsByMealId(@Param("mealId") Long mealId); - @Query("SELECT DISTINCT mm.meal.id FROM MealMenu mm WHERE mm.menu.id IN :menuIds") - List findMealIdsByMenuIds(@Param("menuIds") List menuIds); + @Query("SELECT DISTINCT mm.meal.id FROM MealMenu mm WHERE mm.menu.id IN :menuIds") + List findMealIdsByMenuIds(@Param("menuIds") List menuIds); - @Query("SELECT mm.menu FROM MealMenu mm WHERE mm.meal = :meal") - List findMenusByMeal(Meal meal); + @Query("SELECT mm.menu FROM MealMenu mm WHERE mm.meal = :meal") + List findMenusByMeal(Meal meal); } diff --git a/src/main/java/ssu/eatssu/domain/menu/persistence/MealRepository.java b/src/main/java/ssu/eatssu/domain/menu/persistence/MealRepository.java index dcc471cc..df59f5d7 100644 --- a/src/main/java/ssu/eatssu/domain/menu/persistence/MealRepository.java +++ b/src/main/java/ssu/eatssu/domain/menu/persistence/MealRepository.java @@ -1,17 +1,16 @@ package ssu.eatssu.domain.menu.persistence; -import java.util.Date; -import java.util.List; - import org.springframework.data.jpa.repository.JpaRepository; - import ssu.eatssu.domain.menu.entity.Meal; import ssu.eatssu.domain.menu.entity.constants.TimePart; import ssu.eatssu.domain.restaurant.entity.Restaurant; +import java.util.Date; +import java.util.List; + public interface MealRepository extends JpaRepository { - List findAllByDateAndTimePartAndRestaurant(Date date, TimePart timePart, Restaurant restaurant); + List findAllByDateAndTimePartAndRestaurant(Date date, TimePart timePart, Restaurant restaurant); - List findByRestaurant(Restaurant restaurant); + List findByRestaurant(Restaurant restaurant); } diff --git a/src/main/java/ssu/eatssu/domain/menu/persistence/MenuCategoryRepository.java b/src/main/java/ssu/eatssu/domain/menu/persistence/MenuCategoryRepository.java index 5b171e61..ffc0fcdd 100644 --- a/src/main/java/ssu/eatssu/domain/menu/persistence/MenuCategoryRepository.java +++ b/src/main/java/ssu/eatssu/domain/menu/persistence/MenuCategoryRepository.java @@ -1,13 +1,12 @@ package ssu.eatssu.domain.menu.persistence; -import java.util.List; - import org.springframework.data.jpa.repository.JpaRepository; - import ssu.eatssu.domain.menu.entity.MenuCategory; import ssu.eatssu.domain.restaurant.entity.Restaurant; +import java.util.List; + public interface MenuCategoryRepository extends JpaRepository { - List findAllByRestaurant(Restaurant restaurant); + List findAllByRestaurant(Restaurant restaurant); } diff --git a/src/main/java/ssu/eatssu/domain/menu/persistence/MenuRepository.java b/src/main/java/ssu/eatssu/domain/menu/persistence/MenuRepository.java index 19ab9f94..e87ece59 100644 --- a/src/main/java/ssu/eatssu/domain/menu/persistence/MenuRepository.java +++ b/src/main/java/ssu/eatssu/domain/menu/persistence/MenuRepository.java @@ -1,20 +1,19 @@ package ssu.eatssu.domain.menu.persistence; -import java.util.List; -import java.util.Optional; - import org.springframework.data.jpa.repository.JpaRepository; - import ssu.eatssu.domain.menu.entity.Menu; import ssu.eatssu.domain.menu.entity.MenuCategory; import ssu.eatssu.domain.restaurant.entity.Restaurant; +import java.util.List; +import java.util.Optional; + public interface MenuRepository extends JpaRepository { - boolean existsByNameAndRestaurant(String name, Restaurant restaurant); + boolean existsByNameAndRestaurant(String name, Restaurant restaurant); - Optional findByNameAndRestaurant(String name, Restaurant restaurant); + Optional findByNameAndRestaurant(String name, Restaurant restaurant); - List findAllByRestaurantAndCategory(Restaurant restaurant, MenuCategory category); + List findAllByRestaurantAndCategory(Restaurant restaurant, MenuCategory category); - List findAll(); + List findAll(); } diff --git a/src/main/java/ssu/eatssu/domain/menu/persistence/QuerydslMealRatingCalculator.java b/src/main/java/ssu/eatssu/domain/menu/persistence/QuerydslMealRatingCalculator.java index f68363ff..482cb4dc 100644 --- a/src/main/java/ssu/eatssu/domain/menu/persistence/QuerydslMealRatingCalculator.java +++ b/src/main/java/ssu/eatssu/domain/menu/persistence/QuerydslMealRatingCalculator.java @@ -1,64 +1,62 @@ package ssu.eatssu.domain.menu.persistence; -import java.util.List; - -import org.springframework.stereotype.Repository; - import com.querydsl.core.types.dsl.BooleanExpression; import com.querydsl.jpa.impl.JPAQueryFactory; - import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Repository; import ssu.eatssu.domain.menu.entity.QMenu; import ssu.eatssu.domain.review.entity.QReview; +import java.util.List; + @Repository @RequiredArgsConstructor public class QuerydslMealRatingCalculator { - private final MealMenuQueryRepository mealMenuQueryRepository; - - private final JPAQueryFactory queryFactory; - private final QMenu menu = QMenu.menu; - - private final QReview review = QReview.review; - - public Double getMainRatingAverage(Long mealId) { - List menuIds = mealMenuQueryRepository.getMenuIds(mealId); - return queryFactory - .select(review.ratings.mainRating.avg()) - .from(review) - .join(review.menu, menu) - .where( - menuIdIn(menuIds) - ) - .fetchOne(); - } - - public Double getTasteRatingAverage(Long mealId) { - List menuIds = mealMenuQueryRepository.getMenuIds(mealId); - return queryFactory - .select(review.ratings.tasteRating.avg()) - .from(review) - .join(review.menu, menu) - .where( - menuIdIn(menuIds) - ) - .fetchOne(); - } - - public Double getAmountRatingAverage(Long mealId) { - List menuIds = mealMenuQueryRepository.getMenuIds(mealId); - return queryFactory - .select(review.ratings.amountRating.avg()) - .from(review) - .join(review.menu, menu) - .where( - menuIdIn(menuIds) - ) - .fetchOne(); - } - - private BooleanExpression menuIdIn(List menuIds) { - return menuIds != null && !menuIds.isEmpty() ? menu.id.in(menuIds) : null; - } + private final MealMenuQueryRepository mealMenuQueryRepository; + + private final JPAQueryFactory queryFactory; + private final QMenu menu = QMenu.menu; + + private final QReview review = QReview.review; + + public Double getMainRatingAverage(Long mealId) { + List menuIds = mealMenuQueryRepository.getMenuIds(mealId); + return queryFactory + .select(review.ratings.mainRating.avg()) + .from(review) + .join(review.menu, menu) + .where( + menuIdIn(menuIds) + ) + .fetchOne(); + } + + public Double getTasteRatingAverage(Long mealId) { + List menuIds = mealMenuQueryRepository.getMenuIds(mealId); + return queryFactory + .select(review.ratings.tasteRating.avg()) + .from(review) + .join(review.menu, menu) + .where( + menuIdIn(menuIds) + ) + .fetchOne(); + } + + public Double getAmountRatingAverage(Long mealId) { + List menuIds = mealMenuQueryRepository.getMenuIds(mealId); + return queryFactory + .select(review.ratings.amountRating.avg()) + .from(review) + .join(review.menu, menu) + .where( + menuIdIn(menuIds) + ) + .fetchOne(); + } + + private BooleanExpression menuIdIn(List menuIds) { + return menuIds != null && !menuIds.isEmpty() ? menu.id.in(menuIds) : null; + } } diff --git a/src/main/java/ssu/eatssu/domain/menu/persistence/QuerydslMealRatingCounter.java b/src/main/java/ssu/eatssu/domain/menu/persistence/QuerydslMealRatingCounter.java index a4125b0d..0222a171 100644 --- a/src/main/java/ssu/eatssu/domain/menu/persistence/QuerydslMealRatingCounter.java +++ b/src/main/java/ssu/eatssu/domain/menu/persistence/QuerydslMealRatingCounter.java @@ -1,58 +1,56 @@ package ssu.eatssu.domain.menu.persistence; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import org.springframework.stereotype.Repository; - import com.querydsl.core.Tuple; import com.querydsl.core.types.dsl.BooleanExpression; import com.querydsl.jpa.impl.JPAQueryFactory; - import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Repository; import ssu.eatssu.domain.menu.entity.QMenu; import ssu.eatssu.domain.review.entity.QReview; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + @Repository @RequiredArgsConstructor public class QuerydslMealRatingCounter { - private final MealMenuQueryRepository mealMenuQueryRepository; - - private final JPAQueryFactory queryFactory; - private final QMenu menu = QMenu.menu; - - private final QReview review = QReview.review; - - public Map getRatingCountMap(Long mealId) { - List tuples = queryFactory.select(review.ratings.mainRating, review.count()) - .from(review) - .join(review.menu, menu) - .groupBy(review.ratings.mainRating) - .where(menuIdIn(mealMenuQueryRepository.getMenuIds(mealId))) - .fetch(); - - Map ratingCountMap = new HashMap<>(); - for (Tuple tuple : tuples) { - Integer rating = tuple.get(review.ratings.mainRating); - Long count = tuple.get(review.count()); - ratingCountMap.put(rating, count); - } - return ratingCountMap; - } - - public Long getTotalRatingCount(Long mealId) { - return queryFactory - .select(review.count()) - .from(review) - .join(review.menu, menu) - .where(menuIdIn(mealMenuQueryRepository.getMenuIds(mealId))) - .fetchOne(); - } - - private BooleanExpression menuIdIn(List menuIds) { - return menuIds != null && !menuIds.isEmpty() ? menu.id.in(menuIds) : null; - } + private final MealMenuQueryRepository mealMenuQueryRepository; + + private final JPAQueryFactory queryFactory; + private final QMenu menu = QMenu.menu; + + private final QReview review = QReview.review; + + public Map getRatingCountMap(Long mealId) { + List tuples = queryFactory.select(review.ratings.mainRating, review.count()) + .from(review) + .join(review.menu, menu) + .groupBy(review.ratings.mainRating) + .where(menuIdIn(mealMenuQueryRepository.getMenuIds(mealId))) + .fetch(); + + Map ratingCountMap = new HashMap<>(); + for (Tuple tuple : tuples) { + Integer rating = tuple.get(review.ratings.mainRating); + Long count = tuple.get(review.count()); + ratingCountMap.put(rating, count); + } + return ratingCountMap; + } + + public Long getTotalRatingCount(Long mealId) { + return queryFactory + .select(review.count()) + .from(review) + .join(review.menu, menu) + .where(menuIdIn(mealMenuQueryRepository.getMenuIds(mealId))) + .fetchOne(); + } + + private BooleanExpression menuIdIn(List menuIds) { + return menuIds != null && !menuIds.isEmpty() ? menu.id.in(menuIds) : null; + } } 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 dcfb2795..97e33f81 100644 --- a/src/main/java/ssu/eatssu/domain/menu/persistence/QuerydslMenuRatingCalculator.java +++ b/src/main/java/ssu/eatssu/domain/menu/persistence/QuerydslMenuRatingCalculator.java @@ -1,11 +1,9 @@ package ssu.eatssu.domain.menu.persistence; -import org.springframework.stereotype.Repository; - import com.querydsl.core.types.dsl.BooleanExpression; import com.querydsl.jpa.impl.JPAQueryFactory; - import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Repository; import ssu.eatssu.domain.menu.entity.QMenu; import ssu.eatssu.domain.review.entity.QReview; @@ -13,44 +11,44 @@ @RequiredArgsConstructor public class QuerydslMenuRatingCalculator { - private final JPAQueryFactory queryFactory; - private final QMenu menu = QMenu.menu; - private final QReview review = QReview.review; - - public Double getMainRatingAverage(Long menuId) { - return queryFactory - .select(review.ratings.mainRating.avg()) - .from(review) - .join(review.menu, menu) - .where( - menuIdEq(menuId) - ) - .fetchOne(); - } - - public Double getTasteRatingAverage(Long menuId) { - return queryFactory - .select(review.ratings.tasteRating.avg()) - .from(review) - .join(review.menu, menu) - .where( - menuIdEq(menuId) - ) - .fetchOne(); - } - - public Double getAmountRatingAverage(Long menuId) { - return queryFactory - .select(review.ratings.amountRating.avg()) - .from(review) - .join(review.menu, menu) - .where( - menuIdEq(menuId) - ) - .fetchOne(); - } - - private BooleanExpression menuIdEq(Long menuId) { - return menu.id.eq(menuId); - } + private final JPAQueryFactory queryFactory; + private final QMenu menu = QMenu.menu; + private final QReview review = QReview.review; + + public Double getMainRatingAverage(Long menuId) { + return queryFactory + .select(review.ratings.mainRating.avg()) + .from(review) + .join(review.menu, menu) + .where( + menuIdEq(menuId) + ) + .fetchOne(); + } + + public Double getTasteRatingAverage(Long menuId) { + return queryFactory + .select(review.ratings.tasteRating.avg()) + .from(review) + .join(review.menu, menu) + .where( + menuIdEq(menuId) + ) + .fetchOne(); + } + + public Double getAmountRatingAverage(Long menuId) { + return queryFactory + .select(review.ratings.amountRating.avg()) + .from(review) + .join(review.menu, menu) + .where( + menuIdEq(menuId) + ) + .fetchOne(); + } + + private BooleanExpression menuIdEq(Long menuId) { + return menu.id.eq(menuId); + } } diff --git a/src/main/java/ssu/eatssu/domain/menu/persistence/QuerydslMenuRatingCounter.java b/src/main/java/ssu/eatssu/domain/menu/persistence/QuerydslMenuRatingCounter.java index db426a00..e035465d 100644 --- a/src/main/java/ssu/eatssu/domain/menu/persistence/QuerydslMenuRatingCounter.java +++ b/src/main/java/ssu/eatssu/domain/menu/persistence/QuerydslMenuRatingCounter.java @@ -1,46 +1,44 @@ package ssu.eatssu.domain.menu.persistence; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import org.springframework.stereotype.Repository; - import com.querydsl.core.Tuple; import com.querydsl.core.types.dsl.BooleanExpression; import com.querydsl.jpa.impl.JPAQueryFactory; - import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Repository; import ssu.eatssu.domain.menu.entity.QMenu; import ssu.eatssu.domain.review.entity.QReview; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + @Repository @RequiredArgsConstructor public class QuerydslMenuRatingCounter { - private final JPAQueryFactory queryFactory; - private final QMenu menu = QMenu.menu; - private final QReview review = QReview.review; - - public Map getRatingCountMap(Long menuId) { - List tuples = queryFactory.select(review.ratings.mainRating, review.count()) - .from(review) - .join(review.menu, menu) - .groupBy(review.ratings.mainRating) - .where(menuIdEq(menuId)) - .fetch(); - - Map ratingCountMap = new HashMap<>(); - - for (Tuple tuple : tuples) { - Integer rating = tuple.get(review.ratings.mainRating); - Long count = tuple.get(review.count()); - ratingCountMap.put(rating, count); - } - return ratingCountMap; - } - - private BooleanExpression menuIdEq(Long menuId) { - return menu.id.eq(menuId); - } + private final JPAQueryFactory queryFactory; + private final QMenu menu = QMenu.menu; + private final QReview review = QReview.review; + + public Map getRatingCountMap(Long menuId) { + List tuples = queryFactory.select(review.ratings.mainRating, review.count()) + .from(review) + .join(review.menu, menu) + .groupBy(review.ratings.mainRating) + .where(menuIdEq(menuId)) + .fetch(); + + Map ratingCountMap = new HashMap<>(); + + for (Tuple tuple : tuples) { + Integer rating = tuple.get(review.ratings.mainRating); + Long count = tuple.get(review.count()); + ratingCountMap.put(rating, count); + } + return ratingCountMap; + } + + private BooleanExpression menuIdEq(Long menuId) { + return menu.id.eq(menuId); + } } diff --git a/src/main/java/ssu/eatssu/domain/menu/presentation/dto/request/CreateMealRequest.java b/src/main/java/ssu/eatssu/domain/menu/presentation/dto/request/CreateMealRequest.java index 6286bc52..a865c887 100644 --- a/src/main/java/ssu/eatssu/domain/menu/presentation/dto/request/CreateMealRequest.java +++ b/src/main/java/ssu/eatssu/domain/menu/presentation/dto/request/CreateMealRequest.java @@ -1,13 +1,13 @@ package ssu.eatssu.domain.menu.presentation.dto.request; -import java.util.List; - import io.swagger.v3.oas.annotations.media.Schema; +import java.util.List; + @Schema(title = "식단 추가") public record CreateMealRequest( - @Schema(description = "메뉴명 리스트", example = "[\"돈까스\", \"샐러드\", \"김치\"]") - List menuNames + @Schema(description = "메뉴명 리스트", example = "[\"돈까스\", \"샐러드\", \"김치\"]") + List menuNames ) { } diff --git a/src/main/java/ssu/eatssu/domain/menu/presentation/dto/request/MealCreateWithPriceRequest.java b/src/main/java/ssu/eatssu/domain/menu/presentation/dto/request/MealCreateWithPriceRequest.java index 0c5c2bcf..7035d835 100644 --- a/src/main/java/ssu/eatssu/domain/menu/presentation/dto/request/MealCreateWithPriceRequest.java +++ b/src/main/java/ssu/eatssu/domain/menu/presentation/dto/request/MealCreateWithPriceRequest.java @@ -1,15 +1,15 @@ package ssu.eatssu.domain.menu.presentation.dto.request; -import java.util.List; - import io.swagger.v3.oas.annotations.media.Schema; +import java.util.List; + @Schema(title = "식단 추가(가격 입력 받는 버전)") public record MealCreateWithPriceRequest( - @Schema(description = "메뉴명 리스트", example = "[\"돈까스\", \"샐러드\", \"김치\"]") - List menuNames, - @Schema(description = "식단 가격", example = "3000") - Integer price + @Schema(description = "메뉴명 리스트", example = "[\"돈까스\", \"샐러드\", \"김치\"]") + List menuNames, + @Schema(description = "식단 가격", example = "3000") + Integer price ) { } diff --git a/src/main/java/ssu/eatssu/domain/menu/presentation/dto/response/BriefMenuResponse.java b/src/main/java/ssu/eatssu/domain/menu/presentation/dto/response/BriefMenuResponse.java index 2f0dd1f6..5906c9ab 100644 --- a/src/main/java/ssu/eatssu/domain/menu/presentation/dto/response/BriefMenuResponse.java +++ b/src/main/java/ssu/eatssu/domain/menu/presentation/dto/response/BriefMenuResponse.java @@ -10,14 +10,14 @@ @Schema(title = "메뉴 간단 설명") public class BriefMenuResponse { - @Schema(description = "메뉴 식별자", example = "2") - private Long menuId; + @Schema(description = "메뉴 식별자", example = "2") + private Long menuId; - @Schema(description = "메뉴 이름", example = "돈까스") - private String name; + @Schema(description = "메뉴 이름", example = "돈까스") + private String name; - public BriefMenuResponse(Menu menu) { - this.menuId = menu.getId(); - this.name = menu.getName(); - } + public BriefMenuResponse(Menu menu) { + this.menuId = menu.getId(); + this.name = menu.getName(); + } } diff --git a/src/main/java/ssu/eatssu/domain/menu/presentation/dto/response/CategoryWithMenusResponse.java b/src/main/java/ssu/eatssu/domain/menu/presentation/dto/response/CategoryWithMenusResponse.java index 20eee35d..c96c6e19 100644 --- a/src/main/java/ssu/eatssu/domain/menu/presentation/dto/response/CategoryWithMenusResponse.java +++ b/src/main/java/ssu/eatssu/domain/menu/presentation/dto/response/CategoryWithMenusResponse.java @@ -1,26 +1,26 @@ package ssu.eatssu.domain.menu.presentation.dto.response; -import java.util.List; - import io.swagger.v3.oas.annotations.media.Schema; import lombok.Getter; +import java.util.List; + @Getter @Schema(title = "카테고리 별 메뉴 목록 Response") public class CategoryWithMenusResponse { - @Schema(description = "카테고리명") - private final String category; + @Schema(description = "카테고리명") + private final String category; - @Schema(description = "카테고리의 메뉴 목록") - private final List menus; + @Schema(description = "카테고리의 메뉴 목록") + private final List menus; - public CategoryWithMenusResponse(String category, List menus) { - this.category = category; - this.menus = menus; - } + public CategoryWithMenusResponse(String category, List menus) { + this.category = category; + this.menus = menus; + } - public static CategoryWithMenusResponse of(String category, List menus) { - return new CategoryWithMenusResponse(category, menus); - } + public static CategoryWithMenusResponse of(String category, List menus) { + return new CategoryWithMenusResponse(category, menus); + } } diff --git a/src/main/java/ssu/eatssu/domain/menu/presentation/dto/response/MealDetailResponse.java b/src/main/java/ssu/eatssu/domain/menu/presentation/dto/response/MealDetailResponse.java index 81544743..afdbd0df 100644 --- a/src/main/java/ssu/eatssu/domain/menu/presentation/dto/response/MealDetailResponse.java +++ b/src/main/java/ssu/eatssu/domain/menu/presentation/dto/response/MealDetailResponse.java @@ -1,7 +1,5 @@ package ssu.eatssu.domain.menu.presentation.dto.response; -import java.util.List; - import io.swagger.v3.oas.annotations.media.Schema; import lombok.AllArgsConstructor; import lombok.Getter; @@ -9,37 +7,39 @@ import ssu.eatssu.domain.menu.entity.Meal; import ssu.eatssu.domain.menu.entity.MealMenu; +import java.util.List; + @Getter @NoArgsConstructor @AllArgsConstructor @Schema(title = "오늘의 식단 리스트 조회 Response") public class MealDetailResponse { - @Schema(description = "식단 식별자 mealId", example = "12") - private Long mealId; + @Schema(description = "식단 식별자 mealId", example = "12") + private Long mealId; - @Schema(description = "가격", example = "5000") - private Integer price; + @Schema(description = "가격", example = "5000") + private Integer price; - @Schema(description = "식단 평점(평점이 없으면 null)", example = "4.4") - private Double rating; + @Schema(description = "식단 평점(평점이 없으면 null)", example = "4.4") + private Double rating; - @Schema(description = "식단 속 메뉴 정보 리스트") - private List briefMenus; + @Schema(description = "식단 속 메뉴 정보 리스트") + private List briefMenus; - public static MealDetailResponse from(Meal meal, Double mainRating) { - List briefMenus = meal.getMealMenus().stream() - .map(MealMenu::getMenu) - .map(BriefMenuResponse::new) - .toList(); + public static MealDetailResponse from(Meal meal, Double mainRating) { + List briefMenus = meal.getMealMenus().stream() + .map(MealMenu::getMenu) + .map(BriefMenuResponse::new) + .toList(); - return new MealDetailResponse( - meal.getId(), - meal.getPrice(), - mainRating, - briefMenus - ); - } + return new MealDetailResponse( + meal.getId(), + meal.getPrice(), + mainRating, + briefMenus + ); + } } diff --git a/src/main/java/ssu/eatssu/domain/menu/presentation/dto/response/MenuResponse.java b/src/main/java/ssu/eatssu/domain/menu/presentation/dto/response/MenuResponse.java index 70e3d5de..bd3f6e87 100644 --- a/src/main/java/ssu/eatssu/domain/menu/presentation/dto/response/MenuResponse.java +++ b/src/main/java/ssu/eatssu/domain/menu/presentation/dto/response/MenuResponse.java @@ -12,19 +12,19 @@ @Schema(title = "고정 메뉴 정보") public class MenuResponse { - @Schema(description = "메뉴 식별자", example = "2") - private Long menuId; + @Schema(description = "메뉴 식별자", example = "2") + private Long menuId; - @Schema(description = "메뉴 이름", example = "돈까스") - private String name; + @Schema(description = "메뉴 이름", example = "돈까스") + private String name; - @Schema(description = "가격", example = "5000") - private Integer price; + @Schema(description = "가격", example = "5000") + private Integer price; - @Schema(description = "메뉴 평점 (평점이 없으면 null) ", example = "4.4") - private Double rating; + @Schema(description = "메뉴 평점 (평점이 없으면 null) ", example = "4.4") + private Double rating; - public static MenuResponse from(Menu menu, Double rating) { - return new MenuResponse(menu.getId(), menu.getName(), menu.getPrice(), rating); - } + public static MenuResponse from(Menu menu, Double rating) { + return new MenuResponse(menu.getId(), menu.getName(), menu.getPrice(), rating); + } } diff --git a/src/main/java/ssu/eatssu/domain/menu/presentation/dto/response/MenuRestaurantResponse.java b/src/main/java/ssu/eatssu/domain/menu/presentation/dto/response/MenuRestaurantResponse.java index ec1e7a53..8e61d251 100644 --- a/src/main/java/ssu/eatssu/domain/menu/presentation/dto/response/MenuRestaurantResponse.java +++ b/src/main/java/ssu/eatssu/domain/menu/presentation/dto/response/MenuRestaurantResponse.java @@ -1,28 +1,28 @@ package ssu.eatssu.domain.menu.presentation.dto.response; +import lombok.Getter; + import java.util.ArrayList; import java.util.List; -import lombok.Getter; - @Getter public class MenuRestaurantResponse { - private final List categoryMenuListCollection; + private final List categoryMenuListCollection; - private MenuRestaurantResponse(List categoryMenu) { - this.categoryMenuListCollection = categoryMenu; - } + private MenuRestaurantResponse(List categoryMenu) { + this.categoryMenuListCollection = categoryMenu; + } - public static MenuRestaurantResponse init() { - return new MenuRestaurantResponse(new ArrayList<>()); - } + public static MenuRestaurantResponse init() { + return new MenuRestaurantResponse(new ArrayList<>()); + } - public void add(CategoryWithMenusResponse categoryWithMenusResponse) { - this.categoryMenuListCollection.add(categoryWithMenusResponse); - } + public void add(CategoryWithMenusResponse categoryWithMenusResponse) { + this.categoryMenuListCollection.add(categoryWithMenusResponse); + } - public void addAll(List categoryWithMenusResponses) { - this.categoryMenuListCollection.addAll(categoryWithMenusResponses); - } + public void addAll(List categoryWithMenusResponses) { + this.categoryMenuListCollection.addAll(categoryWithMenusResponses); + } } 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 85480c5d..cc58ea3d 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 @@ -1,7 +1,5 @@ package ssu.eatssu.domain.menu.presentation.dto.response; -import java.util.List; - import io.swagger.v3.oas.annotations.media.Schema; import lombok.AllArgsConstructor; import lombok.Getter; @@ -9,21 +7,23 @@ import ssu.eatssu.domain.menu.entity.Meal; import ssu.eatssu.domain.menu.entity.MealMenu; +import java.util.List; + @Getter @NoArgsConstructor @AllArgsConstructor @Schema(title = "식단 속 메뉴 정보 리스트") public class MenusInMealResponse { - @Schema(description = "식단 속 메뉴 목록", example = "[]") - private List briefMenus; + @Schema(description = "식단 속 메뉴 목록", example = "[]") + private List briefMenus; - public static MenusInMealResponse from(Meal meal) { - List menusInformation = meal.getMealMenus().stream() - .map(MealMenu::getMenu) - .map(BriefMenuResponse::new) - .toList(); + public static MenusInMealResponse from(Meal meal) { + List menusInformation = meal.getMealMenus().stream() + .map(MealMenu::getMenu) + .map(BriefMenuResponse::new) + .toList(); - return new MenusInMealResponse(menusInformation); - } + return new MenusInMealResponse(menusInformation); + } } diff --git a/src/main/java/ssu/eatssu/domain/menu/presentation/rest/MealController.java b/src/main/java/ssu/eatssu/domain/menu/presentation/rest/MealController.java index 04f24f7a..d2206241 100644 --- a/src/main/java/ssu/eatssu/domain/menu/presentation/rest/MealController.java +++ b/src/main/java/ssu/eatssu/domain/menu/presentation/rest/MealController.java @@ -1,10 +1,13 @@ package ssu.eatssu.domain.menu.presentation.rest; -import static ssu.eatssu.global.handler.response.BaseResponseStatus.*; - -import java.util.Date; -import java.util.List; - +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.tags.Tag; +import lombok.RequiredArgsConstructor; import org.springframework.format.annotation.DateTimeFormat; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; @@ -14,15 +17,6 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; - -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.tags.Tag; -import lombok.RequiredArgsConstructor; import ssu.eatssu.domain.menu.entity.constants.TimePart; import ssu.eatssu.domain.menu.presentation.dto.request.CreateMealRequest; import ssu.eatssu.domain.menu.presentation.dto.request.MealCreateWithPriceRequest; @@ -34,109 +28,114 @@ import ssu.eatssu.global.handler.response.BaseException; import ssu.eatssu.global.handler.response.BaseResponse; +import java.util.Date; +import java.util.List; + +import static ssu.eatssu.global.handler.response.BaseResponseStatus.NOT_SUPPORT_RESTAURANT; + @RestController @RequiredArgsConstructor @RequestMapping("/meals") @Tag(name = "Meal", description = "식단 API") public class MealController { - private final MealService mealService; + private final MealService mealService; - @Operation(summary = "식단 추가 [인증 토큰 필요 X]", description = """ - 식단을 추가하는 API 입니다.

- 변동메뉴 식당(학생식당, 도담, 기숙사 식당)의 특정날짜(yyyyMMdd), 특정시간대(아침/점심/저녁)에 해당하는 식단을 추가합니다.

- 이미 존재하는 식단일 경우 중복저장 되지 않도록 처리합니다.(별도의 ErrorResponse 응답 X) - """) - @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "식단 추가 성공"), - @ApiResponse(responseCode = "400", description = "지원하지 않는 식당(고정 메뉴 식당)", content = @Content(schema = @Schema(implementation = BaseResponse.class))), - @ApiResponse(responseCode = "400", description = "잘못된 날짜형식", content = @Content(schema = @Schema(implementation = BaseResponse.class))), - @ApiResponse(responseCode = "404", description = "존재하지 않는 식당", content = @Content(schema = @Schema(implementation = BaseResponse.class))) - }) - @PostMapping("") - public BaseResponse createMeal( - @Parameter(schema = @Schema(type = "string", format = "date", example = "20240101")) @RequestParam("date") @DateTimeFormat(pattern = "yyyyMMdd") Date date, - @Parameter(description = "식당이름") @RequestParam("restaurant") Restaurant restaurant, - @Parameter(description = "시간대") @RequestParam("time") TimePart timePart, - @RequestBody CreateMealRequest mealCreateRequest) { - if (RestaurantType.isFixedType(restaurant)) { - throw new BaseException(NOT_SUPPORT_RESTAURANT); - } + @Operation(summary = "식단 추가 [인증 토큰 필요 X]", description = """ + 식단을 추가하는 API 입니다.

+ 변동메뉴 식당(학생식당, 도담, 기숙사 식당)의 특정날짜(yyyyMMdd), 특정시간대(아침/점심/저녁)에 해당하는 식단을 추가합니다.

+ 이미 존재하는 식단일 경우 중복저장 되지 않도록 처리합니다.(별도의 ErrorResponse 응답 X) + """) + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "식단 추가 성공"), + @ApiResponse(responseCode = "400", description = "지원하지 않는 식당(고정 메뉴 식당)", content = @Content(schema = @Schema(implementation = BaseResponse.class))), + @ApiResponse(responseCode = "400", description = "잘못된 날짜형식", content = @Content(schema = @Schema(implementation = BaseResponse.class))), + @ApiResponse(responseCode = "404", description = "존재하지 않는 식당", content = @Content(schema = @Schema(implementation = BaseResponse.class))) + }) + @PostMapping("") + public BaseResponse createMeal( + @Parameter(schema = @Schema(type = "string", format = "date", example = "20240101")) @RequestParam("date") @DateTimeFormat(pattern = "yyyyMMdd") Date date, + @Parameter(description = "식당이름") @RequestParam("restaurant") Restaurant restaurant, + @Parameter(description = "시간대") @RequestParam("time") TimePart timePart, + @RequestBody CreateMealRequest mealCreateRequest) { + if (RestaurantType.isFixedType(restaurant)) { + throw new BaseException(NOT_SUPPORT_RESTAURANT); + } - mealService.createMeal(date, restaurant, timePart, mealCreateRequest); - return BaseResponse.success(); - } + mealService.createMeal(date, restaurant, timePart, mealCreateRequest); + return BaseResponse.success(); + } - @Operation(summary = "식단 추가 [인증 토큰 필요 X]", description = """ - 식단을 추가하는 API 입니다.

- 변동메뉴 식당(학생식당, 도담, 기숙사 식당)의 특정날짜(yyyyMMdd), 특정시간대(아침/점심/저녁)에 해당하는 식단을 추가합니다.

- 이미 존재하는 식단일 경우 중복저장 되지 않도록 처리합니다.(별도의 ErrorResponse 응답 X) - 가격을 외부에서 입력받습니다. - """) - @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "식단 추가 성공"), - @ApiResponse(responseCode = "400", description = "지원하지 않는 식당(고정 메뉴 식당)", content = @Content(schema = @Schema(implementation = BaseResponse.class))), - @ApiResponse(responseCode = "400", description = "잘못된 날짜형식", content = @Content(schema = @Schema(implementation = BaseResponse.class))), - @ApiResponse(responseCode = "404", description = "존재하지 않는 식당", content = @Content(schema = @Schema(implementation = BaseResponse.class))) - }) - @PostMapping("/with-price") - public BaseResponse createMealWithPrice( - @Parameter(schema = @Schema(type = "string", format = "date", example = "20240101")) @RequestParam("date") @DateTimeFormat(pattern = "yyyyMMdd") Date date, - @Parameter(description = "식당이름") @RequestParam("restaurant") Restaurant restaurant, - @Parameter(description = "시간대") @RequestParam("time") TimePart timePart, - @RequestBody MealCreateWithPriceRequest request) { - if (RestaurantType.isFixedType(restaurant)) { - throw new BaseException(NOT_SUPPORT_RESTAURANT); - } + @Operation(summary = "식단 추가 [인증 토큰 필요 X]", description = """ + 식단을 추가하는 API 입니다.

+ 변동메뉴 식당(학생식당, 도담, 기숙사 식당)의 특정날짜(yyyyMMdd), 특정시간대(아침/점심/저녁)에 해당하는 식단을 추가합니다.

+ 이미 존재하는 식단일 경우 중복저장 되지 않도록 처리합니다.(별도의 ErrorResponse 응답 X) + 가격을 외부에서 입력받습니다. + """) + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "식단 추가 성공"), + @ApiResponse(responseCode = "400", description = "지원하지 않는 식당(고정 메뉴 식당)", content = @Content(schema = @Schema(implementation = BaseResponse.class))), + @ApiResponse(responseCode = "400", description = "잘못된 날짜형식", content = @Content(schema = @Schema(implementation = BaseResponse.class))), + @ApiResponse(responseCode = "404", description = "존재하지 않는 식당", content = @Content(schema = @Schema(implementation = BaseResponse.class))) + }) + @PostMapping("/with-price") + public BaseResponse createMealWithPrice( + @Parameter(schema = @Schema(type = "string", format = "date", example = "20240101")) @RequestParam("date") @DateTimeFormat(pattern = "yyyyMMdd") Date date, + @Parameter(description = "식당이름") @RequestParam("restaurant") Restaurant restaurant, + @Parameter(description = "시간대") @RequestParam("time") TimePart timePart, + @RequestBody MealCreateWithPriceRequest request) { + if (RestaurantType.isFixedType(restaurant)) { + throw new BaseException(NOT_SUPPORT_RESTAURANT); + } - mealService.createMealWithPrice(date, restaurant, timePart, request); - return BaseResponse.success(); - } + mealService.createMealWithPrice(date, restaurant, timePart, request); + return BaseResponse.success(); + } - @Operation(summary = "변동 메뉴 식단 리스트 조회 [인증 토큰 필요 X]", description = """ - 변동 메뉴 식단 리스트를 조회하는 API 입니다. - 변동 메뉴 식당 (학생 식당, 도담, 기숙사 식당) 의 특정날짜(yyyyMMdd), 특정시간대(아침/점심/저녁)에 해당하는 식단 목록을 조회합니다. - 일반적으로 학생식당과 도담의 경우 식단 여러 개가 조회되고 기숙사식당은 한 개만 조회됩니다.) - """) - @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "식단 리스트 조회 성공"), - @ApiResponse(responseCode = "400", description = "지원 하지 않는 식당 (고정 메뉴 식당)", content = @Content(schema = @Schema(implementation = BaseResponse.class))), - @ApiResponse(responseCode = "404", description = "존재 하지 않는 식당", content = @Content(schema = @Schema(implementation = BaseResponse.class))) - }) - @GetMapping("") - public BaseResponse> getMealDetail( - @Parameter(schema = @Schema(type = "string", format = "date", example = "20240101")) @RequestParam("date") @DateTimeFormat(pattern = "yyyyMMdd") Date date, - @Parameter(description = "식당 이름") @RequestParam("restaurant") Restaurant restaurant, - @Parameter(description = "시간대") @RequestParam("time") TimePart timePart) { + @Operation(summary = "변동 메뉴 식단 리스트 조회 [인증 토큰 필요 X]", description = """ + 변동 메뉴 식단 리스트를 조회하는 API 입니다. + 변동 메뉴 식당 (학생 식당, 도담, 기숙사 식당) 의 특정날짜(yyyyMMdd), 특정시간대(아침/점심/저녁)에 해당하는 식단 목록을 조회합니다. + 일반적으로 학생식당과 도담의 경우 식단 여러 개가 조회되고 기숙사식당은 한 개만 조회됩니다.) + """) + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "식단 리스트 조회 성공"), + @ApiResponse(responseCode = "400", description = "지원 하지 않는 식당 (고정 메뉴 식당)", content = @Content(schema = @Schema(implementation = BaseResponse.class))), + @ApiResponse(responseCode = "404", description = "존재 하지 않는 식당", content = @Content(schema = @Schema(implementation = BaseResponse.class))) + }) + @GetMapping("") + public BaseResponse> getMealDetail( + @Parameter(schema = @Schema(type = "string", format = "date", example = "20240101")) @RequestParam("date") @DateTimeFormat(pattern = "yyyyMMdd") Date date, + @Parameter(description = "식당 이름") @RequestParam("restaurant") Restaurant restaurant, + @Parameter(description = "시간대") @RequestParam("time") TimePart timePart) { - return BaseResponse.success( - mealService.getMealDetailsByDateAndRestaurantAndTimePart(date, restaurant, timePart)); - } + return BaseResponse.success( + mealService.getMealDetailsByDateAndRestaurantAndTimePart(date, restaurant, timePart)); + } - @Operation(summary = "식단 삭제 [인증 토큰 필요 X]", description = "식단을 삭제하는 API 입니다.") - @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "식단 삭제 성공"), - @ApiResponse(responseCode = "404", description = "존재하지 않는 식단", content = @Content(schema = @Schema(implementation = BaseResponse.class))) - }) - @DeleteMapping("/{mealId}") - public BaseResponse deleteMeal( - @Parameter(description = "mealId") @PathVariable("mealId") Long mealId) { + @Operation(summary = "식단 삭제 [인증 토큰 필요 X]", description = "식단을 삭제하는 API 입니다.") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "식단 삭제 성공"), + @ApiResponse(responseCode = "404", description = "존재하지 않는 식단", content = @Content(schema = @Schema(implementation = BaseResponse.class))) + }) + @DeleteMapping("/{mealId}") + public BaseResponse deleteMeal( + @Parameter(description = "mealId") @PathVariable("mealId") Long mealId) { - mealService.deleteByMealId(mealId); - return BaseResponse.success(); - } + mealService.deleteByMealId(mealId); + return BaseResponse.success(); + } - @Operation(summary = "메뉴 정보 리스트 조회 [인증 토큰 필요 X]", description = """ - 메뉴 정보 리스트를 조회하는 API 입니다.

- 식단식별자(mealId)로 해당 식단에 속하는 메뉴 정보 목록을 조회합니다.") - """) - @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "메뉴 정보 리스트 조회 성공"), - @ApiResponse(responseCode = "404", description = "존재하지 않는 식단", content = @Content(schema = @Schema(implementation = BaseResponse.class))) - }) - @GetMapping("/{mealId}/menus-info") - public BaseResponse getMenusInMeal(@Parameter(description = "mealId") - @PathVariable("mealId") Long mealId) { - return BaseResponse.success(mealService.getMenusInMealByMealId(mealId)); - } + @Operation(summary = "메뉴 정보 리스트 조회 [인증 토큰 필요 X]", description = """ + 메뉴 정보 리스트를 조회하는 API 입니다.

+ 식단식별자(mealId)로 해당 식단에 속하는 메뉴 정보 목록을 조회합니다.") + """) + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "메뉴 정보 리스트 조회 성공"), + @ApiResponse(responseCode = "404", description = "존재하지 않는 식단", content = @Content(schema = @Schema(implementation = BaseResponse.class))) + }) + @GetMapping("/{mealId}/menus-info") + public BaseResponse getMenusInMeal(@Parameter(description = "mealId") + @PathVariable("mealId") Long mealId) { + return BaseResponse.success(mealService.getMenusInMealByMealId(mealId)); + } } diff --git a/src/main/java/ssu/eatssu/domain/menu/presentation/rest/MenuController.java b/src/main/java/ssu/eatssu/domain/menu/presentation/rest/MenuController.java index 21abe326..7c125034 100644 --- a/src/main/java/ssu/eatssu/domain/menu/presentation/rest/MenuController.java +++ b/src/main/java/ssu/eatssu/domain/menu/presentation/rest/MenuController.java @@ -1,10 +1,5 @@ package ssu.eatssu.domain.menu.presentation.rest; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.bind.annotation.RestController; - import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.media.Content; import io.swagger.v3.oas.annotations.media.Schema; @@ -12,6 +7,10 @@ import io.swagger.v3.oas.annotations.responses.ApiResponses; import io.swagger.v3.oas.annotations.tags.Tag; import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; import ssu.eatssu.domain.menu.presentation.dto.response.MenuRestaurantResponse; import ssu.eatssu.domain.menu.service.MenuService; import ssu.eatssu.domain.restaurant.entity.Restaurant; @@ -23,24 +22,24 @@ @Tag(name = "Menu", description = "메뉴 API") public class MenuController { - private final MenuService menuService; + private final MenuService menuService; - @Operation(summary = "고정 메뉴 리스트 조회 [인증 토큰 필요 X]", - description = - """ - 고정 메뉴 리스트를 조회하는 API 입니다. - 메뉴가 고정된 식당(푸드코트, 스낵코너, 더 키친)의 메뉴 리스트를 조회합니다. - """ - ) - @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "메뉴 리스트 조회 성공"), - @ApiResponse(responseCode = "400", description = "지원하지 않는 식당(변동 메뉴 식당)", content = @Content(schema = @Schema(implementation = BaseResponse.class))), - @ApiResponse(responseCode = "404", description = "존재하지 않는 식당", content = @Content(schema = @Schema(implementation = BaseResponse.class))) - }) - @GetMapping - public BaseResponse getMenus( - @RequestParam("restaurant") Restaurant restaurant) { + @Operation(summary = "고정 메뉴 리스트 조회 [인증 토큰 필요 X]", + description = + """ + 고정 메뉴 리스트를 조회하는 API 입니다. + 메뉴가 고정된 식당(푸드코트, 스낵코너, 더 키친)의 메뉴 리스트를 조회합니다. + """ + ) + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "메뉴 리스트 조회 성공"), + @ApiResponse(responseCode = "400", description = "지원하지 않는 식당(변동 메뉴 식당)", content = @Content(schema = @Schema(implementation = BaseResponse.class))), + @ApiResponse(responseCode = "404", description = "존재하지 않는 식당", content = @Content(schema = @Schema(implementation = BaseResponse.class))) + }) + @GetMapping + public BaseResponse getMenus( + @RequestParam("restaurant") Restaurant restaurant) { - return BaseResponse.success(menuService.getMenusByRestaurant(restaurant)); - } + return BaseResponse.success(menuService.getMenusByRestaurant(restaurant)); + } } diff --git a/src/main/java/ssu/eatssu/domain/menu/service/MealRatingService.java b/src/main/java/ssu/eatssu/domain/menu/service/MealRatingService.java index 4756055c..b338e725 100644 --- a/src/main/java/ssu/eatssu/domain/menu/service/MealRatingService.java +++ b/src/main/java/ssu/eatssu/domain/menu/service/MealRatingService.java @@ -1,18 +1,17 @@ package ssu.eatssu.domain.menu.service; -import org.springframework.stereotype.Service; - import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; import ssu.eatssu.domain.menu.persistence.QuerydslMealRatingCalculator; @Service @RequiredArgsConstructor public class MealRatingService { - private final QuerydslMealRatingCalculator mealRatingCalculator; + private final QuerydslMealRatingCalculator mealRatingCalculator; - public Double getMainRatingAverage(Long mealId) { - return mealRatingCalculator.getMainRatingAverage(mealId); - } + public Double getMainRatingAverage(Long mealId) { + return mealRatingCalculator.getMainRatingAverage(mealId); + } } 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 a858543c..4fc0859d 100644 --- a/src/main/java/ssu/eatssu/domain/menu/service/MealService.java +++ b/src/main/java/ssu/eatssu/domain/menu/service/MealService.java @@ -1,16 +1,9 @@ package ssu.eatssu.domain.menu.service; -import static ssu.eatssu.global.handler.response.BaseResponseStatus.*; - -import java.util.Date; -import java.util.List; -import java.util.Optional; - -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; import ssu.eatssu.domain.menu.entity.Meal; import ssu.eatssu.domain.menu.entity.MealMenu; import ssu.eatssu.domain.menu.entity.Menu; @@ -26,129 +19,135 @@ import ssu.eatssu.global.handler.response.BaseException; import ssu.eatssu.global.handler.response.BaseResponseStatus; +import java.util.Date; +import java.util.List; +import java.util.Optional; + +import static ssu.eatssu.global.handler.response.BaseResponseStatus.NOT_SUPPORT_RESTAURANT; + @Slf4j @Service @RequiredArgsConstructor @Transactional(readOnly = true) public class MealService { - private final MealRepository mealRepository; - private final MealMenuRepository mealMenuRepository; - private final MealRatingService mealRatingService; - private final MenuService menuService; - - public MenusInMealResponse getMenusInMealByMealId(Long mealId) { - Meal meal = mealRepository.findById(mealId) - .orElseThrow(() -> new BaseException(BaseResponseStatus.NOT_FOUND_MEAL)); - - return MenusInMealResponse.from(meal); - } - - public List getMealDetailsByDateAndRestaurantAndTimePart( - Date date, Restaurant restaurant, TimePart timePart) { - - validateMealRestaurant(restaurant); - - List meals = findMealsByDateAndTimePartAndRestaurant(date, timePart, restaurant); - - return meals.stream() - .map(meal -> MealDetailResponse.from(meal, - mealRatingService.getMainRatingAverage(meal.getId()))) - .toList(); - } - - private List findMealsByDateAndTimePartAndRestaurant(Date date, TimePart timePart, - Restaurant restaurant) { - return mealRepository.findAllByDateAndTimePartAndRestaurant(date, timePart, restaurant); - } - - private void validateMealRestaurant(Restaurant restaurant) { - if (RestaurantType.isFixedType(restaurant)) { - throw new BaseException(NOT_SUPPORT_RESTAURANT); - } - } - - @Transactional - public Long createMeal(Date date, Restaurant restaurant, TimePart timePart, - CreateMealRequest request) { - return createMealWithOptionalPrice(date, restaurant, timePart, request.menuNames(), null); - } - - @Transactional - public Long createMealWithPrice(Date date, Restaurant restaurant, TimePart timePart, - MealCreateWithPriceRequest request) { - return createMealWithOptionalPrice(date, restaurant, timePart, request.menuNames(), request.price()); - } - - private Long createMealWithOptionalPrice(Date date, Restaurant restaurant, TimePart timePart, - List menuNames, Integer price) { - - Optional existingMealId = getExistingMealId(date, timePart, restaurant, menuNames); - if (existingMealId.isPresent()) { - return existingMealId.get(); // 이미 존재하는 식단의 ID를 반환 - } - - Meal meal = new Meal(date, timePart, restaurant, price); - Meal savedMeal = mealRepository.save(meal); - addMenusToMeal(meal, restaurant, menuNames); - - return savedMeal.getId(); - } - - private Optional getExistingMealId(Date date, TimePart timePart, Restaurant restaurant, - List menuNames) { - List meals = mealRepository.findAllByDateAndTimePartAndRestaurant(date, timePart, restaurant); - - List sortedRequestMenuNames = menuNames.stream() - .sorted() - .toList(); - - return meals.stream() - .filter(meal -> { - List sortedMenuNames = meal.getMenuNames().stream() - .sorted() - .toList(); - return sortedMenuNames.equals(sortedRequestMenuNames); - }) - .findFirst() - .map(Meal::getId); - } - - private void addMenusToMeal(Meal meal, Restaurant restaurant, List menuNames) { - for (String menuName : menuNames) { - Menu menu = menuService.createOrGetMenu(menuName, restaurant); - createMealMenu(meal, menu); - } - } - - private void createMealMenu(Meal meal, Menu menu) { - MealMenu mealMenu = MealMenu.builder() - .menu(menu) - .meal(meal) - .build(); - - mealMenuRepository.save(mealMenu); - meal.addMealMenu(mealMenu); - } - - @Transactional - public void deleteByMealId(Long mealId) { - Meal meal = mealRepository.findById(mealId) - .orElseThrow(() -> new BaseException(BaseResponseStatus.NOT_FOUND_MEAL)); - - List menus = meal.getMealMenus().stream() - .map(MealMenu::getMenu) - .toList(); - - mealRepository.delete(meal); - mealRepository.flush(); - - deleteUnusedMenus(menus); - } - - private void deleteUnusedMenus(List menus) { - menus.stream() - .filter(menu -> menu.getMealMenus().isEmpty()) - .forEach(menuService::deleteMenu); - } + private final MealRepository mealRepository; + private final MealMenuRepository mealMenuRepository; + private final MealRatingService mealRatingService; + private final MenuService menuService; + + public MenusInMealResponse getMenusInMealByMealId(Long mealId) { + Meal meal = mealRepository.findById(mealId) + .orElseThrow(() -> new BaseException(BaseResponseStatus.NOT_FOUND_MEAL)); + + return MenusInMealResponse.from(meal); + } + + public List getMealDetailsByDateAndRestaurantAndTimePart( + Date date, Restaurant restaurant, TimePart timePart) { + + validateMealRestaurant(restaurant); + + List meals = findMealsByDateAndTimePartAndRestaurant(date, timePart, restaurant); + + return meals.stream() + .map(meal -> MealDetailResponse.from(meal, + mealRatingService.getMainRatingAverage(meal.getId()))) + .toList(); + } + + private List findMealsByDateAndTimePartAndRestaurant(Date date, TimePart timePart, + Restaurant restaurant) { + return mealRepository.findAllByDateAndTimePartAndRestaurant(date, timePart, restaurant); + } + + private void validateMealRestaurant(Restaurant restaurant) { + if (RestaurantType.isFixedType(restaurant)) { + throw new BaseException(NOT_SUPPORT_RESTAURANT); + } + } + + @Transactional + public Long createMeal(Date date, Restaurant restaurant, TimePart timePart, + CreateMealRequest request) { + return createMealWithOptionalPrice(date, restaurant, timePart, request.menuNames(), null); + } + + @Transactional + public Long createMealWithPrice(Date date, Restaurant restaurant, TimePart timePart, + MealCreateWithPriceRequest request) { + return createMealWithOptionalPrice(date, restaurant, timePart, request.menuNames(), request.price()); + } + + private Long createMealWithOptionalPrice(Date date, Restaurant restaurant, TimePart timePart, + List menuNames, Integer price) { + + Optional existingMealId = getExistingMealId(date, timePart, restaurant, menuNames); + if (existingMealId.isPresent()) { + return existingMealId.get(); // 이미 존재하는 식단의 ID를 반환 + } + + Meal meal = new Meal(date, timePart, restaurant, price); + Meal savedMeal = mealRepository.save(meal); + addMenusToMeal(meal, restaurant, menuNames); + + return savedMeal.getId(); + } + + private Optional getExistingMealId(Date date, TimePart timePart, Restaurant restaurant, + List menuNames) { + List meals = mealRepository.findAllByDateAndTimePartAndRestaurant(date, timePart, restaurant); + + List sortedRequestMenuNames = menuNames.stream() + .sorted() + .toList(); + + return meals.stream() + .filter(meal -> { + List sortedMenuNames = meal.getMenuNames().stream() + .sorted() + .toList(); + return sortedMenuNames.equals(sortedRequestMenuNames); + }) + .findFirst() + .map(Meal::getId); + } + + private void addMenusToMeal(Meal meal, Restaurant restaurant, List menuNames) { + for (String menuName : menuNames) { + Menu menu = menuService.createOrGetMenu(menuName, restaurant); + createMealMenu(meal, menu); + } + } + + private void createMealMenu(Meal meal, Menu menu) { + MealMenu mealMenu = MealMenu.builder() + .menu(menu) + .meal(meal) + .build(); + + mealMenuRepository.save(mealMenu); + meal.addMealMenu(mealMenu); + } + + @Transactional + public void deleteByMealId(Long mealId) { + Meal meal = mealRepository.findById(mealId) + .orElseThrow(() -> new BaseException(BaseResponseStatus.NOT_FOUND_MEAL)); + + List menus = meal.getMealMenus().stream() + .map(MealMenu::getMenu) + .toList(); + + mealRepository.delete(meal); + mealRepository.flush(); + + deleteUnusedMenus(menus); + } + + private void deleteUnusedMenus(List menus) { + menus.stream() + .filter(menu -> menu.getMealMenus().isEmpty()) + .forEach(menuService::deleteMenu); + } } diff --git a/src/main/java/ssu/eatssu/domain/menu/service/MenuRatingService.java b/src/main/java/ssu/eatssu/domain/menu/service/MenuRatingService.java index 454b15de..7a0fb173 100644 --- a/src/main/java/ssu/eatssu/domain/menu/service/MenuRatingService.java +++ b/src/main/java/ssu/eatssu/domain/menu/service/MenuRatingService.java @@ -1,17 +1,16 @@ package ssu.eatssu.domain.menu.service; -import org.springframework.stereotype.Service; - import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; import ssu.eatssu.domain.menu.persistence.QuerydslMenuRatingCalculator; @Service @RequiredArgsConstructor public class MenuRatingService { - private final QuerydslMenuRatingCalculator menuRatingCalculator; + private final QuerydslMenuRatingCalculator menuRatingCalculator; - public Double getMainRatingAverage(Long menuId) { - return menuRatingCalculator.getMainRatingAverage(menuId); - } + public Double getMainRatingAverage(Long menuId) { + return menuRatingCalculator.getMainRatingAverage(menuId); + } } diff --git a/src/main/java/ssu/eatssu/domain/menu/service/MenuService.java b/src/main/java/ssu/eatssu/domain/menu/service/MenuService.java index cd8eb7de..8b6a67e2 100644 --- a/src/main/java/ssu/eatssu/domain/menu/service/MenuService.java +++ b/src/main/java/ssu/eatssu/domain/menu/service/MenuService.java @@ -1,15 +1,10 @@ package ssu.eatssu.domain.menu.service; -import static ssu.eatssu.global.handler.response.BaseResponseStatus.*; - -import java.util.List; - -import org.jetbrains.annotations.NotNull; -import org.springframework.stereotype.Service; - import jakarta.transaction.Transactional; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.jetbrains.annotations.NotNull; +import org.springframework.stereotype.Service; import ssu.eatssu.domain.menu.entity.Menu; import ssu.eatssu.domain.menu.entity.MenuCategory; import ssu.eatssu.domain.menu.persistence.MenuCategoryRepository; @@ -22,68 +17,72 @@ import ssu.eatssu.global.handler.response.BaseException; import ssu.eatssu.global.handler.response.BaseResponseStatus; +import java.util.List; + +import static ssu.eatssu.global.handler.response.BaseResponseStatus.NOT_SUPPORT_RESTAURANT; + @Slf4j @Service @RequiredArgsConstructor @Transactional public class MenuService { - private final MenuRepository menuRepository; - private final MenuCategoryRepository menuCategoryRepository; - private final MenuRatingService menuRatingService; - - private static void validateMenuRestaurant(Restaurant restaurant) { - if (RestaurantType.isVariableType(restaurant)) { - throw new BaseException(NOT_SUPPORT_RESTAURANT); - } - } - - public MenuRestaurantResponse getMenusByRestaurant(Restaurant restaurant) { - validateMenuRestaurant(restaurant); - - List categories = findCategoriesByRestaurant(restaurant); - - MenuRestaurantResponse response = MenuRestaurantResponse.init(); - - for (MenuCategory category : categories) { - List menus = getMenuResponsesByRestaurantAndCategory(restaurant, - category); - response.add(CategoryWithMenusResponse.of(category.getName(), menus)); - } - - return response; - } - - private List findCategoriesByRestaurant(Restaurant restaurant) { - return menuCategoryRepository.findAllByRestaurant(restaurant); - } - - @NotNull - private List getMenuResponsesByRestaurantAndCategory(Restaurant restaurant, - MenuCategory category) { - return menuRepository.findAllByRestaurantAndCategory(restaurant, category) - .stream() - .filter(Menu::isContinued) - .map(menu -> MenuResponse.from(menu, - menuRatingService.getMainRatingAverage(menu.getId()))) - .toList(); - } - - @Transactional - public Menu createOrGetMenu(String menuName, Restaurant restaurant) { - return menuRepository.existsByNameAndRestaurant(menuName, restaurant) ? - findMenu(menuName, restaurant) : - menuRepository.save(Menu.createVariable(menuName, restaurant)); - } - - private Menu findMenu(String addMenuName, Restaurant restaurant) { - Menu menu = menuRepository.findByNameAndRestaurant(addMenuName, restaurant) - .orElseThrow(() -> new BaseException(BaseResponseStatus.NOT_FOUND_MENU)); - return menu; - } - - @Transactional - public void deleteMenu(Menu menu) { - menuRepository.delete(menu); - } + private final MenuRepository menuRepository; + private final MenuCategoryRepository menuCategoryRepository; + private final MenuRatingService menuRatingService; + + private static void validateMenuRestaurant(Restaurant restaurant) { + if (RestaurantType.isVariableType(restaurant)) { + throw new BaseException(NOT_SUPPORT_RESTAURANT); + } + } + + public MenuRestaurantResponse getMenusByRestaurant(Restaurant restaurant) { + validateMenuRestaurant(restaurant); + + List categories = findCategoriesByRestaurant(restaurant); + + MenuRestaurantResponse response = MenuRestaurantResponse.init(); + + for (MenuCategory category : categories) { + List menus = getMenuResponsesByRestaurantAndCategory(restaurant, + category); + response.add(CategoryWithMenusResponse.of(category.getName(), menus)); + } + + return response; + } + + private List findCategoriesByRestaurant(Restaurant restaurant) { + return menuCategoryRepository.findAllByRestaurant(restaurant); + } + + @NotNull + private List getMenuResponsesByRestaurantAndCategory(Restaurant restaurant, + MenuCategory category) { + return menuRepository.findAllByRestaurantAndCategory(restaurant, category) + .stream() + .filter(Menu::isContinued) + .map(menu -> MenuResponse.from(menu, + menuRatingService.getMainRatingAverage(menu.getId()))) + .toList(); + } + + @Transactional + public Menu createOrGetMenu(String menuName, Restaurant restaurant) { + return menuRepository.existsByNameAndRestaurant(menuName, restaurant) ? + findMenu(menuName, restaurant) : + menuRepository.save(Menu.createVariable(menuName, restaurant)); + } + + private Menu findMenu(String addMenuName, Restaurant restaurant) { + Menu menu = menuRepository.findByNameAndRestaurant(addMenuName, restaurant) + .orElseThrow(() -> new BaseException(BaseResponseStatus.NOT_FOUND_MENU)); + return menu; + } + + @Transactional + public void deleteMenu(Menu menu) { + menuRepository.delete(menu); + } } \ No newline at end of file diff --git a/src/main/java/ssu/eatssu/domain/menu/service/QuerydslMenuRatingCountProvider.java b/src/main/java/ssu/eatssu/domain/menu/service/QuerydslMenuRatingCountProvider.java index d8c18dd7..78ff0766 100644 --- a/src/main/java/ssu/eatssu/domain/menu/service/QuerydslMenuRatingCountProvider.java +++ b/src/main/java/ssu/eatssu/domain/menu/service/QuerydslMenuRatingCountProvider.java @@ -1,38 +1,36 @@ package ssu.eatssu.domain.menu.service; -import org.springframework.stereotype.Repository; - import com.querydsl.core.types.dsl.BooleanExpression; import com.querydsl.jpa.impl.JPAQueryFactory; - import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Repository; import ssu.eatssu.domain.menu.entity.QMenu; import ssu.eatssu.domain.review.entity.QReview; @Repository @RequiredArgsConstructor public class QuerydslMenuRatingCountProvider { - private final JPAQueryFactory queryFactory; - private final QMenu menu = QMenu.menu; - private final QReview review = QReview.review; + private final JPAQueryFactory queryFactory; + private final QMenu menu = QMenu.menu; + private final QReview review = QReview.review; - public Integer getReviewCountByStar(Long menuId, Integer star) { - return queryFactory - .select(review.count()) - .from(review) - .join(review.menu, menu) - .where( - menuIdEq(menuId), - reviewStarIs(star) - ) - .fetchOne().intValue(); - } + public Integer getReviewCountByStar(Long menuId, Integer star) { + return queryFactory + .select(review.count()) + .from(review) + .join(review.menu, menu) + .where( + menuIdEq(menuId), + reviewStarIs(star) + ) + .fetchOne().intValue(); + } - private BooleanExpression reviewStarIs(Integer star) { - return review.ratings.mainRating.eq(star); - } + private BooleanExpression reviewStarIs(Integer star) { + return review.ratings.mainRating.eq(star); + } - private BooleanExpression menuIdEq(Long menuId) { - return menu.id.eq(menuId); - } + private BooleanExpression menuIdEq(Long menuId) { + return menu.id.eq(menuId); + } } diff --git a/src/main/java/ssu/eatssu/domain/partnership/dto/CreatePartnershipRequest.java b/src/main/java/ssu/eatssu/domain/partnership/dto/CreatePartnershipRequest.java index cacfb46d..b5c061b9 100644 --- a/src/main/java/ssu/eatssu/domain/partnership/dto/CreatePartnershipRequest.java +++ b/src/main/java/ssu/eatssu/domain/partnership/dto/CreatePartnershipRequest.java @@ -1,49 +1,40 @@ package ssu.eatssu.domain.partnership.dto; -import java.time.LocalDate; - import io.swagger.v3.oas.annotations.media.Schema; import lombok.AllArgsConstructor; import lombok.Getter; import ssu.eatssu.domain.partnership.entity.Partnership; +import ssu.eatssu.domain.partnership.entity.PartnershipRestaurant; import ssu.eatssu.domain.partnership.entity.PartnershipType; -import ssu.eatssu.domain.partnership.entity.RestaurantType; + +import java.time.LocalDate; @Schema(title = "제휴 등록") @Getter @AllArgsConstructor public class CreatePartnershipRequest { - @Schema(description = "제휴 가게 이름", example = "먹돼지") - private String storeName; - @Schema(description = "제휴 종류(DRINK, DISCOUNT, SIDE, OTHER)", example = "DISCOUNT") - private PartnershipType partnershipType; - @Schema(description = "제휴 타겟 종류(college or department)", example = "college") - private String targetType; // college or department - @Schema(description = "제휴 타겟", example = "IT대") - private String targetName; - @Schema(description = "제휴 내용", example = "IT대 학생증은 10% 할인") - private String description; - @Schema(description = "제휴 시작 날짜", example = "2025-03-01") - private LocalDate startDate; - @Schema(description = "제휴 종료 날짜", example = "2025-07-30") - private LocalDate endDate; - @Schema(description = "제휴 가게 종류(RESTAURANT, CAFE)", example = "RESTAURANT") - private RestaurantType restaurantType; - @Schema(description = "경도(x축)", example = "126.9566592") - private Double longitude; - @Schema(description = "위도(y축)", example = "37.4949404") - private Double latitude; + @Schema(description = "제휴 가게 식별자", example = "1") + private Long storeId; + @Schema(description = "제휴 종류(DRINK, DISCOUNT, SIDE, OTHER)", example = "DISCOUNT") + private PartnershipType partnershipType; + @Schema(description = "단과대", example = "IT대") + private String college; + @Schema(description = "학과", example = "컴퓨터학부") + private String department; + @Schema(description = "제휴 내용", example = "IT대 학생증은 10% 할인") + private String description; + @Schema(description = "제휴 시작 날짜", example = "2025-03-01") + private LocalDate startDate; + @Schema(description = "제휴 종료 날짜", example = "2025-07-30") + private LocalDate endDate; - public Partnership toPartnershipEntity() { - return Partnership.builder() - .partnershipType(partnershipType) - .storeName(storeName) - .description(description) - .startDate(startDate) - .endDate(endDate) - .restaurantType(restaurantType) - .longitude(longitude) - .latitude(latitude) - .build(); - } + public Partnership toPartnershipEntity(PartnershipRestaurant partnershipRestaurant) { + return Partnership.builder() + .partnershipRestaurant(partnershipRestaurant) + .partnershipType(partnershipType) + .description(description) + .startDate(startDate) + .endDate(endDate) + .build(); + } } diff --git a/src/main/java/ssu/eatssu/domain/partnership/dto/PartnershipDetailResponse.java b/src/main/java/ssu/eatssu/domain/partnership/dto/PartnershipDetailResponse.java index 4e09634a..4febcf51 100644 --- a/src/main/java/ssu/eatssu/domain/partnership/dto/PartnershipDetailResponse.java +++ b/src/main/java/ssu/eatssu/domain/partnership/dto/PartnershipDetailResponse.java @@ -1,54 +1,50 @@ package ssu.eatssu.domain.partnership.dto; -import java.time.LocalDate; -import java.util.List; -import java.util.stream.Collectors; - import lombok.AllArgsConstructor; import lombok.Getter; import ssu.eatssu.domain.partnership.entity.Partnership; +import ssu.eatssu.domain.partnership.entity.PartnershipRestaurant; import ssu.eatssu.domain.partnership.entity.PartnershipType; import ssu.eatssu.domain.partnership.entity.RestaurantType; +import java.time.LocalDate; + @Getter @AllArgsConstructor public class PartnershipDetailResponse { - private Long id; - private PartnershipType partnershipType; - private String storeName; - private String description; - private LocalDate startDate; - private LocalDate endDate; - private RestaurantType restaurantType; - private Double longitude; - private Double latitude; - private List collegeNames; - private List departmentNames; - private int partnershipLikeCount; - private boolean likedByUser; + private Long id; + private PartnershipType partnershipType; + private String storeName; + private String description; + private LocalDate startDate; + private LocalDate endDate; + private RestaurantType restaurantType; + private Double longitude; + private Double latitude; + private String collegeName; + private String departmentName; + private int partnershipLikeCount; + private boolean likedByUser; - public static PartnershipDetailResponse fromEntity(Partnership partnership, boolean likedByUser) { - List collegeNames = partnership.getPartnershipColleges().stream() - .map(pc -> pc.getCollege().getName()) - .collect(Collectors.toList()); - List departmentNames = partnership.getPartnershipDepartments().stream() - .map(pc -> pc.getDepartment().getName()) - .collect(Collectors.toList()); + public static PartnershipDetailResponse fromEntity(PartnershipRestaurant partnershipRestaurant, + Partnership partnership, + boolean likedByUser) { - return new PartnershipDetailResponse( - partnership.getId(), - partnership.getPartnershipType(), - partnership.getStoreName(), - partnership.getDescription(), - partnership.getStartDate(), - partnership.getEndDate(), - partnership.getRestaurantType(), - partnership.getLongitude(), - partnership.getLatitude(), - collegeNames, - departmentNames, - partnership.getLikes().size(), - likedByUser - ); - } + return new PartnershipDetailResponse( + partnership.getId(), + partnership.getPartnershipType(), + partnershipRestaurant.getStoreName(), + partnership.getDescription(), + partnership.getStartDate(), + partnership.getEndDate(), + partnershipRestaurant.getRestaurantType(), + partnershipRestaurant.getLongitude(), + partnershipRestaurant.getLatitude(), + partnership.getPartnershipCollege() == null ? null : partnership.getPartnershipCollege().getName(), + partnership.getPartnershipDepartment() == null ? null : partnership.getPartnershipDepartment() + .getName(), + partnershipRestaurant.getLikes().size(), + likedByUser + ); + } } diff --git a/src/main/java/ssu/eatssu/domain/partnership/dto/PartnershipInfo.java b/src/main/java/ssu/eatssu/domain/partnership/dto/PartnershipInfo.java new file mode 100644 index 00000000..282140ef --- /dev/null +++ b/src/main/java/ssu/eatssu/domain/partnership/dto/PartnershipInfo.java @@ -0,0 +1,41 @@ +package ssu.eatssu.domain.partnership.dto; + +import lombok.Builder; +import lombok.Getter; +import ssu.eatssu.domain.partnership.entity.Partnership; +import ssu.eatssu.domain.partnership.entity.PartnershipRestaurant; +import ssu.eatssu.domain.partnership.entity.PartnershipType; + +import java.time.LocalDate; + +@Builder +@Getter +public class PartnershipInfo { + private Long id; + private PartnershipType partnershipType; + private String collegeName; + private String departmentName; + private Integer likeCount; + private Boolean isLiked; + private String description; + private LocalDate startDate; + private LocalDate endDate; + + public static PartnershipInfo fromEntity(Partnership partnership, + PartnershipRestaurant restaurant, + boolean isLiked) { + return PartnershipInfo.builder() + .id(partnership.getId()) + .partnershipType(partnership.getPartnershipType()) + .description(partnership.getDescription()) + .startDate(partnership.getStartDate()) + .endDate(partnership.getEndDate()) + .collegeName(partnership.getPartnershipCollege() == null ? null : partnership.getPartnershipCollege() + .getName()) + .departmentName(partnership.getPartnershipDepartment() == null ? null : partnership.getPartnershipDepartment() + .getName()) + .likeCount(restaurant.getLikes() != null ? restaurant.getLikes().size() : 0) + .isLiked(isLiked) + .build(); + } +} diff --git a/src/main/java/ssu/eatssu/domain/partnership/dto/PartnershipResponse.java b/src/main/java/ssu/eatssu/domain/partnership/dto/PartnershipResponse.java index af0d82d5..1380c807 100644 --- a/src/main/java/ssu/eatssu/domain/partnership/dto/PartnershipResponse.java +++ b/src/main/java/ssu/eatssu/domain/partnership/dto/PartnershipResponse.java @@ -1,50 +1,41 @@ package ssu.eatssu.domain.partnership.dto; -import java.time.LocalDate; -import java.util.List; -import java.util.stream.Collectors; - import lombok.AllArgsConstructor; +import lombok.Builder; import lombok.Getter; -import ssu.eatssu.domain.partnership.entity.Partnership; -import ssu.eatssu.domain.partnership.entity.PartnershipType; +import ssu.eatssu.domain.partnership.entity.PartnershipRestaurant; import ssu.eatssu.domain.partnership.entity.RestaurantType; +import java.util.List; +import java.util.stream.Collectors; + @Getter +@Builder @AllArgsConstructor public class PartnershipResponse { - private Long id; - private PartnershipType partnershipType; - private String storeName; - private String description; - private LocalDate startDate; - private LocalDate endDate; - private RestaurantType restaurantType; - private Double longitude; - private Double latitude; - private List collegeNames; - private List departmentNames; + private String storeName; + private Double longitude; + private Double latitude; + private RestaurantType restaurantType; + private List partnershipInfos; - public static PartnershipResponse fromEntity(Partnership partnership) { - List collegeNames = partnership.getPartnershipColleges().stream() - .map(pc -> pc.getCollege().getName()) - .collect(Collectors.toList()); - List departmentNames = partnership.getPartnershipDepartments().stream() - .map(pc -> pc.getDepartment().getName()) - .collect(Collectors.toList()); + public static PartnershipResponse fromEntity(PartnershipRestaurant restaurant, Long userId) { + boolean isLiked = restaurant.getLikes().stream() + .anyMatch(like -> like.getUser().getId().equals(userId)); - return new PartnershipResponse( - partnership.getId(), - partnership.getPartnershipType(), - partnership.getStoreName(), - partnership.getDescription(), - partnership.getStartDate(), - partnership.getEndDate(), - partnership.getRestaurantType(), - partnership.getLongitude(), - partnership.getLatitude(), - collegeNames, - departmentNames - ); - } + List infos = restaurant.getPartnerships().stream() + .map(partnership -> PartnershipInfo.fromEntity(partnership, + restaurant, + isLiked)) + .collect(Collectors.toList()); + + return PartnershipResponse.builder() + .storeName(restaurant.getStoreName()) + .longitude(restaurant.getLongitude()) + .latitude(restaurant.getLatitude()) + .restaurantType(restaurant.getRestaurantType()) + .partnershipInfos(infos) + .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 75f037c7..d784217d 100644 --- a/src/main/java/ssu/eatssu/domain/partnership/entity/Partnership.java +++ b/src/main/java/ssu/eatssu/domain/partnership/entity/Partnership.java @@ -1,25 +1,25 @@ package ssu.eatssu.domain.partnership.entity; -import java.time.LocalDate; -import java.util.ArrayList; -import java.util.List; - -import org.hibernate.annotations.Where; - -import jakarta.persistence.CascadeType; 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; import jakarta.persistence.Id; -import jakarta.persistence.OneToMany; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; import lombok.AccessLevel; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; +import org.hibernate.annotations.Where; +import ssu.eatssu.domain.user.department.entity.College; +import ssu.eatssu.domain.user.department.entity.Department; + +import java.time.LocalDate; @Entity @Getter @@ -28,46 +28,40 @@ @AllArgsConstructor @Where(clause = "end_date >= CURRENT_DATE") public class Partnership { - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - @Column(name = "partnership_id") - private Long id; - - @Enumerated(EnumType.STRING) - @Column(name = "partnership_type", nullable = false) - private PartnershipType partnershipType; - - @Column(name = "store_name", nullable = false) - private String storeName; - - @Column(name = "description", nullable = false) - private String description; + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "partnership_id") + private Long id; - @Column(name = "start_date", nullable = false) - private LocalDate startDate; + @Enumerated(EnumType.STRING) + @Column(name = "partnership_type", nullable = false) + private PartnershipType partnershipType; - @Column(name = "end_date", nullable = false) - private LocalDate endDate; + @Column(name = "description", nullable = false) + private String description; - @Enumerated(EnumType.STRING) - @Column(name = "restaurant_type", nullable = false) - private RestaurantType restaurantType; + @Column(name = "start_date", nullable = false) + private LocalDate startDate; - @Column(name = "longitude", nullable = false) - private Double longitude; // 경도 == x축 + @Column(name = "end_date", nullable = false) + private LocalDate endDate; + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "partnership_college_id") + private College partnershipCollege; - @Column(name = "latitude", nullable = false) - private Double latitude; // 위도 == y축 + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "partnership_department_id") + private Department partnershipDepartment; - @Builder.Default - @OneToMany(mappedBy = "partnership", cascade = CascadeType.ALL, orphanRemoval = true) - private List partnershipColleges = new ArrayList<>(); + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "partnership_restaurant_id") + private PartnershipRestaurant partnershipRestaurant; - @Builder.Default - @OneToMany(mappedBy = "partnership", cascade = CascadeType.ALL, orphanRemoval = true) - private List partnershipDepartments = new ArrayList<>(); + public void setPartnershipCollege(College partnershipCollege) { + this.partnershipCollege = partnershipCollege; + } - @Builder.Default - @OneToMany(mappedBy = "partnership", cascade = CascadeType.ALL, orphanRemoval = true) - private List likes = new ArrayList<>(); + public void setPartnershipDepartment(Department partnershipDepartment) { + this.partnershipDepartment = partnershipDepartment; + } } diff --git a/src/main/java/ssu/eatssu/domain/partnership/entity/PartnershipCollege.java b/src/main/java/ssu/eatssu/domain/partnership/entity/PartnershipCollege.java deleted file mode 100644 index eb7de49e..00000000 --- a/src/main/java/ssu/eatssu/domain/partnership/entity/PartnershipCollege.java +++ /dev/null @@ -1,37 +0,0 @@ -package ssu.eatssu.domain.partnership.entity; - -import jakarta.persistence.Column; -import jakarta.persistence.Entity; -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 lombok.AccessLevel; -import lombok.Getter; -import lombok.NoArgsConstructor; -import ssu.eatssu.domain.user.department.entity.College; - -@Entity -@Getter -@NoArgsConstructor(access = AccessLevel.PROTECTED) -public class PartnershipCollege { - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - @Column(name = "partnership_college_id") - private Long id; - - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "partnership_id") - private Partnership partnership; - - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "college_id") - private College college; - - public PartnershipCollege(Partnership partnership, College college) { - this.partnership = partnership; - this.college = college; - } -} diff --git a/src/main/java/ssu/eatssu/domain/partnership/entity/PartnershipDepartment.java b/src/main/java/ssu/eatssu/domain/partnership/entity/PartnershipDepartment.java deleted file mode 100644 index b3bb887b..00000000 --- a/src/main/java/ssu/eatssu/domain/partnership/entity/PartnershipDepartment.java +++ /dev/null @@ -1,37 +0,0 @@ -package ssu.eatssu.domain.partnership.entity; - -import jakarta.persistence.Column; -import jakarta.persistence.Entity; -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 lombok.AccessLevel; -import lombok.Getter; -import lombok.NoArgsConstructor; -import ssu.eatssu.domain.user.department.entity.Department; - -@Entity -@Getter -@NoArgsConstructor(access = AccessLevel.PROTECTED) -public class PartnershipDepartment { - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - @Column(name = "partnership_college_id") - private Long id; - - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "partnership_id") - private Partnership partnership; - - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "department_id") - private Department department; - - public PartnershipDepartment(Partnership partnership, Department department) { - this.partnership = partnership; - this.department = department; - } -} diff --git a/src/main/java/ssu/eatssu/domain/partnership/entity/PartnershipLike.java b/src/main/java/ssu/eatssu/domain/partnership/entity/PartnershipLike.java index 12283630..498ac070 100644 --- a/src/main/java/ssu/eatssu/domain/partnership/entity/PartnershipLike.java +++ b/src/main/java/ssu/eatssu/domain/partnership/entity/PartnershipLike.java @@ -17,21 +17,21 @@ @Getter @NoArgsConstructor(access = AccessLevel.PROTECTED) public class PartnershipLike { - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - @Column(name = "partnership_like_id") - private Long id; + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "partnership_like_id") + private Long id; - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "user_id") - private User user; + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "user_id") + private User user; - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "partnership_id") - private Partnership partnership; + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "partnership_restaurant_id") + private PartnershipRestaurant partnershipRestaurant; - public PartnershipLike(User user, Partnership partnership) { - this.user = user; - this.partnership = partnership; - } + public PartnershipLike(User user, PartnershipRestaurant partnershipRestaurant) { + this.user = user; + this.partnershipRestaurant = partnershipRestaurant; + } } diff --git a/src/main/java/ssu/eatssu/domain/partnership/entity/PartnershipRestaurant.java b/src/main/java/ssu/eatssu/domain/partnership/entity/PartnershipRestaurant.java new file mode 100644 index 00000000..732837e8 --- /dev/null +++ b/src/main/java/ssu/eatssu/domain/partnership/entity/PartnershipRestaurant.java @@ -0,0 +1,45 @@ +package ssu.eatssu.domain.partnership.entity; + +import jakarta.persistence.CascadeType; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.OneToMany; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; +import org.hibernate.annotations.BatchSize; + +import java.util.ArrayList; +import java.util.List; + +@Entity +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class PartnershipRestaurant { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "partnershipRestaurant_id") + private Long id; + @Enumerated(EnumType.STRING) + @Column(name = "restaurant_type", nullable = false) + private RestaurantType restaurantType; + + @Column(name = "longitude", nullable = false) + private Double longitude; // 경도 == x축 + + @Column(name = "latitude", nullable = false) + private Double latitude; // 위도 == y축 + @Column(name = "store_name", nullable = false) + private String storeName; + + @OneToMany(mappedBy = "partnershipRestaurant") + @BatchSize(size = 20) + private List likes = new ArrayList<>(); + @OneToMany(mappedBy = "partnershipRestaurant", cascade = CascadeType.ALL, orphanRemoval = true) + private List partnerships = new ArrayList<>(); +} diff --git a/src/main/java/ssu/eatssu/domain/partnership/entity/PartnershipType.java b/src/main/java/ssu/eatssu/domain/partnership/entity/PartnershipType.java index fdf72878..9d91ab0e 100644 --- a/src/main/java/ssu/eatssu/domain/partnership/entity/PartnershipType.java +++ b/src/main/java/ssu/eatssu/domain/partnership/entity/PartnershipType.java @@ -4,14 +4,14 @@ @Getter public enum PartnershipType { - DRINK("음료"), - DISCOUNT("할인"), - SIDE("사이드"), - OTHER("기타"); + DRINK("음료"), + DISCOUNT("할인"), + SIDE("사이드"), + OTHER("기타"); - private final String type; + private final String type; - PartnershipType(String type) { - this.type = type; - } + PartnershipType(String type) { + this.type = type; + } } diff --git a/src/main/java/ssu/eatssu/domain/partnership/entity/RestaurantType.java b/src/main/java/ssu/eatssu/domain/partnership/entity/RestaurantType.java index 0de32722..d849580c 100644 --- a/src/main/java/ssu/eatssu/domain/partnership/entity/RestaurantType.java +++ b/src/main/java/ssu/eatssu/domain/partnership/entity/RestaurantType.java @@ -1,12 +1,12 @@ package ssu.eatssu.domain.partnership.entity; public enum RestaurantType { - RESTAURANT("음식점"), - CAFE("카페"); + RESTAURANT("음식점"), + CAFE("카페"); - private final String type; + private final String type; - RestaurantType(String type) { - this.type = type; - } + RestaurantType(String type) { + this.type = type; + } } diff --git a/src/main/java/ssu/eatssu/domain/partnership/persistence/PartnershipLikeRepository.java b/src/main/java/ssu/eatssu/domain/partnership/persistence/PartnershipLikeRepository.java index f107da58..4d0b4b0f 100644 --- a/src/main/java/ssu/eatssu/domain/partnership/persistence/PartnershipLikeRepository.java +++ b/src/main/java/ssu/eatssu/domain/partnership/persistence/PartnershipLikeRepository.java @@ -1,16 +1,24 @@ package ssu.eatssu.domain.partnership.persistence; -import java.util.List; -import java.util.Optional; - import org.springframework.data.jpa.repository.JpaRepository; - -import ssu.eatssu.domain.partnership.entity.Partnership; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; import ssu.eatssu.domain.partnership.entity.PartnershipLike; +import ssu.eatssu.domain.partnership.entity.PartnershipRestaurant; import ssu.eatssu.domain.user.entity.User; +import java.util.List; +import java.util.Optional; + public interface PartnershipLikeRepository extends JpaRepository { - Optional findByUserAndPartnership(User user, Partnership partnership); + Optional findByUserAndPartnershipRestaurant(User user, + PartnershipRestaurant partnershipRestaurant); - List findAllByUser(User user); + @Query("SELECT pl FROM PartnershipLike pl " + + "JOIN FETCH pl.partnershipRestaurant pr " + + "JOIN FETCH pr.partnerships p " + + "LEFT JOIN FETCH p.partnershipCollege " + + "LEFT JOIN FETCH p.partnershipDepartment " + + "WHERE pl.user = :user") + List findAllByUserWithDetails(@Param("user") User user); } 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 896eab1d..786059e9 100644 --- a/src/main/java/ssu/eatssu/domain/partnership/persistence/PartnershipRepository.java +++ b/src/main/java/ssu/eatssu/domain/partnership/persistence/PartnershipRepository.java @@ -1,22 +1,19 @@ package ssu.eatssu.domain.partnership.persistence; -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 ssu.eatssu.domain.partnership.entity.Partnership; import ssu.eatssu.domain.user.department.entity.College; import ssu.eatssu.domain.user.department.entity.Department; +import java.util.List; + public interface PartnershipRepository extends JpaRepository { - @Query("SELECT DISTINCT p FROM Partnership p " + - "LEFT JOIN p.partnershipColleges pc " + - "LEFT JOIN pc.college c " + - "LEFT JOIN p.partnershipDepartments pd " + - "LEFT JOIN pd.department d " + - "WHERE (c = :college OR d = :department OR (c IS NOT NULL AND c.name = '총학'))") - List findRelevantPartnerships(@Param("college") College college, - @Param("department") Department department); + @Query("SELECT DISTINCT p FROM Partnership p " + + "LEFT JOIN p.partnershipCollege pc " + + "LEFT JOIN p.partnershipDepartment pd " + + "WHERE (pc = :college OR pd = :department OR (pc IS NOT NULL AND pc.name = '총학'))") + List findRelevantPartnerships(@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 new file mode 100644 index 00000000..5fd32d87 --- /dev/null +++ b/src/main/java/ssu/eatssu/domain/partnership/persistence/PartnershipRestaurantRepository.java @@ -0,0 +1,15 @@ +package ssu.eatssu.domain.partnership.persistence; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import ssu.eatssu.domain.partnership.entity.PartnershipRestaurant; + +import java.util.List; + +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 ") + List findAllWithDetails(); +} diff --git a/src/main/java/ssu/eatssu/domain/partnership/presentation/PartnershipController.java b/src/main/java/ssu/eatssu/domain/partnership/presentation/PartnershipController.java index a3534c2f..0e76475e 100644 --- a/src/main/java/ssu/eatssu/domain/partnership/presentation/PartnershipController.java +++ b/src/main/java/ssu/eatssu/domain/partnership/presentation/PartnershipController.java @@ -1,15 +1,5 @@ package ssu.eatssu.domain.partnership.presentation; -import java.util.List; - -import org.springframework.security.core.annotation.AuthenticationPrincipal; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.media.Content; import io.swagger.v3.oas.annotations.media.Schema; @@ -17,69 +7,64 @@ import io.swagger.v3.oas.annotations.responses.ApiResponses; import io.swagger.v3.oas.annotations.tags.Tag; import lombok.RequiredArgsConstructor; +import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; import ssu.eatssu.domain.auth.security.CustomUserDetails; import ssu.eatssu.domain.partnership.dto.CreatePartnershipRequest; -import ssu.eatssu.domain.partnership.dto.PartnershipDetailResponse; import ssu.eatssu.domain.partnership.dto.PartnershipResponse; import ssu.eatssu.domain.partnership.service.PartnershipService; import ssu.eatssu.global.handler.response.BaseResponse; +import java.util.List; + @RestController @RequestMapping("/partnerships") @RequiredArgsConstructor @Tag(name = "Partnership", description = "제휴 API") public class PartnershipController { - private final PartnershipService partnershipService; + private final PartnershipService partnershipService; - @Operation(summary = "제휴 등록", description = "제휴를 등록하는 API 입니다.") - @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "제휴 등록 성공"), - @ApiResponse(responseCode = "404", description = "존재하지 않는 대학", content = @Content(schema = - @Schema(implementation = BaseResponse.class))), - @ApiResponse(responseCode = "404", description = "존재하지 않는 학과", content = @Content(schema = - @Schema(implementation = BaseResponse.class))), - }) - @PostMapping - public BaseResponse createPartnership(@RequestBody CreatePartnershipRequest request) { - partnershipService.createPartnership(request); - return BaseResponse.success(); - } + @Operation(summary = "제휴 등록", description = "제휴를 등록하는 API 입니다.") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "제휴 등록 성공"), + @ApiResponse(responseCode = "404", description = "존재하지 않는 대학", content = @Content(schema = + @Schema(implementation = BaseResponse.class))), + @ApiResponse(responseCode = "404", description = "존재하지 않는 학과", content = @Content(schema = + @Schema(implementation = BaseResponse.class))), + }) + @PostMapping + public BaseResponse createPartnership(@RequestBody CreatePartnershipRequest request) { + partnershipService.createPartnership(request); + return BaseResponse.success(); + } - @Operation(summary = "전체 제휴 조회", description = "전체 제휴를 조회하는 API 입니다.") - @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "제휴 조회 성공"), - }) - @GetMapping - public BaseResponse> getAllPartnerships() { - return BaseResponse.success(partnershipService.getAllPartnerships()); - } + @Operation(summary = "식당별 전체 제휴 조회", description = "식당별 전체 제휴를 조회하는 API 입니다.") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "제휴 조회 성공"), + }) + @GetMapping + public BaseResponse> getAllPartnerships(@AuthenticationPrincipal CustomUserDetails userDetails) { + return BaseResponse.success(partnershipService.getAllPartnerships(userDetails)); + } - @Operation(summary = "개별 제휴 조회", description = "개별 제휴를 조회하는 API 입니다.") - @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "제휴 조회 성공"), - @ApiResponse(responseCode = "404", description = "존재하지 않는 제휴", content = @Content(schema = - @Schema(implementation = BaseResponse.class))), - @ApiResponse(responseCode = "404", description = "존재하지 않는 유저", content = @Content(schema = @Schema(implementation = BaseResponse.class))), - }) - @GetMapping("/{partnershipId}") - public BaseResponse getPartnership( - @PathVariable Long partnershipId, - @AuthenticationPrincipal CustomUserDetails userDetails) { - return BaseResponse.success(partnershipService.getPartnership(partnershipId, userDetails)); - } - @Operation(summary = "제휴 찜 등록하기/취소하기", description = "제휴 찜 등록하기/취소하기(토글) API 입니다.") - @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "제휴 찜 등록/취소 성공"), - @ApiResponse(responseCode = "404", description = "존재하지 않는 제휴", content = @Content(schema = - @Schema(implementation = BaseResponse.class))), - @ApiResponse(responseCode = "404", description = "존재하지 않는 유저", content = @Content(schema = @Schema(implementation = BaseResponse.class))), - }) - @PostMapping("/{partnershipId}/like") - public BaseResponse togglePartnershipLike( - @PathVariable Long partnershipId, - @AuthenticationPrincipal CustomUserDetails userDetails) { - partnershipService.togglePartnershipLike(partnershipId, userDetails); - return BaseResponse.success(); - } + @Operation(summary = "제휴 찜 등록하기/취소하기", description = "제휴 찜 등록하기/취소하기(토글) API 입니다.") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "제휴 찜 등록/취소 성공"), + @ApiResponse(responseCode = "404", description = "존재하지 않는 제휴", content = @Content(schema = + @Schema(implementation = BaseResponse.class))), + @ApiResponse(responseCode = "404", description = "존재하지 않는 유저", content = @Content(schema = @Schema(implementation = BaseResponse.class))), + }) + @PostMapping("/{partnershipId}/like") + public BaseResponse togglePartnershipLike( + @PathVariable Long partnershipId, + @AuthenticationPrincipal CustomUserDetails userDetails) { + partnershipService.togglePartnershipLike(partnershipId, userDetails); + return BaseResponse.success(); + } } 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 38558e62..6e1bd075 100644 --- a/src/main/java/ssu/eatssu/domain/partnership/service/PartnershipService.java +++ b/src/main/java/ssu/eatssu/domain/partnership/service/PartnershipService.java @@ -1,25 +1,17 @@ package ssu.eatssu.domain.partnership.service; -import static ssu.eatssu.global.handler.response.BaseResponseStatus.*; - -import java.util.List; -import java.util.Optional; -import java.util.stream.Collectors; - +import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; - -import lombok.RequiredArgsConstructor; import ssu.eatssu.domain.auth.security.CustomUserDetails; import ssu.eatssu.domain.partnership.dto.CreatePartnershipRequest; -import ssu.eatssu.domain.partnership.dto.PartnershipDetailResponse; import ssu.eatssu.domain.partnership.dto.PartnershipResponse; import ssu.eatssu.domain.partnership.entity.Partnership; -import ssu.eatssu.domain.partnership.entity.PartnershipCollege; -import ssu.eatssu.domain.partnership.entity.PartnershipDepartment; import ssu.eatssu.domain.partnership.entity.PartnershipLike; +import ssu.eatssu.domain.partnership.entity.PartnershipRestaurant; import ssu.eatssu.domain.partnership.persistence.PartnershipLikeRepository; import ssu.eatssu.domain.partnership.persistence.PartnershipRepository; +import ssu.eatssu.domain.partnership.persistence.PartnershipRestaurantRepository; import ssu.eatssu.domain.user.department.entity.College; import ssu.eatssu.domain.user.department.entity.Department; import ssu.eatssu.domain.user.department.persistence.CollegeRepository; @@ -28,101 +20,109 @@ import ssu.eatssu.domain.user.repository.UserRepository; import ssu.eatssu.global.handler.response.BaseException; +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; + +import static ssu.eatssu.global.handler.response.BaseResponseStatus.MISSING_USER_DEPARTMENT; +import static ssu.eatssu.global.handler.response.BaseResponseStatus.NOT_FOUND_COLLEGE; +import static ssu.eatssu.global.handler.response.BaseResponseStatus.NOT_FOUND_DEPARTMENT; +import static ssu.eatssu.global.handler.response.BaseResponseStatus.NOT_FOUND_PARTNERSHIP; +import static ssu.eatssu.global.handler.response.BaseResponseStatus.NOT_FOUND_PARTNERSHIP_RESTAURANT; +import static ssu.eatssu.global.handler.response.BaseResponseStatus.NOT_FOUND_USER; + @Service @RequiredArgsConstructor public class PartnershipService { - private final PartnershipRepository partnershipRepository; - private final CollegeRepository collegeRepository; - private final DepartmentRepository departmentRepository; - private final UserRepository userRepository; - private final PartnershipLikeRepository partnershipLikeRepository; - - @Transactional - public void createPartnership(CreatePartnershipRequest request) { - Partnership partnership = request.toPartnershipEntity(); - - if ("college".equalsIgnoreCase(request.getTargetType())) { - College college = collegeRepository.findByName(request.getTargetName()) - .orElseThrow(() -> new BaseException(NOT_FOUND_COLLEGE)); - PartnershipCollege partnershipCollege = new PartnershipCollege(partnership, college); - partnership.getPartnershipColleges().add(partnershipCollege); - } else if ("department".equalsIgnoreCase(request.getTargetType())) { - Department department = departmentRepository.findByName(request.getTargetName()) - .orElseThrow(() -> new BaseException(NOT_FOUND_DEPARTMENT)); - PartnershipDepartment partnershipDepartment = new PartnershipDepartment(partnership, department); - partnership.getPartnershipDepartments().add(partnershipDepartment); - } else { - throw new BaseException(INVALID_TARGET_TYPE); - } - partnershipRepository.save(partnership); - } - - public List getAllPartnerships() { - List partnerships = partnershipRepository.findAll(); - return partnerships.stream().map(PartnershipResponse::fromEntity) - .collect(Collectors.toList()); - } - - public PartnershipDetailResponse getPartnership(Long partnershipId, CustomUserDetails userDetails) { - Partnership partnership = partnershipRepository.findById(partnershipId) - .orElseThrow(() -> new BaseException(NOT_FOUND_PARTNERSHIP)); - - boolean likedByUser = false; - if (userDetails != null) { - User user = userRepository.findById(userDetails.getId()) - .orElseThrow(() -> new BaseException(NOT_FOUND_USER)); - likedByUser = partnershipLikeRepository.findByUserAndPartnership(user, partnership).isPresent(); - } - - return PartnershipDetailResponse.fromEntity(partnership, likedByUser); - } - - @Transactional - public void togglePartnershipLike(Long partnershipId, CustomUserDetails userDetails) { - Partnership partnership = partnershipRepository.findById(partnershipId) - .orElseThrow(() -> new BaseException(NOT_FOUND_PARTNERSHIP)); - User user = userRepository.findById(userDetails.getId()) - .orElseThrow(() -> new BaseException(NOT_FOUND_USER)); - - Optional optionalPartnershipLike = partnershipLikeRepository.findByUserAndPartnership(user, - partnership); - if (optionalPartnershipLike.isPresent()) { - PartnershipLike partnershipLike = optionalPartnershipLike.get(); - partnership.getLikes().remove(partnershipLike); - partnershipLikeRepository.delete(partnershipLike); - } else { - PartnershipLike partnershipLike = new PartnershipLike(user, partnership); - partnership.getLikes().add(partnershipLike); - partnershipLikeRepository.save(partnershipLike); - } - } - - public List getUserLikedPartnerships(CustomUserDetails userDetails) { - User user = userRepository.findById(userDetails.getId()) - .orElseThrow(() -> new BaseException(NOT_FOUND_USER)); - - List likes = partnershipLikeRepository.findAllByUser(user); - return likes - .stream() - .map(PartnershipLike::getPartnership) - .map(PartnershipResponse::fromEntity) - .collect(Collectors.toList()); - } - - public List getUserDepartmentPartnerships(CustomUserDetails userDetails) { - User user = userRepository.findById(userDetails.getId()) - .orElseThrow(() -> new BaseException(NOT_FOUND_USER)); - - Department department = user.getDepartment(); - if (department == null) { - throw new BaseException(MISSING_USER_DEPARTMENT); - } - College college = department.getCollege(); - - return partnershipRepository - .findRelevantPartnerships(college, department) - .stream() - .map(PartnershipResponse::fromEntity) - .collect(Collectors.toList()); - } + private final PartnershipRepository partnershipRepository; + private final CollegeRepository collegeRepository; + private final DepartmentRepository departmentRepository; + private final UserRepository userRepository; + private final PartnershipLikeRepository partnershipLikeRepository; + private final PartnershipRestaurantRepository partnerShipRestaurantRepository; + + @Transactional + public void createPartnership(CreatePartnershipRequest request) { + PartnershipRestaurant partnershipRestaurant = partnerShipRestaurantRepository.findById(request.getStoreId()) + .orElseThrow(() -> new BaseException( + NOT_FOUND_PARTNERSHIP_RESTAURANT)); + Partnership partnership = request.toPartnershipEntity(partnershipRestaurant); + + College college = collegeRepository.findByName(request.getCollege()) + .orElseThrow(() -> new BaseException(NOT_FOUND_COLLEGE)); + partnership.setPartnershipCollege(college); + Department department = departmentRepository.findByName(request.getDepartment()) + .orElseThrow(() -> new BaseException(NOT_FOUND_DEPARTMENT)); + partnership.setPartnershipDepartment(department); + partnershipRepository.save(partnership); + } + + public List getAllPartnerships(CustomUserDetails customUserDetails) { + return partnerShipRestaurantRepository.findAllWithDetails().stream() + .map(restaurant -> PartnershipResponse.fromEntity(restaurant, + customUserDetails.getId())) + .collect(Collectors.toList()); + } + + + @Transactional + public void togglePartnershipLike(Long partnershipId, CustomUserDetails userDetails) { + Partnership partnership = partnershipRepository.findById(partnershipId) + .orElseThrow(() -> new BaseException(NOT_FOUND_PARTNERSHIP)); + User user = userRepository.findById(userDetails.getId()) + .orElseThrow(() -> new BaseException(NOT_FOUND_USER)); + PartnershipRestaurant partnershipRestaurant = partnership.getPartnershipRestaurant(); + + Optional optionalPartnershipLike = partnershipLikeRepository.findByUserAndPartnershipRestaurant( + user, + partnershipRestaurant); + if (optionalPartnershipLike.isPresent()) { + PartnershipLike partnershipLike = optionalPartnershipLike.get(); + partnershipRestaurant.getLikes().remove(partnershipLike); + partnershipLikeRepository.delete(partnershipLike); + } else { + PartnershipLike partnershipLike = new PartnershipLike(user, partnershipRestaurant); + partnershipRestaurant.getLikes().add(partnershipLike); + partnershipLikeRepository.save(partnershipLike); + } + } + + public List getUserLikedPartnerships(CustomUserDetails customUserDetails) { + User user = userRepository.findById(customUserDetails.getId()) + .orElseThrow(() -> new BaseException(NOT_FOUND_USER)); + + List likes = partnershipLikeRepository.findAllByUserWithDetails(user); + + return likes.stream() + .flatMap(like -> { + PartnershipRestaurant restaurant = like.getPartnershipRestaurant(); + return restaurant.getPartnerships() + .stream() + .map(partnership -> PartnershipResponse.fromEntity(restaurant, + customUserDetails.getId())); + }).collect(Collectors.toList()); + } + + + public List getUserDepartmentPartnerships(CustomUserDetails customUserDetails) { + User user = userRepository.findById(customUserDetails.getId()) + .orElseThrow(() -> new BaseException(NOT_FOUND_USER)); + + Department department = user.getDepartment(); + if (department == null) { + throw new BaseException(MISSING_USER_DEPARTMENT); + } + College college = department.getCollege(); + + return partnershipRepository + .findRelevantPartnerships(college, department) + .stream() + .map(partnership -> { + PartnershipRestaurant partnershipRestaurant = partnership.getPartnershipRestaurant(); + + return PartnershipResponse.fromEntity(partnershipRestaurant, + customUserDetails.getId()); + }) + .collect(Collectors.toList()); + } } diff --git a/src/main/java/ssu/eatssu/domain/rating/entity/JpaLoadCollectionRatingCalculator.java b/src/main/java/ssu/eatssu/domain/rating/entity/JpaLoadCollectionRatingCalculator.java index e3060aee..11572cd5 100644 --- a/src/main/java/ssu/eatssu/domain/rating/entity/JpaLoadCollectionRatingCalculator.java +++ b/src/main/java/ssu/eatssu/domain/rating/entity/JpaLoadCollectionRatingCalculator.java @@ -11,125 +11,125 @@ */ public class JpaLoadCollectionRatingCalculator implements RatingCalculator { - // 식단에 포함된 메뉴들의 평점 별 리뷰 개수 계산 - public ReviewRatingCount mealRatingCount(Meal meal) { - ReviewRating.resetAll(); - - meal.getMealMenus().forEach(mealMenu -> mealMenu.getMenu().getReviews().calculateReviewRatings()); - - return ReviewRating.toResponse(); - } - - // 각 메뉴의 평점 별 개수 계산 - public ReviewRatingCount menuRatingCount(Menu menu) { - ReviewRating.resetAll(); - - menu.getReviews().calculateReviewRatings(); - - return ReviewRating.toResponse(); - } - - // 식단 평균 평점 세트 계산 - public RatingAverages mealAverageRatings(Meal meal) { - long totalReviewCount = mealTotalReviewCount(meal); - - if (totalReviewCount == 0) - return new RatingAverages(null, null, null); - - return RatingAverages.builder() - .mainRating(averageRating(mealTotalMainRating(meal), totalReviewCount)) - .amountRating(averageRating(mealTotalAmountRating(meal), totalReviewCount)) - .tasteRating(averageRating(mealTotalTasteRating(meal), totalReviewCount)) - .build(); - } - - // 메뉴 평균 평점 세트 계산 - public RatingAverages menuAverageRatings(Menu menu) { - int totalReviewCount = menu.getTotalReviewCount(); - - if (totalReviewCount == 0) { - return new RatingAverages(null, null, null); - } - - return RatingAverages.builder() - .mainRating(averageRating(menuTotalMainRating(menu), totalReviewCount)) - .amountRating(averageRating(menuTotalAmountRating(menu), totalReviewCount)) - .tasteRating(averageRating(menuTotalTasteRating(menu), totalReviewCount)) - .build(); - } - - // 식단 메인 평점 평균 계산 - public Double mealAverageMainRating(Meal meal) { - long totalReviewCount = mealTotalReviewCount(meal); - - if (totalReviewCount == 0) { - return null; - } - - return averageRating(mealTotalMainRating(meal), totalReviewCount); - } - - // 메뉴 메인 평점 평균 계산 - public Double menuAverageMainRating(Menu menu) { - if (menu.getReviews().size() == 0) { - return null; - } - - return averageRating(menuTotalMainRating(menu), menu.getReviews().size()); - } - - // 식단 총 리뷰 개수 - public long mealTotalReviewCount(Meal meal) { - return meal.getMealMenus().stream() - .map(MealMenu::getMenu) - .mapToLong(menu -> menu.getReviews().size()).sum(); - } - - // 평균 평점 계산 - public Double averageRating(Integer totalRating, long totalReviewCount) { - if (totalRating == null || totalReviewCount == 0) { - return null; - } - return totalRating / (double)totalReviewCount; - } - - // 식단 메인 평점 총합 - public Integer mealTotalMainRating(Meal meal) { - return meal.getMealMenus().stream() - .map(MealMenu::getMenu) - .map(menu -> menu.getReviews().getTotalMainRating()) - .reduce(null, this::sum); - } - - // 식단 양 평점 총합 - public Integer mealTotalAmountRating(Meal meal) { - return meal.getMealMenus().stream() - .map(MealMenu::getMenu) - .map(menu -> menu.getReviews().getTotalAmountRating()) - .reduce(null, this::sum); - } - - // 식단 맛 평점 총합 - public Integer mealTotalTasteRating(Meal meal) { - return meal.getMealMenus().stream() - .map(MealMenu::getMenu) - .map(menu -> menu.getReviews().getTotalTasteRating()) - .reduce(null, this::sum); - } - - // 메뉴 메인 평점 총합 - public Integer menuTotalMainRating(Menu menu) { - return menu.getReviews().getTotalMainRating(); - } - - // 메뉴 양 평점 총합 - public Integer menuTotalAmountRating(Menu menu) { - return menu.getReviews().getTotalAmountRating(); - } - - // 메뉴 맛 평점 총합 - public Integer menuTotalTasteRating(Menu menu) { - return menu.getReviews().getTotalTasteRating(); - } + // 식단에 포함된 메뉴들의 평점 별 리뷰 개수 계산 + public ReviewRatingCount mealRatingCount(Meal meal) { + ReviewRating.resetAll(); + + meal.getMealMenus().forEach(mealMenu -> mealMenu.getMenu().getReviews().calculateReviewRatings()); + + return ReviewRating.toResponse(); + } + + // 각 메뉴의 평점 별 개수 계산 + public ReviewRatingCount menuRatingCount(Menu menu) { + ReviewRating.resetAll(); + + menu.getReviews().calculateReviewRatings(); + + return ReviewRating.toResponse(); + } + + // 식단 평균 평점 세트 계산 + public RatingAverages mealAverageRatings(Meal meal) { + long totalReviewCount = mealTotalReviewCount(meal); + + if (totalReviewCount == 0) + return new RatingAverages(null, null, null); + + return RatingAverages.builder() + .mainRating(averageRating(mealTotalMainRating(meal), totalReviewCount)) + .amountRating(averageRating(mealTotalAmountRating(meal), totalReviewCount)) + .tasteRating(averageRating(mealTotalTasteRating(meal), totalReviewCount)) + .build(); + } + + // 메뉴 평균 평점 세트 계산 + public RatingAverages menuAverageRatings(Menu menu) { + int totalReviewCount = menu.getTotalReviewCount(); + + if (totalReviewCount == 0) { + return new RatingAverages(null, null, null); + } + + return RatingAverages.builder() + .mainRating(averageRating(menuTotalMainRating(menu), totalReviewCount)) + .amountRating(averageRating(menuTotalAmountRating(menu), totalReviewCount)) + .tasteRating(averageRating(menuTotalTasteRating(menu), totalReviewCount)) + .build(); + } + + // 식단 메인 평점 평균 계산 + public Double mealAverageMainRating(Meal meal) { + long totalReviewCount = mealTotalReviewCount(meal); + + if (totalReviewCount == 0) { + return null; + } + + return averageRating(mealTotalMainRating(meal), totalReviewCount); + } + + // 메뉴 메인 평점 평균 계산 + public Double menuAverageMainRating(Menu menu) { + if (menu.getReviews().size() == 0) { + return null; + } + + return averageRating(menuTotalMainRating(menu), menu.getReviews().size()); + } + + // 식단 총 리뷰 개수 + public long mealTotalReviewCount(Meal meal) { + return meal.getMealMenus().stream() + .map(MealMenu::getMenu) + .mapToLong(menu -> menu.getReviews().size()).sum(); + } + + // 평균 평점 계산 + public Double averageRating(Integer totalRating, long totalReviewCount) { + if (totalRating == null || totalReviewCount == 0) { + return null; + } + return totalRating / (double) totalReviewCount; + } + + // 식단 메인 평점 총합 + public Integer mealTotalMainRating(Meal meal) { + return meal.getMealMenus().stream() + .map(MealMenu::getMenu) + .map(menu -> menu.getReviews().getTotalMainRating()) + .reduce(null, this::sum); + } + + // 식단 양 평점 총합 + public Integer mealTotalAmountRating(Meal meal) { + return meal.getMealMenus().stream() + .map(MealMenu::getMenu) + .map(menu -> menu.getReviews().getTotalAmountRating()) + .reduce(null, this::sum); + } + + // 식단 맛 평점 총합 + public Integer mealTotalTasteRating(Meal meal) { + return meal.getMealMenus().stream() + .map(MealMenu::getMenu) + .map(menu -> menu.getReviews().getTotalTasteRating()) + .reduce(null, this::sum); + } + + // 메뉴 메인 평점 총합 + public Integer menuTotalMainRating(Menu menu) { + return menu.getReviews().getTotalMainRating(); + } + + // 메뉴 양 평점 총합 + public Integer menuTotalAmountRating(Menu menu) { + return menu.getReviews().getTotalAmountRating(); + } + + // 메뉴 맛 평점 총합 + public Integer menuTotalTasteRating(Menu menu) { + return menu.getReviews().getTotalTasteRating(); + } } \ No newline at end of file diff --git a/src/main/java/ssu/eatssu/domain/rating/entity/JpaProjectionRatingCalculator.java b/src/main/java/ssu/eatssu/domain/rating/entity/JpaProjectionRatingCalculator.java index 85f5056c..c5fadaac 100644 --- a/src/main/java/ssu/eatssu/domain/rating/entity/JpaProjectionRatingCalculator.java +++ b/src/main/java/ssu/eatssu/domain/rating/entity/JpaProjectionRatingCalculator.java @@ -1,7 +1,5 @@ package ssu.eatssu.domain.rating.entity; -import java.util.Collection; - import lombok.RequiredArgsConstructor; import ssu.eatssu.domain.menu.entity.Meal; import ssu.eatssu.domain.menu.entity.Menu; @@ -10,6 +8,8 @@ import ssu.eatssu.domain.review.dto.ReviewRatingCount; import ssu.eatssu.domain.review.repository.ReviewRepository; +import java.util.Collection; + /** * Jpa projection 을 이용하여 구현한 클래스입니다. * 더미 데이터 @@ -20,78 +20,78 @@ @RequiredArgsConstructor public class JpaProjectionRatingCalculator implements RatingCalculator { - private final ReviewRepository reviewRepository; + private final ReviewRepository reviewRepository; - @Override - public ReviewRatingCount mealRatingCount(Meal meal) { - ReviewRating.resetAll(); + @Override + public ReviewRatingCount mealRatingCount(Meal meal) { + ReviewRating.resetAll(); - Collection mealRatings = reviewRepository.findByMenu_MealMenus_Meal(meal, RatingsDto.class); - mealRatings.forEach(ratings -> ReviewRating.fromValue(ratings.getMainRating()).incrementCount()); + Collection mealRatings = reviewRepository.findByMenu_MealMenus_Meal(meal, RatingsDto.class); + mealRatings.forEach(ratings -> ReviewRating.fromValue(ratings.getMainRating()).incrementCount()); - return ReviewRating.toResponse(); - } + return ReviewRating.toResponse(); + } - @Override - public ReviewRatingCount menuRatingCount(Menu menu) { - return null; - } + @Override + public ReviewRatingCount menuRatingCount(Menu menu) { + return null; + } - @Override - public RatingAverages mealAverageRatings(Meal meal) { - long totalReviewCount = mealTotalReviewCount(meal); + @Override + public RatingAverages mealAverageRatings(Meal meal) { + long totalReviewCount = mealTotalReviewCount(meal); - if (totalReviewCount == 0) - return new RatingAverages(null, null, null); + if (totalReviewCount == 0) + return new RatingAverages(null, null, null); - Collection mealRatings = reviewRepository.findByMenu_MealMenus_Meal(meal, RatingsDto.class); + Collection mealRatings = reviewRepository.findByMenu_MealMenus_Meal(meal, RatingsDto.class); - Integer totalMainRating = mealRatings.stream().mapToInt(RatingsDto::getMainRating).sum(); - Integer totalTasteRating = mealRatings.stream().mapToInt(RatingsDto::getTasteRating).sum(); - Integer totalAmountRating = mealRatings.stream().mapToInt(RatingsDto::getAmountRating).sum(); + Integer totalMainRating = mealRatings.stream().mapToInt(RatingsDto::getMainRating).sum(); + Integer totalTasteRating = mealRatings.stream().mapToInt(RatingsDto::getTasteRating).sum(); + Integer totalAmountRating = mealRatings.stream().mapToInt(RatingsDto::getAmountRating).sum(); - return RatingAverages.builder() - .mainRating(averageRating(totalMainRating, totalReviewCount)) - .tasteRating(averageRating(totalTasteRating, totalReviewCount)) - .amountRating(averageRating(totalAmountRating, totalReviewCount)) - .build(); - } + return RatingAverages.builder() + .mainRating(averageRating(totalMainRating, totalReviewCount)) + .tasteRating(averageRating(totalTasteRating, totalReviewCount)) + .amountRating(averageRating(totalAmountRating, totalReviewCount)) + .build(); + } - @Override - public RatingAverages menuAverageRatings(Menu menu) { - return null; - } + @Override + public RatingAverages menuAverageRatings(Menu menu) { + return null; + } - @Override - public Double mealAverageMainRating(Meal meal) { - long totalReviewCount = mealTotalReviewCount(meal); + @Override + public Double mealAverageMainRating(Meal meal) { + long totalReviewCount = mealTotalReviewCount(meal); - if (totalReviewCount == 0) { - return null; - } + if (totalReviewCount == 0) { + return null; + } - return averageRating(mealTotalMainRating(meal), totalReviewCount); - } + return averageRating(mealTotalMainRating(meal), totalReviewCount); + } - @Override - public Double menuAverageMainRating(Menu menu) { - return null; - } + @Override + public Double menuAverageMainRating(Menu menu) { + return null; + } - @Override - public long mealTotalReviewCount(Meal meal) { - return reviewRepository.countByMenu_MealMenus_Meal(meal); - } + @Override + public long mealTotalReviewCount(Meal meal) { + return reviewRepository.countByMenu_MealMenus_Meal(meal); + } - public Double averageRating(Integer totalRating, long totalReviewCount) { - if (totalRating == null || totalReviewCount == 0) { - return null; - } - return totalRating / (double)totalReviewCount; - } + public Double averageRating(Integer totalRating, long totalReviewCount) { + if (totalRating == null || totalReviewCount == 0) { + return null; + } + return totalRating / (double) totalReviewCount; + } - public Integer mealTotalMainRating(Meal meal) { - return null; - } + public Integer mealTotalMainRating(Meal meal) { + return null; + } } diff --git a/src/main/java/ssu/eatssu/domain/rating/entity/RatingCalculator.java b/src/main/java/ssu/eatssu/domain/rating/entity/RatingCalculator.java index e2ed40e7..a7b66327 100644 --- a/src/main/java/ssu/eatssu/domain/rating/entity/RatingCalculator.java +++ b/src/main/java/ssu/eatssu/domain/rating/entity/RatingCalculator.java @@ -7,37 +7,37 @@ public interface RatingCalculator { - // 식단에 포함된 메뉴들의 평점 별 리뷰 개수 계산 - ReviewRatingCount mealRatingCount(Meal meal); - - // 각 메뉴의 평점 별 개수 계산 - ReviewRatingCount menuRatingCount(Menu menu); - - // 식단 평균 평점 세트 계산 - RatingAverages mealAverageRatings(Meal meal); - - // 메뉴 평균 평점 세트 계산 - RatingAverages menuAverageRatings(Menu menu); - - // 식단 메인 평점 평균 계산 - Double mealAverageMainRating(Meal meal); - - // 메뉴 메인 평점 평균 계산 - Double menuAverageMainRating(Menu menu); - - // 식단 총 리뷰 개수 - long mealTotalReviewCount(Meal meal); - - /** - * 리뷰 평접 총합 계산용 - * nullable 의 합 계산을 위한 메소드입니다. - */ - default Integer sum(Integer a, Integer b) { - if (a == null) - return b; - if (b == null) - return a; - return a + b; - } + // 식단에 포함된 메뉴들의 평점 별 리뷰 개수 계산 + ReviewRatingCount mealRatingCount(Meal meal); + + // 각 메뉴의 평점 별 개수 계산 + ReviewRatingCount menuRatingCount(Menu menu); + + // 식단 평균 평점 세트 계산 + RatingAverages mealAverageRatings(Meal meal); + + // 메뉴 평균 평점 세트 계산 + RatingAverages menuAverageRatings(Menu menu); + + // 식단 메인 평점 평균 계산 + Double mealAverageMainRating(Meal meal); + + // 메뉴 메인 평점 평균 계산 + Double menuAverageMainRating(Menu menu); + + // 식단 총 리뷰 개수 + long mealTotalReviewCount(Meal meal); + + /** + * 리뷰 평접 총합 계산용 + * nullable 의 합 계산을 위한 메소드입니다. + */ + default Integer sum(Integer a, Integer b) { + if (a == null) + return b; + if (b == null) + return a; + return a + b; + } } diff --git a/src/main/java/ssu/eatssu/domain/rating/entity/RatingCountMap.java b/src/main/java/ssu/eatssu/domain/rating/entity/RatingCountMap.java index 06fe244e..744ac1cf 100644 --- a/src/main/java/ssu/eatssu/domain/rating/entity/RatingCountMap.java +++ b/src/main/java/ssu/eatssu/domain/rating/entity/RatingCountMap.java @@ -5,31 +5,31 @@ public class RatingCountMap { - private Map ratingCountMap; - - public RatingCountMap() { - this.ratingCountMap = new HashMap<>(); - this.init(); - - } - - private void init() { - this.ratingCountMap.put(1, 0); - this.ratingCountMap.put(2, 0); - this.ratingCountMap.put(3, 0); - this.ratingCountMap.put(4, 0); - this.ratingCountMap.put(5, 0); - } - - public void setRatingCount(Integer rating, Integer count) { - this.ratingCountMap.put(rating, count); - } - - public void merge(RatingCountMap map) { - for (Map.Entry entry : map.ratingCountMap.entrySet()) { - Integer currentCount = this.ratingCountMap.getOrDefault(entry.getKey(), 0); - Integer newCount = entry.getValue(); - this.ratingCountMap.put(entry.getKey(), currentCount + newCount); - } - } + private final Map ratingCountMap; + + public RatingCountMap() { + this.ratingCountMap = new HashMap<>(); + this.init(); + + } + + private void init() { + this.ratingCountMap.put(1, 0); + this.ratingCountMap.put(2, 0); + this.ratingCountMap.put(3, 0); + this.ratingCountMap.put(4, 0); + this.ratingCountMap.put(5, 0); + } + + public void setRatingCount(Integer rating, Integer count) { + this.ratingCountMap.put(rating, count); + } + + public void merge(RatingCountMap map) { + for (Map.Entry entry : map.ratingCountMap.entrySet()) { + Integer currentCount = this.ratingCountMap.getOrDefault(entry.getKey(), 0); + Integer newCount = entry.getValue(); + this.ratingCountMap.put(entry.getKey(), currentCount + newCount); + } + } } diff --git a/src/main/java/ssu/eatssu/domain/rating/entity/Ratings.java b/src/main/java/ssu/eatssu/domain/rating/entity/Ratings.java index 47e4282f..99bd8253 100644 --- a/src/main/java/ssu/eatssu/domain/rating/entity/Ratings.java +++ b/src/main/java/ssu/eatssu/domain/rating/entity/Ratings.java @@ -10,22 +10,22 @@ @Embeddable public class Ratings { - private Integer mainRating; + private Integer mainRating; - private Integer amountRating; + private Integer amountRating; - private Integer tasteRating; + private Integer tasteRating; - private Ratings(Integer mainRating, Integer amountRating, Integer tasteRating) { - Assert.isTrue(mainRating >= 0 && mainRating <= 5, "mainRating must be between 0 and 5"); - Assert.isTrue(amountRating >= 0 && amountRating <= 5, "amountRating must be between 0 and 5"); - Assert.isTrue(tasteRating >= 0 && tasteRating <= 5, "tasteRating must be between 0 and 5"); - this.mainRating = mainRating; - this.amountRating = amountRating; - this.tasteRating = tasteRating; - } + private Ratings(Integer mainRating, Integer amountRating, Integer tasteRating) { + Assert.isTrue(mainRating >= 0 && mainRating <= 5, "mainRating must be between 0 and 5"); + Assert.isTrue(amountRating >= 0 && amountRating <= 5, "amountRating must be between 0 and 5"); + Assert.isTrue(tasteRating >= 0 && tasteRating <= 5, "tasteRating must be between 0 and 5"); + this.mainRating = mainRating; + this.amountRating = amountRating; + this.tasteRating = tasteRating; + } - public static Ratings of(Integer mainRating, Integer amountRating, Integer tasteRating) { - return new Ratings(mainRating, amountRating, tasteRating); - } + public static Ratings of(Integer mainRating, Integer amountRating, Integer tasteRating) { + return new Ratings(mainRating, amountRating, tasteRating); + } } diff --git a/src/main/java/ssu/eatssu/domain/rating/entity/ReviewRating.java b/src/main/java/ssu/eatssu/domain/rating/entity/ReviewRating.java index acc013f0..611e0074 100644 --- a/src/main/java/ssu/eatssu/domain/rating/entity/ReviewRating.java +++ b/src/main/java/ssu/eatssu/domain/rating/entity/ReviewRating.java @@ -3,54 +3,54 @@ import ssu.eatssu.domain.review.dto.ReviewRatingCount; public enum ReviewRating { - RATING_ONE(1), - RATING_TWO(2), - RATING_THREE(3), - RATING_FOUR(4), - RATING_FIVE(5); - - private final int value; - private int count; - - ReviewRating(int value) { - this.value = value; - this.count = 0; - } - - public static void resetAll() { - for (ReviewRating RATING : ReviewRating.values()) { - RATING.resetCount(); - } - } - - public static ReviewRating fromValue(int value) { - for (ReviewRating RATING : ReviewRating.values()) { - if (RATING.value == value) { - return RATING; - } - } - throw new IllegalArgumentException("Invalid ratings value: " + value); - } - - public static ReviewRatingCount toResponse() { - return new ReviewRatingCount( - RATING_ONE.getCount(), - RATING_TWO.getCount(), - RATING_THREE.getCount(), - RATING_FOUR.getCount(), - RATING_FIVE.getCount() - ); - } - - public void incrementCount() { - this.count++; - } - - public int getCount() { - return count; - } - - private void resetCount() { - this.count = 0; - } + RATING_ONE(1), + RATING_TWO(2), + RATING_THREE(3), + RATING_FOUR(4), + RATING_FIVE(5); + + private final int value; + private int count; + + ReviewRating(int value) { + this.value = value; + this.count = 0; + } + + public static void resetAll() { + for (ReviewRating RATING : ReviewRating.values()) { + RATING.resetCount(); + } + } + + public static ReviewRating fromValue(int value) { + for (ReviewRating RATING : ReviewRating.values()) { + if (RATING.value == value) { + return RATING; + } + } + throw new IllegalArgumentException("Invalid ratings value: " + value); + } + + public static ReviewRatingCount toResponse() { + return new ReviewRatingCount( + RATING_ONE.getCount(), + RATING_TWO.getCount(), + RATING_THREE.getCount(), + RATING_FOUR.getCount(), + RATING_FIVE.getCount() + ); + } + + public void incrementCount() { + this.count++; + } + + public int getCount() { + return count; + } + + private void resetCount() { + this.count = 0; + } } 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 ee168a77..6ddd6800 100644 --- a/src/main/java/ssu/eatssu/domain/report/dto/ReportCreateRequest.java +++ b/src/main/java/ssu/eatssu/domain/report/dto/ReportCreateRequest.java @@ -6,13 +6,13 @@ @Schema(title = "리뷰 신고하기") public record ReportCreateRequest( - @Schema(description = "신고할 리뷰 id", example = "4") - Long reviewId, + @Schema(description = "신고할 리뷰 id", example = "4") + Long reviewId, - @Schema(description = "신고 타입", example = "BAD_WORD") - ReportType reportType, + @Schema(description = "신고 타입", example = "BAD_WORD") + ReportType reportType, - @Schema(description = "신고 내용", example = "음란성, 욕설 등 부적절한 내용") - String content) { + @Schema(description = "신고 내용", example = "음란성, 욕설 등 부적절한 내용") + String content) { } diff --git a/src/main/java/ssu/eatssu/domain/report/dto/ReportTypeInformation.java b/src/main/java/ssu/eatssu/domain/report/dto/ReportTypeInformation.java index 1bdae80f..db545667 100644 --- a/src/main/java/ssu/eatssu/domain/report/dto/ReportTypeInformation.java +++ b/src/main/java/ssu/eatssu/domain/report/dto/ReportTypeInformation.java @@ -6,14 +6,14 @@ @Getter @Schema(title = "리뷰 신고 사유") public class ReportTypeInformation { - @Schema(description = "신고 사유 타입") - private String type; + @Schema(description = "신고 사유 타입") + private final String type; - @Schema(description = "신고 사유 설명") - private String description; + @Schema(description = "신고 사유 설명") + private final String description; - public ReportTypeInformation(String type, String description) { - this.type = type; - this.description = description; - } + public ReportTypeInformation(String type, String description) { + this.type = type; + this.description = description; + } } diff --git a/src/main/java/ssu/eatssu/domain/report/dto/ReportTypeList.java b/src/main/java/ssu/eatssu/domain/report/dto/ReportTypeList.java index e0c2eb18..b2967172 100644 --- a/src/main/java/ssu/eatssu/domain/report/dto/ReportTypeList.java +++ b/src/main/java/ssu/eatssu/domain/report/dto/ReportTypeList.java @@ -1,25 +1,25 @@ package ssu.eatssu.domain.report.dto; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - import io.swagger.v3.oas.annotations.media.Schema; import lombok.Getter; import ssu.eatssu.domain.report.entity.ReportType; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + @Getter @Schema(title = "리뷰 신고 사유 목록 Response") public class ReportTypeList { - @Schema(description = "리뷰 신고 사유 목록") - private final List response = new ArrayList<>(); + @Schema(description = "리뷰 신고 사유 목록") + private final List response = new ArrayList<>(); - public static ReportTypeList get() { - ReportTypeList reportTypeList = new ReportTypeList(); - Arrays.stream(ReportType.values()) - .forEach(reportType -> reportTypeList.response.add( - new ReportTypeInformation(reportType.name(), reportType.getDescription()))); - return reportTypeList; - } + public static ReportTypeList get() { + ReportTypeList reportTypeList = new ReportTypeList(); + Arrays.stream(ReportType.values()) + .forEach(reportType -> reportTypeList.response.add( + new ReportTypeInformation(reportType.name(), reportType.getDescription()))); + return reportTypeList; + } } diff --git a/src/main/java/ssu/eatssu/domain/report/entity/ReportStatus.java b/src/main/java/ssu/eatssu/domain/report/entity/ReportStatus.java index 67b8af13..1d7a746e 100644 --- a/src/main/java/ssu/eatssu/domain/report/entity/ReportStatus.java +++ b/src/main/java/ssu/eatssu/domain/report/entity/ReportStatus.java @@ -1,30 +1,29 @@ package ssu.eatssu.domain.report.entity; -import java.util.Arrays; - import com.fasterxml.jackson.annotation.JsonCreator; - import lombok.Getter; +import java.util.Arrays; + @Getter public enum ReportStatus { - PENDING("대기 중"), - IN_PROGRESS("진행 중"), - RESOLVED("해결됨"), - REJECTED("거절됨"), - FALSE_REPORT("거짓 신고"); + PENDING("대기 중"), + IN_PROGRESS("진행 중"), + RESOLVED("해결됨"), + REJECTED("거절됨"), + FALSE_REPORT("거짓 신고"); - private final String description; + private final String description; - ReportStatus(final String description) { - this.description = description; - } + ReportStatus(final String description) { + this.description = description; + } - @JsonCreator - public static ReportStatus from(final String description) { - return Arrays.stream(ReportStatus.values()) - .filter(v -> v.getDescription().equals(description)) - .findAny() - .orElseThrow(IllegalArgumentException::new); - } + @JsonCreator + public static ReportStatus from(final String description) { + return Arrays.stream(ReportStatus.values()) + .filter(v -> v.getDescription().equals(description)) + .findAny() + .orElseThrow(IllegalArgumentException::new); + } } diff --git a/src/main/java/ssu/eatssu/domain/report/entity/ReportType.java b/src/main/java/ssu/eatssu/domain/report/entity/ReportType.java index f029c7bf..7b6e3c07 100644 --- a/src/main/java/ssu/eatssu/domain/report/entity/ReportType.java +++ b/src/main/java/ssu/eatssu/domain/report/entity/ReportType.java @@ -1,28 +1,27 @@ package ssu.eatssu.domain.report.entity; -import java.util.Locale; - import com.fasterxml.jackson.annotation.JsonCreator; - import lombok.Getter; +import java.util.Locale; + @Getter public enum ReportType { - NO_ASSOCIATE_CONTENT("메뉴와 관련 없는 내용"), - IMPROPER_CONTENT("음란성, 욕설 등 부적절한 내용"), - IMPROPER_ADVERTISEMENT("부적절한 홍보 및 광고"), - COPY("리뷰 취지에 맞지 않는 행동(복사글 등)"), - COPYRIGHT("저작권 도용 의심(사진 등)"), - EXTRA("기타 (하단 내용 작성)"); + NO_ASSOCIATE_CONTENT("메뉴와 관련 없는 내용"), + IMPROPER_CONTENT("음란성, 욕설 등 부적절한 내용"), + IMPROPER_ADVERTISEMENT("부적절한 홍보 및 광고"), + COPY("리뷰 취지에 맞지 않는 행동(복사글 등)"), + COPYRIGHT("저작권 도용 의심(사진 등)"), + EXTRA("기타 (하단 내용 작성)"); - private final String description; + private final String description; - ReportType(String description) { - this.description = description; - } + ReportType(String description) { + this.description = description; + } - @JsonCreator - public static ReportType from(final String name) { - return ReportType.valueOf(name.toUpperCase(Locale.ROOT)); - } + @JsonCreator + public static ReportType from(final String name) { + return ReportType.valueOf(name.toUpperCase(Locale.ROOT)); + } } diff --git a/src/main/java/ssu/eatssu/domain/report/presentation/ReportController.java b/src/main/java/ssu/eatssu/domain/report/presentation/ReportController.java index 17694962..ccec0ac1 100644 --- a/src/main/java/ssu/eatssu/domain/report/presentation/ReportController.java +++ b/src/main/java/ssu/eatssu/domain/report/presentation/ReportController.java @@ -1,12 +1,5 @@ package ssu.eatssu.domain.report.presentation; -import org.springframework.security.core.annotation.AuthenticationPrincipal; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.media.Content; import io.swagger.v3.oas.annotations.media.Schema; @@ -14,6 +7,12 @@ import io.swagger.v3.oas.annotations.responses.ApiResponses; import io.swagger.v3.oas.annotations.tags.Tag; import lombok.RequiredArgsConstructor; +import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; import ssu.eatssu.domain.auth.security.CustomUserDetails; import ssu.eatssu.domain.report.dto.ReportCreateRequest; import ssu.eatssu.domain.report.dto.ReportTypeList; @@ -30,31 +29,31 @@ @Tag(name = "Report", description = "신고 API") public class ReportController { - private final ReportService reportService; - private final SlackService slackService; + private final ReportService reportService; + private final SlackService slackService; - @Operation(summary = "리뷰 신고 사유 종류 조회", description = "리뷰 신고 사유 종류를 조회하는 API 입니다.") - @GetMapping("/types") - public BaseResponse getReportType() { - ReportTypeList reportTypeList = reportService.getReportType(); - return BaseResponse.success(reportTypeList); - } + @Operation(summary = "리뷰 신고 사유 종류 조회", description = "리뷰 신고 사유 종류를 조회하는 API 입니다.") + @GetMapping("/types") + public BaseResponse getReportType() { + ReportTypeList reportTypeList = reportService.getReportType(); + return BaseResponse.success(reportTypeList); + } - /** - * 리뷰 신고 - */ - @Operation(summary = "리뷰 신고하기", description = "리뷰를 신고하는 API 입니다.") - @PostMapping("") - @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "리뷰 신고 성공", content = @Content(schema = @Schema(implementation = BaseResponse.class))), - @ApiResponse(responseCode = "404", description = "존재하지 않는 유저", content = @Content(schema = @Schema(implementation = BaseResponse.class))) - }) - public BaseResponse reportReview(@RequestBody ReportCreateRequest createReportRequest, - @AuthenticationPrincipal CustomUserDetails customUserDetails) { - Report report = reportService.reportReview(customUserDetails, createReportRequest); - slackService.sendSlackMessage(SlackMessageFormat.sendReport(report), - SlackChannel.REPORT_CHANNEL); - return BaseResponse.success(); - } + /** + * 리뷰 신고 + */ + @Operation(summary = "리뷰 신고하기", description = "리뷰를 신고하는 API 입니다.") + @PostMapping("") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "리뷰 신고 성공", content = @Content(schema = @Schema(implementation = BaseResponse.class))), + @ApiResponse(responseCode = "404", description = "존재하지 않는 유저", content = @Content(schema = @Schema(implementation = BaseResponse.class))) + }) + public BaseResponse reportReview(@RequestBody ReportCreateRequest createReportRequest, + @AuthenticationPrincipal CustomUserDetails customUserDetails) { + Report report = reportService.reportReview(customUserDetails, createReportRequest); + slackService.sendSlackMessage(SlackMessageFormat.sendReport(report), + SlackChannel.REPORT_CHANNEL); + return BaseResponse.success(); + } } 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 3cb1a9e9..58d0a066 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,6 @@ package ssu.eatssu.domain.report.repository; import org.springframework.data.jpa.repository.JpaRepository; - import ssu.eatssu.domain.review.entity.Report; public interface ReportRepository extends JpaRepository { 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 d701996b..f0d7050b 100644 --- a/src/main/java/ssu/eatssu/domain/report/service/ReportService.java +++ b/src/main/java/ssu/eatssu/domain/report/service/ReportService.java @@ -1,11 +1,8 @@ package ssu.eatssu.domain.report.service; -import static ssu.eatssu.global.handler.response.BaseResponseStatus.*; - +import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; - -import lombok.RequiredArgsConstructor; import ssu.eatssu.domain.auth.security.CustomUserDetails; import ssu.eatssu.domain.report.dto.ReportCreateRequest; import ssu.eatssu.domain.report.dto.ReportTypeList; @@ -18,27 +15,30 @@ import ssu.eatssu.domain.user.repository.UserRepository; import ssu.eatssu.global.handler.response.BaseException; +import static ssu.eatssu.global.handler.response.BaseResponseStatus.NOT_FOUND_REVIEW; +import static ssu.eatssu.global.handler.response.BaseResponseStatus.NOT_FOUND_USER; + @RequiredArgsConstructor @Service @Transactional public class ReportService { - private final ReviewRepository reviewRepository; - private final UserRepository userRepository; - private final ReportRepository reportRepository; + private final ReviewRepository reviewRepository; + private final UserRepository userRepository; + private final ReportRepository reportRepository; - public Report reportReview(CustomUserDetails userDetails, ReportCreateRequest request) { - User user = userRepository.findById(userDetails.getId()) - .orElseThrow(() -> new BaseException(NOT_FOUND_USER)); + public Report reportReview(CustomUserDetails userDetails, ReportCreateRequest request) { + User user = userRepository.findById(userDetails.getId()) + .orElseThrow(() -> new BaseException(NOT_FOUND_USER)); - Review review = reviewRepository.findById(request.reviewId()) - .orElseThrow(() -> new BaseException(NOT_FOUND_REVIEW)); + Review review = reviewRepository.findById(request.reviewId()) + .orElseThrow(() -> new BaseException(NOT_FOUND_REVIEW)); - Report report = Report.create(user, review, request, ReportStatus.PENDING); - return reportRepository.save(report); - } + Report report = Report.create(user, review, request, ReportStatus.PENDING); + return reportRepository.save(report); + } - public ReportTypeList getReportType() { - return ReportTypeList.get(); - } + 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 5a7f15bf..553e53bd 100644 --- a/src/main/java/ssu/eatssu/domain/restaurant/entity/Restaurant.java +++ b/src/main/java/ssu/eatssu/domain/restaurant/entity/Restaurant.java @@ -1,35 +1,35 @@ package ssu.eatssu.domain.restaurant.entity; -import java.util.Arrays; - import com.fasterxml.jackson.annotation.JsonCreator; - import lombok.Getter; import ssu.eatssu.global.handler.response.BaseException; import ssu.eatssu.global.handler.response.BaseResponseStatus; +import java.util.Arrays; + @Getter public enum Restaurant { - DODAM("DODAM", 6000), - DORMITORY("DORMITORY", 5500), - FOOD_COURT("FOOD_COURT", null), - SNACK_CORNER("SNACK_CORNER", null), - HAKSIK("HAKSIK", 5000); - - private final String restaurantName; - private final Integer restaurantPrice; - - Restaurant(String restaurantName, Integer price) { - this.restaurantName = restaurantName; - this.restaurantPrice = price; - } - - @JsonCreator - public static Restaurant from(String restaurantName) { - return Arrays.stream(Restaurant.values()) - .filter(r -> r.getRestaurantName().equals(restaurantName)) - .findAny() - .orElseThrow(() -> new BaseException(BaseResponseStatus.NOT_FOUND_RESTAURANT)); - } + DODAM("DODAM", 6000), + DORMITORY("DORMITORY", 5500), + FOOD_COURT("FOOD_COURT", null), + SNACK_CORNER("SNACK_CORNER", null), + HAKSIK("HAKSIK", 5000), + FACULTY("FACULTY",null); + + private final String restaurantName; + private final Integer restaurantPrice; + + Restaurant(String restaurantName, Integer price) { + this.restaurantName = restaurantName; + this.restaurantPrice = price; + } + + @JsonCreator + public static Restaurant from(String restaurantName) { + return Arrays.stream(Restaurant.values()) + .filter(r -> r.getRestaurantName().equals(restaurantName)) + .findAny() + .orElseThrow(() -> new BaseException(BaseResponseStatus.NOT_FOUND_RESTAURANT)); + } } \ No newline at end of file diff --git a/src/main/java/ssu/eatssu/domain/restaurant/entity/RestaurantType.java b/src/main/java/ssu/eatssu/domain/restaurant/entity/RestaurantType.java index 3d93bcf8..5e771e9b 100644 --- a/src/main/java/ssu/eatssu/domain/restaurant/entity/RestaurantType.java +++ b/src/main/java/ssu/eatssu/domain/restaurant/entity/RestaurantType.java @@ -1,28 +1,28 @@ package ssu.eatssu.domain.restaurant.entity; +import lombok.Getter; + import java.util.Arrays; import java.util.List; -import lombok.Getter; - @Getter public enum RestaurantType { - FIXED("고정메뉴 식당", Arrays.asList(Restaurant.FOOD_COURT, Restaurant.SNACK_CORNER)), - VARIABLE("변동메뉴 식당", Arrays.asList(Restaurant.DODAM, Restaurant.DORMITORY, Restaurant.HAKSIK)); + FIXED("고정메뉴 식당", Arrays.asList(Restaurant.FOOD_COURT, Restaurant.SNACK_CORNER)), + VARIABLE("변동메뉴 식당", Arrays.asList(Restaurant.DODAM, Restaurant.DORMITORY, Restaurant.HAKSIK)); - private final String description; - private final List restaurants; + private final String description; + private final List restaurants; - RestaurantType(String description, List restaurants) { - this.description = description; - this.restaurants = restaurants; - } + RestaurantType(String description, List restaurants) { + this.description = description; + this.restaurants = restaurants; + } - public static boolean isFixedType(Restaurant restaurant) { - return FIXED.restaurants.contains(restaurant); - } + public static boolean isFixedType(Restaurant restaurant) { + return FIXED.restaurants.contains(restaurant); + } - public static boolean isVariableType(Restaurant restaurant) { - return VARIABLE.restaurants.contains(restaurant); - } + public static boolean isVariableType(Restaurant restaurant) { + return VARIABLE.restaurants.contains(restaurant); + } } diff --git a/src/main/java/ssu/eatssu/domain/review/dto/CreateMealReviewRequest.java b/src/main/java/ssu/eatssu/domain/review/dto/CreateMealReviewRequest.java index bc9ec02e..d510ca1a 100644 --- a/src/main/java/ssu/eatssu/domain/review/dto/CreateMealReviewRequest.java +++ b/src/main/java/ssu/eatssu/domain/review/dto/CreateMealReviewRequest.java @@ -1,8 +1,5 @@ package ssu.eatssu.domain.review.dto; -import java.util.List; -import java.util.stream.Collectors; - import io.swagger.v3.oas.annotations.media.Schema; import lombok.AllArgsConstructor; import lombok.Getter; @@ -11,32 +8,35 @@ import ssu.eatssu.domain.review.entity.ReviewImage; import ssu.eatssu.domain.user.entity.User; -@Schema(title = "리뷰 작성") +import java.util.List; +import java.util.stream.Collectors; + +@Schema(title = "meal에 대한 리뷰 작성") @Getter @AllArgsConstructor public class CreateMealReviewRequest { - @Schema(description = "식단 식별자", example = "123") - private Long mealId; - @Schema(description = "평점", example = "4") - private Integer rating; - private List menuLikes; - @Schema(description = "한줄평", example = "맛있어용") - private String content; - @Schema(description = "리뷰 이미지 url 리스트", example = "[\"imgurl1\", \"imgurl2\"]") - private List imageUrls; + @Schema(description = "식단 식별자", example = "123") + private Long mealId; + @Schema(description = "평점", example = "4") + private Integer rating; + private List menuLikes; + @Schema(description = "한줄평", example = "맛있어용") + private String content; + @Schema(description = "리뷰 이미지 url 리스트", example = "[\"imgurl1\", \"imgurl2\"]") + private List imageUrls; - public Review toReviewEntity(User user, Meal meal) { - return Review.builder() - .user(user) - .meal(meal) - .content(content) - .rating(rating) - .build(); - } + public Review toReviewEntity(User user, Meal meal) { + return Review.builder() + .user(user) + .meal(meal) + .content(content) + .rating(rating) + .build(); + } - public List createReviewImages(Review review) { - return imageUrls.stream() - .map(imageUrl -> new ReviewImage(review, imageUrl)) - .collect(Collectors.toList()); - } + public List createReviewImages(Review review) { + return imageUrls.stream() + .map(imageUrl -> new ReviewImage(review, imageUrl)) + .collect(Collectors.toList()); + } } diff --git a/src/main/java/ssu/eatssu/domain/review/dto/CreateMenuReviewRequest.java b/src/main/java/ssu/eatssu/domain/review/dto/CreateMenuReviewRequest.java new file mode 100644 index 00000000..3a39d4ea --- /dev/null +++ b/src/main/java/ssu/eatssu/domain/review/dto/CreateMenuReviewRequest.java @@ -0,0 +1,59 @@ +package ssu.eatssu.domain.review.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.Max; +import lombok.AccessLevel; +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; +import ssu.eatssu.domain.user.entity.User; + +@Getter +@Setter +@Schema(title = "menu에 대한 리뷰 작성") +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class CreateMenuReviewRequest { + @Schema(description = "메뉴 식별자", example = "123") + private Long menuId; + @Schema(description = "평점-메인", example = "4") + private Integer mainRating; + + @Schema(description = "평점-양", example = "4") + private Integer amountRating; + + @Schema(description = "평점-맛", example = "4") + private Integer tasteRating; + + @Max(150) + @Schema(description = "한줄평", example = "맛있어용") + private String content; + + @Schema(description = "리뷰 이미지 URL", example = "https://s3.~~~.jpg") + private String imageUrl; + private MenuLikeRequest menuLike; + + public CreateMenuReviewRequest(int mainRating, int amountRating, int tasteRating, String content) { + Assert.isTrue(mainRating >= 1 && mainRating <= 5, "평점은 1에서 5 사이 여야 합니다."); + Assert.isTrue(amountRating >= 1 && amountRating <= 5, "평점은 1에서 5 사이 여야 합니다."); + Assert.isTrue(tasteRating >= 1 && tasteRating <= 5, "평점은 1에서 5 사이 여야 합니다."); + Assert.notNull(content, "리뷰는 null이 될 수 없습니다."); + this.mainRating = mainRating; + this.amountRating = amountRating; + this.tasteRating = tasteRating; + this.content = content; + } + + public Review toReviewEntity(User user, Menu menu) { + Ratings ratings = Ratings.of(this.mainRating, this.amountRating, this.tasteRating); + return Review.builder() + .user(user) + .content(this.content) + .ratings(ratings) + .menu(menu) + .build(); + } +} 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 b15f081d..633cb7c6 100644 --- a/src/main/java/ssu/eatssu/domain/review/dto/MealReviewResponse.java +++ b/src/main/java/ssu/eatssu/domain/review/dto/MealReviewResponse.java @@ -1,102 +1,86 @@ package ssu.eatssu.domain.review.dto; -import java.time.LocalDate; -import java.util.ArrayList; -import java.util.List; -import java.util.stream.Collectors; - import io.swagger.v3.oas.annotations.media.Schema; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Getter; import ssu.eatssu.domain.review.entity.Review; import ssu.eatssu.domain.review.entity.ReviewMenuLike; -import ssu.eatssu.domain.user.util.UserAliasUtil; + +import java.time.LocalDate; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; @AllArgsConstructor @Builder @Schema(title = "리뷰 상세") @Getter public class MealReviewResponse { - @Schema(description = "리뷰 식별자", example = "123") - Long reviewId; - - @Schema(description = "작성자 식별자", example = "123") - Long writerId; - - @Schema(description = "본인 글인지 여부(true/false)", example = "true") - Boolean isWriter; - - @Schema(description = "작성자 닉네임", example = "숭시리시리") - private String writerNickname; - - @Schema(description = "평점", example = "4") - private Integer rating; - - @Schema(description = "리뷰 작성 날짜(format = yyyy-MM-dd)", example = "2023-04-07") - private LocalDate writtenAt; - - @Schema(description = "리뷰 내용", example = "맛있습니당") - private String content; - - @Schema(description = "리뷰 이미지 url 리스트", example = "[\"imgurl1\", \"imgurl2\"]") - private List imageUrls; - - @Schema(description = "좋아요한 메뉴명 리스트", example = "[\"메뉴1\", \"메뉴2\"]") - private List likedMenuNames; - - @Schema(description = "본인이 이 리뷰에 좋아요를 눌렀는지 여부(true/false)", example = "true") - private Boolean isLikedByUser; - - @Schema(description = "리뷰 좋아요 개수", example = "10") - private Integer likeCount; - - @Schema(description = "별칭", example = "미슈테리 미식가") - private String alias; - - public static MealReviewResponse from(Review review, Long userId) { - 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()); - - boolean isLikedByUser = (userId != null) && review.getReviewLikes().stream() - .anyMatch(like -> like.getUser().getId().equals(userId)); - - String alias = (review.getUser() != null) ? UserAliasUtil.getUserAlias(review.getUser(), - review.getUser().getReviews()) : "알 수 없음"; - - MealReviewResponseBuilder builder = MealReviewResponse.builder() - .reviewId(review.getId()) - .rating(review.getRating()) - .writtenAt(review.getCreatedDate().toLocalDate()) - .content(review.getContent()) - .imageUrls(imageUrls) - .likedMenuNames(likedMenuNames) - .isLikedByUser(isLikedByUser) - .likeCount(review.getReviewLikes().size()) - .alias(alias); - - if (review.getUser() == null) { - return builder.writerId(null) - .writerNickname("알 수 없음") - .isWriter(false) - .build(); - } - - if (review.getUser().getId().equals(userId)) { - return builder.writerId(review.getUser().getId()) - .writerNickname(review.getUser().getNickname()) - .isWriter(true) - .build(); - } - - return builder.writerId(review.getUser().getId()) - .writerNickname(review.getUser().getNickname()) - .isWriter(false) - .build(); - } + @Schema(description = "리뷰 식별자", example = "123") + Long reviewId; + + @Schema(description = "작성자 식별자", example = "123") + Long writerId; + + @Schema(description = "본인 글인지 여부(true/false)", example = "true") + Boolean isWriter; + + @Schema(description = "작성자 닉네임", example = "숭시리시리") + private String writerNickname; + + @Schema(description = "평점", example = "4") + private Integer rating; + + @Schema(description = "리뷰 작성 날짜(format = yyyy-MM-dd)", example = "2023-04-07") + private LocalDate writtenAt; + + @Schema(description = "리뷰 내용", example = "맛있습니당") + private String content; + + @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) { + 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()); + + MealReviewResponseBuilder builder = MealReviewResponse.builder() + .reviewId(review.getId()) + .rating(review.getRating()) + .writtenAt(review.getCreatedDate().toLocalDate()) + .content(review.getContent()) + .imageUrls(imageUrls) + .menuNames(review.getMeal().getMenuNames()) + .likedMenuNames(likedMenuNames); + + if (review.getUser() == null) { + return builder.writerId(null) + .writerNickname("알 수 없음") + .isWriter(false) + .build(); + } + + if (review.getUser().getId().equals(userId)) { + return builder.writerId(review.getUser().getId()) + .writerNickname(review.getUser().getNickname()) + .isWriter(true) + .build(); + } + + return builder.writerId(review.getUser().getId()) + .writerNickname(review.getUser().getNickname()) + .isWriter(false) + .build(); + } } diff --git a/src/main/java/ssu/eatssu/domain/review/dto/MealReviewsResponse.java b/src/main/java/ssu/eatssu/domain/review/dto/MealReviewsResponse.java index b7c9e5ab..05ba5a92 100644 --- a/src/main/java/ssu/eatssu/domain/review/dto/MealReviewsResponse.java +++ b/src/main/java/ssu/eatssu/domain/review/dto/MealReviewsResponse.java @@ -1,46 +1,46 @@ package ssu.eatssu.domain.review.dto; -import java.util.List; - import io.swagger.v3.oas.annotations.media.Schema; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Getter; +import java.util.List; + @Schema(title = "식단 리뷰 정보(평점 등등)") @Getter @AllArgsConstructor @Builder public class MealReviewsResponse implements ReviewInformationResponse { - @Schema(description = "메뉴명 리스트", example = "['고구마치즈돈까스', '막국수', '미니밥','단무지', '요구르트']") - private List menuNames; + @Schema(description = "메뉴명 리스트", example = "['고구마치즈돈까스', '막국수', '미니밥','단무지', '요구르트']") + private List menuNames; - @Schema(description = "리뷰 개수", example = "15") - private Long totalReviewCount; + @Schema(description = "리뷰 개수", example = "15") + private Long totalReviewCount; - @Schema(description = "평점-메인", example = "4.4") - private Double mainRating; + @Schema(description = "평점-메인", example = "4.4") + private Double mainRating; - @Schema(description = "평점-양", example = "4.4") - private Double amountRating; + @Schema(description = "평점-양", example = "4.4") + private Double amountRating; - @Schema(description = "평점-맛", example = "4.4") - private Double tasteRating; + @Schema(description = "평점-맛", example = "4.4") + private Double tasteRating; - @Schema(description = "평점 별 갯수") - private ReviewRatingCount reviewRatingCount; + @Schema(description = "평점 별 갯수") + private ReviewRatingCount reviewRatingCount; - public static MealReviewsResponse of(Long totalReviewCount, List menuNames, - RatingAverages ratingAverages, ReviewRatingCount reviewRatingCount) { + public static MealReviewsResponse of(Long totalReviewCount, List menuNames, + RatingAverages ratingAverages, ReviewRatingCount reviewRatingCount) { - return MealReviewsResponse.builder() - .menuNames(menuNames) - .mainRating(ratingAverages.mainRating()) - .amountRating(ratingAverages.amountRating()) - .tasteRating(ratingAverages.tasteRating()) - .totalReviewCount(totalReviewCount) - .reviewRatingCount(reviewRatingCount) - .build(); - } + return MealReviewsResponse.builder() + .menuNames(menuNames) + .mainRating(ratingAverages.mainRating()) + .amountRating(ratingAverages.amountRating()) + .tasteRating(ratingAverages.tasteRating()) + .totalReviewCount(totalReviewCount) + .reviewRatingCount(reviewRatingCount) + .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 ff19e98c..2fa91875 100644 --- a/src/main/java/ssu/eatssu/domain/review/dto/MealReviewsV2Response.java +++ b/src/main/java/ssu/eatssu/domain/review/dto/MealReviewsV2Response.java @@ -1,44 +1,39 @@ package ssu.eatssu.domain.review.dto; -import java.util.List; - import io.swagger.v3.oas.annotations.media.Schema; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Getter; -import ssu.eatssu.domain.menu.entity.Meal; + +import java.util.List; @Schema(title = "식단 리뷰 정보V2(평점 등등)") @Getter @AllArgsConstructor @Builder -public class MealReviewsV2Response implements ReviewInformationResponse{ - @Schema(description = "메뉴명 리스트", example = "['고구마치즈돈까스', '막국수', '미니밥','단무지', '요구르트']") - private List menuNames; - - @Schema(description = "리뷰 개수", example = "15") - private Long totalReviewCount; - - @Schema(description = "평점-메인", example = "4.4") - private Double mainRating; - - @Schema(description = "좋아요 개수", example = "4.4") - private Integer likeCount; - - @Schema(description = "싫어요 개수", example = "4.4") - private Integer unlikeCount; - - @Schema(description = "평점 별 갯수") - private ReviewRatingCount reviewRatingCount; - - public static MealReviewsV2Response of(Long totalReviewCount, List menuNames, - RatingAverages ratingAverages, ReviewRatingCount reviewRatingCount) { - - return MealReviewsV2Response.builder() - .menuNames(menuNames) - .mainRating(ratingAverages.mainRating()) - .totalReviewCount(totalReviewCount) - .reviewRatingCount(reviewRatingCount) - .build(); - } +public class MealReviewsV2Response implements ReviewInformationResponse { + @Schema(description = "메뉴명 리스트", example = "['고구마치즈돈까스', '막국수', '미니밥','단무지', '요구르트']") + private List menuNames; + + @Schema(description = "리뷰 개수", example = "15") + private Long totalReviewCount; + + @Schema(description = "평점-메인", example = "4.4") + private Double mainRating; + + @Schema(description = "좋아요 개수", example = "4.4") + private Integer likeCount; + @Schema(description = "평점 별 갯수") + private ReviewRatingCount reviewRatingCount; + + public static MealReviewsV2Response of(Long totalReviewCount, List menuNames, + RatingAverages ratingAverages, ReviewRatingCount reviewRatingCount) { + + return MealReviewsV2Response.builder() + .menuNames(menuNames) + .mainRating(ratingAverages.mainRating()) + .totalReviewCount(totalReviewCount) + .reviewRatingCount(reviewRatingCount) + .build(); + } } diff --git a/src/main/java/ssu/eatssu/domain/review/dto/MenuLikeRequest.java b/src/main/java/ssu/eatssu/domain/review/dto/MenuLikeRequest.java index 593cdc4d..1042f12c 100644 --- a/src/main/java/ssu/eatssu/domain/review/dto/MenuLikeRequest.java +++ b/src/main/java/ssu/eatssu/domain/review/dto/MenuLikeRequest.java @@ -8,8 +8,8 @@ @Getter @AllArgsConstructor public class MenuLikeRequest { - @Schema(description = "메뉴 식별자", example = "123") - private Long menuId; - @Schema(description = "좋아요 or 싫어요", example = "좋아요 : true or 싫어요 : false") - private Boolean isLike; + @Schema(description = "메뉴 식별자", example = "123") + private Long menuId; + @Schema(description = "좋아요 or 싫어요", example = "좋아요 : true or 싫어요 : false") + private Boolean isLike; } diff --git a/src/main/java/ssu/eatssu/domain/review/dto/MenuReviewResponse.java b/src/main/java/ssu/eatssu/domain/review/dto/MenuReviewResponse.java index c94bdc44..77056b7a 100644 --- a/src/main/java/ssu/eatssu/domain/review/dto/MenuReviewResponse.java +++ b/src/main/java/ssu/eatssu/domain/review/dto/MenuReviewResponse.java @@ -12,34 +12,34 @@ @Builder public class MenuReviewResponse implements ReviewInformationResponse { - @Schema(description = "메뉴 이름", example = "바질토마토베이글") - private String menuName; - - @Schema(description = "리뷰 개수", example = "15") - private Integer totalReviewCount; - - @Schema(description = "평점-메인", example = "4.4") - private Double mainRating; - - @Schema(description = "평점-양", example = "4.4") - private Double amountRating; - - @Schema(description = "평점-맛", example = "4.4") - private Double tasteRating; - - @Schema(description = "평점 별 갯수") - private ReviewRatingCount reviewRatingCount; - - public static MenuReviewResponse of(Menu menu, - RatingAverages ratingAverages, - ReviewRatingCount reviewRatingCount) { - return MenuReviewResponse.builder() - .menuName(menu.getName()) - .totalReviewCount(menu.getTotalReviewCount()) - .mainRating(ratingAverages.mainRating()) - .amountRating(ratingAverages.amountRating()) - .tasteRating(ratingAverages.tasteRating()) - .reviewRatingCount(reviewRatingCount) - .build(); - } + @Schema(description = "메뉴 이름", example = "바질토마토베이글") + private String menuName; + + @Schema(description = "리뷰 개수", example = "15") + private Integer totalReviewCount; + + @Schema(description = "평점-메인", example = "4.4") + private Double mainRating; + + @Schema(description = "평점-양", example = "4.4") + private Double amountRating; + + @Schema(description = "평점-맛", example = "4.4") + private Double tasteRating; + + @Schema(description = "평점 별 갯수") + private ReviewRatingCount reviewRatingCount; + + public static MenuReviewResponse of(Menu menu, + RatingAverages ratingAverages, + ReviewRatingCount reviewRatingCount) { + return MenuReviewResponse.builder() + .menuName(menu.getName()) + .totalReviewCount(menu.getTotalReviewCount()) + .mainRating(ratingAverages.mainRating()) + .amountRating(ratingAverages.amountRating()) + .tasteRating(ratingAverages.tasteRating()) + .reviewRatingCount(reviewRatingCount) + .build(); + } } 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 15c753a8..7c68fe56 100644 --- a/src/main/java/ssu/eatssu/domain/review/dto/MenuReviewsV2Response.java +++ b/src/main/java/ssu/eatssu/domain/review/dto/MenuReviewsV2Response.java @@ -1,12 +1,9 @@ package ssu.eatssu.domain.review.dto; -import java.util.List; - import io.swagger.v3.oas.annotations.media.Schema; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Getter; -import ssu.eatssu.domain.menu.entity.Meal; import ssu.eatssu.domain.menu.entity.Menu; @Schema(title = "메뉴 리뷰 정보V2(평점 등등)") @@ -14,34 +11,33 @@ @AllArgsConstructor @Builder public class MenuReviewsV2Response implements ReviewInformationResponse { - @Schema(description = "메뉴명 리스트", example = "['고구마치즈돈까스', '막국수', '미니밥','단무지', '요구르트']") - private List menuNames; - - @Schema(description = "리뷰 개수", example = "15") - private Long totalReviewCount; - - @Schema(description = "평점-메인", example = "4.4") - private Double mainRating; - - @Schema(description = "좋아요 개수", example = "4.4") - private Integer likeCount; - - @Schema(description = "싫어요 개수", example = "4.4") - private Integer unlikeCount; - - @Schema(description = "평점 별 갯수") - private ReviewRatingCount reviewRatingCount; - - public static MenuReviewsV2Response of(Long totalReviewCount, List menuNames, - RatingAverages ratingAverages, ReviewRatingCount reviewRatingCount,Menu menu) { - - return MenuReviewsV2Response.builder() - .menuNames(menuNames) - .mainRating(ratingAverages.mainRating()) - .likeCount(menu.getLikeCount()) - .unlikeCount(menu.getUnlikeCount()) - .totalReviewCount(totalReviewCount) - .reviewRatingCount(reviewRatingCount) - .build(); - } + @Schema(description = "메뉴명", example = "고구마 치즈 돈까스") + private String menuName; + + @Schema(description = "리뷰 개수", example = "15") + private Long totalReviewCount; + + @Schema(description = "평점-메인", example = "4.4") + private Double mainRating; + + @Schema(description = "좋아요 개수", example = "4.4") + private Integer likeCount; + + @Schema(description = "평점 별 갯수") + private ReviewRatingCount reviewRatingCount; + + public static MenuReviewsV2Response of(Long totalReviewCount, + String menuName, + RatingAverages ratingAverages, + ReviewRatingCount reviewRatingCount, + Menu menu) { + + return MenuReviewsV2Response.builder() + .menuName(menuName) + .mainRating(ratingAverages.mainRating()) + .likeCount(menu.getLikeCount()) + .totalReviewCount(totalReviewCount) + .reviewRatingCount(reviewRatingCount) + .build(); + } } diff --git a/src/main/java/ssu/eatssu/domain/review/dto/RatingAverages.java b/src/main/java/ssu/eatssu/domain/review/dto/RatingAverages.java index 66a4a8cd..d96d03eb 100644 --- a/src/main/java/ssu/eatssu/domain/review/dto/RatingAverages.java +++ b/src/main/java/ssu/eatssu/domain/review/dto/RatingAverages.java @@ -4,7 +4,7 @@ public record RatingAverages(Double mainRating, Double amountRating, Double tasteRating) { - @Builder - public RatingAverages { - } + @Builder + public RatingAverages { + } } \ No newline at end of file diff --git a/src/main/java/ssu/eatssu/domain/review/dto/RatingsDto.java b/src/main/java/ssu/eatssu/domain/review/dto/RatingsDto.java index c0b3efe8..dab7e5f5 100644 --- a/src/main/java/ssu/eatssu/domain/review/dto/RatingsDto.java +++ b/src/main/java/ssu/eatssu/domain/review/dto/RatingsDto.java @@ -4,16 +4,16 @@ public record RatingsDto(Ratings ratings) { - public Integer getMainRating() { - return ratings.getMainRating(); - } + public Integer getMainRating() { + return ratings.getMainRating(); + } - public Integer getTasteRating() { - return ratings.getTasteRating(); - } + public Integer getTasteRating() { + return ratings.getTasteRating(); + } - public Integer getAmountRating() { - return ratings.getAmountRating(); - } + public Integer getAmountRating() { + return ratings.getAmountRating(); + } } 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 962152b8..197d6f5f 100644 --- a/src/main/java/ssu/eatssu/domain/review/dto/RestaurantReviewResponse.java +++ b/src/main/java/ssu/eatssu/domain/review/dto/RestaurantReviewResponse.java @@ -10,18 +10,18 @@ @AllArgsConstructor @Builder public class RestaurantReviewResponse { - @Schema(description = "리뷰 개수", example = "15") - private Integer totalReviewCount; + @Schema(description = "리뷰 개수", example = "15") + private Integer totalReviewCount; - @Schema(description = "별점 별 개수") - private ReviewRatingCount reviewRatingCount; + @Schema(description = "별점 별 개수") + private ReviewRatingCount reviewRatingCount; - @Schema(description = "리뷰 평점", example = "4.4") - private Double mainRating; + @Schema(description = "리뷰 평점", example = "4.4") + private Double mainRating; - @Schema(description = "좋아요 개수", example = "15") - private Integer likeCount; + @Schema(description = "좋아요 개수", example = "15") + private Integer likeCount; - @Schema(description = "싫어요 개수", example = "15") - private Integer unlikeCount; + @Schema(description = "싫어요 개수", example = "15") + private Integer unlikeCount; } \ No newline at end of file diff --git a/src/main/java/ssu/eatssu/domain/review/dto/ReviewCreateRequest.java b/src/main/java/ssu/eatssu/domain/review/dto/ReviewCreateRequest.java index c138369f..6843b8cc 100644 --- a/src/main/java/ssu/eatssu/domain/review/dto/ReviewCreateRequest.java +++ b/src/main/java/ssu/eatssu/domain/review/dto/ReviewCreateRequest.java @@ -1,13 +1,12 @@ package ssu.eatssu.domain.review.dto; -import org.springframework.util.Assert; - import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.Max; import lombok.AccessLevel; 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; @@ -19,38 +18,38 @@ @Setter public class ReviewCreateRequest { - @Schema(description = "평점-메인", example = "4") - private Integer mainRating; - - @Schema(description = "평점-양", example = "4") - private Integer amountRating; - - @Schema(description = "평점-맛", example = "4") - private Integer tasteRating; - - @Max(150) - @Schema(description = "한줄평", example = "맛있어용") - private String content; - - public ReviewCreateRequest(int mainRating, int amountRating, int tasteRating, String content) { - Assert.isTrue(mainRating >= 1 && mainRating <= 5, "평점은 1에서 5 사이 여야 합니다."); - Assert.isTrue(amountRating >= 1 && amountRating <= 5, "평점은 1에서 5 사이 여야 합니다."); - Assert.isTrue(tasteRating >= 1 && tasteRating <= 5, "평점은 1에서 5 사이 여야 합니다."); - Assert.notNull(content, "리뷰는 null이 될 수 없습니다."); - this.mainRating = mainRating; - this.amountRating = amountRating; - this.tasteRating = tasteRating; - this.content = content; - } - - public Review toEntity(User user, Menu menu) { - Ratings ratings = Ratings.of(this.mainRating, this.amountRating, this.tasteRating); - return Review.builder() - .user(user) - .content(this.content) - .ratings(ratings) - .menu(menu) - .build(); - } + @Schema(description = "평점-메인", example = "4") + private Integer mainRating; + + @Schema(description = "평점-양", example = "4") + private Integer amountRating; + + @Schema(description = "평점-맛", example = "4") + private Integer tasteRating; + + @Max(150) + @Schema(description = "한줄평", example = "맛있어용") + private String content; + + public ReviewCreateRequest(int mainRating, int amountRating, int tasteRating, String content) { + Assert.isTrue(mainRating >= 1 && mainRating <= 5, "평점은 1에서 5 사이 여야 합니다."); + Assert.isTrue(amountRating >= 1 && amountRating <= 5, "평점은 1에서 5 사이 여야 합니다."); + Assert.isTrue(tasteRating >= 1 && tasteRating <= 5, "평점은 1에서 5 사이 여야 합니다."); + Assert.notNull(content, "리뷰는 null이 될 수 없습니다."); + this.mainRating = mainRating; + this.amountRating = amountRating; + this.tasteRating = tasteRating; + this.content = content; + } + + public Review toEntity(User user, Menu menu) { + Ratings ratings = Ratings.of(this.mainRating, this.amountRating, this.tasteRating); + return Review.builder() + .user(user) + .content(this.content) + .ratings(ratings) + .menu(menu) + .build(); + } } 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 b99ebd3c..40eb70b5 100644 --- a/src/main/java/ssu/eatssu/domain/review/dto/ReviewDetail.java +++ b/src/main/java/ssu/eatssu/domain/review/dto/ReviewDetail.java @@ -1,88 +1,88 @@ package ssu.eatssu.domain.review.dto; -import java.time.LocalDate; -import java.util.ArrayList; -import java.util.List; - import io.swagger.v3.oas.annotations.media.Schema; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Getter; import ssu.eatssu.domain.review.entity.Review; +import java.time.LocalDate; +import java.util.ArrayList; +import java.util.List; + @AllArgsConstructor @Builder @Schema(title = "리뷰 상세") @Getter public class ReviewDetail { - @Schema(description = "리뷰 식별자", example = "123") - Long reviewId; - - @Schema(description = "메뉴 이름", example = "콥샐러드") - String menu; - - @Schema(description = "작성자 식별자", example = "123") - Long writerId; - - @Schema(description = "본인 글인지 여부(true/false)", example = "true") - Boolean isWriter; - - @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 = "리뷰 작성 날짜(format = yyyy-MM-dd)", example = "2023-04-07") - private LocalDate writedAt; - - @Schema(description = "리뷰 내용", example = "맛있습니당") - private String content; - - @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())); - - ReviewDetailBuilder builder = ReviewDetail.builder() - .reviewId(review.getId()) - .mainRating(review.getRatings().getMainRating()) - .amountRating(review.getRatings().getAmountRating()) - .tasteRating(review.getRatings().getTasteRating()) - .writedAt(review.getCreatedDate().toLocalDate()) - .content(review.getContent()) - .imageUrls(imageUrls) - .menu(review.getMenu().getName()); - - if (review.getUser() == null) { - return builder - .writerId(null) - .writerNickname("알 수 없음") - .isWriter(false) - .build(); - } - - if (review.getUser().getId().equals(userId)) { - return builder - .writerId(review.getUser().getId()) - .writerNickname(review.getUser().getNickname()) - .isWriter(true) - .build(); - } - - return builder - .writerId(review.getUser().getId()) - .writerNickname(review.getUser().getNickname()) - .isWriter(false) - .build(); - } + @Schema(description = "리뷰 식별자", example = "123") + Long reviewId; + + @Schema(description = "메뉴 이름", example = "콥샐러드") + String menu; + + @Schema(description = "작성자 식별자", example = "123") + Long writerId; + + @Schema(description = "본인 글인지 여부(true/false)", example = "true") + Boolean isWriter; + + @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 = "리뷰 작성 날짜(format = yyyy-MM-dd)", example = "2023-04-07") + private LocalDate writedAt; + + @Schema(description = "리뷰 내용", example = "맛있습니당") + private String content; + + @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())); + + ReviewDetailBuilder builder = ReviewDetail.builder() + .reviewId(review.getId()) + .mainRating(review.getRatings().getMainRating()) + .amountRating(review.getRatings().getAmountRating()) + .tasteRating(review.getRatings().getTasteRating()) + .writedAt(review.getCreatedDate().toLocalDate()) + .content(review.getContent()) + .imageUrls(imageUrls) + .menu(review.getMenu().getName()); + + if (review.getUser() == null) { + return builder + .writerId(null) + .writerNickname("알 수 없음") + .isWriter(false) + .build(); + } + + if (review.getUser().getId().equals(userId)) { + return builder + .writerId(review.getUser().getId()) + .writerNickname(review.getUser().getNickname()) + .isWriter(true) + .build(); + } + + return builder + .writerId(review.getUser().getId()) + .writerNickname(review.getUser().getNickname()) + .isWriter(false) + .build(); + } } diff --git a/src/main/java/ssu/eatssu/domain/review/dto/ReviewRatingCount.java b/src/main/java/ssu/eatssu/domain/review/dto/ReviewRatingCount.java index 2edd23f9..facbdead 100644 --- a/src/main/java/ssu/eatssu/domain/review/dto/ReviewRatingCount.java +++ b/src/main/java/ssu/eatssu/domain/review/dto/ReviewRatingCount.java @@ -1,31 +1,32 @@ package ssu.eatssu.domain.review.dto; +import ssu.eatssu.domain.review.entity.Review; + import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.stream.Collectors; -import ssu.eatssu.domain.review.entity.Review; - //COMMENT: 더 좋은 매개변수 명이 있을 것 같은데 제 머리로는 이게 한계입니다... 멋진 변수명으로 바꿔주세요 public record ReviewRatingCount(long oneStarCount, long twoStarCount, long threeStarCount, long fourStarCount, - long fiveStarCount) { + long fiveStarCount) { - public ReviewRatingCount { - } + public ReviewRatingCount { + } - public static ReviewRatingCount from(List reviews) { - Map ratingDistribution = reviews.stream() - .collect( - Collectors.groupingBy(Review::getRating, LinkedHashMap::new, - Collectors.counting())); + public static ReviewRatingCount from(List reviews) { + Map ratingDistribution = reviews.stream() + .filter(r -> r.getRating() != null) + .collect(Collectors.groupingBy(Review::getRating, + LinkedHashMap::new, + Collectors.counting())); - return new ReviewRatingCount( - ratingDistribution.getOrDefault(1, 0L), - ratingDistribution.getOrDefault(2, 0L), - ratingDistribution.getOrDefault(3, 0L), - ratingDistribution.getOrDefault(4, 0L), - ratingDistribution.getOrDefault(5, 0L) - ); - } + return new ReviewRatingCount( + ratingDistribution.getOrDefault(1, 0L), + ratingDistribution.getOrDefault(2, 0L), + ratingDistribution.getOrDefault(3, 0L), + ratingDistribution.getOrDefault(4, 0L), + ratingDistribution.getOrDefault(5, 0L) + ); + } } diff --git a/src/main/java/ssu/eatssu/domain/review/dto/ReviewUpdateRequest.java b/src/main/java/ssu/eatssu/domain/review/dto/ReviewUpdateRequest.java index 471c07ba..fd2b6565 100644 --- a/src/main/java/ssu/eatssu/domain/review/dto/ReviewUpdateRequest.java +++ b/src/main/java/ssu/eatssu/domain/review/dto/ReviewUpdateRequest.java @@ -6,18 +6,18 @@ @Schema(title = "리뷰 수정(글)") public record ReviewUpdateRequest( - @Schema(description = "평점-메인", example = "4") - Integer mainRating, + @Schema(description = "평점-메인", example = "4") + Integer mainRating, - @Schema(description = "평점-양", example = "4") - Integer amountRating, + @Schema(description = "평점-양", example = "4") + Integer amountRating, - @Schema(description = "평점-맛", example = "4") - Integer tasteRating, + @Schema(description = "평점-맛", example = "4") + Integer tasteRating, - @Max(150) - @Schema(description = "한줄평", example = "맛있어용") - String content + @Max(150) + @Schema(description = "한줄평", example = "맛있어용") + String content ) { } \ No newline at end of file diff --git a/src/main/java/ssu/eatssu/domain/review/dto/SavedReviewImage.java b/src/main/java/ssu/eatssu/domain/review/dto/SavedReviewImage.java index 430f5cfc..0529ad4a 100644 --- a/src/main/java/ssu/eatssu/domain/review/dto/SavedReviewImage.java +++ b/src/main/java/ssu/eatssu/domain/review/dto/SavedReviewImage.java @@ -9,6 +9,6 @@ @Schema(title = "업로드 된 이미지 url") public class SavedReviewImage { - @Schema(description = "이미지 url", example = "image123.jpg") - private String url; + @Schema(description = "이미지 url", example = "image123.jpg") + private String url; } diff --git a/src/main/java/ssu/eatssu/domain/review/dto/UpdateMealReviewRequest.java b/src/main/java/ssu/eatssu/domain/review/dto/UpdateMealReviewRequest.java index 21a9d57c..dce5e9ad 100644 --- a/src/main/java/ssu/eatssu/domain/review/dto/UpdateMealReviewRequest.java +++ b/src/main/java/ssu/eatssu/domain/review/dto/UpdateMealReviewRequest.java @@ -1,20 +1,20 @@ package ssu.eatssu.domain.review.dto; -import java.util.List; - import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.Max; import lombok.AllArgsConstructor; import lombok.Getter; +import java.util.List; + @Schema(title = "리뷰 수정(글)") @Getter @AllArgsConstructor public class UpdateMealReviewRequest { - @Schema(description = "평점", example = "4") - private Integer rating; - private List menuLikes; - @Max(150) - @Schema(description = "한줄평", example = "맛있어용") - private String content; + @Schema(description = "평점", example = "4") + private Integer rating; + private List menuLikes; + @Max(150) + @Schema(description = "한줄평", example = "맛있어용") + private String content; } diff --git a/src/main/java/ssu/eatssu/domain/review/dto/UploadReviewRequest.java b/src/main/java/ssu/eatssu/domain/review/dto/UploadReviewRequest.java index 57c3b344..20890cee 100644 --- a/src/main/java/ssu/eatssu/domain/review/dto/UploadReviewRequest.java +++ b/src/main/java/ssu/eatssu/domain/review/dto/UploadReviewRequest.java @@ -1,13 +1,12 @@ package ssu.eatssu.domain.review.dto; -import org.springframework.util.Assert; - import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.Max; import lombok.AccessLevel; 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; @@ -19,42 +18,42 @@ @NoArgsConstructor(access = AccessLevel.PROTECTED) public class UploadReviewRequest { - @Schema(description = "평점-메인", example = "4") - private Integer mainRating; - - @Schema(description = "평점-양", example = "4") - private Integer amountRating; - - @Schema(description = "평점-맛", example = "4") - private Integer tasteRating; - - @Max(150) - @Schema(description = "한줄평", example = "맛있어용") - private String content; - - @Schema(description = "리뷰 이미지 URL", example = "https://s3.~~~.jpg") - private String imageUrl; - - public UploadReviewRequest(int mainRating, int amountRating, int tasteRating, String content) { - Assert.isTrue(mainRating >= 1 && mainRating <= 5, "평점은 1에서 5 사이 여야 합니다."); - Assert.isTrue(amountRating >= 1 && amountRating <= 5, "평점은 1에서 5 사이 여야 합니다."); - Assert.isTrue(tasteRating >= 1 && tasteRating <= 5, "평점은 1에서 5 사이 여야 합니다."); - Assert.notNull(content, "리뷰는 null이 될 수 없습니다."); - this.mainRating = mainRating; - this.amountRating = amountRating; - this.tasteRating = tasteRating; - this.content = content; - } - - public Review toReviewEntity(User user, Menu menu) { - Ratings ratings = Ratings.of(this.mainRating, this.amountRating, this.tasteRating); - return Review.builder() - .user(user) - .content(this.content) - .ratings(ratings) - .menu(menu) - .build(); - } + @Schema(description = "평점-메인", example = "4") + private Integer mainRating; + + @Schema(description = "평점-양", example = "4") + private Integer amountRating; + + @Schema(description = "평점-맛", example = "4") + private Integer tasteRating; + + @Max(150) + @Schema(description = "한줄평", example = "맛있어용") + private String content; + + @Schema(description = "리뷰 이미지 URL", example = "https://s3.~~~.jpg") + private String imageUrl; + + public UploadReviewRequest(int mainRating, int amountRating, int tasteRating, String content) { + Assert.isTrue(mainRating >= 1 && mainRating <= 5, "평점은 1에서 5 사이 여야 합니다."); + Assert.isTrue(amountRating >= 1 && amountRating <= 5, "평점은 1에서 5 사이 여야 합니다."); + Assert.isTrue(tasteRating >= 1 && tasteRating <= 5, "평점은 1에서 5 사이 여야 합니다."); + Assert.notNull(content, "리뷰는 null이 될 수 없습니다."); + this.mainRating = mainRating; + this.amountRating = amountRating; + this.tasteRating = tasteRating; + this.content = content; + } + + public Review toReviewEntity(User user, Menu menu) { + Ratings ratings = Ratings.of(this.mainRating, this.amountRating, this.tasteRating); + return Review.builder() + .user(user) + .content(this.content) + .ratings(ratings) + .menu(menu) + .build(); + } } diff --git a/src/main/java/ssu/eatssu/domain/review/dto/ValidMenuForViewResponse.java b/src/main/java/ssu/eatssu/domain/review/dto/ValidMenuForViewResponse.java new file mode 100644 index 00000000..6cc2a4a2 --- /dev/null +++ b/src/main/java/ssu/eatssu/domain/review/dto/ValidMenuForViewResponse.java @@ -0,0 +1,20 @@ +package ssu.eatssu.domain.review.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.Setter; + +import java.util.List; + +@Getter +@Setter +@Builder +@Schema(title = "리뷰에 포함되는 메뉴") +@AllArgsConstructor +public class ValidMenuForViewResponse { + @Schema(description = "리뷰에 포함되는 메뉴 리스트", example = "[김치볶음밥, 고구마치즈돈까스, 김자반]") + private List menuList; + +} diff --git a/src/main/java/ssu/eatssu/domain/review/entity/Report.java b/src/main/java/ssu/eatssu/domain/review/entity/Report.java index 72879752..a96803c0 100644 --- a/src/main/java/ssu/eatssu/domain/review/entity/Report.java +++ b/src/main/java/ssu/eatssu/domain/review/entity/Report.java @@ -28,35 +28,35 @@ @AllArgsConstructor public class Report extends BaseTimeEntity { - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - @Column(name = "review_report_id") - private Long id; - - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "reporter_id") - private User user; - - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "review_id") - private Review review; - - @Enumerated(EnumType.STRING) - private ReportType reportType; - - private String content; - - @Enumerated(EnumType.STRING) - private ReportStatus status; - - public static Report create(User user, Review review, ReportCreateRequest request, - ReportStatus status) { - return Report.builder() - .user(user) - .review(review) - .reportType(request.reportType()) - .content(request.content()) - .status(status) - .build(); - } + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "review_report_id") + private Long id; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "reporter_id") + private User user; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "review_id") + private Review review; + + @Enumerated(EnumType.STRING) + private ReportType reportType; + + private String content; + + @Enumerated(EnumType.STRING) + private ReportStatus status; + + public static Report create(User user, Review review, ReportCreateRequest request, + ReportStatus status) { + return Report.builder() + .user(user) + .review(review) + .reportType(request.reportType()) + .content(request.content()) + .status(status) + .build(); + } } diff --git a/src/main/java/ssu/eatssu/domain/review/entity/Review.java b/src/main/java/ssu/eatssu/domain/review/entity/Review.java index 72c42056..ce98406d 100644 --- a/src/main/java/ssu/eatssu/domain/review/entity/Review.java +++ b/src/main/java/ssu/eatssu/domain/review/entity/Review.java @@ -1,10 +1,5 @@ package ssu.eatssu.domain.review.entity; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.stream.Collectors; - import jakarta.persistence.CascadeType; import jakarta.persistence.Column; import jakarta.persistence.Embedded; @@ -28,6 +23,11 @@ import ssu.eatssu.domain.user.entity.BaseTimeEntity; import ssu.eatssu.domain.user.entity.User; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + @Entity @Getter @Setter @@ -36,113 +36,110 @@ @AllArgsConstructor public class Review extends BaseTimeEntity { - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - @Column(name = "review_id") - private Long id; - - private String content; - - // TODO : 삭제되어야 함 - @Embedded - private Ratings ratings; - - private Integer rating; - - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "user_id") - private User user; - - // TODO : 삭제되어야 함 - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "menu_id") - private Menu menu; - - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "meal_id") - private Meal meal; - - @Builder.Default - @OneToMany(mappedBy = "review", cascade = CascadeType.ALL, orphanRemoval = true) - private List reviewImages = new ArrayList<>(); - - @Builder.Default - @OneToMany(mappedBy = "review", cascade = CascadeType.ALL, orphanRemoval = true) - private List menuLikes = new ArrayList<>(); - - @OneToMany(mappedBy = "review", cascade = CascadeType.ALL, orphanRemoval = true) - private List reviewLikes = new ArrayList<>(); - - public void update(String content, Integer mainRate, Integer amountRate, Integer tasteRate) { - this.content = content; - this.ratings = Ratings.of(mainRate, amountRate, tasteRate); - } - - // TODO : this.user가 null이면? - public boolean isNotWrittenBy(User user) { - return !this.user.equals(user); - } - - public void clearUser() { - this.user = null; - } - - public void addReviewImage(String imageUrl) { - ReviewImage reviewImage = new ReviewImage(this, imageUrl); - this.reviewImages.add(reviewImage); - } - - public void addReviewMenuLike(Menu menu, boolean isLike) { - ReviewMenuLike reviewMenuLike = ReviewMenuLike.create(this, menu, isLike); - this.menuLikes.add(reviewMenuLike); - - if (isLike) { - menu.increaseLikeCount(); - } else { - menu.increaseUnlikeCount(); - } - } - - public void removeReviewMenuLike(ReviewMenuLike reviewMenuLike) { - this.menuLikes.remove(reviewMenuLike); - - if (reviewMenuLike.getIsLike()) { - reviewMenuLike.getMenu().decreaseLikeCount(); - } else { - reviewMenuLike.getMenu().decreaseUnlikeCount(); - } - } - - public void update(String content, int rating, Map updatedMenuLikes) { - this.content = content; - this.rating = rating; - - // 현재 menu like 상태를 menu를 기준으로 매핑 - Map currentMenuLikes = this.menuLikes.stream() - .collect(Collectors.toMap(ReviewMenuLike::getMenu, - menuLike -> menuLike)); - - for (Map.Entry entry : updatedMenuLikes.entrySet()) { - Menu menu = entry.getKey(); - Boolean newState = entry.getValue(); - ReviewMenuLike existingReviewMenuLike = currentMenuLikes.get(menu); - if (existingReviewMenuLike == null) { - // 기존에 없는 메뉴이면 새롭게 추가 - this.addReviewMenuLike(menu, newState); - } else { - // 기존에 있는 경우 : 상태가 다르면 업데이트 - if (!existingReviewMenuLike.getIsLike().equals(newState)) { - existingReviewMenuLike.updateLike(newState); - menu.changeLikeStatus(newState); - } - } - } - } - - public void resetMenuLikes() { - for (ReviewMenuLike reviewMenuLike : this.menuLikes) { - reviewMenuLike.resetMenuLikeStatus(); - } - this.menuLikes.clear(); - } + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "review_id") + private Long id; + @Column(length = 300) + private String content; + + // TODO : 삭제되어야 함 + @Embedded + private Ratings ratings; + + private Integer rating; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "user_id") + private User user; + + // TODO : 삭제되어야 함 + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "menu_id") + private Menu menu; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "meal_id") + private Meal meal; + + @Builder.Default + @OneToMany(mappedBy = "review", cascade = CascadeType.ALL, orphanRemoval = true) + private List reviewImages = new ArrayList<>(); + + @Builder.Default + @OneToMany(mappedBy = "review", cascade = CascadeType.ALL, orphanRemoval = true) + private List menuLikes = new ArrayList<>(); + + // @TODO: 리뷰v2 배포 이후 제거 해야함 + @OneToMany(mappedBy = "review", cascade = CascadeType.ALL, orphanRemoval = true) + private List reviewLikes = new ArrayList<>(); + + public void update(String content, Integer mainRate, Integer amountRate, Integer tasteRate) { + this.content = content; + this.ratings = Ratings.of(mainRate, amountRate, tasteRate); + } + + // TODO : this.user가 null이면? + public boolean isNotWrittenBy(User user) { + return !this.user.equals(user); + } + + public void clearUser() { + this.user = null; + } + + public void addReviewImage(String imageUrl) { + ReviewImage reviewImage = new ReviewImage(this, imageUrl); + this.reviewImages.add(reviewImage); + } + + public void addReviewMenuLike(Menu menu, boolean isLike) { + ReviewMenuLike reviewMenuLike = ReviewMenuLike.create(this, menu, isLike); + this.menuLikes.add(reviewMenuLike); + + if (isLike) { + menu.increaseLikeCount(); + } + } + + public void removeReviewMenuLike(ReviewMenuLike reviewMenuLike) { + this.menuLikes.remove(reviewMenuLike); + + if (reviewMenuLike.getIsLike()) { + reviewMenuLike.getMenu().decreaseLikeCount(); + } + } + + public void update(String content, int rating, Map updatedMenuLikes) { + this.content = content; + this.rating = rating; + + // 현재 menu like 상태를 menu를 기준으로 매핑 + Map currentMenuLikes = this.menuLikes.stream() + .collect(Collectors.toMap(ReviewMenuLike::getMenu, + menuLike -> menuLike)); + + for (Map.Entry entry : updatedMenuLikes.entrySet()) { + Menu menu = entry.getKey(); + Boolean newState = entry.getValue(); + ReviewMenuLike existingReviewMenuLike = currentMenuLikes.get(menu); + if (existingReviewMenuLike == null) { + // 기존에 없는 메뉴이면 새롭게 추가 + this.addReviewMenuLike(menu, newState); + } else { + // 기존에 있는 경우 : 상태가 다르면 업데이트 + if (!existingReviewMenuLike.getIsLike().equals(newState)) { + existingReviewMenuLike.updateLike(newState); + menu.changeLikeStatus(newState); + } + } + } + } + + public void resetMenuLikes() { + for (ReviewMenuLike reviewMenuLike : this.menuLikes) { + reviewMenuLike.resetMenuLikeStatus(); + } + this.menuLikes.clear(); + } } diff --git a/src/main/java/ssu/eatssu/domain/review/entity/ReviewImage.java b/src/main/java/ssu/eatssu/domain/review/entity/ReviewImage.java index c43f7fd7..bced0c86 100644 --- a/src/main/java/ssu/eatssu/domain/review/entity/ReviewImage.java +++ b/src/main/java/ssu/eatssu/domain/review/entity/ReviewImage.java @@ -21,19 +21,19 @@ @AllArgsConstructor public class ReviewImage { - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - @Column(name = "review_image_id") - private Long id; + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "review_image_id") + private Long id; - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "review_id") - private Review review; + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "review_id") + private Review review; - private String imageUrl; + private String imageUrl; - public ReviewImage(Review review, String imageUrl) { - this.review = review; - this.imageUrl = imageUrl; - } + public ReviewImage(Review review, String imageUrl) { + this.review = review; + this.imageUrl = imageUrl; + } } diff --git a/src/main/java/ssu/eatssu/domain/review/entity/ReviewLike.java b/src/main/java/ssu/eatssu/domain/review/entity/ReviewLike.java index 04597f5f..4d6006fb 100644 --- a/src/main/java/ssu/eatssu/domain/review/entity/ReviewLike.java +++ b/src/main/java/ssu/eatssu/domain/review/entity/ReviewLike.java @@ -19,20 +19,20 @@ @NoArgsConstructor(access = AccessLevel.PROTECTED) @AllArgsConstructor public class ReviewLike { - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - @Column(name = "review_like_id") - private Long id; + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "review_like_id") + private Long id; - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "review_id") - private Review review; + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "review_id") + private Review review; - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "user_id") - private User user; + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "user_id") + private User user; - public static ReviewLike create(User user, Review review) { - return new ReviewLike(null, review, user); - } + public static ReviewLike create(User user, Review review) { + return new ReviewLike(null, review, user); + } } diff --git a/src/main/java/ssu/eatssu/domain/review/entity/ReviewMenuLike.java b/src/main/java/ssu/eatssu/domain/review/entity/ReviewMenuLike.java index 7d51e4e3..7eb90e45 100644 --- a/src/main/java/ssu/eatssu/domain/review/entity/ReviewMenuLike.java +++ b/src/main/java/ssu/eatssu/domain/review/entity/ReviewMenuLike.java @@ -21,35 +21,35 @@ @Builder @AllArgsConstructor public class ReviewMenuLike { - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - @Column(name = "review_menu_like_id") - private Long id; - - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "review_id") - private Review review; - - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "menu_id") - private Menu menu; - - @Column(nullable = false) - private Boolean isLike; // true : 좋아요, false : 싫어요 - - public static ReviewMenuLike create(Review review, Menu menu, Boolean isLike) { - return ReviewMenuLike.builder() - .review(review) - .menu(menu) - .isLike(isLike) - .build(); - } - - public void updateLike(Boolean like) { - isLike = like; - } - - public void resetMenuLikeStatus() { - menu.cancelLike(this.isLike); - } + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "review_menu_like_id") + private Long id; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "review_id") + private Review review; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "menu_id") + private Menu menu; + + @Column(nullable = false) + private Boolean isLike; // true : 좋아요, false : 싫어요 + + public static ReviewMenuLike create(Review review, Menu menu, Boolean isLike) { + return ReviewMenuLike.builder() + .review(review) + .menu(menu) + .isLike(isLike) + .build(); + } + + public void updateLike(Boolean like) { + isLike = like; + } + + public void resetMenuLikeStatus() { + menu.cancelLike(this.isLike); + } } diff --git a/src/main/java/ssu/eatssu/domain/review/entity/Reviews.java b/src/main/java/ssu/eatssu/domain/review/entity/Reviews.java index 76eb3a5d..183d663c 100644 --- a/src/main/java/ssu/eatssu/domain/review/entity/Reviews.java +++ b/src/main/java/ssu/eatssu/domain/review/entity/Reviews.java @@ -1,8 +1,5 @@ package ssu.eatssu.domain.review.entity; -import java.util.ArrayList; -import java.util.List; - import jakarta.persistence.CascadeType; import jakarta.persistence.Embeddable; import jakarta.persistence.OneToMany; @@ -11,52 +8,53 @@ import lombok.NoArgsConstructor; import ssu.eatssu.domain.rating.entity.ReviewRating; +import java.util.ArrayList; +import java.util.List; + @Getter @NoArgsConstructor(access = AccessLevel.PUBLIC) @Embeddable public class Reviews { - @OneToMany(mappedBy = "menu", cascade = CascadeType.ALL) - private List reviews = new ArrayList<>(); - - public void add(Review review) { - reviews.add(review); - } - - public int size() { - return reviews.size(); - } - - public void calculateReviewRatings() { - this.reviews.forEach(review -> { - int rateValue = review.getRatings().getMainRating(); - ReviewRating.fromValue(rateValue).incrementCount(); - }); - } - - public int getTotalMainRating() { - return this.reviews.stream().mapToInt(review -> review.getRatings().getMainRating()).sum(); - } - - public int getTotalAmountRating() { - return this.reviews.stream().mapToInt(review -> review.getRatings().getAmountRating()) - .sum(); - } - - public int getTotalTasteRating() { - return this.reviews.stream().mapToInt(review -> review.getRatings().getTasteRating()).sum(); - } - - public double getAverageMainRating() { - return (double)this.getTotalMainRating() / this.size(); - } - - public double getAverageAmountRating() { - return (double)this.getTotalAmountRating() / this.size(); - } + @OneToMany(mappedBy = "menu", cascade = CascadeType.ALL) + private List reviews = new ArrayList<>(); + + public void add(Review review) { + reviews.add(review); + } + + public int size() { + return reviews.size(); + } + + public void calculateReviewRatings() { + this.reviews.forEach(review -> { + if (review.getRatings() != null) { + int rateValue = review.getRatings().getMainRating(); + ReviewRating.fromValue(rateValue).incrementCount(); + } + }); + } + + public int getTotalMainRating() { + return this.reviews.stream() + .filter(review -> review.getRatings() != null) + .mapToInt(review -> review.getRatings().getMainRating()) + .sum(); + } + + public int getTotalAmountRating() { + return this.reviews.stream() + .filter(review -> review.getRatings() != null) + .mapToInt(review -> review.getRatings().getAmountRating()) + .sum(); + } + + public int getTotalTasteRating() { + return this.reviews.stream() + .filter(review -> review.getRatings() != null) + .mapToInt(review -> review.getRatings().getTasteRating()).sum(); + } - public double getAverageTasteRating() { - return (double)this.getTotalTasteRating() / this.size(); - } } diff --git a/src/main/java/ssu/eatssu/domain/review/presentation/ReviewController.java b/src/main/java/ssu/eatssu/domain/review/presentation/ReviewController.java index fd3fac83..b58361ee 100644 --- a/src/main/java/ssu/eatssu/domain/review/presentation/ReviewController.java +++ b/src/main/java/ssu/eatssu/domain/review/presentation/ReviewController.java @@ -1,7 +1,15 @@ package ssu.eatssu.domain.review.presentation; -import java.util.List; - +import io.swagger.v3.oas.annotations.Hidden; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.enums.ParameterIn; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; import org.springdoc.core.annotations.ParameterObject; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Sort; @@ -19,17 +27,6 @@ import org.springframework.web.bind.annotation.RequestPart; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.multipart.MultipartFile; - -import io.swagger.v3.oas.annotations.Hidden; -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.Parameter; -import io.swagger.v3.oas.annotations.enums.ParameterIn; -import io.swagger.v3.oas.annotations.media.Content; -import io.swagger.v3.oas.annotations.media.Schema; -import io.swagger.v3.oas.annotations.responses.ApiResponse; -import io.swagger.v3.oas.annotations.responses.ApiResponses; -import io.swagger.v3.oas.annotations.tags.Tag; -import lombok.RequiredArgsConstructor; import ssu.eatssu.domain.auth.security.CustomUserDetails; import ssu.eatssu.domain.menu.entity.constants.MenuType; import ssu.eatssu.domain.review.dto.MealReviewsResponse; @@ -44,165 +41,167 @@ import ssu.eatssu.domain.slice.service.SliceService; import ssu.eatssu.global.handler.response.BaseResponse; +import java.util.List; + @RestController @RequiredArgsConstructor @RequestMapping("/reviews") @Tag(name = "Review", description = "리뷰 API") public class ReviewController { - private final ReviewService reviewService; - private final SliceService sliceService; + private final ReviewService reviewService; + private final SliceService sliceService; - @Operation(summary = "리뷰 리스트 조회 [인증 토큰 필수 X]", description = """ - 리뷰 리스트를 조회하는 API 입니다.

- menuType=FIX 의 경우 menuId 파라미터를 넣어주세요.

- menuType=CHANGE 의 경우 mealId 파라미터를 넣어주세요.

- 커서 기반 페이지네이션으로 리뷰 리스트를 조회합니다.

- 페이징 기본 값 = {size=20, sort=date, direction=desc}

- """) - @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "리뷰 리스트 조회 성공"), - @ApiResponse(responseCode = "400", description = "쿼리 파라미터 누락", content = @Content(schema = @Schema(implementation = BaseResponse.class))), - @ApiResponse(responseCode = "404", description = "존재하지 않는 메뉴", content = @Content(schema = @Schema(implementation = BaseResponse.class))), - @ApiResponse(responseCode = "404", description = "존재하지 않는 식단", content = @Content(schema = @Schema(implementation = BaseResponse.class))) - }) - @GetMapping("") - public BaseResponse> getReviews( - @Parameter(description = "타입(변동메뉴(식단)/고정메뉴)") @RequestParam("menuType") MenuType menuType, - @Parameter(description = "menuId(고정메뉴)") @RequestParam(value = "menuId", required = false) Long menuId, - @Parameter(description = "mealId(변동메뉴)") @RequestParam(value = "mealId", required = false) Long mealId, - @Parameter(description = "마지막으로 조회된 reviewId값(첫 조회시 값 필요 없음)", in = ParameterIn.QUERY) - @RequestParam(value = "lastReviewId", required = false) Long lastReviewId, - @ParameterObject @PageableDefault(size = 20, sort = "date", direction = Sort.Direction.DESC) Pageable pageable, - @AuthenticationPrincipal CustomUserDetails customUserDetails) { - SliceResponse myReviews = sliceService.findReviews(menuType, menuId, mealId, - pageable, lastReviewId, customUserDetails); + @Operation(summary = "리뷰 리스트 조회 [인증 토큰 필수 X]", description = """ + 리뷰 리스트를 조회하는 API 입니다.

+ menuType=FIX 의 경우 menuId 파라미터를 넣어주세요.

+ menuType=CHANGE 의 경우 mealId 파라미터를 넣어주세요.

+ 커서 기반 페이지네이션으로 리뷰 리스트를 조회합니다.

+ 페이징 기본 값 = {size=20, sort=date, direction=desc}

+ """) + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "리뷰 리스트 조회 성공"), + @ApiResponse(responseCode = "400", description = "쿼리 파라미터 누락", content = @Content(schema = @Schema(implementation = BaseResponse.class))), + @ApiResponse(responseCode = "404", description = "존재하지 않는 메뉴", content = @Content(schema = @Schema(implementation = BaseResponse.class))), + @ApiResponse(responseCode = "404", description = "존재하지 않는 식단", content = @Content(schema = @Schema(implementation = BaseResponse.class))) + }) + @GetMapping("") + public BaseResponse> getReviews( + @Parameter(description = "타입(변동메뉴(식단)/고정메뉴)") @RequestParam("menuType") MenuType menuType, + @Parameter(description = "menuId(고정메뉴)") @RequestParam(value = "menuId", required = false) Long menuId, + @Parameter(description = "mealId(변동메뉴)") @RequestParam(value = "mealId", required = false) Long mealId, + @Parameter(description = "마지막으로 조회된 reviewId값(첫 조회시 값 필요 없음)", in = ParameterIn.QUERY) + @RequestParam(value = "lastReviewId", required = false) Long lastReviewId, + @ParameterObject @PageableDefault(size = 20, sort = "date", direction = Sort.Direction.DESC) Pageable pageable, + @AuthenticationPrincipal CustomUserDetails customUserDetails) { + SliceResponse myReviews = sliceService.findReviews(menuType, menuId, mealId, + pageable, lastReviewId, customUserDetails); - return BaseResponse.success(myReviews); - } + return BaseResponse.success(myReviews); + } - @Hidden - @Operation(summary = "리뷰 작성", description = """ - 리뷰를 작성하는 API 입니다.

- reviewCreate는 application/json, multipartFileList는 multipart/form-data로 요청해주세요.

- 사진은 여러장 첨부 가능합니다.(기획상으로는 한 장만 첨부하도록 제한이 있지만 API 스펙 자체는 여러 장 첨부 가능) - """) - @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "리뷰 작성 성공"), - @ApiResponse(responseCode = "404", description = "존재하지 않는 메뉴", content = @Content(schema = @Schema(implementation = BaseResponse.class))), - @ApiResponse(responseCode = "404", description = "존재하지 않는 유저", content = @Content(schema = @Schema(implementation = BaseResponse.class))), - @ApiResponse(responseCode = "500", description = "이미지 업로드 실패", content = @Content(schema = @Schema(implementation = BaseResponse.class))) - }) - @PostMapping(value = "/{menuId}", - consumes = {MediaType.APPLICATION_JSON_VALUE, MediaType.MULTIPART_FORM_DATA_VALUE}, - produces = MediaType.APPLICATION_JSON_VALUE) - public BaseResponse writeReview( - @Parameter(description = "menuId") @PathVariable("menuId") Long menuId, - @RequestPart ReviewCreateRequest createReviewRequest, - @RequestPart(value = "multipartFileList", required = false) List multipartFileList, - @AuthenticationPrincipal CustomUserDetails customUserDetails) { - reviewService.createReview(customUserDetails, menuId, createReviewRequest, - multipartFileList); - return BaseResponse.success(); - } + @Hidden + @Operation(summary = "리뷰 작성", description = """ + 리뷰를 작성하는 API 입니다.

+ reviewCreate는 application/json, multipartFileList는 multipart/form-data로 요청해주세요.

+ 사진은 여러장 첨부 가능합니다.(기획상으로는 한 장만 첨부하도록 제한이 있지만 API 스펙 자체는 여러 장 첨부 가능) + """) + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "리뷰 작성 성공"), + @ApiResponse(responseCode = "404", description = "존재하지 않는 메뉴", content = @Content(schema = @Schema(implementation = BaseResponse.class))), + @ApiResponse(responseCode = "404", description = "존재하지 않는 유저", content = @Content(schema = @Schema(implementation = BaseResponse.class))), + @ApiResponse(responseCode = "500", description = "이미지 업로드 실패", content = @Content(schema = @Schema(implementation = BaseResponse.class))) + }) + @PostMapping(value = "/{menuId}", + consumes = {MediaType.APPLICATION_JSON_VALUE, MediaType.MULTIPART_FORM_DATA_VALUE}, + produces = MediaType.APPLICATION_JSON_VALUE) + public BaseResponse writeReview( + @Parameter(description = "menuId") @PathVariable("menuId") Long menuId, + @RequestPart ReviewCreateRequest createReviewRequest, + @RequestPart(value = "multipartFileList", required = false) List multipartFileList, + @AuthenticationPrincipal CustomUserDetails customUserDetails) { + reviewService.createReview(customUserDetails, menuId, createReviewRequest, + multipartFileList); + return BaseResponse.success(); + } - /** - * 리뷰 이미지 업로드 - */ - @Operation(summary = "리뷰 이미지 업로드", description = "리뷰 이미지를 업로드하고 이미지 URL 을 반환합니다.") - @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "이미지 업로드 및 URL 반환 성공"), - @ApiResponse(responseCode = "500", description = "이미지 업로드 실패", content = @Content(schema = @Schema(implementation = BaseResponse.class))) - }) - @PostMapping(value = "/upload/image", - consumes = MediaType.MULTIPART_FORM_DATA_VALUE, - produces = MediaType.APPLICATION_JSON_VALUE) - public BaseResponse uploadReviewImage(@RequestPart(value = "image") MultipartFile image) { - return BaseResponse.success(reviewService.uploadImage(image)); - } + /** + * 리뷰 이미지 업로드 + */ + @Operation(summary = "리뷰 이미지 업로드", description = "리뷰 이미지를 업로드하고 이미지 URL 을 반환합니다.") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "이미지 업로드 및 URL 반환 성공"), + @ApiResponse(responseCode = "500", description = "이미지 업로드 실패", content = @Content(schema = @Schema(implementation = BaseResponse.class))) + }) + @PostMapping(value = "/upload/image", + consumes = MediaType.MULTIPART_FORM_DATA_VALUE, + produces = MediaType.APPLICATION_JSON_VALUE) + public BaseResponse uploadReviewImage(@RequestPart(value = "image") MultipartFile image) { + return BaseResponse.success(reviewService.uploadImage(image)); + } - @Operation(summary = "리뷰 작성", description = "리뷰를 작성하는 API 입니다.") - @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "리뷰 작성 성공"), - @ApiResponse(responseCode = "404", description = "존재하지 않는 메뉴", content = @Content(schema = @Schema(implementation = BaseResponse.class))), - @ApiResponse(responseCode = "404", description = "존재하지 않는 유저", content = @Content(schema = @Schema(implementation = BaseResponse.class))), - }) - @PostMapping("/write/{menuId}") - public BaseResponse writeReview(@Parameter(description = "menuId") @PathVariable("menuId") Long menuId, - @RequestBody UploadReviewRequest uploadReviewRequest, - @AuthenticationPrincipal CustomUserDetails customUserDetails) { - reviewService.uploadReview(customUserDetails, menuId, uploadReviewRequest); - return BaseResponse.success(); - } + @Operation(summary = "리뷰 작성", description = "리뷰를 작성하는 API 입니다.") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "리뷰 작성 성공"), + @ApiResponse(responseCode = "404", description = "존재하지 않는 메뉴", content = @Content(schema = @Schema(implementation = BaseResponse.class))), + @ApiResponse(responseCode = "404", description = "존재하지 않는 유저", content = @Content(schema = @Schema(implementation = BaseResponse.class))), + }) + @PostMapping("/write/{menuId}") + public BaseResponse writeReview(@Parameter(description = "menuId") @PathVariable("menuId") Long menuId, + @RequestBody UploadReviewRequest uploadReviewRequest, + @AuthenticationPrincipal CustomUserDetails customUserDetails) { + reviewService.uploadReview(customUserDetails, menuId, uploadReviewRequest); + return BaseResponse.success(); + } - @Operation(summary = "리뷰 수정(글 수정)", description = """ - 리뷰 내용을 수정하는 API 입니다.

- 글 수정만 가능하며 사진 수정은 지원하지 않습니다. - """) - @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "리뷰 수정 성공"), - @ApiResponse(responseCode = "403", description = "리뷰에 대한 권한이 없음", content = @Content(schema = @Schema(implementation = BaseResponse.class))), - @ApiResponse(responseCode = "404", description = "존재하지 않는 리뷰", content = @Content(schema = @Schema(implementation = BaseResponse.class))), - @ApiResponse(responseCode = "404", description = "존재하지 않는 유저", content = @Content(schema = @Schema(implementation = BaseResponse.class))) - }) - @PatchMapping("/{reviewId}") - public BaseResponse updateReview(@Parameter(description = "reviewId") - @PathVariable("reviewId") Long reviewId, - @RequestBody ReviewUpdateRequest request, - @AuthenticationPrincipal CustomUserDetails customUserDetails) { - reviewService.updateReview(customUserDetails, reviewId, request); - return BaseResponse.success(); - } + @Operation(summary = "리뷰 수정(글 수정)", description = """ + 리뷰 내용을 수정하는 API 입니다.

+ 글 수정만 가능하며 사진 수정은 지원하지 않습니다. + """) + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "리뷰 수정 성공"), + @ApiResponse(responseCode = "403", description = "리뷰에 대한 권한이 없음", content = @Content(schema = @Schema(implementation = BaseResponse.class))), + @ApiResponse(responseCode = "404", description = "존재하지 않는 리뷰", content = @Content(schema = @Schema(implementation = BaseResponse.class))), + @ApiResponse(responseCode = "404", description = "존재하지 않는 유저", content = @Content(schema = @Schema(implementation = BaseResponse.class))) + }) + @PatchMapping("/{reviewId}") + public BaseResponse updateReview(@Parameter(description = "reviewId") + @PathVariable("reviewId") Long reviewId, + @RequestBody ReviewUpdateRequest request, + @AuthenticationPrincipal CustomUserDetails customUserDetails) { + reviewService.updateReview(customUserDetails, reviewId, request); + return BaseResponse.success(); + } - @Operation(summary = "리뷰 삭제", description = "리뷰를 삭제하는 API 입니다.") - @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "리뷰 삭제 성공"), - @ApiResponse(responseCode = "403", description = "리뷰에 대한 권한이 없음", content = @Content(schema = @Schema(implementation = BaseResponse.class))), - @ApiResponse(responseCode = "404", description = "존재하지 않는 리뷰", content = @Content(schema = @Schema(implementation = BaseResponse.class))), - @ApiResponse(responseCode = "404", description = "존재하지 않는 유저", content = @Content(schema = @Schema(implementation = BaseResponse.class))) - }) - @DeleteMapping("/{reviewId}") - public BaseResponse deleteReview( - @Parameter(description = "reviewId") @PathVariable("reviewId") Long reviewId, - @AuthenticationPrincipal CustomUserDetails customUserDetails) { - reviewService.deleteReview(customUserDetails, reviewId); - return BaseResponse.success(); - } + @Operation(summary = "리뷰 삭제", description = "리뷰를 삭제하는 API 입니다.") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "리뷰 삭제 성공"), + @ApiResponse(responseCode = "403", description = "리뷰에 대한 권한이 없음", content = @Content(schema = @Schema(implementation = BaseResponse.class))), + @ApiResponse(responseCode = "404", description = "존재하지 않는 리뷰", content = @Content(schema = @Schema(implementation = BaseResponse.class))), + @ApiResponse(responseCode = "404", description = "존재하지 않는 유저", content = @Content(schema = @Schema(implementation = BaseResponse.class))) + }) + @DeleteMapping("/{reviewId}") + public BaseResponse deleteReview( + @Parameter(description = "reviewId") @PathVariable("reviewId") Long reviewId, + @AuthenticationPrincipal CustomUserDetails customUserDetails) { + reviewService.deleteReview(customUserDetails, reviewId); + return BaseResponse.success(); + } - @Operation(summary = "식단(변동 메뉴) 리뷰 정보 조회(메뉴명, 평점 등등) [인증 토큰 필요 X]", description = """ - 식단 리뷰 정보를 조회하는 API 입니다.

- 메뉴명 리스트, 리뷰 수, 메인 평점, 양 평점, 맛 평점, 각 평점의 개수를 조회합니다. - """) - @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "리뷰 정보 조회 성공"), - @ApiResponse(responseCode = "400", description = "쿼리 파라미터 누락", content = @Content(schema = - @Schema(implementation = BaseResponse.class))), - @ApiResponse(responseCode = "404", description = "존재하지 않는 식단", content = @Content(schema = - @Schema(implementation = BaseResponse.class))) - }) - @GetMapping("/meals/{mealId}") - public BaseResponse getMealReviews( - @Parameter(description = "mealId") - @PathVariable(value = "mealId") Long mealId) { - return BaseResponse.success(reviewService.findMealReviews(mealId)); - } + @Operation(summary = "식단(변동 메뉴) 리뷰 정보 조회(메뉴명, 평점 등등) [인증 토큰 필요 X]", description = """ + 식단 리뷰 정보를 조회하는 API 입니다.

+ 메뉴명 리스트, 리뷰 수, 메인 평점, 양 평점, 맛 평점, 각 평점의 개수를 조회합니다. + """) + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "리뷰 정보 조회 성공"), + @ApiResponse(responseCode = "400", description = "쿼리 파라미터 누락", content = @Content(schema = + @Schema(implementation = BaseResponse.class))), + @ApiResponse(responseCode = "404", description = "존재하지 않는 식단", content = @Content(schema = + @Schema(implementation = BaseResponse.class))) + }) + @GetMapping("/meals/{mealId}") + public BaseResponse getMealReviews( + @Parameter(description = "mealId") + @PathVariable(value = "mealId") Long mealId) { + return BaseResponse.success(reviewService.findMealReviews(mealId)); + } - @Operation(summary = "고정 메뉴 리뷰 정보 조회(메뉴명, 평점 등등) [인증 토큰 필요 X]", description = """ - 고정 메뉴 리뷰 정보를 조회하는 API 입니다.

- 메뉴명, 리뷰 수, 메인 평점, 양 평점, 맛 평점, 각 평점의 개수를 조회합니다. - """) - @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "리뷰 정보 조회 성공"), - @ApiResponse(responseCode = "400", description = "쿼리 파라미터 누락", content = @Content(schema = - @Schema(implementation = BaseResponse.class))), - @ApiResponse(responseCode = "404", description = "존재하지 않는 메뉴", content = @Content(schema = - @Schema(implementation = BaseResponse.class))) - }) - @GetMapping("/menus/{menuId}") - public BaseResponse getMainReviews( - @Parameter(description = "menuId") - @PathVariable(value = "menuId") Long menuId) { - return BaseResponse.success(reviewService.findMenuReviews(menuId)); - } + @Operation(summary = "고정 메뉴 리뷰 정보 조회(메뉴명, 평점 등등) [인증 토큰 필요 X]", description = """ + 고정 메뉴 리뷰 정보를 조회하는 API 입니다.

+ 메뉴명, 리뷰 수, 메인 평점, 양 평점, 맛 평점, 각 평점의 개수를 조회합니다. + """) + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "리뷰 정보 조회 성공"), + @ApiResponse(responseCode = "400", description = "쿼리 파라미터 누락", content = @Content(schema = + @Schema(implementation = BaseResponse.class))), + @ApiResponse(responseCode = "404", description = "존재하지 않는 메뉴", content = @Content(schema = + @Schema(implementation = BaseResponse.class))) + }) + @GetMapping("/menus/{menuId}") + public BaseResponse getMainReviews( + @Parameter(description = "menuId") + @PathVariable(value = "menuId") Long menuId) { + return BaseResponse.success(reviewService.findMenuReviews(menuId)); + } } diff --git a/src/main/java/ssu/eatssu/domain/review/presentation/ReviewControllerV2.java b/src/main/java/ssu/eatssu/domain/review/presentation/ReviewControllerV2.java index da44bb5f..4efe696e 100644 --- a/src/main/java/ssu/eatssu/domain/review/presentation/ReviewControllerV2.java +++ b/src/main/java/ssu/eatssu/domain/review/presentation/ReviewControllerV2.java @@ -1,8 +1,19 @@ package ssu.eatssu.domain.review.presentation; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.enums.ParameterIn; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import org.springdoc.core.annotations.ParameterObject; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Sort; +import org.springframework.data.web.PageableDefault; import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; @@ -13,28 +24,20 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; - -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.Parameter; -import io.swagger.v3.oas.annotations.enums.ParameterIn; -import io.swagger.v3.oas.annotations.media.Content; -import io.swagger.v3.oas.annotations.media.Schema; -import io.swagger.v3.oas.annotations.responses.ApiResponse; -import io.swagger.v3.oas.annotations.responses.ApiResponses; -import io.swagger.v3.oas.annotations.tags.Tag; -import lombok.RequiredArgsConstructor; import ssu.eatssu.domain.auth.security.CustomUserDetails; import ssu.eatssu.domain.restaurant.entity.Restaurant; import ssu.eatssu.domain.review.dto.CreateMealReviewRequest; +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.MenuReviewResponse; import ssu.eatssu.domain.review.dto.MenuReviewsV2Response; import ssu.eatssu.domain.review.dto.RestaurantReviewResponse; +import ssu.eatssu.domain.review.dto.ReviewDetail; import ssu.eatssu.domain.review.dto.UpdateMealReviewRequest; +import ssu.eatssu.domain.review.dto.ValidMenuForViewResponse; import ssu.eatssu.domain.review.service.ReviewServiceV2; -import ssu.eatssu.domain.review.service.ReviewService; import ssu.eatssu.domain.slice.dto.SliceResponse; +import ssu.eatssu.domain.user.dto.MyMealReviewResponse; import ssu.eatssu.global.handler.response.BaseResponse; @RestController @@ -42,149 +45,200 @@ @RequestMapping("/v2/reviews") @Tag(name = "Review V2", description = "리뷰 V2 API") public class ReviewControllerV2 { - private final ReviewServiceV2 reviewServiceV2; - - private final ReviewService reviewService; - - @Operation(summary = "리뷰 작성", description = "리뷰를 작성하는 API 입니다.") - @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "리뷰 작성 성공"), - @ApiResponse(responseCode = "404", description = "존재하지 않는 식단", content = @Content(schema = @Schema(implementation = BaseResponse.class))), - @ApiResponse(responseCode = "404", description = "존재하지 않는 메뉴", content = @Content(schema = @Schema(implementation = BaseResponse.class))), - @ApiResponse(responseCode = "404", description = "존재하지 않는 유저", content = @Content(schema = @Schema(implementation = BaseResponse.class))), - }) - @PostMapping() - public BaseResponse createReview( - @RequestBody CreateMealReviewRequest createMealReviewRequest, - @AuthenticationPrincipal CustomUserDetails customUserDetails) { - reviewServiceV2.createReview(customUserDetails, createMealReviewRequest); - return BaseResponse.success(); - } - - @Operation(summary = "특정 식당 모든 리뷰 정보 조회(리뷰 개수, 평점 등등)", description = """ - 특정 식당 모든 리뷰 정보를 조회하는 API 입니다.

- 리뷰 개수, 별점 별 개수, 리뷰 평점, 좋아요 개수, 싫어요 개수를 조회합니다. - """) - @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "리뷰 정보 조회 성공"), - @ApiResponse(responseCode = "400", description = "path parameter 누락", content = @Content(schema = - @Schema(implementation = BaseResponse.class))) - }) - @GetMapping("/statistics") - public BaseResponse getRestaurantReviews( - @Parameter(description = "restaurant") - @RequestParam Restaurant restaurant - ) { - return BaseResponse.success(reviewServiceV2.findRestaurantReviews(restaurant)); - } - - @Operation(summary = "리뷰 리스트 조회", description = """ - 리뷰 리스트를 조회하는 API 입니다.

- 커서 기반 페이지네이션으로 리뷰 리스트를 조회합니다.

- 페이징 기본 값 = {size=20, sort=date, direction=desc}

- """) - @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "리뷰 리스트 조회 성공"), - @ApiResponse(responseCode = "400", description = "쿼리 파라미터 누락", content = @Content(schema = @Schema(implementation = BaseResponse.class))) - }) - @GetMapping("") - public BaseResponse> getReviews( - @Parameter(description = "mealId") - @RequestParam Long mealId, - @Parameter(description = "마지막으로 조회된 reviewId값(첫 조회시 값 필요 없음)", in = ParameterIn.QUERY) - @RequestParam(value = "lastReviewId", required = false) Long lastReviewId, - @AuthenticationPrincipal CustomUserDetails customUserDetails) { - Pageable pageable = PageRequest.of(0, 20, Sort.by(Sort.Direction.DESC, "id")); - SliceResponse myReviews = reviewServiceV2.findReviews(mealId, lastReviewId, pageable, - customUserDetails); - return BaseResponse.success(myReviews); - } - - @Operation(summary = "리뷰 수정(글 수정)", description = """ - 리뷰 내용을 수정하는 API 입니다.

- 글 수정만 가능하며 사진 수정은 지원하지 않습니다. - """) - @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "리뷰 수정 성공"), - @ApiResponse(responseCode = "403", description = "리뷰에 대한 권한이 없음", content = @Content(schema = @Schema(implementation = BaseResponse.class))), - @ApiResponse(responseCode = "404", description = "존재하지 않는 리뷰", content = @Content(schema = @Schema(implementation = BaseResponse.class))), - @ApiResponse(responseCode = "404", description = "존재하지 않는 유저", content = @Content(schema = - @Schema(implementation = BaseResponse.class))), - @ApiResponse(responseCode = "404", description = "존재하지 않는 메뉴", content = @Content(schema = - @Schema(implementation = BaseResponse.class))) - }) - @PatchMapping("/{reviewId}") - public BaseResponse updateReview(@Parameter(description = "reviewId") - @PathVariable("reviewId") Long reviewId, - @RequestBody UpdateMealReviewRequest request, - @AuthenticationPrincipal CustomUserDetails customUserDetails) { - reviewServiceV2.updateReview(customUserDetails, reviewId, request); - return BaseResponse.success(); - } - - @Operation(summary = "리뷰 삭제", description = "리뷰를 삭제하는 API 입니다.") - @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "리뷰 삭제 성공"), - @ApiResponse(responseCode = "403", description = "리뷰에 대한 권한이 없음", content = @Content(schema = @Schema(implementation = BaseResponse.class))), - @ApiResponse(responseCode = "404", description = "존재하지 않는 리뷰", content = @Content(schema = @Schema(implementation = BaseResponse.class))), - @ApiResponse(responseCode = "404", description = "존재하지 않는 유저", content = @Content(schema = @Schema(implementation = BaseResponse.class))) - }) - @DeleteMapping("/{reviewId}") - public BaseResponse deleteReview( - @Parameter(description = "reviewId") @PathVariable("reviewId") Long reviewId, - @AuthenticationPrincipal CustomUserDetails customUserDetails) { - reviewServiceV2.deleteReview(customUserDetails, reviewId); - return BaseResponse.success(); - } - - @Operation(summary = "리뷰 좋아요 누르기/취소하기", description = "리뷰에 좋아요(찜)를 누르거나 취소하는 API 입니다.") - @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "리뷰 좋아요 토글 성공"), - @ApiResponse(responseCode = "404", description = "존재하지 않는 리뷰", content = @Content(schema = @Schema(implementation = BaseResponse.class))), - @ApiResponse(responseCode = "404", description = "존재하지 않는 유저", content = @Content(schema = @Schema(implementation = BaseResponse.class))) - }) - @PostMapping("/{reviewId}/like") - public BaseResponse toggleReviewLike( - @Parameter(description = "reviewId") @PathVariable("reviewId") Long reviewId, - @AuthenticationPrincipal CustomUserDetails customUserDetails) { - reviewServiceV2.toggleReviewLike(customUserDetails, reviewId); - return BaseResponse.success(); - } - - @Operation(summary = "식단(변동 메뉴) 리뷰 정보 조회 V2(메뉴명, 평점 등등) [인증 토큰 필요 X]", description = """ - 식단 리뷰 정보를 조회하는 API 입니다.

- 메뉴명 리스트, 리뷰 수, 메인 평점, 좋아요 개수, 싫어요 개수, 각 평점의 개수를 조회합니다. - """) - @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "리뷰 정보 조회 성공"), - @ApiResponse(responseCode = "400", description = "쿼리 파라미터 누락", content = @Content(schema = - @Schema(implementation = BaseResponse.class))), - @ApiResponse(responseCode = "404", description = "존재하지 않는 식단", content = @Content(schema = - @Schema(implementation = BaseResponse.class))) - }) - @GetMapping("/meals/{mealId}") - public BaseResponse getMealReviews( - @Parameter(description = "mealId") - @PathVariable(value = "mealId") Long mealId) { - return BaseResponse.success(reviewServiceV2.findMealReviews(mealId)); - } - - @Operation(summary = "고정 메뉴 리뷰 정보 조회 V2(메뉴명, 평점 등등) [인증 토큰 필요 X]", description = """ - 고정 메뉴 리뷰 정보를 조회하는 API 입니다.

- 메뉴명, 리뷰 수, 메인 평점, 좋아요 개수, 싫어요 개수, 각 평점의 개수를 조회합니다. - """) - @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "리뷰 정보 조회 성공"), - @ApiResponse(responseCode = "400", description = "쿼리 파라미터 누락", content = @Content(schema = - @Schema(implementation = BaseResponse.class))), - @ApiResponse(responseCode = "404", description = "존재하지 않는 메뉴", content = @Content(schema = - @Schema(implementation = BaseResponse.class))) - }) - @GetMapping("/menus/{menuId}") - public BaseResponse getMainReviews( - @Parameter(description = "menuId") - @PathVariable(value = "menuId") Long menuId) { - return BaseResponse.success(reviewServiceV2.findMenuReviews(menuId)); - } + private final ReviewServiceV2 reviewServiceV2; + + @Operation(summary = "meal(식단)에 대한 리뷰 작성", description = "리뷰를 작성하는 API 입니다.") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "리뷰 작성 성공"), + @ApiResponse(responseCode = "404", description = "존재하지 않는 식단", content = @Content(schema = @Schema(implementation = BaseResponse.class))), + @ApiResponse(responseCode = "404", description = "존재하지 않는 메뉴", content = @Content(schema = @Schema(implementation = BaseResponse.class))), + @ApiResponse(responseCode = "404", description = "존재하지 않는 유저", content = @Content(schema = @Schema(implementation = BaseResponse.class))), + }) + @PostMapping("/meal") + public BaseResponse createMealReview( + @RequestBody CreateMealReviewRequest createMealReviewRequest, + @AuthenticationPrincipal CustomUserDetails customUserDetails) { + reviewServiceV2.createMealReview(customUserDetails, createMealReviewRequest); + return BaseResponse.success(); + } + + @Operation(summary = "특정 식당 모든 리뷰 정보 조회(리뷰 개수, 평점 등등)", description = """ + 특정 식당 모든 리뷰 정보를 조회하는 API 입니다.

+ 리뷰 개수, 별점 별 개수, 리뷰 평점, 좋아요 개수, 싫어요 개수를 조회합니다. + """) + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "리뷰 정보 조회 성공"), + @ApiResponse(responseCode = "400", description = "path parameter 누락", content = @Content(schema = + @Schema(implementation = BaseResponse.class))) + }) + @GetMapping("/statistics/restaurant") + public BaseResponse getRestaurantReviews( + @Parameter(description = "restaurant") + @RequestParam Restaurant restaurant + ) { + return BaseResponse.success(reviewServiceV2.findRestaurantReviews(restaurant)); + } + + @Operation(summary = "meal(식단)에 대한 리뷰 리스트 조회", description = """ + 리뷰 리스트를 조회하는 API 입니다.

+ 커서 기반 페이지네이션으로 리뷰 리스트를 조회합니다.

+ 페이징 기본 값 = {size=20, sort=date, direction=desc}

+ """) + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "리뷰 리스트 조회 성공"), + @ApiResponse(responseCode = "400", description = "쿼리 파라미터 누락", content = @Content(schema = @Schema(implementation = BaseResponse.class))) + }) + @GetMapping("/list/meal") + public BaseResponse> getMealReviewList( + @Parameter(description = "mealId") + @RequestParam Long mealId, + @Parameter(description = "마지막으로 조회된 reviewId값(첫 조회시 값 필요 없음)", in = ParameterIn.QUERY) + @RequestParam(value = "lastReviewId", required = false) Long lastReviewId, + @AuthenticationPrincipal CustomUserDetails customUserDetails) { + Pageable pageable = PageRequest.of(0, 20, Sort.by(Sort.Direction.DESC, "id")); + SliceResponse myReviews = reviewServiceV2.findMealReviewList(mealId, lastReviewId, pageable, + customUserDetails); + return BaseResponse.success(myReviews); + } + + @Operation(summary = "리뷰 수정(글 수정)", description = """ + 리뷰 내용을 수정하는 API 입니다.

+ 글 수정만 가능하며 사진 수정은 지원하지 않습니다. + """) + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "리뷰 수정 성공"), + @ApiResponse(responseCode = "403", description = "리뷰에 대한 권한이 없음", content = @Content(schema = @Schema(implementation = BaseResponse.class))), + @ApiResponse(responseCode = "404", description = "존재하지 않는 리뷰", content = @Content(schema = @Schema(implementation = BaseResponse.class))), + @ApiResponse(responseCode = "404", description = "존재하지 않는 유저", content = @Content(schema = + @Schema(implementation = BaseResponse.class))), + @ApiResponse(responseCode = "404", description = "존재하지 않는 메뉴", content = @Content(schema = + @Schema(implementation = BaseResponse.class))) + }) + @PatchMapping("/{reviewId}") + public BaseResponse updateReview(@Parameter(description = "reviewId") + @PathVariable("reviewId") Long reviewId, + @RequestBody UpdateMealReviewRequest request, + @AuthenticationPrincipal CustomUserDetails customUserDetails) { + reviewServiceV2.updateReview(customUserDetails, reviewId, request); + return BaseResponse.success(); + } + + @Operation(summary = "리뷰 삭제", description = "리뷰를 삭제하는 API 입니다.") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "리뷰 삭제 성공"), + @ApiResponse(responseCode = "403", description = "리뷰에 대한 권한이 없음", content = @Content(schema = @Schema(implementation = BaseResponse.class))), + @ApiResponse(responseCode = "404", description = "존재하지 않는 리뷰", content = @Content(schema = @Schema(implementation = BaseResponse.class))), + @ApiResponse(responseCode = "404", description = "존재하지 않는 유저", content = @Content(schema = @Schema(implementation = BaseResponse.class))) + }) + @DeleteMapping("/{reviewId}") + public BaseResponse deleteReview( + @Parameter(description = "reviewId") @PathVariable("reviewId") Long reviewId, + @AuthenticationPrincipal CustomUserDetails customUserDetails) { + reviewServiceV2.deleteReview(customUserDetails, reviewId); + return BaseResponse.success(); + } + + @Operation(summary = "식단(변동 메뉴) 리뷰 정보 조회 V2(메뉴명, 평점 등등) [인증 토큰 필요 X]", description = """ + 식단 리뷰 정보를 조회하는 API 입니다.

+ 메뉴명 리스트, 리뷰 수, 메인 평점, 좋아요 개수, 싫어요 개수, 각 평점의 개수를 조회합니다. + """) + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "리뷰 정보 조회 성공"), + @ApiResponse(responseCode = "400", description = "쿼리 파라미터 누락", content = @Content(schema = + @Schema(implementation = BaseResponse.class))), + @ApiResponse(responseCode = "404", description = "존재하지 않는 식단", content = @Content(schema = + @Schema(implementation = BaseResponse.class))) + }) + @GetMapping("/statistics/meals/{mealId}") + public BaseResponse getMealReviews( + @Parameter(description = "mealId") + @PathVariable(value = "mealId") Long mealId) { + return BaseResponse.success(reviewServiceV2.findMealReviews(mealId)); + } + + @Operation(summary = "고정 메뉴 리뷰 정보 조회 V2(메뉴명, 평점 등등) [인증 토큰 필요 X]", description = """ + 고정 메뉴 리뷰 정보를 조회하는 API 입니다.

+ 메뉴명, 리뷰 수, 메인 평점, 좋아요 개수, 싫어요 개수, 각 평점의 개수를 조회합니다. + """) + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "리뷰 정보 조회 성공"), + @ApiResponse(responseCode = "400", description = "쿼리 파라미터 누락", content = @Content(schema = + @Schema(implementation = BaseResponse.class))), + @ApiResponse(responseCode = "404", description = "존재하지 않는 메뉴", content = @Content(schema = + @Schema(implementation = BaseResponse.class))) + }) + @GetMapping("/statistics/menus/{menuId}") + public BaseResponse getMainReviews( + @Parameter(description = "menuId") + @PathVariable(value = "menuId") Long menuId) { + return BaseResponse.success(reviewServiceV2.findMenuReviews(menuId)); + } + + @Operation(summary = "menu 에 대한 리뷰 리스트 조회", description = """ + 리뷰 리스트를 조회하는 API 입니다.

+ 커서 기반 페이지네이션으로 리뷰 리스트를 조회합니다.

+ 페이징 기본 값 = {size=20, sort=date, direction=desc}

+ """) + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "리뷰 리스트 조회 성공"), + @ApiResponse(responseCode = "400", description = "쿼리 파라미터 누락", content = @Content(schema = @Schema(implementation = BaseResponse.class))) + }) + @GetMapping("/list/menu") + public BaseResponse> getMenuReviewList( + @Parameter(description = "menuId(고정메뉴)") @RequestParam(value = "menuId") Long menuId, + @Parameter(description = "마지막으로 조회된 reviewId값(첫 조회시 값 필요 없음)", in = ParameterIn.QUERY) + @RequestParam(value = "lastReviewId", required = false) Long lastReviewId, + @ParameterObject @PageableDefault(size = 20, sort = "date", direction = Sort.Direction.DESC) Pageable pageable, + @AuthenticationPrincipal CustomUserDetails customUserDetails) { + SliceResponse myReviews = reviewServiceV2.findMenuReviewList(menuId, + pageable, + lastReviewId, + customUserDetails); + + return BaseResponse.success(myReviews); + } + + @Operation(summary = "menu에 대한 리뷰 작성", description = "리뷰를 작성하는 API 입니다.") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "리뷰 작성 성공"), + @ApiResponse(responseCode = "404", description = "존재하지 않는 메뉴", content = @Content(schema = @Schema(implementation = BaseResponse.class))), + @ApiResponse(responseCode = "404", description = "존재하지 않는 유저", content = @Content(schema = @Schema(implementation = BaseResponse.class))), + }) + @PostMapping("/menu") + public BaseResponse createMenuReview(@RequestBody CreateMenuReviewRequest createMenuReviewRequest, + @AuthenticationPrincipal CustomUserDetails customUserDetails) { + reviewServiceV2.createMenuReview(customUserDetails, createMenuReviewRequest); + return BaseResponse.success(); + } + + @Operation(summary = "내가 쓴 리뷰 리스트 조회", description = "내가 쓴 리뷰 리스트를 조회하는 API V2 입니다.") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "내가 쓴 리뷰 리스트 조회 성공"), + @ApiResponse(responseCode = "404", description = "존재하지 않는 유저", content = @Content(schema = @Schema(implementation = BaseResponse.class))) + }) + @GetMapping("/my") + public BaseResponse> getMyReviews( + @Parameter(description = "마지막으로 조회된 reviewId값(첫 조회시 값 필요 없음)", in = ParameterIn.QUERY) @RequestParam(required = false) Long lastReviewId, + @ParameterObject @PageableDefault(size = 20, sort = "date", direction = Sort.Direction.DESC) Pageable pageable, + @AuthenticationPrincipal CustomUserDetails customUserDetails) { + SliceResponse myReviews = reviewServiceV2.findMyReviews(customUserDetails, + lastReviewId, + pageable); + return BaseResponse.success(myReviews); + } + + @Operation(summary = "식단 id를 통해 리뷰 작성할 수 있는 메뉴들 조회", description = "리뷰 작성할 수 있는 메뉴들 조회하는 API입니다. (노션 문서 > 리뷰v2 기능명세서> 리뷰에 제외되는 메뉴 참고") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "리뷰 작성할 수 있는 메뉴들 조회 성공"), + @ApiResponse(responseCode = "404", description = "존재하지 않는 메뉴", content = @Content(schema = @Schema(implementation = BaseResponse.class))) + }) + @GetMapping("/meal/valid-for-review/{mealId}") + public BaseResponse getValidMenuForReview( + @Parameter(description = "mealId") + @PathVariable("mealId") Long mealId) { + ValidMenuForViewResponse validMenuForViewResponse = reviewServiceV2.validMenuForReview(mealId); + return BaseResponse.success(validMenuForViewResponse); + } + } diff --git a/src/main/java/ssu/eatssu/domain/review/repository/ReviewImageRepository.java b/src/main/java/ssu/eatssu/domain/review/repository/ReviewImageRepository.java index 0395890b..b608010f 100644 --- a/src/main/java/ssu/eatssu/domain/review/repository/ReviewImageRepository.java +++ b/src/main/java/ssu/eatssu/domain/review/repository/ReviewImageRepository.java @@ -1,7 +1,6 @@ package ssu.eatssu.domain.review.repository; import org.springframework.data.jpa.repository.JpaRepository; - import ssu.eatssu.domain.review.entity.ReviewImage; public interface ReviewImageRepository extends JpaRepository { diff --git a/src/main/java/ssu/eatssu/domain/review/repository/ReviewLikeRepository.java b/src/main/java/ssu/eatssu/domain/review/repository/ReviewLikeRepository.java deleted file mode 100644 index fcba8185..00000000 --- a/src/main/java/ssu/eatssu/domain/review/repository/ReviewLikeRepository.java +++ /dev/null @@ -1,13 +0,0 @@ -package ssu.eatssu.domain.review.repository; - -import java.util.Optional; - -import org.springframework.data.jpa.repository.JpaRepository; - -import ssu.eatssu.domain.review.entity.Review; -import ssu.eatssu.domain.review.entity.ReviewLike; -import ssu.eatssu.domain.user.entity.User; - -public interface ReviewLikeRepository extends JpaRepository { - Optional findByReviewAndUser(Review review, User user); -} diff --git a/src/main/java/ssu/eatssu/domain/review/repository/ReviewMenuLikeRepository.java b/src/main/java/ssu/eatssu/domain/review/repository/ReviewMenuLikeRepository.java index 545240e0..28a98bb7 100644 --- a/src/main/java/ssu/eatssu/domain/review/repository/ReviewMenuLikeRepository.java +++ b/src/main/java/ssu/eatssu/domain/review/repository/ReviewMenuLikeRepository.java @@ -1,12 +1,11 @@ package ssu.eatssu.domain.review.repository; -import java.util.List; - import org.springframework.data.jpa.repository.JpaRepository; - import ssu.eatssu.domain.review.entity.Review; import ssu.eatssu.domain.review.entity.ReviewMenuLike; +import java.util.List; + public interface ReviewMenuLikeRepository extends JpaRepository { - List findByReview(Review review); -} + List findByReview(Review review); +} \ No newline at end of file diff --git a/src/main/java/ssu/eatssu/domain/review/repository/ReviewRepository.java b/src/main/java/ssu/eatssu/domain/review/repository/ReviewRepository.java index 21aceef7..d817cfcb 100644 --- a/src/main/java/ssu/eatssu/domain/review/repository/ReviewRepository.java +++ b/src/main/java/ssu/eatssu/domain/review/repository/ReviewRepository.java @@ -1,35 +1,34 @@ package ssu.eatssu.domain.review.repository; -import java.util.Collection; -import java.util.List; - import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; - import ssu.eatssu.domain.menu.entity.Meal; import ssu.eatssu.domain.menu.entity.Menu; import ssu.eatssu.domain.review.entity.Review; +import java.util.Collection; +import java.util.List; + public interface ReviewRepository extends JpaRepository, ReviewRepositoryCustom { - List findAllByMenu(Menu menu); + List findAllByMenu(Menu menu); - List findAllByMeal(Meal meal); + List findAllByMeal(Meal meal); - /** - * dto projection - */ - Collection findByMenu_MealMenus_Meal(Meal meal, Class type); + /** + * dto projection + */ + Collection findByMenu_MealMenus_Meal(Meal meal, Class type); - Long countByMenu_MealMenus_Meal(Meal meal); + Long countByMenu_MealMenus_Meal(Meal meal); - List findByMealIn(List meals); + List findByMealIn(List meals); - @Query("SELECT r FROM Review r WHERE r.meal.id IN :mealIds AND (:lastReviewId IS NULL OR r.id < :lastReviewId) ") - Page findReviewsByMealIds(@Param("mealIds") List mealIds, - @Param("lastReviewId") Long lastReviewId, - Pageable pageable); + @Query("SELECT r FROM Review r WHERE r.meal.id IN :mealIds AND (:lastReviewId IS NULL OR r.id < :lastReviewId) ") + Page findReviewsByMealIds(@Param("mealIds") List mealIds, + @Param("lastReviewId") Long lastReviewId, + Pageable pageable); } diff --git a/src/main/java/ssu/eatssu/domain/review/repository/ReviewRepositoryCustom.java b/src/main/java/ssu/eatssu/domain/review/repository/ReviewRepositoryCustom.java index 96659076..8cbc059f 100644 --- a/src/main/java/ssu/eatssu/domain/review/repository/ReviewRepositoryCustom.java +++ b/src/main/java/ssu/eatssu/domain/review/repository/ReviewRepositoryCustom.java @@ -2,7 +2,6 @@ import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Slice; - import ssu.eatssu.domain.menu.entity.Meal; import ssu.eatssu.domain.menu.entity.Menu; import ssu.eatssu.domain.review.entity.Review; @@ -10,19 +9,19 @@ public interface ReviewRepositoryCustom { - //특정 메뉴 리뷰 - 최신순 정렬 - Slice findAllByMenuOrderByIdDesc(Menu menu, Long lastReviewId, Pageable pageable); + //특정 메뉴 리뷰 - 최신순 정렬 + Slice findAllByMenuOrderByIdDesc(Menu menu, Long lastReviewId, Pageable pageable); - //특정 메뉴 리뷰 - 오래된순 정렬 - //특정 식단 리뷰 - 최신순 정렬 - Slice findAllByMealOrderByIdDesc(Meal meal, Long lastReviewId, Pageable pageable); + //특정 메뉴 리뷰 - 오래된순 정렬 + //특정 식단 리뷰 - 최신순 정렬 + Slice findAllByMealOrderByIdDesc(Meal meal, Long lastReviewId, Pageable pageable); - //특정 메뉴 리뷰 - 오래된순 정렬 - //특정 유저 리뷰 - 최신순 정렬 - Slice findByUserOrderByIdDesc(User user, Long lastReviewId, Pageable pageable); + //특정 메뉴 리뷰 - 오래된순 정렬 + //특정 유저 리뷰 - 최신순 정렬 + Slice findByUserOrderByIdDesc(User user, Long lastReviewId, Pageable pageable); - Slice findAllByMenuOrderByIdAsc(Menu menu, Long lastReviewId, Pageable pageable); + Slice findAllByMenuOrderByIdAsc(Menu menu, Long lastReviewId, Pageable pageable); - Slice findAllByMealOrderByIdAsc(Meal meal, Long lastReviewId, Pageable pageable); + Slice findAllByMealOrderByIdAsc(Meal meal, Long lastReviewId, Pageable pageable); } diff --git a/src/main/java/ssu/eatssu/domain/review/repository/ReviewRepositoryImpl.java b/src/main/java/ssu/eatssu/domain/review/repository/ReviewRepositoryImpl.java index 73339ad9..5f754ebc 100644 --- a/src/main/java/ssu/eatssu/domain/review/repository/ReviewRepositoryImpl.java +++ b/src/main/java/ssu/eatssu/domain/review/repository/ReviewRepositoryImpl.java @@ -1,124 +1,122 @@ package ssu.eatssu.domain.review.repository; -import java.util.ArrayList; -import java.util.List; - +import com.querydsl.core.types.dsl.BooleanExpression; +import com.querydsl.jpa.impl.JPAQueryFactory; +import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Slice; import org.springframework.data.domain.SliceImpl; import org.springframework.stereotype.Repository; - -import com.querydsl.core.types.dsl.BooleanExpression; -import com.querydsl.jpa.impl.JPAQueryFactory; - -import lombok.RequiredArgsConstructor; import ssu.eatssu.domain.menu.entity.Meal; import ssu.eatssu.domain.menu.entity.Menu; import ssu.eatssu.domain.review.entity.QReview; import ssu.eatssu.domain.review.entity.Review; import ssu.eatssu.domain.user.entity.User; +import java.util.ArrayList; +import java.util.List; + @Repository @RequiredArgsConstructor public class ReviewRepositoryImpl implements ReviewRepositoryCustom { - private final JPAQueryFactory queryFactory; - private final QReview review = QReview.review; - - @Override - public Slice findAllByMenuOrderByIdDesc(Menu menu, Long lastReviewId, Pageable pageable) { - List reviewList = queryFactory.selectFrom(review) - .where( - nextFromLastReviewId(lastReviewId), - review.menu.eq(menu) - ) - .orderBy(review.id.desc()) - .limit(pageable.getPageSize() + 1L) - .fetch(); - return checkLastPage(pageable, reviewList); - } - - @Override - public Slice findAllByMenuOrderByIdAsc(Menu menu, Long lastReviewId, Pageable pageable) { - List reviewList = queryFactory.selectFrom(review) - .where( - beforeFromLastReviewId(lastReviewId), - review.menu.eq(menu) - ) - .orderBy(review.id.asc()) - .limit(pageable.getPageSize() + 1L) - .fetch(); - return checkLastPage(pageable, reviewList); - } - - @Override - public Slice findAllByMealOrderByIdDesc(Meal meal, Long lastReviewId, Pageable pageable) { - List menuList = new ArrayList<>(); - meal.getMealMenus().forEach(mealMenu -> menuList.add(mealMenu.getMenu())); - List reviewList = queryFactory.selectFrom(review) - .where( - nextFromLastReviewId(lastReviewId), - review.menu.in(menuList) - ) - .orderBy(review.id.desc()) - .limit(pageable.getPageSize() + 1L) - .fetch(); - return checkLastPage(pageable, reviewList); - } - - @Override - public Slice findAllByMealOrderByIdAsc(Meal meal, Long lastReviewId, Pageable pageable) { - List menuList = new ArrayList<>(); - meal.getMealMenus().forEach(mealMenu -> menuList.add(mealMenu.getMenu())); - List reviewList = queryFactory.selectFrom(review) - .where( - beforeFromLastReviewId(lastReviewId), - review.menu.in(menuList) - ) - .orderBy(review.id.asc()) - .limit(pageable.getPageSize() + 1L) - .fetch(); - return checkLastPage(pageable, reviewList); - } - - @Override - public Slice findByUserOrderByIdDesc(User user, Long lastReviewId, Pageable pageable) { - List reviewList = queryFactory.selectFrom(review) - .where( - nextFromLastReviewId(lastReviewId), - review.user.eq(user) - ) - .orderBy(review.id.desc()) - .limit(pageable.getPageSize() + 1L) - .fetch(); - return checkLastPage(pageable, reviewList); - } - - private BooleanExpression nextFromLastReviewId(Long reviewId) { - if (reviewId == null) { - return null; - } - return review.id.lt(reviewId); //reviewId < lastReviewId - } - - private BooleanExpression beforeFromLastReviewId(Long reviewId) { - if (reviewId == null) { - return null; - } - return review.id.gt(reviewId); //reviewId > lastReviewId - } - - //무한 스크롤 방식 처리하는 메소드 - private Slice checkLastPage(Pageable pageable, List results) { - - boolean hasNext = false; - - // 조회한 결과 개수가 요청한 페이지 사이즈보다 크면 뒤에 더 있음, next = true - if (results.size() > pageable.getPageSize()) { - hasNext = true; - results.remove(pageable.getPageSize()); - } - - return new SliceImpl<>(results, pageable, hasNext); - } + private final JPAQueryFactory queryFactory; + private final QReview review = QReview.review; + + @Override + public Slice findAllByMenuOrderByIdDesc(Menu menu, Long lastReviewId, Pageable pageable) { + List reviewList = queryFactory.selectFrom(review) + .where( + nextFromLastReviewId(lastReviewId), + review.menu.eq(menu) + ) + .orderBy(review.id.desc()) + .limit(pageable.getPageSize() + 1L) + .fetch(); + return checkLastPage(pageable, reviewList); + } + + @Override + public Slice findAllByMenuOrderByIdAsc(Menu menu, Long lastReviewId, Pageable pageable) { + List reviewList = queryFactory.selectFrom(review) + .where( + beforeFromLastReviewId(lastReviewId), + review.menu.eq(menu) + ) + .orderBy(review.id.asc()) + .limit(pageable.getPageSize() + 1L) + .fetch(); + return checkLastPage(pageable, reviewList); + } + + @Override + public Slice findAllByMealOrderByIdDesc(Meal meal, Long lastReviewId, Pageable pageable) { + List menuList = new ArrayList<>(); + meal.getMealMenus().forEach(mealMenu -> menuList.add(mealMenu.getMenu())); + List reviewList = queryFactory.selectFrom(review) + .where( + nextFromLastReviewId(lastReviewId), + review.menu.in(menuList) + ) + .orderBy(review.id.desc()) + .limit(pageable.getPageSize() + 1L) + .fetch(); + return checkLastPage(pageable, reviewList); + } + + @Override + public Slice findAllByMealOrderByIdAsc(Meal meal, Long lastReviewId, Pageable pageable) { + List menuList = new ArrayList<>(); + meal.getMealMenus().forEach(mealMenu -> menuList.add(mealMenu.getMenu())); + List reviewList = queryFactory.selectFrom(review) + .where( + beforeFromLastReviewId(lastReviewId), + review.menu.in(menuList) + ) + .orderBy(review.id.asc()) + .limit(pageable.getPageSize() + 1L) + .fetch(); + return checkLastPage(pageable, reviewList); + } + + @Override + public Slice findByUserOrderByIdDesc(User user, Long lastReviewId, Pageable pageable) { + List reviewList = queryFactory.selectFrom(review) + .where( + nextFromLastReviewId(lastReviewId), + review.user.eq(user) + ) + .orderBy(review.id.desc()) + .limit(pageable.getPageSize() + 1L) + .fetch(); + return checkLastPage(pageable, reviewList); + } + + private BooleanExpression nextFromLastReviewId(Long reviewId) { + if (reviewId == null) { + return null; + } + return review.id.lt(reviewId); //reviewId < lastReviewId + } + + private BooleanExpression beforeFromLastReviewId(Long reviewId) { + if (reviewId == null) { + return null; + } + return review.id.gt(reviewId); //reviewId > lastReviewId + } + + //무한 스크롤 방식 처리하는 메소드 + private Slice checkLastPage(Pageable pageable, List results) { + + boolean hasNext = false; + + // 조회한 결과 개수가 요청한 페이지 사이즈보다 크면 뒤에 더 있음, next = true + if (results.size() > pageable.getPageSize()) { + hasNext = true; + results.remove(pageable.getPageSize()); + } + + return new SliceImpl<>(results, pageable, hasNext); + } } diff --git a/src/main/java/ssu/eatssu/domain/review/service/ReviewRatingService.java b/src/main/java/ssu/eatssu/domain/review/service/ReviewRatingService.java index 8247a80a..cf422bc9 100644 --- a/src/main/java/ssu/eatssu/domain/review/service/ReviewRatingService.java +++ b/src/main/java/ssu/eatssu/domain/review/service/ReviewRatingService.java @@ -1,10 +1,7 @@ package ssu.eatssu.domain.review.service; -import java.util.Map; - -import org.springframework.stereotype.Service; - import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; import ssu.eatssu.domain.menu.entity.Meal; import ssu.eatssu.domain.menu.entity.Menu; import ssu.eatssu.domain.menu.persistence.QuerydslMealRatingCalculator; @@ -15,68 +12,70 @@ import ssu.eatssu.domain.review.dto.RatingAverages; import ssu.eatssu.domain.review.dto.ReviewRatingCount; +import java.util.Map; + @Service @RequiredArgsConstructor public class ReviewRatingService implements RatingCalculator { - private final QuerydslMealRatingCalculator mealRatingCalculator; - private final QuerydslMenuRatingCalculator menuRatingCalculator; - private final QuerydslMealRatingCounter mealRatingCounter; - private final QuerydslMenuRatingCounter menuRatingCounter; + private final QuerydslMealRatingCalculator mealRatingCalculator; + private final QuerydslMenuRatingCalculator menuRatingCalculator; + private final QuerydslMealRatingCounter mealRatingCounter; + private final QuerydslMenuRatingCounter menuRatingCounter; - @Override - public ReviewRatingCount mealRatingCount(Meal meal) { - Map ratingCountMap = mealRatingCounter.getRatingCountMap(meal.getId()); - return getRatingCount(ratingCountMap); - } + @Override + public ReviewRatingCount mealRatingCount(Meal meal) { + Map ratingCountMap = mealRatingCounter.getRatingCountMap(meal.getId()); + return getRatingCount(ratingCountMap); + } - @Override - public ReviewRatingCount menuRatingCount(Menu menu) { - Map ratingCountMap = menuRatingCounter.getRatingCountMap(menu.getId()); - return getRatingCount(ratingCountMap); - } + @Override + public ReviewRatingCount menuRatingCount(Menu menu) { + Map ratingCountMap = menuRatingCounter.getRatingCountMap(menu.getId()); + return getRatingCount(ratingCountMap); + } - @Override - public RatingAverages mealAverageRatings(Meal meal) { - Double mainRatingAverage = mealRatingCalculator.getMainRatingAverage(meal.getId()); - Double amountRatingAverage = mealRatingCalculator.getAmountRatingAverage(meal.getId()); - Double tasteRatingAverage = mealRatingCalculator.getTasteRatingAverage(meal.getId()); - return new RatingAverages(mainRatingAverage, amountRatingAverage, tasteRatingAverage); - } + @Override + public RatingAverages mealAverageRatings(Meal meal) { + Double mainRatingAverage = mealRatingCalculator.getMainRatingAverage(meal.getId()); + Double amountRatingAverage = mealRatingCalculator.getAmountRatingAverage(meal.getId()); + Double tasteRatingAverage = mealRatingCalculator.getTasteRatingAverage(meal.getId()); + return new RatingAverages(mainRatingAverage, amountRatingAverage, tasteRatingAverage); + } - @Override - public RatingAverages menuAverageRatings(Menu menu) { - Double mainRatingAverage = menuRatingCalculator.getMainRatingAverage(menu.getId()); - Double amountRatingAverage = menuRatingCalculator.getAmountRatingAverage(menu.getId()); - Double tasteRatingAverage = menuRatingCalculator.getTasteRatingAverage(menu.getId()); - return new RatingAverages(mainRatingAverage, amountRatingAverage, tasteRatingAverage); - } + @Override + public RatingAverages menuAverageRatings(Menu menu) { + Double mainRatingAverage = menuRatingCalculator.getMainRatingAverage(menu.getId()); + Double amountRatingAverage = menuRatingCalculator.getAmountRatingAverage(menu.getId()); + Double tasteRatingAverage = menuRatingCalculator.getTasteRatingAverage(menu.getId()); + return new RatingAverages(mainRatingAverage, amountRatingAverage, tasteRatingAverage); + } - @Override - public Double mealAverageMainRating(Meal meal) { - return mealRatingCalculator.getMainRatingAverage(meal.getId()); - } + @Override + public Double mealAverageMainRating(Meal meal) { + return mealRatingCalculator.getMainRatingAverage(meal.getId()); + } - @Override - public Double menuAverageMainRating(Menu menu) { - return menuRatingCalculator.getMainRatingAverage(menu.getId()); - } + @Override + public Double menuAverageMainRating(Menu menu) { + return menuRatingCalculator.getMainRatingAverage(menu.getId()); + } - @Override - public long mealTotalReviewCount(Meal meal) { - return mealRatingCounter.getTotalRatingCount(meal.getId()); - } + @Override + public long mealTotalReviewCount(Meal meal) { + return mealRatingCounter.getTotalRatingCount(meal.getId()); + } - private ReviewRatingCount getRatingCount(Map ratingCountMap) { - long oneStarCount = getCount(ratingCountMap, 1); - long twoStarCount = getCount(ratingCountMap, 2); - long threeStarCount = getCount(ratingCountMap, 3); - long fourStarCount = getCount(ratingCountMap, 4); - long fiveStarCount = getCount(ratingCountMap, 5); - return new ReviewRatingCount(oneStarCount, twoStarCount, threeStarCount, fourStarCount, fiveStarCount); - } + private ReviewRatingCount getRatingCount(Map ratingCountMap) { + long oneStarCount = getCount(ratingCountMap, 1); + long twoStarCount = getCount(ratingCountMap, 2); + long threeStarCount = getCount(ratingCountMap, 3); + long fourStarCount = getCount(ratingCountMap, 4); + long fiveStarCount = getCount(ratingCountMap, 5); + return new ReviewRatingCount(oneStarCount, twoStarCount, threeStarCount, fourStarCount, fiveStarCount); + } - private long getCount(Map ratingCountMap, int star) { - return ratingCountMap.getOrDefault(star, 0L); - } + private long getCount(Map ratingCountMap, int star) { + return ratingCountMap.getOrDefault(star, 0L); + } } diff --git a/src/main/java/ssu/eatssu/domain/review/service/ReviewService.java b/src/main/java/ssu/eatssu/domain/review/service/ReviewService.java index b9502371..5e62ce31 100644 --- a/src/main/java/ssu/eatssu/domain/review/service/ReviewService.java +++ b/src/main/java/ssu/eatssu/domain/review/service/ReviewService.java @@ -1,16 +1,10 @@ package ssu.eatssu.domain.review.service; -import static ssu.eatssu.global.handler.response.BaseResponseStatus.*; - -import java.io.IOException; -import java.util.List; - +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.web.multipart.MultipartFile; - -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; import ssu.eatssu.domain.auth.security.CustomUserDetails; import ssu.eatssu.domain.menu.entity.Meal; import ssu.eatssu.domain.menu.entity.Menu; @@ -34,139 +28,149 @@ import ssu.eatssu.global.handler.response.BaseException; import ssu.eatssu.global.util.S3Uploader; +import java.io.IOException; +import java.util.List; + +import static ssu.eatssu.global.handler.response.BaseResponseStatus.FAIL_IMAGE_UPLOAD; +import static ssu.eatssu.global.handler.response.BaseResponseStatus.NOT_FOUND_MEAL; +import static ssu.eatssu.global.handler.response.BaseResponseStatus.NOT_FOUND_MENU; +import static ssu.eatssu.global.handler.response.BaseResponseStatus.NOT_FOUND_REVIEW; +import static ssu.eatssu.global.handler.response.BaseResponseStatus.NOT_FOUND_USER; +import static ssu.eatssu.global.handler.response.BaseResponseStatus.REVIEW_PERMISSION_DENIED; + @Slf4j @RequiredArgsConstructor @Service @Transactional public class ReviewService { - private final UserRepository userRepository; - private final ReviewRepository reviewRepository; - private final ReviewImageRepository reviewImageRepository; - private final MenuRepository menuRepository; - private final MealRepository mealRepository; - private final RatingCalculator ratingCalculator; - private final S3Uploader s3Uploader; - - public Review createReview(CustomUserDetails userDetails, Long menuId, ReviewCreateRequest request, - List images) { - - User user = userRepository.findById(userDetails.getId()) - .orElseThrow(() -> new BaseException(NOT_FOUND_USER)); - - Menu menu = menuRepository.findById(menuId) - .orElseThrow(() -> new BaseException(NOT_FOUND_MENU)); - - Review review = request.toEntity(user, menu); - - menu.addReview(review); - processReviewImages(images, review); - - return reviewRepository.save(review); - } - - private void processReviewImages(List images, Review review) { - if (images == null || images.isEmpty()) { - return; - } - for (MultipartFile image : images) { - addReviewImage(review, image); - } - } - - private void addReviewImage(Review review, MultipartFile image) { - if (!image.isEmpty()) { - try { - String reviewImageUrl = s3Uploader.upload(image, "reviewImg"); - ReviewImage reviewImage = ReviewImage.builder().review(review) - .imageUrl(reviewImageUrl) - .build(); - reviewImageRepository.save(reviewImage); - } catch (IOException e) { - throw new BaseException(FAIL_IMAGE_UPLOAD); - } - } - } - - public SavedReviewImage uploadImage(MultipartFile image) { - try { - String imageUrl = s3Uploader.upload(image, "reviewImg"); - SavedReviewImage savedReviewImage = new SavedReviewImage(imageUrl); - log.info("[리뷰 이미지 업로드 완료] 업로드된 URL = {}", savedReviewImage.getUrl()); - return savedReviewImage; - } catch (IOException e) { - log.error("[리뷰 이미지 업로드 실패]", e); - throw new BaseException(FAIL_IMAGE_UPLOAD); - } - } - - public void uploadReview(CustomUserDetails userDetails, Long menuId, UploadReviewRequest request) { - User user = userRepository.findById(userDetails.getId()) - .orElseThrow(() -> new BaseException(NOT_FOUND_USER)); - - Menu menu = menuRepository.findById(menuId) - .orElseThrow(() -> new BaseException(NOT_FOUND_MENU)); - - Review review = request.toReviewEntity(user, menu); - reviewRepository.save(review); - - ReviewImage reviewImage = new ReviewImage(review, request.getImageUrl()); - reviewImageRepository.save(reviewImage); - - menu.addReview(review); - } - - public void updateReview(CustomUserDetails userDetails, Long reviewId, - ReviewUpdateRequest request) { - User user = userRepository.findById(userDetails.getId()) - .orElseThrow(() -> new BaseException(NOT_FOUND_USER)); - - Review review = reviewRepository.findById(reviewId) - .orElseThrow(() -> new BaseException(NOT_FOUND_REVIEW)); - - if (review.isNotWrittenBy(user)) { - throw new BaseException(REVIEW_PERMISSION_DENIED); - } - - review.update(request.content(), request.mainRating(), request.amountRating(), - request.tasteRating()); - } - - public void deleteReview(CustomUserDetails userDetails, Long reviewId) { - User user = userRepository.findById(userDetails.getId()) - .orElseThrow(() -> new BaseException(NOT_FOUND_USER)); - - Review review = reviewRepository.findById(reviewId) - .orElseThrow(() -> new BaseException(NOT_FOUND_REVIEW)); - - if (review.isNotWrittenBy(user)) { - throw new BaseException(REVIEW_PERMISSION_DENIED); - } - reviewRepository.delete(review); - reviewRepository.flush(); - } - - public MenuReviewResponse findMenuReviews(Long menuId) { - Menu menu = menuRepository.findById(menuId) - .orElseThrow(() -> new BaseException(NOT_FOUND_MENU)); - - RatingAverages ratingAverages = ratingCalculator.menuAverageRatings(menu); - ReviewRatingCount ratingCount = ratingCalculator.menuRatingCount(menu); - - return MenuReviewResponse.of(menu, ratingAverages, ratingCount); - - } - - public MealReviewsResponse findMealReviews(Long mealId) { - Meal meal = mealRepository.findById(mealId) - .orElseThrow(() -> new BaseException(NOT_FOUND_MEAL)); - - long reviewCount = ratingCalculator.mealTotalReviewCount(meal); - - RatingAverages averageRating = ratingCalculator.mealAverageRatings(meal); - ReviewRatingCount ratingCountMap = ratingCalculator.mealRatingCount(meal); - - return MealReviewsResponse.of(reviewCount, meal.getMenuNames(), averageRating, - ratingCountMap); - } + private final UserRepository userRepository; + private final ReviewRepository reviewRepository; + private final ReviewImageRepository reviewImageRepository; + private final MenuRepository menuRepository; + private final MealRepository mealRepository; + private final RatingCalculator ratingCalculator; + private final S3Uploader s3Uploader; + + public Review createReview(CustomUserDetails userDetails, Long menuId, ReviewCreateRequest request, + List images) { + + User user = userRepository.findById(userDetails.getId()) + .orElseThrow(() -> new BaseException(NOT_FOUND_USER)); + + Menu menu = menuRepository.findById(menuId) + .orElseThrow(() -> new BaseException(NOT_FOUND_MENU)); + + Review review = request.toEntity(user, menu); + + menu.addReview(review); + processReviewImages(images, review); + + return reviewRepository.save(review); + } + + private void processReviewImages(List images, Review review) { + if (images == null || images.isEmpty()) { + return; + } + for (MultipartFile image : images) { + addReviewImage(review, image); + } + } + + private void addReviewImage(Review review, MultipartFile image) { + if (!image.isEmpty()) { + try { + String reviewImageUrl = s3Uploader.upload(image, "reviewImg"); + ReviewImage reviewImage = ReviewImage.builder().review(review) + .imageUrl(reviewImageUrl) + .build(); + reviewImageRepository.save(reviewImage); + } catch (IOException e) { + throw new BaseException(FAIL_IMAGE_UPLOAD); + } + } + } + + public SavedReviewImage uploadImage(MultipartFile image) { + try { + String imageUrl = s3Uploader.upload(image, "reviewImg"); + SavedReviewImage savedReviewImage = new SavedReviewImage(imageUrl); + log.info("[리뷰 이미지 업로드 완료] 업로드된 URL = {}", savedReviewImage.getUrl()); + return savedReviewImage; + } catch (IOException e) { + log.error("[리뷰 이미지 업로드 실패]", e); + throw new BaseException(FAIL_IMAGE_UPLOAD); + } + } + + public void uploadReview(CustomUserDetails userDetails, Long menuId, UploadReviewRequest request) { + User user = userRepository.findById(userDetails.getId()) + .orElseThrow(() -> new BaseException(NOT_FOUND_USER)); + + Menu menu = menuRepository.findById(menuId) + .orElseThrow(() -> new BaseException(NOT_FOUND_MENU)); + + Review review = request.toReviewEntity(user, menu); + reviewRepository.save(review); + + ReviewImage reviewImage = new ReviewImage(review, request.getImageUrl()); + reviewImageRepository.save(reviewImage); + + menu.addReview(review); + } + + public void updateReview(CustomUserDetails userDetails, Long reviewId, + ReviewUpdateRequest request) { + User user = userRepository.findById(userDetails.getId()) + .orElseThrow(() -> new BaseException(NOT_FOUND_USER)); + + Review review = reviewRepository.findById(reviewId) + .orElseThrow(() -> new BaseException(NOT_FOUND_REVIEW)); + + if (review.isNotWrittenBy(user)) { + throw new BaseException(REVIEW_PERMISSION_DENIED); + } + + review.update(request.content(), request.mainRating(), request.amountRating(), + request.tasteRating()); + } + + public void deleteReview(CustomUserDetails userDetails, Long reviewId) { + User user = userRepository.findById(userDetails.getId()) + .orElseThrow(() -> new BaseException(NOT_FOUND_USER)); + + Review review = reviewRepository.findById(reviewId) + .orElseThrow(() -> new BaseException(NOT_FOUND_REVIEW)); + + if (review.isNotWrittenBy(user)) { + throw new BaseException(REVIEW_PERMISSION_DENIED); + } + reviewRepository.delete(review); + reviewRepository.flush(); + } + + public MenuReviewResponse findMenuReviews(Long menuId) { + Menu menu = menuRepository.findById(menuId) + .orElseThrow(() -> new BaseException(NOT_FOUND_MENU)); + + RatingAverages ratingAverages = ratingCalculator.menuAverageRatings(menu); + ReviewRatingCount ratingCount = ratingCalculator.menuRatingCount(menu); + + return MenuReviewResponse.of(menu, ratingAverages, ratingCount); + + } + + public MealReviewsResponse findMealReviews(Long mealId) { + Meal meal = mealRepository.findById(mealId) + .orElseThrow(() -> new BaseException(NOT_FOUND_MEAL)); + + long reviewCount = ratingCalculator.mealTotalReviewCount(meal); + + RatingAverages averageRating = ratingCalculator.mealAverageRatings(meal); + ReviewRatingCount ratingCountMap = ratingCalculator.mealRatingCount(meal); + + return MealReviewsResponse.of(reviewCount, meal.getMenuNames(), averageRating, + ratingCountMap); + } } 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 36ac0359..56c04f16 100644 --- a/src/main/java/ssu/eatssu/domain/review/service/ReviewServiceV2.java +++ b/src/main/java/ssu/eatssu/domain/review/service/ReviewServiceV2.java @@ -1,21 +1,11 @@ package ssu.eatssu.domain.review.service; -import static ssu.eatssu.global.handler.response.BaseResponseStatus.*; - -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; -import java.util.stream.Collectors; - +import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Slice; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; - -import lombok.RequiredArgsConstructor; import ssu.eatssu.domain.auth.security.CustomUserDetails; import ssu.eatssu.domain.menu.entity.Meal; import ssu.eatssu.domain.menu.entity.Menu; @@ -24,306 +14,374 @@ import ssu.eatssu.domain.menu.persistence.MenuRepository; import ssu.eatssu.domain.restaurant.entity.Restaurant; import ssu.eatssu.domain.review.dto.CreateMealReviewRequest; +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.MenuLikeRequest; import ssu.eatssu.domain.review.dto.MenuReviewsV2Response; import ssu.eatssu.domain.review.dto.RestaurantReviewResponse; +import ssu.eatssu.domain.review.dto.ReviewDetail; import ssu.eatssu.domain.review.dto.ReviewRatingCount; import ssu.eatssu.domain.review.dto.UpdateMealReviewRequest; +import ssu.eatssu.domain.review.dto.ValidMenuForViewResponse; import ssu.eatssu.domain.review.entity.Review; -import ssu.eatssu.domain.review.entity.ReviewLike; -import ssu.eatssu.domain.review.repository.ReviewLikeRepository; +import ssu.eatssu.domain.review.entity.ReviewImage; +import ssu.eatssu.domain.review.repository.ReviewImageRepository; import ssu.eatssu.domain.review.repository.ReviewRepository; +import ssu.eatssu.domain.review.utils.MenuFilterUtil; import ssu.eatssu.domain.slice.dto.SliceResponse; import ssu.eatssu.domain.user.dto.MyMealReviewResponse; import ssu.eatssu.domain.user.entity.User; import ssu.eatssu.domain.user.repository.UserRepository; import ssu.eatssu.global.handler.response.BaseException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.stream.Collectors; + +import static ssu.eatssu.global.handler.response.BaseResponseStatus.NOT_FOUND_MEAL; +import static ssu.eatssu.global.handler.response.BaseResponseStatus.NOT_FOUND_MENU; +import static ssu.eatssu.global.handler.response.BaseResponseStatus.NOT_FOUND_REVIEW; +import static ssu.eatssu.global.handler.response.BaseResponseStatus.NOT_FOUND_USER; +import static ssu.eatssu.global.handler.response.BaseResponseStatus.REVIEW_PERMISSION_DENIED; + @RequiredArgsConstructor @Service public class ReviewServiceV2 { - private final UserRepository userRepository; - private final ReviewRepository reviewRepository; - private final MenuRepository menuRepository; - private final MealRepository mealRepository; - private final MealMenuRepository mealMenuRepository; - private final ReviewLikeRepository reviewLikeRepository; - - /** - * 리뷰 생성 - */ - @Transactional - public void createReview(CustomUserDetails userDetails, CreateMealReviewRequest request) { - User user = userRepository.findById(userDetails.getId()) - .orElseThrow(() -> new BaseException(NOT_FOUND_USER)); - - Meal meal = mealRepository.findById(request.getMealId()) - .orElseThrow(() -> new BaseException(NOT_FOUND_MEAL)); - - Review review = request.toReviewEntity(user, meal); - - request.getImageUrls().forEach(review::addReviewImage); - - for (MenuLikeRequest menuLike : request.getMenuLikes()) { - Menu menu = menuRepository.findById(menuLike.getMenuId()) - .orElseThrow(() -> new BaseException(NOT_FOUND_MENU)); - review.addReviewMenuLike(menu, menuLike.getIsLike()); - } - - reviewRepository.save(review); - } - - /** - * 특정 식당 리뷰 조회 - */ - public RestaurantReviewResponse findRestaurantReviews(Restaurant restaurant) { - List meals = mealRepository.findByRestaurant(restaurant); - List reviews = reviewRepository.findByMealIn(meals); - List menus = mealMenuRepository.findMenusByMeals(meals); - - Double averageRating = Optional.ofNullable(reviews) - .orElse(Collections.emptyList()) - .stream() - .filter(Objects::nonNull) - .map(Review::getRating) - .filter(Objects::nonNull) - .mapToInt(Integer::intValue) - .average() - .orElse(0.0); - - Integer likeCount = Optional.ofNullable(menus) - .orElse(Collections.emptyList()) - .stream() - .filter(Objects::nonNull) - .map(Menu::getLikeCount) - .filter(Objects::nonNull) - .mapToInt(Integer::intValue) - .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 RestaurantReviewResponse.builder() - .totalReviewCount(reviews.size()) - .reviewRatingCount(reviewRatingCount) - .mainRating(Math.round(averageRating * 10) / 10.0) - .likeCount(likeCount) - .unlikeCount(unlikeCount) - .build(); - } - - /** - * 특정 식단 리뷰 리스트 조회 - */ - public SliceResponse findReviews(Long mealId, Long lastReviewId, Pageable pageable, - CustomUserDetails userDetails) { - if (!mealRepository.existsById(mealId)) { - throw new BaseException(NOT_FOUND_MEAL); - } - - List menuIds = mealMenuRepository.findMenuIdsByMealId(mealId); - if (menuIds.isEmpty()) { - return SliceResponse.empty(); - } - - List mealIds = mealMenuRepository.findMealIdsByMenuIds(menuIds); - if (mealIds.isEmpty()) { - 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)).collect(Collectors.toList()); - - return SliceResponse.builder() - .numberOfElements(pageReviews.getNumberOfElements()) - .hasNext(pageReviews.hasNext()) - .dataList(mealReviewResponses) - .build(); - } - - /** - * 특정 Menu 리뷰 조회 - */ - public MenuReviewsV2Response findMenuReviews(Long menuId) { - Menu menu = menuRepository.findById(menuId).orElseThrow(()-> new BaseException(NOT_FOUND_MENU)); - List reviews = reviewRepository.findAllByMenu(menu); - - Double averageRating = Optional.ofNullable(reviews) - .orElse(Collections.emptyList()) - .stream() - .filter(Objects::nonNull) - .map(Review::getRating) - .filter(Objects::nonNull) - .mapToInt(Integer::intValue) - .average() - .orElse(0.0); - - Integer likeCount = menu.getLikeCount(); - - - Integer unlikeCount = menu.getUnlikeCount(); - - ReviewRatingCount reviewRatingCount = ReviewRatingCount.from(reviews); - - return MenuReviewsV2Response.builder() - .totalReviewCount((long)reviews.size()) - .reviewRatingCount(reviewRatingCount) - .mainRating(Math.round(averageRating * 10) / 10.0) - .likeCount(likeCount) - .unlikeCount(unlikeCount) - .build(); - } - - /** - * 특정 Meal 리뷰 조회 - */ - 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); - - Double averageRating = Optional.ofNullable(reviews) - .orElse(Collections.emptyList()) - .stream() - .filter(Objects::nonNull) - .map(Review::getRating) - .filter(Objects::nonNull) - .mapToInt(Integer::intValue) - .average() - .orElse(0.0); - - Integer likeCount = Optional.ofNullable(menus) - .orElse(Collections.emptyList()) - .stream() - .filter(Objects::nonNull) - .map(Menu::getLikeCount) - .filter(Objects::nonNull) - .mapToInt(Integer::intValue) - .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() - .totalReviewCount((long)reviews.size()) - .reviewRatingCount(reviewRatingCount) - .mainRating(Math.round(averageRating * 10) / 10.0) - .likeCount(likeCount) - .unlikeCount(unlikeCount) - .build(); - } - - /** - * 리뷰 수정 - */ - @Transactional - public void updateReview(CustomUserDetails userDetails, Long reviewId, UpdateMealReviewRequest request) { - User user = userRepository.findById(userDetails.getId()) - .orElseThrow(() -> new BaseException(NOT_FOUND_USER)); - - Review review = reviewRepository.findById(reviewId) - .orElseThrow(() -> new BaseException(NOT_FOUND_REVIEW)); - - if (review.isNotWrittenBy(user)) { - throw new BaseException(REVIEW_PERMISSION_DENIED); - } - - Map menuLikes = request.getMenuLikes().stream() - .collect(Collectors.toMap( - menuLike -> menuRepository.findById(menuLike.getMenuId()) - .orElseThrow(() -> new BaseException( - NOT_FOUND_MENU)), - MenuLikeRequest::getIsLike)); - - review.update(request.getContent(), request.getRating(), menuLikes); - reviewRepository.save(review); - } - - /** - * 리뷰 삭제 - */ - @Transactional - public void deleteReview(CustomUserDetails userDetails, Long reviewId) { - User user = userRepository.findById(userDetails.getId()) - .orElseThrow(() -> new BaseException(NOT_FOUND_USER)); - - Review review = reviewRepository.findById(reviewId) - .orElseThrow(() -> new BaseException(NOT_FOUND_REVIEW)); - - if (review.isNotWrittenBy(user)) { - throw new BaseException(REVIEW_PERMISSION_DENIED); - } - - review.resetMenuLikes(); - reviewRepository.delete(review); - } - - /** - * 내 리뷰 리스트 조회 - */ - public SliceResponse findMyReviews(CustomUserDetails userDetails, Long lastReviewId, - Pageable pageable) { - User user = userRepository.findById(userDetails.getId()) - .orElseThrow(() -> new BaseException(NOT_FOUND_USER)); - - Slice sliceReviews = reviewRepository.findByUserOrderByIdDesc(user, lastReviewId, - pageable); - - List myMealReviewResponses = sliceReviews.getContent().stream() - .map(MyMealReviewResponse::from).toList(); - - return SliceResponse.builder() - .numberOfElements(sliceReviews.getNumberOfElements()) - .hasNext(sliceReviews.hasNext()) - .dataList(myMealReviewResponses) - .build(); - } - - /** - * 리뷰 좋아요 누르기/취소하기 - */ - @Transactional - public void toggleReviewLike(CustomUserDetails userDetails, Long reviewId) { - User user = userRepository.findById(userDetails.getId()) - .orElseThrow(() -> new BaseException(NOT_FOUND_USER)); - Review review = reviewRepository.findById(reviewId) - .orElseThrow(() -> new BaseException(NOT_FOUND_REVIEW)); - - Optional optionalReviewLike = reviewLikeRepository.findByReviewAndUser(review, user); - if (optionalReviewLike.isPresent()) { - // 이미 좋아요 한 경우 -> 좋아요 취소 처리 - ReviewLike reviewLike = optionalReviewLike.get(); - review.getReviewLikes().remove(reviewLike); - reviewLikeRepository.delete(reviewLike); - } else { - // 좋아요 하지 않은 경우 -> 좋아요 추가 처리 - ReviewLike reviewLike = ReviewLike.create(user, review); - review.getReviewLikes().add(reviewLike); - reviewLikeRepository.save(reviewLike); - } - } + private final UserRepository userRepository; + private final ReviewRepository reviewRepository; + private final MenuRepository menuRepository; + private final MealRepository mealRepository; + private final MealMenuRepository mealMenuRepository; + private final ReviewImageRepository reviewImageRepository; + + /** + * meal에 대한 리뷰 생성 + */ + @Transactional + public void createMealReview(CustomUserDetails userDetails, CreateMealReviewRequest request) { + User user = userRepository.findById(userDetails.getId()) + .orElseThrow(() -> new BaseException(NOT_FOUND_USER)); + + Meal meal = mealRepository.findById(request.getMealId()) + .orElseThrow(() -> new BaseException(NOT_FOUND_MEAL)); + + Review review = request.toReviewEntity(user, meal); + + request.getImageUrls().forEach(review::addReviewImage); + + for (MenuLikeRequest menuLike : request.getMenuLikes()) { + Menu menu = menuRepository.findById(menuLike.getMenuId()) + .orElseThrow(() -> new BaseException(NOT_FOUND_MENU)); + review.addReviewMenuLike(menu, menuLike.getIsLike()); + } + + reviewRepository.save(review); + } + + /** + * menu에 대한 리뷰 작성 + */ + @Transactional + public void createMenuReview(CustomUserDetails userDetails, CreateMenuReviewRequest request) { + User user = userRepository.findById(userDetails.getId()) + .orElseThrow(() -> new BaseException(NOT_FOUND_USER)); + + Menu menu = menuRepository.findById(request.getMenuId()) + .orElseThrow(() -> new BaseException(NOT_FOUND_MENU)); + + Review review = request.toReviewEntity(user, menu); + review.addReviewMenuLike(menu, request.getMenuLike().getIsLike()); + reviewRepository.save(review); + + ReviewImage reviewImage = new ReviewImage(review, request.getImageUrl()); + reviewImageRepository.save(reviewImage); + + menu.addReview(review); + } + + /** + * 특정 식당 리뷰 조회 + */ + public RestaurantReviewResponse findRestaurantReviews(Restaurant restaurant) { + List meals = mealRepository.findByRestaurant(restaurant); + List reviews = reviewRepository.findByMealIn(meals); + List menus = mealMenuRepository.findMenusByMeals(meals); + + Double averageRating = Optional.ofNullable(reviews) + .orElse(Collections.emptyList()) + .stream() + .filter(Objects::nonNull) + .map(Review::getRating) + .filter(Objects::nonNull) + .mapToInt(Integer::intValue) + .average() + .orElse(0.0); + + Integer likeCount = Optional.ofNullable(menus) + .orElse(Collections.emptyList()) + .stream() + .filter(Objects::nonNull) + .map(Menu::getLikeCount) + .filter(Objects::nonNull) + .mapToInt(Integer::intValue) + .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 RestaurantReviewResponse.builder() + .totalReviewCount(reviews.size()) + .reviewRatingCount(reviewRatingCount) + .mainRating(Math.round(averageRating * 10) / 10.0) + .likeCount(likeCount) + .unlikeCount(unlikeCount) + .build(); + } + + /** + * 특정 식단 리뷰 리스트 조회 + */ + public SliceResponse findMealReviewList(Long mealId, Long lastReviewId, Pageable pageable, + CustomUserDetails userDetails) { + if (!mealRepository.existsById(mealId)) { + throw new BaseException(NOT_FOUND_MEAL); + } + + List menuIds = mealMenuRepository.findMenuIdsByMealId(mealId); + if (menuIds.isEmpty()) { + return SliceResponse.empty(); + } + + List mealIds = mealMenuRepository.findMealIdsByMenuIds(menuIds); + if (mealIds.isEmpty()) { + 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)) + .collect(Collectors.toList()); + + return SliceResponse.builder() + .numberOfElements(pageReviews.getNumberOfElements()) + .hasNext(pageReviews.hasNext()) + .dataList(mealReviewResponses) + .build(); + } + + /** + * 특정 메뉴 리뷰 리스트 조회 + */ + + public SliceResponse findMenuReviewList(Long menuId, + Pageable pageable, + Long lastReviewId, + CustomUserDetails userDetails) { + Slice sliceReviews = null; + Menu menu = menuRepository.findById(menuId) + .orElseThrow(() -> new BaseException(NOT_FOUND_MENU)); + sliceReviews = reviewRepository.findAllByMenuOrderByIdDesc(menu, lastReviewId, + pageable); + + Long userId = (userDetails != null) ? userDetails.getId() : null; + return convertToReviewDetail(sliceReviews, userId); + } + + private SliceResponse convertToReviewDetail(Slice sliceReviews, + Long userId) { + List reviewDetails = sliceReviews.getContent().stream() + .map(review -> ReviewDetail.from(review, userId)) + .collect(Collectors.toList()); + + return SliceResponse.builder() + .numberOfElements(sliceReviews.getNumberOfElements()) + .hasNext(sliceReviews.hasNext()) + .dataList(reviewDetails) + .build(); + } + + + /** + * 특정 Menu 리뷰 조회 + */ + public MenuReviewsV2Response findMenuReviews(Long menuId) { + Menu menu = menuRepository.findById(menuId).orElseThrow(() -> new BaseException(NOT_FOUND_MENU)); + List reviews = reviewRepository.findAllByMenu(menu); + + double averageRating = Optional.ofNullable(reviews) + .orElse(Collections.emptyList()) + .stream() + .filter(Objects::nonNull) + .map(Review::getRating) + .filter(Objects::nonNull) + .mapToInt(Integer::intValue) + .average() + .orElse(0.0); + + Integer likeCount = menu.getLikeCount(); + + ReviewRatingCount reviewRatingCount = ReviewRatingCount.from(reviews); + + return MenuReviewsV2Response + .builder() + .menuName(menu.getName()) + .totalReviewCount((long) reviews.size()) + .reviewRatingCount(reviewRatingCount) + .mainRating(Math.round(averageRating * 10) / 10.0) + .likeCount(likeCount != null ? likeCount : 0) + .build(); + } + + /** + * 특정 Meal 리뷰 조회 + */ + 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); + + Double averageRating = Optional.ofNullable(reviews) + .orElse(Collections.emptyList()) + .stream() + .filter(Objects::nonNull) + .map(Review::getRating) + .filter(Objects::nonNull) + .mapToInt(Integer::intValue) + .average() + .orElse(0.0); + + Integer likeCount = Optional.ofNullable(menus) + .orElse(Collections.emptyList()) + .stream() + .filter(Objects::nonNull) + .map(Menu::getLikeCount) + .filter(Objects::nonNull) + .mapToInt(Integer::intValue) + .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())) + .totalReviewCount((long) reviews.size()) + .reviewRatingCount(reviewRatingCount) + .mainRating(Math.round(averageRating * 10) / 10.0) + .likeCount(likeCount) + .build(); + } + + /** + * 리뷰 수정 + */ + @Transactional + public void updateReview(CustomUserDetails userDetails, Long reviewId, UpdateMealReviewRequest request) { + User user = userRepository.findById(userDetails.getId()) + .orElseThrow(() -> new BaseException(NOT_FOUND_USER)); + + Review review = reviewRepository.findById(reviewId) + .orElseThrow(() -> new BaseException(NOT_FOUND_REVIEW)); + + if (review.isNotWrittenBy(user)) { + throw new BaseException(REVIEW_PERMISSION_DENIED); + } + + Map menuLikes = request.getMenuLikes().stream() + .collect(Collectors.toMap( + menuLike -> menuRepository.findById(menuLike.getMenuId()) + .orElseThrow(() -> new BaseException( + NOT_FOUND_MENU)), + MenuLikeRequest::getIsLike)); + + review.update(request.getContent(), request.getRating(), menuLikes); + reviewRepository.save(review); + } + + /** + * 리뷰 삭제 + */ + @Transactional + public void deleteReview(CustomUserDetails userDetails, Long reviewId) { + User user = userRepository.findById(userDetails.getId()) + .orElseThrow(() -> new BaseException(NOT_FOUND_USER)); + + Review review = reviewRepository.findById(reviewId) + .orElseThrow(() -> new BaseException(NOT_FOUND_REVIEW)); + + if (review.isNotWrittenBy(user)) { + throw new BaseException(REVIEW_PERMISSION_DENIED); + } + + review.resetMenuLikes(); + reviewRepository.delete(review); + } + + /** + * 내 리뷰 리스트 조회 + */ + public SliceResponse findMyReviews(CustomUserDetails userDetails, Long lastReviewId, + Pageable pageable) { + User user = userRepository.findById(userDetails.getId()) + .orElseThrow(() -> new BaseException(NOT_FOUND_USER)); + + Slice sliceReviews = reviewRepository.findByUserOrderByIdDesc(user, lastReviewId, + pageable); + + List myMealReviewResponses = sliceReviews.getContent().stream() + .map(MyMealReviewResponse::from).toList(); + + return SliceResponse.builder() + .numberOfElements(sliceReviews.getNumberOfElements()) + .hasNext(sliceReviews.hasNext()) + .dataList(myMealReviewResponses) + .build(); + } + + + public ValidMenuForViewResponse validMenuForReview(Long mealId) { + Meal meal = mealRepository.findById(mealId).orElseThrow(() -> new BaseException(NOT_FOUND_MEAL)); + List menuNames = meal.getMenuNames(); + List validMenuNames = new ArrayList<>(); + for (String menu : menuNames) { + if (!MenuFilterUtil.isExcludedFromReview(menu)) { + validMenuNames.add(menu); + } + } + + return ValidMenuForViewResponse.builder() + .menuList(validMenuNames).build(); + } } diff --git a/src/main/java/ssu/eatssu/domain/review/utils/MenuFilterUtil.java b/src/main/java/ssu/eatssu/domain/review/utils/MenuFilterUtil.java new file mode 100644 index 00000000..a587501f --- /dev/null +++ b/src/main/java/ssu/eatssu/domain/review/utils/MenuFilterUtil.java @@ -0,0 +1,38 @@ +package ssu.eatssu.domain.review.utils; + +import java.util.Set; +import java.util.regex.Pattern; + +public class MenuFilterUtil { + private static final Set EXCLUDED_KEYWORDS = Set.of( + "작은밥", "쌀밥", "잡곡밥", "찰흑미밥", "귀리밥", "백미밥", + "깍두기", "열무김치", "포기김치", "맛김치", "배추김치", + "단무지", "오이피클", "야쿠르트", "요구르트", "우동국물" + ); + private static final Pattern SAUCE_PATTERN = Pattern.compile(".*소스.*"); + + public static boolean isExcludedFromReview(String menuName) { + if (menuName == null || menuName.isBlank()) return true; + + menuName = menuName.trim(); + + for (String keyword : EXCLUDED_KEYWORDS) { + if (menuName.contains(keyword)) { + return true; + } + } + + if (menuName.contains("밥") + && !menuName.contains("볶음밥") + && !menuName.contains("비빔밥")) { + return true; + } + + if (menuName.contains("김치") + && !menuName.contains("김치볶음밥")) { + return true; + } + + return SAUCE_PATTERN.matcher(menuName).matches(); + } +} diff --git a/src/main/java/ssu/eatssu/domain/slack/entity/SlackChannel.java b/src/main/java/ssu/eatssu/domain/slack/entity/SlackChannel.java index ce6f562a..d9b142dc 100644 --- a/src/main/java/ssu/eatssu/domain/slack/entity/SlackChannel.java +++ b/src/main/java/ssu/eatssu/domain/slack/entity/SlackChannel.java @@ -4,16 +4,16 @@ public enum SlackChannel { - REPORT_CHANNEL("#신고"), - ADDMENU_CHANNEL("#메뉴_추가"), - ERROR_CHANNEL("#장애"), - USER_INQUIRY_CHANNEL("#유저-문의"), - SERVER_ERROR("C092J4J6F0U"); + REPORT_CHANNEL("#신고"), + ADDMENU_CHANNEL("#메뉴_추가"), + ERROR_CHANNEL("#장애"), + USER_INQUIRY_CHANNEL("#유저-문의"), + SERVER_ERROR("C092J4J6F0U"); - @Getter - private String krName; + @Getter + private final String krName; - SlackChannel(String krName) { - this.krName = krName; - } + SlackChannel(String krName) { + this.krName = krName; + } } diff --git a/src/main/java/ssu/eatssu/domain/slack/entity/SlackMessageFormat.java b/src/main/java/ssu/eatssu/domain/slack/entity/SlackMessageFormat.java index 81cabe99..adb4cb1d 100644 --- a/src/main/java/ssu/eatssu/domain/slack/entity/SlackMessageFormat.java +++ b/src/main/java/ssu/eatssu/domain/slack/entity/SlackMessageFormat.java @@ -1,90 +1,90 @@ package ssu.eatssu.domain.slack.entity; -import java.text.MessageFormat; - import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; - import ssu.eatssu.domain.inquiry.entity.Inquiry; import ssu.eatssu.domain.review.entity.Report; import ssu.eatssu.domain.review.entity.Review; import ssu.eatssu.domain.user.entity.User; +import ssu.eatssu.global.handler.response.BaseException; + +import java.text.MessageFormat; @Component public class SlackMessageFormat { - private static String serverEnv; + private static String serverEnv; - private SlackMessageFormat(@Value("${server.env:unknown}") String serverEnvValue) { - SlackMessageFormat.serverEnv = serverEnvValue; - } + private SlackMessageFormat(@Value("${server.env:unknown}") String serverEnvValue) { + SlackMessageFormat.serverEnv = serverEnvValue; + } - public static String sendReport(Report report) { - User reporter = report.getUser(); - Review review = report.getReview(); - MessageFormat messageFormat = new MessageFormat( - """ - =================== - *신고자 INFO* - - 신고자 ID: {0} - - 닉네임: {1} - *신고된 리뷰 INFO* - - 리뷰 ID: {2} - - 리뷰 작성자 ID : {3} - - 리뷰 작성자 닉네임 : {4} - - 리뷰 메뉴: {5} - - 리뷰 내용: {6} - - 리뷰 날짜: {7} - *신고 INFO* - - 신고사유: {8} - - 신고내용: {9} - - 신고 날짜: {10} - =================== - """ - ); - Object[] args = {reporter.getId(), reporter.getNickname(), review.getId(), review.getUser().getId(), - review.getUser().getNickname(), review.getMenu().getName(), review.getContent(), - review.getModifiedDate().toString(), report.getReportType().getDescription(), report.getContent(), - report.getCreatedDate()}; - return messageFormat.format(args); - } + public static String sendReport(Report report) { + User reporter = report.getUser(); + Review review = report.getReview(); + MessageFormat messageFormat = new MessageFormat( + """ + =================== + *신고자 INFO* + - 신고자 ID: {0} + - 닉네임: {1} + *신고된 리뷰 INFO* + - 리뷰 ID: {2} + - 리뷰 작성자 ID : {3} + - 리뷰 작성자 닉네임 : {4} + - 리뷰 메뉴: {5} + - 리뷰 내용: {6} + - 리뷰 날짜: {7} + *신고 INFO* + - 신고사유: {8} + - 신고내용: {9} + - 신고 날짜: {10} + =================== + """ + ); + Object[] args = {reporter.getId(), reporter.getNickname(), review.getId(), review.getUser().getId(), + review.getUser().getNickname(), review.getMenu().getName(), review.getContent(), + review.getModifiedDate().toString(), report.getReportType().getDescription(), report.getContent(), + report.getCreatedDate()}; + return messageFormat.format(args); + } - public static String sendUserInquiry(Inquiry inquiry) { - MessageFormat messageFormat = new MessageFormat( - """ - =================== - *문의 INFO* - - 문의자 ID: {0} - - 닉네임: {1} - - 이메일: {2} - *문의 내용* - - Date: {3} - - Content: {4} - =================== - """ - ); - Object[] args = {inquiry.getUser().getId(), inquiry.getUser().getNickname(), inquiry.getUser().getEmail() - , inquiry.getCreatedDate(), inquiry.getContent()}; - return messageFormat.format(args); - } + public static String sendUserInquiry(Inquiry inquiry) { + MessageFormat messageFormat = new MessageFormat( + """ + =================== + *문의 INFO* + - 문의자 ID: {0} + - 닉네임: {1} + - 이메일: {2} + *문의 내용* + - Date: {3} + - Content: {4} + =================== + """ + ); + Object[] args = {inquiry.getUser().getId(), inquiry.getUser().getNickname(), inquiry.getUser().getEmail() + , inquiry.getCreatedDate(), inquiry.getContent()}; + return messageFormat.format(args); + } - public static String sendServerError(Exception ex) { - MessageFormat messageFormat = new MessageFormat( - """ - =================== - *서버 에러 발생* - - 예외 클래스: {0} - - 예외 메시지: {1} - - 개발환경: {2} - =================== - """ - ); - Object[] args = { - ex.getClass().getName(), - ex.getMessage(), - serverEnv, - }; - return messageFormat.format(args); - } + public static String sendServerError(BaseException ex) { + MessageFormat messageFormat = new MessageFormat( + """ + =================== + *서버 에러 발생* + - 예외 상태코드: {0} + - 예외 메시지: {1} + - 개발환경: {2} + =================== + """ + ); + Object[] args = { + ex.getStatus(), + ex.getStatus().getMessage(), + serverEnv, + }; + return messageFormat.format(args); + } } diff --git a/src/main/java/ssu/eatssu/domain/slack/service/SlackErrorNotifier.java b/src/main/java/ssu/eatssu/domain/slack/service/SlackErrorNotifier.java new file mode 100644 index 00000000..e9c1d316 --- /dev/null +++ b/src/main/java/ssu/eatssu/domain/slack/service/SlackErrorNotifier.java @@ -0,0 +1,33 @@ +package ssu.eatssu.domain.slack.service; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; +import ssu.eatssu.domain.slack.entity.SlackChannel; +import ssu.eatssu.domain.slack.entity.SlackMessageFormat; +import ssu.eatssu.global.handler.response.BaseException; + +@Service +@Slf4j +public class SlackErrorNotifier { + private final SlackService slackService; + @Value("${server.env:unknown}") + private String serverEnv; + + public SlackErrorNotifier(SlackService slackService) { + this.slackService = slackService; + } + + public void notify(BaseException ex) { + if (!"prod".equals(serverEnv)) { + return; + } + try { + String message = SlackMessageFormat.sendServerError(ex); + slackService.sendSlackMessage(message, SlackChannel.SERVER_ERROR); + } catch (Exception slackEx) { + log.warn("슬랙 전송 실패: {}", slackEx.getMessage()); + } + } +} + diff --git a/src/main/java/ssu/eatssu/domain/slack/service/SlackService.java b/src/main/java/ssu/eatssu/domain/slack/service/SlackService.java index 33a5a5d4..4f20ac3a 100644 --- a/src/main/java/ssu/eatssu/domain/slack/service/SlackService.java +++ b/src/main/java/ssu/eatssu/domain/slack/service/SlackService.java @@ -1,43 +1,40 @@ package ssu.eatssu.domain.slack.service; -import java.io.IOException; - -import org.springframework.beans.factory.annotation.Value; -import org.springframework.stereotype.Service; - import com.slack.api.Slack; import com.slack.api.methods.MethodsClient; import com.slack.api.methods.SlackApiException; import com.slack.api.methods.request.chat.ChatPostMessageRequest; -import com.slack.api.methods.response.chat.ChatPostMessageResponse; - import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; import ssu.eatssu.domain.slack.entity.SlackChannel; +import java.io.IOException; + @Slf4j @Service public class SlackService { - @Value(value = "${slack.token}") - String slackToken; + @Value(value = "${slack.token}") + String slackToken; - public void sendSlackMessage(String message, SlackChannel channel) { + public void sendSlackMessage(String message, SlackChannel channel) { - String channelAddress = channel.getKrName(); + String channelAddress = channel.getKrName(); - try { - MethodsClient methods = Slack.getInstance().methods(slackToken); + try { + MethodsClient methods = Slack.getInstance().methods(slackToken); - ChatPostMessageRequest request = ChatPostMessageRequest.builder() - .channel(channelAddress) - .text(message) - .build(); + ChatPostMessageRequest request = ChatPostMessageRequest.builder() + .channel(channelAddress) + .text(message) + .build(); - methods.chatPostMessage(request); + methods.chatPostMessage(request); - log.info("Slack " + channel + " 에 메시지 보냄"); - } catch (SlackApiException | IOException e) { - log.error(e.getMessage()); - } - } + log.info("Slack " + channel + " 에 메시지 보냄"); + } catch (SlackApiException | IOException e) { + log.error(e.getMessage()); + } + } } diff --git a/src/main/java/ssu/eatssu/domain/slice/dto/SliceResponse.java b/src/main/java/ssu/eatssu/domain/slice/dto/SliceResponse.java index 6d7015ba..fd82109a 100644 --- a/src/main/java/ssu/eatssu/domain/slice/dto/SliceResponse.java +++ b/src/main/java/ssu/eatssu/domain/slice/dto/SliceResponse.java @@ -1,27 +1,27 @@ package ssu.eatssu.domain.slice.dto; -import java.util.Collections; -import java.util.List; - import io.swagger.v3.oas.annotations.media.Schema; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Getter; +import java.util.Collections; +import java.util.List; + @AllArgsConstructor @Builder @Getter public class SliceResponse { - @Schema(description = "현재 넘겨준 페이지에 넘어간 개수(마지막 페이지일시, size 보다 작을 수 있음)", example = "20") - private int numberOfElements; + @Schema(description = "현재 넘겨준 페이지에 넘어간 개수(마지막 페이지일시, size 보다 작을 수 있음)", example = "20") + private int numberOfElements; - @Schema(description = "다음페이지가 있는지 알려주는 값(마지막 페이지라면 false)", example = "true") - private boolean hasNext; + @Schema(description = "다음페이지가 있는지 알려주는 값(마지막 페이지라면 false)", example = "true") + private boolean hasNext; - @Schema(description = "데이터 리스트") - private List dataList; + @Schema(description = "데이터 리스트") + private List dataList; - public static SliceResponse empty() { - return new SliceResponse<>(0, false, Collections.emptyList()); - } + public static SliceResponse empty() { + return new SliceResponse<>(0, false, Collections.emptyList()); + } } diff --git a/src/main/java/ssu/eatssu/domain/slice/service/SliceService.java b/src/main/java/ssu/eatssu/domain/slice/service/SliceService.java index 7ad650aa..bc4bd390 100644 --- a/src/main/java/ssu/eatssu/domain/slice/service/SliceService.java +++ b/src/main/java/ssu/eatssu/domain/slice/service/SliceService.java @@ -1,15 +1,9 @@ package ssu.eatssu.domain.slice.service; -import static ssu.eatssu.global.handler.response.BaseResponseStatus.*; - -import java.util.List; -import java.util.stream.Collectors; - +import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Slice; import org.springframework.stereotype.Service; - -import lombok.RequiredArgsConstructor; import ssu.eatssu.domain.auth.security.CustomUserDetails; import ssu.eatssu.domain.menu.entity.Meal; import ssu.eatssu.domain.menu.entity.Menu; @@ -25,77 +19,88 @@ import ssu.eatssu.domain.user.repository.UserRepository; import ssu.eatssu.global.handler.response.BaseException; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.stream.Collectors; + +import static ssu.eatssu.global.handler.response.BaseResponseStatus.NOT_FOUND_MENU; +import static ssu.eatssu.global.handler.response.BaseResponseStatus.NOT_FOUND_USER; + @Service @RequiredArgsConstructor public class SliceService { - private final UserRepository userRepository; - private final ReviewRepository reviewRepository; - private final MenuRepository menuRepository; - private final MealRepository mealRepository; - - public SliceResponse findMyReviews( - CustomUserDetails userDetails, - Pageable pageable, - Long lastReviewId) { - - User user = userRepository.findById(userDetails.getId()) - .orElseThrow(() -> new BaseException(NOT_FOUND_USER)); - - Slice sliceReviews = reviewRepository.findByUserOrderByIdDesc(user, lastReviewId, - pageable); - - return convertToMyReviewDetail(sliceReviews); - } - - public SliceResponse findReviews(MenuType menuType, - Long menuId, - Long mealId, - Pageable pageable, - Long lastReviewId, - CustomUserDetails userDetails) { - Slice sliceReviews = null; - if (menuType == MenuType.FIXED) { - Menu menu = menuRepository.findById(menuId) - .orElseThrow(() -> new BaseException(NOT_FOUND_MENU)); - sliceReviews = reviewRepository.findAllByMenuOrderByIdDesc(menu, lastReviewId, - pageable); - } - - if (menuType == MenuType.VARIABLE) { - Meal meal = mealRepository.findById(mealId) - .orElseThrow(() -> new BaseException(NOT_FOUND_MENU)); - sliceReviews = reviewRepository.findAllByMealOrderByIdDesc(meal, lastReviewId, - pageable); - } - - Long userId = (userDetails != null) ? userDetails.getId() : null; - return convertToReviewDetail(sliceReviews, userId); - } - - private SliceResponse convertToReviewDetail(Slice sliceReviews, - Long userId) { - List reviewDetails = sliceReviews.getContent().stream() - .map(review -> ReviewDetail.from(review, userId)) - .collect(Collectors.toList()); - - return SliceResponse.builder() - .numberOfElements(sliceReviews.getNumberOfElements()) - .hasNext(sliceReviews.hasNext()) - .dataList(reviewDetails) - .build(); - } - - private SliceResponse convertToMyReviewDetail(Slice sliceReviews) { - List myReviewDetails = sliceReviews.getContent().stream() - .map(MyReviewDetail::from) - .collect(Collectors.toList()); - - return SliceResponse.builder() - .numberOfElements(sliceReviews.getNumberOfElements()) - .hasNext(sliceReviews.hasNext()) - .dataList(myReviewDetails) - .build(); - } + private final UserRepository userRepository; + private final ReviewRepository reviewRepository; + private final MenuRepository menuRepository; + private final MealRepository mealRepository; + + public SliceResponse findMyReviews( + CustomUserDetails userDetails, + Pageable pageable, + Long lastReviewId) { + + User user = userRepository.findById(userDetails.getId()) + .orElseThrow(() -> new BaseException(NOT_FOUND_USER)); + + Slice sliceReviews = reviewRepository.findByUserOrderByIdDesc(user, lastReviewId, + pageable); + + return convertToMyReviewDetail(sliceReviews); + } + + public SliceResponse findReviews(MenuType menuType, + Long menuId, + Long mealId, + Pageable pageable, + Long lastReviewId, + CustomUserDetails userDetails) { + Slice sliceReviews = null; + if (menuType == MenuType.FIXED) { + Menu menu = menuRepository.findById(menuId) + .orElseThrow(() -> new BaseException(NOT_FOUND_MENU)); + sliceReviews = reviewRepository.findAllByMenuOrderByIdDesc(menu, lastReviewId, + pageable); + } + + if (menuType == MenuType.VARIABLE) { + Meal meal = mealRepository.findById(mealId) + .orElseThrow(() -> new BaseException(NOT_FOUND_MENU)); + sliceReviews = reviewRepository.findAllByMealOrderByIdDesc(meal, lastReviewId, + pageable); + } + + Long userId = (userDetails != null) ? userDetails.getId() : null; + return convertToReviewDetail(sliceReviews, userId); + } + + private SliceResponse convertToReviewDetail(Slice sliceReviews, + Long userId) { + List reviewDetails = sliceReviews.getContent().stream() + .map(review -> ReviewDetail.from(review, userId)) + .collect(Collectors.toList()); + + return SliceResponse.builder() + .numberOfElements(sliceReviews.getNumberOfElements()) + .hasNext(sliceReviews.hasNext()) + .dataList(reviewDetails) + .build(); + } + + private SliceResponse convertToMyReviewDetail(Slice sliceReviews) { + List myReviewDetails = Optional.of(sliceReviews.getContent()) + .orElse(List.of()) + .stream() + .filter(Objects::nonNull) + .map(MyReviewDetail::from) + .collect(Collectors.toList()); + + return SliceResponse.builder() + .numberOfElements(sliceReviews.getNumberOfElements()) + .hasNext(sliceReviews.hasNext()) + .dataList(myReviewDetails) + .build(); + } } 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 1f6ec4e3..70ae0260 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 @@ -1,8 +1,5 @@ package ssu.eatssu.domain.user.department.entity; -import java.util.ArrayList; -import java.util.List; - import jakarta.persistence.CascadeType; import jakarta.persistence.Column; import jakarta.persistence.Entity; @@ -13,23 +10,30 @@ import lombok.AccessLevel; import lombok.Getter; import lombok.NoArgsConstructor; -import ssu.eatssu.domain.partnership.entity.PartnershipCollege; +import ssu.eatssu.domain.partnership.entity.Partnership; + +import java.util.ArrayList; +import java.util.List; @Entity @Getter @NoArgsConstructor(access = AccessLevel.PROTECTED) public class College { - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - @Column(name = "college_id") - private Long id; + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "college_id") + private Long id; + + @Column(nullable = false, unique = true) + private String name; - @Column(nullable = false, unique = true) - private String name; + @OneToMany(mappedBy = "college", cascade = CascadeType.ALL, orphanRemoval = true) + private final List departments = new ArrayList<>(); - @OneToMany(mappedBy = "college", cascade = CascadeType.ALL, orphanRemoval = true) - private List departments = new ArrayList<>(); + @OneToMany(mappedBy = "partnershipCollege", cascade = CascadeType.ALL, orphanRemoval = true) + private final List partnerships = new ArrayList<>(); - @OneToMany(mappedBy = "college", cascade = CascadeType.ALL, orphanRemoval = true) - private List partnershipColleges = 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 f91bd744..9c5f227c 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 @@ -1,8 +1,5 @@ package ssu.eatssu.domain.user.department.entity; -import java.util.ArrayList; -import java.util.List; - import jakarta.persistence.CascadeType; import jakarta.persistence.Column; import jakarta.persistence.Entity; @@ -16,24 +13,31 @@ import lombok.AccessLevel; import lombok.Getter; import lombok.NoArgsConstructor; -import ssu.eatssu.domain.partnership.entity.PartnershipDepartment; +import ssu.eatssu.domain.partnership.entity.Partnership; + +import java.util.ArrayList; +import java.util.List; @Entity @Getter @NoArgsConstructor(access = AccessLevel.PROTECTED) public class Department { - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - @Column(name = "department_id") - private Long id; + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "department_id") + private Long id; + + @Column(nullable = false) + private String name; - @Column(nullable = false) - private String name; + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "college_id") + private College college; - @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<>(); - @OneToMany(mappedBy = "department", cascade = CascadeType.ALL, orphanRemoval = true) - private List partnershipDepartments = new ArrayList<>(); + public Department(String name) { + this.name = name; + } } diff --git a/src/main/java/ssu/eatssu/domain/user/department/persistence/CollegeRepository.java b/src/main/java/ssu/eatssu/domain/user/department/persistence/CollegeRepository.java index c9d21e6c..4e7c6d89 100644 --- a/src/main/java/ssu/eatssu/domain/user/department/persistence/CollegeRepository.java +++ b/src/main/java/ssu/eatssu/domain/user/department/persistence/CollegeRepository.java @@ -1,11 +1,10 @@ package ssu.eatssu.domain.user.department.persistence; -import java.util.Optional; - import org.springframework.data.jpa.repository.JpaRepository; - import ssu.eatssu.domain.user.department.entity.College; +import java.util.Optional; + public interface CollegeRepository extends JpaRepository { - Optional findByName(String name); + Optional findByName(String name); } diff --git a/src/main/java/ssu/eatssu/domain/user/department/persistence/DepartmentRepository.java b/src/main/java/ssu/eatssu/domain/user/department/persistence/DepartmentRepository.java index db9a9bfb..942e4063 100644 --- a/src/main/java/ssu/eatssu/domain/user/department/persistence/DepartmentRepository.java +++ b/src/main/java/ssu/eatssu/domain/user/department/persistence/DepartmentRepository.java @@ -1,11 +1,10 @@ package ssu.eatssu.domain.user.department.persistence; -import java.util.Optional; - import org.springframework.data.jpa.repository.JpaRepository; - import ssu.eatssu.domain.user.department.entity.Department; +import java.util.Optional; + public interface DepartmentRepository extends JpaRepository { - Optional findByName(String name); + Optional findByName(String name); } diff --git a/src/main/java/ssu/eatssu/domain/user/dto/DepartmentResponse.java b/src/main/java/ssu/eatssu/domain/user/dto/DepartmentResponse.java index ef62a91b..7df39fdb 100644 --- a/src/main/java/ssu/eatssu/domain/user/dto/DepartmentResponse.java +++ b/src/main/java/ssu/eatssu/domain/user/dto/DepartmentResponse.java @@ -6,5 +6,5 @@ @Getter @AllArgsConstructor public class DepartmentResponse { - private String departmentName; + private String departmentName; } 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 d5336984..ff8604c2 100644 --- a/src/main/java/ssu/eatssu/domain/user/dto/MyMealReviewResponse.java +++ b/src/main/java/ssu/eatssu/domain/user/dto/MyMealReviewResponse.java @@ -1,45 +1,63 @@ package ssu.eatssu.domain.user.dto; -import java.time.LocalDate; -import java.util.ArrayList; -import java.util.List; - import io.swagger.v3.oas.annotations.media.Schema; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Getter; 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.Collections; +import java.util.List; @AllArgsConstructor @Builder @Schema(title = "리뷰 상세 - 내 리뷰 리스트 조회 용") @Getter public class MyMealReviewResponse { - @Schema(description = "리뷰 식별자", example = "123") - Long reviewId; - - @Schema(description = "평점", example = "4") - private Integer rating; - - @Schema(description = "리뷰 작성 날짜(format = yyyy-MM-dd)", example = "2023-04-07") - private LocalDate writtenAt; - - @Schema(description = "리뷰 내용", example = "맛있습니당") - private String content; - - @Schema(description = "리뷰 이미지 url 리스트", example = "[\"imgurl1\", \"imgurl2\"]") - private List imageUrls; - - public static MyMealReviewResponse from(Review review) { - List imgUrlList = new ArrayList<>(); - review.getReviewImages().forEach(i -> imgUrlList.add(i.getImageUrl())); - - return MyMealReviewResponse.builder() - .reviewId(review.getId()) - .rating(review.getRating()) - .writtenAt(review.getCreatedDate().toLocalDate()) - .content(review.getContent()) - .imageUrls(imgUrlList) - .build(); - } + @Schema(description = "리뷰 식별자", example = "123") + Long reviewId; + + @Schema(description = "평점", example = "4") + private Integer rating; + + @Schema(description = "리뷰 작성 날짜(format = yyyy-MM-dd)", example = "2023-04-07") + private LocalDate writtenAt; + + @Schema(description = "리뷰 내용", example = "맛있습니당") + private String content; + + @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 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(); + + List menuNames = review.getMeal() == null ? Collections.singletonList(review.getMenu() + .getName()) : review.getMeal() + .getMenuNames(); + return MyMealReviewResponse + .builder() + .reviewId(review.getId()) + .rating(review.getRating()) + .writtenAt(review.getCreatedDate().toLocalDate()) + .content(review.getContent()) + .imageUrls(imgUrlList) + .likedMenuNames(likedMenuNames) + .menuNames(menuNames) + .build(); + } } diff --git a/src/main/java/ssu/eatssu/domain/user/dto/MyPageResponse.java b/src/main/java/ssu/eatssu/domain/user/dto/MyPageResponse.java index 22d0b19e..d5d7d529 100644 --- a/src/main/java/ssu/eatssu/domain/user/dto/MyPageResponse.java +++ b/src/main/java/ssu/eatssu/domain/user/dto/MyPageResponse.java @@ -11,9 +11,9 @@ @Schema(title = "마이페이지 정보") @Getter public class MyPageResponse { - @Schema(description = "닉네임", example = "피치푸치") - private String nickname; + @Schema(description = "닉네임", example = "피치푸치") + private String nickname; - @Schema(description = "연결 계정 정보", example = "피치푸치") - private OAuthProvider provider; + @Schema(description = "연결 계정 정보", example = "피치푸치") + private OAuthProvider provider; } diff --git a/src/main/java/ssu/eatssu/domain/user/dto/MyReviewDetail.java b/src/main/java/ssu/eatssu/domain/user/dto/MyReviewDetail.java index 72af9b1b..e45d05e3 100644 --- a/src/main/java/ssu/eatssu/domain/user/dto/MyReviewDetail.java +++ b/src/main/java/ssu/eatssu/domain/user/dto/MyReviewDetail.java @@ -1,59 +1,80 @@ package ssu.eatssu.domain.user.dto; -import java.time.LocalDate; -import java.util.ArrayList; -import java.util.List; - import io.swagger.v3.oas.annotations.media.Schema; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Getter; +import ssu.eatssu.domain.rating.entity.Ratings; import ssu.eatssu.domain.review.entity.Review; +import java.time.LocalDate; +import java.util.ArrayList; +import java.util.List; + @AllArgsConstructor @Builder @Schema(title = "리뷰 상세 - 내 리뷰 리스트 조회 용") @Getter public class MyReviewDetail { - @Schema(description = "리뷰 식별자", example = "123") - Long reviewId; + @Schema(description = "리뷰 식별자", example = "123") + Long reviewId; + + @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 mainRating; + @Schema(description = "리뷰 작성 날짜(format = yyyyMMdd)", example = "20230407") + private LocalDate writeDate; - @Schema(description = "평점-양", example = "4") - private Integer amountRating; + @Schema(description = "메뉴 이름", example = "돈까스") + private String menuName; - @Schema(description = "평점-맛", example = "4") - private Integer tasteRating; + @Schema(description = "리뷰 내용", example = "맛있습니당") + private String content; - @Schema(description = "리뷰 작성 날짜(format = yyyyMMdd)", example = "20230407") - private LocalDate writeDate; + @Schema(description = "리뷰 이미지 url 리스트", example = "[\"imgurl1\", \"imgurl2\"]") + private List imgUrlList; - @Schema(description = "메뉴 이름", example = "돈까스") - private String menuName; + public static MyReviewDetail from(Review review) { + List imgUrlList = new ArrayList<>(); + if (review.getReviewImages() != null) { + review.getReviewImages().forEach(image -> { + if (image != null && image.getImageUrl() != null) { + imgUrlList.add(image.getImageUrl()); + } + }); + } - @Schema(description = "리뷰 내용", example = "맛있습니당") - private String content; + Ratings ratings = review.getRatings(); + int mainRating = 0; + int amountRating = 0; + int tasteRating = 0; - @Schema(description = "리뷰 이미지 url 리스트", example = "[\"imgurl1\", \"imgurl2\"]") - private List imgUrlList; + if (ratings != null) { + mainRating = ratings.getMainRating() != null ? ratings.getMainRating() : 0; + amountRating = ratings.getAmountRating() != null ? ratings.getAmountRating() : 0; + tasteRating = ratings.getTasteRating() != null ? ratings.getTasteRating() : 0; + } - public static MyReviewDetail from(Review review) { + String menuName = review.getMenu() != null ? review.getMenu().getName() : null; + LocalDate writeDate = review.getCreatedDate() != null ? review.getCreatedDate().toLocalDate() : null; - List imgUrlList = new ArrayList<>(); - review.getReviewImages().forEach(i -> imgUrlList.add(i.getImageUrl())); + return MyReviewDetail.builder() + .reviewId(review.getId()) + .mainRating(mainRating) + .amountRating(amountRating) + .tasteRating(tasteRating) + .writeDate(writeDate) + .content(review.getContent()) + .imgUrlList(imgUrlList) + .menuName(menuName) + .build(); + } - return MyReviewDetail.builder() - .reviewId(review.getId()) - .mainRating(review.getRatings().getMainRating()) - .amountRating(review.getRatings().getAmountRating()) - .tasteRating(review.getRatings().getTasteRating()) - .writeDate(review.getCreatedDate().toLocalDate()) - .content(review.getContent()) - .imgUrlList(imgUrlList) - .menuName(review.getMenu().getName()) - .build(); - } } diff --git a/src/main/java/ssu/eatssu/domain/user/dto/NicknameUpdateRequest.java b/src/main/java/ssu/eatssu/domain/user/dto/NicknameUpdateRequest.java index f55579b0..10fcaef0 100644 --- a/src/main/java/ssu/eatssu/domain/user/dto/NicknameUpdateRequest.java +++ b/src/main/java/ssu/eatssu/domain/user/dto/NicknameUpdateRequest.java @@ -5,6 +5,6 @@ @Schema(title = "닉네임 수정") public record NicknameUpdateRequest( - @NotBlank(message = "닉네임을 입력해주세요.") @Schema(description = "닉네임", example = "jumukzzang") String nickname) { + @NotBlank(message = "닉네임을 입력해주세요.") @Schema(description = "닉네임", example = "jumukzzang") String nickname) { } diff --git a/src/main/java/ssu/eatssu/domain/user/dto/Tokens.java b/src/main/java/ssu/eatssu/domain/user/dto/Tokens.java index a97686a3..a10fbecf 100644 --- a/src/main/java/ssu/eatssu/domain/user/dto/Tokens.java +++ b/src/main/java/ssu/eatssu/domain/user/dto/Tokens.java @@ -1,22 +1,13 @@ package ssu.eatssu.domain.user.dto; import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Builder; -import lombok.Getter; @Schema(title = "jwt 토큰들") -@Getter -public class Tokens { +public record Tokens( + @Schema(description = "accessToken", example = "eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJ7XCJpZFwiOjEsXCJlbWFpbFwiOlwiZW1haWxAZW1haWwuY29tXCJ9IiwiYXV0aCI6IlJPTEVfVVNFUiIsImV4cCI6MTY3OTIyNzc0NX0.gM7F00Dh7OvybtEYODxYqFNATDDdquGCIivAeifNrEnF1ush3Fx1ChWqwD60U6Ek7rmJRU3CUUFAMLUrWDi4Aw") + String accessToken, - @Schema(description = "accessToken", example = "eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJ7XCJpZFwiOjEsXCJlbWFpbFwiOlwiZW1haWxAZW1haWwuY29tXCJ9IiwiYXV0aCI6IlJPTEVfVVNFUiIsImV4cCI6MTY3OTIyNzc0NX0.gM7F00Dh7OvybtEYODxYqFNATDDdquGCIivAeifNrEnF1ush3Fx1ChWqwD60U6Ek7rmJRU3CUUFAMLUrWDi4Aw") - private String accessToken; - - @Schema(description = "refreshToken", example = "eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJ7XCJpZFwiOjEsXCJlbWFpbFwiOlwiZW1haWxAZW1haWwuY29tXCJ9IiwiYXV0aCI6IlJPTEVfVVNFUiIsImV4cCI6MTY3OTgzMDc0NX0.OBtrGGKuSujBxwLTNVs-sc4eEH8uYiG8-Cwomqb_OgB9ADVbWbtSqaml9Ll34TFrKhPZuhMvzdchsWHqMQQ_kg") - private String refreshToken; - - @Builder - public Tokens(String accessToken, String refreshToken) { - this.accessToken = accessToken; - this.refreshToken = refreshToken; - } + @Schema(description = "refreshToken", example = "eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJ7XCJpZFwiOjEsXCJlbWFpbFwiOlwiZW1haWxAZW1haWwuY29tXCJ9IiwiYXV0aCI6IlJPTEVfVVNFUiIsImV4cCI6MTY3OTgzMDc0NX0.OBtrGGKuSujBxwLTNVs-sc4eEH8uYiG8-Cwomqb_OgB9ADVbWbtSqaml9Ll34TFrKhPZuhMvzdchsWHqMQQ_kg") + String refreshToken +) { } diff --git a/src/main/java/ssu/eatssu/domain/user/dto/UpdateDepartmentRequest.java b/src/main/java/ssu/eatssu/domain/user/dto/UpdateDepartmentRequest.java index 72acf3df..c93bd8b1 100644 --- a/src/main/java/ssu/eatssu/domain/user/dto/UpdateDepartmentRequest.java +++ b/src/main/java/ssu/eatssu/domain/user/dto/UpdateDepartmentRequest.java @@ -10,6 +10,6 @@ @AllArgsConstructor @NoArgsConstructor public class UpdateDepartmentRequest { - @Schema(description = "학과 이름", example = "소프트") - private String departmentName; + @Schema(description = "학과 이름", example = "소프트") + private String departmentName; } diff --git a/src/main/java/ssu/eatssu/domain/user/entity/BaseTimeEntity.java b/src/main/java/ssu/eatssu/domain/user/entity/BaseTimeEntity.java index 573e920c..c975acf3 100644 --- a/src/main/java/ssu/eatssu/domain/user/entity/BaseTimeEntity.java +++ b/src/main/java/ssu/eatssu/domain/user/entity/BaseTimeEntity.java @@ -1,27 +1,26 @@ package ssu.eatssu.domain.user.entity; -import java.time.LocalDateTime; - -import org.springframework.data.annotation.CreatedDate; -import org.springframework.data.annotation.LastModifiedDate; -import org.springframework.data.jpa.domain.support.AuditingEntityListener; - import jakarta.persistence.Column; import jakarta.persistence.EntityListeners; import jakarta.persistence.MappedSuperclass; import lombok.Getter; +import org.springframework.data.annotation.CreatedDate; +import org.springframework.data.annotation.LastModifiedDate; +import org.springframework.data.jpa.domain.support.AuditingEntityListener; + +import java.time.LocalDateTime; @Getter @MappedSuperclass @EntityListeners(AuditingEntityListener.class) public abstract class BaseTimeEntity { - @Column(nullable = false) - @CreatedDate - private LocalDateTime createdDate; + @Column(nullable = false) + @CreatedDate + private LocalDateTime createdDate; - @Column(nullable = false) - @LastModifiedDate - private LocalDateTime modifiedDate; + @Column(nullable = false) + @LastModifiedDate + private LocalDateTime modifiedDate; } \ No newline at end of file diff --git a/src/main/java/ssu/eatssu/domain/user/entity/Role.java b/src/main/java/ssu/eatssu/domain/user/entity/Role.java index ef27b76e..19780594 100644 --- a/src/main/java/ssu/eatssu/domain/user/entity/Role.java +++ b/src/main/java/ssu/eatssu/domain/user/entity/Role.java @@ -1,25 +1,24 @@ package ssu.eatssu.domain.user.entity; -import org.springframework.security.core.GrantedAuthority; - import lombok.Getter; +import org.springframework.security.core.GrantedAuthority; @Getter public enum Role implements GrantedAuthority { - USER("ROLE_USER", "유저"), - ADMIN("ROLE_ADMIN", "관리자"); + USER("ROLE_USER", "유저"), + ADMIN("ROLE_ADMIN", "관리자"); - private final String authority; - private final String description; + private final String authority; + private final String description; - Role(String authority, String description) { - this.authority = authority; - this.description = description; - } + Role(String authority, String description) { + this.authority = authority; + this.description = description; + } - @Override - public String getAuthority() { - return authority; - } + @Override + public String getAuthority() { + return authority; + } } diff --git a/src/main/java/ssu/eatssu/domain/user/entity/User.java b/src/main/java/ssu/eatssu/domain/user/entity/User.java index 5523bf44..58e0c6f0 100644 --- a/src/main/java/ssu/eatssu/domain/user/entity/User.java +++ b/src/main/java/ssu/eatssu/domain/user/entity/User.java @@ -1,10 +1,5 @@ package ssu.eatssu.domain.user.entity; -import java.util.ArrayList; -import java.util.List; - -import org.jetbrains.annotations.NotNull; - import jakarta.persistence.CascadeType; import jakarta.persistence.Column; import jakarta.persistence.Entity; @@ -20,6 +15,7 @@ import lombok.AccessLevel; import lombok.Getter; import lombok.NoArgsConstructor; +import org.jetbrains.annotations.NotNull; import ssu.eatssu.domain.auth.entity.OAuthProvider; import ssu.eatssu.domain.inquiry.entity.Inquiry; import ssu.eatssu.domain.partnership.entity.PartnershipLike; @@ -28,97 +24,87 @@ import ssu.eatssu.domain.review.entity.ReviewLike; import ssu.eatssu.domain.user.department.entity.Department; +import java.util.ArrayList; +import java.util.List; + @Entity @Getter @NoArgsConstructor(access = AccessLevel.PROTECTED) public class User extends BaseTimeEntity { - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - @Column(name = "user_id") - private Long id; - - @Enumerated(EnumType.STRING) - private Role role; - - @Column(unique = true) - private String email; - - private String nickname; - - @Enumerated(EnumType.STRING) - private OAuthProvider provider; - - private String providerId; - - private String credentials; - - @Enumerated(EnumType.STRING) - private UserStatus status; - - @OneToMany(mappedBy = "user", cascade = {CascadeType.PERSIST, CascadeType.DETACH, CascadeType.MERGE - , CascadeType.REFRESH}) - private List reviews = new ArrayList<>(); - - @OneToMany(mappedBy = "user", cascade = {CascadeType.ALL}) - private List reviewReports = new ArrayList<>(); - - @OneToMany(mappedBy = "user", cascade = {CascadeType.ALL}) - private List userInquiries = new ArrayList<>(); - - @OneToMany(mappedBy = "user", cascade = {CascadeType.ALL}, orphanRemoval = true) - private List reviewLikes = new ArrayList<>(); - - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "department_id") - private Department department; - - @OneToMany(mappedBy = "user", cascade = {CascadeType.ALL}, orphanRemoval = true) - private List partnershipLikes = new ArrayList<>(); - - /** - * Oauth 회원가입 용 생성자 - */ - private User(@NotNull String email, String nickname, @NotNull Role role, @NotNull OAuthProvider provider, - @NotNull String providerId, @NotNull UserStatus status, @NotNull String credentials) { - this.email = email; - this.nickname = nickname; - this.role = role; - this.provider = provider; - this.providerId = providerId; - this.status = status; - this.credentials = credentials; - } - - /** - * <--Static Factory Method--> - * Oauth 회원가입 - */ - public static User create(@NotNull String email, @NotNull String nickname, @NotNull OAuthProvider provider, - String providerId, - String credentials) { - return new User(email, nickname, Role.USER, provider, providerId, UserStatus.ACTIVE, credentials); - } - - /** - * <--Static Factory Method--> - * admin 회원가입 - * Role 은 다른 방법으로 세팅할 예정 - */ - public static User adminJoin(@NotNull String loginId, @NotNull String credentials) { - return new User(loginId, null, Role.USER, OAuthProvider.EATSSU, loginId, UserStatus.INACTIVE, credentials); - } - - public void updateNickname(@NotNull String nickname) { - this.nickname = nickname; - } - - public void updateEmail(String email) { - this.email = email; - } - - public void updateDepartment(Department department) { - this.department = department; - } + @OneToMany(mappedBy = "user", cascade = {CascadeType.PERSIST, CascadeType.DETACH, CascadeType.MERGE + , CascadeType.REFRESH}) + private final List reviews = new ArrayList<>(); + @OneToMany(mappedBy = "user", cascade = {CascadeType.ALL}) + private final List reviewReports = new ArrayList<>(); + @OneToMany(mappedBy = "user", cascade = {CascadeType.ALL}) + private final List userInquiries = new ArrayList<>(); + @OneToMany(mappedBy = "user", cascade = {CascadeType.ALL}, orphanRemoval = true) + private final List reviewLikes = new ArrayList<>(); + @OneToMany(mappedBy = "user", cascade = {CascadeType.ALL}, orphanRemoval = true) + private final List partnershipLikes = new ArrayList<>(); + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "user_id") + private Long id; + @Enumerated(EnumType.STRING) + private Role role; + @Column(unique = true) + private String email; + private String nickname; + @Enumerated(EnumType.STRING) + private OAuthProvider provider; + private String providerId; + private String credentials; + @Enumerated(EnumType.STRING) + private UserStatus status; + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "department_id") + private Department department; + + /** + * Oauth 회원가입 용 생성자 + */ + private User(@NotNull String email, String nickname, @NotNull Role role, @NotNull OAuthProvider provider, + @NotNull String providerId, @NotNull UserStatus status, @NotNull String credentials) { + this.email = email; + this.nickname = nickname; + this.role = role; + this.provider = provider; + this.providerId = providerId; + this.status = status; + this.credentials = credentials; + } + + /** + * <--Static Factory Method--> + * Oauth 회원가입 + */ + public static User create(@NotNull String email, @NotNull String nickname, @NotNull OAuthProvider provider, + String providerId, + String credentials) { + return new User(email, nickname, Role.USER, provider, providerId, UserStatus.ACTIVE, credentials); + } + + /** + * <--Static Factory Method--> + * admin 회원가입 + * Role 은 다른 방법으로 세팅할 예정 + */ + public static User adminJoin(@NotNull String loginId, @NotNull String credentials) { + return new User(loginId, null, Role.USER, OAuthProvider.EATSSU, loginId, UserStatus.INACTIVE, credentials); + } + + public void updateNickname(@NotNull String nickname) { + this.nickname = nickname; + } + + public void updateEmail(String email) { + this.email = email; + } + + public void updateDepartment(Department department) { + this.department = department; + } } diff --git a/src/main/java/ssu/eatssu/domain/user/entity/UserStatus.java b/src/main/java/ssu/eatssu/domain/user/entity/UserStatus.java index 3827d4e1..4f374c39 100644 --- a/src/main/java/ssu/eatssu/domain/user/entity/UserStatus.java +++ b/src/main/java/ssu/eatssu/domain/user/entity/UserStatus.java @@ -4,5 +4,5 @@ @Getter public enum UserStatus { - ACTIVE, INACTIVE + ACTIVE, INACTIVE } diff --git a/src/main/java/ssu/eatssu/domain/user/presentation/UserController.java b/src/main/java/ssu/eatssu/domain/user/presentation/UserController.java index 77d58615..83f064a1 100644 --- a/src/main/java/ssu/eatssu/domain/user/presentation/UserController.java +++ b/src/main/java/ssu/eatssu/domain/user/presentation/UserController.java @@ -1,7 +1,15 @@ package ssu.eatssu.domain.user.presentation; -import java.util.List; - +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.enums.ParameterIn; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; import org.springdoc.core.annotations.ParameterObject; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Sort; @@ -16,25 +24,12 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; - -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.Parameter; -import io.swagger.v3.oas.annotations.enums.ParameterIn; -import io.swagger.v3.oas.annotations.media.Content; -import io.swagger.v3.oas.annotations.media.Schema; -import io.swagger.v3.oas.annotations.responses.ApiResponse; -import io.swagger.v3.oas.annotations.responses.ApiResponses; -import io.swagger.v3.oas.annotations.tags.Tag; -import jakarta.validation.Valid; -import lombok.RequiredArgsConstructor; import ssu.eatssu.domain.auth.security.CustomUserDetails; import ssu.eatssu.domain.partnership.dto.PartnershipResponse; import ssu.eatssu.domain.partnership.service.PartnershipService; -import ssu.eatssu.domain.review.service.ReviewServiceV2; import ssu.eatssu.domain.slice.dto.SliceResponse; import ssu.eatssu.domain.slice.service.SliceService; import ssu.eatssu.domain.user.dto.DepartmentResponse; -import ssu.eatssu.domain.user.dto.MyMealReviewResponse; import ssu.eatssu.domain.user.dto.MyPageResponse; import ssu.eatssu.domain.user.dto.MyReviewDetail; import ssu.eatssu.domain.user.dto.NicknameUpdateRequest; @@ -42,172 +37,147 @@ import ssu.eatssu.domain.user.service.UserService; import ssu.eatssu.global.handler.response.BaseResponse; +import java.util.List; + @RestController @RequiredArgsConstructor @RequestMapping("/users") @Tag(name = "User", description = "유저 API") public class UserController { - private final UserService userService; - private final SliceService sliceService; - private final ReviewServiceV2 mealReviewService; - private final PartnershipService partnershipService; - - @Operation(summary = "이메일 중복 체크", description = """ - 이메일 중복 체크 API 입니다.

- 중복되지 않은 이메일이면 true 를 반환합니다 - """) - @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "중복되지 않은 이메일") - }) - @PostMapping("/validate/email/{email}") //todo: 중복인 경우 error throw, 중복 아니면 ApiReposne return - public BaseResponse validateDuplicatedEmail( - @Parameter(description = "이메일") @PathVariable String email) { - return BaseResponse.success(userService.validateDuplicatedEmail(email)); - } - - @Operation(summary = "닉네임 중복 체크", description = """ - 닉네임 중복 체크 API 입니다.

- 중복되지 않은 닉네임이면 true 를 반환합니다 - """) - @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "중복되지 않은 닉네임") - }) - @GetMapping("/validate/nickname") - public BaseResponse validateDuplicatedNickname(@Parameter(description = "닉네임") - @RequestParam(value = "nickname") String nickname) { - return BaseResponse.success(userService.validateDuplicatedNickname(nickname)); - } - - @Operation(summary = "닉네임 수정", description = "닉네임 수정 API 입니다.") - @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "닉네임 수정 성공"), - @ApiResponse(responseCode = "404", description = "존재하지 않는 유저", content = @Content(schema = @Schema(implementation = BaseResponse.class))) - }) - @PatchMapping("/nickname") - public BaseResponse updateNickname( - @Valid @RequestBody NicknameUpdateRequest updateNicknameRequest, - @AuthenticationPrincipal CustomUserDetails userDetails) { - userService.updateNickname(userDetails, updateNicknameRequest); - return BaseResponse.success(); - } - - @Operation(summary = "유저 탈퇴", description = "유저 탈퇴 API 입니다.") - @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "유저 탈퇴 성공"), - @ApiResponse(responseCode = "404", description = "존재하지 않는 유저", content = @Content(schema = @Schema(implementation = BaseResponse.class))) - }) - @DeleteMapping("") - public BaseResponse withdraw(@AuthenticationPrincipal CustomUserDetails userDetails) { - return BaseResponse.success(userService.withdraw(userDetails)); - } - - @Operation(summary = "내가 쓴 리뷰 리스트 조회", description = "내가 쓴 리뷰 리스트를 조회하는 API 입니다.") - @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "내가 쓴 리뷰 리스트 조회 성공"), - @ApiResponse(responseCode = "404", description = "존재하지 않는 유저", content = @Content(schema = @Schema(implementation = BaseResponse.class))) - }) - @GetMapping("/reviews") - public BaseResponse> getMyReviewList( - @Parameter(description = "마지막으로 조회된 reviewId값(첫 조회시 값 필요 없음)", in = ParameterIn.QUERY) @RequestParam(required = false) Long lastReviewId, - @ParameterObject @PageableDefault(size = 20, sort = "date", direction = Sort.Direction.DESC) Pageable pageable, - @AuthenticationPrincipal CustomUserDetails customUserDetails) { - SliceResponse myReviews = sliceService.findMyReviews(customUserDetails, - pageable, - lastReviewId); - return BaseResponse.success(myReviews); - } - - @Operation(summary = "내가 쓴 리뷰 리스트 조회 V2", description = "내가 쓴 리뷰 리스트를 조회하는 API V2 입니다.") - @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "내가 쓴 리뷰 리스트 조회 성공"), - @ApiResponse(responseCode = "404", description = "존재하지 않는 유저", content = @Content(schema = @Schema(implementation = BaseResponse.class))) - }) - @GetMapping("/v2/reviews") - public BaseResponse> getMyReviews( - @Parameter(description = "마지막으로 조회된 reviewId값(첫 조회시 값 필요 없음)", in = ParameterIn.QUERY) @RequestParam(required = false) Long lastReviewId, - @ParameterObject @PageableDefault(size = 20, sort = "date", direction = Sort.Direction.DESC) Pageable pageable, - @AuthenticationPrincipal CustomUserDetails customUserDetails) { - SliceResponse myReviews = mealReviewService.findMyReviews(customUserDetails, - lastReviewId, - pageable); - return BaseResponse.success(myReviews); - } - - @Operation(summary = "마이페이지 정보 조회", description = "마이페이지 정보를 조회하는 API 입니다.") - @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "마이페이지 정보 조회 성공"), - @ApiResponse(responseCode = "404", description = "존재하지 않는 유저", content = @Content(schema = @Schema(implementation = BaseResponse.class))) - }) - @GetMapping("/mypage") - public BaseResponse getMyPage( - @AuthenticationPrincipal CustomUserDetails customUserDetails) { - return BaseResponse.success(userService.findMyPage(customUserDetails)); - } - - @Operation(summary = "유저가 찜한 제휴 조회", description = "유저가 찜한 제휴를 조회하는 API 입니다.") - @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "유저가 찜한 제휴 조회 성공"), - @ApiResponse(responseCode = "404", description = "존재하지 않는 유저", content = @Content(schema = - @Schema(implementation = BaseResponse.class))), - }) - @GetMapping("/partnerships") - public BaseResponse> getUserLikedPartnerships( - @AuthenticationPrincipal CustomUserDetails userDetails) { - return BaseResponse.success(partnershipService.getUserLikedPartnerships(userDetails)); - } - - @Operation(summary = "유저의 학과 등록", description = "유저의 학과를 등록하는 API 입니다.") - @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "유저의 학과 등록 성공"), - @ApiResponse(responseCode = "404", description = "존재하지 않는 유저", content = @Content(schema = - @Schema(implementation = BaseResponse.class))), - @ApiResponse(responseCode = "404", description = "존재하지 않는 학과", content = @Content(schema = - @Schema(implementation = BaseResponse.class))), - }) - @PostMapping("/department") - public BaseResponse registerDepartment(@RequestBody UpdateDepartmentRequest request, - @AuthenticationPrincipal CustomUserDetails userDetails) { - userService.registerDepartment(request, userDetails); - return BaseResponse.success(); - } - - @Operation(summary = "유저의 단과대/학과 제휴 조회", description = "유저의 단과대/학과 제휴를 조회하는 API 입니다.") - @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "유저의 단과대/학과 제휴 조회 성공"), - @ApiResponse(responseCode = "404", description = "존재하지 않는 유저", content = @Content(schema = - @Schema(implementation = BaseResponse.class))), - @ApiResponse(responseCode = "400", description = "유저의 학과 정보가 등록되지 않음", content = @Content(schema = - @Schema(implementation = BaseResponse.class))), - }) - @GetMapping("/department/partnerships") - public BaseResponse> getUserDepartmentPartnerships( - @AuthenticationPrincipal CustomUserDetails userDetails) { - return BaseResponse.success(partnershipService.getUserDepartmentPartnerships(userDetails)); - } - - @Operation(summary = "학과 기입 여부 체크", description = """ - 학과 기입 여부 체크 API 입니다.

- 학과를 기입했으면 true, 아니면 false 를 반환합니다 - """) - @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "학과 기입함") - }) - @GetMapping("/validate/department") - public BaseResponse validateDepartmentExists(@AuthenticationPrincipal CustomUserDetails userDetails) { - return BaseResponse.success(userService.validateDepartmentExists(userDetails)); - } - - @Operation(summary = "유저의 학과 조회", description = "유저의 학과를 조회하는 API 입니다.") - @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "유저의 학과 조회 성공"), - @ApiResponse(responseCode = "404", description = "존재하지 않는 유저", content = @Content(schema = - @Schema(implementation = BaseResponse.class))), - @ApiResponse(responseCode = "400", description = "유저의 학과 정보가 등록되지 않음", content = @Content(schema = - @Schema(implementation = BaseResponse.class))), - }) - @GetMapping("/department") - public BaseResponse getDepartment(@AuthenticationPrincipal CustomUserDetails userDetails) { - return BaseResponse.success(userService.getDepartment(userDetails)); - } + private final UserService userService; + private final SliceService sliceService; + private final PartnershipService partnershipService; + + @Operation(summary = "이메일 중복 체크", description = """ + 이메일 중복 체크 API 입니다.

+ 중복되지 않은 이메일이면 true 를 반환합니다 + """) + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "중복되지 않은 이메일") + }) + @PostMapping("/validate/email/{email}") //todo: 중복인 경우 error throw, 중복 아니면 ApiReposne return + public BaseResponse validateDuplicatedEmail( + @Parameter(description = "이메일") @PathVariable String email) { + return BaseResponse.success(userService.validateDuplicatedEmail(email)); + } + + @Operation(summary = "닉네임 중복 체크", description = """ + 닉네임 중복 체크 API 입니다.

+ 중복되지 않은 닉네임이면 true 를 반환합니다 + """) + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "중복되지 않은 닉네임") + }) + @GetMapping("/validate/nickname") + public BaseResponse validateDuplicatedNickname(@Parameter(description = "닉네임") + @RequestParam(value = "nickname") String nickname) { + return BaseResponse.success(userService.validateDuplicatedNickname(nickname)); + } + + @Operation(summary = "닉네임 수정", description = "닉네임 수정 API 입니다.") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "닉네임 수정 성공"), + @ApiResponse(responseCode = "404", description = "존재하지 않는 유저", content = @Content(schema = @Schema(implementation = BaseResponse.class))) + }) + @PatchMapping("/nickname") + public BaseResponse updateNickname( + @Valid @RequestBody NicknameUpdateRequest updateNicknameRequest, + @AuthenticationPrincipal CustomUserDetails userDetails) { + userService.updateNickname(userDetails, updateNicknameRequest); + return BaseResponse.success(); + } + + @Operation(summary = "유저 탈퇴", description = "유저 탈퇴 API 입니다.") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "유저 탈퇴 성공"), + @ApiResponse(responseCode = "404", description = "존재하지 않는 유저", content = @Content(schema = @Schema(implementation = BaseResponse.class))) + }) + @DeleteMapping("") + public BaseResponse withdraw(@AuthenticationPrincipal CustomUserDetails userDetails) { + return BaseResponse.success(userService.withdraw(userDetails)); + } + + @Operation(summary = "내가 쓴 리뷰 리스트 조회", description = "내가 쓴 리뷰 리스트를 조회하는 API 입니다.") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "내가 쓴 리뷰 리스트 조회 성공"), + @ApiResponse(responseCode = "404", description = "존재하지 않는 유저", content = @Content(schema = @Schema(implementation = BaseResponse.class))) + }) + @GetMapping("/reviews") + public BaseResponse> getMyReviewList( + @Parameter(description = "마지막으로 조회된 reviewId값(첫 조회시 값 필요 없음)", in = ParameterIn.QUERY) @RequestParam(required = false) Long lastReviewId, + @ParameterObject @PageableDefault(size = 20, sort = "date", direction = Sort.Direction.DESC) Pageable pageable, + @AuthenticationPrincipal CustomUserDetails customUserDetails) { + SliceResponse myReviews = sliceService.findMyReviews(customUserDetails, + pageable, + lastReviewId); + return BaseResponse.success(myReviews); + } + + + @Operation(summary = "마이페이지 정보 조회", description = "마이페이지 정보를 조회하는 API 입니다.") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "마이페이지 정보 조회 성공"), + @ApiResponse(responseCode = "404", description = "존재하지 않는 유저", content = @Content(schema = @Schema(implementation = BaseResponse.class))) + }) + @GetMapping("/mypage") + public BaseResponse getMyPage( + @AuthenticationPrincipal CustomUserDetails customUserDetails) { + return BaseResponse.success(userService.findMyPage(customUserDetails)); + } + + @Operation(summary = "유저가 찜한 제휴 조회", description = "유저가 찜한 제휴를 조회하는 API 입니다.") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "유저가 찜한 제휴 조회 성공"), + @ApiResponse(responseCode = "404", description = "존재하지 않는 유저", content = @Content(schema = + @Schema(implementation = BaseResponse.class))), + }) + @GetMapping("/partnerships") + public BaseResponse> getUserLikedPartnerships( + @AuthenticationPrincipal CustomUserDetails userDetails) { + return BaseResponse.success(partnershipService.getUserLikedPartnerships(userDetails)); + } + + @Operation(summary = "유저의 학과 등록", description = "유저의 학과를 등록하는 API 입니다.") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "유저의 학과 등록 성공"), + @ApiResponse(responseCode = "404", description = "존재하지 않는 유저", content = @Content(schema = + @Schema(implementation = BaseResponse.class))), + @ApiResponse(responseCode = "404", description = "존재하지 않는 학과", content = @Content(schema = + @Schema(implementation = BaseResponse.class))), + }) + @PostMapping("/department") + public BaseResponse registerDepartment(@RequestBody UpdateDepartmentRequest request, + @AuthenticationPrincipal CustomUserDetails userDetails) { + userService.registerDepartment(request, userDetails); + return BaseResponse.success(); + } + + @Operation(summary = "유저의 단과대/학과 제휴 조회", description = "유저의 단과대/학과 제휴를 조회하는 API 입니다.") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "유저의 단과대/학과 제휴 조회 성공"), + @ApiResponse(responseCode = "404", description = "존재하지 않는 유저", content = @Content(schema = + @Schema(implementation = BaseResponse.class))), + @ApiResponse(responseCode = "400", description = "유저의 학과 정보가 등록되지 않음", content = @Content(schema = + @Schema(implementation = BaseResponse.class))), + }) + @GetMapping("/department/partnerships") + public BaseResponse> getUserDepartmentPartnerships( + @AuthenticationPrincipal CustomUserDetails userDetails) { + return BaseResponse.success(partnershipService.getUserDepartmentPartnerships(userDetails)); + } + + + @Operation(summary = "유저의 학과 조회", description = "유저의 학과를 조회하는 API 입니다.") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "유저의 학과 조회 성공"), + @ApiResponse(responseCode = "404", description = "존재하지 않는 유저", content = @Content(schema = + @Schema(implementation = BaseResponse.class))), + @ApiResponse(responseCode = "400", description = "유저의 학과 정보가 등록되지 않음", content = @Content(schema = + @Schema(implementation = BaseResponse.class))), + }) + @GetMapping("/department") + public BaseResponse getDepartment(@AuthenticationPrincipal CustomUserDetails userDetails) { + return BaseResponse.success(userService.getDepartment(userDetails)); + } } diff --git a/src/main/java/ssu/eatssu/domain/user/repository/UserRepository.java b/src/main/java/ssu/eatssu/domain/user/repository/UserRepository.java index 8dbf0a9d..ea4f9cf9 100644 --- a/src/main/java/ssu/eatssu/domain/user/repository/UserRepository.java +++ b/src/main/java/ssu/eatssu/domain/user/repository/UserRepository.java @@ -1,17 +1,16 @@ package ssu.eatssu.domain.user.repository; -import java.util.Optional; - import org.springframework.data.jpa.repository.JpaRepository; - import ssu.eatssu.domain.user.entity.User; +import java.util.Optional; + public interface UserRepository extends JpaRepository { - boolean existsByEmail(String email); + boolean existsByEmail(String email); - boolean existsByNickname(String nickname); + boolean existsByNickname(String nickname); - Optional findByEmail(String email); + Optional findByEmail(String email); - Optional findByProviderId(String providerId); + Optional findByProviderId(String providerId); } 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 cd9c4dd4..8dd68dc8 100644 --- a/src/main/java/ssu/eatssu/domain/user/service/UserService.java +++ b/src/main/java/ssu/eatssu/domain/user/service/UserService.java @@ -1,16 +1,11 @@ package ssu.eatssu.domain.user.service; -import static ssu.eatssu.global.handler.response.BaseResponseStatus.*; - -import java.util.UUID; - -import org.springframework.security.crypto.password.PasswordEncoder; -import org.springframework.stereotype.Component; -import org.springframework.stereotype.Service; - import jakarta.transaction.Transactional; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.stereotype.Component; +import org.springframework.stereotype.Service; import ssu.eatssu.domain.auth.entity.OAuthProvider; import ssu.eatssu.domain.auth.security.CustomUserDetails; import ssu.eatssu.domain.review.entity.Review; @@ -25,6 +20,12 @@ import ssu.eatssu.domain.user.repository.UserRepository; import ssu.eatssu.global.handler.response.BaseException; +import java.util.UUID; + +import static ssu.eatssu.global.handler.response.BaseResponseStatus.DUPLICATE_NICKNAME; +import static ssu.eatssu.global.handler.response.BaseResponseStatus.NOT_FOUND_DEPARTMENT; +import static ssu.eatssu.global.handler.response.BaseResponseStatus.NOT_FOUND_USER; + @Slf4j @Service @RequiredArgsConstructor @@ -32,95 +33,86 @@ @Component public class UserService { - private final UserRepository userRepository; - private final PasswordEncoder passwordEncoder; - private final DepartmentRepository departmentRepository; - private final UserProperties userProperties; - - public User join(String email, OAuthProvider provider, String providerId) { - String credentials = createCredentials(provider, providerId); - String nickname = createNickname(); - User user = User.create(email, nickname, provider, providerId, credentials); - return userRepository.save(user); - } - - public void updateNickname(CustomUserDetails userDetails, NicknameUpdateRequest request) { - User user = userRepository.findById(userDetails.getId()) - .orElseThrow(() -> new BaseException(NOT_FOUND_USER)); - - if (isForbiddenNickname(request.nickname()) || userRepository.existsByNickname(request.nickname())) { - throw new BaseException(DUPLICATE_NICKNAME); - } - - user.updateNickname(request.nickname()); - } - - public MyPageResponse findMyPage(CustomUserDetails userDetails) { - User user = userRepository.findById(userDetails.getId()) - .orElseThrow(() -> new BaseException(NOT_FOUND_USER)); - return new MyPageResponse(user.getNickname(), user.getProvider()); - } - - public boolean withdraw(CustomUserDetails userDetails) { - User user = userRepository.findById(userDetails.getId()) - .orElseThrow(() -> new BaseException(NOT_FOUND_USER)); - - user.getReviews().forEach(Review::clearUser); - user.getUserInquiries().forEach(inquiry -> inquiry.clearUser()); - userRepository.delete(user); - - return true; - } - - public Boolean validateDuplicatedEmail(String email) { - return !userRepository.existsByEmail(email); - } - - public Boolean validateDuplicatedNickname(String nickname) { - if (isForbiddenNickname(nickname)) { - return false; - } - return !userRepository.existsByNickname(nickname); - } - - public String createNickname() { - String uuid = UUID.randomUUID().toString(); - String shortUUID = uuid.substring(0, 4); - return "user-" + shortUUID; - } - - private String createCredentials(OAuthProvider provider, String providerId) { - return passwordEncoder.encode(provider + providerId); - } - - @Transactional - public void registerDepartment(UpdateDepartmentRequest request, CustomUserDetails userDetails) { - User user = userRepository.findById(userDetails.getId()) - .orElseThrow(() -> new BaseException(NOT_FOUND_USER)); - Department department = departmentRepository.findByName(request.getDepartmentName()) - .orElseThrow(() -> new BaseException(NOT_FOUND_DEPARTMENT)); - - user.updateDepartment(department); - } - - public Boolean validateDepartmentExists(CustomUserDetails userDetails) { - User user = userRepository.findById(userDetails.getId()) - .orElseThrow(() -> new BaseException(NOT_FOUND_USER)); - return user.getDepartment() != null; - } - - public DepartmentResponse getDepartment(CustomUserDetails userDetails) { - User user = userRepository.findById(userDetails.getId()) - .orElseThrow(() -> new BaseException(NOT_FOUND_USER)); - Department department = user.getDepartment(); - if (department == null) { - throw new BaseException(MISSING_USER_DEPARTMENT); - } - return new DepartmentResponse(department.getName()); - } - - private boolean isForbiddenNickname(String nickname) { - return userProperties.getForbiddenNicknames().stream() - .anyMatch(forbidden -> forbidden.equalsIgnoreCase(nickname)); - } + private final UserRepository userRepository; + private final PasswordEncoder passwordEncoder; + private final DepartmentRepository departmentRepository; + private final UserProperties userProperties; + + public User join(String email, OAuthProvider provider, String providerId) { + String credentials = createCredentials(provider, providerId); + String nickname = createNickname(); + User user = User.create(email, nickname, provider, providerId, credentials); + return userRepository.save(user); + } + + public void updateNickname(CustomUserDetails userDetails, NicknameUpdateRequest request) { + User user = userRepository.findById(userDetails.getId()) + .orElseThrow(() -> new BaseException(NOT_FOUND_USER)); + + if (isForbiddenNickname(request.nickname()) || userRepository.existsByNickname(request.nickname())) { + throw new BaseException(DUPLICATE_NICKNAME); + } + + user.updateNickname(request.nickname()); + } + + public MyPageResponse findMyPage(CustomUserDetails userDetails) { + User user = userRepository.findById(userDetails.getId()) + .orElseThrow(() -> new BaseException(NOT_FOUND_USER)); + return new MyPageResponse(user.getNickname(), user.getProvider()); + } + + public boolean withdraw(CustomUserDetails userDetails) { + User user = userRepository.findById(userDetails.getId()) + .orElseThrow(() -> new BaseException(NOT_FOUND_USER)); + + user.getReviews().forEach(Review::clearUser); + user.getUserInquiries().forEach(inquiry -> inquiry.clearUser()); + userRepository.delete(user); + + return true; + } + + public Boolean validateDuplicatedEmail(String email) { + return !userRepository.existsByEmail(email); + } + + public Boolean validateDuplicatedNickname(String nickname) { + if (isForbiddenNickname(nickname)) { + return false; + } + return !userRepository.existsByNickname(nickname); + } + + public String createNickname() { + String uuid = UUID.randomUUID().toString(); + String shortUUID = uuid.substring(0, 4); + return "user-" + shortUUID; + } + + private String createCredentials(OAuthProvider provider, String providerId) { + return passwordEncoder.encode(provider + providerId); + } + + @Transactional + public void registerDepartment(UpdateDepartmentRequest request, CustomUserDetails userDetails) { + User user = userRepository.findById(userDetails.getId()) + .orElseThrow(() -> new BaseException(NOT_FOUND_USER)); + Department department = departmentRepository.findByName(request.getDepartmentName()) + .orElseThrow(() -> new BaseException(NOT_FOUND_DEPARTMENT)); + + user.updateDepartment(department); + } + + public DepartmentResponse getDepartment(CustomUserDetails userDetails) { + User user = userRepository.findById(userDetails.getId()) + .orElseThrow(() -> new BaseException(NOT_FOUND_USER)); + Department department = user.getDepartment(); + return new DepartmentResponse(department != null ? department.getName() : ""); + } + + 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/domain/user/util/UserAliasUtil.java b/src/main/java/ssu/eatssu/domain/user/util/UserAliasUtil.java deleted file mode 100644 index b02c9efa..00000000 --- a/src/main/java/ssu/eatssu/domain/user/util/UserAliasUtil.java +++ /dev/null @@ -1,37 +0,0 @@ -package ssu.eatssu.domain.user.util; - -import java.util.List; -import java.util.Objects; - -import ssu.eatssu.domain.review.entity.Review; -import ssu.eatssu.domain.user.entity.User; - -public class UserAliasUtil { - public static String getUserAlias(User user, List reviews) { - if (reviews == null || reviews.isEmpty()) { - return "미슈테리 미식가"; - } - double avg = reviews.stream() - .map(Review::getRating) - .filter(Objects::nonNull) - .mapToInt(Integer::intValue) - .average() - .orElse(0.0); - - int avgRating = (int)Math.round(avg); - switch (avgRating) { - case 1: - return "깐깐슈"; - case 2: - return "아이슈"; - case 3: - return "밸런슈"; - case 4: - return "딱좋슈"; - case 5: - return "다 맛있슈"; - default: - return "미슈테리 미식가"; - } - } -} diff --git a/src/main/java/ssu/eatssu/global/config/QueryDslConfig.java b/src/main/java/ssu/eatssu/global/config/QueryDslConfig.java index 0ad66cbf..8262a5ce 100644 --- a/src/main/java/ssu/eatssu/global/config/QueryDslConfig.java +++ b/src/main/java/ssu/eatssu/global/config/QueryDslConfig.java @@ -1,22 +1,20 @@ package ssu.eatssu.global.config; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; - import com.querydsl.jpa.impl.JPAQueryFactory; - import jakarta.persistence.EntityManager; import jakarta.persistence.PersistenceContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; @Configuration public class QueryDslConfig { - @PersistenceContext - private EntityManager entityManager; + @PersistenceContext + private EntityManager entityManager; - @Bean - public JPAQueryFactory queryFactory() { - return new JPAQueryFactory(entityManager); - } + @Bean + public JPAQueryFactory queryFactory() { + return new JPAQueryFactory(entityManager); + } } diff --git a/src/main/java/ssu/eatssu/global/config/S3Config.java b/src/main/java/ssu/eatssu/global/config/S3Config.java index 092cc3a8..575d76dc 100644 --- a/src/main/java/ssu/eatssu/global/config/S3Config.java +++ b/src/main/java/ssu/eatssu/global/config/S3Config.java @@ -1,35 +1,34 @@ package ssu.eatssu.global.config; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; - import com.amazonaws.auth.AWSCredentials; import com.amazonaws.auth.AWSStaticCredentialsProvider; import com.amazonaws.auth.BasicAWSCredentials; import com.amazonaws.services.s3.AmazonS3Client; import com.amazonaws.services.s3.AmazonS3ClientBuilder; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; @Configuration public class S3Config { - @Value("${cloud.aws.credentials.accessKey}") - private String accessKey; + @Value("${cloud.aws.credentials.accessKey}") + private String accessKey; - @Value("${cloud.aws.credentials.secretKey}") - private String secretKey; + @Value("${cloud.aws.credentials.secretKey}") + private String secretKey; - @Value("${cloud.aws.region.static}") - private String region; + @Value("${cloud.aws.region.static}") + private String region; - @Bean - public AmazonS3Client amazonS3Client() { - AWSCredentials credentials = new BasicAWSCredentials(accessKey, secretKey); + @Bean + public AmazonS3Client amazonS3Client() { + AWSCredentials credentials = new BasicAWSCredentials(accessKey, secretKey); - return (AmazonS3Client)AmazonS3ClientBuilder - .standard() - .withCredentials(new AWSStaticCredentialsProvider(credentials)) - .withRegion(region) - .build(); - } + return (AmazonS3Client) AmazonS3ClientBuilder + .standard() + .withCredentials(new AWSStaticCredentialsProvider(credentials)) + .withRegion(region) + .build(); + } } diff --git a/src/main/java/ssu/eatssu/global/config/SwaggerConfig.java b/src/main/java/ssu/eatssu/global/config/SwaggerConfig.java index 12811e8f..531036c8 100644 --- a/src/main/java/ssu/eatssu/global/config/SwaggerConfig.java +++ b/src/main/java/ssu/eatssu/global/config/SwaggerConfig.java @@ -1,52 +1,51 @@ package ssu.eatssu.global.config; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -import org.springframework.beans.factory.annotation.Value; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; - import io.swagger.v3.oas.models.Components; import io.swagger.v3.oas.models.OpenAPI; import io.swagger.v3.oas.models.info.Info; import io.swagger.v3.oas.models.security.SecurityRequirement; import io.swagger.v3.oas.models.security.SecurityScheme; import io.swagger.v3.oas.models.servers.Server; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; @Configuration public class SwaggerConfig { - private static final String API_NAME = "EAT-SSU API"; - private static final String API_VERSION = "1.0.0"; - private static final String API_DESCRIPTION = "EAT-SSU API 명세서"; - - @Value("${swagger.url}") - private String SERVER_URL; - @Value("${swagger.description}") - private String SERVER_DESCRIPTION; - - @Bean - public OpenAPI openAPI() { - Info info = new Info() - .version(API_VERSION) - .title(API_NAME) - .description(API_DESCRIPTION); - - List servers = new ArrayList<>(); - servers.add(new Server().url(SERVER_URL).description(SERVER_DESCRIPTION)); - - SecurityScheme securityScheme = new SecurityScheme() - .type(SecurityScheme.Type.HTTP).scheme("bearer").bearerFormat("JWT") - .in(SecurityScheme.In.HEADER).name("Authorization"); - - SecurityRequirement securityRequirement = new SecurityRequirement().addList("bearerAuth"); - - return new OpenAPI() - .components(new Components().addSecuritySchemes("bearerAuth", securityScheme)) - .security(Collections.singletonList(securityRequirement)) - .servers(servers) - .info(info); - } + private static final String API_NAME = "EAT-SSU API"; + private static final String API_VERSION = "1.0.0"; + private static final String API_DESCRIPTION = "EAT-SSU API 명세서"; + + @Value("${swagger.url}") + private String SERVER_URL; + @Value("${swagger.description}") + private String SERVER_DESCRIPTION; + + @Bean + public OpenAPI openAPI() { + Info info = new Info() + .version(API_VERSION) + .title(API_NAME) + .description(API_DESCRIPTION); + + List servers = new ArrayList<>(); + servers.add(new Server().url(SERVER_URL).description(SERVER_DESCRIPTION)); + + SecurityScheme securityScheme = new SecurityScheme() + .type(SecurityScheme.Type.HTTP).scheme("bearer").bearerFormat("JWT") + .in(SecurityScheme.In.HEADER).name("Authorization"); + + SecurityRequirement securityRequirement = new SecurityRequirement().addList("bearerAuth"); + + return new OpenAPI() + .components(new Components().addSecuritySchemes("bearerAuth", securityScheme)) + .security(Collections.singletonList(securityRequirement)) + .servers(servers) + .info(info); + } } \ No newline at end of file diff --git a/src/main/java/ssu/eatssu/global/handler/ErrorTestController.java b/src/main/java/ssu/eatssu/global/handler/ErrorTestController.java index e4cfd34d..1d446190 100644 --- a/src/main/java/ssu/eatssu/global/handler/ErrorTestController.java +++ b/src/main/java/ssu/eatssu/global/handler/ErrorTestController.java @@ -5,8 +5,8 @@ @RestController public class ErrorTestController { - @GetMapping("/error-test") - public String triggerError() { - throw new RuntimeException("임의로 발생시킨 런타임 에러입니다!"); - } + @GetMapping("/error-test") + public String triggerError() { + throw new RuntimeException("임의로 발생시킨 런타임 에러입니다!"); + } } diff --git a/src/main/java/ssu/eatssu/global/handler/GlobalExceptionHandler.java b/src/main/java/ssu/eatssu/global/handler/GlobalExceptionHandler.java index 0e7b2c71..1935a8b4 100644 --- a/src/main/java/ssu/eatssu/global/handler/GlobalExceptionHandler.java +++ b/src/main/java/ssu/eatssu/global/handler/GlobalExceptionHandler.java @@ -1,6 +1,9 @@ package ssu.eatssu.global.handler; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.springframework.beans.TypeMismatchException; +import org.springframework.beans.factory.annotation.Value; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatusCode; @@ -22,11 +25,7 @@ import org.springframework.web.multipart.support.MissingServletRequestPartException; import org.springframework.web.servlet.NoHandlerFoundException; import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler; - -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import ssu.eatssu.domain.slack.entity.SlackMessageFormat; -import ssu.eatssu.domain.slack.service.SlackService; +import ssu.eatssu.domain.slack.service.SlackErrorNotifier; import ssu.eatssu.global.handler.response.BaseException; import ssu.eatssu.global.handler.response.BaseResponse; import ssu.eatssu.global.handler.response.BaseResponseStatus; @@ -38,225 +37,218 @@ @RestControllerAdvice @RequiredArgsConstructor public class GlobalExceptionHandler extends ResponseEntityExceptionHandler { - private final SlackService slackService; - - /** - * BaseException 처리 - */ - @ExceptionHandler(BaseException.class) - public ResponseEntity> handleBaseException(BaseException e) { - log.info(e.getStatus().toString()); - sendErrorToSlack(e); - return ResponseEntity.status(e.getStatus().getHttpStatus()).body(BaseResponse.fail(e.getStatus())); - } - @ExceptionHandler(Exception.class) - public ResponseEntity> handleAllUnhandledException(Exception ex) { - sendErrorToSlack(ex); - return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) - .body(BaseResponse.fail(BaseResponseStatus.INTERNAL_SERVER_ERROR)); - } - - - /** - * 경로는 있으나 지원하지 않는 http method로 요청 시 - */ - @Override - protected ResponseEntity handleHttpRequestMethodNotSupported( - @NonNull HttpRequestMethodNotSupportedException ex, - @NonNull HttpHeaders headers, - @NonNull HttpStatusCode status, - @NonNull WebRequest request) { - return ResponseEntity.status(HttpStatus.METHOD_NOT_ALLOWED).body(BaseResponse.fail( - BaseResponseStatus.METHOD_NOT_ALLOWED)); - } - - /** - * 지원하지 않는 content type으로 요청 시 - */ - @Override - protected ResponseEntity handleHttpMediaTypeNotSupported( - @NonNull HttpMediaTypeNotSupportedException ex, - @NonNull HttpHeaders headers, - @NonNull HttpStatusCode status, - @NonNull WebRequest request) { - return ResponseEntity.status(HttpStatus.UNSUPPORTED_MEDIA_TYPE) - .body(BaseResponse.fail(BaseResponseStatus.UNSUPPORTED_MEDIA_TYPE)); - } - - /** - * 인식할 수 없는 content type으로 요청 시 - */ - @Override - protected ResponseEntity handleHttpMediaTypeNotAcceptable( - @NonNull HttpMediaTypeNotAcceptableException ex, - @NonNull HttpHeaders headers, - @NonNull HttpStatusCode status, - @NonNull WebRequest request) { - return ResponseEntity.status(HttpStatus.NOT_ACCEPTABLE) - .body(BaseResponse.fail(BaseResponseStatus.NOT_ACCEPTABLE)); - } - - /** - * PathVariable 값 누락 시 - */ - @Override - protected ResponseEntity handleMissingPathVariable( - @NonNull MissingPathVariableException ex, - @NonNull HttpHeaders headers, - @NonNull HttpStatusCode status, - @NonNull WebRequest request) { - return ResponseEntity.status(HttpStatus.BAD_REQUEST) - .body(BaseResponse.fail(BaseResponseStatus.MISSING_PATH_VARIABLE)); - } - - /*** - * RequestParam 값 누락 시 - */ - @Override - protected ResponseEntity handleMissingServletRequestParameter( - @NonNull MissingServletRequestParameterException ex, - @NonNull HttpHeaders headers, - @NonNull HttpStatusCode status, - @NonNull WebRequest request) { - return ResponseEntity.status(HttpStatus.BAD_REQUEST) - .body(BaseResponse.fail(BaseResponseStatus.MISSING_REQUEST_PARAM)); - } - - /** - * RequestPart 값 누락 시 - */ - @Override - protected ResponseEntity handleMissingServletRequestPart( - @NonNull MissingServletRequestPartException ex, - @NonNull HttpHeaders headers, - @NonNull HttpStatusCode status, - @NonNull WebRequest request) { - return ResponseEntity.status(HttpStatus.BAD_REQUEST) - .body(BaseResponse.fail(BaseResponseStatus.MISSING_REQUEST_PART)); - } - - /** - * 요청 값 바인딩 처리에 실패한 경우 - */ - @Override - protected ResponseEntity handleServletRequestBindingException( - @NonNull ServletRequestBindingException ex, - @NonNull HttpHeaders headers, - @NonNull HttpStatusCode status, - @NonNull WebRequest request) { - return ResponseEntity.status(HttpStatus.BAD_REQUEST) - .body(BaseResponse.fail(BaseResponseStatus.REQ_BINDING_FAIL)); - } - - /** - * request @Valid 유효성 체크를 통과하지 못한 경우 - */ - @Override - protected ResponseEntity handleMethodArgumentNotValid( - @NonNull MethodArgumentNotValidException ex, - @NonNull HttpHeaders headers, - @NonNull HttpStatusCode status, - @NonNull WebRequest request) { - return ResponseEntity.status(HttpStatus.BAD_REQUEST) - .body(BaseResponse.fail(BaseResponseStatus.FAILED_VALIDATION)); - } - - /** - * Dispatcher Servlet에서 핸들러를 찾지 못한 경우 - *

기본적으로는 404-Not Found응답을 내리지만 Dispatcher Servlet의 throwExceptionIfNoHandlerFound 값이 true인 경우 - * NoHandlerFoundException 예외를 발생

- */ - @Override - protected ResponseEntity handleNoHandlerFoundException( - @NonNull NoHandlerFoundException ex, - @NonNull HttpHeaders headers, - @NonNull HttpStatusCode status, - @NonNull WebRequest request) { - return ResponseEntity.status(HttpStatus.NOT_FOUND) - .body(BaseResponse.fail(BaseResponseStatus.NOT_FOUND)); - } - - /** - * 비동기 요청의 응답시간이 초과될 경우 - */ - @Override - protected ResponseEntity handleAsyncRequestTimeoutException( - @NonNull AsyncRequestTimeoutException ex, - @NonNull HttpHeaders headers, - @NonNull HttpStatusCode status, - @NonNull WebRequest request) { - return ResponseEntity.status(HttpStatus.SERVICE_UNAVAILABLE) - .body(BaseResponse.fail(BaseResponseStatus.INTERNAL_SERVER_TIME_OUT)); - } - - /** - * 파라미터 타입 불일치가 발생한 경우 - */ - @Override - protected ResponseEntity handleTypeMismatch( - @NonNull TypeMismatchException ex, - @NonNull HttpHeaders headers, - @NonNull HttpStatusCode status, - @NonNull WebRequest request) { - return ResponseEntity.status(HttpStatus.BAD_REQUEST) - .body(BaseResponse.fail(BaseResponseStatus.MISMATCH_PARAM_TYPE)); - } - - /** - * 적절하지 않은 RequestBody 때문에 HttpMessageConverter.read 메소드 실패한 경우 - */ - @Override - protected ResponseEntity handleHttpMessageNotReadable( - @NonNull HttpMessageNotReadableException ex, - @NonNull HttpHeaders headers, - @NonNull HttpStatusCode status, - @NonNull WebRequest request) { - return ResponseEntity.status(HttpStatus.BAD_REQUEST) - .body(BaseResponse.fail(BaseResponseStatus.BAD_REQUEST)); - } - - /** - * 직렬화 실패한 경우 - */ - @Override - protected ResponseEntity handleHttpMessageNotWritable( - @NonNull HttpMessageNotWritableException ex, - @NonNull HttpHeaders headers, - @NonNull HttpStatusCode status, - @NonNull WebRequest request) { - return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) - .body(BaseResponse.fail(BaseResponseStatus.INTERNAL_SERVER_ERROR)); - } - - @Override - protected ResponseEntity handleExceptionInternal( - @NonNull Exception ex, - Object body, - @NonNull HttpHeaders headers, - @NonNull HttpStatusCode statusCode, - @NonNull WebRequest request) { - - HttpStatus status = HttpStatus.valueOf(statusCode.value()); - - if (status.is4xxClientError() || status.is5xxServerError()) { - sendErrorToSlack(ex); - } - - BaseResponseStatus responseStatus = status.is4xxClientError() - ? BaseResponseStatus.BAD_REQUEST - : BaseResponseStatus.INTERNAL_SERVER_ERROR; - - return ResponseEntity.status(status).body(BaseResponse.fail(responseStatus)); - } - - private void sendErrorToSlack(Exception ex) { - try { - String message = SlackMessageFormat.sendServerError(ex); - slackService.sendSlackMessage(message, ssu.eatssu.domain.slack.entity.SlackChannel.SERVER_ERROR); - } catch (Exception slackEx) { - log.warn("슬랙 전송 실패: {}", slackEx.getMessage()); - } - } + private final SlackErrorNotifier slackErrorNotifier; + @Value("${server.env:unknown}") + private String serverEnv; + + /** + * BaseException 처리 + */ + @ExceptionHandler(BaseException.class) + public ResponseEntity> handleBaseException(BaseException e) { + slackErrorNotifier.notify(e); + return ResponseEntity.status(e.getStatus().getHttpStatus()).body(BaseResponse.fail(e.getStatus())); + } + + @ExceptionHandler(Exception.class) + public ResponseEntity> handleAllUnhandledException(Exception ex) { + slackErrorNotifier.notify(new BaseException(BaseResponseStatus.BAD_REQUEST)); + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) + .body(BaseResponse.fail(BaseResponseStatus.INTERNAL_SERVER_ERROR)); + } + + + /** + * 경로는 있으나 지원하지 않는 http method로 요청 시 + */ + @Override + protected ResponseEntity handleHttpRequestMethodNotSupported( + @NonNull HttpRequestMethodNotSupportedException ex, + @NonNull HttpHeaders headers, + @NonNull HttpStatusCode status, + @NonNull WebRequest request) { + return ResponseEntity.status(HttpStatus.METHOD_NOT_ALLOWED).body(BaseResponse.fail( + BaseResponseStatus.METHOD_NOT_ALLOWED)); + } + + /** + * 지원하지 않는 content type으로 요청 시 + */ + @Override + protected ResponseEntity handleHttpMediaTypeNotSupported( + @NonNull HttpMediaTypeNotSupportedException ex, + @NonNull HttpHeaders headers, + @NonNull HttpStatusCode status, + @NonNull WebRequest request) { + return ResponseEntity.status(HttpStatus.UNSUPPORTED_MEDIA_TYPE) + .body(BaseResponse.fail(BaseResponseStatus.UNSUPPORTED_MEDIA_TYPE)); + } + + /** + * 인식할 수 없는 content type으로 요청 시 + */ + @Override + protected ResponseEntity handleHttpMediaTypeNotAcceptable( + @NonNull HttpMediaTypeNotAcceptableException ex, + @NonNull HttpHeaders headers, + @NonNull HttpStatusCode status, + @NonNull WebRequest request) { + return ResponseEntity.status(HttpStatus.NOT_ACCEPTABLE) + .body(BaseResponse.fail(BaseResponseStatus.NOT_ACCEPTABLE)); + } + + /** + * PathVariable 값 누락 시 + */ + @Override + protected ResponseEntity handleMissingPathVariable( + @NonNull MissingPathVariableException ex, + @NonNull HttpHeaders headers, + @NonNull HttpStatusCode status, + @NonNull WebRequest request) { + return ResponseEntity.status(HttpStatus.BAD_REQUEST) + .body(BaseResponse.fail(BaseResponseStatus.MISSING_PATH_VARIABLE)); + } + + /*** + * RequestParam 값 누락 시 + */ + @Override + protected ResponseEntity handleMissingServletRequestParameter( + @NonNull MissingServletRequestParameterException ex, + @NonNull HttpHeaders headers, + @NonNull HttpStatusCode status, + @NonNull WebRequest request) { + return ResponseEntity.status(HttpStatus.BAD_REQUEST) + .body(BaseResponse.fail(BaseResponseStatus.MISSING_REQUEST_PARAM)); + } + + /** + * RequestPart 값 누락 시 + */ + @Override + protected ResponseEntity handleMissingServletRequestPart( + @NonNull MissingServletRequestPartException ex, + @NonNull HttpHeaders headers, + @NonNull HttpStatusCode status, + @NonNull WebRequest request) { + return ResponseEntity.status(HttpStatus.BAD_REQUEST) + .body(BaseResponse.fail(BaseResponseStatus.MISSING_REQUEST_PART)); + } + + /** + * 요청 값 바인딩 처리에 실패한 경우 + */ + @Override + protected ResponseEntity handleServletRequestBindingException( + @NonNull ServletRequestBindingException ex, + @NonNull HttpHeaders headers, + @NonNull HttpStatusCode status, + @NonNull WebRequest request) { + return ResponseEntity.status(HttpStatus.BAD_REQUEST) + .body(BaseResponse.fail(BaseResponseStatus.REQ_BINDING_FAIL)); + } + + /** + * request @Valid 유효성 체크를 통과하지 못한 경우 + */ + @Override + protected ResponseEntity handleMethodArgumentNotValid( + @NonNull MethodArgumentNotValidException ex, + @NonNull HttpHeaders headers, + @NonNull HttpStatusCode status, + @NonNull WebRequest request) { + return ResponseEntity.status(HttpStatus.BAD_REQUEST) + .body(BaseResponse.fail(BaseResponseStatus.FAILED_VALIDATION)); + } + + /** + * Dispatcher Servlet에서 핸들러를 찾지 못한 경우 + *

기본적으로는 404-Not Found응답을 내리지만 Dispatcher Servlet의 throwExceptionIfNoHandlerFound 값이 true인 경우 + * NoHandlerFoundException 예외를 발생

+ */ + @Override + protected ResponseEntity handleNoHandlerFoundException( + @NonNull NoHandlerFoundException ex, + @NonNull HttpHeaders headers, + @NonNull HttpStatusCode status, + @NonNull WebRequest request) { + return ResponseEntity.status(HttpStatus.NOT_FOUND) + .body(BaseResponse.fail(BaseResponseStatus.NOT_FOUND)); + } + + /** + * 비동기 요청의 응답시간이 초과될 경우 + */ + @Override + protected ResponseEntity handleAsyncRequestTimeoutException( + @NonNull AsyncRequestTimeoutException ex, + @NonNull HttpHeaders headers, + @NonNull HttpStatusCode status, + @NonNull WebRequest request) { + return ResponseEntity.status(HttpStatus.SERVICE_UNAVAILABLE) + .body(BaseResponse.fail(BaseResponseStatus.INTERNAL_SERVER_TIME_OUT)); + } + + /** + * 파라미터 타입 불일치가 발생한 경우 + */ + @Override + protected ResponseEntity handleTypeMismatch( + @NonNull TypeMismatchException ex, + @NonNull HttpHeaders headers, + @NonNull HttpStatusCode status, + @NonNull WebRequest request) { + return ResponseEntity.status(HttpStatus.BAD_REQUEST) + .body(BaseResponse.fail(BaseResponseStatus.MISMATCH_PARAM_TYPE)); + } + + /** + * 적절하지 않은 RequestBody 때문에 HttpMessageConverter.read 메소드 실패한 경우 + */ + @Override + protected ResponseEntity handleHttpMessageNotReadable( + @NonNull HttpMessageNotReadableException ex, + @NonNull HttpHeaders headers, + @NonNull HttpStatusCode status, + @NonNull WebRequest request) { + return ResponseEntity.status(HttpStatus.BAD_REQUEST) + .body(BaseResponse.fail(BaseResponseStatus.BAD_REQUEST)); + } + + /** + * 직렬화 실패한 경우 + */ + @Override + protected ResponseEntity handleHttpMessageNotWritable( + @NonNull HttpMessageNotWritableException ex, + @NonNull HttpHeaders headers, + @NonNull HttpStatusCode status, + @NonNull WebRequest request) { + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) + .body(BaseResponse.fail(BaseResponseStatus.INTERNAL_SERVER_ERROR)); + } + + @Override + protected ResponseEntity handleExceptionInternal( + @NonNull Exception ex, + Object body, + @NonNull HttpHeaders headers, + @NonNull HttpStatusCode statusCode, + @NonNull WebRequest request) { + + HttpStatus status = HttpStatus.valueOf(statusCode.value()); + + if (status.is4xxClientError() || status.is5xxServerError()) { + slackErrorNotifier.notify(new BaseException(BaseResponseStatus.BAD_REQUEST)); + } + + BaseResponseStatus responseStatus = status.is4xxClientError() + ? BaseResponseStatus.BAD_REQUEST + : BaseResponseStatus.INTERNAL_SERVER_ERROR; + + return ResponseEntity.status(status).body(BaseResponse.fail(responseStatus)); + } } diff --git a/src/main/java/ssu/eatssu/global/handler/JwtAccessDeniedHandler.java b/src/main/java/ssu/eatssu/global/handler/JwtAccessDeniedHandler.java index ea2846e0..2ce13a9c 100644 --- a/src/main/java/ssu/eatssu/global/handler/JwtAccessDeniedHandler.java +++ b/src/main/java/ssu/eatssu/global/handler/JwtAccessDeniedHandler.java @@ -1,21 +1,19 @@ package ssu.eatssu.global.handler; -import java.io.IOException; - -import org.springframework.http.MediaType; -import org.springframework.security.access.AccessDeniedException; -import org.springframework.security.web.access.AccessDeniedHandler; -import org.springframework.stereotype.Component; - import com.fasterxml.jackson.databind.ObjectMapper; - import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; +import org.springframework.http.MediaType; +import org.springframework.security.access.AccessDeniedException; +import org.springframework.security.web.access.AccessDeniedHandler; +import org.springframework.stereotype.Component; import ssu.eatssu.global.handler.response.BaseResponse; import ssu.eatssu.global.handler.response.BaseResponseStatus; +import java.io.IOException; + /** * jwt 인가 실패 시 처리 * HttpStatus 403 @@ -24,21 +22,21 @@ @RequiredArgsConstructor public class JwtAccessDeniedHandler implements AccessDeniedHandler { - private final ObjectMapper objectMapper; + private final ObjectMapper objectMapper; - @Override - public void handle(HttpServletRequest request, HttpServletResponse response, - AccessDeniedException accessDeniedException) throws IOException, ServletException { + @Override + public void handle(HttpServletRequest request, HttpServletResponse response, + AccessDeniedException accessDeniedException) throws IOException, ServletException { - BaseResponseStatus responseStatus = BaseResponseStatus.FORBIDDEN; + BaseResponseStatus responseStatus = BaseResponseStatus.FORBIDDEN; - BaseResponse body = BaseResponse.fail(responseStatus); - String bodyString = objectMapper.writeValueAsString(body); + BaseResponse body = BaseResponse.fail(responseStatus); + String bodyString = objectMapper.writeValueAsString(body); - //response setting - response.setStatus(responseStatus.getHttpStatus().value()); - response.setContentType(MediaType.APPLICATION_JSON_VALUE); - response.setCharacterEncoding("UTF-8"); - response.getWriter().write(bodyString); - } + //response setting + response.setStatus(responseStatus.getHttpStatus().value()); + response.setContentType(MediaType.APPLICATION_JSON_VALUE); + response.setCharacterEncoding("UTF-8"); + response.getWriter().write(bodyString); + } } diff --git a/src/main/java/ssu/eatssu/global/handler/JwtAuthenticationEntryPoint.java b/src/main/java/ssu/eatssu/global/handler/JwtAuthenticationEntryPoint.java index 2555f4f2..f8032dc3 100644 --- a/src/main/java/ssu/eatssu/global/handler/JwtAuthenticationEntryPoint.java +++ b/src/main/java/ssu/eatssu/global/handler/JwtAuthenticationEntryPoint.java @@ -1,21 +1,19 @@ package ssu.eatssu.global.handler; -import java.io.IOException; - -import org.springframework.http.MediaType; -import org.springframework.security.core.AuthenticationException; -import org.springframework.security.web.AuthenticationEntryPoint; -import org.springframework.stereotype.Component; - import com.fasterxml.jackson.databind.ObjectMapper; - import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; +import org.springframework.http.MediaType; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.web.AuthenticationEntryPoint; +import org.springframework.stereotype.Component; import ssu.eatssu.global.handler.response.BaseResponse; import ssu.eatssu.global.handler.response.BaseResponseStatus; +import java.io.IOException; + /** * jwt 인증 실패 시 처리 * HttpStatus 401 @@ -24,21 +22,21 @@ @RequiredArgsConstructor public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint { - private final ObjectMapper objectMapper; + private final ObjectMapper objectMapper; - @Override - public void commence(HttpServletRequest request, HttpServletResponse response, - AuthenticationException authException) throws IOException, ServletException { + @Override + public void commence(HttpServletRequest request, HttpServletResponse response, + AuthenticationException authException) throws IOException, ServletException { - BaseResponseStatus responseStatus = BaseResponseStatus.UNAUTHORIZED; + BaseResponseStatus responseStatus = BaseResponseStatus.UNAUTHORIZED; - BaseResponse body = BaseResponse.fail(responseStatus); - String bodyString = objectMapper.writeValueAsString(body); + BaseResponse body = BaseResponse.fail(responseStatus); + String bodyString = objectMapper.writeValueAsString(body); - //response setting - response.setStatus(responseStatus.getHttpStatus().value()); - response.setContentType(MediaType.APPLICATION_JSON_VALUE); - response.setCharacterEncoding("UTF-8"); - response.getWriter().write(bodyString); - } + //response setting + response.setStatus(responseStatus.getHttpStatus().value()); + response.setContentType(MediaType.APPLICATION_JSON_VALUE); + response.setCharacterEncoding("UTF-8"); + response.getWriter().write(bodyString); + } } diff --git a/src/main/java/ssu/eatssu/global/handler/response/BaseException.java b/src/main/java/ssu/eatssu/global/handler/response/BaseException.java index a7aa52c1..6ab7f076 100644 --- a/src/main/java/ssu/eatssu/global/handler/response/BaseException.java +++ b/src/main/java/ssu/eatssu/global/handler/response/BaseException.java @@ -8,5 +8,5 @@ @Setter @AllArgsConstructor public class BaseException extends RuntimeException { - private final BaseResponseStatus status; + private final BaseResponseStatus status; } diff --git a/src/main/java/ssu/eatssu/global/handler/response/BaseResponse.java b/src/main/java/ssu/eatssu/global/handler/response/BaseResponse.java index 108e1e95..28411d04 100644 --- a/src/main/java/ssu/eatssu/global/handler/response/BaseResponse.java +++ b/src/main/java/ssu/eatssu/global/handler/response/BaseResponse.java @@ -2,7 +2,6 @@ import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonPropertyOrder; - import io.swagger.v3.oas.annotations.media.Schema; import lombok.AllArgsConstructor; import lombok.Getter; @@ -12,46 +11,46 @@ @JsonPropertyOrder({"isSuccess", "code", "message", "result"}) public class BaseResponse { - @Schema(description = "성공 여부") - private final Boolean isSuccess; - private final String message; - private final int code; - - @Schema(description = "결과 값") - @JsonInclude(JsonInclude.Include.NON_NULL) //JSON으로 응답이 나갈 때 null인 경우 포함되지 않음 - private T result; - - /** - * 정적 팩토리 메서드를 위한 private 생성자 - */ - private BaseResponse(BaseResponseStatus status) { - this.isSuccess = status.isSuccess(); - this.message = status.getMessage(); - this.code = status.getCode(); - } - - private BaseResponse(BaseResponseStatus status, T result) { - this.isSuccess = status.isSuccess(); - this.message = status.getMessage(); - this.code = status.getCode(); - this.result = result; - } - - /** - * API 성공 응답 - */ - public static BaseResponse success() { - return new BaseResponse<>(BaseResponseStatus.SUCCESS); - } - - public static BaseResponse success(T result) { - return new BaseResponse<>(BaseResponseStatus.SUCCESS, result); - } - - /** - * API 실패 응답 - */ - public static BaseResponse fail(BaseResponseStatus status) { - return new BaseResponse<>(status); - } + @Schema(description = "성공 여부") + private final Boolean isSuccess; + private final String message; + private final int code; + + @Schema(description = "결과 값") + @JsonInclude(JsonInclude.Include.NON_NULL) //JSON으로 응답이 나갈 때 null인 경우 포함되지 않음 + private T result; + + /** + * 정적 팩토리 메서드를 위한 private 생성자 + */ + private BaseResponse(BaseResponseStatus status) { + this.isSuccess = status.isSuccess(); + this.message = status.getMessage(); + this.code = status.getCode(); + } + + private BaseResponse(BaseResponseStatus status, T result) { + this.isSuccess = status.isSuccess(); + this.message = status.getMessage(); + this.code = status.getCode(); + this.result = result; + } + + /** + * API 성공 응답 + */ + public static BaseResponse success() { + return new BaseResponse<>(BaseResponseStatus.SUCCESS); + } + + public static BaseResponse success(T result) { + return new BaseResponse<>(BaseResponseStatus.SUCCESS, result); + } + + /** + * API 실패 응답 + */ + public static BaseResponse fail(BaseResponseStatus status) { + return new BaseResponse<>(status); + } } 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 cebc6da1..c17fc045 100644 --- a/src/main/java/ssu/eatssu/global/handler/response/BaseResponseStatus.java +++ b/src/main/java/ssu/eatssu/global/handler/response/BaseResponseStatus.java @@ -1,8 +1,7 @@ package ssu.eatssu.global.handler.response; -import org.springframework.http.HttpStatus; - import lombok.Getter; +import org.springframework.http.HttpStatus; /** * 에러 코드 관리 @@ -10,99 +9,100 @@ @Getter public enum BaseResponseStatus { - /** - * 1000 : 요청 성공 - */ - SUCCESS(true, HttpStatus.OK, 1000, "요청에 성공하였습니다."), - - /** - * 400 BAD_REQUEST 잘못된 요청 - */ - BAD_REQUEST(false, HttpStatus.BAD_REQUEST, 400, "잘못된 요청입니다."), - VALIDATION_ERROR(false, HttpStatus.BAD_REQUEST, 40001, "입력값을 확인해주세요."), - MISSING_PATH_VARIABLE(false, HttpStatus.BAD_REQUEST, 40002, "경로 변수가 누락되었습니다."), - MISSING_REQUEST_PARAM(false, HttpStatus.BAD_REQUEST, 40003, "쿼리 파라미터가 누락되었습니다."), - MISSING_REQUEST_PART(false, HttpStatus.BAD_REQUEST, 40004, "multipart/form-data 파일이 누락되었습니다."), - REQ_BINDING_FAIL(false, HttpStatus.BAD_REQUEST, 40005, "잘못된 request 입니다."), - MISMATCH_PARAM_TYPE(false, HttpStatus.BAD_REQUEST, 40006, "잘못된 파라미터 타입입니다."), - FAILED_VALIDATION(false, HttpStatus.BAD_REQUEST, 40007, "입력값이 누락되었거나, 부적절한 입력 값이 있습니다."), - INVALID_DATE(false, HttpStatus.BAD_REQUEST, 40008, "잘못된 날짜형식입니다."), - NOT_SUPPORT_RESTAURANT(false, HttpStatus.BAD_REQUEST, 40009, "해당 식당은 지원하지 않습니다."), - INVALID_IDENTITY_TOKEN(false, HttpStatus.BAD_REQUEST, 40010, "잘못된 identityToken 입니다."), - EXISTED_MEAL(false, HttpStatus.BAD_REQUEST, 40011, "이미 존재하는 식단입니다."), - INVALID_TARGET_TYPE(false, HttpStatus.BAD_REQUEST, 40012, "잘못된 targetType 입니다."), - MISSING_USER_DEPARTMENT(false, HttpStatus.BAD_REQUEST, 40013, "사용자의 학과 정보가 없습니다."), - - /** - * 401 UNAUTHORIZED 권한없음(인증 실패) - */ - UNAUTHORIZED(false, HttpStatus.UNAUTHORIZED, 401, "인증에 실패했습니다."), - INVALID_TOKEN(false, HttpStatus.UNAUTHORIZED, 40101, "유효하지 않은 토큰 입니다."), - - /** - * 403 FORBIDDEN 권한없음(인가 실패) - */ - FORBIDDEN(false, HttpStatus.FORBIDDEN, 403, "권한이 없습니다."), - REVIEW_PERMISSION_DENIED(false, HttpStatus.FORBIDDEN, 40301, "리뷰에 대한 권한이 없습니다."), - - /** - * 404 NOT_FOUND 잘못된 리소스 접근 - */ - NOT_FOUND(false, HttpStatus.NOT_FOUND, 404, "요청한 리소스를 찾을 수 없습니다."), - NOT_FOUND_USER(false, HttpStatus.NOT_FOUND, 40401, "해당 회원을 찾을 수 없습니다."), - NOT_FOUND_MENU(false, HttpStatus.NOT_FOUND, 40402, "해당 메뉴를 찾을 수 없습니다."), - NOT_FOUND_MEAL(false, HttpStatus.NOT_FOUND, 40403, "해당 식단을 찾을 수 없습니다."), - NOT_FOUND_RESTAURANT(false, HttpStatus.NOT_FOUND, 40404, "해당 식당을 찾을 수 없습니다."), - NOT_FOUND_REVIEW(false, HttpStatus.NOT_FOUND, 40405, "해당 리뷰을 찾을 수 없습니다."), - NOT_FOUND_REVIEW_REPORT(false, HttpStatus.NOT_FOUND, 40406, "해당 리뷰 신고 내역을 찾을 수 없습니다."), - NOT_FOUND_USER_INQUIRY(false, HttpStatus.NOT_FOUND, 40407, "해당 문의 내역을 찾을 수 없습니다."), - NOT_FOUND_COLLEGE(false, HttpStatus.NOT_FOUND, 40408, "해당 대학을 찾을 수 없습니다."), - NOT_FOUND_DEPARTMENT(false, HttpStatus.NOT_FOUND, 40409, "해당 학과를 찾을 수 없습니다."), - NOT_FOUND_PARTNERSHIP(false, HttpStatus.NOT_FOUND, 40410, "해당 제휴를 찾을 수 없습니다."), - - /** - * 405 METHOD_NOT_ALLOWED 지원하지 않은 method 호출 - */ - METHOD_NOT_ALLOWED(false, HttpStatus.METHOD_NOT_ALLOWED, 405, "해당 method는 지원하지 않습니다."), - - /** - * 406 NOT_ACCEPTABLE 인식할 수 없는 content type - */ - NOT_ACCEPTABLE(false, HttpStatus.NOT_ACCEPTABLE, 406, "인식할 수 없는 미디어 타입입니다."), - - /** - * 409 CONFLICT 중복된 리소스 - */ - CONFLICT(false, HttpStatus.CONFLICT, 409, "중복된 리소스를 입력했습니다."), - DUPLICATE_EMAIL(false, HttpStatus.CONFLICT, 40901, "중복된 이메일입니다."), - DUPLICATE_NICKNAME(false, HttpStatus.CONFLICT, 40902, "중복된 닉네임입니다."), - - /** - * 415 UNSUPPORTED_MEDIA_TYPE 지원하지 않는 content type - */ - UNSUPPORTED_MEDIA_TYPE(false, HttpStatus.UNSUPPORTED_MEDIA_TYPE, 415, "지원하지 않는 미디어 타입입니다."), - - /** - * 500 INTERNAL_SERVER_ERROR 서버 내부 에러 - */ - INTERNAL_SERVER_ERROR(false, HttpStatus.INTERNAL_SERVER_ERROR, 500, "서버 내부 에러입니다"), - FAIL_IMAGE_UPLOAD(false, HttpStatus.INTERNAL_SERVER_ERROR, 50001, "이미지 업로드에 실패했습니다."), - - /** - * 503 SERVICE_UNAVAILABLE 서버 내부 에러 - */ - SERVICE_UNAVAILABLE(false, HttpStatus.SERVICE_UNAVAILABLE, 503, "현재 서비스가 불가능한 상태입니다."), - INTERNAL_SERVER_TIME_OUT(false, HttpStatus.SERVICE_UNAVAILABLE, 503, "서버에서 시간초과가 발생했습니다."); - - private final boolean isSuccess; - private final HttpStatus httpStatus; - private final int code; - private final String message; - - BaseResponseStatus(boolean isSuccess, HttpStatus httpStatus, int code, String message) { - this.isSuccess = isSuccess; - this.httpStatus = httpStatus; - this.code = code; - this.message = message; - } + /** + * 1000 : 요청 성공 + */ + SUCCESS(true, HttpStatus.OK, 1000, "요청에 성공하였습니다."), + + /** + * 400 BAD_REQUEST 잘못된 요청 + */ + BAD_REQUEST(false, HttpStatus.BAD_REQUEST, 400, "잘못된 요청입니다."), + VALIDATION_ERROR(false, HttpStatus.BAD_REQUEST, 40001, "입력값을 확인해주세요."), + MISSING_PATH_VARIABLE(false, HttpStatus.BAD_REQUEST, 40002, "경로 변수가 누락되었습니다."), + MISSING_REQUEST_PARAM(false, HttpStatus.BAD_REQUEST, 40003, "쿼리 파라미터가 누락되었습니다."), + MISSING_REQUEST_PART(false, HttpStatus.BAD_REQUEST, 40004, "multipart/form-data 파일이 누락되었습니다."), + REQ_BINDING_FAIL(false, HttpStatus.BAD_REQUEST, 40005, "잘못된 request 입니다."), + MISMATCH_PARAM_TYPE(false, HttpStatus.BAD_REQUEST, 40006, "잘못된 파라미터 타입입니다."), + FAILED_VALIDATION(false, HttpStatus.BAD_REQUEST, 40007, "입력값이 누락되었거나, 부적절한 입력 값이 있습니다."), + INVALID_DATE(false, HttpStatus.BAD_REQUEST, 40008, "잘못된 날짜형식입니다."), + NOT_SUPPORT_RESTAURANT(false, HttpStatus.BAD_REQUEST, 40009, "해당 식당은 지원하지 않습니다."), + INVALID_IDENTITY_TOKEN(false, HttpStatus.BAD_REQUEST, 40010, "잘못된 identityToken 입니다."), + EXISTED_MEAL(false, HttpStatus.BAD_REQUEST, 40011, "이미 존재하는 식단입니다."), + INVALID_TARGET_TYPE(false, HttpStatus.BAD_REQUEST, 40012, "잘못된 targetType 입니다."), + MISSING_USER_DEPARTMENT(false, HttpStatus.BAD_REQUEST, 40013, "사용자의 학과 정보가 없습니다."), + + /** + * 401 UNAUTHORIZED 권한없음(인증 실패) + */ + UNAUTHORIZED(false, HttpStatus.UNAUTHORIZED, 401, "인증에 실패했습니다."), + INVALID_TOKEN(false, HttpStatus.UNAUTHORIZED, 401, "유효하지 않은 토큰 입니다."), + + /** + * 403 FORBIDDEN 권한없음(인가 실패) + */ + FORBIDDEN(false, HttpStatus.FORBIDDEN, 403, "권한이 없습니다."), + REVIEW_PERMISSION_DENIED(false, HttpStatus.FORBIDDEN, 40301, "리뷰에 대한 권한이 없습니다."), + + /** + * 404 NOT_FOUND 잘못된 리소스 접근 + */ + NOT_FOUND(false, HttpStatus.NOT_FOUND, 404, "요청한 리소스를 찾을 수 없습니다."), + NOT_FOUND_USER(false, HttpStatus.NOT_FOUND, 40401, "해당 회원을 찾을 수 없습니다."), + NOT_FOUND_MENU(false, HttpStatus.NOT_FOUND, 40402, "해당 메뉴를 찾을 수 없습니다."), + NOT_FOUND_MEAL(false, HttpStatus.NOT_FOUND, 40403, "해당 식단을 찾을 수 없습니다."), + NOT_FOUND_RESTAURANT(false, HttpStatus.NOT_FOUND, 40404, "해당 식당을 찾을 수 없습니다."), + NOT_FOUND_REVIEW(false, HttpStatus.NOT_FOUND, 40405, "해당 리뷰을 찾을 수 없습니다."), + NOT_FOUND_REVIEW_REPORT(false, HttpStatus.NOT_FOUND, 40406, "해당 리뷰 신고 내역을 찾을 수 없습니다."), + NOT_FOUND_USER_INQUIRY(false, HttpStatus.NOT_FOUND, 40407, "해당 문의 내역을 찾을 수 없습니다."), + NOT_FOUND_COLLEGE(false, HttpStatus.NOT_FOUND, 40408, "해당 대학을 찾을 수 없습니다."), + 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, "해당 제휴 식당을 찾을 수 없습니다."), + + /** + * 405 METHOD_NOT_ALLOWED 지원하지 않은 method 호출 + */ + METHOD_NOT_ALLOWED(false, HttpStatus.METHOD_NOT_ALLOWED, 405, "해당 method는 지원하지 않습니다."), + + /** + * 406 NOT_ACCEPTABLE 인식할 수 없는 content type + */ + NOT_ACCEPTABLE(false, HttpStatus.NOT_ACCEPTABLE, 406, "인식할 수 없는 미디어 타입입니다."), + + /** + * 409 CONFLICT 중복된 리소스 + */ + CONFLICT(false, HttpStatus.CONFLICT, 409, "중복된 리소스를 입력했습니다."), + DUPLICATE_EMAIL(false, HttpStatus.CONFLICT, 40901, "중복된 이메일입니다."), + DUPLICATE_NICKNAME(false, HttpStatus.CONFLICT, 40902, "중복된 닉네임입니다."), + + /** + * 415 UNSUPPORTED_MEDIA_TYPE 지원하지 않는 content type + */ + UNSUPPORTED_MEDIA_TYPE(false, HttpStatus.UNSUPPORTED_MEDIA_TYPE, 415, "지원하지 않는 미디어 타입입니다."), + + /** + * 500 INTERNAL_SERVER_ERROR 서버 내부 에러 + */ + INTERNAL_SERVER_ERROR(false, HttpStatus.INTERNAL_SERVER_ERROR, 500, "서버 내부 에러입니다"), + FAIL_IMAGE_UPLOAD(false, HttpStatus.INTERNAL_SERVER_ERROR, 50001, "이미지 업로드에 실패했습니다."), + + /** + * 503 SERVICE_UNAVAILABLE 서버 내부 에러 + */ + SERVICE_UNAVAILABLE(false, HttpStatus.SERVICE_UNAVAILABLE, 503, "현재 서비스가 불가능한 상태입니다."), + INTERNAL_SERVER_TIME_OUT(false, HttpStatus.SERVICE_UNAVAILABLE, 503, "서버에서 시간초과가 발생했습니다."); + + private final boolean isSuccess; + private final HttpStatus httpStatus; + private final int code; + private final String message; + + BaseResponseStatus(boolean isSuccess, HttpStatus httpStatus, int code, String message) { + this.isSuccess = isSuccess; + this.httpStatus = httpStatus; + this.code = code; + this.message = message; + } } diff --git a/src/main/java/ssu/eatssu/global/runner/WarmUpRunner.java b/src/main/java/ssu/eatssu/global/runner/WarmUpRunner.java index d31f7542..f749e873 100644 --- a/src/main/java/ssu/eatssu/global/runner/WarmUpRunner.java +++ b/src/main/java/ssu/eatssu/global/runner/WarmUpRunner.java @@ -1,10 +1,9 @@ package ssu.eatssu.global.runner; +import lombok.RequiredArgsConstructor; import org.springframework.boot.ApplicationArguments; import org.springframework.boot.ApplicationRunner; import org.springframework.stereotype.Component; - -import lombok.RequiredArgsConstructor; import ssu.eatssu.domain.auth.dto.AppleLoginRequest; import ssu.eatssu.domain.auth.dto.KakaoLoginRequest; import ssu.eatssu.domain.auth.service.OAuthService; @@ -13,20 +12,20 @@ @RequiredArgsConstructor public class WarmUpRunner implements ApplicationRunner { - private final OAuthService oAuthService; + private final OAuthService oAuthService; - @Override - public void run(ApplicationArguments args) throws Exception { - try{ - KakaoLoginRequest kakaoRequest = new KakaoLoginRequest("",""); - oAuthService.kakaoLogin(kakaoRequest); + @Override + public void run(ApplicationArguments args) throws Exception { + try { + KakaoLoginRequest kakaoRequest = new KakaoLoginRequest("", ""); + oAuthService.kakaoLogin(kakaoRequest); - AppleLoginRequest appleLoginRequest = new AppleLoginRequest(""); - oAuthService.appleLogin(appleLoginRequest); + AppleLoginRequest appleLoginRequest = new AppleLoginRequest(""); + oAuthService.appleLogin(appleLoginRequest); - }catch (Exception e){ + } catch (Exception e) { - } + } - } + } } diff --git a/src/main/java/ssu/eatssu/global/util/S3Uploader.java b/src/main/java/ssu/eatssu/global/util/S3Uploader.java index 2bbb7925..70d606bc 100644 --- a/src/main/java/ssu/eatssu/global/util/S3Uploader.java +++ b/src/main/java/ssu/eatssu/global/util/S3Uploader.java @@ -1,5 +1,14 @@ package ssu.eatssu.global.util; +import com.amazonaws.services.s3.AmazonS3Client; +import com.amazonaws.services.s3.model.CannedAccessControlList; +import com.amazonaws.services.s3.model.PutObjectRequest; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; +import org.springframework.web.multipart.MultipartFile; + import java.io.File; import java.io.FileOutputStream; import java.io.IOException; @@ -8,64 +17,53 @@ import java.util.Optional; import java.util.UUID; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.stereotype.Component; -import org.springframework.web.multipart.MultipartFile; - -import com.amazonaws.services.s3.AmazonS3Client; -import com.amazonaws.services.s3.model.CannedAccessControlList; -import com.amazonaws.services.s3.model.PutObjectRequest; - -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; - @RequiredArgsConstructor @Component @Slf4j public class S3Uploader { - private final AmazonS3Client amazonS3Client; + private final AmazonS3Client amazonS3Client; - @Value("${cloud.aws.s3.bucket}") - private String bucket; + @Value("${cloud.aws.s3.bucket}") + private String bucket; - public String upload(MultipartFile multipartFile, String dirName) throws IOException { - File uploadFile = convert(multipartFile) - .orElseThrow(() -> new IllegalArgumentException("MultipartFile -> File로 전환이 실패했습니다.")); - return upload(uploadFile, dirName); - } + public String upload(MultipartFile multipartFile, String dirName) throws IOException { + File uploadFile = convert(multipartFile) + .orElseThrow(() -> new IllegalArgumentException("MultipartFile -> File로 전환이 실패했습니다.")); + return upload(uploadFile, dirName); + } - private String upload(File uploadFile, String dirName) { - String fileName = dirName + "/" + UUID.randomUUID() + uploadFile.getName(); - String uploadImageUrl = putS3(uploadFile, fileName); - removeNewFile(uploadFile); - return uploadImageUrl; - } + private String upload(File uploadFile, String dirName) { + String fileName = dirName + "/" + UUID.randomUUID() + uploadFile.getName(); + String uploadImageUrl = putS3(uploadFile, fileName); + removeNewFile(uploadFile); + return uploadImageUrl; + } - private String putS3(File uploadFile, String fileName) { - amazonS3Client.putObject(new PutObjectRequest(bucket, fileName, uploadFile).withCannedAcl( - CannedAccessControlList.PublicRead)); - return amazonS3Client.getUrl(bucket, fileName).toString(); - } + private String putS3(File uploadFile, String fileName) { + amazonS3Client.putObject(new PutObjectRequest(bucket, fileName, uploadFile).withCannedAcl( + CannedAccessControlList.PublicRead)); + return amazonS3Client.getUrl(bucket, fileName).toString(); + } - private void removeNewFile(File targetFile) { - if (targetFile.delete()) { - log.info("파일이 삭제되었습니다."); - } else { - log.info("파일이 삭제되지 못했습니다."); - } - } + private void removeNewFile(File targetFile) { + if (targetFile.delete()) { + log.info("파일이 삭제되었습니다."); + } else { + log.info("파일이 삭제되지 못했습니다."); + } + } - private Optional convert(MultipartFile file) throws IOException { - String formatedNow = LocalDateTime.now() - .format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss")); - File convertFile = new File(formatedNow + file.getOriginalFilename()); - if (convertFile.createNewFile()) { - try (FileOutputStream fos = new FileOutputStream(convertFile)) { - fos.write(file.getBytes()); - } - return Optional.of(convertFile); - } - return Optional.empty(); - } + private Optional convert(MultipartFile file) throws IOException { + String formatedNow = LocalDateTime.now() + .format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss")); + File convertFile = new File(formatedNow + file.getOriginalFilename()); + if (convertFile.createNewFile()) { + try (FileOutputStream fos = new FileOutputStream(convertFile)) { + fos.write(file.getBytes()); + } + return Optional.of(convertFile); + } + return Optional.empty(); + } } \ No newline at end of file diff --git a/src/main/resources/application-dev.yml b/src/main/resources/application-dev.yml index 73e0008b..a5043661 100644 --- a/src/main/resources/application-dev.yml +++ b/src/main/resources/application-dev.yml @@ -25,12 +25,12 @@ spring: hibernate: ddl-auto: none properties: - hibernate: - jdbc: - lob: - non_contextual_creation: true - format_sql: false - show_sql: true + hibernate: + jdbc: + lob: + non_contextual_creation: true + format_sql: false + show_sql: true servlet: multipart: @@ -41,8 +41,8 @@ spring: jwt: secret: key: ${EATSSU_JWT_SECRET_DEV} - token-validity-in-seconds: 86400 - refresh-token-validity-in-seconds: 604800 + token-validity-in-seconds: 60 + refresh-token-validity-in-seconds: 180 #S3 cloud: @@ -51,11 +51,11 @@ cloud: accessKey: ${EATSSU_AWS_ACCESS_KEY_DEV} secretKey: ${EATSSU_AWS_SECRET_KEY_DEV} s3: - bucket: eatssu-bucket + bucket: eatssu-bucket region: - static: ap-northeast-2 + static: ap-northeast-2 stack: - auto: false + auto: false #Slack slack: diff --git a/src/main/resources/application-local.yml b/src/main/resources/application-local.yml index 1a4ffcd6..a85011b2 100644 --- a/src/main/resources/application-local.yml +++ b/src/main/resources/application-local.yml @@ -1,74 +1,62 @@ -## port number server: port: 9000 env: local spring: - ## Database datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: ${EATSSU_DB_URL_DEV} username: ${EATSSU_DB_USERNAME} password: ${EATSSU_DB_PASSWORD} - ## JPA jpa: hibernate: - ddl-auto: none + ddl-auto: update properties: - hibernate: - jdbc: - lob: - non_contextual_creation: true - format_sql: true - show_sql: false + hibernate: + jdbc: + lob: + non_contextual_creation: true + format_sql: true + show_sql: false servlet: multipart: max-file-size: 20MB max-request-size: 20MB -## Auth jwt: secret: key: ${EATSSU_JWT_SECRET_LOCAL} - token-validity-in-seconds: 86400 - refresh-token-validity-in-seconds: 259200 + token-validity-in-seconds: 10 + refresh-token-validity-in-seconds: 30 -#S3 cloud: aws: credentials: accessKey: ${EATSSU_AWS_ACCESS_KEY_DEV} secretKey: ${EATSSU_AWS_SECRET_KEY_DEV} s3: - bucket: eatssu-bucket + bucket: eatssu-bucket region: - static: ap-northeast-2 + static: ap-northeast-2 stack: - auto: false + auto: false -#Slack slack: token: ${EATSSU_SLACK_TOKEN} -#Swagger swagger: url: http://localhost:9000 description: Test Server Swagger API springdoc: swagger-ui: - # Swagger UI path: /swagger-ui.html - # Group groups-order: DESC - # API operationsSorter: method - # Swagger UI disable-swagger-default-url: true - # API display-request-duration: true api-docs: path: /v3/api-docs diff --git a/src/main/resources/application-prod.yml b/src/main/resources/application-prod.yml index 29d82927..3786480b 100644 --- a/src/main/resources/application-prod.yml +++ b/src/main/resources/application-prod.yml @@ -25,12 +25,12 @@ spring: hibernate: ddl-auto: none properties: - hibernate: - jdbc: - lob: - non_contextual_creation: true - format_sql: true - show_sql: false + hibernate: + jdbc: + lob: + non_contextual_creation: true + format_sql: true + show_sql: false servlet: multipart: @@ -51,11 +51,11 @@ cloud: accessKey: ${EATSSU_AWS_ACCESS_KEY_PROD} secretKey: ${EATSSU_AWS_SECRET_KEY_PROD} s3: - bucket: eatssu-prod-bucket + bucket: eatssu-prod-bucket region: - static: ap-northeast-2 + static: ap-northeast-2 stack: - auto: false + auto: false #Slack slack: diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index d2133024..fb673485 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -1,11 +1,40 @@ spring: profiles: - # include : prod - # include : dev - include : local - # include: test + active: local + config: + import: optional:file:.env[.properties] user: forbidden-nicknames: - EAT-SSU - EATSSU + - 잇슈 + - 읻슈 + - 잍슈 + - 잇쓔 + - 잇쓲 + - 잇씨유 + - 잇슈우 + - 잇슈웅 + - eat-ssu + - eatssu + - eatsu + - e4tssu + - 3at-ssu + - ea7-ssu + - "e @t-ssu" + - e.at.ssu + - e-a-t-s-s-u + - e_a_t_s_s_u + - "e a t ssu" + - eat_ssu + - eatssu_ + - eatssu123 + - "e @tssu" + - ēat-ssu + - 3atssu + - "eat$u" + - eat5su + - "eats$u" + - eats-u + - E4T슈 diff --git a/src/test/java/ssu/eatssu/EatSsuApplicationTests.java b/src/test/java/ssu/eatssu/EatSsuApplicationTests.java index d8ba04b6..0d956ea4 100644 --- a/src/test/java/ssu/eatssu/EatSsuApplicationTests.java +++ b/src/test/java/ssu/eatssu/EatSsuApplicationTests.java @@ -2,12 +2,14 @@ import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; @SpringBootTest +@ActiveProfiles("test") class EatSsuApplicationTests { - @Test - void contextLoads() { - } + @Test + void contextLoads() { + } } diff --git a/src/test/java/ssu/eatssu/domain/auth/infrastructure/TestAppleAuthenticator.java b/src/test/java/ssu/eatssu/domain/auth/infrastructure/TestAppleAuthenticator.java deleted file mode 100644 index cf22a4eb..00000000 --- a/src/test/java/ssu/eatssu/domain/auth/infrastructure/TestAppleAuthenticator.java +++ /dev/null @@ -1,12 +0,0 @@ -package ssu.eatssu.domain.auth.infrastructure; - -import ssu.eatssu.domain.auth.dto.OAuthInfo; -import ssu.eatssu.domain.auth.entity.AppleAuthenticator; - -public class TestAppleAuthenticator implements AppleAuthenticator { - - @Override - public OAuthInfo getOAuthInfoByIdentityToken(String identityToken) { - return new OAuthInfo("test@test.com", "1234567890"); - } -} diff --git a/src/test/java/ssu/eatssu/domain/auth/service/OAuthServiceTest.java b/src/test/java/ssu/eatssu/domain/auth/service/OAuthServiceTest.java new file mode 100644 index 00000000..b66bccee --- /dev/null +++ b/src/test/java/ssu/eatssu/domain/auth/service/OAuthServiceTest.java @@ -0,0 +1,533 @@ +package ssu.eatssu.domain.auth.service; + +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.SignatureAlgorithm; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.transaction.annotation.Transactional; +import ssu.eatssu.domain.auth.dto.AppleLoginRequest; +import ssu.eatssu.domain.auth.dto.KakaoLoginRequest; +import ssu.eatssu.domain.auth.dto.OAuthInfo; +import ssu.eatssu.domain.auth.dto.ValidRequest; +import ssu.eatssu.domain.auth.entity.AppleAuthenticator; +import ssu.eatssu.domain.auth.entity.OAuthProvider; +import ssu.eatssu.domain.auth.security.CustomUserDetails; +import ssu.eatssu.domain.user.dto.Tokens; +import ssu.eatssu.domain.user.entity.Role; +import ssu.eatssu.domain.user.entity.User; +import ssu.eatssu.domain.user.entity.UserStatus; +import ssu.eatssu.domain.user.repository.UserRepository; +import ssu.eatssu.domain.user.service.UserService; + +import javax.crypto.KeyGenerator; +import javax.crypto.SecretKey; +import java.security.NoSuchAlgorithmException; +import java.time.Instant; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.BDDMockito.given; + +@SpringBootTest +@ActiveProfiles("test") +@Transactional +class OAuthServiceTest { + + @Autowired + private OAuthService oAuthService; + + @Autowired + private UserRepository userRepository; + + @Autowired + private UserService userService; + + @MockBean + private AppleAuthenticator appleAuthenticator; + + @BeforeEach + void setUp() { + userRepository.deleteAll(); + } + + private String createMockAppleIdentityToken(String email, String providerId) { + try { + SecretKey key = KeyGenerator.getInstance("HmacSHA256").generateKey(); + + Map claims = new HashMap<>(); + claims.put("iss", "https://appleid.apple.com"); + claims.put("aud", "com.example.testapp"); + claims.put("exp", Instant.now().plusSeconds(3600).getEpochSecond()); + claims.put("iat", Instant.now().getEpochSecond()); + claims.put("sub", providerId); + claims.put("email", email); + claims.put("email_verified", "true"); + + Map header = new HashMap<>(); + header.put("kid", "test-key-id"); + header.put("alg", "HS256"); + + return Jwts.builder() + .setHeader(header) + .setClaims(claims) + .setIssuedAt(new Date()) + .setExpiration(new Date(System.currentTimeMillis() + 3600000)) + .signWith(key, SignatureAlgorithm.HS256) + .compact(); + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException("모의 Apple Identity Token 생성에 실패했습니다", e); + } + } + + @Test + @DisplayName("카카오 로그인 - 신규 사용자 가입 및 토큰 생성") + void kakaoLogin_신규사용자_토큰생성() { + // given + String email = "test@kakao.com"; + String providerId = "kakao123"; + KakaoLoginRequest request = new KakaoLoginRequest(email, providerId); + + // when + Tokens tokens = oAuthService.kakaoLogin(request); + + // then + assertThat(tokens).isNotNull(); + assertThat(tokens.accessToken()).isNotBlank(); + assertThat(tokens.refreshToken()).isNotBlank(); + + User savedUser = userRepository.findByProviderId(providerId).orElse(null); + assertThat(savedUser).isNotNull(); + assertThat(savedUser.getEmail()).isEqualTo(email); + assertThat(savedUser.getProvider()).isEqualTo(OAuthProvider.KAKAO); + assertThat(savedUser.getProviderId()).isEqualTo(providerId); + assertThat(savedUser.getRole()).isEqualTo(Role.USER); + assertThat(savedUser.getStatus()).isEqualTo(UserStatus.ACTIVE); + } + + @Test + @DisplayName("카카오 로그인 - 기존 사용자 로그인") + void kakaoLogin_기존사용자_로그인() { + // given + String email = "existing@kakao.com"; + String providerId = "existing123"; + User existingUser = userService.join(email, OAuthProvider.KAKAO, providerId); + existingUser.updateNickname("테스트닉네임"); + userRepository.save(existingUser); + + KakaoLoginRequest request = new KakaoLoginRequest(email, providerId); + + // when + Tokens tokens = oAuthService.kakaoLogin(request); + + // then + assertThat(tokens).isNotNull(); + assertThat(tokens.accessToken()).isNotBlank(); + assertThat(tokens.refreshToken()).isNotBlank(); + + long userCount = userRepository.count(); + assertThat(userCount).isEqualTo(1); + } + + @Test + @DisplayName("애플 로그인 - 신규 사용자 가입 및 토큰 생성") + void appleLogin_신규사용자_토큰생성() { + // given + String email = "test@apple.com"; + String providerId = "apple123"; + String identityToken = "mock.identity.token"; + AppleLoginRequest request = new AppleLoginRequest(identityToken); + + OAuthInfo mockOAuthInfo = new OAuthInfo(email, providerId); + given(appleAuthenticator.getOAuthInfoByIdentityToken(identityToken)).willReturn(mockOAuthInfo); + + // when + Tokens tokens = oAuthService.appleLogin(request); + + // then + assertThat(tokens).isNotNull(); + assertThat(tokens.accessToken()).isNotBlank(); + assertThat(tokens.refreshToken()).isNotBlank(); + + User savedUser = userRepository.findByProviderId(providerId).orElse(null); + assertThat(savedUser).isNotNull(); + assertThat(savedUser.getEmail()).isEqualTo(email); + assertThat(savedUser.getProvider()).isEqualTo(OAuthProvider.APPLE); + assertThat(savedUser.getProviderId()).isEqualTo(providerId); + } + + @Test + @DisplayName("애플 로그인 - 기존 사용자 로그인") + void appleLogin_기존사용자_로그인() { + // given + String email = "existing@apple.com"; + String providerId = "existing_apple123"; + String identityToken = "mock.identity.token"; + + User existingUser = userService.join(email, OAuthProvider.APPLE, providerId); + existingUser.updateNickname("기존애플사용자"); + userRepository.save(existingUser); + + AppleLoginRequest request = new AppleLoginRequest(identityToken); + OAuthInfo mockOAuthInfo = new OAuthInfo(email, providerId); + given(appleAuthenticator.getOAuthInfoByIdentityToken(identityToken)).willReturn(mockOAuthInfo); + + // when + Tokens tokens = oAuthService.appleLogin(request); + + // then + assertThat(tokens).isNotNull(); + assertThat(tokens.accessToken()).isNotBlank(); + assertThat(tokens.refreshToken()).isNotBlank(); + + long userCount = userRepository.count(); + assertThat(userCount).isEqualTo(1); + } + + @Test + @DisplayName("애플 로그인 - 프라이빗 릴레이 이메일에서 실제 이메일로 업데이트") + void appleLogin_프라이빗릴레이이메일_실제이메일업데이트() { + // given + String privateEmail = "abc123@privaterelay.appleid.com"; + String realEmail = "user@icloud.com"; + String providerId = "apple_private123"; + String identityToken = "mock.identity.token"; + + User existingUser = userService.join(privateEmail, OAuthProvider.APPLE, providerId); + existingUser.updateNickname("프라이빗사용자"); + userRepository.save(existingUser); + + AppleLoginRequest request = new AppleLoginRequest(identityToken); + OAuthInfo mockOAuthInfo = new OAuthInfo(realEmail, providerId); + given(appleAuthenticator.getOAuthInfoByIdentityToken(identityToken)).willReturn(mockOAuthInfo); + + // when + Tokens tokens = oAuthService.appleLogin(request); + + // then + assertThat(tokens).isNotNull(); + + User updatedUser = userRepository.findByProviderId(providerId).orElse(null); + assertThat(updatedUser).isNotNull(); + assertThat(updatedUser.getEmail()).isEqualTo(realEmail); + } + + @Test + @DisplayName("애플 로그인 - 실제 이메일에서 프라이빗 릴레이 이메일로 변경 시 업데이트 안됨") + void appleLogin_실제이메일_프라이빗릴레이이메일_업데이트안됨() { + // given + String realEmail = "user@icloud.com"; + String privateEmail = "xyz789@privaterelay.appleid.com"; + String providerId = "apple_real123"; + String identityToken = "mock.identity.token"; + + User existingUser = userService.join(realEmail, OAuthProvider.APPLE, providerId); + existingUser.updateNickname("실제사용자"); + userRepository.save(existingUser); + + AppleLoginRequest request = new AppleLoginRequest(identityToken); + OAuthInfo mockOAuthInfo = new OAuthInfo(privateEmail, providerId); + given(appleAuthenticator.getOAuthInfoByIdentityToken(identityToken)).willReturn(mockOAuthInfo); + + // when + Tokens tokens = oAuthService.appleLogin(request); + + // then + assertThat(tokens).isNotNull(); + + User updatedUser = userRepository.findByProviderId(providerId).orElse(null); + assertThat(updatedUser).isNotNull(); + assertThat(updatedUser.getEmail()).isEqualTo(realEmail); + } + + @Test + @DisplayName("토큰 새로고침 - 유효한 Authentication으로 새 토큰 생성") + void refreshTokens_유효한인증_새토큰생성() { + // given + String email = "refresh@test.com"; + String providerId = "refresh123"; + KakaoLoginRequest request = new KakaoLoginRequest(email, providerId); + Tokens originalTokens = oAuthService.kakaoLogin(request); + + User user = userRepository.findByProviderId(providerId).orElseThrow(); + CustomUserDetails userDetails = new CustomUserDetails(user); + Authentication authentication = new UsernamePasswordAuthenticationToken( + userDetails, + userDetails.getCredentials(), + userDetails.getAuthorities() + ); + + // when + Tokens refreshedTokens = oAuthService.refreshTokens(authentication); + + // then + assertThat(refreshedTokens).isNotNull(); + assertThat(refreshedTokens.accessToken()).isNotBlank(); + assertThat(refreshedTokens.refreshToken()).isNotBlank(); + } + + @Test + @DisplayName("유효한 토큰 검증 - 유효한 토큰") + void validToken_유효한토큰_true반환() { + // given + String email = "valid@test.com"; + String providerId = "valid123"; + KakaoLoginRequest request = new KakaoLoginRequest(email, providerId); + Tokens tokens = oAuthService.kakaoLogin(request); + + ValidRequest validRequest = new ValidRequest(tokens.accessToken()); + + // when + Boolean result = oAuthService.validToken(validRequest); + + // then + assertThat(result).isTrue(); + } + + @Test + @DisplayName("유효하지 않은 토큰 검증 - 무효한 토큰") + void validToken_무효한토큰_false반환() { + // given + String invalidToken = "invalid.jwt.token"; + ValidRequest request = new ValidRequest(invalidToken); + + // when + Boolean result = oAuthService.validToken(request); + + // then + assertThat(result).isFalse(); + } + + @Test + @DisplayName("토큰 검증 - 빈 토큰") + void validToken_빈토큰_false반환() { + // given + String emptyToken = ""; + ValidRequest request = new ValidRequest(emptyToken); + + // when + Boolean result = oAuthService.validToken(request); + + // then + assertThat(result).isFalse(); + } + + @Test + @DisplayName("토큰 검증 - null 토큰") + void validToken_null토큰_false반환() { + // given + ValidRequest request = new ValidRequest(null); + + // when + Boolean result = oAuthService.validToken(request); + + // then + assertThat(result).isFalse(); + } + + @ParameterizedTest + @DisplayName("다양한 providerId로 카카오 로그인 테스트") + @ValueSource(strings = {"kakao123", "kakao_test_456", "1234567890"}) + void kakaoLogin_다양한providerId_성공(String providerId) { + // given + String email = "test@example.com"; + KakaoLoginRequest request = new KakaoLoginRequest(email, providerId); + + // when + Tokens tokens = oAuthService.kakaoLogin(request); + + // then + assertThat(tokens).isNotNull(); + assertThat(tokens.accessToken()).isNotBlank(); + assertThat(tokens.refreshToken()).isNotBlank(); + + User savedUser = userRepository.findByProviderId(providerId).orElse(null); + assertThat(savedUser).isNotNull(); + assertThat(savedUser.getProviderId()).isEqualTo(providerId); + assertThat(savedUser.getProvider()).isEqualTo(OAuthProvider.KAKAO); + } + + @Test + @DisplayName("동일한 providerId로 중복 로그인 시도") + void kakaoLogin_중복providerId_기존사용자반환() { + // given + String email = "first@kakao.com"; + String providerId = "duplicate123"; + + KakaoLoginRequest firstRequest = new KakaoLoginRequest(email, providerId); + oAuthService.kakaoLogin(firstRequest); + + KakaoLoginRequest secondRequest = new KakaoLoginRequest("different@email.com", providerId); + + // when + Tokens secondTokens = oAuthService.kakaoLogin(secondRequest); + + // then + assertThat(secondTokens).isNotNull(); + long userCount = userRepository.count(); + assertThat(userCount).isEqualTo(1); + + User user = userRepository.findByProviderId(providerId).orElse(null); + assertThat(user).isNotNull(); + assertThat(user.getEmail()).isEqualTo(email); + } + + @ParameterizedTest + @DisplayName("프라이빗 릴레이 이메일 패턴 테스트") + @ValueSource(strings = { + "test123@privaterelay.appleid.com", + "a1b2c3d4e5f6@privaterelay.appleid.com", + "verylongusername@privaterelay.appleid.com" + }) + void appleLogin_프라이빗릴레이이메일패턴_인식확인(String privateEmail) { + // given + String providerId = "apple_private_test"; + String identityToken = "mock.identity.token"; + + AppleLoginRequest request = new AppleLoginRequest(identityToken); + OAuthInfo mockOAuthInfo = new OAuthInfo(privateEmail, providerId); + given(appleAuthenticator.getOAuthInfoByIdentityToken(identityToken)).willReturn(mockOAuthInfo); + + // when + Tokens tokens = oAuthService.appleLogin(request); + + // then + assertThat(tokens).isNotNull(); + + User savedUser = userRepository.findByProviderId(providerId).orElse(null); + assertThat(savedUser).isNotNull(); + assertThat(savedUser.getEmail()).isEqualTo(privateEmail); + } + + @Test + @DisplayName("실제 Identity Token을 사용한 Apple 로그인 테스트") + void appleLogin_실제IdentityToken_성공() { + // given + String email = "realtest@icloud.com"; + String providerId = "001234.abcd1234abcd1234abcd1234abcd1234.1234"; + String realIdentityToken = createMockAppleIdentityToken(email, providerId); + + AppleLoginRequest request = new AppleLoginRequest(realIdentityToken); + OAuthInfo mockOAuthInfo = new OAuthInfo(email, providerId); + given(appleAuthenticator.getOAuthInfoByIdentityToken(realIdentityToken)).willReturn(mockOAuthInfo); + + // when + Tokens tokens = oAuthService.appleLogin(request); + + // then + assertThat(tokens).isNotNull(); + assertThat(tokens.accessToken()).isNotBlank(); + assertThat(tokens.refreshToken()).isNotBlank(); + + User savedUser = userRepository.findByProviderId(providerId).orElse(null); + assertThat(savedUser).isNotNull(); + assertThat(savedUser.getEmail()).isEqualTo(email); + assertThat(savedUser.getProvider()).isEqualTo(OAuthProvider.APPLE); + assertThat(savedUser.getProviderId()).isEqualTo(providerId); + } + + @Test + @DisplayName("실제 Identity Token - 프라이빗 릴레이 이메일 테스트") + void appleLogin_실제IdentityToken_프라이빗릴레이이메일() { + // given + String privateEmail = "real123test@privaterelay.appleid.com"; + String providerId = "001234.private1234private1234private1234.5678"; + String realIdentityToken = createMockAppleIdentityToken(privateEmail, providerId); + + AppleLoginRequest request = new AppleLoginRequest(realIdentityToken); + OAuthInfo mockOAuthInfo = new OAuthInfo(privateEmail, providerId); + given(appleAuthenticator.getOAuthInfoByIdentityToken(realIdentityToken)).willReturn(mockOAuthInfo); + + // when + Tokens tokens = oAuthService.appleLogin(request); + + // then + assertThat(tokens).isNotNull(); + assertThat(tokens.accessToken()).isNotBlank(); + assertThat(tokens.refreshToken()).isNotBlank(); + + User savedUser = userRepository.findByProviderId(providerId).orElse(null); + assertThat(savedUser).isNotNull(); + assertThat(savedUser.getEmail()).isEqualTo(privateEmail); + assertThat(savedUser.getProvider()).isEqualTo(OAuthProvider.APPLE); + assertThat(savedUser.getProviderId()).isEqualTo(providerId); + } + + @Test + @DisplayName("실제 Identity Token - 이메일 업데이트 시나리오") + void appleLogin_실제IdentityToken_이메일업데이트() { + // given + String privateEmail = "update123test@privaterelay.appleid.com"; + String realEmail = "updated.user@icloud.com"; + String providerId = "001234.update1234update1234update1234.9012"; + + // 기존 사용자를 프라이빗 이메일로 생성 + User existingUser = userService.join(privateEmail, OAuthProvider.APPLE, providerId); + existingUser.updateNickname("업데이트테스트사용자"); + userRepository.save(existingUser); + + // 실제 이메일이 포함된 Identity Token 생성 + String realIdentityToken = createMockAppleIdentityToken(realEmail, providerId); + + AppleLoginRequest request = new AppleLoginRequest(realIdentityToken); + OAuthInfo mockOAuthInfo = new OAuthInfo(realEmail, providerId); + given(appleAuthenticator.getOAuthInfoByIdentityToken(realIdentityToken)).willReturn(mockOAuthInfo); + + // when + Tokens tokens = oAuthService.appleLogin(request); + + // then + assertThat(tokens).isNotNull(); + + User updatedUser = userRepository.findByProviderId(providerId).orElse(null); + assertThat(updatedUser).isNotNull(); + assertThat(updatedUser.getEmail()).isEqualTo(realEmail); // 프라이빗 이메일에서 실제 이메일로 업데이트 + assertThat(updatedUser.getProvider()).isEqualTo(OAuthProvider.APPLE); + assertThat(updatedUser.getProviderId()).isEqualTo(providerId); + + long userCount = userRepository.count(); + assertThat(userCount).isEqualTo(1); // 사용자는 한 명만 존재 + } + + @ParameterizedTest + @DisplayName("다양한 실제 Identity Token 패턴 테스트") + @ValueSource(strings = { + "test1@icloud.com", + "user.name@me.com", + "developer@mac.com", + "testuser123@privaterelay.appleid.com", + "a1b2c3d4e5@privaterelay.appleid.com" + }) + void appleLogin_다양한실제이메일패턴_성공(String email) { + // given + String providerId = "001234.pattern" + email.hashCode() + ".test"; + String realIdentityToken = createMockAppleIdentityToken(email, providerId); + + AppleLoginRequest request = new AppleLoginRequest(realIdentityToken); + OAuthInfo mockOAuthInfo = new OAuthInfo(email, providerId); + given(appleAuthenticator.getOAuthInfoByIdentityToken(realIdentityToken)).willReturn(mockOAuthInfo); + + // when + Tokens tokens = oAuthService.appleLogin(request); + + // then + assertThat(tokens).isNotNull(); + assertThat(tokens.accessToken()).isNotBlank(); + assertThat(tokens.refreshToken()).isNotBlank(); + + User savedUser = userRepository.findByProviderId(providerId).orElse(null); + assertThat(savedUser).isNotNull(); + assertThat(savedUser.getEmail()).isEqualTo(email); + assertThat(savedUser.getProvider()).isEqualTo(OAuthProvider.APPLE); + } +} \ No newline at end of file diff --git a/src/test/java/ssu/eatssu/domain/auth/service/OauthServiceTest.java b/src/test/java/ssu/eatssu/domain/auth/service/OauthServiceTest.java deleted file mode 100644 index 5ce77db1..00000000 --- a/src/test/java/ssu/eatssu/domain/auth/service/OauthServiceTest.java +++ /dev/null @@ -1,43 +0,0 @@ -package ssu.eatssu.domain.auth.service; - -import static org.junit.jupiter.api.Assertions.*; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.InjectMocks; -import org.mockito.Mock; -import org.mockito.Mockito; -import org.mockito.junit.jupiter.MockitoExtension; -import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; - -import ssu.eatssu.domain.auth.dto.ValidRequest; -import ssu.eatssu.domain.auth.security.JwtTokenProvider; -import ssu.eatssu.domain.auth.service.OAuthService; -import ssu.eatssu.domain.review.repository.ReviewRepository; -import ssu.eatssu.domain.user.repository.UserRepository; -import ssu.eatssu.domain.user.service.UserService; - -@ExtendWith(MockitoExtension.class) -class OauthServiceTest { - - @InjectMocks - private OAuthService oauthService; - - @Mock - private JwtTokenProvider jwtTokenProvider; - - @Test - void 토큰_유효성_검사() { - // given - String token = "eyJraWQiOiJXNldjT0tCIiwiYWxnIjoi"; - ValidRequest request = new ValidRequest(token); - Mockito.when(jwtTokenProvider.validateToken(token)).thenReturn(true); - - // when - Boolean result = oauthService.validToken(request); - - // then - assertTrue(result); - } -} diff --git a/src/test/java/ssu/eatssu/domain/menu/service/MealServiceTest.java b/src/test/java/ssu/eatssu/domain/menu/service/MealServiceTest.java index 7d1f252d..f76810ab 100644 --- a/src/test/java/ssu/eatssu/domain/menu/service/MealServiceTest.java +++ b/src/test/java/ssu/eatssu/domain/menu/service/MealServiceTest.java @@ -1,20 +1,11 @@ package ssu.eatssu.domain.menu.service; -import static org.assertj.core.api.Assertions.*; -import static ssu.eatssu.domain.menu.entity.constants.TimePart.*; - -import java.sql.Date; -import java.util.List; -import java.util.Optional; - import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.test.context.ActiveProfiles; import org.springframework.transaction.annotation.Transactional; - import ssu.eatssu.domain.menu.entity.Meal; import ssu.eatssu.domain.menu.entity.constants.TimePart; import ssu.eatssu.domain.menu.persistence.MealRepository; @@ -22,73 +13,79 @@ import ssu.eatssu.domain.menu.presentation.dto.response.MenusInMealResponse; import ssu.eatssu.domain.restaurant.entity.Restaurant; +import java.sql.Date; +import java.util.List; +import java.util.Optional; + +import static org.assertj.core.api.Assertions.assertThat; +import static ssu.eatssu.domain.menu.entity.constants.TimePart.LUNCH; + @SpringBootTest @DisplayName("MealService 테스트") -@ActiveProfiles("test") class MealServiceTest { - @Autowired - private MealService mealService; - - @Autowired - private MealRepository mealRepository; - - @BeforeEach - void setUp() { - mealRepository.deleteAll(); - } - - @Transactional - @Test - void 식단을_생성한다() { - // when - Long mealId = 식단_생성_요청(); - - // then - Optional meal = mealRepository.findById(mealId); - - assertThat(meal).isPresent(); - assertThat(mealRepository.findAll()).hasSize(1); - - Meal createdMeal = meal.get(); - assertThat(createdMeal.getMenuNames()).containsExactlyInAnyOrder("돈까스", "샐러드", "김치"); - assertThat(createdMeal.getRestaurant().name()).isEqualTo("HAKSIK"); - assertThat(createdMeal.getTimePart()).isEqualTo(LUNCH); - } - - private Long 식단_생성_요청() { - // given & when - Date date = Date.valueOf("2024-01-03"); - TimePart timePart = LUNCH; - Restaurant restaurant = Restaurant.from("HAKSIK"); - CreateMealRequest request = new CreateMealRequest(List.of("돈까스", "샐러드", "김치")); - - // then - return mealService.createMeal(date, restaurant, timePart, request); - } - - @Test - void 식단을_조회한다() { - // given - Long meadId = 식단_생성_요청(); - - // when - MenusInMealResponse response = mealService.getMenusInMealByMealId(meadId); - - // then - assertThat(response.getBriefMenus()).hasSize(3); - } - - @Test - void 식단을_삭제한다() { - // given - Long mealId = 식단_생성_요청(); - - // when - mealService.deleteByMealId(mealId); - - // then - assertThat(mealRepository.findAll()).hasSize(0); - assertThat(mealRepository.findAll()).hasSize(0); - } + @Autowired + private MealService mealService; + + @Autowired + private MealRepository mealRepository; + + @BeforeEach + void setUp() { + mealRepository.deleteAll(); + } + + @Transactional + @Test + void 식단을_생성한다() { + // when + Long mealId = 식단_생성_요청(); + + // then + Optional meal = mealRepository.findById(mealId); + + assertThat(meal).isPresent(); + assertThat(mealRepository.findAll()).hasSize(1); + + Meal createdMeal = meal.get(); + assertThat(createdMeal.getMenuNames()).containsExactlyInAnyOrder("돈까스", "샐러드", "김치"); + assertThat(createdMeal.getRestaurant().name()).isEqualTo("HAKSIK"); + assertThat(createdMeal.getTimePart()).isEqualTo(LUNCH); + } + + private Long 식단_생성_요청() { + // given & when + Date date = Date.valueOf("2024-01-03"); + TimePart timePart = LUNCH; + Restaurant restaurant = Restaurant.from("HAKSIK"); + CreateMealRequest request = new CreateMealRequest(List.of("돈까스", "샐러드", "김치")); + + // then + return mealService.createMeal(date, restaurant, timePart, request); + } + + @Test + void 식단을_조회한다() { + // given + Long meadId = 식단_생성_요청(); + + // when + MenusInMealResponse response = mealService.getMenusInMealByMealId(meadId); + + // then + assertThat(response.getBriefMenus()).hasSize(3); + } + + @Test + void 식단을_삭제한다() { + // given + Long mealId = 식단_생성_요청(); + + // when + mealService.deleteByMealId(mealId); + + // then + assertThat(mealRepository.findAll()).hasSize(0); + assertThat(mealRepository.findAll()).hasSize(0); + } } \ No newline at end of file diff --git a/src/test/java/ssu/eatssu/domain/menu/service/MenuServiceTest.java b/src/test/java/ssu/eatssu/domain/menu/service/MenuServiceTest.java index aeaafcc9..22ec1531 100644 --- a/src/test/java/ssu/eatssu/domain/menu/service/MenuServiceTest.java +++ b/src/test/java/ssu/eatssu/domain/menu/service/MenuServiceTest.java @@ -1,17 +1,10 @@ package ssu.eatssu.domain.menu.service; -import static org.assertj.core.api.Assertions.*; - -import java.util.ArrayList; -import java.util.List; - import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.test.context.ActiveProfiles; - import ssu.eatssu.domain.menu.entity.Menu; import ssu.eatssu.domain.menu.entity.MenuCategory; import ssu.eatssu.domain.menu.persistence.MenuCategoryRepository; @@ -19,46 +12,50 @@ import ssu.eatssu.domain.menu.presentation.dto.response.MenuRestaurantResponse; import ssu.eatssu.domain.restaurant.entity.Restaurant; +import java.util.ArrayList; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; + @SpringBootTest @DisplayName("MenuService 테스트") -@ActiveProfiles("test") class MenuServiceTest { - @Autowired - private MenuService menuService; + @Autowired + private MenuService menuService; - @Autowired - private MenuRepository menuRepository; + @Autowired + private MenuRepository menuRepository; - @Autowired - private MenuCategoryRepository menuCategoryRepository; + @Autowired + private MenuCategoryRepository menuCategoryRepository; - @BeforeEach - void setUp() { - menuRepository.deleteAll(); - } + @BeforeEach + void setUp() { + menuRepository.deleteAll(); + } - @Test - void 식당_이름으로_고정_메뉴를_조회한다() { - // given - List menus = new ArrayList<>(); - Restaurant foodCourt = Restaurant.from("FOOD_COURT"); + @Test + void 식당_이름으로_고정_메뉴를_조회한다() { + // given + List menus = new ArrayList<>(); + Restaurant foodCourt = Restaurant.from("FOOD_COURT"); - MenuCategory category1 = MenuCategory.builder().name("분식").restaurant(foodCourt).build(); - MenuCategory category2 = MenuCategory.builder().name("한식").restaurant(foodCourt).build(); + MenuCategory category1 = MenuCategory.builder().name("분식").restaurant(foodCourt).build(); + MenuCategory category2 = MenuCategory.builder().name("한식").restaurant(foodCourt).build(); - menus.add(Menu.createFixed("라면", foodCourt, 3000, category1)); - menus.add(Menu.createFixed("떡볶이", foodCourt, 5000, category2)); - menus.add(Menu.createFixed("짜게치", foodCourt, 4000, category1)); + menus.add(Menu.createFixed("라면", foodCourt, 3000, category1)); + menus.add(Menu.createFixed("떡볶이", foodCourt, 5000, category2)); + menus.add(Menu.createFixed("짜게치", foodCourt, 4000, category1)); - menuCategoryRepository.save(category1); - menuCategoryRepository.save(category2); - menuRepository.saveAll(menus); + menuCategoryRepository.save(category1); + menuCategoryRepository.save(category2); + menuRepository.saveAll(menus); - // when - MenuRestaurantResponse response = menuService.getMenusByRestaurant(foodCourt); + // when + MenuRestaurantResponse response = menuService.getMenusByRestaurant(foodCourt); - // then - assertThat(response.getCategoryMenuListCollection()).hasSize(2); - } + // then + assertThat(response.getCategoryMenuListCollection()).hasSize(2); + } } \ No newline at end of file diff --git a/src/test/java/ssu/eatssu/domain/report/service/ReportServiceTest.java b/src/test/java/ssu/eatssu/domain/report/service/ReportServiceTest.java index 89d3b443..12a08797 100644 --- a/src/test/java/ssu/eatssu/domain/report/service/ReportServiceTest.java +++ b/src/test/java/ssu/eatssu/domain/report/service/ReportServiceTest.java @@ -1,12 +1,11 @@ package ssu.eatssu.domain.report.service; +import jakarta.transaction.Transactional; import org.assertj.core.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; - -import jakarta.transaction.Transactional; import ssu.eatssu.domain.auth.entity.OAuthProvider; import ssu.eatssu.domain.auth.security.CustomUserDetails; import ssu.eatssu.domain.report.dto.ReportCreateRequest; @@ -21,58 +20,58 @@ @SpringBootTest class ReportServiceTest { - @Autowired - private ReportService reportService; + @Autowired + private ReportService reportService; - @Autowired - private ReviewRepository reviewRepository; + @Autowired + private ReviewRepository reviewRepository; - @Autowired - private ReportRepository reportRepository; + @Autowired + private ReportRepository reportRepository; - @Autowired - private UserRepository userRepository; + @Autowired + private UserRepository userRepository; - @BeforeEach - void setUp() { - reportRepository.deleteAll(); - reviewRepository.deleteAll(); - userRepository.deleteAll(); - } + @BeforeEach + void setUp() { + reportRepository.deleteAll(); + reviewRepository.deleteAll(); + userRepository.deleteAll(); + } - @Test - @Transactional - void 신고를_생성한다() { - //given - User user = createUser(); // 이 부분에서 User가 데이터베이스에 저장되었는지 확인 - Review review = createReview(user); - CustomUserDetails userDetails = createCustomUserDetails(user); - ReportCreateRequest request = new ReportCreateRequest( - review.getId(), ReportType.IMPROPER_ADVERTISEMENT, "음란성, 욕설 등 부적절한 내용"); + @Test + @Transactional + void 신고를_생성한다() { + //given + User user = createUser(); // 이 부분에서 User가 데이터베이스에 저장되었는지 확인 + Review review = createReview(user); + CustomUserDetails userDetails = createCustomUserDetails(user); + ReportCreateRequest request = new ReportCreateRequest( + review.getId(), ReportType.IMPROPER_ADVERTISEMENT, "음란성, 욕설 등 부적절한 내용"); - //when - Report createdReport = reportService.reportReview(userDetails, request); + //when + Report createdReport = reportService.reportReview(userDetails, request); - //then - Assertions.assertThat(reportRepository.findAll()).hasSize(1); - Assertions.assertThat(createdReport.getContent()) - .isEqualTo("음란성, 욕설 등 부적절한 내용"); - } + //then + Assertions.assertThat(reportRepository.findAll()).hasSize(1); + Assertions.assertThat(createdReport.getContent()) + .isEqualTo("음란성, 욕설 등 부적절한 내용"); + } - private User createUser() { - return User.create("test1@test.com", "user-test", OAuthProvider.EATSSU, "1234", "1234"); - } + private User createUser() { + return User.create("test1@test.com", "user-test", OAuthProvider.EATSSU, "1234", "1234"); + } - private CustomUserDetails createCustomUserDetails(User user) { - User savedUser = userRepository.save(user); - return new CustomUserDetails(savedUser); - } + private CustomUserDetails createCustomUserDetails(User user) { + User savedUser = userRepository.save(user); + return new CustomUserDetails(savedUser); + } - private Review createReview(User user) { - return reviewRepository.save(Review.builder() - .user(user) - .content("굿") - .build()); - } + private Review createReview(User user) { + return reviewRepository.save(Review.builder() + .user(user) + .content("굿") + .build()); + } } \ No newline at end of file diff --git a/src/test/java/ssu/eatssu/domain/review/service/ReviewServiceTest.java b/src/test/java/ssu/eatssu/domain/review/service/ReviewServiceTest.java index 727981f5..b2011705 100644 --- a/src/test/java/ssu/eatssu/domain/review/service/ReviewServiceTest.java +++ b/src/test/java/ssu/eatssu/domain/review/service/ReviewServiceTest.java @@ -1,12 +1,9 @@ package ssu.eatssu.domain.review.service; -import static org.assertj.core.api.Assertions.*; - import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; - import ssu.eatssu.domain.auth.entity.OAuthProvider; import ssu.eatssu.domain.auth.security.CustomUserDetails; import ssu.eatssu.domain.menu.entity.Menu; @@ -19,92 +16,94 @@ import ssu.eatssu.domain.user.entity.User; import ssu.eatssu.domain.user.repository.UserRepository; +import static org.assertj.core.api.Assertions.assertThat; + @SpringBootTest class ReviewServiceTest { - @Autowired - private ReviewService reviewService; - - @Autowired - private UserRepository userRepository; - - @Autowired - private MenuRepository menuRepository; - - @Autowired - private ReviewRepository reviewRepository; - - @BeforeEach - void setup() { - reviewRepository.deleteAll(); - userRepository.deleteAll(); - menuRepository.deleteAll(); - } - - @Test - void 리뷰를_생성한다() { - User user = createUser(); - CustomUserDetails userDetails = createCustomUserDetails(user); - Review createdReview = 리뷰_생성_요청(userDetails); - - // then - assertThat(reviewRepository.findAll()).hasSize(1); - assertThat(reviewRepository.findById(createdReview.getId()).orElseThrow().getContent()) - .isEqualTo("굿"); - } - - @Test - void 리뷰를_업데이트한다() { - // given - User user = createUser(); - CustomUserDetails userDetails = createCustomUserDetails(user); - Review createdReview = 리뷰_생성_요청(userDetails); - - // when - ReviewUpdateRequest request = new ReviewUpdateRequest(5, 5, 5, "굿굿"); - reviewService.updateReview(userDetails, createdReview.getId(), request); - - // then - assertThat(reviewRepository.findById(createdReview.getId()).orElseThrow().getContent()) - .isEqualTo("굿굿"); - } - - @Test - void 리뷰를_삭제한다() { - // given - User user = createUser(); - CustomUserDetails userDetails = createCustomUserDetails(user); - Review createdReview = 리뷰_생성_요청(userDetails); - - // when - reviewService.deleteReview(userDetails, createdReview.getId()); - - // then - assertThat(reviewRepository.findAll()).hasSize(0); - } - - private Review 리뷰_생성_요청(CustomUserDetails userDetails) { - // given - Menu menu = createMenu(); - ReviewCreateRequest request = new ReviewCreateRequest(4, 4, 4, "굿"); - - // when - Review createdReview = reviewService.createReview(userDetails, menu.getId(), - request, null); - return createdReview; - } - - private User createUser() { - return User.create("test@test.com", "user-test", OAuthProvider.EATSSU, "1234", "1234"); - } - - private CustomUserDetails createCustomUserDetails(User user) { - User savedUser = userRepository.save(user); - return new CustomUserDetails(savedUser); - } - - private Menu createMenu() { - Menu menu = Menu.createFixed("라면", Restaurant.FOOD_COURT, 3000, null); - return menuRepository.save(menu); - } + @Autowired + private ReviewService reviewService; + + @Autowired + private UserRepository userRepository; + + @Autowired + private MenuRepository menuRepository; + + @Autowired + private ReviewRepository reviewRepository; + + @BeforeEach + void setup() { + reviewRepository.deleteAll(); + userRepository.deleteAll(); + menuRepository.deleteAll(); + } + + @Test + void 리뷰를_생성한다() { + User user = createUser(); + CustomUserDetails userDetails = createCustomUserDetails(user); + Review createdReview = 리뷰_생성_요청(userDetails); + + // then + assertThat(reviewRepository.findAll()).hasSize(1); + assertThat(reviewRepository.findById(createdReview.getId()).orElseThrow().getContent()) + .isEqualTo("굿"); + } + + @Test + void 리뷰를_업데이트한다() { + // given + User user = createUser(); + CustomUserDetails userDetails = createCustomUserDetails(user); + Review createdReview = 리뷰_생성_요청(userDetails); + + // when + ReviewUpdateRequest request = new ReviewUpdateRequest(5, 5, 5, "굿굿"); + reviewService.updateReview(userDetails, createdReview.getId(), request); + + // then + assertThat(reviewRepository.findById(createdReview.getId()).orElseThrow().getContent()) + .isEqualTo("굿굿"); + } + + @Test + void 리뷰를_삭제한다() { + // given + User user = createUser(); + CustomUserDetails userDetails = createCustomUserDetails(user); + Review createdReview = 리뷰_생성_요청(userDetails); + + // when + reviewService.deleteReview(userDetails, createdReview.getId()); + + // then + assertThat(reviewRepository.findAll()).hasSize(0); + } + + private Review 리뷰_생성_요청(CustomUserDetails userDetails) { + // given + Menu menu = createMenu(); + ReviewCreateRequest request = new ReviewCreateRequest(4, 4, 4, "굿"); + + // when + Review createdReview = reviewService.createReview(userDetails, menu.getId(), + request, null); + return createdReview; + } + + private User createUser() { + return User.create("test@test.com", "user-test", OAuthProvider.EATSSU, "1234", "1234"); + } + + private CustomUserDetails createCustomUserDetails(User user) { + User savedUser = userRepository.save(user); + return new CustomUserDetails(savedUser); + } + + private Menu createMenu() { + Menu menu = Menu.createFixed("라면", Restaurant.FOOD_COURT, 3000, null); + return menuRepository.save(menu); + } } \ No newline at end of file diff --git a/src/test/java/ssu/eatssu/domain/user/service/UserServiceTest.java b/src/test/java/ssu/eatssu/domain/user/service/UserServiceTest.java index 9c612f23..9f49ce8d 100644 --- a/src/test/java/ssu/eatssu/domain/user/service/UserServiceTest.java +++ b/src/test/java/ssu/eatssu/domain/user/service/UserServiceTest.java @@ -1,13 +1,10 @@ package ssu.eatssu.domain.user.service; -import static org.assertj.core.api.Assertions.*; - import org.jetbrains.annotations.NotNull; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; - import ssu.eatssu.domain.auth.entity.OAuthProvider; import ssu.eatssu.domain.auth.security.CustomUserDetails; import ssu.eatssu.domain.review.repository.ReviewRepository; @@ -15,76 +12,78 @@ import ssu.eatssu.domain.user.entity.User; import ssu.eatssu.domain.user.repository.UserRepository; +import static org.assertj.core.api.Assertions.assertThat; + @SpringBootTest class UserServiceTest { - @Autowired - private UserService userService; - @Autowired - private UserRepository userRepository; - @Autowired - private ReviewRepository reviewRepository; - - @BeforeEach - void setUp() { - reviewRepository.deleteAll(); - userRepository.deleteAll(); - } - - @Test - void 회원가입을_한다() { - // given & when - 회원가입_요청(); - - // then - assertThat(userRepository.findAll()).hasSize(1); - } - - @Test - void 닉네임을_변경한다() { - // given - User user = 회원가입_요청(); - CustomUserDetails userDetails = UserDetails_생성(user); - - // when - userService.updateNickname(userDetails, new NicknameUpdateRequest("newNickname")); - - // then - user = userRepository.findById(userDetails.getId()).orElseThrow(); - assertThat(user.getNickname()).isEqualTo("newNickname"); - } - - @Test - void 회원탈퇴를_한다() { - // given - User user = 회원가입_요청(); - CustomUserDetails userDetails = UserDetails_생성(user); - - // when - userService.withdraw(userDetails); - - // then - assertThat(userRepository.findAll()).hasSize(0); - } - - @Test - void 중복_이메일을_확인한다() { - // given - User user = 회원가입_요청(); - - // when - boolean isDuplicated = userService.validateDuplicatedEmail(user.getEmail()); - - // then - assertThat(isDuplicated).isFalse(); - } - - private User 회원가입_요청() { - return userService.join("test@test.com", OAuthProvider.EATSSU, "1234"); - } - - @NotNull - private CustomUserDetails UserDetails_생성(User user) { - return new CustomUserDetails(user); - } + @Autowired + private UserService userService; + @Autowired + private UserRepository userRepository; + @Autowired + private ReviewRepository reviewRepository; + + @BeforeEach + void setUp() { + reviewRepository.deleteAll(); + userRepository.deleteAll(); + } + + @Test + void 회원가입을_한다() { + // given & when + 회원가입_요청(); + + // then + assertThat(userRepository.findAll()).hasSize(1); + } + + @Test + void 닉네임을_변경한다() { + // given + User user = 회원가입_요청(); + CustomUserDetails userDetails = UserDetails_생성(user); + + // when + userService.updateNickname(userDetails, new NicknameUpdateRequest("newNickname")); + + // then + user = userRepository.findById(userDetails.getId()).orElseThrow(); + assertThat(user.getNickname()).isEqualTo("newNickname"); + } + + @Test + void 회원탈퇴를_한다() { + // given + User user = 회원가입_요청(); + CustomUserDetails userDetails = UserDetails_생성(user); + + // when + userService.withdraw(userDetails); + + // then + assertThat(userRepository.findAll()).hasSize(0); + } + + @Test + void 중복_이메일을_확인한다() { + // given + User user = 회원가입_요청(); + + // when + boolean isDuplicated = userService.validateDuplicatedEmail(user.getEmail()); + + // then + assertThat(isDuplicated).isFalse(); + } + + private User 회원가입_요청() { + return userService.join("test@test.com", OAuthProvider.EATSSU, "1234"); + } + + @NotNull + private CustomUserDetails UserDetails_생성(User user) { + return new CustomUserDetails(user); + } } diff --git a/src/main/resources/application-test.yml b/src/test/resources/application-test.yml similarity index 57% rename from src/main/resources/application-test.yml rename to src/test/resources/application-test.yml index e6835a44..d2e73163 100644 --- a/src/main/resources/application-test.yml +++ b/src/test/resources/application-test.yml @@ -1,59 +1,52 @@ -## port number server: port: 9000 + env: test spring: - ## Database datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: ${EATSSU_DB_URL_TEST} - username: ${EATSSU_DB_USERNAME_TEST} - password: ${EATSSU_DB_PASSWORD_TEST} + username: ${EATSSU_DB_USERNAME} + password: ${EATSSU_DB_PASSWORD} - ## JPA jpa: hibernate: - ddl-auto: update + ddl-auto: create-drop properties: - hibernate: -# jdbc: - lob: - non_contextual_creation: true - format_sql: true - show_sql: true - dialect: org.hibernate.dialect.MySQLDialect + hibernate: + jdbc: + lob: + non_contextual_creation: true + format_sql: false + show_sql: false servlet: multipart: max-file-size: 20MB max-request-size: 20MB -## Auth jwt: secret: key: ${EATSSU_JWT_SECRET_TEST} token-validity-in-seconds: 86400 refresh-token-validity-in-seconds: 259200 -#S3 cloud: aws: credentials: - accessKey: ${EATSSU_AWS_ACCESS_KEY_PROD} - secretKey: ${EATSSU_AWS_SECRET_KEY_PROD} + accessKey: ${EATSSU_AWS_ACCESS_KEY_DEV} + secretKey: ${EATSSU_AWS_SECRET_KEY_DEV} s3: - bucket: eatssu-dev-bucket + bucket: eatssu-dev-bucket region: - static: ap-northeast-2 + static: ap-northeast-2 stack: - auto: false + auto: false -#Slack slack: token: ${EATSSU_SLACK_TOKEN} -#Swagger swagger: url: http://localhost:9000 description: Test Server Swagger API @@ -71,4 +64,21 @@ springdoc: default-consumes-media-type: application/json default-produces-media-type: application/json paths-to-match: - - /** \ No newline at end of file + - /** + +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/test/resources/application.yml b/src/test/resources/application.yml new file mode 100644 index 00000000..16b31d4b --- /dev/null +++ b/src/test/resources/application.yml @@ -0,0 +1,5 @@ +spring: + profiles: + active: test + config: + import: optional:file:.env[.properties] \ No newline at end of file