diff --git a/.github/workflows/wbc-actions.yaml b/.github/workflows/wbc-actions.yaml new file mode 100644 index 0000000..c6ddbab --- /dev/null +++ b/.github/workflows/wbc-actions.yaml @@ -0,0 +1,81 @@ +name: App with Github Actions, wbc + +on: + # workflow_dispatch + push: + branches: + - dev + +jobs: + ci-cd: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: Set up JDK 17 + uses: actions/setup-java@v4 + with: + java-version: '17' + distribution: 'temurin' + + - name: Make application.properties + run: | + cd ./src/main/resources + touch ./application.properties + echo "${{ secrets.PROPERTIES }}" > ./application.properties + shell: bash + + - name: Grant execute permission for Gradle Wrapper + run: chmod +x ./gradlew + + - name: Build with Gradle + run: ./gradlew clean build -x test + # env: + # GRADLE_OPTS: "-Dorg.gradle.daemon=false" + + - name: Upload build artifact + uses: actions/upload-artifact@v4 + with: + name: app + path: build/libs + + - name: Configure AWS credentials + uses: aws-actions/configure-aws-credentials@v4 + with: + aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} + aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + aws-region: ${{ secrets.AWS_REGION }} + + - name: Login to Amazon ECR + id: login-ecr + uses: aws-actions/amazon-ecr-login@v1 + + - name: Build, tag, and push image to Amazon ECR + id: build-image + env: + ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }} + ECR_REPOSITORY: ${{ secrets.AWS_ECR_REPOSITORY }} + IMAGE_TAG: ${{ github.sha }} + run: | + docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG . + docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG + + - name: Update application image version for ArgoCD + uses: actions/checkout@v4 + with: + repository: ${{ secrets.G_USER }}/${{ secrets.G_REPOSITORY }} + ref: main + token: ${{ secrets.G_TOKEN }} + + - name: Set up Image + run: | + sed -i "s%image: ${{ secrets.AWS_ECR_ID }}.dkr.ecr.${{ secrets.AWS_ECR_REGION }}.amazonaws.com/${{ secrets.AWS_ECR_REPOSITORY }}:[a-zA-Z0-9]*%image: ${{ secrets.AWS_ECR_ID }}.dkr.ecr.${{ secrets.AWS_ECR_REGION }}.amazonaws.com/${{ secrets.AWS_ECR_REPOSITORY }}:${{ github.sha }}%" ./manifest/wbc-app-manifest.yaml + + - name: Commit and push changes + run: | + git config --local user.email "${{ secrets.G_USER_EMAIL }}" + git config --local user.name "${{ secrets.G_USER_NAME }}" + git add . + git commit -m "Update application image version for ArgoCD" + git push diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..b658e66 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,4 @@ +FROM openjdk:17 +ARG JAR_FILE=build/libs/*.jar +COPY ${JAR_FILE} app.jar +ENTRYPOINT ["java", "-jar", "/app.jar"] \ No newline at end of file diff --git a/build.gradle b/build.gradle index 425d12c..b77cb77 100644 --- a/build.gradle +++ b/build.gradle @@ -54,10 +54,6 @@ dependencies { implementation platform('software.amazon.awssdk:bom:2.27.21') implementation 'io.awspring.cloud:spring-cloud-aws-starter-s3:3.0.2' implementation 'software.amazon.awssdk:s3' - - - - } tasks.named('test') { diff --git a/setting.md b/setting.md index 9ef2eee..527671d 100644 --- a/setting.md +++ b/setting.md @@ -1,4 +1,4 @@ -## db +git ## db - 로컬에 db 계정 먼저 해주세용 - user: wbc diff --git a/src/main/java/ce3/wbc/WbcApplication.java b/src/main/java/ce3/wbc/WbcApplication.java index 38f476a..4cedc68 100644 --- a/src/main/java/ce3/wbc/WbcApplication.java +++ b/src/main/java/ce3/wbc/WbcApplication.java @@ -2,7 +2,9 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.data.jpa.repository.config.EnableJpaAuditing; +@EnableJpaAuditing @SpringBootApplication public class WbcApplication { diff --git a/src/main/java/ce3/wbc/config/S3Config.java b/src/main/java/ce3/wbc/config/S3Config.java index 143459b..15141d6 100644 --- a/src/main/java/ce3/wbc/config/S3Config.java +++ b/src/main/java/ce3/wbc/config/S3Config.java @@ -25,7 +25,7 @@ public class S3Config { @Bean public S3Client s3client() { - // S3 사용 인증 객체 + // S3 사용 인증 객체 //AWSCredentials credentials = new BasicAWSCredentials(awsAccessKey, awsSecretKey); // 리전 정보 입력 -> S3 사용 객체 생성 diff --git a/src/main/java/ce3/wbc/config/SecurityConfig.java b/src/main/java/ce3/wbc/config/SecurityConfig.java index 133bd7b..6471cb3 100644 --- a/src/main/java/ce3/wbc/config/SecurityConfig.java +++ b/src/main/java/ce3/wbc/config/SecurityConfig.java @@ -1,12 +1,98 @@ package ce3.wbc.config; +import ce3.wbc.security.WbcAuthenticationFailureHandler; +import ce3.wbc.security.WbcAuthenticationSuccessHandler; +import ce3.wbc.security.WbcUserDetailsService; +import lombok.RequiredArgsConstructor; +import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.http.HttpMethod; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.authentication.AuthenticationProvider; +import org.springframework.security.authentication.dao.DaoAuthenticationProvider; +import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration; import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.security.web.SecurityFilterChain; -@EnableMethodSecurity(prePostEnabled = true,securedEnabled = true) +@EnableMethodSecurity(prePostEnabled = true, securedEnabled = true) @EnableWebSecurity @Configuration +@RequiredArgsConstructor public class SecurityConfig { + private final WbcAuthenticationSuccessHandler wbcAuthenticationSuccessHandler; + private final WbcAuthenticationFailureHandler wbcAuthenticationFailureHandler; + private final WbcUserDetailsService wbcUserDetailsService; + + @Bean + public SecurityFilterChain securityFilterChain(HttpSecurity security) throws Exception { + security.csrf(csrf -> csrf.disable()) + .cors(cors -> cors.disable()); + //인가 + security.authorizeHttpRequests((authorize -> + authorize + .requestMatchers(HttpMethod.GET,"/restaurants/{restId}").permitAll() + .requestMatchers(HttpMethod.GET,"/api/restaurants/{restId}").permitAll() + .requestMatchers(HttpMethod.POST, "/users/sign-up").permitAll() + .requestMatchers(HttpMethod.POST, "/replies/**").authenticated() + .requestMatchers(HttpMethod.PUT, "/replies/**").authenticated() + .requestMatchers(HttpMethod.DELETE, "/replies/**").authenticated() + + .requestMatchers("/favicon.ico").permitAll() + .requestMatchers("/error", "/error/**").permitAll() + .anyRequest().permitAll() + + )); + //로그인 폼사용 + security.formLogin(form -> form + .loginPage("/users/login") + .loginProcessingUrl("/users/login") //프론트엔드에서 action="/users/login"으로 설정할것 + .successHandler(wbcAuthenticationSuccessHandler) // 로그인 처리 + .failureUrl("/login?error=true") + .failureHandler(wbcAuthenticationFailureHandler) + + ); + + //Spring Security기본설정 : POST /logout + security.logout(logout -> logout.logoutSuccessUrl("/chefs")) //Spring Security기본설정 : POST /logout + .logout(logout -> logout.logoutUrl("/users/logout") //FE GET/logout,추가 + .logoutSuccessUrl("/chefs").invalidateHttpSession(true) + .deleteCookies("JSESSIONID")); + + // 세션 + security.sessionManagement(httpSecuritySessionManagementConfigurer + -> httpSecuritySessionManagementConfigurer + .invalidSessionUrl("/login") + .sessionFixation().migrateSession() + .maximumSessions(2) + .expiredUrl("/login")); + + return security.build(); + } + + @Bean + public AuthenticationProvider authenticationProvider() { + DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider(); + authProvider.setUserDetailsService(wbcUserDetailsService); //WbcUserDetailsService + authProvider.setPasswordEncoder(passwordEncoder()); //비밀번호 암호화 설정 + return authProvider; + } + + //비밀번호 암호화 규칙 + @Bean + public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception { + return authenticationConfiguration.getAuthenticationManager(); + } + + + + @Bean + public PasswordEncoder passwordEncoder() { + return new BCryptPasswordEncoder(); + } + } diff --git a/src/main/java/ce3/wbc/controller/ChefController.java b/src/main/java/ce3/wbc/controller/ChefController.java index a10058b..c24b19b 100644 --- a/src/main/java/ce3/wbc/controller/ChefController.java +++ b/src/main/java/ce3/wbc/controller/ChefController.java @@ -1,18 +1,49 @@ package ce3.wbc.controller; +import ce3.wbc.controller.rto.request.ChefCreate; +import ce3.wbc.controller.rto.response.ChefRes; +import ce3.wbc.dto.ChefDto; import ce3.wbc.service.ChefService; +import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; import org.springframework.stereotype.Controller; -import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.ui.Model; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; + +import java.util.List; +import java.util.Map; @Controller -@RequestMapping("wbs/chefs") +@RequestMapping("/chefs") @RequiredArgsConstructor public class ChefController { private final ChefService chefService; + @GetMapping + public String getAllChefs(Model model) { + List chefs = chefService.getAllChefs(); + model.addAttribute("chefs", chefs); + return "main"; + } + @GetMapping("/groupedByCategory") + public String getChefsGroupedByCategory(Model model) { + Map> groupedChefs = chefService.getChefsGroupedByCategory(); + model.addAttribute("groupedChefs", groupedChefs); + return "groupedByCategory"; + } + @ResponseStatus(HttpStatus.CREATED) + @ResponseBody + @PostMapping(value = "/create", consumes = {MediaType.APPLICATION_JSON_VALUE, MediaType.MULTIPART_FORM_DATA_VALUE}) + public ChefRes createChef (@RequestPart(value = "chefCreate") @Valid ChefCreate chefCreate, + @RequestPart(value = "file", required = false) MultipartFile file) { -} + ChefDto chefDto = chefService.saveChef(ChefCreate.toChefDto(chefCreate), file); + return ChefRes.toResponse(chefDto); + } +} \ No newline at end of file diff --git a/src/main/java/ce3/wbc/controller/CommentController.java b/src/main/java/ce3/wbc/controller/CommentController.java index 37a1859..cc53300 100644 --- a/src/main/java/ce3/wbc/controller/CommentController.java +++ b/src/main/java/ce3/wbc/controller/CommentController.java @@ -1,15 +1,92 @@ package ce3.wbc.controller; +import java.util.List; +import java.util.stream.Collectors; + +import ce3.wbc.security.WbcUserDetails; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.stereotype.Controller; +import org.springframework.validation.BindingResult; +import org.springframework.web.bind.annotation.*; +import ce3.wbc.controller.rto.request.CommentReq; +import ce3.wbc.controller.rto.request.CommentUpdateReq; +import ce3.wbc.controller.rto.response.CommentRes; +import ce3.wbc.dto.CommentDto; +import ce3.wbc.dto.UserDto; import ce3.wbc.service.CommentService; +import ce3.wbc.service.RestaurantService; +import ce3.wbc.service.UserService; +import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Controller; -import org.springframework.web.bind.annotation.RequestMapping; + @Controller -@RequestMapping("wbs/replies") +@RequestMapping("replies") @RequiredArgsConstructor public class CommentController { private final CommentService commentService; + private final RestaurantService restaurantService; + private final UserService userService; + + /************************************************** 댓글 등록 **************************************************/ + @PostMapping + public ResponseEntity registerComment(@RequestBody @Valid CommentReq commentReq, + BindingResult bindingResult, + @AuthenticationPrincipal WbcUserDetails wbcUser) { + // 댓글과 별점 + String content = commentReq.getCommContent(); + String star = commentReq.getCommStar(); + + UserDto userDto = UserDto.toDto(wbcUser.getUser()); + + // 식당 + Integer restId = commentReq.getRestId(); + + // 댓글 생성 + CommentDto commentDto = CommentDto.of(content, star, restId, userDto); + + // DB에 저장 + commentService.addComment(commentReq, userDto); + return new ResponseEntity<>(HttpStatus.OK); + } + + + /******************************************* 댓글 조회: restaurant id *******************************************/ + @GetMapping + public ResponseEntity> getComments(@RequestParam("restaurant") Integer restId) { + + List comments = commentService.getComments(restId); + + List response = comments.stream() + .map(CommentRes::toResponse) + .collect(Collectors.toList()); + + return ResponseEntity.ok(response); // 200 + } + + /********************************************* 댓글 수정: comment id *********************************************/ + @PutMapping("/{commId}") + public ResponseEntity updateComment(@PathVariable("commId") Integer commId, + @RequestBody @Valid CommentUpdateReq commentUpdateReq, BindingResult bindingResult) { + // 요청 유효성 검사 + if (bindingResult.hasErrors()) { + return ResponseEntity.status(HttpStatus.BAD_REQUEST).build(); // 400 + } + + CommentDto commentDto = commentService.updateComment(commId, commentUpdateReq.getCommContent(), commentUpdateReq.getCommStar()); + CommentRes response = CommentRes.toResponse(commentDto); + + return ResponseEntity.ok(response); // 200 + } + + /********************************************* 댓글 삭제: comment id *********************************************/ + @DeleteMapping("/{commId}") + public ResponseEntity deleteComment(@PathVariable("commId") Integer commId) { + commentService.deleteComment(commId); + return ResponseEntity.noContent().build(); + } } diff --git a/src/main/java/ce3/wbc/controller/GlobalExceptionHandler.java b/src/main/java/ce3/wbc/controller/GlobalExceptionHandler.java new file mode 100644 index 0000000..ab1539a --- /dev/null +++ b/src/main/java/ce3/wbc/controller/GlobalExceptionHandler.java @@ -0,0 +1,36 @@ +package ce3.wbc.controller; + + +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.MethodArgumentNotValidException; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestControllerAdvice; + +import java.util.HashMap; +import java.util.Map; + +@RestControllerAdvice +public class GlobalExceptionHandler { + @ExceptionHandler(IllegalArgumentException.class) + public ResponseEntity> handleIllegalArgumentException(IllegalArgumentException ex) { + Map errorResponse = new HashMap<>(); + errorResponse.put("error", ex.getMessage()); + return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(errorResponse); + } + @ExceptionHandler(MethodArgumentNotValidException.class) + public ResponseEntity> handleValidationException(MethodArgumentNotValidException ex) { + Map errors = new HashMap<>(); + ex.getBindingResult().getFieldErrors().forEach(error -> + errors.put(error.getField(), error.getDefaultMessage())); + return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(errors); + } + @ExceptionHandler(Exception.class) + public ResponseEntity> handleGlobalException(Exception ex) { + Map errorResponse = new HashMap<>(); + errorResponse.put("error", "서버 내부 오류 발생"); + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(errorResponse); + } + +} + diff --git a/src/main/java/ce3/wbc/controller/RestaurantApiController.java b/src/main/java/ce3/wbc/controller/RestaurantApiController.java new file mode 100644 index 0000000..2706671 --- /dev/null +++ b/src/main/java/ce3/wbc/controller/RestaurantApiController.java @@ -0,0 +1,81 @@ +package ce3.wbc.controller; + +import ce3.wbc.controller.rto.request.RestaurantCreate; +import ce3.wbc.controller.rto.request.RestaurantEdit; +import ce3.wbc.controller.rto.request.RestaurantReq; +import ce3.wbc.controller.rto.response.RestaurantRes; +import ce3.wbc.dto.RestaurantDto; +import ce3.wbc.service.RestaurantService; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; +import org.springframework.data.web.PageableDefault; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; + +@RestController +@RequestMapping("/api/restaurants") +@RequiredArgsConstructor +public class RestaurantApiController { + private final RestaurantService restaurantService; + + @ResponseStatus(HttpStatus.OK) + @GetMapping("") + public Page getAllRestaurants( + @PageableDefault(size = 6, sort = "restName", direction = Sort.Direction.ASC) Pageable pageable) { + return restaurantService.findAllRestList(pageable).map(RestaurantRes::toResponse); + } + + @ResponseStatus(HttpStatus.OK) + @GetMapping("/chef/{chefId}") + public Page getChefRestaurants(@PathVariable int chefId, + @PageableDefault(size = 6, sort = "restName", direction = Sort.Direction.ASC) Pageable pageable) { + System.out.println("셰프id = "+ chefId); + return restaurantService.findRestList(chefId,pageable).map(RestaurantRes::toResponse); + } + + @ResponseStatus(HttpStatus.OK) + @GetMapping("/filter") + public Page getFilterRestaurants(@ModelAttribute RestaurantReq req, + @PageableDefault(size = 6, sort = "restName", direction = Sort.Direction.ASC)Pageable pageable) { + return restaurantService.findFilterRestList(req,pageable).map(RestaurantRes::toResponse); + } + + @GetMapping("/{restId}") + public ResponseEntity getRestaurantById(@PathVariable int restId){ + return new ResponseEntity<>(restaurantService.getRestaurantById(restId), HttpStatus.OK); + } + + @ResponseStatus(HttpStatus.CREATED) + @PostMapping(value = "/create", consumes = {MediaType.APPLICATION_JSON_VALUE, MediaType.MULTIPART_FORM_DATA_VALUE}) + public RestaurantRes createRestaurant(@RequestPart(value = "restaurantCreate") @Valid RestaurantCreate restaurantCreate, + @RequestPart(value = "file", required = false) MultipartFile file) { + + RestaurantDto restaurantDto = restaurantService.create(restaurantCreate, file); + + return RestaurantRes.toResponse(restaurantDto); + } + + @ResponseStatus(HttpStatus.OK) + @PutMapping(value = "/edit/{id}", consumes = {MediaType.APPLICATION_JSON_VALUE, MediaType.MULTIPART_FORM_DATA_VALUE}) + public RestaurantRes editRestaurant(@PathVariable int id, + @Valid @RequestBody RestaurantEdit restaurantEdit, + @RequestPart(value = "file", required = false) MultipartFile file) { + RestaurantRes updated = restaurantService.update(id, restaurantEdit,file); + return updated; + + } + + @ResponseStatus(HttpStatus.NO_CONTENT) + @DeleteMapping("/delete/{id}") + public void deleteRestaurant(@PathVariable int id) { + restaurantService.delete(id); + + } + +} diff --git a/src/main/java/ce3/wbc/controller/RestaurantController.java b/src/main/java/ce3/wbc/controller/RestaurantController.java index 95af8c1..becf94c 100644 --- a/src/main/java/ce3/wbc/controller/RestaurantController.java +++ b/src/main/java/ce3/wbc/controller/RestaurantController.java @@ -1,14 +1,81 @@ package ce3.wbc.controller; +import ce3.wbc.controller.rto.request.RestaurantReq; +import ce3.wbc.controller.rto.response.RestaurantRes; +import ce3.wbc.service.PagingService; import ce3.wbc.service.RestaurantService; import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; +import org.springframework.data.web.PageableDefault; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Controller; -import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.ui.Model; +import org.springframework.web.bind.annotation.*; + +import java.util.List; @Controller -@RequestMapping("wbs/restaurants") +@RequestMapping("/restaurants") @RequiredArgsConstructor public class RestaurantController { private final RestaurantService restaurantService; + private final PagingService pagingService; + + @GetMapping("") + public String getAllRestaurants(Model model, + @PageableDefault(size = 6, + sort = "restName", + direction = Sort.Direction.ASC) Pageable pageable) { + + Page restaurants = restaurantService.findAllRestList(pageable).map(RestaurantRes::toResponse); + addPaging(restaurants, model, pageable); + return "index"; + } + + @GetMapping("chef/chefId") + public String getChefRestaurants(@PathVariable int chefId, Model model, + @PageableDefault(size = 6, + sort = "restName", + direction = Sort.Direction.ASC) Pageable pageable) { + Page restaurants = restaurantService.findRestList(chefId, pageable).map(RestaurantRes::toResponse); + model.addAttribute("chefId", chefId); + addPaging(restaurants, model, pageable); + return "index"; + } + + @GetMapping("filter") + public String getFilterRestaurants(@ModelAttribute RestaurantReq req, + @PageableDefault(size = 6, + sort = "restName", + direction = Sort.Direction.ASC) Pageable pageable, + Model model) { + Page restaurants = restaurantService.findFilterRestList(req,pageable).map(RestaurantRes::toResponse); + addPaging(restaurants, model, pageable); + return "index"; + } + + private void addPaging(Page restaurants, Model model, Pageable pageable) { + model.addAttribute("restaurants", restaurants); + model.addAttribute("currentpage", pageable.getPageNumber()); //현재 페이지 번호 + model.addAttribute("pagesize", pageable.getPageSize()); // 한 페이지당 보여줄 데이터 개수 + model.addAttribute("totalPages", restaurants.getTotalPages());// 총 페이지 + model.addAttribute("totalElements", restaurants.getTotalElements()); // 총 갯수 + model.addAttribute("hasNext", restaurants.hasNext()); // 다음페이지있니? + model.addAttribute("hasPrevious", pageable.hasPrevious()); // 이전페이지있니? + + //페이징 번호 리스트(1부터처리 UI로 진행) + List pagingNumbers = pagingService.getPageNumbers(pageable.getPageNumber(), restaurants.getTotalPages()); + model.addAttribute("pagingNumbers", pagingNumbers); + + } + @GetMapping("/{restId}") + public String getRestaurantById(@PathVariable int restId, Model model){ + RestaurantRes restaurant = restaurantService.getRestaurantById(restId); + model.addAttribute("restaurant", restaurant); + return "detail"; + } } diff --git a/src/main/java/ce3/wbc/controller/UserController.java b/src/main/java/ce3/wbc/controller/UserController.java index 257891f..6c713fc 100644 --- a/src/main/java/ce3/wbc/controller/UserController.java +++ b/src/main/java/ce3/wbc/controller/UserController.java @@ -1,15 +1,73 @@ package ce3.wbc.controller; +import ce3.wbc.controller.rto.request.SignupForm; import ce3.wbc.service.UserService; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpSession; +import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.validation.BindingResult; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.ModelAttribute; +import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; - +@Slf4j @Controller -@RequestMapping("wbs/users") +@RequestMapping("/users") @RequiredArgsConstructor public class UserController { private final UserService userService; + //가입페이지 + @GetMapping("/sign-up") + public String getSignUp(Model model) { + model.addAttribute("signupForm", new SignupForm()); + log.info("|>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>.getSignUp"); + return "sign-up"; + } + + // 회원가입 처리 + @PostMapping("/sign-up") + public String SignUp (@Valid @ModelAttribute SignupForm form, BindingResult bindingResult) { + try { + if (bindingResult.hasErrors()) { + log.warn("회원가입 유효성 검사 실패: {}", bindingResult.getAllErrors()); + return "sign-up"; + } + userService.createUser(SignupForm.toDto(form)); + } catch (Exception e) { + log.error("회원가입 중 예외 발생: ", e); + return "error/500"; + } + return "redirect:/users/login"; + } + + // 로그인 페이지 + @GetMapping("/login") + public String getLoginPage(HttpServletRequest request, Model model) { + HttpSession session = request.getSession(false); + + // 이전페이지URL저장 + String referer = request.getHeader("Referer"); + if ( session != null && referer != null && !referer.contains("/login") && !referer.contains("/sign-up") ) { + session.setAttribute("prevPage", referer); + } + //에러메세지처리, 한번만 표시후 삭제 + if (session != null) { + if (session.getAttribute("errorMessage")!= null) { + model.addAttribute("errorMessage", session.getAttribute("errorMessage")); + session.removeAttribute("errorMessage"); + } + } + return "login"; + } + + // @PostMapping("/login")public String login() {} : 세큐리티 처리 + // @PostMapping("/logout")public String logout() {} 세큐리티 처리 + + } diff --git a/src/main/java/ce3/wbc/controller/rto/request/AddressCreate.java b/src/main/java/ce3/wbc/controller/rto/request/AddressCreate.java new file mode 100644 index 0000000..3314a6b --- /dev/null +++ b/src/main/java/ce3/wbc/controller/rto/request/AddressCreate.java @@ -0,0 +1,24 @@ +package ce3.wbc.controller.rto.request; + +import jakarta.validation.constraints.NotBlank; +import lombok.*; + +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor(access = AccessLevel.PRIVATE) +@Builder +public class AddressCreate { + @NotBlank(message = "city 필수 입력 값입니다.") + private String city; + + @NotBlank(message = "street 필수 입력 값입니다.") + private String street; + + @NotBlank(message = "zipcode 필수 입력 값입니다.") + private String zipcode; + + public String getAddress() { + return String.format("%s,%s,%s", city, street, zipcode); + } +} + diff --git a/src/main/java/ce3/wbc/controller/rto/request/AddressReq.java b/src/main/java/ce3/wbc/controller/rto/request/AddressReq.java index 73561f0..4d2115a 100644 --- a/src/main/java/ce3/wbc/controller/rto/request/AddressReq.java +++ b/src/main/java/ce3/wbc/controller/rto/request/AddressReq.java @@ -1,6 +1,6 @@ package ce3.wbc.controller.rto.request; -import jakarta.validation.constraints.NotBlank; +import jakarta.annotation.Nullable; import lombok.*; @Getter @@ -8,13 +8,18 @@ @AllArgsConstructor(access = AccessLevel.PRIVATE) @Builder public class AddressReq { - @NotBlank(message = "city 필수 입력 값입니다.") + @Nullable private String city; - @NotBlank(message = "street 필수 입력 값입니다.") + @Nullable private String street; - @NotBlank(message = "zipcode 필수 입력 값입니다.") + @Nullable private String zipcode; + public String getAddress() { + return String.format("%s,%s,%s", city, street, zipcode); + } + } + diff --git a/src/main/java/ce3/wbc/controller/rto/request/ChefCreate.java b/src/main/java/ce3/wbc/controller/rto/request/ChefCreate.java new file mode 100644 index 0000000..3c418f1 --- /dev/null +++ b/src/main/java/ce3/wbc/controller/rto/request/ChefCreate.java @@ -0,0 +1,29 @@ +package ce3.wbc.controller.rto.request; + +import ce3.wbc.dto.ChefDto; +import jakarta.validation.constraints.Pattern; +import lombok.*; + +@AllArgsConstructor(access = AccessLevel.PRIVATE) +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@Getter +@Builder +public class ChefCreate { + private String chefName; + private String chefCategory; + @Pattern(regexp = "^[a-zA-Z0-]*$", message = "영어와 숫자만 입력 가능합니다.") + private String chefEngName; + @Pattern(regexp = "^[a-zA-Z0-]*$", message = "영어와 숫자만 입력 가능합니다.") + private String originalImgName; + + public static ChefDto toChefDto (ChefCreate chefCreate) { + return ChefDto.of( + chefCreate.getChefName(), + chefCreate.getChefCategory(), + chefCreate.getChefEngName(), + chefCreate.getOriginalImgName() + ); + } + + +} diff --git a/src/main/java/ce3/wbc/controller/rto/request/CommentListReq.java b/src/main/java/ce3/wbc/controller/rto/request/CommentListReq.java new file mode 100644 index 0000000..b04abd5 --- /dev/null +++ b/src/main/java/ce3/wbc/controller/rto/request/CommentListReq.java @@ -0,0 +1,19 @@ +package ce3.wbc.controller.rto.request; + +import com.fasterxml.jackson.annotation.JsonProperty; +import jakarta.validation.constraints.NotNull; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@AllArgsConstructor(access = AccessLevel.PRIVATE) +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@Getter +@Builder +public class CommentListReq { + @NotNull + @JsonProperty("restId") + private Integer restId; +} \ No newline at end of file diff --git a/src/main/java/ce3/wbc/controller/rto/request/CommentReq.java b/src/main/java/ce3/wbc/controller/rto/request/CommentReq.java index 386dea7..f2bf478 100644 --- a/src/main/java/ce3/wbc/controller/rto/request/CommentReq.java +++ b/src/main/java/ce3/wbc/controller/rto/request/CommentReq.java @@ -1,7 +1,12 @@ package ce3.wbc.controller.rto.request; import jakarta.validation.constraints.NotBlank; -import lombok.*; +import jakarta.validation.constraints.NotNull; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; /** * DTO for {@link ce3.wbc.entity.Comment} @@ -15,5 +20,7 @@ public class CommentReq { private String commContent; @NotBlank(message = "별점은 필수 입력 값입니다.") private String commStar; + @NotNull + private Integer restId; +} -} \ No newline at end of file diff --git a/src/main/java/ce3/wbc/controller/rto/request/CommentUpdateReq.java b/src/main/java/ce3/wbc/controller/rto/request/CommentUpdateReq.java new file mode 100644 index 0000000..271617a --- /dev/null +++ b/src/main/java/ce3/wbc/controller/rto/request/CommentUpdateReq.java @@ -0,0 +1,20 @@ +package ce3.wbc.controller.rto.request; + +import jakarta.validation.constraints.NotNull; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@AllArgsConstructor(access = AccessLevel.PRIVATE) +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@Getter +@Builder +public class CommentUpdateReq { + @NotNull + private String commContent; + + @NotNull + private String commStar; +} \ No newline at end of file diff --git a/src/main/java/ce3/wbc/controller/rto/request/RestaurantCreate.java b/src/main/java/ce3/wbc/controller/rto/request/RestaurantCreate.java new file mode 100644 index 0000000..41b4047 --- /dev/null +++ b/src/main/java/ce3/wbc/controller/rto/request/RestaurantCreate.java @@ -0,0 +1,36 @@ +package ce3.wbc.controller.rto.request; + +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Pattern; +import lombok.*; + +/** + * DTO for {@link ce3.wbc.entity.Restaurant} + * just create + */ +@AllArgsConstructor(access = AccessLevel.PRIVATE) +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@Getter +@Builder +public class RestaurantCreate { + @NotBlank(message = "Restaurant Name은 필수 입력 값입니다.") + private String restName; + @Pattern(regexp = "^[a-zA-Z0-]*$", message = "영어와 숫자만 입력 가능합니다.") + private String restEngName; + @Pattern(regexp = "^[a-zA-Z0-]*$", message = "영어와 숫자만 입력 가능합니다.") + private String originalImgName; + @Valid + private AddressCreate address; + @NotBlank(message = "chefName은 필수 입력 값입니다.") + private String restPhone; + + // boolean은 null이 될 수 없으므로 별도 검증 필요 없음. + private boolean restRental; + private boolean groupReservation; + private boolean corkage; + private boolean noKidsZone; + + @NotBlank(message = "chef Name은 필수 입력 값입니다.") + private String chefName; +} diff --git a/src/main/java/ce3/wbc/controller/rto/request/RestaurantEdit.java b/src/main/java/ce3/wbc/controller/rto/request/RestaurantEdit.java new file mode 100644 index 0000000..8be75e4 --- /dev/null +++ b/src/main/java/ce3/wbc/controller/rto/request/RestaurantEdit.java @@ -0,0 +1,41 @@ +package ce3.wbc.controller.rto.request; + +import jakarta.annotation.Nullable; +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotNull; +import lombok.*; + +/** + * DTO for {@link ce3.wbc.entity.Restaurant} + * just create + */ +@AllArgsConstructor(access = AccessLevel.PRIVATE) +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@Getter +@Builder +public class RestaurantEdit { + @NotNull(message = "수정시 필수값입니다") + private Integer restId; + @Nullable + private String restName; + @Nullable + private String restEngName; + @Nullable + private String originalImgName; + @Valid + private AddressReq address; + @Nullable + private String restPhone; + + // boolean은 null이 될 수 없으므로 별도 검증 필요 없음. + private boolean restRental; + private boolean groupReservation; + private boolean corkage; + private boolean noKidsZone; + @Nullable + private String chefName; + + + + +} diff --git a/src/main/java/ce3/wbc/controller/rto/request/RestaurantReq.java b/src/main/java/ce3/wbc/controller/rto/request/RestaurantReq.java index 603ab2d..00cab6a 100644 --- a/src/main/java/ce3/wbc/controller/rto/request/RestaurantReq.java +++ b/src/main/java/ce3/wbc/controller/rto/request/RestaurantReq.java @@ -1,30 +1,29 @@ package ce3.wbc.controller.rto.request; -import jakarta.validation.Valid; -import jakarta.validation.constraints.NotBlank; +import jakarta.annotation.Nullable; import lombok.*; /** * DTO for {@link ce3.wbc.entity.Restaurant} + * just find */ @AllArgsConstructor(access = AccessLevel.PRIVATE) @NoArgsConstructor(access = AccessLevel.PROTECTED) @Getter @Builder public class RestaurantReq { - @NotBlank(message = "chefName은 필수 입력 값입니다.") + @Nullable private String restName; - @Valid + @Nullable private AddressReq address; - @NotBlank(message = "chefName은 필수 입력 값입니다.") - private String restPhone; - - // boolean은 null이 될 수 없으므로 별도 검증 필요 없음. - private boolean restRental; - private boolean groupReservation; - private boolean corkage; - private boolean noKidsZone; - - @NotBlank(message = "chefName은 필수 입력 값입니다.") + @Nullable + private Boolean restRental; + @Nullable + private Boolean groupReservation; + @Nullable + private Boolean corkage; + @Nullable + private Boolean noKidsZone; + @Nullable private String chefName; } \ No newline at end of file diff --git a/src/main/java/ce3/wbc/controller/rto/request/SignupForm.java b/src/main/java/ce3/wbc/controller/rto/request/SignupForm.java new file mode 100644 index 0000000..9541209 --- /dev/null +++ b/src/main/java/ce3/wbc/controller/rto/request/SignupForm.java @@ -0,0 +1,30 @@ +package ce3.wbc.controller.rto.request; + +import ce3.wbc.dto.UserDto; +import jakarta.validation.constraints.NotBlank; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@AllArgsConstructor +@NoArgsConstructor +@Getter +@Data +public class SignupForm { + + @NotBlank(message = "userName 필수 입력 값입니다.") + private String userName; + @NotBlank(message = "userPassword 필수 입력 값입니다.") + private String userPassword; + @NotBlank(message = "userId 필수 입력 값입니다.") + private String userId; + + public static UserDto toDto(SignupForm form) { + return UserDto.builder() + .userName(form.getUserName()) + .userPassword(form.getUserPassword()) + .userId(form.getUserId()) + .build(); + } +} diff --git a/src/main/java/ce3/wbc/controller/rto/request/UserReq.java b/src/main/java/ce3/wbc/controller/rto/request/UserReq.java deleted file mode 100644 index d83b876..0000000 --- a/src/main/java/ce3/wbc/controller/rto/request/UserReq.java +++ /dev/null @@ -1,20 +0,0 @@ -package ce3.wbc.controller.rto.request; - -import jakarta.validation.constraints.NotBlank; -import lombok.*; - -/** - * DTO for {@link ce3.wbc.entity.User} - */ -@AllArgsConstructor(access = AccessLevel.PRIVATE) -@NoArgsConstructor(access = AccessLevel.PROTECTED) -@Getter -@Builder -public class UserReq { - @NotBlank(message = "userName 필수 입력 값입니다.") - private String userName; - @NotBlank(message = "userPassword 필수 입력 값입니다.") - private String userPassword; - @NotBlank(message = "userId 필수 입력 값입니다.") - private String userId; -} \ No newline at end of file diff --git a/src/main/java/ce3/wbc/controller/rto/response/ChefRes.java b/src/main/java/ce3/wbc/controller/rto/response/ChefRes.java index 4c9004c..dcbbb1d 100644 --- a/src/main/java/ce3/wbc/controller/rto/response/ChefRes.java +++ b/src/main/java/ce3/wbc/controller/rto/response/ChefRes.java @@ -1,10 +1,12 @@ package ce3.wbc.controller.rto.response; import ce3.wbc.dto.ChefDto; +import ce3.wbc.entity.Chef; import lombok.*; @AllArgsConstructor(access = AccessLevel.PRIVATE) @NoArgsConstructor(access = AccessLevel.PROTECTED) +@Setter @Getter @Builder public class ChefRes { @@ -25,5 +27,17 @@ public static ChefRes toResponse(ChefDto chefDto) { .build(); } + public static ChefRes toResponse(Chef chef) { + if (chef == null) { + return null; + } + return ChefRes.builder() + .chefId(chef.getChefId()) + .chefName(chef.getChefName()) + .chefCategory(chef.getChefCategory()) + .chefImage(chef.getChefImage()) + .build(); + } + } diff --git a/src/main/java/ce3/wbc/controller/rto/response/CommentRes.java b/src/main/java/ce3/wbc/controller/rto/response/CommentRes.java index 10dad74..007d805 100644 --- a/src/main/java/ce3/wbc/controller/rto/response/CommentRes.java +++ b/src/main/java/ce3/wbc/controller/rto/response/CommentRes.java @@ -11,7 +11,6 @@ public class CommentRes { private Integer commId; private String commContent; private String commStar; - private RestaurantRes restaurant; private UserRes user; public static CommentRes toResponse(CommentDto commentDto) { @@ -22,9 +21,7 @@ public static CommentRes toResponse(CommentDto commentDto) { .commId(commentDto.getCommId()) .commContent(commentDto.getCommContent()) .commStar(commentDto.getCommStar()) - .restaurant(RestaurantRes.toResponse(commentDto.getRestaurantDto())) .user(UserRes.toResponse(commentDto.getUserDto())) .build(); } - } diff --git a/src/main/java/ce3/wbc/controller/rto/response/RestaurantRes.java b/src/main/java/ce3/wbc/controller/rto/response/RestaurantRes.java index 5670eb1..1dfb466 100644 --- a/src/main/java/ce3/wbc/controller/rto/response/RestaurantRes.java +++ b/src/main/java/ce3/wbc/controller/rto/response/RestaurantRes.java @@ -15,6 +15,7 @@ public class RestaurantRes { private Integer restId; private String restName; private String restImg; + private String originalImgName; private String restPhone; private Address address; private boolean restRental; @@ -32,6 +33,7 @@ public static RestaurantRes toResponse(RestaurantDto restaurantDto) { .restId(restaurantDto.getRestId()) .restName(restaurantDto.getRestName()) .restImg(restaurantDto.getRestImg()) + .originalImgName(restaurantDto.getOriginalImgName()) .restPhone(restaurantDto.getRestPhone()) .address(restaurantDto.getAddress()) .restRental(restaurantDto.isRestRental()) @@ -39,7 +41,7 @@ public static RestaurantRes toResponse(RestaurantDto restaurantDto) { .corkage(restaurantDto.isCorkage()) .noKidsZone(restaurantDto.isNoKidsZone()) .chef(ChefRes.toResponse(restaurantDto.getChefDto())) - .comments(restaurantDto.getComments().stream() // ✅ null 방지 로직 제거 + .comments(restaurantDto.getComments().stream() // null 방지 로직 제거 .map(CommentRes::toResponse) .collect(Collectors.toList())) .build(); diff --git a/src/main/java/ce3/wbc/dto/ChefDto.java b/src/main/java/ce3/wbc/dto/ChefDto.java index 741a710..1e03600 100644 --- a/src/main/java/ce3/wbc/dto/ChefDto.java +++ b/src/main/java/ce3/wbc/dto/ChefDto.java @@ -9,22 +9,25 @@ @AllArgsConstructor(access = AccessLevel.PRIVATE) @NoArgsConstructor(access = AccessLevel.PROTECTED) @Getter +@Setter @Builder public class ChefDto { - Integer chefId; - String chefName; - String chefCategory; - String chefImage; + private Integer chefId; + private String chefName; + private String chefCategory; + private String chefImage; + private String originalImgName; public static ChefDto toDto(Chef chef) { if (chef == null) { - return new ChefDto(-1, "없어용", "W/B", "default.jpg"); + return new ChefDto(-1, "없어용", "W/B", "default.jpg","default"); } return ChefDto.builder() .chefId(chef.getChefId()) .chefName(chef.getChefName()) .chefCategory(chef.getChefCategory()) .chefImage(chef.getChefImage()) + .originalImgName(chef.getOriginalImgName()) .build(); } @@ -32,8 +35,12 @@ public static Chef toEntity(ChefDto chefDto) { return Chef.of( chefDto.getChefName(), chefDto.getChefCategory(), - chefDto.getChefImage() + chefDto.getChefImage(), + chefDto.getOriginalImgName() ); } + public static ChefDto of(String chefName, String chefCategory, String chefImage, String originalImgName) { + return new ChefDto(null,chefName, chefCategory, chefImage, originalImgName); + } } \ No newline at end of file diff --git a/src/main/java/ce3/wbc/dto/CommentDto.java b/src/main/java/ce3/wbc/dto/CommentDto.java index f982951..8017a68 100644 --- a/src/main/java/ce3/wbc/dto/CommentDto.java +++ b/src/main/java/ce3/wbc/dto/CommentDto.java @@ -1,8 +1,15 @@ package ce3.wbc.dto; +import ce3.wbc.controller.rto.request.CommentReq; import ce3.wbc.entity.Comment; -import lombok.*; +import ce3.wbc.entity.Restaurant; +import ce3.wbc.entity.User; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; /** * DTO for {@link ce3.wbc.entity.Comment} @@ -12,29 +19,47 @@ @Getter @Builder public class CommentDto { - private Integer commId; - private String commContent; - private String commStar; - - private RestaurantDto restaurantDto; + private Integer commId; + private String commContent; + private String commStar; + private Integer restId; private UserDto userDto; + + public static CommentDto of(Integer commId, String commContent, String commStar, Integer restId, UserDto userDto) { + return new CommentDto(commId, commContent, commStar, restId, userDto); + } + + public static CommentDto of(String commContent, String commStar, Integer restId, UserDto userDto) { + return of(null, commContent, commStar, restId, userDto); + } + + public static CommentDto toCommentDto(Comment comment) { return CommentDto.builder() .commId(comment.getCommId()) .commContent(comment.getCommContent()) .commStar(comment.getCommStar()) - .restaurantDto(RestaurantDto.toDto(comment.getRestaurant())) + .restId(comment.getRestaurant().getRestId()) .userDto(UserDto.toDto(comment.getUser())) .build(); } - public static Comment toEntity(CommentDto commentDto) { + public static Comment toEntity(CommentDto commentDto, Restaurant restaurant, User user) { return Comment.of( commentDto.getCommContent(), commentDto.getCommStar(), - RestaurantDto.toEntity(commentDto.getRestaurantDto()), - UserDto.toEntity(commentDto.getUserDto()) + restaurant, + user + ); + } + + public static Comment toEntity(CommentReq commentReq, Restaurant restaurant, User user) { + return Comment.of( + commentReq.getCommContent(), + commentReq.getCommStar(), + restaurant, + user ); } } diff --git a/src/main/java/ce3/wbc/dto/RestaurantDto.java b/src/main/java/ce3/wbc/dto/RestaurantDto.java index d3b4318..925c5eb 100644 --- a/src/main/java/ce3/wbc/dto/RestaurantDto.java +++ b/src/main/java/ce3/wbc/dto/RestaurantDto.java @@ -1,7 +1,11 @@ package ce3.wbc.dto; +import ce3.wbc.entity.Chef; +import ce3.wbc.entity.Comment; import ce3.wbc.entity.Restaurant; +import ce3.wbc.entity.User; import ce3.wbc.entity.attribute.Address; +import com.fasterxml.jackson.annotation.JsonIgnore; import lombok.*; import java.util.ArrayList; @@ -20,24 +24,28 @@ public class RestaurantDto { private Integer restId; private String restName; private String restImg; + private String originalImgName; private String restPhone; private Address address; private boolean restRental; private boolean groupReservation; private boolean corkage; private boolean noKidsZone; + @JsonIgnore private ChefDto chefDto; - private List comments; + @Builder.Default @JsonIgnore + private List comments = new ArrayList<>(); public static RestaurantDto toDto(Restaurant restaurant) { if(restaurant == null) { - return new RestaurantDto(-1,"없어용", "default.jpg","번호없음" ,Address.of("","",""), + return new RestaurantDto(-1,"없어용", "default.jpg","default","번호없음" ,Address.of("","",""), false,false,false,false,ChefDto.toDto(null), new ArrayList<>()); } return RestaurantDto.builder() .restId(restaurant.getRestId()) .restName(restaurant.getRestName()) .restImg(restaurant.getRestImg()) + .originalImgName(restaurant.getOriginalImgName()) .restPhone(restaurant.getRestPhone()) .address(restaurant.getAddress()) .restRental(restaurant.isRestRental()) @@ -51,43 +59,38 @@ public static RestaurantDto toDto(Restaurant restaurant) { .build(); } + public static Restaurant toEntityWithComm(RestaurantDto restaurantDto, Chef chef, List commentDtos, User user) { + if (chef == null) { + throw new IllegalArgumentException("레스토랑을 생성하려면 Chef가 반드시 필요합니다."); // ✅ 예외 처리 + } + + Restaurant restaurant = toEntity(restaurantDto, chef); - public static Restaurant toEntity(RestaurantDto restaurantDto, ChefDto chefDto) { - return Restaurant.of( - restaurantDto.getRestName(), - restaurantDto.getRestImg(), - restaurantDto.getRestPhone(), - restaurantDto.getAddress(), - restaurantDto.isRestRental(), - restaurantDto.isGroupReservation(), - restaurantDto.isCorkage(), - restaurantDto.isNoKidsZone(), - ChefDto.toEntity(chefDto), - restaurantDto.getComments() != null - ? restaurantDto.getComments().stream() - .map(CommentDto::toEntity) // ✅ CommentDto → Comment 변환 - .collect(Collectors.toList()) - : new ArrayList<>() + // CommentDto 리스트 → Comment 엔티티 리스트 변환 + List comments = commentDtos.stream() + .map(dto -> CommentDto.toEntity(dto, restaurant, user)) // ✅ Restaurant을 전달 + .collect(Collectors.toList()); - ); + // Restaurant에 Comment 추가 + restaurant.getComments().addAll(comments); + + return restaurant; } - public static Restaurant toEntity(RestaurantDto restaurantDto) { + + public static Restaurant toEntity(RestaurantDto restaurantDto, Chef chef) { return Restaurant.of( + restaurantDto.getRestId(), restaurantDto.getRestName(), restaurantDto.getRestImg(), + restaurantDto.getOriginalImgName(), restaurantDto.getRestPhone(), restaurantDto.getAddress(), restaurantDto.isRestRental(), restaurantDto.isGroupReservation(), restaurantDto.isCorkage(), restaurantDto.isNoKidsZone(), - restaurantDto.getChefDto() != null ? ChefDto.toEntity(restaurantDto.getChefDto()) : null, - restaurantDto.getComments() != null - ? restaurantDto.getComments().stream() - .map(CommentDto::toEntity) // ✅ CommentDto → Comment 변환 - .collect(Collectors.toList()) - : new ArrayList<>() + chef, + new ArrayList<>() ); } - } \ No newline at end of file diff --git a/src/main/java/ce3/wbc/dto/UserDto.java b/src/main/java/ce3/wbc/dto/UserDto.java index 8156039..2582f20 100644 --- a/src/main/java/ce3/wbc/dto/UserDto.java +++ b/src/main/java/ce3/wbc/dto/UserDto.java @@ -36,4 +36,12 @@ public static User toEntity(UserDto userDto) { userDto.getUserId() ); } + + public User toEntity() { + return User.of( + userName, + userPassword, + userId + ); + } } \ No newline at end of file diff --git a/src/main/java/ce3/wbc/entity/Chef.java b/src/main/java/ce3/wbc/entity/Chef.java index c49a410..7b8600a 100644 --- a/src/main/java/ce3/wbc/entity/Chef.java +++ b/src/main/java/ce3/wbc/entity/Chef.java @@ -5,6 +5,9 @@ import lombok.AllArgsConstructor; import lombok.Getter; import lombok.NoArgsConstructor; +import org.hibernate.proxy.HibernateProxy; + +import java.util.Objects; @Getter @NoArgsConstructor(access = AccessLevel.PROTECTED) @@ -18,14 +21,36 @@ public class Chef { @Column(name = "chef_name", nullable = false) private String chefName; + @Column(name = "chef_category", nullable = false) private String chefCategory; + @Column(name = "chef_image") private String chefImage; + @Column(name = "original_img_name") + private String originalImgName; + + - public static Chef of(String chefName, String chefCategory, String chefImage) { - return new Chef(null,chefName, chefCategory, chefImage); + public static Chef of(String chefName, String chefCategory, String chefImage, String originalImgName) { + return new Chef(null,chefName, chefCategory, chefImage, originalImgName); } + + @Override + public final boolean equals(Object o) { + if (this == o) return true; + if (o == null) return false; + Class oEffectiveClass = o instanceof HibernateProxy ? ((HibernateProxy) o).getHibernateLazyInitializer().getPersistentClass() : o.getClass(); + Class thisEffectiveClass = this instanceof HibernateProxy ? ((HibernateProxy) this).getHibernateLazyInitializer().getPersistentClass() : this.getClass(); + if (thisEffectiveClass != oEffectiveClass) return false; + Chef chef = (Chef) o; + return getChefId() != null && Objects.equals(getChefId(), chef.getChefId()); + } + + @Override + public final int hashCode() { + return this instanceof HibernateProxy ? ((HibernateProxy) this).getHibernateLazyInitializer().getPersistentClass().hashCode() : getClass().hashCode(); + } } diff --git a/src/main/java/ce3/wbc/entity/Comment.java b/src/main/java/ce3/wbc/entity/Comment.java index 0c6ae92..bc1cc95 100644 --- a/src/main/java/ce3/wbc/entity/Comment.java +++ b/src/main/java/ce3/wbc/entity/Comment.java @@ -8,6 +8,9 @@ import lombok.NoArgsConstructor; import org.hibernate.annotations.OnDelete; import org.hibernate.annotations.OnDeleteAction; +import org.hibernate.proxy.HibernateProxy; + +import java.util.Objects; @Getter @NoArgsConstructor(access = AccessLevel.PROTECTED) @@ -27,13 +30,21 @@ public class Comment extends AuditingFields { //연관 관계 - @ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.REMOVE) - @JoinColumn(name = "rest_id", nullable = true) + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "rest_id", nullable = true) //? private Restaurant restaurant; + //연관관계 편의 메서드 + public void assignToRestaurant(Restaurant restaurant) { + if (restaurant == null) { + throw new IllegalArgumentException("댓글은 반드시 특정 레스토랑에 속해야 합니다."); + } + this.restaurant = restaurant; + } + //연관 관계 - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "user_id", nullable = true) + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "user_id", nullable = true) // ? @OnDelete(action = OnDeleteAction.SET_NULL) private User user; @@ -41,6 +52,28 @@ public class Comment extends AuditingFields { public static Comment of(String commContent, String commStar, Restaurant restaurant, User user) { return new Comment(null, commContent, commStar, restaurant, user); } + + public Comment update(Integer commId, String commContent, String commStar) { + this.commContent = commContent; + this.commStar = commStar; + return null; + + } + + @Override + public final boolean equals(Object o) { + if (this == o) return true; + if (o == null) return false; + Class oEffectiveClass = o instanceof HibernateProxy ? ((HibernateProxy) o).getHibernateLazyInitializer().getPersistentClass() : o.getClass(); + Class thisEffectiveClass = this instanceof HibernateProxy ? ((HibernateProxy) this).getHibernateLazyInitializer().getPersistentClass() : this.getClass(); + if (thisEffectiveClass != oEffectiveClass) return false; + Comment comment = (Comment) o; + return getCommId() != null && Objects.equals(getCommId(), comment.getCommId()); + } + @Override + public final int hashCode() { + return this instanceof HibernateProxy ? ((HibernateProxy) this).getHibernateLazyInitializer().getPersistentClass().hashCode() : getClass().hashCode(); + } } diff --git a/src/main/java/ce3/wbc/entity/Restaurant.java b/src/main/java/ce3/wbc/entity/Restaurant.java index dbce5c2..46c228d 100644 --- a/src/main/java/ce3/wbc/entity/Restaurant.java +++ b/src/main/java/ce3/wbc/entity/Restaurant.java @@ -1,14 +1,14 @@ package ce3.wbc.entity; import ce3.wbc.entity.attribute.Address; +import com.fasterxml.jackson.annotation.JsonIgnore; import jakarta.persistence.*; -import lombok.AccessLevel; -import lombok.AllArgsConstructor; -import lombok.Getter; -import lombok.NoArgsConstructor; +import lombok.*; +import org.hibernate.proxy.HibernateProxy; import java.util.ArrayList; import java.util.List; +import java.util.Objects; @Getter @NoArgsConstructor(access = AccessLevel.PROTECTED) @@ -24,7 +24,10 @@ public class Restaurant { private String restName; @Column(name = "rest_img") - private String restImg; + private String restImg;//s3Key==EngName == chefImage + + @Column(name = "original_img_name") + private String originalImgName;//사용자정의 @Column(name = "rest_phone") private String restPhone; @@ -48,12 +51,44 @@ public class Restaurant { //연관 관계 @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "chef_id") + @JsonIgnore private Chef chef; - @OneToMany(mappedBy = "restaurant", cascade = CascadeType.ALL, orphanRemoval = true) + @OneToMany(mappedBy = "restaurant", cascade = CascadeType.REMOVE, orphanRemoval = true) + @ToString.Exclude private List comments = new ArrayList<>(); - public static Restaurant of(String restName, String restImg, String restPhone, Address address, boolean restRental, boolean groupReservation, boolean corkage, boolean noKidsZone,Chef chef, List comments) { - return new Restaurant(null, restName, restImg, restPhone, address, restRental, groupReservation, corkage, noKidsZone, chef, new ArrayList<>()); + //연관관계 편의 메서드 + public void addComment(Comment comment) { + if (comment == null) { + throw new IllegalArgumentException("댓글이 null일 수 없습니다."); + } + this.comments.add(comment); + comment.assignToRestaurant(this); // `Comment`의 필드도 설정 + } + + + + public static Restaurant of(String restName, String restImg,String originalImgName, String restPhone, Address address, boolean restRental, boolean groupReservation, boolean corkage, boolean noKidsZone, Chef chef, List comments) { + return new Restaurant(null, restName, restImg,originalImgName,restPhone, address, restRental, groupReservation, corkage, noKidsZone, chef,comments); + } + public static Restaurant of(Integer restId, String restName, String restImg,String originalImgName, String restPhone, Address address, boolean restRental, boolean groupReservation, boolean corkage, boolean noKidsZone,Chef chef, List comments) { + return new Restaurant(restId, restName, restImg,originalImgName,restPhone, address, restRental, groupReservation, corkage, noKidsZone, chef, comments); + } + + @Override + public final boolean equals(Object o) { + if (this == o) return true; + if (o == null) return false; + Class oEffectiveClass = o instanceof HibernateProxy ? ((HibernateProxy) o).getHibernateLazyInitializer().getPersistentClass() : o.getClass(); + Class thisEffectiveClass = this instanceof HibernateProxy ? ((HibernateProxy) this).getHibernateLazyInitializer().getPersistentClass() : this.getClass(); + if (thisEffectiveClass != oEffectiveClass) return false; + Restaurant that = (Restaurant) o; + return getRestId() != null && Objects.equals(getRestId(), that.getRestId()); + } + + @Override + public final int hashCode() { + return this instanceof HibernateProxy ? ((HibernateProxy) this).getHibernateLazyInitializer().getPersistentClass().hashCode() : getClass().hashCode(); } } diff --git a/src/main/java/ce3/wbc/entity/User.java b/src/main/java/ce3/wbc/entity/User.java index 2d9684f..069a452 100644 --- a/src/main/java/ce3/wbc/entity/User.java +++ b/src/main/java/ce3/wbc/entity/User.java @@ -1,10 +1,15 @@ package ce3.wbc.entity; +import ce3.wbc.entity.attribute.UserRole; +import ce3.wbc.util.UserRoleConverter; import jakarta.persistence.*; import lombok.AccessLevel; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.NoArgsConstructor; +import org.hibernate.proxy.HibernateProxy; + +import java.util.Objects; @Getter @NoArgsConstructor(access = AccessLevel.PROTECTED) @@ -19,13 +24,37 @@ public class User { @Column(name = "user_name", nullable = false) private String userName; + @Column(name = "user_password", nullable = false) private String userPassword; @Column(name = "user_id", nullable = false) private String userId; - + @Column(name = "user_role", nullable = false) + @Convert(converter = UserRoleConverter.class) + private UserRole userRole; public static User of(String userName, String userPassword, String userId) { - return new User(null,userName, userPassword, userId); + return new User(null ,userName, userPassword, userId, UserRole.USER); + } + + public static User of(Integer uId,String userName, String userPassword, String userId, UserRole userRole) { + return new User(uId, userName, userPassword, userId, + userRole != null ? userRole : UserRole.USER ); + } + + @Override + public final boolean equals(Object o) { + if (this == o) return true; + if (o == null) return false; + Class oEffectiveClass = o instanceof HibernateProxy ? ((HibernateProxy) o).getHibernateLazyInitializer().getPersistentClass() : o.getClass(); + Class thisEffectiveClass = this instanceof HibernateProxy ? ((HibernateProxy) this).getHibernateLazyInitializer().getPersistentClass() : this.getClass(); + if (thisEffectiveClass != oEffectiveClass) return false; + User user = (User) o; + return uId != null && Objects.equals(uId, user.uId); + } + + @Override + public final int hashCode() { + return this instanceof HibernateProxy ? ((HibernateProxy) this).getHibernateLazyInitializer().getPersistentClass().hashCode() : getClass().hashCode(); } diff --git a/src/main/java/ce3/wbc/entity/attribute/Address.java b/src/main/java/ce3/wbc/entity/attribute/Address.java index 52e9639..bcaa279 100644 --- a/src/main/java/ce3/wbc/entity/attribute/Address.java +++ b/src/main/java/ce3/wbc/entity/attribute/Address.java @@ -6,6 +6,9 @@ import lombok.AllArgsConstructor; import lombok.Getter; import lombok.NoArgsConstructor; +import org.hibernate.proxy.HibernateProxy; + +import java.util.Objects; @Embeddable @Getter @@ -22,7 +25,7 @@ public class Address { @Column(length = 50) private String zipcode; - public String fullAddress() { + public String getAddress() { return String.format("%s,%s,%s", city, street, zipcode); } @@ -36,5 +39,21 @@ public static Address of(String city, String street, String zipcode) { return new Address(city, street, zipcode); } + @Override + public final boolean equals(Object o) { + if (this == o) return true; + if (o == null) return false; + Class oEffectiveClass = o instanceof HibernateProxy ? ((HibernateProxy) o).getHibernateLazyInitializer().getPersistentClass() : o.getClass(); + Class thisEffectiveClass = this instanceof HibernateProxy ? ((HibernateProxy) this).getHibernateLazyInitializer().getPersistentClass() : this.getClass(); + if (thisEffectiveClass != oEffectiveClass) return false; + Address address = (Address) o; + return getCity() != null && Objects.equals(getCity(), address.getCity()) + && getStreet() != null && Objects.equals(getStreet(), address.getStreet()) + && getZipcode() != null && Objects.equals(getZipcode(), address.getZipcode()); + } + @Override + public final int hashCode() { + return Objects.hash(city, street, zipcode); + } } diff --git a/src/main/java/ce3/wbc/entity/attribute/AuditingFields.java b/src/main/java/ce3/wbc/entity/attribute/AuditingFields.java index ec2cc97..2956fc2 100644 --- a/src/main/java/ce3/wbc/entity/attribute/AuditingFields.java +++ b/src/main/java/ce3/wbc/entity/attribute/AuditingFields.java @@ -19,5 +19,6 @@ public class AuditingFields { protected LocalDateTime createdDate; @LastModifiedDate + @Column(insertable = false) protected LocalDateTime modifiedDate; } diff --git a/src/main/java/ce3/wbc/entity/attribute/UserRole.java b/src/main/java/ce3/wbc/entity/attribute/UserRole.java new file mode 100644 index 0000000..7ce6ad9 --- /dev/null +++ b/src/main/java/ce3/wbc/entity/attribute/UserRole.java @@ -0,0 +1,26 @@ +package ce3.wbc.entity.attribute; + +import lombok.Getter; +import lombok.ToString; + +@ToString +@Getter +public enum UserRole { + USER("ROLE_USER"), + ADMIN("ROLE_ADMIN"); + + private String roleType; + + UserRole(String roleType) {this.roleType = roleType;} + + public static UserRole getInstance(String roleType) { + for (UserRole role : values()) { + if (role.roleType.equals(roleType)) { + return role; + } + } + throw new IllegalArgumentException("Invalid roleType: " + roleType); + } + + +} diff --git a/src/main/java/ce3/wbc/repository/ChefRepository.java b/src/main/java/ce3/wbc/repository/ChefRepository.java index 5fba839..e12a405 100644 --- a/src/main/java/ce3/wbc/repository/ChefRepository.java +++ b/src/main/java/ce3/wbc/repository/ChefRepository.java @@ -1,9 +1,19 @@ package ce3.wbc.repository; + import ce3.wbc.entity.Chef; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; +import java.util.Optional; + + @Repository public interface ChefRepository extends JpaRepository { + + + boolean existsByChefName(String chef); + + Optional findByChefName(String chefName); } + diff --git a/src/main/java/ce3/wbc/repository/CommentRepository.java b/src/main/java/ce3/wbc/repository/CommentRepository.java index 1b16fc0..62a642b 100644 --- a/src/main/java/ce3/wbc/repository/CommentRepository.java +++ b/src/main/java/ce3/wbc/repository/CommentRepository.java @@ -1,9 +1,15 @@ package ce3.wbc.repository; import ce3.wbc.entity.Comment; +import ce3.wbc.entity.Restaurant; + +import java.util.List; + import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; @Repository public interface CommentRepository extends JpaRepository { + + List findByRestaurant(Restaurant restaurant); } diff --git a/src/main/java/ce3/wbc/repository/RestaurantRepository.java b/src/main/java/ce3/wbc/repository/RestaurantRepository.java index 44ee1d3..7a35c9d 100644 --- a/src/main/java/ce3/wbc/repository/RestaurantRepository.java +++ b/src/main/java/ce3/wbc/repository/RestaurantRepository.java @@ -1,9 +1,20 @@ package ce3.wbc.repository; import ce3.wbc.entity.Restaurant; +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.JpaSpecificationExecutor; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; @Repository -public interface RestaurantRepository extends JpaRepository { +public interface RestaurantRepository extends JpaRepository, JpaSpecificationExecutor { + @Query("SELECT r FROM Restaurant r JOIN r.chef c WHERE c.id = :chefId") + Page findRestaurantByChefId(@Param("chefId") int chefId, Pageable pageable); + + + + } diff --git a/src/main/java/ce3/wbc/repository/UserRepository.java b/src/main/java/ce3/wbc/repository/UserRepository.java index 0f23473..d9fa6c4 100644 --- a/src/main/java/ce3/wbc/repository/UserRepository.java +++ b/src/main/java/ce3/wbc/repository/UserRepository.java @@ -4,6 +4,10 @@ import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; +import java.util.Optional; + @Repository public interface UserRepository extends JpaRepository { + Optional findByUserId(String userId); + } diff --git a/src/main/java/ce3/wbc/security/WbcAuthenticationFailureHandler.java b/src/main/java/ce3/wbc/security/WbcAuthenticationFailureHandler.java new file mode 100644 index 0000000..1ce4a2b --- /dev/null +++ b/src/main/java/ce3/wbc/security/WbcAuthenticationFailureHandler.java @@ -0,0 +1,44 @@ +package ce3.wbc.security; + +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.context.MessageSource; +import org.springframework.security.authentication.*; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.security.web.authentication.AuthenticationFailureHandler; +import org.springframework.stereotype.Component; + +import java.io.IOException; + +@Slf4j +@RequiredArgsConstructor +@Component +public class WbcAuthenticationFailureHandler implements AuthenticationFailureHandler { + private final MessageSource messageSource; + + @Override + public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException { + + String errorMessage = "로그인에 실패하였습니다. 아이디 또는 비밀번호를 확인해주세요!"; + + if (exception instanceof UsernameNotFoundException) { + errorMessage = messageSource.getMessage("username.notfound", null,errorMessage ,request.getLocale()); + } else if (exception instanceof BadCredentialsException) { + errorMessage = messageSource.getMessage("bad.credentials", null,errorMessage ,request.getLocale()); + } else if (exception instanceof LockedException) { + errorMessage = "계정이 잠겼습니다. 관리자에게 문의하세요."; + } else if (exception instanceof CredentialsExpiredException) { + errorMessage = "계정이 잠겼습니다. 관리자에게 문의하세요."; + } else if (exception instanceof AccountExpiredException) { + errorMessage = "계정이 잠겼습니다. 관리자에게 문의하세요."; + } else if (exception instanceof DisabledException) { + errorMessage = "비밀번호가 만료되었습니다. 새 비밀번호로 변경해주세요."; + } + request.setAttribute("errorMessage", errorMessage); + response.sendRedirect(request.getContextPath() + "/login?error="); + } +} diff --git a/src/main/java/ce3/wbc/security/WbcAuthenticationSuccessHandler.java b/src/main/java/ce3/wbc/security/WbcAuthenticationSuccessHandler.java new file mode 100644 index 0000000..63ab1c0 --- /dev/null +++ b/src/main/java/ce3/wbc/security/WbcAuthenticationSuccessHandler.java @@ -0,0 +1,30 @@ +package ce3.wbc.security; + +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.security.core.Authentication; +import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler; +import org.springframework.stereotype.Component; + +import java.io.IOException; + +@Component +public class WbcAuthenticationSuccessHandler extends SimpleUrlAuthenticationSuccessHandler { + + @Override + public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, + Authentication authentication) throws IOException, ServletException { + String prevPage = (String) request.getSession().getAttribute("prevPage"); + request.getSession().removeAttribute("prevPage"); + if (prevPage != null && !prevPage.equals("/") && !prevPage.contains("/login")) { + getRedirectStrategy().sendRedirect(request, response,prevPage); + } else { + getRedirectStrategy().sendRedirect(request, response,"/chefs"); + } + + } + + + +} diff --git a/src/main/java/ce3/wbc/security/WbcUserDetails.java b/src/main/java/ce3/wbc/security/WbcUserDetails.java new file mode 100644 index 0000000..400a7f2 --- /dev/null +++ b/src/main/java/ce3/wbc/security/WbcUserDetails.java @@ -0,0 +1,62 @@ +package ce3.wbc.security; + +import ce3.wbc.entity.User; +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 java.util.Collection; +import java.util.List; + +@Getter +@RequiredArgsConstructor +public class WbcUserDetails implements UserDetails { + + private final User user; + + @Override + public Collection getAuthorities() { + return List.of(new SimpleGrantedAuthority(user.getUserRole().getRoleType())); + // Spring Security 사용권장 : SimpleGrantedAuthority + } + + @Override // 비밀버노인증 + public String getPassword() { + return user.getUserPassword(); + } + + @Override //Name인증 + public String getUsername() { + return user.getUserName(); + } + + //id인증 + public String getUserId() { + return user.getUserId(); + } + + @Override // 계정 잠금 + public boolean isAccountNonLocked() { + return true; + } + + @Override // 비밀번호가 만료 + public boolean isCredentialsNonExpired() { + return true; + } + + @Override // 계정이 만료됨 + public boolean isAccountNonExpired() { + return UserDetails.super.isAccountNonExpired(); + } + + @Override // 계정이 비활성화 + public boolean isEnabled() { + return true; + } +} + + + diff --git a/src/main/java/ce3/wbc/security/WbcUserDetailsService.java b/src/main/java/ce3/wbc/security/WbcUserDetailsService.java new file mode 100644 index 0000000..706ea04 --- /dev/null +++ b/src/main/java/ce3/wbc/security/WbcUserDetailsService.java @@ -0,0 +1,24 @@ +package ce3.wbc.security; + +import ce3.wbc.repository.UserRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.stereotype.Service; + +import java.util.Optional; + +@Service +@RequiredArgsConstructor +public class WbcUserDetailsService implements UserDetailsService { + + private final UserRepository userRepository; + + @Override + public UserDetails loadUserByUsername(String userId) throws UsernameNotFoundException { + Optional wbcUserDetails = userRepository.findByUserId(userId).map(WbcUserDetails::new); + return wbcUserDetails.orElseThrow(() -> new UsernameNotFoundException(userId)); + + } +} diff --git a/src/main/java/ce3/wbc/service/ChefService.java b/src/main/java/ce3/wbc/service/ChefService.java index ce7d461..5b857b1 100644 --- a/src/main/java/ce3/wbc/service/ChefService.java +++ b/src/main/java/ce3/wbc/service/ChefService.java @@ -1,14 +1,61 @@ package ce3.wbc.service; +import ce3.wbc.controller.rto.response.ChefRes; +import ce3.wbc.dto.ChefDto; +import ce3.wbc.entity.Chef; import ce3.wbc.repository.ChefRepository; +import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.multipart.MultipartFile; -@Service +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; +@Transactional @RequiredArgsConstructor +@Service public class ChefService { + private final ChefRepository chefRepository; + private final S3ImageService s3ImageService; + private static final String DEFAULT_IMAGE_KEY = "ChefDefault.jpg"; + + + public List getAllChefs() { + return chefRepository.findAll().stream() + .map(ChefRes::toResponse) + .collect(Collectors.toList()); + } + + public Map> getChefsGroupedByCategory() { + return chefRepository.findAll().stream() + .collect(Collectors.groupingBy(Chef::getChefCategory, + Collectors.mapping(ChefRes::toResponse, Collectors.toList()))); + } + + public ChefDto saveChef(@Valid ChefDto chefDto, MultipartFile file) { + String s3Key; + String chefEngName = chefDto.getChefImage();// EngName == chefImage + String originalImgName = (file != null) ? file.getOriginalFilename() : "ChefDefault"; + + if (file == null || file.isEmpty()) { + s3Key = DEFAULT_IMAGE_KEY; + } else { + //s3이미지저장 + s3Key = s3ImageService.uploadS3(file, chefEngName); + } + Chef chef = Chef.of( + chefDto.getChefName(), + chefDto.getChefCategory(), + s3Key, + originalImgName); + Chef saved = chefRepository.save(chef); + + return ChefDto.toDto(saved); + } } diff --git a/src/main/java/ce3/wbc/service/CommentService.java b/src/main/java/ce3/wbc/service/CommentService.java index 2f01618..01199a4 100644 --- a/src/main/java/ce3/wbc/service/CommentService.java +++ b/src/main/java/ce3/wbc/service/CommentService.java @@ -1,12 +1,73 @@ package ce3.wbc.service; +import java.util.ArrayList; +import java.util.List; + +import ce3.wbc.controller.rto.request.CommentReq; +import ce3.wbc.dto.UserDto; +import ce3.wbc.repository.RestaurantRepository; +import org.springframework.stereotype.Service; +import ce3.wbc.dto.CommentDto; +import ce3.wbc.entity.Comment; +import ce3.wbc.entity.Restaurant; +import ce3.wbc.entity.User; import ce3.wbc.repository.CommentRepository; +import jakarta.persistence.EntityNotFoundException; +import jakarta.transaction.Transactional; import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Service; @Service @RequiredArgsConstructor public class CommentService { private final CommentRepository commentRepository; + private final RestaurantRepository restaurantRepository; + private final UserService userService; + + public List getComments(Integer restId) { + Restaurant restaurant = restaurantRepository.findById(restId) + .orElseThrow(() -> new IllegalArgumentException("존재 하지 않는 레스토랑입니다.")); + List comments = commentRepository.findByRestaurant(restaurant); + + List commentDtos = new ArrayList<>(); + + for (Comment comment : comments) { + commentDtos.add(CommentDto.toCommentDto(comment)); + } + return commentDtos; + } + + @Transactional + public void addComment(CommentReq commentReq, UserDto userDto) { + User user = userService.getUser(userDto.getUId()); + Integer restId = commentReq.getRestId(); + Restaurant restaurant = restaurantRepository.findById(restId) + .orElseThrow(() -> new IllegalArgumentException("존재 하지 않는 레스토랑입니다.")); + + Comment comment = CommentDto.toEntity(commentReq, restaurant, user); + commentRepository.save(comment); + + } + + // 댓글 수정 + @Transactional + public CommentDto updateComment(Integer commId, String commContent, String commStar) { + Comment comment = commentRepository.findById(commId) + .orElseThrow(() -> new EntityNotFoundException("Comment not found with id: " + commId));; + // 수정 + comment.update(commId, commContent, commStar); + + // Restaurant + Restaurant restaurant = comment.getRestaurant(); + + return CommentDto.toCommentDto(comment); + + } + // 댓글 삭제 + @Transactional + public void deleteComment(Integer commId) { + Comment comment = commentRepository.findById(commId).orElseThrow(() -> new EntityNotFoundException("Comment not found with id: " + commId)); + + commentRepository.deleteById(comment.getCommId()); + } } diff --git a/src/main/java/ce3/wbc/service/PagingService.java b/src/main/java/ce3/wbc/service/PagingService.java new file mode 100644 index 0000000..4cb2b1c --- /dev/null +++ b/src/main/java/ce3/wbc/service/PagingService.java @@ -0,0 +1,23 @@ +package ce3.wbc.service; + +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.stream.IntStream; + +@Service +public class PagingService { + public static final int PAGE_LENGTH = 6;// 한페이지 몇개? + // 하단 페이징 숫자 + public List getPageNumbers(int pageNumber, int totalPages) { + int startPage = Math.max((pageNumber - (PAGE_LENGTH / 2)), 0); // 가장 왼쪽 + int endPage = Math.min((startPage + PAGE_LENGTH), totalPages); // 가장 오른쪽 + + return IntStream.range(startPage, endPage).boxed().toList(); + } + + public int getPageLength() { + return PAGE_LENGTH; + } + +} diff --git a/src/main/java/ce3/wbc/service/RestaurantService.java b/src/main/java/ce3/wbc/service/RestaurantService.java index b77cb5c..aef50f6 100644 --- a/src/main/java/ce3/wbc/service/RestaurantService.java +++ b/src/main/java/ce3/wbc/service/RestaurantService.java @@ -1,13 +1,182 @@ package ce3.wbc.service; - +import ce3.wbc.controller.rto.request.RestaurantCreate; +import ce3.wbc.controller.rto.request.RestaurantEdit; +import ce3.wbc.controller.rto.request.RestaurantReq; +import ce3.wbc.controller.rto.response.RestaurantRes; +import ce3.wbc.dto.RestaurantDto; +import ce3.wbc.entity.Chef; +import ce3.wbc.entity.Restaurant; +import ce3.wbc.entity.attribute.Address; +import ce3.wbc.repository.ChefRepository; import ce3.wbc.repository.RestaurantRepository; +import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.domain.Specification; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.multipart.MultipartFile; +import java.util.ArrayList; +@Transactional(readOnly = true) @Service @RequiredArgsConstructor public class RestaurantService { private final RestaurantRepository restaurantRepository; + private final ChefRepository chefRepository; + private final S3ImageService s3ImageService; + private static final String DEFAULT_IMAGE_KEY = "default.jpg"; + + public Page findAllRestList(Pageable pageable) { + Page restaurants = restaurantRepository.findAll(pageable); + return restaurants.map(RestaurantDto::toDto); + } + + public Page findRestList(int chef, Pageable pageable) { + Page restaurants = restaurantRepository.findRestaurantByChefId(chef, pageable); + return restaurants.map(RestaurantDto::toDto); + } + + // filtering + public Page findFilterRestList(RestaurantReq req , Pageable pageable) { + // 동적 조건 받음 + Specification spec = Specification.where(null); + if (req.getRestName() != null) { + spec = spec.and((root, query, criteriaBuilder) + -> criteriaBuilder.equal(root.get("restName"), req.getChefName())); + } + if (req.getAddress() != null && req.getAddress().getCity() != null) { + spec = spec.and((root, query, criteriaBuilder) + -> criteriaBuilder.like(root.get("address").get("city"), req.getAddress().getCity() + "%")); + } + if (req.getRestRental() != null) { + spec = spec.and(((root, query, criteriaBuilder) + -> criteriaBuilder.equal(root.get("restRental"), req.getRestRental()))); + } + if (req.getGroupReservation() != null) { + spec = spec.and(((root, query, criteriaBuilder) + -> criteriaBuilder.equal(root.get("groupReservation"), req.getGroupReservation()))); + } + if (req.getCorkage() != null) { + spec = spec.and(((root, query, criteriaBuilder) + -> criteriaBuilder.equal(root.get("corkage"), req.getCorkage()))); + } + if (req.getNoKidsZone() != null) { + spec = spec.and(((root, query, criteriaBuilder) + -> criteriaBuilder.equal(root.get("noKidsZone"), req.getNoKidsZone()))); + } + if (req.getChefName() != null) { + spec = spec.and(((root, query, criteriaBuilder) + -> criteriaBuilder.equal(root.get("chef").get("chefName"), req.getChefName()))); + } + + Page filteredRest = restaurantRepository.findAll(spec, pageable); + return filteredRest.map(RestaurantDto::toDto); + + } + + @Transactional + public RestaurantDto create(@Valid RestaurantCreate restaurantCreate, MultipartFile file) { + String s3Key; + String restEngName = restaurantCreate.getRestEngName(); + String originalImgName = (file != null) ? file.getOriginalFilename() : "default"; + + if (file == null || file.isEmpty()) { + s3Key = DEFAULT_IMAGE_KEY; + } else { + //s3이미지저장 + s3Key = s3ImageService.uploadS3(file, restEngName); + } + Chef chef = chefRepository.findByChefName(restaurantCreate.getChefName()) + .orElseThrow(() -> new IllegalArgumentException("해당 셰프가 존재하지 않습니다.")); + + Address address = Address.of( + restaurantCreate.getAddress().getCity(), + restaurantCreate.getAddress().getStreet(), + restaurantCreate.getAddress().getZipcode() + ); + Restaurant restaurant = Restaurant.of( + restaurantCreate.getRestName(), // 레스토랑 이름 + s3Key, // S3에 저장된 이미지 키 (restImg) + originalImgName, // 원본 이미지명 + restaurantCreate.getRestPhone(), + address, // 주소 값타입 + restaurantCreate.isRestRental(), + restaurantCreate.isGroupReservation(), + restaurantCreate.isCorkage(), + restaurantCreate.isNoKidsZone(), + chef, // 셰프 엔티티 + new ArrayList<>()// 댓글 리스트 (초기엔 빈 리스트) + ); + Restaurant savedRestaurant = restaurantRepository.save(restaurant); + return RestaurantDto.toDto(savedRestaurant); + + } + @Transactional + public RestaurantRes update(int id, RestaurantEdit restaurantEdit, MultipartFile file) { + Restaurant restaurant = restaurantRepository.findById(id) + .orElseThrow(() -> new IllegalArgumentException("해당 레스토랑이 존재하지 않습니다. id: " + id)); + + if (restaurantEdit.getRestId() != null && !restaurantEdit.getRestId().equals(id)) { + throw new IllegalArgumentException("경로의 id와 요청의 id가 일치하지 않습니다."); + } + String updatedImage = restaurant.getRestImg(); // 기존 이미지 유지 + String updatedOriginalImgName = restaurant.getOriginalImgName(); // 기존 원본 이미지 유지 + + if (file != null && !file.isEmpty()) { + + updatedImage = s3ImageService.uploadS3(file, restaurant.getRestName()); + updatedOriginalImgName = file.getOriginalFilename(); + } + + + Address updatedAddress = restaurantEdit.getAddress() != null ? + Address.of( + restaurantEdit.getAddress().getCity(), + restaurantEdit.getAddress().getStreet(), + restaurantEdit.getAddress().getZipcode() + ) : restaurant.getAddress(); + + + Chef updatedChef = restaurantEdit.getChefName() != null ? + chefRepository.findByChefName(restaurantEdit.getChefName()) + .orElseThrow(() -> new IllegalArgumentException("해당 셰프가 존재하지 않습니다.")) : + restaurant.getChef(); + + + Restaurant updatedRestaurant = Restaurant.of( + restaurant.getRestId(), // 기존 ID 유지 + restaurantEdit.getRestName() != null ? restaurantEdit.getRestName() : restaurant.getRestName(), + updatedImage, // 변경된 이미지 반영 + updatedOriginalImgName, // 변경된 원본 이미지명 반영 + restaurantEdit.getRestPhone() != null ? restaurantEdit.getRestPhone() : restaurant.getRestPhone(), + updatedAddress, + restaurantEdit.isRestRental(), + restaurantEdit.isGroupReservation(), + restaurantEdit.isCorkage(), + restaurantEdit.isNoKidsZone(), + updatedChef, + restaurant.getComments() // 기존 댓글 유지 + ); + + Restaurant savedRestaurant = restaurantRepository.save(updatedRestaurant); + + return RestaurantRes.toResponse(RestaurantDto.toDto(savedRestaurant)); + } + + + @Transactional + public void delete(int id) { + Restaurant restaurant = restaurantRepository.findById(id) + .orElseThrow(() -> new IllegalArgumentException("해당 레스토랑이 존재하지 않습니다. id: " + id)); + restaurantRepository.delete(restaurant); + } + public RestaurantRes getRestaurantById(int restId) { + Restaurant restaurant = restaurantRepository.findById(restId) + .orElseThrow(() -> new IllegalArgumentException("존재하지 않는 레스토랑입니다.")); + return RestaurantRes.toResponse(RestaurantDto.toDto(restaurant)); + } } diff --git a/src/main/java/ce3/wbc/service/S3ImageService.java b/src/main/java/ce3/wbc/service/S3ImageService.java new file mode 100644 index 0000000..4377660 --- /dev/null +++ b/src/main/java/ce3/wbc/service/S3ImageService.java @@ -0,0 +1,96 @@ +package ce3.wbc.service; + +import ce3.wbc.util.WbcFileUtil; +import io.awspring.cloud.s3.S3Exception; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.multipart.MultipartFile; +import software.amazon.awssdk.core.sync.RequestBody; +import software.amazon.awssdk.services.s3.S3Client; +import software.amazon.awssdk.services.s3.model.NoSuchKeyException; +import software.amazon.awssdk.services.s3.model.PutObjectRequest; + +import java.io.File; +import java.io.IOException; + +@Service +@RequiredArgsConstructor +@Slf4j +@Transactional +public class S3ImageService { + private final S3Client s3Client; + private static final long MAX_SIZE = 10 * 1024 * 1024; + + + @Value("${s3.bucket}") + private String bucketName; + //임시저장 경로 +/* @Value("${storage.temp.path}") + private String tempStoragePath;*/ + + //s3업로드 + public String uploadS3(MultipartFile multipartFile, String RestEngName) { + + //파일명 생성 시 영문 가게 이름 사용 : + String fileName = WbcFileUtil.createFileName(multipartFile.getOriginalFilename(), RestEngName); + + String s3Key = RestEngName + "/" + fileName; + + + try { + PutObjectRequest putObjectRequest = PutObjectRequest.builder() + .bucket(bucketName) + .key(s3Key) + //.acl(ObjectCannedACL.PUBLIC_READ) + .contentType(multipartFile.getContentType()) + .build(); + + //파일 크기(10MB)에따라 + if (multipartFile.getSize() > MAX_SIZE) { + File file = WbcFileUtil.MultipartFileToFile(multipartFile); + s3Client.putObject(putObjectRequest, RequestBody.fromFile(file)); + file.delete();// 업로드 후 임시 파일 삭제 + } else { + s3Client.putObject(putObjectRequest, RequestBody.fromBytes(multipartFile.getBytes())); + } + // S3에 저장된 파일의 키 반환 + return s3Key; + } catch (IOException e) { + throw new RuntimeException("s3업로드 실패", e); + } + + } + + private boolean doesS3DirectoryExist(String directoryName) { + try { + return !s3Client.listObjectsV2(builder -> builder + .bucket(bucketName) + .prefix(directoryName + "/") //디렉토리처럼 보이는 키(prefix) 검색 + .maxKeys(1) // 최소 하나의 객체라도 있으면 존재하는 것으로 간주 + ).contents().isEmpty(); + } catch (NoSuchKeyException | S3Exception e) { + return false; + } + } + + private void createS3Directory(String directoryName) { + try { + String s3Key = directoryName + "/"; + PutObjectRequest putObjectRequest = PutObjectRequest.builder() + .bucket(bucketName) + .key(s3Key) + .build(); + s3Client.putObject(putObjectRequest, RequestBody.empty()); + log.info("S3 디렉토리 생성 완료: {}", directoryName); + } catch (Exception e) { + throw new RuntimeException("s3 디렉토리 생성 실패!", e); + } + } + +} + + + diff --git a/src/main/java/ce3/wbc/service/UserService.java b/src/main/java/ce3/wbc/service/UserService.java index 5ca52e8..d834880 100644 --- a/src/main/java/ce3/wbc/service/UserService.java +++ b/src/main/java/ce3/wbc/service/UserService.java @@ -1,13 +1,42 @@ package ce3.wbc.service; -import ce3.wbc.repository.RestaurantRepository; +import ce3.wbc.dto.UserDto; +import ce3.wbc.entity.User; +import ce3.wbc.repository.UserRepository; +import jakarta.persistence.EntityNotFoundException; import lombok.RequiredArgsConstructor; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; @Service @RequiredArgsConstructor +@Transactional public class UserService { - private final RestaurantRepository restaurantRepository; + + private final UserRepository userRepository; + private final AuthenticationManager authenticationManager; + private final PasswordEncoder passwordEncoder; + + + + public User getUser(Integer uId) { + User user = userRepository.findById(uId) + .orElseThrow(() -> new EntityNotFoundException("User not found with id: " + uId)); + + return user; + } + + public UserDto createUser(UserDto userDto) { + User user = User.of( + userDto.getUserId(), + passwordEncoder.encode(userDto.getUserPassword()), + userDto.getUserName() + ); + userRepository.save(user); + return UserDto.toDto(user); + } diff --git a/src/main/java/ce3/wbc/util/UserRoleConverter.java b/src/main/java/ce3/wbc/util/UserRoleConverter.java new file mode 100644 index 0000000..0cbed58 --- /dev/null +++ b/src/main/java/ce3/wbc/util/UserRoleConverter.java @@ -0,0 +1,19 @@ +package ce3.wbc.util; + +import ce3.wbc.entity.attribute.UserRole; +import jakarta.persistence.AttributeConverter; +import jakarta.persistence.Convert; + +@Convert +public class UserRoleConverter implements AttributeConverter { + + @Override + public String convertToDatabaseColumn(UserRole attribute) { + return attribute.getRoleType(); + } + + @Override + public UserRole convertToEntityAttribute(String dbData) { + return UserRole.getInstance(dbData); + } +} diff --git a/src/main/java/ce3/wbc/util/WbcFileUtil.java b/src/main/java/ce3/wbc/util/WbcFileUtil.java new file mode 100644 index 0000000..d3d59f5 --- /dev/null +++ b/src/main/java/ce3/wbc/util/WbcFileUtil.java @@ -0,0 +1,39 @@ +package ce3.wbc.util; + +import org.springframework.web.multipart.MultipartFile; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.UUID; + +public class WbcFileUtil { + + public static File MultipartFileToFile(MultipartFile multipartFile) throws IOException { + // 임시 디렉토리 경로와 원본 파일명을 사용하여 File 객체 생성 + File file = new File(System.getProperty("java.io.tmpdir") + "/" + multipartFile.getOriginalFilename()); + + try (FileOutputStream fops = new FileOutputStream(file)) { + fops.write(multipartFile.getBytes()); + } + return file; + + } + + public static String createFileName(String originalFileName, String RestEngName) { + if (originalFileName == null || originalFileName.isEmpty()) { + throw new IllegalArgumentException(" 파일명이 비었습니다"); + } + //파일명과 확장자 분리 + String extension = "";// 확장자 담기 + int lastIndex = originalFileName.lastIndexOf("."); + if (lastIndex != -1) { + extension = originalFileName.substring(lastIndex); + } + //특수문자 제거 + 공백 제거 + String s3Name = RestEngName.replaceAll("[^\\p{L}\\p{N}]", ""); + + // 유일한 파일명 + return UUID.randomUUID().toString().substring(0, 8) + "_" + s3Name + extension; + } +} diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 139597f..e69de29 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1,2 +0,0 @@ - - diff --git a/src/main/resources/messages_ko.properties b/src/main/resources/messages_ko.properties new file mode 100644 index 0000000..e0ec486 --- /dev/null +++ b/src/main/resources/messages_ko.properties @@ -0,0 +1,2 @@ +username.notfound="???? ?? ??????." +bad.credentials="??? ?? ????? ???????." \ No newline at end of file diff --git a/src/main/resources/static/css/header.css b/src/main/resources/static/css/header.css new file mode 100644 index 0000000..6286044 --- /dev/null +++ b/src/main/resources/static/css/header.css @@ -0,0 +1,46 @@ +.header { + background-color: #333; + color: white; + padding: 1rem; +} + +.header-content { + display: flex; + justify-content: space-between; + align-items: center; + max-width: 1200px; + margin: 0 auto; +} + +.logo { + font-size: 1.5rem; + font-weight: bold; + text-decoration: none; + color: white; +} + +.nav-links { + display: flex; + gap: 1rem; +} + +.nav-links a, .nav-links button { + color: white; + text-decoration: none; + background: none; + border: none; + cursor: pointer; + font-size: 1rem; +} + +.login-btn { + background-color: #4CAF50; + padding: 0.5rem 1rem; + border-radius: 4px; +} + +.logout-btn { + background-color: #f44336; + padding: 0.5rem 1rem; + border-radius: 4px; +} \ No newline at end of file diff --git a/src/main/resources/static/default_image.png b/src/main/resources/static/default_image.png new file mode 100644 index 0000000..dabea5e Binary files /dev/null and b/src/main/resources/static/default_image.png differ diff --git a/src/main/resources/static/wbc_logo.png b/src/main/resources/static/wbc_logo.png new file mode 100644 index 0000000..7be9fbf Binary files /dev/null and b/src/main/resources/static/wbc_logo.png differ diff --git a/src/main/resources/templates/detail.html b/src/main/resources/templates/detail.html new file mode 100644 index 0000000..b5ceceb --- /dev/null +++ b/src/main/resources/templates/detail.html @@ -0,0 +1,335 @@ + + + + + + + WBC + + + + + +
+
+ +
+

