From 8da003fb73b17191ff6ce85bf4f4533eac3c1cdb Mon Sep 17 00:00:00 2001 From: BOMIN LYU Date: Tue, 30 Jul 2024 17:41:29 +0900 Subject: [PATCH] Move from spring-gift-order --- README.md | 34 +++- build.gradle | 10 + .../LoginMemberArgumentResolver.java | 80 ++++++++ .../gift/Annotation/LoginMemberResolver.java | 11 ++ src/main/java/gift/Config/WebConfig.java | 22 +++ .../gift/Controller/CategoryController.java | 29 +++ .../Controller/GlobalExceptionHandler.java | 45 +++++ .../gift/Controller/KakaoOAuthController.java | 115 ++++++++++++ .../gift/Controller/MemberController.java | 80 ++++++++ .../gift/Controller/OptionController.java | 130 +++++++++++++ .../gift/Controller/ProductController.java | 99 ++++++++++ .../gift/Controller/WishlistController.java | 80 ++++++++ .../DatabaseInitializer.java | 32 ++++ src/main/java/gift/Entity/Category.java | 41 ++++ src/main/java/gift/Entity/Member.java | 72 +++++++ src/main/java/gift/Entity/Option.java | 69 +++++++ src/main/java/gift/Entity/Product.java | 78 ++++++++ src/main/java/gift/Entity/Wishlist.java | 96 ++++++++++ src/main/java/gift/Entity/WishlistId.java | 36 ++++ .../gift/Exception/ForbiddenException.java | 11 ++ .../gift/Exception/UnauthorizedException.java | 11 ++ src/main/java/gift/Mapper/Mapper.java | 89 +++++++++ src/main/java/gift/Model/CategoryDto.java | 32 ++++ .../gift/Model/GetKakaoTokenInformation.java | 20 ++ .../java/gift/Model/KakaoAccessTokenDto.java | 33 ++++ src/main/java/gift/Model/KakaoAccount.java | 22 +++ src/main/java/gift/Model/KakaoMemberDto.java | 21 +++ src/main/java/gift/Model/Link.java | 32 ++++ src/main/java/gift/Model/MemberDto.java | 70 +++++++ src/main/java/gift/Model/OptionDto.java | 71 +++++++ src/main/java/gift/Model/OrderRequestDto.java | 51 +++++ src/main/java/gift/Model/ProductDto.java | 83 ++++++++ src/main/java/gift/Model/TextObject.java | 41 ++++ src/main/java/gift/Model/WishlistDto.java | 80 ++++++++ .../Repository/CategoryJpaRepository.java | 8 + .../gift/Repository/MemberJpaRepository.java | 16 ++ .../gift/Repository/OptionJpaRepository.java | 11 ++ .../gift/Repository/ProductJpaRepository.java | 14 ++ .../Repository/WishlistJpaRepository.java | 26 +++ .../java/gift/Service/CategoryService.java | 35 ++++ .../java/gift/Service/KakaoTalkService.java | 99 ++++++++++ src/main/java/gift/Service/MemberService.java | 56 ++++++ src/main/java/gift/Service/OptionService.java | 68 +++++++ .../java/gift/Service/ProductService.java | 72 +++++++ .../java/gift/Service/WishlistService.java | 64 +++++++ src/main/java/gift/Utils/JwtUtil.java | 55 ++++++ .../gift/Validation/KakaoTokenValidator.java | 31 +++ src/main/resources/application.properties | 12 ++ .../static/kakao_login_medium_narrow.png | Bin 0 -> 2946 bytes src/main/resources/templates/login.html | 35 ++++ src/main/resources/templates/option_form.html | 36 ++++ .../resources/templates/product_form.html | 51 +++++ src/main/resources/templates/products.html | 143 ++++++++++++++ src/main/resources/templates/register.html | 29 +++ .../resources/templates/user_products.html | 177 ++++++++++++++++++ src/main/resources/templates/wishlist.html | 137 ++++++++++++++ .../KakaoOAuthControllerTest.java | 105 +++++++++++ .../MemberDtoRepositoryTest.java | 100 ++++++++++ .../RepositoryTest/OptionRepositoryTest.java | 115 ++++++++++++ .../ProductDtoRepositoryTest.java | 128 +++++++++++++ .../WishlistRepositoryTest.java | 174 +++++++++++++++++ .../gift/ServiceTest/MemberServiceTest.java | 94 ++++++++++ .../gift/ServiceTest/OptionServiceTest.java | 123 ++++++++++++ .../gift/ServiceTest/ProductServiceTest.java | 118 ++++++++++++ .../gift/ServiceTest/WishlistServiceTest.java | 127 +++++++++++++ 65 files changed, 4084 insertions(+), 1 deletion(-) create mode 100644 src/main/java/gift/Annotation/LoginMemberArgumentResolver.java create mode 100644 src/main/java/gift/Annotation/LoginMemberResolver.java create mode 100644 src/main/java/gift/Config/WebConfig.java create mode 100644 src/main/java/gift/Controller/CategoryController.java create mode 100644 src/main/java/gift/Controller/GlobalExceptionHandler.java create mode 100644 src/main/java/gift/Controller/KakaoOAuthController.java create mode 100644 src/main/java/gift/Controller/MemberController.java create mode 100644 src/main/java/gift/Controller/OptionController.java create mode 100644 src/main/java/gift/Controller/ProductController.java create mode 100644 src/main/java/gift/Controller/WishlistController.java create mode 100644 src/main/java/gift/DatabaseInitialize/DatabaseInitializer.java create mode 100644 src/main/java/gift/Entity/Category.java create mode 100644 src/main/java/gift/Entity/Member.java create mode 100644 src/main/java/gift/Entity/Option.java create mode 100644 src/main/java/gift/Entity/Product.java create mode 100644 src/main/java/gift/Entity/Wishlist.java create mode 100644 src/main/java/gift/Entity/WishlistId.java create mode 100644 src/main/java/gift/Exception/ForbiddenException.java create mode 100644 src/main/java/gift/Exception/UnauthorizedException.java create mode 100644 src/main/java/gift/Mapper/Mapper.java create mode 100644 src/main/java/gift/Model/CategoryDto.java create mode 100644 src/main/java/gift/Model/GetKakaoTokenInformation.java create mode 100644 src/main/java/gift/Model/KakaoAccessTokenDto.java create mode 100644 src/main/java/gift/Model/KakaoAccount.java create mode 100644 src/main/java/gift/Model/KakaoMemberDto.java create mode 100644 src/main/java/gift/Model/Link.java create mode 100644 src/main/java/gift/Model/MemberDto.java create mode 100644 src/main/java/gift/Model/OptionDto.java create mode 100644 src/main/java/gift/Model/OrderRequestDto.java create mode 100644 src/main/java/gift/Model/ProductDto.java create mode 100644 src/main/java/gift/Model/TextObject.java create mode 100644 src/main/java/gift/Model/WishlistDto.java create mode 100644 src/main/java/gift/Repository/CategoryJpaRepository.java create mode 100644 src/main/java/gift/Repository/MemberJpaRepository.java create mode 100644 src/main/java/gift/Repository/OptionJpaRepository.java create mode 100644 src/main/java/gift/Repository/ProductJpaRepository.java create mode 100644 src/main/java/gift/Repository/WishlistJpaRepository.java create mode 100644 src/main/java/gift/Service/CategoryService.java create mode 100644 src/main/java/gift/Service/KakaoTalkService.java create mode 100644 src/main/java/gift/Service/MemberService.java create mode 100644 src/main/java/gift/Service/OptionService.java create mode 100644 src/main/java/gift/Service/ProductService.java create mode 100644 src/main/java/gift/Service/WishlistService.java create mode 100644 src/main/java/gift/Utils/JwtUtil.java create mode 100644 src/main/java/gift/Validation/KakaoTokenValidator.java create mode 100644 src/main/resources/static/kakao_login_medium_narrow.png create mode 100644 src/main/resources/templates/login.html create mode 100644 src/main/resources/templates/option_form.html create mode 100644 src/main/resources/templates/product_form.html create mode 100644 src/main/resources/templates/products.html create mode 100644 src/main/resources/templates/register.html create mode 100644 src/main/resources/templates/user_products.html create mode 100644 src/main/resources/templates/wishlist.html create mode 100644 src/test/java/gift/ControllerTest/KakaoOAuthControllerTest.java create mode 100644 src/test/java/gift/RepositoryTest/MemberDtoRepositoryTest.java create mode 100644 src/test/java/gift/RepositoryTest/OptionRepositoryTest.java create mode 100644 src/test/java/gift/RepositoryTest/ProductDtoRepositoryTest.java create mode 100644 src/test/java/gift/RepositoryTest/WishlistRepositoryTest.java create mode 100644 src/test/java/gift/ServiceTest/MemberServiceTest.java create mode 100644 src/test/java/gift/ServiceTest/OptionServiceTest.java create mode 100644 src/test/java/gift/ServiceTest/ProductServiceTest.java create mode 100644 src/test/java/gift/ServiceTest/WishlistServiceTest.java diff --git a/README.md b/README.md index b2202f113..248d9665a 100644 --- a/README.md +++ b/README.md @@ -1 +1,33 @@ -# spring-gift-point \ No newline at end of file +# spring-gift-order +# 1단계 - 카카오 로그인 + +## 요구사항 +### 기능 요구사항 +1. 카카오 로그인을 통해 인가 코드를 받는다. +2. 인가 코드를 사용해 토큰을 받은 후 향후 카카오 API 사용을 준비한다. +3. 카카오계정 로그인을 통해 인증 코드를 받는다. +4. 토큰 받기를 읽고 액세스 토큰을 추출한다. +5. 앱 키, 인가 코드가 절대 유출되지 않도록 한다. + - 특히 시크릿 키는 GitHub나 클라이언트 코드 등 외부에서 볼 수 있는 곳에 추가하지 않는다. +6. 오류 처리 +레퍼런스와 문제 해결을 참고하여 발생할 수 있는 다양한 오류를 처리한다. + +# 2단계 - 주문하기 + +## 요구사항 +### 기능 요구사항 +1. 카카오톡 메시지 API를 사용하여 주문하기 기능을 구현한다. +2. 주문할 때 수령인에게 보낼 메시지를 작성할 수 있다. +3. 상품 옵션과 해당 수량을 선택하여 주문하면 해당 상품 옵션의 수량이 차감된다. +4. 해당 상품이 위시 리스트에 있는 경우 위시 리스트에서 삭제한다. +5. 나에게 보내기를 읽고 주문 내역을 카카오톡 메시지로 전송한다. +6. 메시지는 메시지 템플릿의 기본 템플릿이나 사용자 정의 템플릿을 사용하여 자유롭게 작성한다. + +# 3단계 - API 문서 만들기 + +## 요구사항 +### 기능 요구사항 +API 사양에 관해 클라이언트와 어떻게 소통할 수 있을까? 어떻게 하면 편하게 소통할 수 있을지 고민해 보고 그 방법을 구현한다. +1. API 문서를 만들어 클라이언트에게 제공한다. +2. API 문서는 Swagger 또는 restDoc을 사용하여 만든다. +3. API 문서에는 API 명세, 요청, 응답, 예외 처리 등이 포함되어야 한다. diff --git a/build.gradle b/build.gradle index df7db9334..0d8054bad 100644 --- a/build.gradle +++ b/build.gradle @@ -21,9 +21,19 @@ dependencies { implementation 'org.springframework.boot:spring-boot-starter-jdbc' implementation 'org.springframework.boot:spring-boot-starter-thymeleaf' implementation 'org.springframework.boot:spring-boot-starter-web' + implementation 'org.springframework.boot:spring-boot-starter-data-jpa' + implementation 'javax.xml.bind:jaxb-api:2.3.1' + implementation 'org.springframework.boot:spring-boot-starter-validation' + implementation 'io.jsonwebtoken:jjwt-impl:0.11.2' + implementation 'io.jsonwebtoken:jjwt-jackson:0.11.2' + implementation 'org.springframework.plugin:spring-plugin-core:3.0.0' + implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.1.0' runtimeOnly 'com.h2database:h2' + compileOnly 'io.jsonwebtoken:jjwt-api:0.11.2' testImplementation 'org.springframework.boot:spring-boot-starter-test' + testImplementation 'org.mockito:mockito-inline:4.9.0' testRuntimeOnly 'org.junit.platform:junit-platform-launcher' + } tasks.named('test') { diff --git a/src/main/java/gift/Annotation/LoginMemberArgumentResolver.java b/src/main/java/gift/Annotation/LoginMemberArgumentResolver.java new file mode 100644 index 000000000..06e1c2ece --- /dev/null +++ b/src/main/java/gift/Annotation/LoginMemberArgumentResolver.java @@ -0,0 +1,80 @@ +package gift.Annotation; + +import gift.Model.MemberDto; +import gift.Service.MemberService; +import gift.Utils.JwtUtil; +import gift.Validation.KakaoTokenValidator; +import io.jsonwebtoken.Claims; +import jakarta.servlet.http.Cookie; +import jakarta.servlet.http.HttpServletRequest; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.core.MethodParameter; +import org.springframework.stereotype.Component; +import org.springframework.web.bind.support.WebDataBinderFactory; +import org.springframework.web.context.request.NativeWebRequest; +import org.springframework.web.method.support.HandlerMethodArgumentResolver; +import org.springframework.web.method.support.ModelAndViewContainer; + +import java.util.Optional; + +@Component +public class LoginMemberArgumentResolver implements HandlerMethodArgumentResolver { + private final MemberService memberService; + private final JwtUtil jwtUtil; + private boolean isKakaoToken = false; + + @Autowired + public LoginMemberArgumentResolver(MemberService memberService, JwtUtil jwtUtil) { + this.memberService = memberService; + this.jwtUtil = jwtUtil; + } + + @Override + public boolean supportsParameter(MethodParameter methodParameter) { + return methodParameter.getParameterType().equals(MemberDto.class); + } + + @Override + public Object resolveArgument(MethodParameter methodParameter, ModelAndViewContainer modelAndViewContainer, NativeWebRequest nativeWebRequest, WebDataBinderFactory webDataBinderFactory) throws Exception { + HttpServletRequest request = (HttpServletRequest) nativeWebRequest.getNativeRequest(); + + // 쿠키에서 JWT 토큰 추출 + String token = null; + if (request.getCookies() != null) { + for (Cookie cookie : request.getCookies()) { + if ("token".equals(cookie.getName())) { + token = cookie.getValue(); + break; + } + + if ("accessToken".equals(cookie.getName())) { + token = cookie.getValue(); + isKakaoToken = true; + break; + } + } + } + + if (token == null) { + throw new IllegalArgumentException("JWT token not found in cookies"); + } + + if (isKakaoToken) { + //카카오 토큰 검증 + long kakaoId = KakaoTokenValidator.validateToken(token); + Optional memberDto = memberService.findByKakaoId(kakaoId); + + return memberDto.orElseThrow(() -> new IllegalArgumentException("User not found")); + } + + Claims claims = jwtUtil.decodeToken(token); //decode + String memberEmail = claims.getSubject(); // subject를 email로 설정했기 때문에 userEmail로 사용 + Optional memberDto = memberService.findByEmail(memberEmail); //null이라면 인증된 것이 아닐 것이고 null이 아니라면 인증된 것 + if (memberDto.isEmpty()) { + throw new IllegalArgumentException("User not found"); + } + return memberDto; + + } + +} diff --git a/src/main/java/gift/Annotation/LoginMemberResolver.java b/src/main/java/gift/Annotation/LoginMemberResolver.java new file mode 100644 index 000000000..d8c93ba94 --- /dev/null +++ b/src/main/java/gift/Annotation/LoginMemberResolver.java @@ -0,0 +1,11 @@ +package gift.Annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target(ElementType.PARAMETER) +@Retention(RetentionPolicy.RUNTIME) +public @interface LoginMemberResolver { +} diff --git a/src/main/java/gift/Config/WebConfig.java b/src/main/java/gift/Config/WebConfig.java new file mode 100644 index 000000000..12ffb2f78 --- /dev/null +++ b/src/main/java/gift/Config/WebConfig.java @@ -0,0 +1,22 @@ +package gift.Config; + +import gift.Annotation.LoginMemberArgumentResolver; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.method.support.HandlerMethodArgumentResolver; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +import java.util.List; + +@Configuration +public class WebConfig implements WebMvcConfigurer { + + @Autowired + private LoginMemberArgumentResolver loginMemberResolverHandlerMethodArgumentResolver; + + @Override + public void addArgumentResolvers(List resolvers) { + resolvers.add(loginMemberResolverHandlerMethodArgumentResolver); + } + +} diff --git a/src/main/java/gift/Controller/CategoryController.java b/src/main/java/gift/Controller/CategoryController.java new file mode 100644 index 000000000..fcae66f9e --- /dev/null +++ b/src/main/java/gift/Controller/CategoryController.java @@ -0,0 +1,29 @@ +package gift.Controller; + +import gift.Model.CategoryDto; +import gift.Service.CategoryService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.GetMapping; + +import java.util.List; + +@Controller +@Tag(name = "Category", description = "카테고리 관련 api") +public class CategoryController { + + private final CategoryService categoryService; + + public CategoryController(CategoryService categoryService) { + this.categoryService = categoryService; + } + + @Operation(summary = "모든 카테고리 조회", description = "모든 카테고리를 조회합니다.") + @GetMapping("/api/categories") + public ResponseEntity> getAllCategories() { + List categoryDtoList = categoryService.getAllCategories(); + return ResponseEntity.ok(categoryDtoList); + } +} diff --git a/src/main/java/gift/Controller/GlobalExceptionHandler.java b/src/main/java/gift/Controller/GlobalExceptionHandler.java new file mode 100644 index 000000000..e536ca7b8 --- /dev/null +++ b/src/main/java/gift/Controller/GlobalExceptionHandler.java @@ -0,0 +1,45 @@ +package gift.Controller; + +import gift.Exception.ForbiddenException; +import gift.Exception.UnauthorizedException; +import jakarta.servlet.http.HttpServletRequest; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import jakarta.servlet.http.HttpServletRequest; +import org.springframework.ui.Model; +import org.springframework.validation.FieldError; +import org.springframework.web.bind.MethodArgumentNotValidException; +import org.springframework.web.bind.annotation.ControllerAdvice; +import org.springframework.web.bind.annotation.ExceptionHandler; + +@ControllerAdvice +public class GlobalExceptionHandler { + + @ExceptionHandler(MethodArgumentNotValidException.class) + public String handleMethodArgumentNotValid(MethodArgumentNotValidException ex, Model model, HttpServletRequest request) { + FieldError fieldError = ex.getBindingResult().getFieldError(); + String errorMessage = fieldError.getDefaultMessage(); + + model.addAttribute("error", errorMessage); + model.addAttribute("product", ex.getBindingResult().getTarget()); + + // 에러가 발생한 URL에 따라 다시 해당 페이지로 돌아가기 + String requestUrl = request.getRequestURI(); + if (requestUrl.equals("/api/products/create") || requestUrl.startsWith("/api/products/update")) { + return "product_form"; // product_form 뷰로 포워딩 + } + + return "redirect:/api/products"; + } + + @ExceptionHandler(UnauthorizedException.class) + public ResponseEntity handleUnauthorizedException(UnauthorizedException ex) { + return new ResponseEntity<>(ex.getMessage(), HttpStatus.UNAUTHORIZED); + } + + @ExceptionHandler(ForbiddenException.class) + public ResponseEntity handleForbiddenException(ForbiddenException ex) { + return new ResponseEntity<>(ex.getMessage(), HttpStatus.FORBIDDEN); + } + +} diff --git a/src/main/java/gift/Controller/KakaoOAuthController.java b/src/main/java/gift/Controller/KakaoOAuthController.java new file mode 100644 index 000000000..3e45c0da3 --- /dev/null +++ b/src/main/java/gift/Controller/KakaoOAuthController.java @@ -0,0 +1,115 @@ +package gift.Controller; + +import com.fasterxml.jackson.databind.ObjectMapper; +import gift.Model.KakaoAccessTokenDto; +import gift.Model.KakaoMemberDto; +import gift.Model.MemberDto; +import gift.Service.MemberService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.servlet.http.Cookie; +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.*; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.client.RestTemplate; + +import java.io.IOException; + +@RestController +@Tag(name = "Kakao Login", description = "카카오 로그인 관련 api") +public class KakaoOAuthController { + + @Value("${kakao.clientId}") + private String clientId; + + @Autowired + private MemberService memberService; + + @GetMapping("/oauth/authorize") + @Operation(summary = "카카오 로그인 화면", description = "카카오 로그인 화면을 보여줍니다.") + public void authorize(HttpServletResponse response) throws IOException { + var url = "https://kauth.kakao.com/oauth/authorize"; + var redirectUri = "http://localhost:8080/auth/kakao/callback"; + var responseType = "code"; + + String redirectUrl = url + "?response_type=" + responseType + "&client_id=" + clientId + "&redirect_uri=" + redirectUri; + response.sendRedirect(redirectUrl); + } + + + @GetMapping("/auth/kakao/callback") + @Operation(summary = "카카오 로그인 수행", description = "인가 코드를 통해 로그인을 수행합니다.") + public ResponseEntity callBack(@RequestParam("code") String code, HttpServletResponse response) throws Exception { + //인가 코드로 토큰 받아오기 + String accessToken = getAccessToken(code); + + //토큰을 쿠키에 저장하기 + Cookie cookie = new Cookie("accessToken", accessToken); + cookie.setHttpOnly(true); + cookie.setPath("/"); + response.addCookie(cookie); + + //토큰을 통해 사용자 정보 받아오기 + RestTemplate restTemplate = new RestTemplate(); + HttpHeaders headers = new HttpHeaders(); + headers.add("Authorization", "Bearer " + accessToken); + headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED); + + HttpEntity entity = new HttpEntity<>(null, headers); + ResponseEntity responseEntity = restTemplate.exchange( + "https://kapi.kakao.com/v2/user/me", + HttpMethod.GET, + entity, + String.class + ); + + ObjectMapper objectMapper = new ObjectMapper(); + //id는 가능한데 email을 가져오려고 하면 null이 되는 이유?????????? + KakaoMemberDto kakaoMemberDto = objectMapper.readValue(responseEntity.getBody(), KakaoMemberDto.class); + long id = kakaoMemberDto.getId(); + + //이미 가입된 회원인지 확인, 가입되지 않은 회원이라면 회원가입 진행 + if (memberService.findByKakaoId(id).isEmpty()) { + MemberDto memberDto = new MemberDto(); + memberDto.setKakaoId(id); + memberService.register(memberDto); + } + + //로그인 처리 + headers = new HttpHeaders(); + headers.add("Location", "/products"); + + return new ResponseEntity<>("Successfully logged in", headers, HttpStatus.SEE_OTHER); + + } + + @Operation(summary = "토큰 발급", description = "토큰을 발급받습니다.") + public String getAccessToken(String code) throws Exception { + RestTemplate restTemplate = new RestTemplate(); + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED); + + MultiValueMap body = new LinkedMultiValueMap<>(); + body.add("grant_type", "authorization_code"); + body.add("client_id", clientId); + body.add("redirect_uri", "http://localhost:8080/auth/kakao/callback"); + body.add("code", code); + + HttpEntity> requestEntity = new HttpEntity<>(body, headers); //순서가 body가 먼저 + + ResponseEntity response = restTemplate.exchange( + "https://kauth.kakao.com/oauth/token", + HttpMethod.POST, + requestEntity, + String.class + ); //Http 요청 보내고 받기 + + ObjectMapper objectMapper = new ObjectMapper(); + KakaoAccessTokenDto kakaoAccessTokenDto = objectMapper.readValue(response.getBody(), KakaoAccessTokenDto.class); + return kakaoAccessTokenDto.getAccess_token(); + } +} diff --git a/src/main/java/gift/Controller/MemberController.java b/src/main/java/gift/Controller/MemberController.java new file mode 100644 index 000000000..4bb72c8a3 --- /dev/null +++ b/src/main/java/gift/Controller/MemberController.java @@ -0,0 +1,80 @@ +package gift.Controller; + +import gift.Entity.Member; +import gift.Model.MemberDto; +import gift.Service.MemberService; +import gift.Utils.JwtUtil; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.servlet.http.Cookie; +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.web.bind.annotation.*; +import jakarta.servlet.http.HttpServletRequest; + +import java.util.Optional; + +@Controller +@Tag(name = "Member", description = "회원 관련 api") +public class MemberController { + + private final MemberService memberService; + private final JwtUtil jwtUtil; + + @Autowired + public MemberController(MemberService memberService, JwtUtil jwtUtil) { + this.memberService = memberService; + this.jwtUtil = jwtUtil; + } + + @GetMapping("/login") + @Operation(summary = "로그인 페이지 전환", description = "로그인 페이지로 전환합니다.") + public String login(Model model) { + model.addAttribute("user", new MemberDto()); + return "login"; + } + + @PostMapping(value = "/login") + @Operation(summary = "로그인 수행", description = "로그인을 수행합니다.") + public String login(@ModelAttribute MemberDto memberDto, Model model, HttpServletResponse response) { + String email = memberDto.getEmail(); + String password = memberDto.getPassword(); + + boolean isAuthenticated = memberService.authenticate(email, password); + if (isAuthenticated) { + boolean isAdmin = memberService.isAdmin(email); + Optional authenticatedMember = memberService.findByEmail(email); + String token = jwtUtil.generateToken(authenticatedMember.get(), isAdmin); + // Set token in HttpOnly cookie + Cookie cookie = new Cookie("token", token); + cookie.setHttpOnly(true); + cookie.setPath("/"); + response.addCookie(cookie); + + if (isAdmin) { + return "redirect:/api/products"; + } + return "redirect:/products"; + } + + model.addAttribute("error", "Authentication failed"); + return "login"; + } + + @RequestMapping(value = "/register", method = {RequestMethod.GET, RequestMethod.POST}) + @Operation(summary = "회원가입", description = "회원가입 페이지를 보여주고 회원가입을 수행합니다.") + public String register(@ModelAttribute MemberDto memberDto, Model model, HttpServletRequest request) { + if ("GET".equalsIgnoreCase(request.getMethod())) { + model.addAttribute("user", new MemberDto()); + return "register"; + } else if ("POST".equalsIgnoreCase(request.getMethod())) { + model.addAttribute("user", memberDto); + memberService.register(memberDto); + model.addAttribute("message", "회원가입에 성공했습니다."); + return "login"; + } + return "login"; + } +} diff --git a/src/main/java/gift/Controller/OptionController.java b/src/main/java/gift/Controller/OptionController.java new file mode 100644 index 000000000..a92f23153 --- /dev/null +++ b/src/main/java/gift/Controller/OptionController.java @@ -0,0 +1,130 @@ +package gift.Controller; + +import gift.Annotation.LoginMemberResolver; +import gift.Model.MemberDto; +import gift.Model.OptionDto; +import gift.Model.OrderRequestDto; +import gift.Service.KakaoTalkService; +import gift.Service.OptionService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.servlet.http.Cookie; +import jakarta.servlet.http.HttpServletRequest; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.*; +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +@Controller +@Tag(name = "Option", description = "옵션 관련 api") +public class OptionController { + + private final OptionService optionService; + private final KakaoTalkService kakaoTalkService; + + @Autowired + public OptionController(OptionService optionService, KakaoTalkService kakaoTalkService) { + this.optionService = optionService; + this.kakaoTalkService = kakaoTalkService; + } + + @GetMapping("/api/products/options/{productId}") + @Operation(summary = "옵션 표출", description = "상품에 대한 옵션을 보여줍니다.") + public ResponseEntity> getAllOptionsByProductId(@PathVariable Long productId) { + List options = optionService.getAllOptionsByProductId(productId); + if (options.isEmpty()) { + return ResponseEntity.notFound().build(); + } + return ResponseEntity.ok(options); + } + + @GetMapping("/api/products/options/add/{productId}") + @Operation(summary = "옵션 추가 화면", description = "옵션 추가 화면을 보여줍니다.") + public String addOption(Model model, @PathVariable("productId") long productId) { + OptionDto optionDto = new OptionDto(); + optionDto.setProductId(productId); + model.addAttribute("optionDto", optionDto); + return "option_form"; + } + + @PostMapping("/api/products/options/add") + @Operation(summary = "옵션 추가", description = "상품에 대한 옵션을 추가합니다.") + public String addOption(@ModelAttribute OptionDto optionDto, Model model, @RequestParam("productId") long productId) { + model.addAttribute("optionDto", optionDto); + optionDto.setProductId(productId); + optionService.addOption(optionDto); + return "redirect:/api/products"; + } + + @GetMapping("/api/products/options/{productId}/{optionId}/update") + @Operation(summary = "옵션 수정 화면", description = "옵션 수정 화면을 보여줍니다.") + public String updateOption(Model model, @PathVariable long productId, @PathVariable long optionId) { + OptionDto optionDto = optionService.getOptionById(optionId); + model.addAttribute("optionDto", optionDto); + return "option_form"; + } + + @PostMapping("/api/products/options/{productId}/{optionId}/update") + @Operation(summary = "옵션 수정", description = "상품에 대한 옵션을 수정합니다.") + public ResponseEntity updateOption(@PathVariable long productId, @PathVariable long optionId, @ModelAttribute OptionDto optionDto, Model model) { + try { + model.addAttribute("optionDto", optionDto); + optionDto.setProductId(productId); + optionDto.setId(optionId); + optionService.updateOption(optionDto); + HttpHeaders headers = new HttpHeaders(); + headers.add("Location", "/api/products"); + return new ResponseEntity<>("Option updated successfully", headers, HttpStatus.SEE_OTHER); + } catch (Exception e) { + return ResponseEntity.badRequest().body("Error updating option"); + } + } + + @DeleteMapping("/api/products/options/{optionId}/delete") + @Operation(summary = "옵션 삭제", description = "상품에 대한 옵션을 삭제합니다.") + public ResponseEntity deleteOption(@PathVariable long optionId) { + try { + optionService.deleteOption(optionId); + HttpHeaders headers = new HttpHeaders(); + headers.add("Location", "/api/products"); + return new ResponseEntity<>("Option updated successfully", headers, HttpStatus.SEE_OTHER); + } catch (Exception e) { + return ResponseEntity.badRequest().body("Error deleting option"); + } + } + + @PostMapping("/option/purchase") + @Operation(summary = "위시리스트 구매", description = "위시리스트에 있는 상품을 구매합니다.") + public ResponseEntity purchaseWishlist(@LoginMemberResolver MemberDto memberDto, @RequestBody List orderRequestDtoList, HttpServletRequest request) { + for (OrderRequestDto orderRequestDto : orderRequestDtoList) { + orderRequestDto.setMemberId(memberDto.getId()); + } + optionService.subtractOption(orderRequestDtoList); + + //카카오 토큰 가져오기 + String token = null; + Cookie[] cookies = request.getCookies(); + if (cookies != null) { + for (Cookie cookie : cookies) { + if ("accessToken".equals(cookie.getName())) { + token = cookie.getValue(); + break; + } + } + } + + //토큰으로 메시지 보내기 + if (token != null) { // + try { + kakaoTalkService.sendMessageToMe(token, orderRequestDtoList); + } catch (Exception e) { + return ResponseEntity.badRequest().body("Error sending message"); + } + } + + return ResponseEntity.ok("Purchase successful"); + } +} diff --git a/src/main/java/gift/Controller/ProductController.java b/src/main/java/gift/Controller/ProductController.java new file mode 100644 index 000000000..afa3fb146 --- /dev/null +++ b/src/main/java/gift/Controller/ProductController.java @@ -0,0 +1,99 @@ +package gift.Controller; + +import gift.Model.ProductDto; +import gift.Service.CategoryService; +import gift.Service.ProductService; + +import java.util.Optional; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.validation.Valid; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.web.bind.annotation.*; + +@Controller +@Tag(name = "Product", description = "상품 관련 api") +public class ProductController { + + private final ProductService productService; + private final CategoryService categoryService; + + @Autowired + public ProductController(ProductService productService, CategoryService categoryService) { + this.productService = productService; + this.categoryService = categoryService; + } + + @GetMapping("/api/products") + @Operation(summary = "모든 상품 조회", description = "모든 상품을 조회합니다.") + public String getAllProductsByRoot(Model model, + @RequestParam(value = "page", defaultValue = "0") int page, + @RequestParam(value = "size", defaultValue = "10") int size) { + Pageable pageable = PageRequest.of(page, size); + Page paging = productService.getAllProductsByPage(pageable); + model.addAttribute("paging", paging); + model.addAttribute("currentPage", page); + model.addAttribute("totalPages", paging.getTotalPages()); + return "products"; + } + + @GetMapping("/products") + @Operation(summary = "모든 상품 조회", description = "모든 상품을 조회합니다.") + public String getAllProductsByUser(Model model, + @RequestParam(value = "page", defaultValue = "0") int page, + @RequestParam(value = "size", defaultValue = "10") int size) { + Pageable pageable = PageRequest.of(page, size); + Page paging = productService.getAllProductsByPage(pageable); + model.addAttribute("paging", paging); + model.addAttribute("currentPage", page); + model.addAttribute("totalPages", paging.getTotalPages()); + return "user_products"; + } + + @RequestMapping(value = "/api/products/create", method = {RequestMethod.GET, RequestMethod.POST}) + @Operation(summary = "상품 추가", description = "상품 추가 화면을 보여주고 상품을 추가합니다.") + public String createProduct(@Valid @ModelAttribute ProductDto productDto, HttpServletRequest request, Model model) { + if ("GET".equalsIgnoreCase(request.getMethod())) { + model.addAttribute("product", new ProductDto()); + model.addAttribute("categories", categoryService.getAllCategories()); + return "product_form"; + } else if ("POST".equalsIgnoreCase(request.getMethod())) { + model.addAttribute("product", productDto); + productService.saveProduct(productDto); + return "redirect:/api/products"; + } + return "error"; + } + + @RequestMapping(value = "/api/products/update/{id}", method = {RequestMethod.GET, RequestMethod.POST}) + @Operation(summary = "상품 수정", description = "상품 수정 화면을 보여주고 상품을 수정합니다.") + public String updateProductById(@PathVariable Long id, @Valid @ModelAttribute ProductDto productDtoDetails, HttpServletRequest request, Model model) { + if ("GET".equalsIgnoreCase(request.getMethod())) { + Optional optionalProduct = productService.getProductById(id); + model.addAttribute("product", optionalProduct.get()); + model.addAttribute("categories", categoryService.getAllCategories()); + return "product_form"; + } else if ("POST".equalsIgnoreCase(request.getMethod())) { + productService.updateProduct(productDtoDetails); + return "redirect:/api/products"; + } + return "error"; + } + + @PostMapping("/api/products/delete/{id}") + @Operation(summary = "상품 삭제", description = "상품을 삭제합니다.") + public String deleteProduct(@PathVariable Long id, Model model) { + Optional optionalProduct = productService.getProductById(id); + model.addAttribute("product", optionalProduct.get()); + productService.deleteProduct(id); + return "redirect:/api/products"; // 제품 목록 페이지로 리디렉션 + } + +} diff --git a/src/main/java/gift/Controller/WishlistController.java b/src/main/java/gift/Controller/WishlistController.java new file mode 100644 index 000000000..375c66694 --- /dev/null +++ b/src/main/java/gift/Controller/WishlistController.java @@ -0,0 +1,80 @@ +package gift.Controller; + +import gift.Annotation.LoginMemberResolver; +import gift.Entity.Wishlist; +import gift.Model.MemberDto; +import gift.Model.WishlistDto; +import gift.Service.WishlistService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.web.bind.annotation.*; + +import java.util.Optional; + +@Controller +@Tag(name = "Wishlist", description = "위시 리스트 관련 api") +public class WishlistController { + + private final WishlistService wishlistService; + + @Autowired + public WishlistController(WishlistService wishlistService) { + this.wishlistService = wishlistService; + } + + @GetMapping("/wishlist") + @Operation(summary = "위시 리스트 조회", description = "위시 리스트를 조회합니다.") + public String getWishlist(@LoginMemberResolver MemberDto memberDto, Model model, + @RequestParam(value = "page", defaultValue = "0") int page, + @RequestParam(value = "size", defaultValue = "10") int size) { + Pageable pageable = PageRequest.of(page, size); + Page paging = wishlistService.getWishlistByPage(memberDto, pageable); + model.addAttribute("paging", paging); + model.addAttribute("currentPage", page); + model.addAttribute("totalPages", paging.getTotalPages()); + return "wishlist"; + } + + @PostMapping("/wishlist/add") + @Operation(summary = "위시 리스트 추가", description = "위시 리스트에 상품을 추가합니다.") + public String addWishlistItem(@LoginMemberResolver MemberDto memberDto, @RequestBody WishlistDto wishlistDto) { + if (memberDto == null) { + return "redirect:/login"; + } + wishlistDto.setUserId(memberDto.getId()); + wishlistService.addWishlistItem(wishlistDto); + return "redirect:/products"; + } + + @PostMapping("/wishlist/remove") + @Operation(summary = "위시 리스트 삭제", description = "위시 리스트에서 상품을 삭제합니다.") + public String removeWishlistItem(@LoginMemberResolver MemberDto memberDto, @RequestBody WishlistDto wishlistDto) { + if (memberDto == null) { + return "redirect:/login"; + } + + Optional wishlistOptional = wishlistService.getWishlist(memberDto.getId()).stream() + .filter(wishlist -> wishlist.getProductId() == wishlistDto.getProductId()) + .findFirst(); + wishlistDto.setUserId(memberDto.getId()); + + wishlistService.removeWishlistItem(wishlistDto, wishlistOptional.get()); + + return "redirect:/wishlist"; + } + + @PostMapping("/wishlist/clear") + @Operation(summary = "위시 리스트 비우기", description = "위시 리스트를 비웁니다.") + public ResponseEntity clearWishlist(@LoginMemberResolver MemberDto memberDto) { + wishlistService.clearWishlist(memberDto.getId()); + return ResponseEntity.ok().build(); + } + +} diff --git a/src/main/java/gift/DatabaseInitialize/DatabaseInitializer.java b/src/main/java/gift/DatabaseInitialize/DatabaseInitializer.java new file mode 100644 index 000000000..08b78e7dc --- /dev/null +++ b/src/main/java/gift/DatabaseInitialize/DatabaseInitializer.java @@ -0,0 +1,32 @@ +package gift.DatabaseInitialize; + +import gift.Entity.Category; +import gift.Repository.CategoryJpaRepository; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.CommandLineRunner; +import org.springframework.stereotype.Component; + +@Component +public class DatabaseInitializer implements CommandLineRunner { + + private final CategoryJpaRepository categoryJpaRepository; + + @Autowired + public DatabaseInitializer(CategoryJpaRepository categoryJpaRepository) { + this.categoryJpaRepository = categoryJpaRepository; + } + + @Override + public void run(String... args) throws Exception { + categoryJpaRepository.save(new Category(1, "교환권")); + categoryJpaRepository.save(new Category(2, "상품권")); + categoryJpaRepository.save(new Category(3, "뷰티")); + categoryJpaRepository.save(new Category(4, "패션")); + categoryJpaRepository.save(new Category(5, "식품")); + categoryJpaRepository.save(new Category(6, "리빙/도서")); + categoryJpaRepository.save(new Category(7, "레저/스포츠")); + + } + + +} diff --git a/src/main/java/gift/Entity/Category.java b/src/main/java/gift/Entity/Category.java new file mode 100644 index 000000000..9e3124617 --- /dev/null +++ b/src/main/java/gift/Entity/Category.java @@ -0,0 +1,41 @@ +package gift.Entity; + +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; + +@Entity +public class Category { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private long categoryId; + private String name; + + protected Category() { + + } + + public Category(long categoryId, String name) { + this.categoryId = categoryId; + this.name = name; + } + + + public long getCategoryId() { + return categoryId; + } + + public void setCategoryId(long categoryId) { + this.categoryId = categoryId; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } +} diff --git a/src/main/java/gift/Entity/Member.java b/src/main/java/gift/Entity/Member.java new file mode 100644 index 000000000..520f06ad8 --- /dev/null +++ b/src/main/java/gift/Entity/Member.java @@ -0,0 +1,72 @@ +package gift.Entity; + +import jakarta.persistence.*; + +@Entity +public class Member { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private long memberId; + private String email; + private String name; + private String password; + private boolean isAdmin; + private long kakaoId; + + protected Member() { + } + + public Member(long memberId, String email, String name, String password, boolean isAdmin) { + this.memberId = memberId; + this.email = email; + this.name = name; + this.password = password; + this.isAdmin = isAdmin; + } + + // Getters and setters + public long getId() { + return memberId; + } + + public void setId(Long memberId) { + this.memberId = memberId; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + + public boolean isAdmin() { + return isAdmin; + } + + public void setAdmin(boolean admin) { + isAdmin = admin; + } + + public void setKakaoId(long kakaoId) { + this.kakaoId = kakaoId; + } +} \ No newline at end of file diff --git a/src/main/java/gift/Entity/Option.java b/src/main/java/gift/Entity/Option.java new file mode 100644 index 000000000..a78d2fc8b --- /dev/null +++ b/src/main/java/gift/Entity/Option.java @@ -0,0 +1,69 @@ +package gift.Entity; + +import jakarta.persistence.*; + +@Entity +public class Option { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private long id; + + @ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.REFRESH) + @JoinColumn(name = "productId", referencedColumnName = "productId") + private Product product; + private String name; + private int price; + private int quantity; + + protected Option() { + + } + + public Option(long id, Product product, String name, int price, int quantity) { + this.id = id; + this.product = product; + this.name = name; + this.price = price; + this.quantity = quantity; + } + + public long getId() { + return id; + } + + public void setId(long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public int getPrice() { + return price; + } + + public void setPrice(int price) { + this.price = price; + } + + public int getQuantity() { + return quantity; + } + + public void setQuantity(int quantity) { + this.quantity = quantity; + } + + public Product getProduct() { + return product; + } + + public void setProduct(Product product) { + this.product = product; + } +} diff --git a/src/main/java/gift/Entity/Product.java b/src/main/java/gift/Entity/Product.java new file mode 100644 index 000000000..0ea2014fd --- /dev/null +++ b/src/main/java/gift/Entity/Product.java @@ -0,0 +1,78 @@ +package gift.Entity; + +import jakarta.persistence.*; + +@Entity +public class Product { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private long productId; + + @ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.REFRESH) + @JoinColumn(name = "categoryId", referencedColumnName = "categoryId") + private Category category; + private String name; + private int price; + private String imageUrl; + private boolean isDeleted; + + protected Product() { + } + + public Product(long productId, String name, Category category, int price, String imageUrl, boolean isDeleted) { + this.productId = productId; + this.name = name; + this.category = category; + this.price = price; + this.imageUrl = imageUrl; + this.isDeleted = isDeleted; + } + + public long getId() { + return productId; + } + + public void setId(Long productId) { + this.productId = productId; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public int getPrice() { + return price; + } + + public void setPrice(int price) { + this.price = price; + } + + public String getImageUrl() { + return imageUrl; + } + + public void setImageUrl(String imageUrl) { + this.imageUrl = imageUrl; + } + + public boolean isDeleted() { + return isDeleted; + } + + public void setDeleted(boolean deleted) { + isDeleted = deleted; + } + + public Category getCategory() { + return category; + } + + public void setCategory(Category category) { + this.category = category; + } +} diff --git a/src/main/java/gift/Entity/Wishlist.java b/src/main/java/gift/Entity/Wishlist.java new file mode 100644 index 000000000..aa9bdd96d --- /dev/null +++ b/src/main/java/gift/Entity/Wishlist.java @@ -0,0 +1,96 @@ +package gift.Entity; + +import gift.Model.MemberDto; +import gift.Model.ProductDto; +import gift.Model.WishlistDto; +import jakarta.persistence.*; + +@Entity +public class Wishlist { + + @EmbeddedId + private WishlistId id; + + @ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.REFRESH) + @MapsId("memberId") + @JoinColumn(name = "memberId", referencedColumnName = "memberId") + private Member member; + + @ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.REFRESH) + @MapsId("productId") + @JoinColumn(name = "productId", referencedColumnName = "productId") + private Product product; + + private String productName; + private int count; + private int price; + + @ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.REFRESH) + @JoinColumn(name = "optionId", referencedColumnName = "id") + private Option option; + + protected Wishlist() { + } + + public Wishlist(WishlistId id, Member member, Product product, String productName, int count, int price, Option option) { + this.id = id; + this.member = member; + this.product = product; + this.productName = productName; + this.count = count; + this.price = price; + this.option = option; + } + + public WishlistId getId() { + return id; + } + + public void setId(WishlistId id) { + this.id = id; + } + + public long getMemberId() { + return id.getMemberId(); + } + + public void setMemberId(long memberId) { + id.setMemberId(memberId); + } + + public long getProductId() { + return id.getProductId(); + } + + public void setProductId(long productId) { + id.setProductId(productId); + } + + public String getProductName() { + return productName; + } + + public void setProductName(String productName) { + this.productName = productName; + } + + public int getCount() { + return count; + } + + public void setCount(int count) { + this.count = count; + } + + public int getPrice() { + return price; + } + + public void setPrice(int price) { + this.price = price; + } + + public Option getOption() { + return option; + } +} diff --git a/src/main/java/gift/Entity/WishlistId.java b/src/main/java/gift/Entity/WishlistId.java new file mode 100644 index 000000000..e5cc5e5fe --- /dev/null +++ b/src/main/java/gift/Entity/WishlistId.java @@ -0,0 +1,36 @@ +package gift.Entity; + +import jakarta.persistence.Embeddable; + +import java.io.Serializable; + +@Embeddable +public class WishlistId implements Serializable { + + private long memberId; + private long productId; + + protected WishlistId() { + } + + public WishlistId(long memberId, long productId) { + this.memberId = memberId; + this.productId = productId; + } + + public long getMemberId() { + return memberId; + } + + public void setMemberId(long memberId) { + this.memberId = memberId; + } + + public long getProductId() { + return productId; + } + + public void setProductId(long productId) { + this.productId = productId; + } +} \ No newline at end of file diff --git a/src/main/java/gift/Exception/ForbiddenException.java b/src/main/java/gift/Exception/ForbiddenException.java new file mode 100644 index 000000000..cc48acc25 --- /dev/null +++ b/src/main/java/gift/Exception/ForbiddenException.java @@ -0,0 +1,11 @@ +package gift.Exception; + +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.ResponseStatus; + +@ResponseStatus(HttpStatus.FORBIDDEN) +public class ForbiddenException extends RuntimeException { + public ForbiddenException(String message) { + super(message); + } +} diff --git a/src/main/java/gift/Exception/UnauthorizedException.java b/src/main/java/gift/Exception/UnauthorizedException.java new file mode 100644 index 000000000..2ff78ad99 --- /dev/null +++ b/src/main/java/gift/Exception/UnauthorizedException.java @@ -0,0 +1,11 @@ +package gift.Exception; + +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.ResponseStatus; + +@ResponseStatus(HttpStatus.UNAUTHORIZED) +public class UnauthorizedException extends RuntimeException { + public UnauthorizedException(String message) { + super(message); + } +} diff --git a/src/main/java/gift/Mapper/Mapper.java b/src/main/java/gift/Mapper/Mapper.java new file mode 100644 index 000000000..b9c7f83fd --- /dev/null +++ b/src/main/java/gift/Mapper/Mapper.java @@ -0,0 +1,89 @@ +package gift.Mapper; + +import gift.Entity.*; +import gift.Model.*; +import gift.Service.*; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Lazy; +import org.springframework.stereotype.Component; + +import java.util.Optional; + +@Component +public class Mapper { + + private final ProductService productService; + private final MemberService memberService; + private final WishlistService wishlistService; + private final CategoryService categoryService; + private final OptionService optionService; + + @Autowired + public Mapper(@Lazy ProductService productService, @Lazy MemberService memberService, @Lazy WishlistService wishListService, @Lazy CategoryService categoryService, @Lazy OptionService optionService) { + this.productService = productService; + this.memberService = memberService; + this.wishlistService = wishListService; + this.categoryService = categoryService; + this.optionService = optionService; + } + + + public Wishlist wishlistDtoToEntity(WishlistDto wishlistDto) { + Optional memberDtoOptional = memberService.findByUserId(wishlistDto.getUserId()); + MemberDto memberDto = memberDtoOptional.get(); + + Optional productDtoOptional = productService.getProductById(wishlistDto.getProductId()); + ProductDto productDto = productDtoOptional.get(); + productDto.setCategoryId(productDtoOptional.get().getCategoryId()); + + OptionDto optionDto = optionService.getOptionById(wishlistDto.getOptionId()); + + WishlistId id = new WishlistId(memberDto.getId(), productDto.getId()); + return new Wishlist(id, memberDtoToEntity(memberDto), productDtoToEntity(productDto), wishlistDto.getProductName(), wishlistDto.getCount(), wishlistDto.getPrice(), optionDtoToEntity(optionDto)); + + } + + public WishlistDto wishlistToDto(Wishlist wishlist) { + return new WishlistDto(wishlist.getMemberId(), wishlist.getProductId(), wishlist.getCount(), 0, wishlist.getProductName(), wishlist.getPrice(), wishlist.getOption().getId()); + } + + public Category categoryDtoToEntity(CategoryDto categoryDto) { + return new Category(categoryDto.getId(), categoryDto.getName()); + } + + public CategoryDto categoryToDto(Category category) { + return new CategoryDto(category.getCategoryId(), category.getName()); + } + + //categoryService를 포함하기엔 뭔가 일관성이 깨지는 느낌인데??? + public Product productDtoToEntity(ProductDto productDto) { + Category category = categoryService.getCategoryById(productDto.getCategoryId()).get(); + CategoryDto categoryDto = categoryToDto(category); + return new Product(productDto.getId(), productDto.getName(), categoryDtoToEntity(categoryDto), productDto.getPrice(), productDto.getImageUrl(), productDto.isDeleted()); + } + + public ProductDto productToDto(Product product) { + Category category = categoryService.getCategoryById(product.getCategory().getCategoryId()).get(); + + return new ProductDto(product.getId(), product.getName(), categoryToDto(category).getId(), product.getPrice(), product.getImageUrl(), product.isDeleted()); + } + + public Member memberDtoToEntity(MemberDto memberDto) { + return new Member(memberDto.getId(), memberDto.getEmail(), memberDto.getName(), memberDto.getPassword(), memberDto.isAdmin()); + } + + public MemberDto memberToDto(Member member) { + return new MemberDto(member.getId(), member.getEmail(), member.getName(), member.getPassword(), member.isAdmin()); + } + + public OptionDto optionToDto(Option option) { + return new OptionDto(option.getId(), option.getProduct().getId(), option.getName(), option.getPrice(), option.getQuantity()); + } + + public Option optionDtoToEntity(OptionDto optionDto) { + Optional productDtoOptional = productService.getProductById(optionDto.getProductId()); + ProductDto productDto = productDtoOptional.get(); + return new Option(optionDto.getId(), productDtoToEntity(productDto), optionDto.getName(), optionDto.getPrice(), optionDto.getQuantity()); + } + +} diff --git a/src/main/java/gift/Model/CategoryDto.java b/src/main/java/gift/Model/CategoryDto.java new file mode 100644 index 000000000..e047d81a3 --- /dev/null +++ b/src/main/java/gift/Model/CategoryDto.java @@ -0,0 +1,32 @@ +package gift.Model; + +public class CategoryDto { + + private long id; + private String name; + + public CategoryDto() { + + } + + public CategoryDto(long id, String name) { + this.id = id; + this.name = name; + } + + public long getId() { + return id; + } + + public void setId(long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } +} diff --git a/src/main/java/gift/Model/GetKakaoTokenInformation.java b/src/main/java/gift/Model/GetKakaoTokenInformation.java new file mode 100644 index 000000000..4d2283587 --- /dev/null +++ b/src/main/java/gift/Model/GetKakaoTokenInformation.java @@ -0,0 +1,20 @@ +package gift.Model; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; + +@JsonIgnoreProperties(ignoreUnknown = true) +public class GetKakaoTokenInformation { + + private long id; + + @JsonProperty("expires_in") + private int expiresIn; + + @JsonProperty("app_id") + private int appId; + + public long getId() { + return id; + } +} diff --git a/src/main/java/gift/Model/KakaoAccessTokenDto.java b/src/main/java/gift/Model/KakaoAccessTokenDto.java new file mode 100644 index 000000000..6f04563b4 --- /dev/null +++ b/src/main/java/gift/Model/KakaoAccessTokenDto.java @@ -0,0 +1,33 @@ +package gift.Model; + +import com.fasterxml.jackson.annotation.JsonProperty; + +public class KakaoAccessTokenDto { + @JsonProperty("token_type") + private String tokenType; + + @JsonProperty("access_token") + private String accessToken; + + @JsonProperty("id_token") + private String idToken; + + @JsonProperty("expires_in") + private int expiresIn; + + @JsonProperty("refresh_token") + private String refreshToken; + + @JsonProperty("refresh_token_expires_in") + private int refreshTokenExpiresIn; + + @JsonProperty("scope") + private String scope; + + public KakaoAccessTokenDto() { + } + + public String getAccess_token() { + return accessToken; + } +} diff --git a/src/main/java/gift/Model/KakaoAccount.java b/src/main/java/gift/Model/KakaoAccount.java new file mode 100644 index 000000000..62943dd70 --- /dev/null +++ b/src/main/java/gift/Model/KakaoAccount.java @@ -0,0 +1,22 @@ +package gift.Model; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; + +@JsonIgnoreProperties(ignoreUnknown = true) +public class KakaoAccount { + + @JsonProperty("email") + private String email; + + @JsonProperty("name") + private String name; + + public String getEmail() { + return email; + } + + public String getName() { + return name; + } +} diff --git a/src/main/java/gift/Model/KakaoMemberDto.java b/src/main/java/gift/Model/KakaoMemberDto.java new file mode 100644 index 000000000..776310b46 --- /dev/null +++ b/src/main/java/gift/Model/KakaoMemberDto.java @@ -0,0 +1,21 @@ +package gift.Model; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; + +@JsonIgnoreProperties(ignoreUnknown = true) +public class KakaoMemberDto { + + private long id; + + @JsonProperty("kakao_account") + private KakaoAccount kakaoAccount; + + public long getId() { + return id; + } + + public KakaoAccount getKakaoAccount() { + return kakaoAccount; + } +} diff --git a/src/main/java/gift/Model/Link.java b/src/main/java/gift/Model/Link.java new file mode 100644 index 000000000..edba562e2 --- /dev/null +++ b/src/main/java/gift/Model/Link.java @@ -0,0 +1,32 @@ +package gift.Model; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; + +@JsonIgnoreProperties(ignoreUnknown = true) +public class Link { + + private String web_url; + private String mobile_web_url; + + public Link(String web_url, String mobile_web_url) { + this.web_url = web_url; + this.mobile_web_url = mobile_web_url; + } + + + public String getWeb_url() { + return web_url; + } + + public void setWeb_url(String web_url) { + this.web_url = web_url; + } + + public String getMobile_web_url() { + return mobile_web_url; + } + + public void setMobile_web_url(String mobile_web_url) { + this.mobile_web_url = mobile_web_url; + } +} diff --git a/src/main/java/gift/Model/MemberDto.java b/src/main/java/gift/Model/MemberDto.java new file mode 100644 index 000000000..0f892e567 --- /dev/null +++ b/src/main/java/gift/Model/MemberDto.java @@ -0,0 +1,70 @@ +package gift.Model; + +public class MemberDto { + private long id; + private String email; + private String name; + private String password; + private boolean isAdmin; + private long kakaoId; + + public MemberDto() { + + } + + public MemberDto(long id, String email, String password, String name, boolean isAdmin) { + this.id = id; + this.email = email; + this.password = password; + this.name = name; + this.isAdmin = isAdmin; + } + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public boolean isAdmin() { + return isAdmin; + } + + public void setAdmin(boolean admin) { + this.isAdmin = admin; + } + + public void setKakaoId(long kakaoId) { + this.kakaoId = kakaoId; + } + + public long getKakaoId() { + return kakaoId; + } +} diff --git a/src/main/java/gift/Model/OptionDto.java b/src/main/java/gift/Model/OptionDto.java new file mode 100644 index 000000000..941a2c59a --- /dev/null +++ b/src/main/java/gift/Model/OptionDto.java @@ -0,0 +1,71 @@ +package gift.Model; + +import jakarta.validation.constraints.*; + +public class OptionDto { + + private long id; + private long productId; + @Size(min = 1, max = 50, message = "이름의 글자 수는 1자 이상, 50자 이하여야 합니다.") + @Pattern(regexp = "^[\\w\\s()+\\-/&_\\[\\]가-힣]*$", message = "( ), [ ], +, -, &, /, _" + + "외의 다른 특수 문자 사용 불가") + @NotNull(message = "옵션 이름은 필수입니다.") + private String name; + private int price; + + @Min(value = 1, message = "옵션의 수량은 최소 하나 이상입니다.") + @Max(value = 100_000_000, message = "옵션의 수량은 1억 이하입니다.") + private int quantity; + + public OptionDto() { + } + + public OptionDto(long id, long productId, String name, int price, int quantity) { + this.id = id; + this.productId = productId; + this.name = name; + this.price = price; + this.quantity = quantity; + } + + public long getId() { + return id; + } + + public void setId(long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public int getPrice() { + return price; + } + + public void setPrice(int price) { + this.price = price; + } + + public int getQuantity() { + return quantity; + } + + public void setQuantity(int quantity) { + this.quantity = quantity; + } + + + public long getProductId() { + return productId; + } + + public void setProductId(long productId) { + this.productId = productId; + } +} diff --git a/src/main/java/gift/Model/OrderRequestDto.java b/src/main/java/gift/Model/OrderRequestDto.java new file mode 100644 index 000000000..4311dd2c7 --- /dev/null +++ b/src/main/java/gift/Model/OrderRequestDto.java @@ -0,0 +1,51 @@ +package gift.Model; + +public class OrderRequestDto { + long productId; + long optionId; + int quantity; + long memberId; + + public OrderRequestDto() { + } + + public OrderRequestDto(long productId, long optionId, int quantity, long memberId) { + this.productId = productId; + this.optionId = optionId; + this.quantity = quantity; + this.memberId = memberId; + } + + public long getProductId() { + return productId; + } + + public void setProductId(long productId) { + this.productId = productId; + } + + public long getOptionId() { + return optionId; + } + + public void setOptionId(long optionId) { + this.optionId = optionId; + } + + public int getQuantity() { + return quantity; + } + + public void setQuantity(int quantity) { + this.quantity = quantity; + } + + public long getMemberId() { + return memberId; + } + + public void setMemberId(long memberId) { + this.memberId = memberId; + } + +} diff --git a/src/main/java/gift/Model/ProductDto.java b/src/main/java/gift/Model/ProductDto.java new file mode 100644 index 000000000..0da98ff07 --- /dev/null +++ b/src/main/java/gift/Model/ProductDto.java @@ -0,0 +1,83 @@ +package gift.Model; + +import jakarta.persistence.*; +import jakarta.validation.constraints.Pattern; +import jakarta.validation.constraints.Size; + +public class ProductDto { + + private long id; + @Size(min = 1, max = 15, message = "글자 수는 1자 이상, 15자 이하여야 합니다.") + @Pattern(regexp = "^[\\w\\s()+\\-/&_\\[\\]가-힣]*$", message = "( ), [ ], +, -, &, /, _" + + "외의 다른 특수 문자 사용 불가") + @Pattern(regexp = "^(?!.*카카오).*$", message = "\"카카오\"가 포함된 문구는 담당 MD와 협의한 경우에만 사용할 수 있습니다.") + private String name; + private long categoryId; + private int price; + private String imageUrl; + private boolean isDeleted; + + public ProductDto() { + + } + + public ProductDto(long id, String name, long categoryId, int price, String imageUrl, boolean isDeleted) { + this.id = id; + this.name = name; + this.categoryId = categoryId; + this.price = price; + this.imageUrl = imageUrl; + this.isDeleted = isDeleted; + } + + public String getName() { + + return name; + } + + public int getPrice() { + + return price; + } + + public String getImageUrl() { + + return imageUrl; + } + + public boolean isDeleted() { + return isDeleted; + } + + public void setDeleted(boolean deleted) { + isDeleted = deleted; + } + + public void setImageUrl(String imageUrl) { + this.imageUrl = imageUrl; + } + + public void setPrice(int price) { + this.price = price; + } + + public void setName(String name) { + this.name = name; + } + + public long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public long getCategoryId() { + return categoryId; + } + + public void setCategoryId(long categoryId) { + this.categoryId = categoryId; + } +} diff --git a/src/main/java/gift/Model/TextObject.java b/src/main/java/gift/Model/TextObject.java new file mode 100644 index 000000000..1bb30de46 --- /dev/null +++ b/src/main/java/gift/Model/TextObject.java @@ -0,0 +1,41 @@ +package gift.Model; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; + +@JsonIgnoreProperties(ignoreUnknown = true) +public class TextObject { + + private String object_type; + private String text; + private Link link; + + public TextObject(String object_type, String text, Link link) { + this.object_type = object_type; + this.text = text; + this.link = link; + } + + public String getObject_type() { + return object_type; + } + + public void setObject_type(String object_type) { + this.object_type = object_type; + } + + public String getText() { + return text; + } + + public void setText(String text) { + this.text = text; + } + + public Link getLink() { + return link; + } + + public void setLink(Link link) { + this.link = link; + } +} diff --git a/src/main/java/gift/Model/WishlistDto.java b/src/main/java/gift/Model/WishlistDto.java new file mode 100644 index 000000000..c3cd6bd7e --- /dev/null +++ b/src/main/java/gift/Model/WishlistDto.java @@ -0,0 +1,80 @@ +package gift.Model; + +public class WishlistDto { + private long userId; + private long productId; + private int count; // 담은 개수 + private int quantity;//뺄 개수 + private String productName; + private int price; + private long optionId; + + public WishlistDto() { + } + + public WishlistDto(long userId, long productId, int count, int quantity, String productName, int price, long optionId) { + this.userId = userId; + this.productId = productId; + this.count = count; + this.quantity = quantity; + this.productName = productName; + this.price = price; + this.optionId = optionId; + } + + public long getUserId() { + return userId; + } + + public void setUserId(long userId) { + this.userId = userId; + } + + public long getProductId() { + return productId; + } + + public void setProductId(long productId) { + this.productId = productId; + } + + public int getCount() { + return count; + } + + public void setCount(int count) { + this.count = count; + } + + public String getProductName() { + return productName; + } + + public void setProductName(String productName) { + this.productName = productName; + } + + public int getPrice() { + return price; + } + + public void setPrice(int price) { + this.price = price; + } + + public int getQuantity() { + return quantity; + } + + public void setQuantity(int quantity) { + this.quantity = quantity; + } + + public long getOptionId() { + return optionId; + } + + public void setOptionId(long optionId) { + this.optionId = optionId; + } +} diff --git a/src/main/java/gift/Repository/CategoryJpaRepository.java b/src/main/java/gift/Repository/CategoryJpaRepository.java new file mode 100644 index 000000000..d5f22dbca --- /dev/null +++ b/src/main/java/gift/Repository/CategoryJpaRepository.java @@ -0,0 +1,8 @@ +package gift.Repository; + +import gift.Entity.Category; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface CategoryJpaRepository extends JpaRepository { + +} diff --git a/src/main/java/gift/Repository/MemberJpaRepository.java b/src/main/java/gift/Repository/MemberJpaRepository.java new file mode 100644 index 000000000..417cf2597 --- /dev/null +++ b/src/main/java/gift/Repository/MemberJpaRepository.java @@ -0,0 +1,16 @@ +package gift.Repository; + +import gift.Entity.Member; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.Optional; + +public interface MemberJpaRepository extends JpaRepository { + + Optional findByEmail(String email); + + Optional findByEmailAndPassword(String email, String password); + + Optional findByKakaoId(long kakaoId); + +} diff --git a/src/main/java/gift/Repository/OptionJpaRepository.java b/src/main/java/gift/Repository/OptionJpaRepository.java new file mode 100644 index 000000000..3653c69c3 --- /dev/null +++ b/src/main/java/gift/Repository/OptionJpaRepository.java @@ -0,0 +1,11 @@ +package gift.Repository; + +import gift.Entity.Option; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; + +public interface OptionJpaRepository extends JpaRepository{ + List