레스토랑 이름

+
+ 셰프: + 셰프 이름 +
+
+ 주소: + 레스토랑 주소 +
+
+ 전화번호: + 전화번호 +
+
+ 대관 여부: + 대관 여부 +
+
+ 단체 예약: + 단체 예약 가능 여부 +
+
+ 콜키지: + 콜키지 가능 여부 +
+
+ 노 키즈존: + 노 키즈존 여부 +
+
+
+
+ +
+
+ + + +
+
+ +
+

+ 댓글을 작성하려면 로그인해야 합니다. +

+
+ +
+ +
+ +
+ +
+
+
+ + +
+
+
+ 작성자 +
+

댓글 내용

+

별점: 5

+
+ + +
+
+
+
+
+ + + + diff --git a/src/main/resources/templates/error/400.html b/src/main/resources/templates/error/400.html new file mode 100644 index 0000000..b8c9e0d --- /dev/null +++ b/src/main/resources/templates/error/400.html @@ -0,0 +1,53 @@ + + + + + Error 400 + + + + + + +
+

400 : Bad Request

+
잘못된 요청입니다.
+ 메인으로 돌아가기 +
+ + diff --git a/src/main/resources/templates/error/500.html b/src/main/resources/templates/error/500.html new file mode 100644 index 0000000..74ef279 --- /dev/null +++ b/src/main/resources/templates/error/500.html @@ -0,0 +1,53 @@ + + + + + Error 500 + + + + + + +
+

500 : Internal Server Error

+
죄송합니다. 서버에 문제가 발생했습니다.
+ 메인으로 돌아가기 +
+ + diff --git a/src/main/resources/templates/header.html b/src/main/resources/templates/header.html new file mode 100644 index 0000000..136efcb --- /dev/null +++ b/src/main/resources/templates/header.html @@ -0,0 +1,16 @@ +
+
+ + +
+
\ No newline at end of file diff --git a/src/main/resources/templates/login.html b/src/main/resources/templates/login.html new file mode 100644 index 0000000..8423de5 --- /dev/null +++ b/src/main/resources/templates/login.html @@ -0,0 +1,91 @@ + + + + + + + WBC + + + + + + \ No newline at end of file diff --git a/src/main/resources/templates/main.html b/src/main/resources/templates/main.html new file mode 100644 index 0000000..815ef77 --- /dev/null +++ b/src/main/resources/templates/main.html @@ -0,0 +1,228 @@ + + + + + + + WBC + + + + + + +
+
+ + + +
+ +
+
+ +
요리사 이름
+
+
+
+ + + + + + + \ No newline at end of file diff --git a/src/main/resources/templates/sign-up.html b/src/main/resources/templates/sign-up.html new file mode 100644 index 0000000..acabc0c --- /dev/null +++ b/src/main/resources/templates/sign-up.html @@ -0,0 +1,125 @@ + + + + + + + WBC + + + + + + + + \ No newline at end of file