From 5123667b5af662739c6308bb2dddf352cfcee3e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=82=98=EC=A0=9C=EB=B2=95?= Date: Thu, 25 Jul 2024 17:56:23 +0900 Subject: [PATCH 001/138] =?UTF-8?q?init:=204=EC=A3=BC=EC=B0=A8=20=EC=BD=94?= =?UTF-8?q?=EB=93=9C=20=EC=97=85=EB=A1=9C=EB=93=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 3 + build.gradle | 8 + src/main/java/gift/Application.java | 7 +- .../annotation/LoginMember.java | 12 + .../AuthenticationExceptionHandlerFilter.java | 32 +++ .../filter/AuthenticationFilter.java | 51 ++++ .../authentication/token/JwtProvider.java | 39 +++ .../authentication/token/JwtResolver.java | 34 +++ .../java/gift/authentication/token/Token.java | 21 ++ .../authentication/token/TokenContext.java | 20 ++ src/main/java/gift/config/WebConfig.java | 46 +++ .../gift/converter/StringToUrlConverter.java | 20 ++ src/main/java/gift/domain/Category.java | 99 +++++++ src/main/java/gift/domain/Member.java | 82 ++++++ src/main/java/gift/domain/Product.java | 134 +++++++++ src/main/java/gift/domain/ProductOption.java | 90 ++++++ src/main/java/gift/domain/WishProduct.java | 88 ++++++ .../java/gift/domain/base/BaseEntity.java | 28 ++ .../java/gift/domain/base/BaseTimeEntity.java | 54 ++++ src/main/java/gift/domain/vo/Color.java | 49 ++++ src/main/java/gift/domain/vo/Email.java | 44 +++ src/main/java/gift/domain/vo/Password.java | 48 ++++ .../gift/repository/CategoryRepository.java | 10 + .../gift/repository/MemberIdAuditorAware.java | 15 + .../gift/repository/MemberRepository.java | 14 + .../repository/ProductOptionRepository.java | 31 ++ .../gift/repository/ProductRepository.java | 14 + .../repository/WishProductRepository.java | 20 ++ .../java/gift/service/CategoryService.java | 57 ++++ .../gift/service/MemberDetailsService.java | 22 ++ src/main/java/gift/service/MemberService.java | 64 +++++ .../gift/service/ProductOptionService.java | 146 ++++++++++ .../java/gift/service/ProductService.java | 99 +++++++ .../java/gift/service/WishProductService.java | 91 ++++++ src/main/java/gift/utils/JsonUtils.java | 27 ++ src/main/java/gift/utils/StringUtils.java | 34 +++ .../controller/api/CategoryApiController.java | 68 +++++ .../controller/api/MemberApiController.java | 77 +++++ .../controller/api/ProductApiController.java | 128 +++++++++ .../view/ProductViewController.java | 44 +++ src/main/java/gift/web/dto/MemberDetails.java | 27 ++ .../gift/web/dto/form/CreateProductForm.java | 23 ++ .../gift/web/dto/form/UpdateProductForm.java | 40 +++ .../gift/web/dto/request/LoginRequest.java | 23 ++ .../category/CreateCategoryRequest.java | 60 ++++ .../category/UpdateCategoryRequest.java | 61 ++++ .../request/member/CreateMemberRequest.java | 42 +++ .../request/product/CreateProductRequest.java | 81 ++++++ .../request/product/UpdateProductRequest.java | 50 ++++ .../CreateProductOptionRequest.java | 47 ++++ .../SubtractProductOptionQuantityRequest.java | 14 + .../UpdateProductOptionRequest.java | 39 +++ .../wishproduct/CreateWishProductRequest.java | 25 ++ .../wishproduct/UpdateWishProductRequest.java | 20 ++ .../gift/web/dto/response/ErrorResponse.java | 49 ++++ .../gift/web/dto/response/LoginResponse.java | 19 ++ .../category/CreateCategoryResponse.java | 47 ++++ .../category/ReadAllCategoriesResponse.java | 16 ++ .../category/ReadCategoryResponse.java | 47 ++++ .../category/UpdateCategoryResponse.java | 47 ++++ .../response/member/CreateMemberResponse.java | 34 +++ .../response/member/ReadMemberResponse.java | 39 +++ .../product/CreateProductResponse.java | 52 ++++ .../product/ReadAllProductsResponse.java | 20 ++ .../response/product/ReadProductResponse.java | 47 ++++ .../product/UpdateProductResponse.java | 47 ++++ .../CreateProductOptionResponse.java | 37 +++ .../ReadAllProductOptionsResponse.java | 20 ++ .../ReadProductOptionResponse.java | 34 +++ ...SubtractProductOptionQuantityResponse.java | 34 +++ .../UpdateProductOptionResponse.java | 33 +++ .../CreateWishProductResponse.java | 27 ++ .../ReadAllWishProductsResponse.java | 19 ++ .../wishproduct/ReadWishProductResponse.java | 75 +++++ .../UpdateWishProductResponse.java | 53 ++++ .../resolver/LoginMemberArgumentResolver.java | 53 ++++ .../web/validation/constraints/HexColor.java | 22 ++ .../web/validation/constraints/Password.java | 22 ++ .../constraints/RequiredKakaoApproval.java | 22 ++ .../constraints/SpecialCharacter.java | 24 ++ .../validation/exception/CustomException.java | 32 +++ .../client/AlreadyExistsException.java | 28 ++ .../exception/client/BadRequestException.java | 32 +++ .../client/IncorrectEmailException.java | 29 ++ .../client/IncorrectPasswordException.java | 33 +++ .../client/InvalidCredentialsException.java | 33 +++ .../client/ResourceNotFoundException.java | 28 ++ .../validation/exception/code/Category.java | 20 ++ .../exception/code/ErrorStatus.java | 50 ++++ .../server/InternalServerException.java | 33 +++ .../handler/ApiExceptionHandler.java | 53 ++++ .../validator/HexColorValidator.java | 23 ++ .../validator/KakaoApprovalValidator.java | 22 ++ .../validator/PasswordValidator.java | 38 +++ .../validator/SpecialCharacterValidator.java | 30 ++ src/main/resources/application-local.yml | 18 ++ src/main/resources/application-secret.yml | 8 + src/main/resources/application.yml | 19 ++ src/main/resources/sql/data.sql | 154 ++++++++++ src/main/resources/sql/schema.sql | 25 ++ src/main/resources/static/index.html | 16 ++ src/main/resources/static/js/script.js | 86 ++++++ src/main/resources/templates/admin.html | 44 +++ .../templates/form/add-product-form.html | 29 ++ .../templates/form/edit-product-form.html | 29 ++ src/test/java/gift/domain/ProductTest.java | 45 +++ .../gift/service/CategoryServiceTest.java | 155 ++++++++++ .../java/gift/service/MemberServiceTest.java | 115 ++++++++ .../service/ProductOptionServiceTest.java | 179 ++++++++++++ .../java/gift/service/ProductServiceTest.java | 264 ++++++++++++++++++ .../gift/service/WishProductServiceTest.java | 173 ++++++++++++ .../gift/utils/CategoryDummyDataProvider.java | 51 ++++ src/test/java/gift/utils/DatabaseCleanup.java | 41 +++ .../gift/utils/MemberDummyDataProvider.java | 48 ++++ .../gift/utils/ProductDummyDataProvider.java | 50 ++++ src/test/java/gift/utils/StringUtilsTest.java | 71 +++++ .../utils/WishProductDummyDataProvider.java | 52 ++++ .../api/MemberApiControllerTest.java | 226 +++++++++++++++ src/test/resources/application-test.yml | 28 ++ 119 files changed, 5870 insertions(+), 1 deletion(-) create mode 100644 src/main/java/gift/authentication/annotation/LoginMember.java create mode 100644 src/main/java/gift/authentication/filter/AuthenticationExceptionHandlerFilter.java create mode 100644 src/main/java/gift/authentication/filter/AuthenticationFilter.java create mode 100644 src/main/java/gift/authentication/token/JwtProvider.java create mode 100644 src/main/java/gift/authentication/token/JwtResolver.java create mode 100644 src/main/java/gift/authentication/token/Token.java create mode 100644 src/main/java/gift/authentication/token/TokenContext.java create mode 100644 src/main/java/gift/config/WebConfig.java create mode 100644 src/main/java/gift/converter/StringToUrlConverter.java create mode 100644 src/main/java/gift/domain/Category.java create mode 100644 src/main/java/gift/domain/Member.java create mode 100644 src/main/java/gift/domain/Product.java create mode 100644 src/main/java/gift/domain/ProductOption.java create mode 100644 src/main/java/gift/domain/WishProduct.java create mode 100644 src/main/java/gift/domain/base/BaseEntity.java create mode 100644 src/main/java/gift/domain/base/BaseTimeEntity.java create mode 100644 src/main/java/gift/domain/vo/Color.java create mode 100644 src/main/java/gift/domain/vo/Email.java create mode 100644 src/main/java/gift/domain/vo/Password.java create mode 100644 src/main/java/gift/repository/CategoryRepository.java create mode 100644 src/main/java/gift/repository/MemberIdAuditorAware.java create mode 100644 src/main/java/gift/repository/MemberRepository.java create mode 100644 src/main/java/gift/repository/ProductOptionRepository.java create mode 100644 src/main/java/gift/repository/ProductRepository.java create mode 100644 src/main/java/gift/repository/WishProductRepository.java create mode 100644 src/main/java/gift/service/CategoryService.java create mode 100644 src/main/java/gift/service/MemberDetailsService.java create mode 100644 src/main/java/gift/service/MemberService.java create mode 100644 src/main/java/gift/service/ProductOptionService.java create mode 100644 src/main/java/gift/service/ProductService.java create mode 100644 src/main/java/gift/service/WishProductService.java create mode 100644 src/main/java/gift/utils/JsonUtils.java create mode 100644 src/main/java/gift/utils/StringUtils.java create mode 100644 src/main/java/gift/web/controller/api/CategoryApiController.java create mode 100644 src/main/java/gift/web/controller/api/MemberApiController.java create mode 100644 src/main/java/gift/web/controller/api/ProductApiController.java create mode 100644 src/main/java/gift/web/controller/view/ProductViewController.java create mode 100644 src/main/java/gift/web/dto/MemberDetails.java create mode 100644 src/main/java/gift/web/dto/form/CreateProductForm.java create mode 100644 src/main/java/gift/web/dto/form/UpdateProductForm.java create mode 100644 src/main/java/gift/web/dto/request/LoginRequest.java create mode 100644 src/main/java/gift/web/dto/request/category/CreateCategoryRequest.java create mode 100644 src/main/java/gift/web/dto/request/category/UpdateCategoryRequest.java create mode 100644 src/main/java/gift/web/dto/request/member/CreateMemberRequest.java create mode 100644 src/main/java/gift/web/dto/request/product/CreateProductRequest.java create mode 100644 src/main/java/gift/web/dto/request/product/UpdateProductRequest.java create mode 100644 src/main/java/gift/web/dto/request/productoption/CreateProductOptionRequest.java create mode 100644 src/main/java/gift/web/dto/request/productoption/SubtractProductOptionQuantityRequest.java create mode 100644 src/main/java/gift/web/dto/request/productoption/UpdateProductOptionRequest.java create mode 100644 src/main/java/gift/web/dto/request/wishproduct/CreateWishProductRequest.java create mode 100644 src/main/java/gift/web/dto/request/wishproduct/UpdateWishProductRequest.java create mode 100644 src/main/java/gift/web/dto/response/ErrorResponse.java create mode 100644 src/main/java/gift/web/dto/response/LoginResponse.java create mode 100644 src/main/java/gift/web/dto/response/category/CreateCategoryResponse.java create mode 100644 src/main/java/gift/web/dto/response/category/ReadAllCategoriesResponse.java create mode 100644 src/main/java/gift/web/dto/response/category/ReadCategoryResponse.java create mode 100644 src/main/java/gift/web/dto/response/category/UpdateCategoryResponse.java create mode 100644 src/main/java/gift/web/dto/response/member/CreateMemberResponse.java create mode 100644 src/main/java/gift/web/dto/response/member/ReadMemberResponse.java create mode 100644 src/main/java/gift/web/dto/response/product/CreateProductResponse.java create mode 100644 src/main/java/gift/web/dto/response/product/ReadAllProductsResponse.java create mode 100644 src/main/java/gift/web/dto/response/product/ReadProductResponse.java create mode 100644 src/main/java/gift/web/dto/response/product/UpdateProductResponse.java create mode 100644 src/main/java/gift/web/dto/response/productoption/CreateProductOptionResponse.java create mode 100644 src/main/java/gift/web/dto/response/productoption/ReadAllProductOptionsResponse.java create mode 100644 src/main/java/gift/web/dto/response/productoption/ReadProductOptionResponse.java create mode 100644 src/main/java/gift/web/dto/response/productoption/SubtractProductOptionQuantityResponse.java create mode 100644 src/main/java/gift/web/dto/response/productoption/UpdateProductOptionResponse.java create mode 100644 src/main/java/gift/web/dto/response/wishproduct/CreateWishProductResponse.java create mode 100644 src/main/java/gift/web/dto/response/wishproduct/ReadAllWishProductsResponse.java create mode 100644 src/main/java/gift/web/dto/response/wishproduct/ReadWishProductResponse.java create mode 100644 src/main/java/gift/web/dto/response/wishproduct/UpdateWishProductResponse.java create mode 100644 src/main/java/gift/web/resolver/LoginMemberArgumentResolver.java create mode 100644 src/main/java/gift/web/validation/constraints/HexColor.java create mode 100644 src/main/java/gift/web/validation/constraints/Password.java create mode 100644 src/main/java/gift/web/validation/constraints/RequiredKakaoApproval.java create mode 100644 src/main/java/gift/web/validation/constraints/SpecialCharacter.java create mode 100644 src/main/java/gift/web/validation/exception/CustomException.java create mode 100644 src/main/java/gift/web/validation/exception/client/AlreadyExistsException.java create mode 100644 src/main/java/gift/web/validation/exception/client/BadRequestException.java create mode 100644 src/main/java/gift/web/validation/exception/client/IncorrectEmailException.java create mode 100644 src/main/java/gift/web/validation/exception/client/IncorrectPasswordException.java create mode 100644 src/main/java/gift/web/validation/exception/client/InvalidCredentialsException.java create mode 100644 src/main/java/gift/web/validation/exception/client/ResourceNotFoundException.java create mode 100644 src/main/java/gift/web/validation/exception/code/Category.java create mode 100644 src/main/java/gift/web/validation/exception/code/ErrorStatus.java create mode 100644 src/main/java/gift/web/validation/exception/server/InternalServerException.java create mode 100644 src/main/java/gift/web/validation/handler/ApiExceptionHandler.java create mode 100644 src/main/java/gift/web/validation/validator/HexColorValidator.java create mode 100644 src/main/java/gift/web/validation/validator/KakaoApprovalValidator.java create mode 100644 src/main/java/gift/web/validation/validator/PasswordValidator.java create mode 100644 src/main/java/gift/web/validation/validator/SpecialCharacterValidator.java create mode 100644 src/main/resources/application-local.yml create mode 100644 src/main/resources/application-secret.yml create mode 100644 src/main/resources/application.yml create mode 100644 src/main/resources/sql/data.sql create mode 100644 src/main/resources/sql/schema.sql create mode 100644 src/main/resources/static/index.html create mode 100644 src/main/resources/static/js/script.js create mode 100644 src/main/resources/templates/admin.html create mode 100644 src/main/resources/templates/form/add-product-form.html create mode 100644 src/main/resources/templates/form/edit-product-form.html create mode 100644 src/test/java/gift/domain/ProductTest.java create mode 100644 src/test/java/gift/service/CategoryServiceTest.java create mode 100644 src/test/java/gift/service/MemberServiceTest.java create mode 100644 src/test/java/gift/service/ProductOptionServiceTest.java create mode 100644 src/test/java/gift/service/ProductServiceTest.java create mode 100644 src/test/java/gift/service/WishProductServiceTest.java create mode 100644 src/test/java/gift/utils/CategoryDummyDataProvider.java create mode 100644 src/test/java/gift/utils/DatabaseCleanup.java create mode 100644 src/test/java/gift/utils/MemberDummyDataProvider.java create mode 100644 src/test/java/gift/utils/ProductDummyDataProvider.java create mode 100644 src/test/java/gift/utils/StringUtilsTest.java create mode 100644 src/test/java/gift/utils/WishProductDummyDataProvider.java create mode 100644 src/test/java/gift/web/controller/api/MemberApiControllerTest.java create mode 100644 src/test/resources/application-test.yml diff --git a/.gitignore b/.gitignore index 0caf866b0..281fb4a5b 100644 --- a/.gitignore +++ b/.gitignore @@ -38,3 +38,6 @@ out/ ### Mac OS ### .DS_Store + +### secret files ### +#src/main/resources/application-secret.yml \ No newline at end of file diff --git a/build.gradle b/build.gradle index df7db9334..9ffb4a4b1 100644 --- a/build.gradle +++ b/build.gradle @@ -19,8 +19,16 @@ repositories { dependencies { implementation 'org.springframework.boot:spring-boot-starter-jdbc' + implementation 'org.springframework.boot:spring-boot-starter-data-jpa' implementation 'org.springframework.boot:spring-boot-starter-thymeleaf' implementation 'org.springframework.boot:spring-boot-starter-web' + implementation 'org.springframework.boot:spring-boot-starter-validation' + + //jwt + implementation 'io.jsonwebtoken:jjwt-api:0.12.6' + runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.12.6' + runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.12.6' + runtimeOnly 'com.h2database:h2' testImplementation 'org.springframework.boot:spring-boot-starter-test' testRuntimeOnly 'org.junit.platform:junit-platform-launcher' diff --git a/src/main/java/gift/Application.java b/src/main/java/gift/Application.java index 61603cca0..037286d2c 100644 --- a/src/main/java/gift/Application.java +++ b/src/main/java/gift/Application.java @@ -2,10 +2,15 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.data.jpa.repository.config.EnableJpaAuditing; +@EnableJpaAuditing(auditorAwareRef = "memberIdAuditorAware") @SpringBootApplication public class Application { + public static void main(String[] args) { - SpringApplication.run(Application.class, args); + SpringApplication app = new SpringApplication(Application.class); + app.setAdditionalProfiles("local"); + app.run(args); } } diff --git a/src/main/java/gift/authentication/annotation/LoginMember.java b/src/main/java/gift/authentication/annotation/LoginMember.java new file mode 100644 index 000000000..3f051341b --- /dev/null +++ b/src/main/java/gift/authentication/annotation/LoginMember.java @@ -0,0 +1,12 @@ +package gift.authentication.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 LoginMember { + +} diff --git a/src/main/java/gift/authentication/filter/AuthenticationExceptionHandlerFilter.java b/src/main/java/gift/authentication/filter/AuthenticationExceptionHandlerFilter.java new file mode 100644 index 000000000..7cf460f49 --- /dev/null +++ b/src/main/java/gift/authentication/filter/AuthenticationExceptionHandlerFilter.java @@ -0,0 +1,32 @@ +package gift.authentication.filter; + +import gift.utils.JsonUtils; +import gift.web.dto.response.ErrorResponse; +import gift.web.validation.exception.client.InvalidCredentialsException; +import io.jsonwebtoken.JwtException; +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import java.io.IOException; +import org.springframework.http.HttpStatus; +import org.springframework.web.filter.OncePerRequestFilter; + +public class AuthenticationExceptionHandlerFilter extends OncePerRequestFilter { + + @Override + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, + FilterChain filterChain) throws ServletException, IOException { + + try { + filterChain.doFilter(request, response); + } catch (JwtException e) { + ErrorResponse errorResponse = ErrorResponse.from(new InvalidCredentialsException()); + String errorResponseJson = JsonUtils.toJson(errorResponse); + + response.setContentType("application/json;charset=UTF-8"); + response.setStatus(HttpStatus.UNAUTHORIZED.value()); + response.getWriter().write(errorResponseJson); + } + } +} diff --git a/src/main/java/gift/authentication/filter/AuthenticationFilter.java b/src/main/java/gift/authentication/filter/AuthenticationFilter.java new file mode 100644 index 000000000..19386bfe2 --- /dev/null +++ b/src/main/java/gift/authentication/filter/AuthenticationFilter.java @@ -0,0 +1,51 @@ +package gift.authentication.filter; + +import gift.authentication.token.JwtResolver; +import gift.authentication.token.Token; +import gift.authentication.token.TokenContext; +import gift.web.validation.exception.client.InvalidCredentialsException; +import io.jsonwebtoken.JwtException; +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.util.List; +import java.util.Objects; +import org.springframework.web.filter.OncePerRequestFilter; + +public class AuthenticationFilter extends OncePerRequestFilter { + + private final List ignorePaths = List.of("/api/members/login", "/api/members/register"); + private final String AUTHORIZATION_HEADER = "Authorization"; + private final String BEARER = "Bearer "; + private final JwtResolver jwtResolver; + + public AuthenticationFilter(JwtResolver jwtResolver) { + this.jwtResolver = jwtResolver; + } + + @Override + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, + FilterChain filterChain) throws ServletException, IOException { + + String authorization = request.getHeader(AUTHORIZATION_HEADER); + if(Objects.nonNull(authorization) && authorization.startsWith(BEARER)) { + String token = authorization.substring(BEARER.length()); + + Long memberId = jwtResolver.resolveId(Token.from(token)).orElseThrow(InvalidCredentialsException::new); + TokenContext.addCurrentMemberId(memberId); + + filterChain.doFilter(request, response); + TokenContext.clear(); + return; + } + + throw new JwtException("Invalid token"); + } + + @Override + protected boolean shouldNotFilter(HttpServletRequest request) throws ServletException { + return ignorePaths.contains(request.getRequestURI()); + } +} diff --git a/src/main/java/gift/authentication/token/JwtProvider.java b/src/main/java/gift/authentication/token/JwtProvider.java new file mode 100644 index 000000000..1080dd9c9 --- /dev/null +++ b/src/main/java/gift/authentication/token/JwtProvider.java @@ -0,0 +1,39 @@ +package gift.authentication.token; + +import gift.domain.Member; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.io.Decoders; +import io.jsonwebtoken.security.Keys; +import java.util.Date; +import javax.crypto.SecretKey; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +@Component +public class JwtProvider { + + private final SecretKey key; + + @Value("${jwt.expiration}") + private long expirationTime; + + private final String MEMBER_ID_CLAIM_KEY = "memberId"; + + public JwtProvider(@Value("${jwt.secretkey}") String secret) { + key = Keys.hmacShaKeyFor(Decoders.BASE64.decode(secret)); + } + + public Token generateToken(Member member) { + long nowMillis = System.currentTimeMillis(); + Date now = new Date(nowMillis); + Date expiration = new Date(nowMillis + expirationTime); + + return Token.from(Jwts.builder() + .claim(MEMBER_ID_CLAIM_KEY, member.getId()) + .issuedAt(now) + .expiration(expiration) + .signWith(key) + .compact()); + } + +} diff --git a/src/main/java/gift/authentication/token/JwtResolver.java b/src/main/java/gift/authentication/token/JwtResolver.java new file mode 100644 index 000000000..998484281 --- /dev/null +++ b/src/main/java/gift/authentication/token/JwtResolver.java @@ -0,0 +1,34 @@ +package gift.authentication.token; + +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.io.Decoders; +import io.jsonwebtoken.security.Keys; +import java.util.Optional; +import javax.crypto.SecretKey; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +@Component +public class JwtResolver { + + private final SecretKey key; + private final String MEMBER_ID_CLAIM_KEY = "memberId"; + + public JwtResolver(@Value("${jwt.secretkey}") String secret) { + this.key = Keys.hmacShaKeyFor(Decoders.BASE64.decode(secret)); + } + + public Claims resolve(Token token) { + return Jwts.parser() + .verifyWith(key) + .build() + .parseSignedClaims(token.getValue()) + .getPayload(); + } + + public Optional resolveId(Token token) { + return Optional.ofNullable(resolve(token).get(MEMBER_ID_CLAIM_KEY, Long.class)); + } + +} diff --git a/src/main/java/gift/authentication/token/Token.java b/src/main/java/gift/authentication/token/Token.java new file mode 100644 index 000000000..8ff73f026 --- /dev/null +++ b/src/main/java/gift/authentication/token/Token.java @@ -0,0 +1,21 @@ +package gift.authentication.token; + +public class Token { + + private String value; + + private Token() { + } + + private Token(String value) { + this.value = value; + } + + public static Token from(String value) { + return new Token(value); + } + + public String getValue() { + return value; + } +} diff --git a/src/main/java/gift/authentication/token/TokenContext.java b/src/main/java/gift/authentication/token/TokenContext.java new file mode 100644 index 000000000..e992e8e11 --- /dev/null +++ b/src/main/java/gift/authentication/token/TokenContext.java @@ -0,0 +1,20 @@ +package gift.authentication.token; + +import java.util.Optional; + +public abstract class TokenContext { + + private static final ThreadLocal currentMember = new ThreadLocal<>(); + + public static void addCurrentMemberId(Long memberId) { + currentMember.set(memberId); + } + + public static Optional getCurrentMemberId() { + return Optional.ofNullable(currentMember.get()); + } + + public static void clear() { + currentMember.remove(); + } +} diff --git a/src/main/java/gift/config/WebConfig.java b/src/main/java/gift/config/WebConfig.java new file mode 100644 index 000000000..e5dc82b40 --- /dev/null +++ b/src/main/java/gift/config/WebConfig.java @@ -0,0 +1,46 @@ +package gift.config; + +import gift.authentication.filter.AuthenticationExceptionHandlerFilter; +import gift.authentication.filter.AuthenticationFilter; +import gift.authentication.token.JwtResolver; +import gift.web.resolver.LoginMemberArgumentResolver; +import java.util.List; +import org.springframework.boot.web.servlet.FilterRegistrationBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.method.support.HandlerMethodArgumentResolver; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +@Configuration +public class WebConfig implements WebMvcConfigurer { + private final LoginMemberArgumentResolver loginUserArgumentResolver; + private final JwtResolver jwtResolver; + + public WebConfig(LoginMemberArgumentResolver loginUserArgumentResolver, JwtResolver jwtResolver) { + this.loginUserArgumentResolver = loginUserArgumentResolver; + this.jwtResolver = jwtResolver; + } + + @Bean + public FilterRegistrationBean authenticationExceptionHandlerFilter() { + FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean(); + filterRegistrationBean.setFilter(new AuthenticationExceptionHandlerFilter()); + filterRegistrationBean.setOrder(1); + return filterRegistrationBean; + } + + @Bean + public FilterRegistrationBean authenticationFilter() { + FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean(); + filterRegistrationBean.setFilter(new AuthenticationFilter(jwtResolver)); + filterRegistrationBean.addUrlPatterns("/api/*"); + filterRegistrationBean.setOrder(2); + return filterRegistrationBean; + } + + @Override + public void addArgumentResolvers(List resolvers) { + resolvers.add(loginUserArgumentResolver); + } + +} diff --git a/src/main/java/gift/converter/StringToUrlConverter.java b/src/main/java/gift/converter/StringToUrlConverter.java new file mode 100644 index 000000000..76426ad25 --- /dev/null +++ b/src/main/java/gift/converter/StringToUrlConverter.java @@ -0,0 +1,20 @@ +package gift.converter; + +import java.net.MalformedURLException; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URL; + +public final class StringToUrlConverter { + + private StringToUrlConverter() { + } + + public static URL convert(String source) { + try { + return new URI(source).toURL(); + } catch (MalformedURLException | URISyntaxException e) { + throw new IllegalArgumentException("유효하지 않은 URL 형식입니다."); + } + } +} diff --git a/src/main/java/gift/domain/Category.java b/src/main/java/gift/domain/Category.java new file mode 100644 index 000000000..a49b60b88 --- /dev/null +++ b/src/main/java/gift/domain/Category.java @@ -0,0 +1,99 @@ +package gift.domain; + +import gift.domain.base.BaseEntity; +import gift.domain.base.BaseTimeEntity; +import gift.domain.vo.Color; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import java.net.URL; +import org.hibernate.annotations.ColumnDefault; + +@Entity +public class Category extends BaseEntity { + + @Column(nullable = false) + private String name; + + @Column(nullable = false) + private String description; + + @Column(nullable = false) + @ColumnDefault("'https://gift-s3.s3.ap-northeast-2.amazonaws.com/default-image.png'") + private URL imageUrl; + + @Column(nullable = false) + private Color color; + + protected Category() { + } + + public static class Builder extends BaseTimeEntity.Builder { + + private String name; + private String description; + private URL imageUrl; + private Color color; + + public Builder name(String name) { + this.name = name; + return this; + } + + public Builder description(String description) { + this.description = description; + return this; + } + + public Builder imageUrl(URL imageUrl) { + this.imageUrl = imageUrl; + return this; + } + + public Builder color(Color color) { + this.color = color; + return this; + } + + @Override + protected Builder self() { + return this; + } + + @Override + public Category build() { + return new Category(this); + } + } + + private Category(Builder builder) { + super(builder); + name = builder.name; + description = builder.description; + imageUrl = builder.imageUrl; + color = builder.color; + } + + public Category update(Category category) { + name = category.name; + description = category.description; + imageUrl = category.imageUrl; + color = category.color; + return this; + } + + public String getName() { + return name; + } + + public String getDescription() { + return description; + } + + public URL getImageUrl() { + return imageUrl; + } + + public Color getColor() { + return color; + } +} \ No newline at end of file diff --git a/src/main/java/gift/domain/Member.java b/src/main/java/gift/domain/Member.java new file mode 100644 index 000000000..bdc25c983 --- /dev/null +++ b/src/main/java/gift/domain/Member.java @@ -0,0 +1,82 @@ +package gift.domain; + +import gift.domain.base.BaseTimeEntity; +import gift.domain.vo.Email; +import gift.domain.vo.Password; +import gift.web.validation.exception.client.IncorrectPasswordException; +import jakarta.persistence.Column; +import jakarta.persistence.Embedded; +import jakarta.persistence.Entity; + +@Entity +public class Member extends BaseTimeEntity { + + @Embedded + private Email email; + + @Embedded + private Password password; + + @Column(nullable = false) + private String name; + + protected Member() { + } + + public static class Builder extends BaseTimeEntity.Builder { + + private Email email; + private Password password; + private String name; + + public Builder email(Email email) { + this.email = email; + return this; + } + + public Builder password(Password password) { + this.password = password; + return this; + } + + public Builder name(String name) { + this.name = name; + return this; + } + + @Override + protected Builder self() { + return this; + } + + @Override + public Member build() { + return new Member(this); + } + } + + private Member(Builder builder) { + super(builder); + email = builder.email; + password = builder.password; + name = builder.name; + } + + public Email getEmail() { + return email; + } + + public Password getPassword() { + return password; + } + + public String getName() { + return name; + } + + public void matchPassword(String password) { + if (!this.password.matches(password)) { + throw new IncorrectPasswordException(); + } + } +} diff --git a/src/main/java/gift/domain/Product.java b/src/main/java/gift/domain/Product.java new file mode 100644 index 000000000..dff5f0613 --- /dev/null +++ b/src/main/java/gift/domain/Product.java @@ -0,0 +1,134 @@ +package gift.domain; + +import gift.domain.base.BaseEntity; +import gift.domain.base.BaseTimeEntity; +import jakarta.persistence.Column; +import jakarta.persistence.ConstraintMode; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.ForeignKey; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.OneToMany; +import java.net.URL; +import java.util.List; +import org.hibernate.annotations.ColumnDefault; +import org.hibernate.annotations.DynamicInsert; + +@DynamicInsert +@Entity +public class Product extends BaseEntity { + + @Column(nullable = false) + private String name; + + @Column(nullable = false) + private Integer price; + + @Column(nullable = false) + @ColumnDefault("'https://gift-s3.s3.ap-northeast-2.amazonaws.com/default-image.png'") + private URL imageUrl; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(foreignKey = @ForeignKey(value = ConstraintMode.NO_CONSTRAINT), nullable = false) + private Category category; + + @OneToMany(mappedBy = "productId", fetch = FetchType.LAZY) + private List productOptions; + + protected Product() { + } + + public static class Builder extends BaseTimeEntity.Builder { + + private String name; + private Integer price; + private URL imageUrl; + private Category category; + private List productOptions; + + public Builder name(String name) { + this.name = name; + return this; + } + + public Builder price(Integer price) { + this.price = price; + return this; + } + + public Builder imageUrl(URL imageUrl) { + this.imageUrl = imageUrl; + return this; + } + + public Builder category(Category category) { + this.category = category; + return this; + } + + public Builder productOptions(List productOptions) { + this.productOptions = productOptions; + return this; + } + + @Override + protected Builder self() { + return this; + } + + @Override + public Product build() { + return new Product(this); + } + + private void validateProductOptionsPresence(List productOptions) { + if (productOptions == null || productOptions.isEmpty()) { + throw new IllegalArgumentException("상품 옵션은 최소 1개 이상이어야 합니다."); + } + } + } + + private Product(Builder builder) { + super(builder); + name = builder.name; + price = builder.price; + imageUrl = builder.imageUrl; + category = builder.category; + productOptions = builder.productOptions; + validateProductOptionsPresence(productOptions); + } + + public Product updateBasicInfo(String name, Integer price, URL imageUrl) { + this.name = name; + this.price = price; + this.imageUrl = imageUrl; + return this; + } + + private void validateProductOptionsPresence(List productOptions) { + if (productOptions == null || productOptions.isEmpty()) { + throw new IllegalArgumentException("상품 옵션은 최소 1개 이상이어야 합니다."); + } + } + + public String getName() { + return name; + } + + public Integer getPrice() { + return price; + } + + public URL getImageUrl() { + return imageUrl; + } + + public Category getCategory() { + return category; + } + + public List getProductOptions() { + return productOptions; + } +} diff --git a/src/main/java/gift/domain/ProductOption.java b/src/main/java/gift/domain/ProductOption.java new file mode 100644 index 000000000..81842896c --- /dev/null +++ b/src/main/java/gift/domain/ProductOption.java @@ -0,0 +1,90 @@ +package gift.domain; + +import gift.domain.base.BaseEntity; +import gift.domain.base.BaseTimeEntity; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; + +@Entity +public class ProductOption extends BaseEntity { + + @Column(nullable = false) + private String name; + + @Column(nullable = false) + private Integer stock; + + private Long productId; + + protected ProductOption() { + } + + public static class Builder extends BaseTimeEntity.Builder { + + private String name; + private Integer stock; + private Long productId; + + public Builder name(String name) { + this.name = name; + return this; + } + + public Builder stock(Integer stock) { + this.stock = stock; + return this; + } + + public Builder productId(Long productId) { + this.productId = productId; + return this; + } + + @Override + protected Builder self() { + return this; + } + + @Override + public ProductOption build() { + return new ProductOption(this); + } + } + + private ProductOption(Builder builder) { + super(builder); + name = builder.name; + stock = builder.stock; + productId = builder.productId; + } + + public ProductOption update(ProductOption option) { + this.name = option.name; + this.stock = option.stock; + return this; + } + + public ProductOption subtractQuantity(Integer quantity) { + validatedRequestQuantity(quantity); + this.stock -= quantity; + return this; + } + + private void validatedRequestQuantity(int quantity) { + if(quantity > stock) { + throw new IllegalStateException("재고보다 많은 수량을 요청할 수 없습니다."); + } + } + + public String getName() { + return name; + } + + public Integer getStock() { + return stock; + } + + public Long getProductId() { + return productId; + } +} diff --git a/src/main/java/gift/domain/WishProduct.java b/src/main/java/gift/domain/WishProduct.java new file mode 100644 index 000000000..25d016cec --- /dev/null +++ b/src/main/java/gift/domain/WishProduct.java @@ -0,0 +1,88 @@ +package gift.domain; + +import gift.domain.base.BaseEntity; +import gift.domain.base.BaseTimeEntity; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; + +@Entity +public class WishProduct extends BaseEntity { + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "member_id") + private Member member; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "product_id") + private Product product; + + @Column(nullable = false) + private Integer quantity; + + protected WishProduct() { + } + + public static class Builder extends BaseTimeEntity.Builder { + + private Member member; + private Product product; + private Integer quantity; + + public Builder member(Member member) { + this.member = member; + return this; + } + + public Builder product(Product product) { + this.product = product; + return this; + } + + public Builder quantity(Integer quantity) { + this.quantity = quantity; + return this; + } + + @Override + protected Builder self() { + return this; + } + + @Override + public WishProduct build() { + return new WishProduct(this); + } + } + + private WishProduct(Builder builder) { + super(builder); + member = builder.member; + product = builder.product; + quantity = builder.quantity; + } + + public Member getMember() { + return member; + } + + public Product getProduct() { + return product; + } + + public Integer getQuantity() { + return quantity; + } + + public Integer updateQuantity(Integer quantity) { + this.quantity = quantity; + return this.quantity; + } + + public Integer addQuantity(Integer quantity) { + this.quantity += quantity; + return this.quantity; + } +} diff --git a/src/main/java/gift/domain/base/BaseEntity.java b/src/main/java/gift/domain/base/BaseEntity.java new file mode 100644 index 000000000..61f23a72d --- /dev/null +++ b/src/main/java/gift/domain/base/BaseEntity.java @@ -0,0 +1,28 @@ +package gift.domain.base; + +import jakarta.persistence.Column; +import jakarta.persistence.EntityListeners; +import jakarta.persistence.MappedSuperclass; +import org.springframework.data.annotation.CreatedBy; +import org.springframework.data.annotation.LastModifiedBy; +import org.springframework.data.jpa.domain.support.AuditingEntityListener; + +@MappedSuperclass +@EntityListeners(AuditingEntityListener.class) +public abstract class BaseEntity extends BaseTimeEntity { + + @CreatedBy + @Column(nullable = false, updatable = false) + private Long createdBy; + + @LastModifiedBy + @Column(nullable = false) + private Long updatedBy; + + protected BaseEntity() { + } + + protected BaseEntity(Builder builder) { + super(builder); + } +} diff --git a/src/main/java/gift/domain/base/BaseTimeEntity.java b/src/main/java/gift/domain/base/BaseTimeEntity.java new file mode 100644 index 000000000..b492e6007 --- /dev/null +++ b/src/main/java/gift/domain/base/BaseTimeEntity.java @@ -0,0 +1,54 @@ +package gift.domain.base; + +import jakarta.persistence.Column; +import jakarta.persistence.EntityListeners; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.MappedSuperclass; +import java.time.LocalDateTime; +import org.springframework.data.annotation.CreatedDate; +import org.springframework.data.annotation.LastModifiedDate; +import org.springframework.data.jpa.domain.support.AuditingEntityListener; + +@MappedSuperclass +@EntityListeners(AuditingEntityListener.class) +public abstract class BaseTimeEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @CreatedDate + @Column(nullable = false, updatable = false) + private LocalDateTime createdAt; + + @LastModifiedDate + @Column(nullable = false) + private LocalDateTime updatedAt; + + protected BaseTimeEntity() { + } + + protected abstract static class Builder> { + + Long id; + + public T id(Long id) { + this.id = id; + return self(); + } + + protected abstract BaseTimeEntity build(); + + protected abstract T self(); + } + + protected BaseTimeEntity(Builder builder) { + id = builder.id; + } + + public Long getId() { + return id; + } +} diff --git a/src/main/java/gift/domain/vo/Color.java b/src/main/java/gift/domain/vo/Color.java new file mode 100644 index 000000000..01f3a760b --- /dev/null +++ b/src/main/java/gift/domain/vo/Color.java @@ -0,0 +1,49 @@ +package gift.domain.vo; + +import jakarta.persistence.Column; +import jakarta.persistence.Embeddable; +import java.util.Objects; + +@Embeddable +public class Color { + + @Column(name = "color", nullable = false) + private String value; + + protected Color() { + } + + private Color(String value) { + this.value = value; + } + + public static Color from(String color) { + return new Color(color); + } + + public String getValue() { + return value; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + Color e = (Color) o; + return Objects.equals(this.value, e.value); + } + + @Override + public int hashCode() { + return Objects.hash(value); + } + + @Override + public String toString() { + return value; + } +} diff --git a/src/main/java/gift/domain/vo/Email.java b/src/main/java/gift/domain/vo/Email.java new file mode 100644 index 000000000..d83213803 --- /dev/null +++ b/src/main/java/gift/domain/vo/Email.java @@ -0,0 +1,44 @@ +package gift.domain.vo; + +import jakarta.persistence.Column; +import jakarta.persistence.Embeddable; +import java.util.Objects; + +@Embeddable +public class Email { + + @Column(name = "email", nullable = false, unique = true) + private String value; + + protected Email() { + } + + private Email(String value) { + this.value = value; + } + + public static Email from(String email) { + return new Email(email); + } + + public String getValue() { + return value; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + Email e = (Email) o; + return Objects.equals(this.value, e.value); + } + + @Override + public int hashCode() { + return Objects.hash(value); + } +} diff --git a/src/main/java/gift/domain/vo/Password.java b/src/main/java/gift/domain/vo/Password.java new file mode 100644 index 000000000..d72f9804b --- /dev/null +++ b/src/main/java/gift/domain/vo/Password.java @@ -0,0 +1,48 @@ +package gift.domain.vo; + +import jakarta.persistence.Column; +import jakarta.persistence.Embeddable; +import java.util.Objects; + +@Embeddable +public class Password { + + @Column(name = "password", nullable = false) + private String value; + + protected Password() { + } + + private Password(String value) { + this.value = value; + } + + public static Password from(String password) { + return new Password(password); + } + + public boolean matches(String password) { + return this.value.equals(password); + } + + public String getValue() { + return value; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + Password p = (Password) o; + return Objects.equals(this.value, p.value); + } + + @Override + public int hashCode() { + return Objects.hash(value); + } +} diff --git a/src/main/java/gift/repository/CategoryRepository.java b/src/main/java/gift/repository/CategoryRepository.java new file mode 100644 index 000000000..04ffe7698 --- /dev/null +++ b/src/main/java/gift/repository/CategoryRepository.java @@ -0,0 +1,10 @@ +package gift.repository; + +import gift.domain.Category; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface CategoryRepository extends JpaRepository { + +} diff --git a/src/main/java/gift/repository/MemberIdAuditorAware.java b/src/main/java/gift/repository/MemberIdAuditorAware.java new file mode 100644 index 000000000..4fc1a0b78 --- /dev/null +++ b/src/main/java/gift/repository/MemberIdAuditorAware.java @@ -0,0 +1,15 @@ +package gift.repository; + +import gift.authentication.token.TokenContext; +import java.util.Optional; +import org.springframework.data.domain.AuditorAware; +import org.springframework.stereotype.Component; + +@Component +public class MemberIdAuditorAware implements AuditorAware { + + @Override + public Optional getCurrentAuditor() { + return TokenContext.getCurrentMemberId(); + } +} diff --git a/src/main/java/gift/repository/MemberRepository.java b/src/main/java/gift/repository/MemberRepository.java new file mode 100644 index 000000000..7ea6e2b3b --- /dev/null +++ b/src/main/java/gift/repository/MemberRepository.java @@ -0,0 +1,14 @@ +package gift.repository; + +import gift.domain.Member; +import gift.domain.vo.Email; +import java.util.Optional; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface MemberRepository extends JpaRepository { + + Optional findByEmail(Email email); + +} diff --git a/src/main/java/gift/repository/ProductOptionRepository.java b/src/main/java/gift/repository/ProductOptionRepository.java new file mode 100644 index 000000000..344217d05 --- /dev/null +++ b/src/main/java/gift/repository/ProductOptionRepository.java @@ -0,0 +1,31 @@ +package gift.repository; + +import gift.domain.ProductOption; +import java.util.List; +import java.util.Optional; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.stereotype.Repository; + +@Repository +public interface ProductOptionRepository extends JpaRepository { + + List findAllByProductId(Long productId); + + boolean existsByNameAndProductId(String name, Long productId); + + /** + * 해당 상품 내에서 현재 옵션을 제외하고 이름이 같은 옵션이 있는지 확인 + * @param id 현재 상품 옵션 아이디 + * @param productId 상품 아이디 + * @param name 상품 옵션 이름 + * @return 현재 옵션을 제외한 중복된 상품 옵션 아이디 + */ + @Query("SELECT po.id" + + " FROM ProductOption po" + + " WHERE po.id != :id AND po.productId = :productId AND po.name = :name" + ) + Optional findDuplicatedProductOption(Long id, Long productId, String name); + + void deleteAllByProductId(Long productId); +} diff --git a/src/main/java/gift/repository/ProductRepository.java b/src/main/java/gift/repository/ProductRepository.java new file mode 100644 index 000000000..0f26e8d25 --- /dev/null +++ b/src/main/java/gift/repository/ProductRepository.java @@ -0,0 +1,14 @@ +package gift.repository; + +import gift.domain.Product; +import java.util.List; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface ProductRepository extends JpaRepository { + + List findByCategoryId(Long categoryId, Pageable pageable); + +} diff --git a/src/main/java/gift/repository/WishProductRepository.java b/src/main/java/gift/repository/WishProductRepository.java new file mode 100644 index 000000000..3c1f2cc93 --- /dev/null +++ b/src/main/java/gift/repository/WishProductRepository.java @@ -0,0 +1,20 @@ +package gift.repository; + +import gift.domain.WishProduct; +import java.util.List; +import java.util.Optional; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface WishProductRepository extends JpaRepository { + + List findByMemberId(Long memberId, Pageable pageable); + + Optional findByMemberIdAndProductId(Long memberId, Long productId); + + void deleteAllByProductId(Long productId); + + void deleteAllByMemberId(Long memberId); +} diff --git a/src/main/java/gift/service/CategoryService.java b/src/main/java/gift/service/CategoryService.java new file mode 100644 index 000000000..5c0ab5f51 --- /dev/null +++ b/src/main/java/gift/service/CategoryService.java @@ -0,0 +1,57 @@ +package gift.service; + +import gift.domain.Category; +import gift.repository.CategoryRepository; +import gift.web.dto.request.category.CreateCategoryRequest; +import gift.web.dto.request.category.UpdateCategoryRequest; +import gift.web.dto.response.category.CreateCategoryResponse; +import gift.web.dto.response.category.ReadAllCategoriesResponse; +import gift.web.dto.response.category.ReadCategoryResponse; +import gift.web.dto.response.category.UpdateCategoryResponse; +import java.util.List; +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@Transactional(readOnly = true) +public class CategoryService { + + private final CategoryRepository categoryRepository; + + public CategoryService(CategoryRepository categoryRepository) { + this.categoryRepository = categoryRepository; + } + + @Transactional + public CreateCategoryResponse createCategory(CreateCategoryRequest request) { + Category category = categoryRepository.save(request.toEntity()); + return CreateCategoryResponse.fromEntity(category); + } + + public ReadAllCategoriesResponse readAllCategories(Pageable pageable) { + List categories = categoryRepository.findAll(pageable).stream() + .map(ReadCategoryResponse::fromEntity) + .toList(); + return new ReadAllCategoriesResponse(categories); + } + + public ReadCategoryResponse readCategory(Long id) { + Category category = categoryRepository.findById(id) + .orElseThrow(() -> new IllegalArgumentException(id + "에 해당하는 카테고리가 없습니다.")); + return ReadCategoryResponse.fromEntity(category); + } + + @Transactional + public UpdateCategoryResponse updateCategory(Long id, UpdateCategoryRequest request) { + Category category = categoryRepository.findById(id) + .orElseThrow(() -> new IllegalArgumentException(id + "에 해당하는 카테고리가 없습니다.")); + category.update(request.toEntity()); + return UpdateCategoryResponse.fromEntity(category); + } + + @Transactional + public void deleteCategory(Long id) { + categoryRepository.deleteById(id); + } +} diff --git a/src/main/java/gift/service/MemberDetailsService.java b/src/main/java/gift/service/MemberDetailsService.java new file mode 100644 index 000000000..9ba39a70f --- /dev/null +++ b/src/main/java/gift/service/MemberDetailsService.java @@ -0,0 +1,22 @@ +package gift.service; + +import gift.repository.MemberRepository; +import gift.web.dto.MemberDetails; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@Transactional(readOnly = true) +public class MemberDetailsService { + + private final MemberRepository memberRepository; + + public MemberDetailsService(MemberRepository memberRepository) { + this.memberRepository = memberRepository; + } + + public MemberDetails loadUserById(Long id) { + return MemberDetails.from(memberRepository.findById(id) + .orElseThrow(() -> new IllegalArgumentException("회원을 찾을 수 없습니다. id: " + id))); + } +} diff --git a/src/main/java/gift/service/MemberService.java b/src/main/java/gift/service/MemberService.java new file mode 100644 index 000000000..a2aef65cd --- /dev/null +++ b/src/main/java/gift/service/MemberService.java @@ -0,0 +1,64 @@ +package gift.service; + +import gift.authentication.token.JwtProvider; +import gift.authentication.token.Token; +import gift.domain.Member; +import gift.domain.vo.Email; +import gift.repository.MemberRepository; +import gift.repository.WishProductRepository; +import gift.web.dto.request.LoginRequest; +import gift.web.dto.request.member.CreateMemberRequest; +import gift.web.dto.response.LoginResponse; +import gift.web.dto.response.member.CreateMemberResponse; +import gift.web.dto.response.member.ReadMemberResponse; +import gift.web.validation.exception.client.IncorrectEmailException; +import java.util.NoSuchElementException; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@Transactional(readOnly = true) +public class MemberService { + + private final MemberRepository memberRepository; + private final WishProductRepository wishProductRepository; + private final JwtProvider jwtProvider; + + public MemberService(MemberRepository memberRepository, + WishProductRepository wishProductRepository, + JwtProvider jwtProvider) { + this.memberRepository = memberRepository; + this.wishProductRepository = wishProductRepository; + this.jwtProvider = jwtProvider; + } + + @Transactional + public CreateMemberResponse createMember(CreateMemberRequest request) { + Member member = request.toEntity(); + return CreateMemberResponse.fromEntity(memberRepository.save(member)); + } + + public ReadMemberResponse readMember(Long id) { + Member member = memberRepository.findById(id) + .orElseThrow(() -> new IllegalArgumentException("회원을 찾을 수 없습니다. id: " + id)); + + return ReadMemberResponse.fromEntity(member); + } + + public void deleteMember(Long id) { + Member member = memberRepository.findById(id).orElseThrow(NoSuchElementException::new); + wishProductRepository.deleteAllByMemberId(id); + memberRepository.delete(member); + } + + public LoginResponse login(LoginRequest request) { + Email email = Email.from(request.getEmail()); + Member member = memberRepository.findByEmail(email).orElseThrow(() -> new IncorrectEmailException(email.getValue())); + + member.matchPassword(request.getPassword()); + + Token token = jwtProvider.generateToken(member); + + return new LoginResponse(token); + } +} diff --git a/src/main/java/gift/service/ProductOptionService.java b/src/main/java/gift/service/ProductOptionService.java new file mode 100644 index 000000000..9663b89a5 --- /dev/null +++ b/src/main/java/gift/service/ProductOptionService.java @@ -0,0 +1,146 @@ +package gift.service; + +import gift.domain.ProductOption; +import gift.repository.ProductOptionRepository; +import gift.web.dto.request.productoption.CreateProductOptionRequest; +import gift.web.dto.request.productoption.SubtractProductOptionQuantityRequest; +import gift.web.dto.request.productoption.UpdateProductOptionRequest; +import gift.web.dto.response.productoption.CreateProductOptionResponse; +import gift.web.dto.response.productoption.ReadAllProductOptionsResponse; +import gift.web.dto.response.productoption.ReadProductOptionResponse; +import gift.web.dto.response.productoption.SubtractProductOptionQuantityResponse; +import gift.web.dto.response.productoption.UpdateProductOptionResponse; +import gift.web.validation.exception.client.AlreadyExistsException; +import gift.web.validation.exception.client.ResourceNotFoundException; +import java.util.List; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@Transactional(readOnly = true) +public class ProductOptionService { + + private final ProductOptionRepository productOptionRepository; + + public ProductOptionService(ProductOptionRepository productOptionRepository) { + this.productOptionRepository = productOptionRepository; + } + + @Transactional + public CreateProductOptionResponse createOption(Long productId, CreateProductOptionRequest request) { + String optionName = request.getName(); + validateOptionNameExists(productId, optionName); + + ProductOption createdOption = productOptionRepository.save(request.toEntity(productId)); + + return CreateProductOptionResponse.fromEntity(createdOption); + } + + /** + * 상품 옵션 이름이 이미 존재하는지 확인합니다.
+ * 이미 존재한다면 {@link AlreadyExistsException} 을 발생시킵니다. + * @param productId 상품 아이디 + * @param name 상품 옵션 이름 + */ + private void validateOptionNameExists(Long productId, String name) { + validateOptionNameExists(null, productId, name); + } + + /** + * 상품 옵션 이름이 이미 존재하는지 확인합니다.
+ * 이미 존재한다면 {@link AlreadyExistsException} 을 발생시킵니다. + * @param optionId 현재 상품 옵션 아이디 (옵션 새로 생성 시 null) + * @param productId 상품 아이디 + * @param name 상품 옵션 이름 + */ + private void validateOptionNameExists(Long optionId, Long productId, String name) { + if(optionId == null) { + optionId = -1L; + } + productOptionRepository.findDuplicatedProductOption(optionId, productId, name) + .ifPresent(duplicatedOptionId -> { + throw new AlreadyExistsException(name); + }); + } + + /** + * 해당 상품에 옵션이 존재하지 않는 경우 최초 옵션 등록을 위해 사용됩니다. + * @param productId 상품 아이디 + * @param request 상품 옵션 생성 요청 + * @return 상품 옵션 생성 응답 + */ + public List createInitialOptions(Long productId, List request) { + List productOptions = request.stream() + .map(productOption -> productOption.toEntity(productId)) + .toList(); + validateDuplicateOptionNames(productOptions); + + List createdOptions = productOptionRepository.saveAll(productOptions); + + return createdOptions.stream() + .map(CreateProductOptionResponse::fromEntity) + .toList(); + } + + /** + * 상품 옵션 이름에 중복이 존재하는지 검열합니다
+ * 중복이 존재한다면 {@link IllegalStateException}을 발생시킵니다. + * @param productOptions 상품 옵션 리스트 + */ + private void validateDuplicateOptionNames(List productOptions) { + long originalCount = productOptions.size(); + long distinctCount = productOptions.stream() + .map(ProductOption::getName) + .distinct() + .count(); + + if (originalCount != distinctCount) { + throw new IllegalStateException("상품 옵션 이름에 중복이 존재합니다"); + } + } + + public ReadAllProductOptionsResponse readAllOptions(Long productId) { + List options = productOptionRepository.findAllByProductId(productId) + .stream() + .map(ReadProductOptionResponse::fromEntity) + .toList(); + return ReadAllProductOptionsResponse.from(options); + } + + @Transactional + public UpdateProductOptionResponse updateOption(Long optionId, Long productId, UpdateProductOptionRequest request) { + String optionName = request.getName(); + validateOptionNameExists(optionId, productId, optionName); + + ProductOption option = productOptionRepository.findById(optionId) + .orElseThrow(() -> new ResourceNotFoundException("상품 옵션", optionId.toString())); + + ProductOption updateParam = request.toEntity(); + option.update(updateParam); + + return UpdateProductOptionResponse.fromEntity(option); + } + + @Transactional + public SubtractProductOptionQuantityResponse subtractOptionStock(Long optionId, SubtractProductOptionQuantityRequest request) { + ProductOption option = productOptionRepository.findById(optionId) + .orElseThrow(() -> new ResourceNotFoundException("상품 옵션", optionId.toString())); + + option.subtractQuantity(request.getQuantity()); + + return SubtractProductOptionQuantityResponse.fromEntity(option); + } + + @Transactional + public void deleteOption(Long optionId) { + ProductOption option = productOptionRepository.findById(optionId) + .orElseThrow(() -> new ResourceNotFoundException("상품 옵션", optionId.toString())); + + productOptionRepository.delete(option); + } + + @Transactional + public void deleteAllOptionsByProductId(Long productId) { + productOptionRepository.deleteAllByProductId(productId); + } +} diff --git a/src/main/java/gift/service/ProductService.java b/src/main/java/gift/service/ProductService.java new file mode 100644 index 000000000..ece497258 --- /dev/null +++ b/src/main/java/gift/service/ProductService.java @@ -0,0 +1,99 @@ +package gift.service; + +import gift.converter.StringToUrlConverter; +import gift.domain.Category; +import gift.domain.Product; +import gift.repository.CategoryRepository; +import gift.repository.ProductRepository; +import gift.repository.WishProductRepository; +import gift.web.dto.request.product.CreateProductRequest; +import gift.web.dto.request.product.UpdateProductRequest; +import gift.web.dto.response.product.CreateProductResponse; +import gift.web.dto.response.product.ReadAllProductsResponse; +import gift.web.dto.response.product.ReadProductResponse; +import gift.web.dto.response.product.UpdateProductResponse; +import java.util.List; +import java.util.NoSuchElementException; +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@Transactional(readOnly = true) +public class ProductService { + + private final ProductRepository productRepository; + private final CategoryRepository categoryRepository; + private final WishProductRepository wishProductRepository; + private final ProductOptionService productOptionService; + + public ProductService(ProductRepository productRepository, + CategoryRepository categoryRepository, + WishProductRepository wishProductRepository, ProductOptionService productOptionService) { + this.productRepository = productRepository; + this.categoryRepository = categoryRepository; + this.wishProductRepository = wishProductRepository; + this.productOptionService = productOptionService; + } + + @Transactional + public CreateProductResponse createProduct(CreateProductRequest request) { + Category category = categoryRepository.findById(request.getCategoryId()) + .orElseThrow(NoSuchElementException::new); + + Product product = request.toEntity(category); + Product savedProduct = productRepository.save(product); + + productOptionService.createInitialOptions(savedProduct.getId(), request.getProductOptions()); + + return CreateProductResponse.fromEntity(savedProduct); + } + + public ReadProductResponse readProductById(Long id) { + Product product = productRepository.findById(id) + .orElseThrow(NoSuchElementException::new); + return ReadProductResponse.fromEntity(product); + } + + public ReadAllProductsResponse readProductsByCategoryId(Long categoryId, Pageable pageable) { + List products = productRepository.findByCategoryId(categoryId, pageable) + .stream() + .map(ReadProductResponse::fromEntity) + .toList(); + return ReadAllProductsResponse.from(products); + } + + public ReadAllProductsResponse readAllProducts() { + List products = productRepository.findAll() + .stream() + .map(ReadProductResponse::fromEntity) + .toList(); + return ReadAllProductsResponse.from(products); + } + + public ReadAllProductsResponse readAllProducts(Pageable pageable) { + List products = productRepository.findAll(pageable) + .stream() + .map(ReadProductResponse::fromEntity) + .toList(); + return ReadAllProductsResponse.from(products); + } + + @Transactional + public UpdateProductResponse updateProduct(Long id, UpdateProductRequest request) { + Product product = productRepository.findById(id) + .orElseThrow(NoSuchElementException::new); + + product.updateBasicInfo(request.getName(), request.getPrice(), StringToUrlConverter.convert(request.getImageUrl())); + return UpdateProductResponse.from(product); + } + + @Transactional + public void deleteProduct(Long id) { + Product product = productRepository.findById(id) + .orElseThrow(NoSuchElementException::new); + wishProductRepository.deleteAllByProductId(id); + productOptionService.deleteAllOptionsByProductId(id); + productRepository.delete(product); + } +} diff --git a/src/main/java/gift/service/WishProductService.java b/src/main/java/gift/service/WishProductService.java new file mode 100644 index 000000000..77d25ca78 --- /dev/null +++ b/src/main/java/gift/service/WishProductService.java @@ -0,0 +1,91 @@ +package gift.service; + +import gift.domain.WishProduct; +import gift.domain.WishProduct.Builder; +import gift.repository.MemberRepository; +import gift.repository.ProductRepository; +import gift.repository.WishProductRepository; +import gift.web.dto.request.wishproduct.CreateWishProductRequest; +import gift.web.dto.request.wishproduct.UpdateWishProductRequest; +import gift.web.dto.response.wishproduct.CreateWishProductResponse; +import gift.web.dto.response.wishproduct.ReadAllWishProductsResponse; +import gift.web.dto.response.wishproduct.ReadWishProductResponse; +import gift.web.dto.response.wishproduct.UpdateWishProductResponse; +import java.util.NoSuchElementException; +import java.util.Optional; +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@Transactional(readOnly = true) +public class WishProductService { + + private final ProductRepository productRepository; + private final MemberRepository memberRepository; + private final WishProductRepository wishProductRepository; + + public WishProductService(ProductRepository productRepository, + MemberRepository memberRepository, + WishProductRepository wishProductRepository) { + this.productRepository = productRepository; + this.memberRepository = memberRepository; + this.wishProductRepository = wishProductRepository; + } + + /** + * 위시 리스트에 상품을 추가합니다. + * 이미 존재하는 WishProduct인 경우 수량만 추가합니다. + * @param memberId 회원 ID + * @param request 상품 정보 + * @return 생성된 WishProduct ID + */ + @Transactional + public CreateWishProductResponse createWishProduct(Long memberId, CreateWishProductRequest request) { + + // 이미 존재하는 WishProduct인 경우 수량만 추가 + Optional existingWishProduct = wishProductRepository.findByMemberIdAndProductId( + memberId, request.getProductId()); + if (existingWishProduct.isPresent()) { + WishProduct wishProduct = existingWishProduct.get(); + wishProduct.addQuantity(request.getQuantity()); + + return CreateWishProductResponse.fromEntity(wishProduct); + } + + //새로운 위시 상품을 추가 + WishProduct wishProduct = new Builder() + .member(memberRepository.findById(memberId) + .orElseThrow(() -> new NoSuchElementException("Member not found"))) + .product(productRepository.findById(request.getProductId()) + .orElseThrow(() -> new NoSuchElementException("Product not found"))) + .quantity(request.getQuantity()).build(); + + return CreateWishProductResponse.fromEntity(wishProductRepository.save(wishProduct)); + } + + public ReadAllWishProductsResponse readAllWishProducts(Long memberId, Pageable pageable) { + return new ReadAllWishProductsResponse( + wishProductRepository.findByMemberId(memberId, pageable) + .stream() + .map(ReadWishProductResponse::fromEntity) + .toList() + ); + } + + @Transactional + public UpdateWishProductResponse updateWishProduct(Long wishProductId, UpdateWishProductRequest request) { + WishProduct wishProduct = wishProductRepository.findById(wishProductId) + .orElseThrow(NoSuchElementException::new); + wishProduct.updateQuantity(request.getQuantity()); + + return UpdateWishProductResponse.fromEntity(wishProduct); + } + + @Transactional + public void deleteWishProduct(Long wishProductId) { + WishProduct wishProduct = wishProductRepository.findById(wishProductId) + .orElseThrow(NoSuchElementException::new); + wishProductRepository.delete(wishProduct); + } +} diff --git a/src/main/java/gift/utils/JsonUtils.java b/src/main/java/gift/utils/JsonUtils.java new file mode 100644 index 000000000..ffb89076f --- /dev/null +++ b/src/main/java/gift/utils/JsonUtils.java @@ -0,0 +1,27 @@ +package gift.utils; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; + +public abstract class JsonUtils { + + private static final ObjectMapper objectMapper = new ObjectMapper() + .registerModule(new JavaTimeModule()) + .disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); + + private JsonUtils() { + } + + /** + * JSON 형식의 문자열로 반환합니다 + * + * @param object 변환 대상 + * @return JSON 형식의 문자열 + * @throws JsonProcessingException + */ + public static String toJson(Object object) throws JsonProcessingException { + return objectMapper.writeValueAsString(object); + } +} diff --git a/src/main/java/gift/utils/StringUtils.java b/src/main/java/gift/utils/StringUtils.java new file mode 100644 index 000000000..148fc8ded --- /dev/null +++ b/src/main/java/gift/utils/StringUtils.java @@ -0,0 +1,34 @@ +package gift.utils; + +import java.util.Set; +import java.util.stream.Collectors; + +public abstract class StringUtils { + + /** + * 문자열과 문자열 집합을 전달받아, 집합에 포함된 어떤 문자열이라도 입력 문자열에 포함되어 있는지 검사합니다. + * + * @param input 검사할 문자열 + * @param substrings 검사할 문자열 집합 + * @return 집합에 포함된 어떤 문자열이라도 입력 문자열에 포함되어 있으면 true, 그렇지 않으면 false + */ + public static boolean containsAnySubstring(String input, Set substrings) { + return substrings.stream().anyMatch(input::contains); + } + + /** + * 문자열과 허용할 특수문자의 목록을 받아서 문자열이 허용할 특수문자 외의 특수문자가 있는지 검사합니다. + * + * @param input 문자열 입력 + * @param allowedSpecialChars 허용할 특수문자의 목록 + * @return 허용되지 않는 특수문자가 있으면 false, 그렇지 않으면 true + */ + public static boolean containsOnlyAllowedSpecialChars(String input, Set allowedSpecialChars) { + String allowedCharsRegex = allowedSpecialChars.stream() + .map(ch -> "\\" + ch) + .collect(Collectors.joining()); + + String regex = "[a-zA-Z0-9ㄱ-ㅎ가-힣ㅏ-ㅣ\\s" + allowedCharsRegex + "]+"; + return input.matches(regex); + } +} diff --git a/src/main/java/gift/web/controller/api/CategoryApiController.java b/src/main/java/gift/web/controller/api/CategoryApiController.java new file mode 100644 index 000000000..6ee5662a6 --- /dev/null +++ b/src/main/java/gift/web/controller/api/CategoryApiController.java @@ -0,0 +1,68 @@ +package gift.web.controller.api; + +import gift.service.CategoryService; +import gift.web.dto.request.category.CreateCategoryRequest; +import gift.web.dto.request.category.UpdateCategoryRequest; +import gift.web.dto.response.category.CreateCategoryResponse; +import gift.web.dto.response.category.ReadAllCategoriesResponse; +import gift.web.dto.response.category.ReadCategoryResponse; +import gift.web.dto.response.category.UpdateCategoryResponse; +import java.net.URI; +import java.net.URISyntaxException; +import org.springframework.data.domain.Pageable; +import org.springframework.data.web.PageableDefault; +import org.springframework.http.ResponseEntity; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/api/categories") +public class CategoryApiController { + + private final CategoryService categoryService; + + public CategoryApiController(CategoryService categoryService) { + this.categoryService = categoryService; + } + + @PostMapping + public ResponseEntity createCategory(@Validated @RequestBody CreateCategoryRequest request) + throws URISyntaxException { + CreateCategoryResponse response = categoryService.createCategory(request); + + URI location = new URI("http://localhost:8080/api/categories/" + response.getId()); + return ResponseEntity.created(location).body(response); + } + + @GetMapping + public ResponseEntity readAllCategories(@PageableDefault Pageable pageable) { + ReadAllCategoriesResponse response = categoryService.readAllCategories(pageable); + return ResponseEntity.ok(response); + } + + @GetMapping("/{id}") + public ResponseEntity readCategory(@PathVariable Long id) { + ReadCategoryResponse response = categoryService.readCategory(id); + return ResponseEntity.ok(response); + } + + @PutMapping("/{id}") + public ResponseEntity updateCategory(@PathVariable Long id, @Validated @RequestBody UpdateCategoryRequest request) { + UpdateCategoryResponse response = categoryService.updateCategory(id, request); + return ResponseEntity.ok(response); + } + + @DeleteMapping("/{id}") + public ResponseEntity deleteCategory(@PathVariable Long id) { + categoryService.deleteCategory(id); + return ResponseEntity.noContent().build(); + } + +} diff --git a/src/main/java/gift/web/controller/api/MemberApiController.java b/src/main/java/gift/web/controller/api/MemberApiController.java new file mode 100644 index 000000000..f0e468347 --- /dev/null +++ b/src/main/java/gift/web/controller/api/MemberApiController.java @@ -0,0 +1,77 @@ +package gift.web.controller.api; + +import gift.authentication.annotation.LoginMember; +import gift.service.MemberService; +import gift.service.WishProductService; +import gift.web.dto.MemberDetails; +import gift.web.dto.request.LoginRequest; +import gift.web.dto.request.member.CreateMemberRequest; +import gift.web.dto.request.wishproduct.UpdateWishProductRequest; +import gift.web.dto.response.LoginResponse; +import gift.web.dto.response.member.CreateMemberResponse; +import gift.web.dto.response.wishproduct.ReadAllWishProductsResponse; +import gift.web.dto.response.wishproduct.UpdateWishProductResponse; +import java.net.URI; +import java.net.URISyntaxException; +import org.springframework.data.domain.Pageable; +import org.springframework.data.web.PageableDefault; +import org.springframework.http.ResponseEntity; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/api/members") +public class MemberApiController { + + private final MemberService memberService; + private final WishProductService wishProductService; + + public MemberApiController(MemberService memberService, WishProductService wishProductService) { + this.memberService = memberService; + this.wishProductService = wishProductService; + } + + @PostMapping("/register") + public ResponseEntity createMember( + @Validated @RequestBody CreateMemberRequest request) + throws URISyntaxException { + CreateMemberResponse response = memberService.createMember(request); + + URI location = new URI("http://localhost:8080/api/members/" + response.getId()); + return ResponseEntity.created(location).body(response); + } + + @PostMapping("/login") + public ResponseEntity login(@Validated @RequestBody LoginRequest request) { + LoginResponse response = memberService.login(request); + return ResponseEntity.ok(response); + } + + @GetMapping("/wishlist") + public ResponseEntity readWishProduct(@LoginMember MemberDetails memberDetails, @PageableDefault Pageable pageable) { + ReadAllWishProductsResponse response = wishProductService.readAllWishProducts(memberDetails.getId(), pageable); + return ResponseEntity.ok(response); + } + + @PutMapping("/wishlist/{wishProductId}") + public ResponseEntity updateWishProduct( + @PathVariable Long wishProductId, + @Validated @RequestBody UpdateWishProductRequest request) { + UpdateWishProductResponse response = wishProductService.updateWishProduct( + wishProductId, request); + return ResponseEntity.ok(response); + } + + @DeleteMapping("/wishlist/{wishProductId}") + public ResponseEntity deleteWishProduct(@PathVariable Long wishProductId) { + wishProductService.deleteWishProduct(wishProductId); + return ResponseEntity.noContent().build(); + } +} diff --git a/src/main/java/gift/web/controller/api/ProductApiController.java b/src/main/java/gift/web/controller/api/ProductApiController.java new file mode 100644 index 000000000..2e2ec7181 --- /dev/null +++ b/src/main/java/gift/web/controller/api/ProductApiController.java @@ -0,0 +1,128 @@ +package gift.web.controller.api; + +import gift.authentication.annotation.LoginMember; +import gift.service.ProductOptionService; +import gift.service.ProductService; +import gift.service.WishProductService; +import gift.web.dto.MemberDetails; +import gift.web.dto.request.product.CreateProductRequest; +import gift.web.dto.request.product.UpdateProductRequest; +import gift.web.dto.request.productoption.CreateProductOptionRequest; +import gift.web.dto.request.productoption.UpdateProductOptionRequest; +import gift.web.dto.request.wishproduct.CreateWishProductRequest; +import gift.web.dto.response.product.CreateProductResponse; +import gift.web.dto.response.product.ReadAllProductsResponse; +import gift.web.dto.response.product.ReadProductResponse; +import gift.web.dto.response.product.UpdateProductResponse; +import gift.web.dto.response.productoption.CreateProductOptionResponse; +import gift.web.dto.response.productoption.ReadAllProductOptionsResponse; +import gift.web.dto.response.productoption.UpdateProductOptionResponse; +import gift.web.dto.response.wishproduct.CreateWishProductResponse; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.NoSuchElementException; +import org.springframework.data.domain.Pageable; +import org.springframework.data.web.PageableDefault; +import org.springframework.http.ResponseEntity; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/api/products") +public class ProductApiController { + + private final ProductService productService; + private final WishProductService wishProductService; + private final ProductOptionService productOptionService; + + public ProductApiController(ProductService productService, WishProductService wishProductService, ProductOptionService productOptionService) { + this.productService = productService; + this.wishProductService = wishProductService; + this.productOptionService = productOptionService; + } + + @GetMapping + public ResponseEntity readAllProducts(@PageableDefault Pageable pageable) { + ReadAllProductsResponse response = productService.readAllProducts(pageable); + return ResponseEntity.ok(response); + } + + @PostMapping + public ResponseEntity createProduct( + @Validated @RequestBody CreateProductRequest request) throws URISyntaxException { + CreateProductResponse response = productService.createProduct(request); + + URI location = new URI("http://localhost:8080/api/products/" + response.getId()); + return ResponseEntity.created(location).body(response); + } + + @GetMapping(params = "categoryId") + public ResponseEntity readProductsByCategoryId(@PageableDefault Pageable pageable, @RequestParam Long categoryId) { + ReadAllProductsResponse response = productService.readProductsByCategoryId(categoryId, pageable); + return ResponseEntity.ok(response); + } + + @GetMapping("/{productId}") + public ResponseEntity readProduct(@PathVariable Long productId) { + ReadProductResponse response; + response = productService.readProductById(productId); + return ResponseEntity.ok(response); + } + + @PutMapping("/{productId}") + public ResponseEntity updateProduct(@PathVariable Long productId, @Validated @RequestBody UpdateProductRequest request) { + UpdateProductResponse response; + try { + response = productService.updateProduct(productId, request); + } catch (NoSuchElementException e) { + return ResponseEntity.notFound().build(); + } + return ResponseEntity.ok(response); + } + + @DeleteMapping("/{productId}") + public ResponseEntity deleteProduct(@PathVariable Long productId) { + productService.deleteProduct(productId); + return ResponseEntity.noContent().build(); + } + + @PostMapping("/wish") + public ResponseEntity createWishProduct(@Validated @RequestBody CreateWishProductRequest request, @LoginMember MemberDetails memberDetails) { + CreateWishProductResponse response = wishProductService.createWishProduct(memberDetails.getId(), request); + return ResponseEntity.ok(response); + } + + @GetMapping("/{productId}/options") + public ResponseEntity readOptions(@PathVariable Long productId) { + ReadAllProductOptionsResponse response = productOptionService.readAllOptions(productId); + return ResponseEntity.ok(response); + } + + @PostMapping("/{productId}/options") + public ResponseEntity createOption(@PathVariable Long productId, @Validated @RequestBody CreateProductOptionRequest request) { + CreateProductOptionResponse response = productOptionService.createOption(productId, request); + + URI location = URI.create("http://localhost:8080/api/products/" + productId + "/options/" + response.getId()); + return ResponseEntity.created(location).body(response); + } + + @PutMapping("/{productId}/options/{optionId}") + public ResponseEntity updateOption(@PathVariable Long productId, @PathVariable Long optionId, @Validated @RequestBody UpdateProductOptionRequest request) { + UpdateProductOptionResponse response = productOptionService.updateOption(optionId, productId, request); + return ResponseEntity.ok(response); + } + + @DeleteMapping("/{productId}/options/{optionId}") + public ResponseEntity deleteOption(@PathVariable Long optionId) { + productOptionService.deleteOption(optionId); + return ResponseEntity.noContent().build(); + } +} diff --git a/src/main/java/gift/web/controller/view/ProductViewController.java b/src/main/java/gift/web/controller/view/ProductViewController.java new file mode 100644 index 000000000..2806f7f5d --- /dev/null +++ b/src/main/java/gift/web/controller/view/ProductViewController.java @@ -0,0 +1,44 @@ +package gift.web.controller.view; + +import gift.service.ProductService; +import gift.web.dto.form.CreateProductForm; +import gift.web.dto.response.product.ReadAllProductsResponse; +import gift.web.dto.response.product.ReadProductResponse; +import java.util.List; +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; + +@Controller +@RequestMapping("/view/products") +public class ProductViewController { + + private final ProductService productService; + + public ProductViewController(ProductService productService) { + this.productService = productService; + } + + @GetMapping + public String readAdminPage(Model model) { + ReadAllProductsResponse allProductsResponse = productService.readAllProducts(); + List products = allProductsResponse.getProducts(); + model.addAttribute("products", products); + return "admin"; + } + + @GetMapping("/add") + public String addForm(Model model) { + model.addAttribute("product", new CreateProductForm()); + return "form/add-product-form"; + } + + @GetMapping("/{id}") + public String editForm(@PathVariable Long id, Model model) { + ReadProductResponse product = productService.readProductById(id); + model.addAttribute("product", product); + return "form/edit-product-form"; + } +} diff --git a/src/main/java/gift/web/dto/MemberDetails.java b/src/main/java/gift/web/dto/MemberDetails.java new file mode 100644 index 000000000..82b07bdfc --- /dev/null +++ b/src/main/java/gift/web/dto/MemberDetails.java @@ -0,0 +1,27 @@ +package gift.web.dto; + +import gift.domain.Member; +import gift.domain.vo.Email; + +public class MemberDetails { + + private Long id; + private Email email; + + public MemberDetails(Long id, Email email) { + this.id = id; + this.email = email; + } + + public static MemberDetails from(Member member) { + return new MemberDetails(member.getId(), member.getEmail()); + } + + public Long getId() { + return id; + } + + public Email getEmail() { + return email; + } +} diff --git a/src/main/java/gift/web/dto/form/CreateProductForm.java b/src/main/java/gift/web/dto/form/CreateProductForm.java new file mode 100644 index 000000000..7d5a1f6a8 --- /dev/null +++ b/src/main/java/gift/web/dto/form/CreateProductForm.java @@ -0,0 +1,23 @@ +package gift.web.dto.form; + +import java.net.URL; + +public class CreateProductForm { + + private String name; + private Integer price; + private URL imageUrl; + + public String getName() { + return name; + } + + public Integer getPrice() { + return price; + } + + public URL getImageUrl() { + return imageUrl; + } + +} diff --git a/src/main/java/gift/web/dto/form/UpdateProductForm.java b/src/main/java/gift/web/dto/form/UpdateProductForm.java new file mode 100644 index 000000000..553a9fd29 --- /dev/null +++ b/src/main/java/gift/web/dto/form/UpdateProductForm.java @@ -0,0 +1,40 @@ +package gift.web.dto.form; + +import gift.domain.Product; +import java.net.URL; + +public class UpdateProductForm { + + private final Long id; + private final String name; + private final Integer price; + private final URL imageUrl; + + private UpdateProductForm(Long id, String name, Integer price, URL imageUrl) { + this.id = id; + this.name = name; + this.price = price; + this.imageUrl = imageUrl; + } + + public static UpdateProductForm fromEntity(Product product) { + return new UpdateProductForm(product.getId(), product.getName(), product.getPrice(), product.getImageUrl()); + } + + public Long getId() { + return id; + } + + public String getName() { + return name; + } + + public Integer getPrice() { + return price; + } + + public URL getImageUrl() { + return imageUrl; + } + +} diff --git a/src/main/java/gift/web/dto/request/LoginRequest.java b/src/main/java/gift/web/dto/request/LoginRequest.java new file mode 100644 index 000000000..7a152dbc1 --- /dev/null +++ b/src/main/java/gift/web/dto/request/LoginRequest.java @@ -0,0 +1,23 @@ +package gift.web.dto.request; + +import jakarta.validation.constraints.Email; + +public class LoginRequest { + + @Email + private final String email; + private final String password; + + public LoginRequest(String email, String password) { + this.email = email; + this.password = password; + } + + public String getEmail() { + return email; + } + + public String getPassword() { + return password; + } +} diff --git a/src/main/java/gift/web/dto/request/category/CreateCategoryRequest.java b/src/main/java/gift/web/dto/request/category/CreateCategoryRequest.java new file mode 100644 index 000000000..704ad4c35 --- /dev/null +++ b/src/main/java/gift/web/dto/request/category/CreateCategoryRequest.java @@ -0,0 +1,60 @@ +package gift.web.dto.request.category; + +import gift.converter.StringToUrlConverter; +import gift.domain.Category; +import gift.domain.vo.Color; +import gift.web.validation.constraints.HexColor; +import gift.web.validation.constraints.SpecialCharacter; +import jakarta.validation.constraints.NotBlank; +import org.hibernate.validator.constraints.Length; +import org.hibernate.validator.constraints.URL; + +public class CreateCategoryRequest { + + @NotBlank + @Length(min = 1, max = 15) + @SpecialCharacter(allowed = "(, ), [, ], +, -, &, /, _") + private String name; + + @NotBlank + private String description; + + @URL + private String imageUrl; + + @NotBlank + @HexColor + private String color; + + public CreateCategoryRequest(String name, String description, String imageUrl, String color) { + this.name = name; + this.description = description; + this.imageUrl = imageUrl; + this.color = color; + } + + public String getName() { + return name; + } + + public String getDescription() { + return description; + } + + public String getImageUrl() { + return imageUrl; + } + + public String getColor() { + return color; + } + + public Category toEntity() { + return new Category.Builder() + .name(this.name) + .description(this.description) + .imageUrl(StringToUrlConverter.convert(this.imageUrl)) + .color(Color.from(this.color)) + .build(); + } +} diff --git a/src/main/java/gift/web/dto/request/category/UpdateCategoryRequest.java b/src/main/java/gift/web/dto/request/category/UpdateCategoryRequest.java new file mode 100644 index 000000000..c68f2a655 --- /dev/null +++ b/src/main/java/gift/web/dto/request/category/UpdateCategoryRequest.java @@ -0,0 +1,61 @@ +package gift.web.dto.request.category; + +import gift.converter.StringToUrlConverter; +import gift.domain.Category; +import gift.domain.vo.Color; +import gift.web.validation.constraints.HexColor; +import gift.web.validation.constraints.SpecialCharacter; +import jakarta.validation.constraints.NotBlank; +import org.hibernate.validator.constraints.Length; +import org.hibernate.validator.constraints.URL; + +public class UpdateCategoryRequest { + + @NotBlank + @Length(min = 1, max = 15) + @SpecialCharacter(allowed = "(, ), [, ], +, -, &, /, _") + private String name; + + @NotBlank + private String description; + + @URL + private String imageUrl; + + @NotBlank + @HexColor + private String color; + + public UpdateCategoryRequest(String name, String description, String imageUrl, String color) { + this.name = name; + this.description = description; + this.imageUrl = imageUrl; + this.color = color; + } + + public String getName() { + return name; + } + + public String getDescription() { + return description; + } + + public String getImageUrl() { + return imageUrl; + } + + public String getColor() { + return color; + } + + public Category toEntity() { + return new Category.Builder() + .name(this.name) + .description(this.description) + .imageUrl(StringToUrlConverter.convert(this.imageUrl)) + .color(Color.from(this.color)) + .build(); + } + +} diff --git a/src/main/java/gift/web/dto/request/member/CreateMemberRequest.java b/src/main/java/gift/web/dto/request/member/CreateMemberRequest.java new file mode 100644 index 000000000..6c7d053ba --- /dev/null +++ b/src/main/java/gift/web/dto/request/member/CreateMemberRequest.java @@ -0,0 +1,42 @@ +package gift.web.dto.request.member; + +import gift.domain.Member; +import gift.web.validation.constraints.Password; +import jakarta.validation.constraints.Email; + +public class CreateMemberRequest { + + @Email + private String email; + + @Password + private String password; + + private String name; + + public CreateMemberRequest(String email, String password, String name) { + this.email = email; + this.password = password; + this.name = name; + } + + public String getEmail() { + return email; + } + + public String getPassword() { + return password; + } + + public String getName() { + return name; + } + + public Member toEntity() { + return new Member.Builder() + .email(gift.domain.vo.Email.from(this.email)) + .password(gift.domain.vo.Password.from(this.password)) + .name(this.name) + .build(); + } +} diff --git a/src/main/java/gift/web/dto/request/product/CreateProductRequest.java b/src/main/java/gift/web/dto/request/product/CreateProductRequest.java new file mode 100644 index 000000000..a463f3d7d --- /dev/null +++ b/src/main/java/gift/web/dto/request/product/CreateProductRequest.java @@ -0,0 +1,81 @@ +package gift.web.dto.request.product; + +import gift.converter.StringToUrlConverter; +import gift.domain.Category; +import gift.domain.Product; +import gift.domain.ProductOption; +import gift.web.dto.request.productoption.CreateProductOptionRequest; +import gift.web.validation.constraints.RequiredKakaoApproval; +import gift.web.validation.constraints.SpecialCharacter; +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import java.util.List; +import org.hibernate.validator.constraints.Length; +import org.hibernate.validator.constraints.Range; +import org.hibernate.validator.constraints.URL; + +public class CreateProductRequest { + + @NotBlank + @Length(min = 1, max = 15) + @SpecialCharacter(allowed = "(, ), [, ], +, -, &, /, _") + @RequiredKakaoApproval + private final String name; + + @Range(min = 1000, max = 10000000) + private final Integer price; + + @URL + private final String imageUrl; + + @NotNull + private final Long categoryId; + + @Valid + @NotEmpty + private final List productOptions; + + public CreateProductRequest(String name, Integer price, String imageUrl, Long categoryId, List productOptions) { + this.name = name; + this.price = price; + this.imageUrl = imageUrl; + this.categoryId = categoryId; + this.productOptions = productOptions; + } + + public String getName() { + return name; + } + + public Integer getPrice() { + return price; + } + + public String getImageUrl() { + return imageUrl; + } + + public Long getCategoryId() { + return categoryId; + } + + public List getProductOptions() { + return productOptions; + } + + public Product toEntity(Category category) { + List productOptions = this.productOptions.stream() + .map(CreateProductOptionRequest::toEntity) + .toList(); + + return new Product.Builder() + .name(this.name) + .price(this.price) + .imageUrl(StringToUrlConverter.convert(this.imageUrl)) + .category(category) + .productOptions(productOptions) + .build(); + } +} diff --git a/src/main/java/gift/web/dto/request/product/UpdateProductRequest.java b/src/main/java/gift/web/dto/request/product/UpdateProductRequest.java new file mode 100644 index 000000000..d74ee114d --- /dev/null +++ b/src/main/java/gift/web/dto/request/product/UpdateProductRequest.java @@ -0,0 +1,50 @@ +package gift.web.dto.request.product; + +import gift.converter.StringToUrlConverter; +import gift.domain.Product; +import gift.web.validation.constraints.RequiredKakaoApproval; +import gift.web.validation.constraints.SpecialCharacter; +import jakarta.validation.constraints.NotBlank; +import org.hibernate.validator.constraints.Length; +import org.hibernate.validator.constraints.Range; +import org.hibernate.validator.constraints.URL; + +public class UpdateProductRequest { + + @NotBlank + @Length(min = 1, max = 15) + @SpecialCharacter(allowed = "(, ), [, ], +, -, &, /, _") + @RequiredKakaoApproval + private final String name; + @Range(min = 1000, max = 10000000) + private final Integer price; + + @URL + private final String imageUrl; + + public UpdateProductRequest(String name, Integer price, String imageUrl) { + this.name = name; + this.price = price; + this.imageUrl = imageUrl; + } + + public Product toEntity() { + return new Product.Builder() + .name(name) + .price(price) + .imageUrl(StringToUrlConverter.convert(imageUrl)) + .build(); + } + + public String getName() { + return name; + } + + public Integer getPrice() { + return price; + } + + public String getImageUrl() { + return imageUrl; + } +} diff --git a/src/main/java/gift/web/dto/request/productoption/CreateProductOptionRequest.java b/src/main/java/gift/web/dto/request/productoption/CreateProductOptionRequest.java new file mode 100644 index 000000000..43c33b6e2 --- /dev/null +++ b/src/main/java/gift/web/dto/request/productoption/CreateProductOptionRequest.java @@ -0,0 +1,47 @@ +package gift.web.dto.request.productoption; + +import gift.domain.ProductOption; +import gift.web.validation.constraints.SpecialCharacter; +import jakarta.validation.constraints.NotBlank; +import org.hibernate.validator.constraints.Length; +import org.hibernate.validator.constraints.Range; + +public class CreateProductOptionRequest { + + @NotBlank + @Length(min = 1, max = 50) + @SpecialCharacter(allowed = "(, ), [, ], +, -, &, /, _") + private String name; + + @Range(min = 1, max = 100_000_000) + private Integer stock; + + public CreateProductOptionRequest(String name, Integer stock) { + this.name = name; + this.stock = stock; + } + + public ProductOption toEntity() { + return new ProductOption.Builder() + .name(this.name) + .stock(this.stock) + .build(); + } + + public ProductOption toEntity(Long productId) { + return new ProductOption.Builder() + .name(this.name) + .stock(this.stock) + .productId(productId) + .build(); + } + + public String getName() { + return name; + } + + public Integer getStock() { + return stock; + } + +} diff --git a/src/main/java/gift/web/dto/request/productoption/SubtractProductOptionQuantityRequest.java b/src/main/java/gift/web/dto/request/productoption/SubtractProductOptionQuantityRequest.java new file mode 100644 index 000000000..efd9bb113 --- /dev/null +++ b/src/main/java/gift/web/dto/request/productoption/SubtractProductOptionQuantityRequest.java @@ -0,0 +1,14 @@ +package gift.web.dto.request.productoption; + +public class SubtractProductOptionQuantityRequest { + + private final Integer quantity; + + public SubtractProductOptionQuantityRequest(Integer quantity) { + this.quantity = quantity; + } + + public Integer getQuantity() { + return quantity; + } +} diff --git a/src/main/java/gift/web/dto/request/productoption/UpdateProductOptionRequest.java b/src/main/java/gift/web/dto/request/productoption/UpdateProductOptionRequest.java new file mode 100644 index 000000000..55907e08d --- /dev/null +++ b/src/main/java/gift/web/dto/request/productoption/UpdateProductOptionRequest.java @@ -0,0 +1,39 @@ +package gift.web.dto.request.productoption; + +import gift.domain.ProductOption; +import gift.web.validation.constraints.SpecialCharacter; +import jakarta.validation.constraints.NotBlank; +import org.hibernate.validator.constraints.Length; +import org.hibernate.validator.constraints.Range; + +public class UpdateProductOptionRequest { + + @NotBlank + @Length(min = 1, max = 50) + @SpecialCharacter(allowed = "(, ), [, ], +, -, &, /, _") + private String name; + + @Range(min = 1, max = 100_000_000) + private Integer stock; + + public UpdateProductOptionRequest(String name, Integer stock) { + this.name = name; + this.stock = stock; + } + + public ProductOption toEntity() { + return new ProductOption.Builder() + .name(name) + .stock(stock) + .build(); + } + + public String getName() { + return name; + } + + public Integer getStock() { + return stock; + } + +} diff --git a/src/main/java/gift/web/dto/request/wishproduct/CreateWishProductRequest.java b/src/main/java/gift/web/dto/request/wishproduct/CreateWishProductRequest.java new file mode 100644 index 000000000..f7e48f865 --- /dev/null +++ b/src/main/java/gift/web/dto/request/wishproduct/CreateWishProductRequest.java @@ -0,0 +1,25 @@ +package gift.web.dto.request.wishproduct; + +import jakarta.validation.constraints.Min; +import jakarta.validation.constraints.NotNull; + +public class CreateWishProductRequest { + + @NotNull + private Long productId; + @Min(1) + private Integer quantity; + + public CreateWishProductRequest(Long productId, Integer quantity) { + this.productId = productId; + this.quantity = quantity; + } + + public Long getProductId() { + return productId; + } + + public Integer getQuantity() { + return quantity; + } +} diff --git a/src/main/java/gift/web/dto/request/wishproduct/UpdateWishProductRequest.java b/src/main/java/gift/web/dto/request/wishproduct/UpdateWishProductRequest.java new file mode 100644 index 000000000..28678565a --- /dev/null +++ b/src/main/java/gift/web/dto/request/wishproduct/UpdateWishProductRequest.java @@ -0,0 +1,20 @@ +package gift.web.dto.request.wishproduct; + +import jakarta.validation.constraints.Min; + +public class UpdateWishProductRequest { + + @Min(1) + private Integer quantity; + + private UpdateWishProductRequest() { + } + + public UpdateWishProductRequest(Integer quantity) { + this.quantity = quantity; + } + + public Integer getQuantity() { + return quantity; + } +} diff --git a/src/main/java/gift/web/dto/response/ErrorResponse.java b/src/main/java/gift/web/dto/response/ErrorResponse.java new file mode 100644 index 000000000..b755f460a --- /dev/null +++ b/src/main/java/gift/web/dto/response/ErrorResponse.java @@ -0,0 +1,49 @@ +package gift.web.dto.response; + +import gift.web.validation.exception.CustomException; +import gift.web.validation.exception.code.ErrorStatus; +import java.time.LocalDateTime; +import org.springframework.validation.BindingResult; + +public class ErrorResponse { + + private int code; + private String category; + private String description; + private LocalDateTime timestamp; + + public ErrorResponse(int code, String category, String description) { + this.code = code; + this.category = category; + this.description = description; + this.timestamp = LocalDateTime.now(); + } + + public static ErrorResponse from(CustomException exception) { + return new ErrorResponse(exception.getErrorStatus().getCode(), exception.getErrorStatus().getCategory().getDescription(), exception.getMessage()); + } + + public static ErrorResponse from(BindingResult bindingResult) { + return new ErrorResponse(-40010, "INVALID_PARAMETER", bindingResult.getFieldError().getDefaultMessage()); + } + + public static ErrorResponse of(ErrorStatus errorStatus, String description) { + return new ErrorResponse(errorStatus.getCode(), errorStatus.getCategory().getDescription(), description); + } + + public int getCode() { + return code; + } + + public String getCategory() { + return category; + } + + public String getDescription() { + return description; + } + + public LocalDateTime getTimestamp() { + return timestamp; + } +} diff --git a/src/main/java/gift/web/dto/response/LoginResponse.java b/src/main/java/gift/web/dto/response/LoginResponse.java new file mode 100644 index 000000000..bdb8b444a --- /dev/null +++ b/src/main/java/gift/web/dto/response/LoginResponse.java @@ -0,0 +1,19 @@ +package gift.web.dto.response; + +import gift.authentication.token.Token; + +public class LoginResponse { + + private Token token; + + private LoginResponse() { + } + + public LoginResponse(Token token) { + this.token = token; + } + + public Token getToken() { + return token; + } +} diff --git a/src/main/java/gift/web/dto/response/category/CreateCategoryResponse.java b/src/main/java/gift/web/dto/response/category/CreateCategoryResponse.java new file mode 100644 index 000000000..7421bb3bb --- /dev/null +++ b/src/main/java/gift/web/dto/response/category/CreateCategoryResponse.java @@ -0,0 +1,47 @@ +package gift.web.dto.response.category; + +import gift.domain.Category; + +public class CreateCategoryResponse { + + private final Long id; + private final String name; + private final String description; + private final String imageUrl; + private final String color; + + public CreateCategoryResponse(Long id, String name, String description, String imageUrl, + String color) { + this.id = id; + this.name = name; + this.description = description; + this.imageUrl = imageUrl; + this.color = color; + } + + public static CreateCategoryResponse fromEntity(Category category) { + return new CreateCategoryResponse( + category.getId(), category.getName(), category.getDescription(), + category.getImageUrl().toString(), category.getColor().toString()); + } + + public Long getId() { + return id; + } + + public String getName() { + return name; + } + + public String getDescription() { + return description; + } + + public String getImageUrl() { + return imageUrl; + } + + public String getColor() { + return color; + } +} diff --git a/src/main/java/gift/web/dto/response/category/ReadAllCategoriesResponse.java b/src/main/java/gift/web/dto/response/category/ReadAllCategoriesResponse.java new file mode 100644 index 000000000..9825237f3 --- /dev/null +++ b/src/main/java/gift/web/dto/response/category/ReadAllCategoriesResponse.java @@ -0,0 +1,16 @@ +package gift.web.dto.response.category; + +import java.util.List; + +public class ReadAllCategoriesResponse { + + private final List categories; + + public ReadAllCategoriesResponse(List categories) { + this.categories = categories; + } + + public List getCategories() { + return categories; + } +} diff --git a/src/main/java/gift/web/dto/response/category/ReadCategoryResponse.java b/src/main/java/gift/web/dto/response/category/ReadCategoryResponse.java new file mode 100644 index 000000000..a6736c6d9 --- /dev/null +++ b/src/main/java/gift/web/dto/response/category/ReadCategoryResponse.java @@ -0,0 +1,47 @@ +package gift.web.dto.response.category; + +import gift.domain.Category; + +public class ReadCategoryResponse { + + private final Long id; + private final String name; + private final String description; + private final String imageUrl; + private final String color; + + public ReadCategoryResponse(Long id, String name, String description, String imageUrl, + String color) { + this.id = id; + this.name = name; + this.description = description; + this.imageUrl = imageUrl; + this.color = color; + } + + public static ReadCategoryResponse fromEntity(Category category) { + return new ReadCategoryResponse( + category.getId(), category.getName(), category.getDescription(), + category.getImageUrl().toString(), category.getColor().toString()); + } + + public Long getId() { + return id; + } + + public String getName() { + return name; + } + + public String getDescription() { + return description; + } + + public String getImageUrl() { + return imageUrl; + } + + public String getColor() { + return color; + } +} diff --git a/src/main/java/gift/web/dto/response/category/UpdateCategoryResponse.java b/src/main/java/gift/web/dto/response/category/UpdateCategoryResponse.java new file mode 100644 index 000000000..30fe3c520 --- /dev/null +++ b/src/main/java/gift/web/dto/response/category/UpdateCategoryResponse.java @@ -0,0 +1,47 @@ +package gift.web.dto.response.category; + +import gift.domain.Category; + +public class UpdateCategoryResponse { + + private final Long id; + private final String name; + private final String description; + private final String imageUrl; + private final String color; + + public UpdateCategoryResponse(Long id, String name, String description, String imageUrl, + String color) { + this.id = id; + this.name = name; + this.description = description; + this.imageUrl = imageUrl; + this.color = color; + } + + public Long getId() { + return id; + } + + public String getName() { + return name; + } + + public String getDescription() { + return description; + } + + public String getImageUrl() { + return imageUrl; + } + + public String getColor() { + return color; + } + + public static UpdateCategoryResponse fromEntity(Category category) { + return new UpdateCategoryResponse( + category.getId(), category.getName(), category.getDescription(), + category.getImageUrl().toString(), category.getColor().toString()); + } +} diff --git a/src/main/java/gift/web/dto/response/member/CreateMemberResponse.java b/src/main/java/gift/web/dto/response/member/CreateMemberResponse.java new file mode 100644 index 000000000..f5cf0a81a --- /dev/null +++ b/src/main/java/gift/web/dto/response/member/CreateMemberResponse.java @@ -0,0 +1,34 @@ +package gift.web.dto.response.member; + +import gift.domain.Member; + +public class CreateMemberResponse { + + private Long id; + + private String email; + + private String name; + + public CreateMemberResponse(Long id, String email, String name) { + this.id = id; + this.email = email; + this.name = name; + } + + public Long getId() { + return id; + } + + public String getEmail() { + return email; + } + + public String getName() { + return name; + } + + public static CreateMemberResponse fromEntity(Member member) { + return new CreateMemberResponse(member.getId(), member.getEmail().getValue(), member.getName()); + } +} diff --git a/src/main/java/gift/web/dto/response/member/ReadMemberResponse.java b/src/main/java/gift/web/dto/response/member/ReadMemberResponse.java new file mode 100644 index 000000000..adfd869fd --- /dev/null +++ b/src/main/java/gift/web/dto/response/member/ReadMemberResponse.java @@ -0,0 +1,39 @@ +package gift.web.dto.response.member; + +import gift.domain.Member; + +public class ReadMemberResponse { + + private Long id; + private String email; + private String password; + private String name; + + private ReadMemberResponse(Long id, String email, String password, String name) { + this.id = id; + this.email = email; + this.password = password; + this.name = name; + } + + public static ReadMemberResponse fromEntity(Member member) { + return new ReadMemberResponse(member.getId(), member.getEmail().getValue(), member.getPassword().getValue(), + member.getName()); + } + + public Long getId() { + return id; + } + + public String getEmail() { + return email; + } + + public String getPassword() { + return password; + } + + public String getName() { + return name; + } +} diff --git a/src/main/java/gift/web/dto/response/product/CreateProductResponse.java b/src/main/java/gift/web/dto/response/product/CreateProductResponse.java new file mode 100644 index 000000000..356b495d3 --- /dev/null +++ b/src/main/java/gift/web/dto/response/product/CreateProductResponse.java @@ -0,0 +1,52 @@ +package gift.web.dto.response.product; + +import gift.domain.Product; +import gift.domain.ProductOption; +import java.util.List; + +public class CreateProductResponse { + + private final Long id; + + private final String name; + + private final Integer price; + + private final String imageUrl; + + private final List options; + + public CreateProductResponse(Long id, String name, Integer price, String imageUrl, + List options) { + this.id = id; + this.name = name; + this.price = price; + this.imageUrl = imageUrl; + this.options = options; + } + + public Long getId() { + return id; + } + + public String getName() { + return name; + } + + public Integer getPrice() { + return price; + } + + public String getImageUrl() { + return imageUrl; + } + + public List getOptions() { + return options; + } + + public static CreateProductResponse fromEntity(Product product) { + return new CreateProductResponse(product.getId(), product.getName(), product.getPrice(), + product.getImageUrl().toString(), product.getProductOptions()); + } +} diff --git a/src/main/java/gift/web/dto/response/product/ReadAllProductsResponse.java b/src/main/java/gift/web/dto/response/product/ReadAllProductsResponse.java new file mode 100644 index 000000000..3cee01800 --- /dev/null +++ b/src/main/java/gift/web/dto/response/product/ReadAllProductsResponse.java @@ -0,0 +1,20 @@ +package gift.web.dto.response.product; + +import java.util.List; + +public class ReadAllProductsResponse { + + private List products; + + private ReadAllProductsResponse(List products) { + this.products = products; + } + + public static ReadAllProductsResponse from(List products) { + return new ReadAllProductsResponse(products); + } + + public List getProducts() { + return products; + } +} diff --git a/src/main/java/gift/web/dto/response/product/ReadProductResponse.java b/src/main/java/gift/web/dto/response/product/ReadProductResponse.java new file mode 100644 index 000000000..48ba51892 --- /dev/null +++ b/src/main/java/gift/web/dto/response/product/ReadProductResponse.java @@ -0,0 +1,47 @@ +package gift.web.dto.response.product; + +import gift.domain.Product; +import gift.web.dto.response.category.ReadCategoryResponse; + +public class ReadProductResponse { + + private final Long id; + private final String name; + private final Integer price; + private final String imageUrl; + private final ReadCategoryResponse category; + + private ReadProductResponse(Long id, String name, Integer price, String imageUrl, + ReadCategoryResponse category) { + this.id = id; + this.name = name; + this.price = price; + this.imageUrl = imageUrl; + this.category = category; + } + + public static ReadProductResponse fromEntity(Product product) { + return new ReadProductResponse(product.getId(), product.getName(), product.getPrice(), product.getImageUrl().toString(), ReadCategoryResponse.fromEntity(product.getCategory())); + } + + public Long getId() { + return id; + } + + public String getName() { + return name; + } + + public Integer getPrice() { + return price; + } + + public String getImageUrl() { + return imageUrl; + } + + public ReadCategoryResponse getCategory() { + return category; + } + +} diff --git a/src/main/java/gift/web/dto/response/product/UpdateProductResponse.java b/src/main/java/gift/web/dto/response/product/UpdateProductResponse.java new file mode 100644 index 000000000..d4f66fb3f --- /dev/null +++ b/src/main/java/gift/web/dto/response/product/UpdateProductResponse.java @@ -0,0 +1,47 @@ +package gift.web.dto.response.product; + +import gift.domain.Product; +import gift.web.dto.response.category.ReadCategoryResponse; + +public class UpdateProductResponse { + + private final Long id; + private final String name; + private final Integer price; + private final String imageUrl; + private final ReadCategoryResponse category; + + private UpdateProductResponse(Long id, String name, Integer price, String imageUrl, + ReadCategoryResponse category) { + this.id = id; + this.name = name; + this.price = price; + this.imageUrl = imageUrl; + this.category = category; + } + + public static UpdateProductResponse from(Product product) { + return new UpdateProductResponse(product.getId(), product.getName(), product.getPrice(), + product.getImageUrl().toString(), ReadCategoryResponse.fromEntity(product.getCategory())); + } + + public Long getId() { + return id; + } + + public String getName() { + return name; + } + + public Integer getPrice() { + return price; + } + + public String getImageUrl() { + return imageUrl; + } + + public ReadCategoryResponse getCategory() { + return category; + } +} diff --git a/src/main/java/gift/web/dto/response/productoption/CreateProductOptionResponse.java b/src/main/java/gift/web/dto/response/productoption/CreateProductOptionResponse.java new file mode 100644 index 000000000..3a0b596f7 --- /dev/null +++ b/src/main/java/gift/web/dto/response/productoption/CreateProductOptionResponse.java @@ -0,0 +1,37 @@ +package gift.web.dto.response.productoption; + +import gift.domain.ProductOption; + +public class CreateProductOptionResponse { + + private final Long id; + private final String name; + private final Integer stock; + + public CreateProductOptionResponse(Long id, String name, Integer stock) { + this.id = id; + this.name = name; + this.stock = stock; + } + + public static CreateProductOptionResponse fromEntity(ProductOption productOption) { + return new CreateProductOptionResponse( + productOption.getId(), + productOption.getName(), + productOption.getStock() + ); + } + + public Long getId() { + return id; + } + + public String getName() { + return name; + } + + public Integer getStock() { + return stock; + } + +} diff --git a/src/main/java/gift/web/dto/response/productoption/ReadAllProductOptionsResponse.java b/src/main/java/gift/web/dto/response/productoption/ReadAllProductOptionsResponse.java new file mode 100644 index 000000000..baee4bdb5 --- /dev/null +++ b/src/main/java/gift/web/dto/response/productoption/ReadAllProductOptionsResponse.java @@ -0,0 +1,20 @@ +package gift.web.dto.response.productoption; + +import java.util.List; + +public class ReadAllProductOptionsResponse { + + List options; + + public ReadAllProductOptionsResponse(List options) { + this.options = options; + } + + public static ReadAllProductOptionsResponse from(List options) { + return new ReadAllProductOptionsResponse(options); + } + + public List getOptions() { + return options; + } +} diff --git a/src/main/java/gift/web/dto/response/productoption/ReadProductOptionResponse.java b/src/main/java/gift/web/dto/response/productoption/ReadProductOptionResponse.java new file mode 100644 index 000000000..2d056b5ea --- /dev/null +++ b/src/main/java/gift/web/dto/response/productoption/ReadProductOptionResponse.java @@ -0,0 +1,34 @@ +package gift.web.dto.response.productoption; + +import gift.domain.ProductOption; + +public class ReadProductOptionResponse { + + private final Long id; + + private final String name; + + private final Integer stock; + + public ReadProductOptionResponse(Long id, String name, Integer stock) { + this.id = id; + this.name = name; + this.stock = stock; + } + + public static ReadProductOptionResponse fromEntity(ProductOption productOption) { + return new ReadProductOptionResponse(productOption.getId(), productOption.getName(), productOption.getStock()); + } + + public Long getId() { + return id; + } + + public String getName() { + return name; + } + + public Integer getStock() { + return stock; + } +} diff --git a/src/main/java/gift/web/dto/response/productoption/SubtractProductOptionQuantityResponse.java b/src/main/java/gift/web/dto/response/productoption/SubtractProductOptionQuantityResponse.java new file mode 100644 index 000000000..1fcb1b0d6 --- /dev/null +++ b/src/main/java/gift/web/dto/response/productoption/SubtractProductOptionQuantityResponse.java @@ -0,0 +1,34 @@ +package gift.web.dto.response.productoption; + +import gift.domain.ProductOption; + +public class SubtractProductOptionQuantityResponse { + + private final Long id; + + private final String name; + + private final Integer stock; + + public SubtractProductOptionQuantityResponse(Long id, String name, Integer stock) { + this.id = id; + this.name = name; + this.stock = stock; + } + + public static SubtractProductOptionQuantityResponse fromEntity(ProductOption productOption) { + return new SubtractProductOptionQuantityResponse(productOption.getId(), productOption.getName(), productOption.getStock()); + } + + public Long getId() { + return id; + } + + public String getName() { + return name; + } + + public Integer getStock() { + return stock; + } +} diff --git a/src/main/java/gift/web/dto/response/productoption/UpdateProductOptionResponse.java b/src/main/java/gift/web/dto/response/productoption/UpdateProductOptionResponse.java new file mode 100644 index 000000000..37087f9a4 --- /dev/null +++ b/src/main/java/gift/web/dto/response/productoption/UpdateProductOptionResponse.java @@ -0,0 +1,33 @@ +package gift.web.dto.response.productoption; + +import gift.domain.ProductOption; + +public class UpdateProductOptionResponse { + + private Long id; + private String name; + private Integer stock; + + public UpdateProductOptionResponse(Long id, String name, Integer stock) { + this.id = id; + this.name = name; + this.stock = stock; + } + + public static UpdateProductOptionResponse fromEntity(ProductOption option) { + return new UpdateProductOptionResponse(option.getId(), option.getName(), option.getStock()); + } + + public Long getId() { + return id; + } + + public String getName() { + return name; + } + + public Integer getStock() { + return stock; + } + +} diff --git a/src/main/java/gift/web/dto/response/wishproduct/CreateWishProductResponse.java b/src/main/java/gift/web/dto/response/wishproduct/CreateWishProductResponse.java new file mode 100644 index 000000000..2a577dbf6 --- /dev/null +++ b/src/main/java/gift/web/dto/response/wishproduct/CreateWishProductResponse.java @@ -0,0 +1,27 @@ +package gift.web.dto.response.wishproduct; + +import gift.domain.WishProduct; + +public class CreateWishProductResponse { + + private final Long id; + + private final Integer quantity; + + public CreateWishProductResponse(Long id, Integer quantity) { + this.id = id; + this.quantity = quantity; + } + + public static CreateWishProductResponse fromEntity(WishProduct wishProduct) { + return new CreateWishProductResponse(wishProduct.getId(), wishProduct.getQuantity()); + } + + public Long getId() { + return id; + } + + public Integer getQuantity() { + return quantity; + } +} diff --git a/src/main/java/gift/web/dto/response/wishproduct/ReadAllWishProductsResponse.java b/src/main/java/gift/web/dto/response/wishproduct/ReadAllWishProductsResponse.java new file mode 100644 index 000000000..d9d607963 --- /dev/null +++ b/src/main/java/gift/web/dto/response/wishproduct/ReadAllWishProductsResponse.java @@ -0,0 +1,19 @@ +package gift.web.dto.response.wishproduct; + +import java.util.List; + +public class ReadAllWishProductsResponse { + + private List wishlist; + + private ReadAllWishProductsResponse() { + } + + public ReadAllWishProductsResponse(List wishlist) { + this.wishlist = wishlist; + } + + public List getWishlist() { + return wishlist; + } +} diff --git a/src/main/java/gift/web/dto/response/wishproduct/ReadWishProductResponse.java b/src/main/java/gift/web/dto/response/wishproduct/ReadWishProductResponse.java new file mode 100644 index 000000000..3cb0a10dc --- /dev/null +++ b/src/main/java/gift/web/dto/response/wishproduct/ReadWishProductResponse.java @@ -0,0 +1,75 @@ +package gift.web.dto.response.wishproduct; + +import gift.domain.Product; +import gift.domain.WishProduct; +import java.util.Objects; + +public class ReadWishProductResponse { + + private final Long id; + private final Long productId; + private final String name; + private final Integer price; + private final Integer quantity; + private final String imageUrl; + + public ReadWishProductResponse(Long id, Long productId, String name, Integer price, + Integer quantity, String imageUrl) { + this.id = id; + this.productId = productId; + this.name = name; + this.price = price; + this.quantity = quantity; + this.imageUrl = imageUrl; + } + + public static ReadWishProductResponse fromEntity(WishProduct wishProduct) { + Product product = wishProduct.getProduct(); + return new ReadWishProductResponse(wishProduct.getId(), product.getId(), product.getName(), product.getPrice(), + wishProduct.getQuantity(), product.getImageUrl().toString()); + } + + public Long getId() { + return id; + } + + public Long getProductId() { + return productId; + } + + public String getName() { + return name; + } + + public Integer getPrice() { + return price; + } + + public Integer getQuantity() { + return quantity; + } + + public String getImageUrl() { + return imageUrl; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + ReadWishProductResponse that = (ReadWishProductResponse) o; + return Objects.equals(id, that.id) && Objects.equals(productId, + that.productId) && Objects.equals(name, that.name) && Objects.equals( + price, that.price) && Objects.equals(quantity, that.quantity) + && Objects.equals(imageUrl, that.imageUrl); + } + + @Override + public int hashCode() { + return Objects.hash(id, productId, name, price, quantity, imageUrl); + } +} diff --git a/src/main/java/gift/web/dto/response/wishproduct/UpdateWishProductResponse.java b/src/main/java/gift/web/dto/response/wishproduct/UpdateWishProductResponse.java new file mode 100644 index 000000000..dfd1b068c --- /dev/null +++ b/src/main/java/gift/web/dto/response/wishproduct/UpdateWishProductResponse.java @@ -0,0 +1,53 @@ +package gift.web.dto.response.wishproduct; + +import gift.domain.Product; +import gift.domain.WishProduct; + +public class UpdateWishProductResponse { + + private final Long id; + private final Long productId; + private final String name; + private final Integer price; + private final Integer quantity; + private final String imageUrl; + + public UpdateWishProductResponse(Long id, Long productId, String name, Integer price, Integer quantity, String imageUrl) { + this.id = id; + this.productId = productId; + this.name = name; + this.price = price; + this.quantity = quantity; + this.imageUrl = imageUrl; + } + + public static UpdateWishProductResponse fromEntity(WishProduct wishProduct) { + Product product = wishProduct.getProduct(); + return new UpdateWishProductResponse(wishProduct.getId(), product.getId(), product.getName(), + product.getPrice(), wishProduct.getQuantity(), product.getImageUrl().toString()); + } + + public Long getId() { + return id; + } + + public Long getProductId() { + return productId; + } + + public String getName() { + return name; + } + + public Integer getPrice() { + return price; + } + + public Integer getQuantity() { + return quantity; + } + + public String getImageUrl() { + return imageUrl; + } +} diff --git a/src/main/java/gift/web/resolver/LoginMemberArgumentResolver.java b/src/main/java/gift/web/resolver/LoginMemberArgumentResolver.java new file mode 100644 index 000000000..9935c1f56 --- /dev/null +++ b/src/main/java/gift/web/resolver/LoginMemberArgumentResolver.java @@ -0,0 +1,53 @@ +package gift.web.resolver; + +import gift.authentication.annotation.LoginMember; +import gift.authentication.token.JwtResolver; +import gift.authentication.token.Token; +import gift.service.MemberDetailsService; +import gift.web.dto.MemberDetails; +import gift.web.validation.exception.client.InvalidCredentialsException; +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; + +@Component +public class LoginMemberArgumentResolver implements HandlerMethodArgumentResolver { + + private final JwtResolver jwtResolver; + private final MemberDetailsService memberDetailsService; + private final String AUTHORIZATION_HEADER = "Authorization"; + + + public LoginMemberArgumentResolver(JwtResolver jwtResolver, MemberDetailsService memberDetailsService) { + this.jwtResolver = jwtResolver; + this.memberDetailsService = memberDetailsService; + } + + @Override + public boolean supportsParameter(MethodParameter parameter) { + boolean hasParameterAnnotation = parameter.hasParameterAnnotation(LoginMember.class); + boolean isAssignable = MemberDetails.class.isAssignableFrom(parameter.getParameterType()); + return hasParameterAnnotation && isAssignable; + } + + @Override + public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, + NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception { + + String authorization = webRequest.getHeader(AUTHORIZATION_HEADER); + Token token = Token.from(extractToken(authorization)); + + Long memberId = jwtResolver.resolveId(token) + .orElseThrow(InvalidCredentialsException::new); + + return memberDetailsService.loadUserById(memberId); + } + + private String extractToken(String Authorization) { + return Authorization.substring(7); + } + +} diff --git a/src/main/java/gift/web/validation/constraints/HexColor.java b/src/main/java/gift/web/validation/constraints/HexColor.java new file mode 100644 index 000000000..7274c42e2 --- /dev/null +++ b/src/main/java/gift/web/validation/constraints/HexColor.java @@ -0,0 +1,22 @@ +package gift.web.validation.constraints; + +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import gift.web.validation.validator.HexColorValidator; +import jakarta.validation.Constraint; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +@Target(ElementType.FIELD) +@Retention(RUNTIME) +@Constraint(validatedBy = HexColorValidator.class) +public @interface HexColor { + + String message() default "올바른 색상 코드를 입력해주세요."; + + Class[] groups() default {}; + + Class[] payload() default {}; + +} diff --git a/src/main/java/gift/web/validation/constraints/Password.java b/src/main/java/gift/web/validation/constraints/Password.java new file mode 100644 index 000000000..ed4da39db --- /dev/null +++ b/src/main/java/gift/web/validation/constraints/Password.java @@ -0,0 +1,22 @@ +package gift.web.validation.constraints; + +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import gift.web.validation.validator.PasswordValidator; +import jakarta.validation.Constraint; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +@Target(ElementType.FIELD) +@Retention(RUNTIME) +@Constraint(validatedBy = PasswordValidator.class) +public @interface Password { + + String message() default "비밀번호는 영문 대소문자, 숫자를 포함하여 8자 이상 15자 이하로 입력해주세요."; + + Class[] groups() default {}; + + Class[] payload() default {}; + +} diff --git a/src/main/java/gift/web/validation/constraints/RequiredKakaoApproval.java b/src/main/java/gift/web/validation/constraints/RequiredKakaoApproval.java new file mode 100644 index 000000000..58715136d --- /dev/null +++ b/src/main/java/gift/web/validation/constraints/RequiredKakaoApproval.java @@ -0,0 +1,22 @@ +package gift.web.validation.constraints; + +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import gift.web.validation.validator.KakaoApprovalValidator; +import jakarta.validation.Constraint; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +@Target(ElementType.FIELD) +@Retention(RUNTIME) +@Constraint(validatedBy = KakaoApprovalValidator.class) +public @interface RequiredKakaoApproval { + + String message() default "담당 MD와 협의한 경우에만 사용가능한 키워드가 존재합니다."; + + Class[] groups() default {}; + + Class[] payload() default {}; + +} diff --git a/src/main/java/gift/web/validation/constraints/SpecialCharacter.java b/src/main/java/gift/web/validation/constraints/SpecialCharacter.java new file mode 100644 index 000000000..fccf22447 --- /dev/null +++ b/src/main/java/gift/web/validation/constraints/SpecialCharacter.java @@ -0,0 +1,24 @@ +package gift.web.validation.constraints; + +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import gift.web.validation.validator.SpecialCharacterValidator; +import jakarta.validation.Constraint; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +@Target(ElementType.FIELD) +@Retention(RUNTIME) +@Constraint(validatedBy = SpecialCharacterValidator.class) +public @interface SpecialCharacter { + + String allowed(); + + String message() default "'(', ')', '[', ']', '+', '-', '&', '/', '_' 외의 특수문자는 사용할 수 없습니다."; + + Class[] groups() default {}; + + Class[] payload() default {}; + +} diff --git a/src/main/java/gift/web/validation/exception/CustomException.java b/src/main/java/gift/web/validation/exception/CustomException.java new file mode 100644 index 000000000..8f32bb4d6 --- /dev/null +++ b/src/main/java/gift/web/validation/exception/CustomException.java @@ -0,0 +1,32 @@ +package gift.web.validation.exception; + +import gift.web.validation.exception.code.ErrorStatus; + +/** + * 커스텀 예외를 정의하고자 하는 경우 상속하여야 한다. + */ +public abstract class CustomException extends RuntimeException { + + protected CustomException() { + super(); + } + + protected CustomException(String message) { + super(message); + } + + protected CustomException(String message, Throwable cause) { + super(message, cause); + } + + protected CustomException(Throwable cause) { + super(cause); + } + + /** + * 예외가 어떤 {@link ErrorStatus} 를 가지는지 반환한다. + * @return {@link ErrorStatus} + */ + public abstract ErrorStatus getErrorStatus(); + +} \ No newline at end of file diff --git a/src/main/java/gift/web/validation/exception/client/AlreadyExistsException.java b/src/main/java/gift/web/validation/exception/client/AlreadyExistsException.java new file mode 100644 index 000000000..aef6ad6e0 --- /dev/null +++ b/src/main/java/gift/web/validation/exception/client/AlreadyExistsException.java @@ -0,0 +1,28 @@ +package gift.web.validation.exception.client; + +import static gift.web.validation.exception.code.ErrorStatus.ALREADY_EXISTS; + +import gift.web.validation.exception.CustomException; +import gift.web.validation.exception.code.ErrorStatus; + +public class AlreadyExistsException extends CustomException { + + private static final String ERROR_MESSAGE = "이미 존재하는 자원입니다. 중복 값: %s"; + + public AlreadyExistsException(String resource) { + super(ERROR_MESSAGE.formatted(resource)); + } + + public AlreadyExistsException(String resource, Throwable cause) { + super(ERROR_MESSAGE.formatted(resource), cause); + } + + public AlreadyExistsException(Throwable cause) { + super(cause); + } + + @Override + public ErrorStatus getErrorStatus() { + return ALREADY_EXISTS; + } +} diff --git a/src/main/java/gift/web/validation/exception/client/BadRequestException.java b/src/main/java/gift/web/validation/exception/client/BadRequestException.java new file mode 100644 index 000000000..5b65b0f1d --- /dev/null +++ b/src/main/java/gift/web/validation/exception/client/BadRequestException.java @@ -0,0 +1,32 @@ +package gift.web.validation.exception.client; + +import static gift.web.validation.exception.code.ErrorStatus.BAD_REQUEST; + +import gift.web.validation.exception.CustomException; +import gift.web.validation.exception.code.ErrorStatus; + +/** + * 4XX 예외 + */ +public class BadRequestException extends CustomException { + + private static final String ERROR_MESSAGE = "잘못된 요청입니다. 요청 URL: %s"; + + public BadRequestException(String url) { + super(ERROR_MESSAGE.formatted(url)); + } + + public BadRequestException(String url, Throwable cause) { + super(ERROR_MESSAGE.formatted(url), cause); + } + + public BadRequestException(Throwable cause) { + super(cause); + } + + @Override + public ErrorStatus getErrorStatus() { + return BAD_REQUEST; + } + +} diff --git a/src/main/java/gift/web/validation/exception/client/IncorrectEmailException.java b/src/main/java/gift/web/validation/exception/client/IncorrectEmailException.java new file mode 100644 index 000000000..ea04b176e --- /dev/null +++ b/src/main/java/gift/web/validation/exception/client/IncorrectEmailException.java @@ -0,0 +1,29 @@ +package gift.web.validation.exception.client; + +import static gift.web.validation.exception.code.ErrorStatus.INCORRECT_EMAIL; + +import gift.web.validation.exception.CustomException; +import gift.web.validation.exception.code.ErrorStatus; + +public class IncorrectEmailException extends CustomException { + + private static final String ERROR_MESSAGE = "%s 는 잘못된 이메일입니다."; + + public IncorrectEmailException(String email) { + super(ERROR_MESSAGE.formatted(email)); + } + + public IncorrectEmailException(String email, Throwable cause) { + super(ERROR_MESSAGE.formatted(email), cause); + } + + public IncorrectEmailException(Throwable cause) { + super(cause); + } + + @Override + public ErrorStatus getErrorStatus() { + return INCORRECT_EMAIL; + } + +} diff --git a/src/main/java/gift/web/validation/exception/client/IncorrectPasswordException.java b/src/main/java/gift/web/validation/exception/client/IncorrectPasswordException.java new file mode 100644 index 000000000..b97c35370 --- /dev/null +++ b/src/main/java/gift/web/validation/exception/client/IncorrectPasswordException.java @@ -0,0 +1,33 @@ +package gift.web.validation.exception.client; + +import static gift.web.validation.exception.code.ErrorStatus.INCORRECT_PASSWORD; + +import gift.web.validation.exception.CustomException; +import gift.web.validation.exception.code.ErrorStatus; + +public class IncorrectPasswordException extends CustomException { + + private static final String ERROR_MESSAGE = "비밀번호가 일치하지 않습니다."; + + public IncorrectPasswordException() { + super(ERROR_MESSAGE); + } + + public IncorrectPasswordException(String message) { + super(message); + } + + public IncorrectPasswordException(String message, Throwable cause) { + super(message, cause); + } + + public IncorrectPasswordException(Throwable cause) { + super(cause); + } + + @Override + public ErrorStatus getErrorStatus() { + return INCORRECT_PASSWORD; + } + +} diff --git a/src/main/java/gift/web/validation/exception/client/InvalidCredentialsException.java b/src/main/java/gift/web/validation/exception/client/InvalidCredentialsException.java new file mode 100644 index 000000000..49cb645bf --- /dev/null +++ b/src/main/java/gift/web/validation/exception/client/InvalidCredentialsException.java @@ -0,0 +1,33 @@ +package gift.web.validation.exception.client; + +import static gift.web.validation.exception.code.ErrorStatus.UNAUTHORIZED_INVALID_CREDENTIALS; + +import gift.web.validation.exception.CustomException; +import gift.web.validation.exception.code.ErrorStatus; + +public class InvalidCredentialsException extends CustomException { + + private static final String ERROR_MESSAGE = "유효하지 않은 신원 정보입니다."; + + public InvalidCredentialsException() { + super(ERROR_MESSAGE); + } + + public InvalidCredentialsException(String message) { + super(message); + } + + public InvalidCredentialsException(String message, Throwable cause) { + super(message, cause); + } + + public InvalidCredentialsException(Throwable cause) { + super(cause); + } + + @Override + public ErrorStatus getErrorStatus() { + return UNAUTHORIZED_INVALID_CREDENTIALS; + } + +} diff --git a/src/main/java/gift/web/validation/exception/client/ResourceNotFoundException.java b/src/main/java/gift/web/validation/exception/client/ResourceNotFoundException.java new file mode 100644 index 000000000..665459c33 --- /dev/null +++ b/src/main/java/gift/web/validation/exception/client/ResourceNotFoundException.java @@ -0,0 +1,28 @@ +package gift.web.validation.exception.client; + +import static gift.web.validation.exception.code.ErrorStatus.NOT_FOUND; + +import gift.web.validation.exception.CustomException; +import gift.web.validation.exception.code.ErrorStatus; + +public class ResourceNotFoundException extends CustomException { + + private static final String ERROR_MESSAGE = "해당 리소스를 찾을 수 없습니다. %s: %s"; + + public ResourceNotFoundException(String type, String identifier) { + super(ERROR_MESSAGE.formatted(type, identifier)); + } + + protected ResourceNotFoundException(String type, String identifier, Throwable cause) { + super(ERROR_MESSAGE.formatted(type, identifier), cause); + } + + protected ResourceNotFoundException(Throwable cause) { + super(cause); + } + + @Override + public ErrorStatus getErrorStatus() { + return NOT_FOUND; + } +} diff --git a/src/main/java/gift/web/validation/exception/code/Category.java b/src/main/java/gift/web/validation/exception/code/Category.java new file mode 100644 index 000000000..08b2b6d7d --- /dev/null +++ b/src/main/java/gift/web/validation/exception/code/Category.java @@ -0,0 +1,20 @@ +package gift.web.validation.exception.code; + +public enum Category { + + COMMON("common"), + AUTHENTICATION("authentication"), + AUTHORIZATION("authorization"), + POLICY("policy"), + ; + + private final String description; + + Category(String description) { + this.description = description; + } + + public String getDescription() { + return description; + } +} diff --git a/src/main/java/gift/web/validation/exception/code/ErrorStatus.java b/src/main/java/gift/web/validation/exception/code/ErrorStatus.java new file mode 100644 index 000000000..d2e13612c --- /dev/null +++ b/src/main/java/gift/web/validation/exception/code/ErrorStatus.java @@ -0,0 +1,50 @@ +package gift.web.validation.exception.code; + +import org.springframework.http.HttpStatus; + +/** + * 에러 코드
+ * code - HTTP 상태 코드 + 두 자리 숫자(내부 규칙 정의) 으로 정의된다.
+ */ +public enum ErrorStatus { + + UNAUTHORIZED_INVALID_CREDENTIALS(-40100, Category.AUTHENTICATION, HttpStatus.UNAUTHORIZED), + UNAUTHORIZED_INVALID_TOKEN(-40101, Category.AUTHENTICATION, HttpStatus.UNAUTHORIZED), + + BAD_REQUEST(-40000, Category.COMMON, HttpStatus.BAD_REQUEST), + INVALID_PARAMETER(-40001, Category.COMMON, HttpStatus.BAD_REQUEST), + KAKAO_APPROVAL_NEEDED(-40002, Category.POLICY, HttpStatus.BAD_REQUEST), + SPECIAL_CHARACTER_NOT_ALLOWED(-40003, Category.POLICY, HttpStatus.BAD_REQUEST), + INVALID_PASSWORD_FORMAT(-40004, Category.POLICY, HttpStatus.BAD_REQUEST), + INCORRECT_PASSWORD(-40005, Category.AUTHENTICATION, HttpStatus.BAD_REQUEST), + INCORRECT_EMAIL(-40006, Category.AUTHENTICATION, HttpStatus.BAD_REQUEST), + ALREADY_EXISTS(-40007, Category.COMMON, HttpStatus.BAD_REQUEST), + + NOT_FOUND(-40400, Category.COMMON, HttpStatus.NOT_FOUND), + + INTERNAL_SERVER_ERROR(-50000, Category.COMMON, HttpStatus.INTERNAL_SERVER_ERROR), + ; + + private final int code; + private final Category category; + private final HttpStatus httpStatus; + + ErrorStatus(int code, Category category, HttpStatus httpStatus) { + this.code = code; + this.category = category; + this.httpStatus = httpStatus; + } + + public int getCode() { + return code; + } + + public Category getCategory() { + return category; + } + + public HttpStatus getHttpStatus() { + return httpStatus; + } + +} diff --git a/src/main/java/gift/web/validation/exception/server/InternalServerException.java b/src/main/java/gift/web/validation/exception/server/InternalServerException.java new file mode 100644 index 000000000..e227daaa9 --- /dev/null +++ b/src/main/java/gift/web/validation/exception/server/InternalServerException.java @@ -0,0 +1,33 @@ +package gift.web.validation.exception.server; + +import static gift.web.validation.exception.code.ErrorStatus.INTERNAL_SERVER_ERROR; + +import gift.web.validation.exception.CustomException; +import gift.web.validation.exception.code.ErrorStatus; + +public class InternalServerException extends CustomException { + + private static final String ERROR_MESSAGE = "서버에서 오류가 발생했습니다."; + + public InternalServerException() { + super(ERROR_MESSAGE); + } + + public InternalServerException(String message) { + super(message); + } + + public InternalServerException(String message, Throwable cause) { + super(message, cause); + } + + public InternalServerException(Throwable cause) { + super(cause); + } + + @Override + public ErrorStatus getErrorStatus() { + return INTERNAL_SERVER_ERROR; + } + +} diff --git a/src/main/java/gift/web/validation/handler/ApiExceptionHandler.java b/src/main/java/gift/web/validation/handler/ApiExceptionHandler.java new file mode 100644 index 000000000..ef955d8f6 --- /dev/null +++ b/src/main/java/gift/web/validation/handler/ApiExceptionHandler.java @@ -0,0 +1,53 @@ +package gift.web.validation.handler; + +import static gift.web.validation.exception.code.ErrorStatus.INTERNAL_SERVER_ERROR; +import static gift.web.validation.exception.code.ErrorStatus.INVALID_PARAMETER; +import static gift.web.validation.exception.code.ErrorStatus.NOT_FOUND; + +import gift.web.dto.response.ErrorResponse; +import gift.web.validation.exception.CustomException; +import java.util.NoSuchElementException; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.validation.BindingResult; +import org.springframework.web.bind.MethodArgumentNotValidException; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestControllerAdvice; +import org.springframework.web.servlet.resource.NoResourceFoundException; + +@RestControllerAdvice +public class ApiExceptionHandler { + + @ExceptionHandler(MethodArgumentNotValidException.class) + public ResponseEntity handleMethodArgumentNotValidException(MethodArgumentNotValidException exception) { + BindingResult bindingResult = exception.getBindingResult(); + ErrorResponse errorResponse = ErrorResponse.from(bindingResult); + + return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(errorResponse); + } + + @ExceptionHandler(NoSuchElementException.class) + public ResponseEntity handleNoSuchElementException(NoSuchElementException exception) { + ErrorResponse errorResponse = ErrorResponse.of(INVALID_PARAMETER, "해당 자원을 찾을 수 없습니다."); + return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(errorResponse); + } + + @ExceptionHandler(NoResourceFoundException.class) + public ResponseEntity handleNoResourceFoundException(NoResourceFoundException exception) { + ErrorResponse errorResponse = ErrorResponse.of(NOT_FOUND, exception.getMessage()); + return ResponseEntity.status(HttpStatus.NOT_FOUND).body(errorResponse); + } + + @ExceptionHandler(CustomException.class) + public ResponseEntity handleCustomException(CustomException exception) { + ErrorResponse errorResponse = ErrorResponse.from(exception); + HttpStatus httpStatus = exception.getErrorStatus().getHttpStatus(); + return ResponseEntity.status(httpStatus).body(errorResponse); + } + + @ExceptionHandler(Exception.class) + public ResponseEntity handleException(Exception exception) { + ErrorResponse errorResponse = ErrorResponse.of(INTERNAL_SERVER_ERROR, exception.getMessage()); + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(errorResponse); + } +} diff --git a/src/main/java/gift/web/validation/validator/HexColorValidator.java b/src/main/java/gift/web/validation/validator/HexColorValidator.java new file mode 100644 index 000000000..748662609 --- /dev/null +++ b/src/main/java/gift/web/validation/validator/HexColorValidator.java @@ -0,0 +1,23 @@ +package gift.web.validation.validator; + +import gift.web.validation.constraints.HexColor; +import jakarta.validation.ConstraintValidator; +import jakarta.validation.ConstraintValidatorContext; +import java.util.regex.Pattern; + +public class HexColorValidator implements ConstraintValidator { + + private Pattern pattern; + private static final String HEX_PATTERN = "^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$"; + + @Override + public void initialize(HexColor constraintAnnotation) { + pattern = Pattern.compile(HEX_PATTERN); + ConstraintValidator.super.initialize(constraintAnnotation); + } + + @Override + public boolean isValid(String value, ConstraintValidatorContext context) { + return pattern.matcher(value).matches(); + } +} diff --git a/src/main/java/gift/web/validation/validator/KakaoApprovalValidator.java b/src/main/java/gift/web/validation/validator/KakaoApprovalValidator.java new file mode 100644 index 000000000..b05932391 --- /dev/null +++ b/src/main/java/gift/web/validation/validator/KakaoApprovalValidator.java @@ -0,0 +1,22 @@ +package gift.web.validation.validator; + +import gift.utils.StringUtils; +import gift.web.validation.constraints.RequiredKakaoApproval; +import jakarta.validation.ConstraintValidator; +import jakarta.validation.ConstraintValidatorContext; +import java.util.Set; + +public class KakaoApprovalValidator implements ConstraintValidator { + + private Set requiredKakaoApprovalNames; + + @Override + public void initialize(RequiredKakaoApproval constraintAnnotation) { + requiredKakaoApprovalNames = Set.of("카카오", "kakao"); + } + + @Override + public boolean isValid(String value, ConstraintValidatorContext context) { + return !StringUtils.containsAnySubstring(value.toLowerCase(), requiredKakaoApprovalNames); + } +} diff --git a/src/main/java/gift/web/validation/validator/PasswordValidator.java b/src/main/java/gift/web/validation/validator/PasswordValidator.java new file mode 100644 index 000000000..462667474 --- /dev/null +++ b/src/main/java/gift/web/validation/validator/PasswordValidator.java @@ -0,0 +1,38 @@ +package gift.web.validation.validator; + +import gift.web.validation.constraints.Password; +import jakarta.validation.ConstraintValidator; +import jakarta.validation.ConstraintValidatorContext; + +public class PasswordValidator implements ConstraintValidator { + + private final int MIN_LENGTH = 8; + private final int MAX_LENGTH = 15; + + @Override + public void initialize(Password constraintAnnotation) { + ConstraintValidator.super.initialize(constraintAnnotation); + } + + /** + * 비밀번호가 제약조건에 맞는지 확인한다.
+ * - 길이는 8자 이상 15자 이하
+ * - 영문자와 숫자를 최소 1자 이상 포함하여야 한다. + * - 영문자와 숫자 이외의 문자는 허용하지 않는다.
+ * @param password 검증 대상 + * @param context 컨텍스트 + * + * @return 제약조건을 만족하면 true, 그렇지 않으면 false + */ + @Override + public boolean isValid(String password, ConstraintValidatorContext context) { + if (isInvalidLength(password)) { + return false; + } + return password.matches("^(?=.*[A-Za-z])(?=.*\\d)[A-Za-z\\d]+$"); + } + + private boolean isInvalidLength(String password) { + return password == null || password.length() < MIN_LENGTH || password.length() > MAX_LENGTH; + } +} diff --git a/src/main/java/gift/web/validation/validator/SpecialCharacterValidator.java b/src/main/java/gift/web/validation/validator/SpecialCharacterValidator.java new file mode 100644 index 000000000..287e758a9 --- /dev/null +++ b/src/main/java/gift/web/validation/validator/SpecialCharacterValidator.java @@ -0,0 +1,30 @@ +package gift.web.validation.validator; + +import gift.utils.StringUtils; +import gift.web.validation.constraints.SpecialCharacter; +import jakarta.validation.ConstraintValidator; +import jakarta.validation.ConstraintValidatorContext; +import java.util.Arrays; +import java.util.Set; +import java.util.stream.Collectors; + +public class SpecialCharacterValidator implements ConstraintValidator { + + private Set allowedSpecialChars; + private final String DELIMITER = ","; + + @Override + public void initialize(SpecialCharacter constraintAnnotation) { + String allowedChars = constraintAnnotation.allowed(); + allowedSpecialChars = Arrays.stream(allowedChars.split(DELIMITER)) + .map(String::trim) + .map(s -> s.charAt(0)) + .collect(Collectors.toSet()); + + } + + @Override + public boolean isValid(String value, ConstraintValidatorContext context) { + return StringUtils.containsOnlyAllowedSpecialChars(value, allowedSpecialChars); + } +} diff --git a/src/main/resources/application-local.yml b/src/main/resources/application-local.yml new file mode 100644 index 000000000..dce64893e --- /dev/null +++ b/src/main/resources/application-local.yml @@ -0,0 +1,18 @@ +spring: + config: + import: application-secret.yml + + sql: + init: + mode: always + data-locations: classpath:sql/data.sql + + jpa: + defer-datasource-initialization: true #Hibernate 초기화 후 data.sql 실행 + +jwt: + expiration: 86400000 #24시간 + +logging: + level: + gift.authentication: debug \ No newline at end of file diff --git a/src/main/resources/application-secret.yml b/src/main/resources/application-secret.yml new file mode 100644 index 000000000..e776ae408 --- /dev/null +++ b/src/main/resources/application-secret.yml @@ -0,0 +1,8 @@ +spring: + datasource: + url: jdbc:h2:tcp://localhost/~/gift + username: sa + password: + +jwt: + secretKey: db8dc50b1bf35d72218b9961ed669ef3dabbd8ddd617c235baeb8a020c66179661e6e148b8c84163af02394a1c59a0af0c4566dc17325a03c77f2027f16d9b54 diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml new file mode 100644 index 000000000..12a1e66ba --- /dev/null +++ b/src/main/resources/application.yml @@ -0,0 +1,19 @@ +spring: + application: + name: spring-gift + + jpa: + hibernate: + ddl-auto: create + properties: + hibernate: + format_sql: true + +logging: + level: + org: + hibernate: + SQL: debug + orm: + jdbc: + bind: trace \ No newline at end of file diff --git a/src/main/resources/sql/data.sql b/src/main/resources/sql/data.sql new file mode 100644 index 000000000..f596a8793 --- /dev/null +++ b/src/main/resources/sql/data.sql @@ -0,0 +1,154 @@ +INSERT INTO product(name, price, image_url, category_id, created_at, created_by, updated_at, updated_by) +VALUES + ('Product 1', 1000, 'https://via.placeholder.com/150', 1, NOW(), 1, NOW(), 1), + ('Product 2', 2000, 'https://via.placeholder.com/250', 1, NOW(), 1, NOW(), 1), + ('Product 3', 3000, 'https://via.placeholder.com/350', 1, NOW(), 1, NOW(), 1), + ('Product 4', 4000, 'https://via.placeholder.com/450', 1, NOW(), 1, NOW(), 1), + ('Product 5', 5000, 'https://via.placeholder.com/550', 1, NOW(), 1, NOW(), 1), + ('Product 6', 6000, 'https://via.placeholder.com/650', 1, NOW(), 1, NOW(), 1), + ('Product 7', 7000, 'https://via.placeholder.com/750', 1, NOW(), 1, NOW(), 1), + ('Product 8', 8000, 'https://via.placeholder.com/850', 1, NOW(), 1, NOW(), 1), + ('Product 9', 9000, 'https://via.placeholder.com/950', 1, NOW(), 1, NOW(), 1), + ('Product 10', 10000, 'https://via.placeholder.com/1050', 2, NOW(), 1, NOW(), 1), + ('Product 11', 11000, 'https://via.placeholder.com/1150', 2, NOW(), 1, NOW(), 1), + ('Product 12', 12000, 'https://via.placeholder.com/1250', 2, NOW(), 1, NOW(), 1), + ('Product 13', 13000, 'https://via.placeholder.com/1350', 2, NOW(), 1, NOW(), 1), + ('Product 14', 14000, 'https://via.placeholder.com/1450', 2, NOW(), 1, NOW(), 1), + ('Product 15', 15000, 'https://via.placeholder.com/1550', 2, NOW(), 1, NOW(), 1), + ('Product 16', 16000, 'https://via.placeholder.com/1650', 2, NOW(), 1, NOW(), 1), + ('Product 17', 17000, 'https://via.placeholder.com/1750', 2, NOW(), 1, NOW(), 1), + ('Product 18', 18000, 'https://via.placeholder.com/1850', 2, NOW(), 1, NOW(), 1), + ('Product 19', 19000, 'https://via.placeholder.com/1950', 2, NOW(), 1, NOW(), 1), + ('Product 20', 20000, 'https://via.placeholder.com/2050', 2, NOW(), 1, NOW(), 1), + ('Product 21', 21000, 'https://via.placeholder.com/2150', 2, NOW(), 1, NOW(), 1), + ('Product 22', 22000, 'https://via.placeholder.com/2250', 2, NOW(), 1, NOW(), 1), + ('Product 23', 23000, 'https://via.placeholder.com/2350', 2, NOW(), 1, NOW(), 1), + ('Product 24', 24000, 'https://via.placeholder.com/2450', 2, NOW(), 1, NOW(), 1), + ('Product 25', 25000, 'https://via.placeholder.com/2550', 2, NOW(), 1, NOW(), 1), + ('Product 26', 26000, 'https://via.placeholder.com/2650', 2, NOW(), 1, NOW(), 1), + ('Product 27', 27000, 'https://via.placeholder.com/2750', 2, NOW(), 1, NOW(), 1), + ('Product 28', 28000, 'https://via.placeholder.com/2850', 2, NOW(), 1, NOW(), 1), + ('Product 29', 29000, 'https://via.placeholder.com/2950', 2, NOW(), 1, NOW(), 1), + ('Product 30', 30000, 'https://via.placeholder.com/3050', 2, NOW(), 1, NOW(), 1), + ('Product 31', 31000, 'https://via.placeholder.com/3150', 2, NOW(), 1, NOW(), 1), + ('Product 32', 32000, 'https://via.placeholder.com/3250', 2, NOW(), 1, NOW(), 1), + ('Product 33', 33000, 'https://via.placeholder.com/3350', 2, NOW(), 1, NOW(), 1), + ('Product 34', 34000, 'https://via.placeholder.com/3450', 2, NOW(), 1, NOW(), 1), + ('Product 35', 35000, 'https://via.placeholder.com/3550', 3, NOW(), 1, NOW(), 1), + ('Product 36', 36000, 'https://via.placeholder.com/3650', 3, NOW(), 1, NOW(), 1), + ('Product 37', 37000, 'https://via.placeholder.com/3750', 3, NOW(), 1, NOW(), 1), + ('Product 38', 38000, 'https://via.placeholder.com/3850', 3, NOW(), 1, NOW(), 1), + ('Product 39', 39000, 'https://via.placeholder.com/3950', 3, NOW(), 1, NOW(), 1), + ('Product 40', 40000, 'https://via.placeholder.com/4050', 3, NOW(), 1, NOW(), 1), + ('Product 41', 41000, 'https://via.placeholder.com/4150', 3, NOW(), 1, NOW(), 1), + ('Product 42', 42000, 'https://via.placeholder.com/4250', 3, NOW(), 1, NOW(), 1), + ('Product 43', 43000, 'https://via.placeholder.com/4350', 3, NOW(), 1, NOW(), 1), + ('Product 44', 44000, 'https://via.placeholder.com/4450', 3, NOW(), 1, NOW(), 1), + ('Product 45', 45000, 'https://via.placeholder.com/4550', 3, NOW(), 1, NOW(), 1), + ('Product 46', 46000, 'https://via.placeholder.com/4650', 3, NOW(), 1, NOW(), 1), + ('Product 47', 47000, 'https://via.placeholder.com/4750', 3, NOW(), 1, NOW(), 1), + ('Product 48', 48000, 'https://via.placeholder.com/4850', 3, NOW(), 1, NOW(), 1), + ('Product 49', 49000, 'https://via.placeholder.com/4950', 3, NOW(), 1, NOW(), 1), + ('Product 50', 50000, 'https://via.placeholder.com/5050', 3, NOW(), 1, NOW(), 1); + +INSERT INTO member(name, email, password, created_at, updated_at) +VALUES + ('Member 1', 'member01@gmail.com', 'member010101', NOW(), NOW()), + ('Member 2', 'member02@gmail.com', 'member020202', NOW(), NOW()), + ('Member 3', 'member03@gmail.com', 'member030303', NOW(), NOW()); + +INSERT INTO wish_product(member_id, product_id, quantity, created_at, created_by, updated_at, updated_by) +VALUES + (1, 1, 1, NOW(), 1, NOW(), 1), + (1, 2, 1, NOW(), 1, NOW(), 1), + (1, 3, 1, NOW(), 1, NOW(), 1), + (1, 4, 1, NOW(), 1, NOW(), 1), + (1, 5, 1, NOW(), 1, NOW(), 1), + (1, 6, 1, NOW(), 1, NOW(), 1), + (1, 7, 1, NOW(), 1, NOW(), 1), + (1, 8, 1, NOW(), 1, NOW(), 1), + (1, 9, 1, NOW(), 1, NOW(), 1), + (1, 10, 1, NOW(), 1, NOW(), 1), + (1, 11, 1, NOW(), 1, NOW(), 1), + (1, 12, 1, NOW(), 1, NOW(), 1), + (1, 13, 1, NOW(), 1, NOW(), 1), + (1, 14, 1, NOW(), 1, NOW(), 1), + (1, 15, 1, NOW(), 1, NOW(), 1), + (1, 16, 1, NOW(), 1, NOW(), 1), + (1, 17, 1, NOW(), 1, NOW(), 1), + (1, 18, 1, NOW(), 1, NOW(), 1), + (1, 19, 1, NOW(), 1, NOW(), 1), + (1, 20, 1, NOW(), 1, NOW(), 1), + (1, 21, 1, NOW(), 1, NOW(), 1), + (1, 22, 1, NOW(), 1, NOW(), 1), + (1, 23, 1, NOW(), 1, NOW(), 1), + (1, 24, 1, NOW(), 1, NOW(), 1), + (1, 25, 1, NOW(), 1, NOW(), 1), + (1, 26, 1, NOW(), 1, NOW(), 1), + (1, 27, 1, NOW(), 1, NOW(), 1), + (1, 28, 1, NOW(), 1, NOW(), 1), + (1, 29, 1, NOW(), 1, NOW(), 1), + (1, 30, 1, NOW(), 1, NOW(), 1), + (1, 31, 1, NOW(), 1, NOW(), 1), + (1, 32, 1, NOW(), 1, NOW(), 1), + (1, 33, 1, NOW(), 1, NOW(), 1), + (1, 34, 1, NOW(), 1, NOW(), 1), + (1, 35, 1, NOW(), 1, NOW(), 1), + (1, 36, 1, NOW(), 1, NOW(), 1), + (1, 37, 1, NOW(), 1, NOW(), 1), + (1, 38, 1, NOW(), 1, NOW(), 1), + (1, 39, 1, NOW(), 1, NOW(), 1), + (1, 40, 1, NOW(), 1, NOW(), 1), + (1, 41, 1, NOW(), 1, NOW(), 1), + (1, 42, 1, NOW(), 1, NOW(), 1), + (1, 43, 1, NOW(), 1, NOW(), 1), + (1, 44, 1, NOW(), 1, NOW(), 1), + (1, 45, 1, NOW(), 1, NOW(), 1), + (1, 46, 1, NOW(), 1, NOW(), 1), + (1, 47, 1, NOW(), 1, NOW(), 1), + (1, 48, 1, NOW(), 1, NOW(), 1), + (1, 49, 1, NOW(), 1, NOW(), 1), + (1, 50, 1, NOW(), 1, NOW(), 1); + +INSERT INTO category(name, description, image_url, color, created_at, created_by, updated_at, updated_by) +VALUES + ('Category 1', 'Category 1 Description', 'https://via.placeholder.com/150', '#FF5733', NOW(), 1, NOW(), 1), + ('Category 2', 'Category 2 Description', 'https://via.placeholder.com/250', '#FF5733', NOW(), 1, NOW(), 1), + ('Category 3', 'Category 3 Description', 'https://via.placeholder.com/350', '#FF5733', NOW(), 1, NOW(), 1), + ('Category 4', 'Category 4 Description', 'https://via.placeholder.com/450', '#FF5733', NOW(), 1, NOW(), 1), + ('Category 5', 'Category 5 Description', 'https://via.placeholder.com/550', '#FF5733', NOW(), 1, NOW(), 1), + ('Category 6', 'Category 6 Description', 'https://via.placeholder.com/650', '#FF5733', NOW(), 1, NOW(), 1), + ('Category 7', 'Category 7 Description', 'https://via.placeholder.com/750', '#FF5733', NOW(), 1, NOW(), 1), + ('Category 8', 'Category 8 Description', 'https://via.placeholder.com/850', '#FF5733', NOW(), 1, NOW(), 1), + ('Category 9', 'Category 9 Description', 'https://via.placeholder.com/950', '#FF5733', NOW(), 1, NOW(), 1), + ('Category 10', 'Category 10 Description', 'https://via.placeholder.com/1050', '#FF5733', NOW(), 1, NOW(), 1), + ('Category 11', 'Category 11 Description', 'https://via.placeholder.com/1150', '#FF5733', NOW(), 1, NOW(), 1), + ('Category 12', 'Category 12 Description', 'https://via.placeholder.com/1250', '#FF5733', NOW(), 1, NOW(), 1), + ('Category 13', 'Category 13 Description', 'https://via.placeholder.com/1350', '#FF5733', NOW(), 1, NOW(), 1), + ('Category 14', 'Category 14 Description', 'https://via.placeholder.com/1450', '#FF5733', NOW(), 1, NOW(), 1), + ('Category 15', 'Category 15 Description', 'https://via.placeholder.com/1550', '#FF5733', NOW(), 1, NOW(), 1), + ('Category 16', 'Category 16 Description', 'https://via.placeholder.com/1650', '#FF5733', NOW(), 1, NOW(), 1), + ('Category 17', 'Category 17 Description', 'https://via.placeholder.com/1750', '#FF5733', NOW(), 1, NOW(), 1), + ('Category 18', 'Category 18 Description', 'https://via.placeholder.com/1850', '#FF5733', NOW(), 1, NOW(), 1), + ('Category 19', 'Category 19 Description', 'https://via.placeholder.com/1950', '#FF5733', NOW(), 1, NOW(), 1), + ('Category 20', 'Category 20 Description', 'https://via.placeholder.com/2050', '#FF5733', NOW(), 1, NOW(), 1), + ('Category 21', 'Category 21 Description', 'https://via.placeholder.com/2150', '#FF5733', NOW(), 1, NOW(), 1), + ('Category 22', 'Category 22 Description', 'https://via.placeholder.com/2250', '#FF5733', NOW(), 1, NOW(), 1), + ('Category 23', 'Category 23 Description', 'https://via.placeholder.com/2350', '#FF5733', NOW(), 1, NOW(), 1), + ('Category 24', 'Category 24 Description', 'https://via.placeholder.com/2450', '#FF5733', NOW(), 1, NOW(), 1), + ('Category 25', 'Category 25 Description', 'https://via.placeholder.com/2550', '#FF5733', NOW(), 1, NOW(), 1), + ('Category 26', 'Category 26 Description', 'https://via.placeholder.com/2650', '#FF5733', NOW(), 1, NOW(), 1), + ('Category 27', 'Category 27 Description', 'https://via.placeholder.com/2750', '#FF5733', NOW(), 1, NOW(), 1), + ('Category 28', 'Category 28 Description', 'https://via.placeholder.com/2850', '#FF5733', NOW(), 1, NOW(), 1), + ('Category 29', 'Category 29 Description', 'https://via.placeholder.com/2950', '#FF5733', NOW(), 1, NOW(), 1), + ('Category 30', 'Category 30 Description', 'https://via.placeholder.com/3050', '#FF5733', NOW(), 1, NOW(), 1); + +INSERT INTO product_option(name, stock, product_id, created_at, created_by, updated_at, updated_by) +VALUES + ('Option 1', 100, 1, NOW(), 1, NOW(), 1), + ('Option 2', 200, 1, NOW(), 1, NOW(), 1), + ('Option 3', 300, 1, NOW(), 1, NOW(), 1), + ('Option 4', 400, 2, NOW(), 2, NOW(), 2), + ('Option 5', 500, 2, NOW(), 2, NOW(), 2), + ('Option 6', 600, 2, NOW(), 2, NOW(), 2), + ('Option 7', 700, 2, NOW(), 2, NOW(), 2); \ No newline at end of file diff --git a/src/main/resources/sql/schema.sql b/src/main/resources/sql/schema.sql new file mode 100644 index 000000000..9eac7df36 --- /dev/null +++ b/src/main/resources/sql/schema.sql @@ -0,0 +1,25 @@ +DROP TABLE IF EXISTS Product; +CREATE TABLE product ( + product_id BIGINT AUTO_INCREMENT PRIMARY KEY, + name VARCHAR(255) NOT NULL, + price INT NOT NULL, + image_url VARCHAR(255) NOT NULL +); + +DROP TABLE IF EXISTS member; +CREATE TABLE member ( + member_id BIGINT AUTO_INCREMENT PRIMARY KEY, + name VARCHAR(255) NOT NULL, + email VARCHAR(255) NOT NULL UNIQUE, + password VARCHAR(255) NOT NULL +); + +DROP TABLE IF EXISTS wish_product; +CREATE TABLE wish_product ( + wish_product_id BIGINT AUTO_INCREMENT PRIMARY KEY, + member_id BIGINT NOT NULL, + product_id BIGINT NOT NULL, + quantity INT NOT NULL, + FOREIGN KEY (member_id) REFERENCES member(member_id), + FOREIGN KEY (product_id) REFERENCES product(product_id) +); \ No newline at end of file diff --git a/src/main/resources/static/index.html b/src/main/resources/static/index.html new file mode 100644 index 000000000..ef654ad6c --- /dev/null +++ b/src/main/resources/static/index.html @@ -0,0 +1,16 @@ + + + + + + Main Page + + + +
+

메인 화면

+
+ 관리자 페이지로 이동 +
+ + diff --git a/src/main/resources/static/js/script.js b/src/main/resources/static/js/script.js new file mode 100644 index 000000000..8738a2c8e --- /dev/null +++ b/src/main/resources/static/js/script.js @@ -0,0 +1,86 @@ +function submitEditForm(productId) { + const form = document.getElementById('editProductForm'); + const formData = new FormData(form); + + const data = {}; + formData.forEach((value, key) => { + data[key] = value; + }); + + console.log('Form data:', data); + + fetch('/api/products/' + productId, { + method: 'PUT', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify(data) + }) + .then(response => { + if (!response.ok) { + throw new Error('알 수 없는 에러가 발생했습니다! ' + response.statusText); + } + return response.json(); + }) + .then(data => { + alert('상품 수정이 완료되었습니다!'); + console.log(data); + window.location.href = '/view/products'; + }) + .catch(error => { + console.error('알 수 없는 에러가 발생했습니다! ', error); + }); +} + +function submitAddForm() { + const form = document.getElementById('addProductForm'); + const formData = new FormData(form); + + const data = {}; + formData.forEach((value, key) => { + data[key] = value; + }); + + console.log('Form data:', data); + + fetch('/api/products', { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify(data) + }) + .then(response => { + if (!response.ok) { + throw new Error('알 수 없는 에러가 발생했습니다! ' + response.statusText); + } + return response.json(); + }) + .then(data => { + alert('상품 추가가 완료되었습니다!'); + console.log(data); + window.location.href = '/view/products'; + }) + .catch(error => { + console.error('알 수 없는 에러가 발생했습니다! ', error); + }); +} + +function deleteProductById(productId) { + fetch('/api/products/' + productId, { + method: 'DELETE', + headers: { + 'Content-Type': 'application/json' + } + }) + .then(response => { + if (!response.ok) { + throw new Error('알 수 없는 에러가 발생했습니다! ' + response.statusText); + } + alert('상품 삭제가 완료되었습니다!'); + window.location.href = '/view/products'; + }) + .catch(error => { + console.error('알 수 없는 에러가 발생했습니다! ', error); + }); +} diff --git a/src/main/resources/templates/admin.html b/src/main/resources/templates/admin.html new file mode 100644 index 000000000..4e3cf3f53 --- /dev/null +++ b/src/main/resources/templates/admin.html @@ -0,0 +1,44 @@ + + + + + 관리자 페이지 + + + +
+

관리자 페이지 - 상품 관리

+ + + + + + + + + + + + + + + + + + + + +
상품ID상품명상품 가격이미지 주소Actions
1234name0https://www.google.com +
+
+ +
+ +
+
+
+ + + diff --git a/src/main/resources/templates/form/add-product-form.html b/src/main/resources/templates/form/add-product-form.html new file mode 100644 index 000000000..8ecd0ddcd --- /dev/null +++ b/src/main/resources/templates/form/add-product-form.html @@ -0,0 +1,29 @@ + + + + + 상품 등록 + + + +
+

상품 등록

+
+
+ + +
+
+ + +
+
+ + +
+ +
+
+ + + \ No newline at end of file diff --git a/src/main/resources/templates/form/edit-product-form.html b/src/main/resources/templates/form/edit-product-form.html new file mode 100644 index 000000000..846ac4b99 --- /dev/null +++ b/src/main/resources/templates/form/edit-product-form.html @@ -0,0 +1,29 @@ + + + + + 상품 수정 + + + +
+

상품 수정

+
+
+ + +
+
+ + +
+
+ + +
+ +
+
+ + + \ No newline at end of file diff --git a/src/test/java/gift/domain/ProductTest.java b/src/test/java/gift/domain/ProductTest.java new file mode 100644 index 000000000..c93b255f8 --- /dev/null +++ b/src/test/java/gift/domain/ProductTest.java @@ -0,0 +1,45 @@ +package gift.domain; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.Assertions.assertAll; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import java.util.List; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +class ProductTest { + + @Nested + @DisplayName("Product 생성자는 ") + class Describe_createProduct { + @Test + @DisplayName("상품 옵션이 없으면 예외를 발생시킨다.") + void create_product_with_no_option() { + assertThatThrownBy(() -> new Product.Builder() + .build()) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("상품 옵션은 최소 1개 이상이어야 합니다."); + } + + @Test + @DisplayName("상품 옵션이 있으면 Product를 생성한다.") + void create_product_with_option() { + //given + List productOptions = List.of(new ProductOption.Builder().build()); + + //when + Product product = new Product.Builder() + .productOptions(productOptions) + .build(); + + //then + assertAll( + () -> assertNotNull(product), + () -> assertEquals(productOptions, product.getProductOptions()) + ); + } + } +} \ No newline at end of file diff --git a/src/test/java/gift/service/CategoryServiceTest.java b/src/test/java/gift/service/CategoryServiceTest.java new file mode 100644 index 000000000..e10ab7877 --- /dev/null +++ b/src/test/java/gift/service/CategoryServiceTest.java @@ -0,0 +1,155 @@ +package gift.service; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertAll; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.BDDMockito.given; + +import gift.converter.StringToUrlConverter; +import gift.domain.Category; +import gift.domain.Category.Builder; +import gift.domain.vo.Color; +import gift.repository.CategoryRepository; +import gift.web.dto.request.category.CreateCategoryRequest; +import gift.web.dto.request.category.UpdateCategoryRequest; +import gift.web.dto.response.category.CreateCategoryResponse; +import gift.web.dto.response.category.ReadAllCategoriesResponse; +import gift.web.dto.response.category.ReadCategoryResponse; +import gift.web.dto.response.category.UpdateCategoryResponse; +import java.util.List; +import java.util.Optional; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.PageRequest; + +@ExtendWith(MockitoExtension.class) +class CategoryServiceTest { + + @Mock + private CategoryRepository categoryRepository; + + @InjectMocks + private CategoryService categoryService; + + @Test + @DisplayName("생성 요청이 정상적일 때, 카테고리를 성공적으로 생성합니다.") + void createCategory() { + //given + CreateCategoryRequest request = new CreateCategoryRequest("카테고리01", "카테고리01 설명", "https://www.google.com", "#FFFFFF"); + Category category = request.toEntity(); + given(categoryRepository.save(any(Category.class))).willReturn(category); + + //when + CreateCategoryResponse response = categoryService.createCategory(request); + + //then + assertAll( + () -> assertThat(response.getName()).isEqualTo(category.getName()), + () -> assertThat(response.getDescription()).isEqualTo(category.getDescription()), + () -> assertThat(response.getImageUrl()).isEqualTo(category.getImageUrl().toString()), + () -> assertThat(response.getColor()).isEqualTo(category.getColor().toString()) + ); + } + + @Test + @DisplayName("모든 카테고리 조회 요청이 정상적일 때, 모든 카테고리를 성공적으로 조회합니다.") + void readAllCategories() { + //given + List categories = List.of( + new Category.Builder().name("카테고리01").description("카테고리01 설명").imageUrl(StringToUrlConverter.convert("https://www.google.com")).color(Color.from("#FFFFFF")).build(), + new Category.Builder().name("카테고리02").description("카테고리02 설명").imageUrl(StringToUrlConverter.convert("https://www.google.com")).color(Color.from("#FFFFFF")).build(), + new Category.Builder().name("카테고리03").description("카테고리03 설명").imageUrl(StringToUrlConverter.convert("https://www.google.com")).color(Color.from("#FFFFFF")).build() + ); + Page page = new PageImpl<>(categories); + given(categoryRepository.findAll(any(PageRequest.class))).willReturn(page); + + //when + ReadAllCategoriesResponse response = categoryService.readAllCategories(PageRequest.of(0, 10)); + + //then + List actual = response.getCategories(); + Assertions.assertAll( + () -> assertThat(actual).hasSize(3), + () -> assertThat(actual.get(0).getName()).isEqualTo(categories.get(0).getName()), + () -> assertThat(actual.get(0).getDescription()).isEqualTo(categories.get(0).getDescription()), + () -> assertThat(actual.get(0).getImageUrl()).isEqualTo(categories.get(0).getImageUrl().toString()), + () -> assertThat(actual.get(0).getColor()).isEqualTo(categories.get(0).getColor().toString()), + + () -> assertThat(actual.get(1).getName()).isEqualTo(categories.get(1).getName()), + () -> assertThat(actual.get(1).getDescription()).isEqualTo(categories.get(1).getDescription()), + () -> assertThat(actual.get(1).getImageUrl()).isEqualTo(categories.get(1).getImageUrl().toString()), + () -> assertThat(actual.get(1).getColor()).isEqualTo(categories.get(1).getColor().toString()), + + () -> assertThat(actual.get(2).getName()).isEqualTo(categories.get(2).getName()), + () -> assertThat(actual.get(2).getDescription()).isEqualTo(categories.get(2).getDescription()), + () -> assertThat(actual.get(2).getImageUrl()).isEqualTo(categories.get(2).getImageUrl().toString()), + () -> assertThat(actual.get(2).getColor()).isEqualTo(categories.get(2).getColor().toString()) + ); + } + + @Test + @DisplayName("단일 카테고리 조회 요청이 정상적일 때, 카테고리를 성공적으로 조회합니다.") + void readCategory() { + //given + Category category = new Builder() + .id(1L) + .name("카테고리01") + .description("카테고리01 설명") + .imageUrl(StringToUrlConverter.convert("https://www.google.com")) + .color(Color.from("#FFFFFF")) + .build(); + given(categoryRepository.findById(any(Long.class))).willReturn(Optional.of(category)); + + //when + ReadCategoryResponse response = categoryService.readCategory(1L); + + //then + assertAll( + () -> assertThat(response.getId()).isEqualTo(category.getId()), + () -> assertThat(response.getName()).isEqualTo(category.getName()), + () -> assertThat(response.getDescription()).isEqualTo(category.getDescription()), + () -> assertThat(response.getImageUrl()).isEqualTo(category.getImageUrl().toString()), + () -> assertThat(response.getColor()).isEqualTo(category.getColor().toString()) + ); + } + + @Test + @DisplayName("카테고리 수정 요청이 정상적일 때, 카테고리를 성공적으로 수정합니다.") + void updateCategory() { + //given + UpdateCategoryRequest request = new UpdateCategoryRequest("카테고리01", + "카테고리01 설명", "https://www.google.com", "#FFFFFF"); + given(categoryRepository.findById(any(Long.class))).willReturn(Optional.of(new Category.Builder().build())); + + //when + UpdateCategoryResponse response = categoryService.updateCategory(1L, request); + + //then + assertAll( + () -> assertThat(response.getName()).isEqualTo(request.getName()), + () -> assertThat(response.getDescription()).isEqualTo(request.getDescription()), + () -> assertThat(response.getImageUrl()).isEqualTo(request.getImageUrl()), + () -> assertThat(response.getColor()).isEqualTo(request.getColor()) + ); + } + + @Test + @DisplayName("카테고리 삭제 요청이 정상적일 때, 카테고리를 성공적으로 삭제합니다.") + void deleteCategory() { + //given + Long categoryId = 1L; + + //when + categoryService.deleteCategory(categoryId); + + //then + Assertions.assertDoesNotThrow(() -> categoryRepository.findById(1L)); + } +} \ No newline at end of file diff --git a/src/test/java/gift/service/MemberServiceTest.java b/src/test/java/gift/service/MemberServiceTest.java new file mode 100644 index 000000000..09c2e0db6 --- /dev/null +++ b/src/test/java/gift/service/MemberServiceTest.java @@ -0,0 +1,115 @@ +package gift.service; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertAll; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.BDDMockito.given; + +import gift.authentication.token.JwtProvider; +import gift.authentication.token.Token; +import gift.domain.Member; +import gift.domain.vo.Email; +import gift.domain.vo.Password; +import gift.repository.MemberRepository; +import gift.repository.WishProductRepository; +import gift.web.dto.request.LoginRequest; +import gift.web.dto.request.member.CreateMemberRequest; +import gift.web.dto.response.LoginResponse; +import gift.web.dto.response.member.CreateMemberResponse; +import gift.web.dto.response.member.ReadMemberResponse; +import java.util.Optional; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +class MemberServiceTest { + + @InjectMocks + private MemberService memberService; + + @Mock + private MemberRepository memberRepository; + + @Mock + private WishProductRepository wishProductRepository; + + @Mock + private JwtProvider jwtProvider; + + @Test + @DisplayName("회원 생성 요청이 정상적일 때, 회원을 성공적으로 생성합니다.") + void createMember() { + //given + CreateMemberRequest request = new CreateMemberRequest("member01@naver.com", "password01", "이름"); + given(memberRepository.save(any())).willReturn( + new Member.Builder().id(1L).name(request.getName()).email(Email.from(request.getEmail())).build()); + + //when + CreateMemberResponse response = memberService.createMember(request); + + //then + assertAll( + () -> assertThat(response.getId()).isNotNull(), + () -> assertThat(response.getEmail()).isEqualTo(request.getEmail()), + () -> assertThat(response.getName()).isEqualTo(request.getName()) + ); + } + + @Test + @DisplayName("회원 조회 요청이 정상적일 때, 회원을 성공적으로 조회합니다.") + void readMember() { + //given + Member member = new Member.Builder().id(1L).name("이름").email(Email.from("member01@naver.com")).password( + Password.from("password01")).build(); + given(memberRepository.findById(1L)).willReturn(Optional.of(member)); + + //when + ReadMemberResponse response = memberService.readMember(1L); + + //then + assertAll( + () -> assertThat(response.getId()).isEqualTo(member.getId()), + () -> assertThat(response.getName()).isEqualTo(member.getName()), + () -> assertThat(response.getEmail()).isEqualTo(member.getEmail().getValue()) + ); + } + + @Test + @DisplayName("회원 삭제 요청이 정상적일 때, 회원을 성공적으로 삭제합니다.") + void deleteMember() { + //given + Member member = new Member.Builder().id(1L).name("이름").email(Email.from("member01@naver.com")).password( + Password.from("password01")).build(); + given(memberRepository.findById(1L)).willReturn(Optional.of(member)); + + //when + //then + assertDoesNotThrow(() -> memberService.deleteMember(1L)); + } + + @Test + @DisplayName("로그인 요청이 정상적일 때, 로그인을 성공적으로 수행합니다.") + void login() { + //given + String email = "member01@naver.com"; + String password = "password01"; + Member member = new Member.Builder().id(1L).email(Email.from(email)).password( + Password.from(password)).build(); + + LoginRequest request = new LoginRequest(email, password); + + given(memberRepository.findByEmail(Email.from(email))).willReturn(Optional.of(member)); + given(jwtProvider.generateToken(member)).willReturn(Token.from("token")); + + //when + LoginResponse response = memberService.login(request); + + //then + assertThat(response.getToken()).isNotNull(); + } +} \ No newline at end of file diff --git a/src/test/java/gift/service/ProductOptionServiceTest.java b/src/test/java/gift/service/ProductOptionServiceTest.java new file mode 100644 index 000000000..69b413f0c --- /dev/null +++ b/src/test/java/gift/service/ProductOptionServiceTest.java @@ -0,0 +1,179 @@ +package gift.service; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.Assertions.assertAll; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.BDDMockito.given; + +import gift.domain.ProductOption; +import gift.domain.ProductOption.Builder; +import gift.repository.ProductOptionRepository; +import gift.web.dto.request.productoption.CreateProductOptionRequest; +import gift.web.dto.request.productoption.SubtractProductOptionQuantityRequest; +import gift.web.dto.request.productoption.UpdateProductOptionRequest; +import gift.web.dto.response.productoption.CreateProductOptionResponse; +import gift.web.dto.response.productoption.ReadAllProductOptionsResponse; +import gift.web.dto.response.productoption.SubtractProductOptionQuantityResponse; +import gift.web.dto.response.productoption.UpdateProductOptionResponse; +import java.util.List; +import java.util.Optional; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +class ProductOptionServiceTest { + + @InjectMocks + private ProductOptionService productOptionService; + + @Mock + private ProductOptionRepository productOptionRepository; + + @Test + @DisplayName("상품 옵션 생성 요청이 정상적일 때, 상품 옵션을 성공적으로 생성합니다.") + void createOption() { + //given + Long productId = 1L; + CreateProductOptionRequest request = new CreateProductOptionRequest("optionName", 1000); + given(productOptionRepository.save(any())).willReturn(request.toEntity(productId)); + + //when + CreateProductOptionResponse response = productOptionService.createOption(productId, request); + + //then + assertAll( + () -> assertThat(response.getName()).isEqualTo(request.getName()), + () -> assertThat(response.getStock()).isEqualTo(request.getStock()) + ); + } + + @Test + @DisplayName("상품 옵션 생성 요청이 중복된 이름일 때, 예외를 발생시킵니다.") + void createInitialOptions() { + //given + List request = List.of( + new CreateProductOptionRequest("optionName", 1000), + new CreateProductOptionRequest("optionName", 1000)); + + //when + //then + assertThatThrownBy(() -> productOptionService.createInitialOptions(1L, request)) + .isInstanceOf(IllegalStateException.class); + } + + @Test + @DisplayName("상품 옵션 조회 요청이 정상적일 때, 상품 옵션을 성공적으로 조회합니다.") + void readAllOptions() { + //given + ProductOption option01 = new Builder().productId(1L).name("optionName").stock(1000).build(); + ProductOption option02 = new Builder().productId(1L).name("optionName").stock(1000).build(); + given(productOptionRepository.findAllByProductId(1L)).willReturn(List.of(option01, option02)); + + //when + ReadAllProductOptionsResponse response = productOptionService.readAllOptions(1L); + + //then + assertAll( + () -> assertThat(response.getOptions().get(0).getName()).isEqualTo(option01.getName()), + () -> assertThat(response.getOptions().get(0).getStock()).isEqualTo(option01.getStock()), + () -> assertThat(response.getOptions().get(1).getName()).isEqualTo(option02.getName()), + () -> assertThat(response.getOptions().get(1).getStock()).isEqualTo(option02.getStock()) + ); + } + + @Test + @DisplayName("상품 옵션 수정 요청이 정상적일 때, 상품 옵션을 성공적으로 수정합니다.") + void updateOption() { + //given + Long productId = 1L; + Long optionId = 1L; + UpdateProductOptionRequest request = new UpdateProductOptionRequest("optionName", 1000); + ProductOption option = new Builder().productId(productId).name("optionName").stock(1000).build(); + given(productOptionRepository.findById(optionId)).willReturn(Optional.of(option)); + + //when + UpdateProductOptionResponse response = productOptionService.updateOption(productId, optionId, request); + + //then + assertAll( + () -> assertThat(response.getName()).isEqualTo(request.getName()), + () -> assertThat(response.getStock()).isEqualTo(request.getStock()) + ); + } + + @Nested + @DisplayName("subtractOptionStock 메서드는") + class SubtractOptionStock { + + @Test + @DisplayName("요청 수량이 재고보다 적을 때, 재고를 감소시킨다.") + void valid_request() { + //given + int requestedQuantity = 3; + SubtractProductOptionQuantityRequest request = new SubtractProductOptionQuantityRequest( + requestedQuantity); + + int stock = 10; + ProductOption productOption = new Builder().stock(stock).build(); + given(productOptionRepository.findById(any())).willReturn(Optional.of(productOption)); + + //when + SubtractProductOptionQuantityResponse response = productOptionService.subtractOptionStock( + 1L, request); + + //then + int expectedStock = stock - requestedQuantity; + assertThat(response.getStock()).isEqualTo(expectedStock); + } + + @Test + @DisplayName("요청 수량이 재고보다 많을 때, 예외를 발생시킨다.") + void invalid_request() { + //given + int requestedQuantity = 11; + SubtractProductOptionQuantityRequest request = new SubtractProductOptionQuantityRequest( + requestedQuantity); + + int stock = 10; + ProductOption productOption = new Builder().stock(stock).build(); + given(productOptionRepository.findById(any())).willReturn(Optional.of(productOption)); + + //when + //then + assertThatThrownBy(() -> productOptionService.subtractOptionStock(1L, request)) + .isInstanceOf(IllegalStateException.class); + } + } + + @Test + @DisplayName("상품 옵션 삭제 요청이 정상적일 때, 상품 옵션을 성공적으로 삭제합니다.") + void deleteOption() { + //given + Long optionId = 1L; + + //when + given(productOptionRepository.findById(optionId)).willReturn(Optional.of(new ProductOption.Builder().build())); + + //then + assertDoesNotThrow(() -> productOptionService.deleteOption(optionId)); + } + + @Test + @DisplayName("상품 옵션 전체 삭제 요청이 정상적일 때, 상품 옵션을 성공적으로 전체 삭제합니다.") + void deleteAllOptionsByProductId() { + //given + Long productId = 1L; + + //when + //then + assertDoesNotThrow(() -> productOptionService.deleteAllOptionsByProductId(productId)); + } +} \ No newline at end of file diff --git a/src/test/java/gift/service/ProductServiceTest.java b/src/test/java/gift/service/ProductServiceTest.java new file mode 100644 index 000000000..ad43d05ca --- /dev/null +++ b/src/test/java/gift/service/ProductServiceTest.java @@ -0,0 +1,264 @@ +package gift.service; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.Assertions.assertAll; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.BDDMockito.given; + +import gift.converter.StringToUrlConverter; +import gift.domain.Category; +import gift.domain.Category.Builder; +import gift.domain.Product; +import gift.domain.ProductOption; +import gift.domain.vo.Color; +import gift.repository.CategoryRepository; +import gift.repository.ProductOptionRepository; +import gift.repository.ProductRepository; +import gift.repository.WishProductRepository; +import gift.web.dto.request.product.CreateProductRequest; +import gift.web.dto.request.product.UpdateProductRequest; +import gift.web.dto.request.productoption.CreateProductOptionRequest; +import gift.web.dto.response.product.CreateProductResponse; +import gift.web.dto.response.product.ReadAllProductsResponse; +import gift.web.dto.response.product.ReadProductResponse; +import gift.web.dto.response.product.UpdateProductResponse; +import java.util.List; +import java.util.Optional; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +class ProductServiceTest { + + private ProductService productService; + + private ProductOptionService productOptionService; + + @Mock + private ProductRepository productRepository; + + @Mock + private CategoryRepository categoryRepository; + + @Mock + private WishProductRepository wishProductRepository; + + @Mock + private ProductOptionRepository productOptionRepository; + + @BeforeEach + void setUp() { + productOptionService = new ProductOptionService(productOptionRepository); + productService = new ProductService(productRepository, categoryRepository, wishProductRepository, productOptionService); + } + + @Nested + @DisplayName("createProduct 메서드는") + class createProduct { + + @Test + @DisplayName("상품 생성 요청이 정상적일 때, 상품을 성공적으로 생성합니다.") + void when_valid_request() { + //given + CreateProductOptionRequest optionRequest1 = new CreateProductOptionRequest("옵션01", 100); + CreateProductOptionRequest optionRequest2 = new CreateProductOptionRequest("옵션02", 100); + CreateProductRequest request = new CreateProductRequest("상품01", 10000, + "https://www.google.com", 1L, List.of(optionRequest1)); + + Category category = new Builder() + .id(1L) + .name("카테고리01") + .description("카테고리01 설명") + .imageUrl(StringToUrlConverter.convert("https://www.google.com")) + .color(Color.from("#FFFFFF")) + .build(); + + Product product = request.toEntity(category); + given(categoryRepository.findById(any())).willReturn(Optional.of(category)); + given(productRepository.save(any())).willReturn(product); + + //when + CreateProductResponse response = productService.createProduct(request); + + //then + assertAll( + () -> assertThat(response.getName()).isEqualTo(product.getName()), + () -> assertThat(response.getPrice()).isEqualTo(product.getPrice()), + () -> assertThat(response.getImageUrl()).isEqualTo(product.getImageUrl().toString()), + + () -> assertThat(response.getOptions().size()).isEqualTo(product.getProductOptions().size()), + () -> assertThat(response.getOptions().get(0).getName()).isEqualTo(product.getProductOptions().get(0).getName()), + () -> assertThat(response.getOptions().get(0).getStock()).isEqualTo(product.getProductOptions().get(0).getStock()) + ); + } + + @Test + @DisplayName("상품 생성 시 옵션 이름에 중복이 존재하면 예외를 발생시킵니다.") + void when_duplicate_option_name() { + //given + CreateProductOptionRequest optionRequest1 = new CreateProductOptionRequest("옵션01", 100); + CreateProductOptionRequest optionRequest2 = new CreateProductOptionRequest("옵션01", 100); + CreateProductRequest request = new CreateProductRequest("상품01", 10000, + "https://www.google.com", 1L, List.of(optionRequest1, optionRequest2)); + + Category category = new Builder() + .id(1L) + .name("카테고리01") + .description("카테고리01 설명") + .imageUrl(StringToUrlConverter.convert("https://www.google.com")) + .color(Color.from("#FFFFFF")) + .build(); + + Product product = request.toEntity(category); + given(categoryRepository.findById(any())).willReturn(Optional.of(category)); + given(productRepository.save(any())).willReturn(product); + + //when + //then + assertThatThrownBy(() -> productService.createProduct(request)) + .isInstanceOf(IllegalStateException.class); + } + + } + + @Test + @DisplayName("상품 조회 요청이 정상적일 때, 상품을 성공적으로 조회합니다.") + void readProductById() { + //given + Product product = new Product.Builder() + .id(1L) + .name("상품01") + .price(10000) + .imageUrl(StringToUrlConverter.convert("https://www.google.com")) + .category(new Builder().id(1L).name("카테고리01").description("카테고리01 설명").imageUrl(StringToUrlConverter.convert("https://www.google.com")).color(Color.from("#FFFFFF")).build()) + .productOptions(List.of(new ProductOption.Builder().id(1L).name("옵션01").stock(100).build())) + .build(); + + given(productRepository.findById(any())).willReturn(Optional.of(product)); + + //when + ReadProductResponse response = productService.readProductById(1L); + + //then + assertAll( + () -> assertThat(response.getId()).isEqualTo(product.getId()), + () -> assertThat(response.getName()).isEqualTo(product.getName()), + () -> assertThat(response.getPrice()).isEqualTo(product.getPrice()), + () -> assertThat(response.getImageUrl()).isEqualTo(product.getImageUrl().toString()), + () -> assertThat(response.getCategory().getId()).isEqualTo(product.getCategory().getId()), + () -> assertThat(response.getCategory().getName()).isEqualTo(product.getCategory().getName()) + ); + } + + @Test + @DisplayName("카테고리 ID로 상품 조회 요청이 정상적일 때, 해당 카테고리에 속한 상품들을 성공적으로 조회합니다.") + void readProductsByCategoryId() { + //given + Category category = new Builder() + .id(1L) + .name("카테고리01") + .description("카테고리01 설명") + .imageUrl(StringToUrlConverter.convert("https://www.google.com")) + .color(Color.from("#FFFFFF")) + .build(); + + Product product01 = new Product.Builder() + .id(1L) + .name("상품01") + .price(10000) + .imageUrl(StringToUrlConverter.convert("https://www.google.com")) + .category(category) + .productOptions(List.of(new ProductOption.Builder().id(1L).name("옵션01").stock(100).build())) + .build(); + + Product product02 = new Product.Builder() + .id(2L) + .name("상품02") + .price(20000) + .imageUrl(StringToUrlConverter.convert("https://www.google.com")) + .category(category) + .productOptions(List.of(new ProductOption.Builder().id(2L).name("옵션02").stock(200).build())) + .build(); + + given(productRepository.findByCategoryId(any(), any())).willReturn(List.of(product01, product02)); + + //when + ReadAllProductsResponse response = productService.readProductsByCategoryId( + 1L, null); + + //then + assertAll( + () -> assertThat(response.getProducts().size()).isEqualTo(2), + + () -> assertThat(response.getProducts().get(0).getId()).isEqualTo(product01.getId()), + () -> assertThat(response.getProducts().get(0).getName()).isEqualTo(product01.getName()), + () -> assertThat(response.getProducts().get(0).getPrice()).isEqualTo(product01.getPrice()), + () -> assertThat(response.getProducts().get(0).getImageUrl()).isEqualTo(product01.getImageUrl().toString()), + () -> assertThat(response.getProducts().get(0).getCategory().getId()).isEqualTo(product01.getCategory().getId()), + () -> assertThat(response.getProducts().get(0).getCategory().getName()).isEqualTo(product01.getCategory().getName()), + + () -> assertThat(response.getProducts().get(1).getId()).isEqualTo(product02.getId()), + () -> assertThat(response.getProducts().get(1).getName()).isEqualTo(product02.getName()), + () -> assertThat(response.getProducts().get(1).getPrice()).isEqualTo(product02.getPrice()), + () -> assertThat(response.getProducts().get(1).getImageUrl()).isEqualTo(product02.getImageUrl().toString()), + () -> assertThat(response.getProducts().get(1).getCategory().getId()).isEqualTo(product02.getCategory().getId()), + () -> assertThat(response.getProducts().get(1).getCategory().getName()).isEqualTo(product02.getCategory().getName()) + ); + } + + @Test + @DisplayName("상품 수정 요청이 정상적일 때, 상품을 성공적으로 수정합니다.") + void updateProduct() { + //given + Product product = new Product.Builder() + .id(1L) + .name("상품01") + .price(10000) + .imageUrl(StringToUrlConverter.convert("https://www.google.com")) + .category(new Builder().id(1L).name("카테고리01").description("카테고리01 설명").imageUrl(StringToUrlConverter.convert("https://www.google.com")).color(Color.from("#FFFFFF")).build()) + .productOptions(List.of(new ProductOption.Builder().id(1L).name("옵션01").stock(100).build())) + .build(); + + given(productRepository.findById(any())).willReturn(Optional.of(product)); + + UpdateProductRequest request = new UpdateProductRequest("상품02", 20000, "https://www.naver.com"); + + //when + UpdateProductResponse response = productService.updateProduct(1L, request); + + //then + assertAll( + () -> assertThat(response.getId()).isEqualTo(product.getId()), + () -> assertThat(response.getName()).isEqualTo(request.getName()), + () -> assertThat(response.getPrice()).isEqualTo(request.getPrice()), + () -> assertThat(response.getImageUrl()).isEqualTo(request.getImageUrl()) + ); + } + + @Test + @DisplayName("상품 삭제 요청이 정상적일 때, 상품을 성공적으로 삭제합니다.") + void deleteProduct() { + //given + Product product = new Product.Builder() + .id(1L) + .name("상품01") + .price(10000) + .imageUrl(StringToUrlConverter.convert("https://www.google.com")) + .category(new Builder().id(1L).name("카테고리01").description("카테고리01 설명").imageUrl(StringToUrlConverter.convert("https://www.google.com")).color(Color.from("#FFFFFF")).build()) + .productOptions(List.of(new ProductOption.Builder().id(1L).name("옵션01").stock(100).build())) + .build(); + + given(productRepository.findById(any())).willReturn(Optional.of(product)); + + //when + //then + assertDoesNotThrow(() -> productService.deleteProduct(1L)); + } +} \ No newline at end of file diff --git a/src/test/java/gift/service/WishProductServiceTest.java b/src/test/java/gift/service/WishProductServiceTest.java new file mode 100644 index 000000000..381d0bb18 --- /dev/null +++ b/src/test/java/gift/service/WishProductServiceTest.java @@ -0,0 +1,173 @@ +package gift.service; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertAll; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.BDDMockito.given; + +import gift.converter.StringToUrlConverter; +import gift.domain.Member; +import gift.domain.Product; +import gift.domain.ProductOption; +import gift.domain.WishProduct; +import gift.domain.WishProduct.Builder; +import gift.repository.MemberRepository; +import gift.repository.ProductRepository; +import gift.repository.WishProductRepository; +import gift.web.dto.request.wishproduct.CreateWishProductRequest; +import gift.web.dto.request.wishproduct.UpdateWishProductRequest; +import gift.web.dto.response.wishproduct.CreateWishProductResponse; +import gift.web.dto.response.wishproduct.ReadAllWishProductsResponse; +import gift.web.dto.response.wishproduct.UpdateWishProductResponse; +import java.util.List; +import java.util.Optional; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +class WishProductServiceTest { + + @InjectMocks + private WishProductService wishProductService; + + @Mock + private WishProductRepository wishProductRepository; + + @Mock + private MemberRepository memberRepository; + + @Mock + private ProductRepository productRepository; + + @Nested + @DisplayName("createWishProduct 메서드는") + class Describe_createWishProduct { + + @Test + @DisplayName("이미 위시 상품이 존재할 경우, 수량만 추가한다.") + void it_adds_quantity_when_wish_product_already_exists() { + // given + int beforeQuantity = 1; + WishProduct wishProduct = new Builder().product(null).member(null).quantity(beforeQuantity).build(); + given(wishProductRepository.findByMemberIdAndProductId(any(), any())) + .willReturn(Optional.of(wishProduct)); + + int addQuantity = 2; + CreateWishProductRequest request = new CreateWishProductRequest(1L, addQuantity); + + // when + CreateWishProductResponse response = wishProductService.createWishProduct(1L, request); + + // then + int expectedQuantity = beforeQuantity + addQuantity; + assertAll( + () -> assertThat(response.getId()).isEqualTo(wishProduct.getId()), + () -> assertThat(response.getQuantity()).isEqualTo(expectedQuantity) + ); + } + + @Test + @DisplayName("새로운 위시 상품을 추가한다.") + void it_adds_new_wish_product() { + // given + int quantity = 1; + CreateWishProductRequest request = new CreateWishProductRequest(1L, quantity); + given(wishProductRepository.findByMemberIdAndProductId(any(), any())) + .willReturn(Optional.empty()); + + given(wishProductRepository.save(any())) + .willReturn(new WishProduct.Builder().id(1L).quantity(quantity).build()); + + Member member = new Member.Builder().id(1L).build(); + given(memberRepository.findById(any())) + .willReturn(Optional.of(member)); + + Product product = new Product.Builder().id(1L).productOptions(List.of(new ProductOption.Builder().id(1L).build())).build(); + given(productRepository.findById(any())) + .willReturn(Optional.of(product)); + + // when + CreateWishProductResponse response = wishProductService.createWishProduct(1L, request); + + // then + assertAll( + () -> assertThat(response.getId()).isNotNull(), + () -> assertThat(response.getQuantity()).isEqualTo(quantity) + ); + } + } + + @Test + @DisplayName("정상 요청일 때, 위시 리스트에 있는 모든 상품을 조회한다.") + void readAllWishProducts() { + //given + Product product01 = new Product.Builder().id(1L).name("상품01").imageUrl(StringToUrlConverter.convert("https://www.google.com")).price(1000).productOptions(List.of(new ProductOption.Builder().id(1L).build())).build(); + Product product02 = new Product.Builder().id(2L).name("상품02").imageUrl(StringToUrlConverter.convert("https://www.google.com")).price(2000).productOptions(List.of(new ProductOption.Builder().id(2L).build())).build(); + + WishProduct wishProduct01 = new Builder().id(1L).quantity(1).product(product01).build(); + WishProduct wishProduct02 = new Builder().id(2L).quantity(2).product(product02).build(); + + List wishProducts = List.of(wishProduct01, wishProduct02); + given(wishProductRepository.findByMemberId(any(), any())) + .willReturn(wishProducts); + + //when + ReadAllWishProductsResponse response = wishProductService.readAllWishProducts(1L, null); + + //then + assertAll( + () -> assertThat(response.getWishlist().size()).isEqualTo(wishProducts.size()), + () -> assertThat(response.getWishlist().get(0).getId()).isEqualTo(wishProduct01.getId()), + () -> assertThat(response.getWishlist().get(0).getQuantity()).isEqualTo(wishProduct01.getQuantity()), + () -> assertThat(response.getWishlist().get(0).getProductId()).isEqualTo(wishProduct01.getProduct().getId()), + () -> assertThat(response.getWishlist().get(0).getName()).isEqualTo(wishProduct01.getProduct().getName()), + () -> assertThat(response.getWishlist().get(0).getImageUrl()).isEqualTo(wishProduct01.getProduct().getImageUrl().toString()), + () -> assertThat(response.getWishlist().get(0).getPrice()).isEqualTo(wishProduct01.getProduct().getPrice()), + () -> assertThat(response.getWishlist().get(1).getId()).isEqualTo(wishProduct02.getId()), + () -> assertThat(response.getWishlist().get(1).getQuantity()).isEqualTo(wishProduct02.getQuantity()), + () -> assertThat(response.getWishlist().get(1).getId()).isEqualTo(wishProduct02.getProduct().getId()), + () -> assertThat(response.getWishlist().get(1).getName()).isEqualTo(wishProduct02.getProduct().getName()), + () -> assertThat(response.getWishlist().get(1).getImageUrl()).isEqualTo(wishProduct02.getProduct().getImageUrl().toString()), + () -> assertThat(response.getWishlist().get(1).getPrice()).isEqualTo(wishProduct02.getProduct().getPrice()) + ); + } + + @Test + @DisplayName("정상 요청일 때, 위시 리스트에 있는 상품의 수량을 수정한다.") + void updateWishProduct() { + //given + Product product01 = new Product.Builder().id(1L).name("상품01").imageUrl(StringToUrlConverter.convert("https://www.google.com")).price(1000).productOptions(List.of(new ProductOption.Builder().id(1L).build())).build(); + WishProduct wishProduct01 = new Builder().id(1L).quantity(1).product(product01).build(); + given(wishProductRepository.findById(any())) + .willReturn(Optional.of(wishProduct01)); + + int quantity = 5; + UpdateWishProductRequest request = new UpdateWishProductRequest(quantity); + //when + UpdateWishProductResponse response = wishProductService.updateWishProduct(1L, request); + + //then + assertAll( + () -> assertThat(response.getId()).isEqualTo(wishProduct01.getId()), + () -> assertThat(response.getQuantity()).isEqualTo(quantity) + ); + } + + @Test + @DisplayName("정상 요청일 때, 위시 리스트에 있는 상품을 삭제한다.") + void deleteWishProduct() { + //given + WishProduct wishProduct = new Builder().id(1L).build(); + given(wishProductRepository.findById(any())) + .willReturn(Optional.of(wishProduct)); + + //when + assertDoesNotThrow(() -> wishProductService.deleteWishProduct(1L)); + } +} \ No newline at end of file diff --git a/src/test/java/gift/utils/CategoryDummyDataProvider.java b/src/test/java/gift/utils/CategoryDummyDataProvider.java new file mode 100644 index 000000000..2773c3497 --- /dev/null +++ b/src/test/java/gift/utils/CategoryDummyDataProvider.java @@ -0,0 +1,51 @@ +package gift.utils; + +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.sql.Timestamp; +import java.time.LocalDateTime; +import org.springframework.context.annotation.Profile; +import org.springframework.jdbc.core.BatchPreparedStatementSetter; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.stereotype.Component; + +@Profile("test") +@Component +public class CategoryDummyDataProvider { + + private final JdbcTemplate jdbcTemplate; + + public CategoryDummyDataProvider(JdbcTemplate jdbcTemplate) { + this.jdbcTemplate = jdbcTemplate; + } + + public void run(int quantity) { + doRun(quantity); + } + + private void doRun(int quantity) { + String sql = "insert into category (name, description, image_url, color, created_at, created_by, updated_at, updated_by) values (?, ?, ?, ?, ?, ?, ?, ?)"; + jdbcTemplate.batchUpdate(sql, getBatchPreparedStatementSetter(quantity)); + } + + private static BatchPreparedStatementSetter getBatchPreparedStatementSetter(int quantity) { + return new BatchPreparedStatementSetter() { + @Override + public void setValues(PreparedStatement ps, int i) throws SQLException { + ps.setString(1, "Category" + i); + ps.setString(2, "Description" + i); + ps.setString(3, "https://via.placeholder.com/" + i); + ps.setString(4, "#FF2829"); + ps.setTimestamp(5, Timestamp.valueOf(LocalDateTime.now())); + ps.setLong(6, 1L); + ps.setTimestamp(7, Timestamp.valueOf(LocalDateTime.now())); + ps.setLong(8, 1L); + } + + @Override + public int getBatchSize() { + return quantity; + } + }; + } +} \ No newline at end of file diff --git a/src/test/java/gift/utils/DatabaseCleanup.java b/src/test/java/gift/utils/DatabaseCleanup.java new file mode 100644 index 000000000..46ea412f2 --- /dev/null +++ b/src/test/java/gift/utils/DatabaseCleanup.java @@ -0,0 +1,41 @@ +package gift.utils; + +import com.fasterxml.jackson.databind.PropertyNamingStrategies.SnakeCaseStrategy; +import jakarta.persistence.Entity; +import jakarta.persistence.EntityManager; +import jakarta.persistence.PersistenceContext; +import java.util.List; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.context.annotation.Profile; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@Profile("test") +public class DatabaseCleanup implements InitializingBean { + + @PersistenceContext + private EntityManager entityManager; + + private List tableNames; + + @Override + public void afterPropertiesSet() { + tableNames = entityManager.getMetamodel().getEntities().stream() + .filter(e -> e.getJavaType().getAnnotation(Entity.class) != null) + .map(e -> SnakeCaseStrategy.INSTANCE.translate(e.getName())) + .toList(); + } + + @Transactional + public void execute() { + entityManager.flush(); + entityManager.createNativeQuery("SET REFERENTIAL_INTEGRITY FALSE").executeUpdate(); + + for (String tableName : tableNames) { + entityManager.createNativeQuery("TRUNCATE TABLE " + tableName).executeUpdate(); + entityManager.createNativeQuery("ALTER TABLE " + tableName + " ALTER COLUMN id RESTART WITH 1").executeUpdate(); + } + entityManager.createNativeQuery("SET REFERENTIAL_INTEGRITY TRUE").executeUpdate(); + } +} diff --git a/src/test/java/gift/utils/MemberDummyDataProvider.java b/src/test/java/gift/utils/MemberDummyDataProvider.java new file mode 100644 index 000000000..cf340e8c2 --- /dev/null +++ b/src/test/java/gift/utils/MemberDummyDataProvider.java @@ -0,0 +1,48 @@ +package gift.utils; + +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.sql.Timestamp; +import java.time.LocalDateTime; +import org.springframework.context.annotation.Profile; +import org.springframework.jdbc.core.BatchPreparedStatementSetter; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.stereotype.Component; + +@Profile("test") +@Component +public class MemberDummyDataProvider { + + private final JdbcTemplate jdbcTemplate; + + public MemberDummyDataProvider(JdbcTemplate jdbcTemplate) { + this.jdbcTemplate = jdbcTemplate; + } + + public void run(int quantity) { + doRun(quantity); + } + + private void doRun(int quantity) { + String sql = "insert into member (name, email, password, created_at, updated_at) values (?, ?, ?, ?, ?)"; + jdbcTemplate.batchUpdate(sql, getBatchPreparedStatementSetter(quantity)); + } + + private static BatchPreparedStatementSetter getBatchPreparedStatementSetter(int quantity) { + return new BatchPreparedStatementSetter() { + @Override + public void setValues(PreparedStatement ps, int i) throws SQLException { + ps.setString(1, "member" + i); + ps.setString(2, "member" + i + "@gmail.com"); + ps.setString(3, "member" + i + "0"); + ps.setTimestamp(4, Timestamp.valueOf(LocalDateTime.now())); + ps.setTimestamp(5, Timestamp.valueOf(LocalDateTime.now())); + } + + @Override + public int getBatchSize() { + return quantity; + } + }; + } +} \ No newline at end of file diff --git a/src/test/java/gift/utils/ProductDummyDataProvider.java b/src/test/java/gift/utils/ProductDummyDataProvider.java new file mode 100644 index 000000000..42ca87d0a --- /dev/null +++ b/src/test/java/gift/utils/ProductDummyDataProvider.java @@ -0,0 +1,50 @@ +package gift.utils; + +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.sql.Timestamp; +import java.time.LocalDateTime; +import org.springframework.context.annotation.Profile; +import org.springframework.jdbc.core.BatchPreparedStatementSetter; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.stereotype.Component; + +@Profile("test") +@Component +public class ProductDummyDataProvider { + + private final JdbcTemplate jdbcTemplate; + + public ProductDummyDataProvider(JdbcTemplate jdbcTemplate) { + this.jdbcTemplate = jdbcTemplate; + } + + public void run(int quantity) { + doRun(quantity); + } + + private void doRun(int quantity) { + String sql = "insert into product (name, price, category_id, created_at, created_by, updated_at, updated_by) values (?, ?, ?, ?, ?, ?, ?)"; + jdbcTemplate.batchUpdate(sql, getBatchPreparedStatementSetter(quantity)); + } + + private static BatchPreparedStatementSetter getBatchPreparedStatementSetter(int quantity) { + return new BatchPreparedStatementSetter() { + @Override + public void setValues(PreparedStatement ps, int i) throws SQLException { + ps.setString(1, "product" + i); + ps.setInt(2, 1000 * i); + ps.setLong(3, 1L); + ps.setTimestamp(4, Timestamp.valueOf(LocalDateTime.now())); + ps.setLong(5, 1L); + ps.setTimestamp(6, Timestamp.valueOf(LocalDateTime.now())); + ps.setLong(7, 1L); + } + + @Override + public int getBatchSize() { + return quantity; + } + }; + } +} \ No newline at end of file diff --git a/src/test/java/gift/utils/StringUtilsTest.java b/src/test/java/gift/utils/StringUtilsTest.java new file mode 100644 index 000000000..8a818eb53 --- /dev/null +++ b/src/test/java/gift/utils/StringUtilsTest.java @@ -0,0 +1,71 @@ +package gift.utils; + +import static org.junit.jupiter.api.Assertions.*; + +import java.util.Set; +import java.util.stream.Stream; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +class StringUtilsTest { + + @ParameterizedTest(name = "{0}의 특수문자는 {1} 만을 포함하고 있어야 한다") + @MethodSource("specialCharsSuccessCase") + void containsOnlyAllowedSpecialCharsSuccessCase(String input, Set allowedSpecialChars) { + assertTrue(StringUtils.containsOnlyAllowedSpecialChars(input, allowedSpecialChars)); + } + + private static Stream specialCharsSuccessCase() { + return Stream.of( + Arguments.of("abc", Set.of()), + Arguments.of("abc", Set.of('!', '@', '#', '$', '%')), + Arguments.of("ab!c!@#", Set.of('!', '@', '#', '$', '%')), + Arguments.of("abc!@#%", Set.of('!', '@', '#', '$', '%')), + Arguments.of("a@bc!#%$", Set.of('!', '@', '#', '$', '%')), + Arguments.of("ab!c!@#%$", Set.of('!', '@', '#', '$', '%')), + Arguments.of("ab^c!@#%$^&", Set.of('!', '@', '#', '$', '%', '^', '&')), + Arguments.of("abc!@#%$^&*", Set.of('!', '@', '#', '$', '%', '^', '&', '*'))); + } + + @ParameterizedTest(name = "{0}의 특수문자는 {1} 외의 특수문자를 포함하고 있다") + @MethodSource("specialCharsFailCase") + void containsOnlyAllowedSpecialCharsFailCase(String input, Set allowedSpecialChars) { + assertFalse(StringUtils.containsOnlyAllowedSpecialChars(input, allowedSpecialChars)); + } + + private static Stream specialCharsFailCase() { + return Stream.of( + Arguments.of("abc!", Set.of()), + Arguments.of("a!b%!%c$", Set.of('!', '%')), + Arguments.of("ab^c!@#%$^&)", Set.of('!', '@', '#', '$', '%', '^')), + Arguments.of("abc!@#%$^&*[", Set.of('!', '@', '#', '$', '%', '^', '&'))); + } + + @ParameterizedTest(name = "{0}는 {1}를 포함하고 있다") + @MethodSource("substringsSuccessCase") + void containsAnySubstringSuccessCase(String input, Set substrings) { + assertTrue(StringUtils.containsAnySubstring(input, substrings)); + } + + private static Stream substringsSuccessCase() { + return Stream.of( + Arguments.of("abc", Set.of("ab")), + Arguments.of("카카오 선풍기", Set.of("카카오")), + Arguments.of("카카오", Set.of("카카오"))); + } + + @ParameterizedTest(name = "{0}는 {1}를 포함하고 있지 않다") + @MethodSource("substringsFailCase") + void containsAnySubstringFailCase(String input, Set substrings) { + assertFalse(StringUtils.containsAnySubstring(input, substrings)); + } + + private static Stream substringsFailCase() { + return Stream.of( + Arguments.of("abcdefg", Set.of("aefg")), + Arguments.of("카카오 선풍기", Set.of("네이버")), + Arguments.of("카카오 선풍기", Set.of("카카오선풍기")) + ); + } +} \ No newline at end of file diff --git a/src/test/java/gift/utils/WishProductDummyDataProvider.java b/src/test/java/gift/utils/WishProductDummyDataProvider.java new file mode 100644 index 000000000..eae21073a --- /dev/null +++ b/src/test/java/gift/utils/WishProductDummyDataProvider.java @@ -0,0 +1,52 @@ +package gift.utils; + +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.sql.Timestamp; +import java.time.LocalDateTime; +import org.springframework.context.annotation.Profile; +import org.springframework.jdbc.core.BatchPreparedStatementSetter; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.stereotype.Component; + +@Profile("test") +@Component +public class WishProductDummyDataProvider { + + private final JdbcTemplate jdbcTemplate; + + private static final Long TARGET_MEMBER_ID = 1L; + + public WishProductDummyDataProvider(JdbcTemplate jdbcTemplate) { + this.jdbcTemplate = jdbcTemplate; + } + + public void run(int quantity) { + doRun(quantity); + } + + private void doRun(int quantity) { + String sql = "insert into wish_product (member_id, product_id, quantity, created_at, created_by, updated_at, updated_by) values (?, ?, ?, ?, ?, ?, ?)"; + jdbcTemplate.batchUpdate(sql, getBatchPreparedStatementSetter(quantity)); + } + + private static BatchPreparedStatementSetter getBatchPreparedStatementSetter(int quantity) { + return new BatchPreparedStatementSetter() { + @Override + public void setValues(PreparedStatement ps, int i) throws SQLException { + ps.setLong(1, TARGET_MEMBER_ID); + ps.setLong(2, i + 1); + ps.setInt(3, i + 1); + ps.setTimestamp(4, Timestamp.valueOf(LocalDateTime.now())); + ps.setLong(5, TARGET_MEMBER_ID); + ps.setTimestamp(6, Timestamp.valueOf(LocalDateTime.now())); + ps.setLong(7, TARGET_MEMBER_ID); + } + + @Override + public int getBatchSize() { + return quantity; + } + }; + } +} diff --git a/src/test/java/gift/web/controller/api/MemberApiControllerTest.java b/src/test/java/gift/web/controller/api/MemberApiControllerTest.java new file mode 100644 index 000000000..1dbea41d9 --- /dev/null +++ b/src/test/java/gift/web/controller/api/MemberApiControllerTest.java @@ -0,0 +1,226 @@ +package gift.web.controller.api; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertAll; +import static org.junit.jupiter.api.Assertions.assertIterableEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import gift.authentication.token.Token; +import gift.domain.Member; +import gift.repository.MemberRepository; +import gift.service.MemberService; +import gift.service.WishProductService; +import gift.utils.CategoryDummyDataProvider; +import gift.utils.DatabaseCleanup; +import gift.utils.MemberDummyDataProvider; +import gift.utils.ProductDummyDataProvider; +import gift.utils.WishProductDummyDataProvider; +import gift.web.dto.request.LoginRequest; +import gift.web.dto.request.member.CreateMemberRequest; +import gift.web.dto.request.wishproduct.UpdateWishProductRequest; +import gift.web.dto.response.LoginResponse; +import gift.web.dto.response.member.CreateMemberResponse; +import gift.web.dto.response.member.ReadMemberResponse; +import gift.web.dto.response.wishproduct.ReadAllWishProductsResponse; +import gift.web.dto.response.wishproduct.UpdateWishProductResponse; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; +import org.springframework.boot.test.web.client.TestRestTemplate; +import org.springframework.boot.test.web.server.LocalServerPort; +import org.springframework.data.domain.PageRequest; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.ResponseEntity; +import org.springframework.test.context.ActiveProfiles; + +@ActiveProfiles("test") +@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT) +class MemberApiControllerTest { + + @LocalServerPort + private int port; + + @Autowired + private TestRestTemplate restTemplate; + + @Autowired + private MemberDummyDataProvider memberDummyDataProvider; + + @Autowired + private ProductDummyDataProvider productDummyDataProvider; + + @Autowired + private WishProductDummyDataProvider wishProductDummyDataProvider; + + @Autowired + private CategoryDummyDataProvider categoryDummyDataProvider; + + @Autowired + private DatabaseCleanup databaseCleanup; + + @Autowired + private MemberService memberService; + + @Autowired + private MemberRepository memberRepository; + + @Autowired + private WishProductService wishProductService; + + //테스트용 회원 + private Member member; + private Token token; + + @BeforeEach + void setUp() { + insertDummyData(100); + member = getTestMember(1L); + token = getAccessToken(); + } + + private Member getTestMember(Long id) { + return memberRepository.findById(id) + .orElseThrow(() -> new IllegalArgumentException("ID: " + id +"인 회원이 존재하지 않습니다.")); + } + + private Token getAccessToken() { + LoginRequest loginRequest = new LoginRequest( + member.getEmail().getValue(), + member.getPassword().getValue() + ); + LoginResponse loginResponse = memberService.login(loginRequest); + return loginResponse.getToken(); + } + + private void insertDummyData(int quantity) { + if (quantity < 2) { + throw new IllegalArgumentException("quantity는 2 이상이어야 합니다."); + } + memberDummyDataProvider.run(quantity); + productDummyDataProvider.run(quantity); + wishProductDummyDataProvider.run(quantity); + categoryDummyDataProvider.run(quantity); + } + + @AfterEach + void tearDown() { + databaseCleanup.execute(); + } + + @Test + @DisplayName("회원 생성 요청에 대한 정상 응답") + void createMember() { + //given + CreateMemberRequest request = new CreateMemberRequest("test@gmail.com", "test1234", "test"); + String url = "http://localhost:" + port + "/api/members/register"; + + //when + ResponseEntity response = restTemplate.postForEntity(url, request, CreateMemberResponse.class); + + //then + Long newMemberId = response.getBody().getId(); + ReadMemberResponse findMember = memberService.readMember(newMemberId); + + assertAll( + () -> assertTrue(response.getStatusCode().is2xxSuccessful()), + () -> assertThat(newMemberId).isEqualTo(findMember.getId()), + () -> assertThat(request.getEmail()).isEqualTo(findMember.getEmail()), + () -> assertThat(request.getName()).isEqualTo(findMember.getName()), + () -> assertThat(request.getPassword()).isEqualTo(findMember.getPassword()) + ); + } + + @Test + @DisplayName("로그인 요청에 대한 정상 응답") + void login() { + //given + String url = "http://localhost:" + port + "/api/members/login"; + String email = member.getEmail().getValue(); + String password = member.getPassword().getValue(); + LoginRequest request = new LoginRequest(email, password); + + //when + ResponseEntity response = restTemplate.postForEntity(url, request, LoginResponse.class); + + //then + assertAll( + () -> assertTrue(response.getStatusCode().is2xxSuccessful()), + () -> assertThat(response.getBody().getToken()).isNotNull() + ); + } + + @Test + @DisplayName("위시 리스트 조회 요청에 대한 정상 응답") + void readWishProduct() { + //given + String url = "http://localhost:" + port + "/api/members/wishlist"; + HttpHeaders httpHeaders = getHttpHeaders(); + HttpEntity httpEntity = new HttpEntity(httpHeaders); + + PageRequest defaultPageRequest = PageRequest.of(0, 10); + ReadAllWishProductsResponse expectedWishProducts = wishProductService.readAllWishProducts(member.getId(), + defaultPageRequest); + + //when + ResponseEntity response = restTemplate.exchange(url, + HttpMethod.GET, httpEntity, ReadAllWishProductsResponse.class); + + //then + assertAll( + () -> assertTrue(response.getStatusCode().is2xxSuccessful()), + () -> assertIterableEquals(response.getBody().getWishlist(), expectedWishProducts.getWishlist()) + ); + } + + @Test + @DisplayName("위시 리스트 상품 수정 요청에 대한 정상 응답") + void updateWishProduct() { + //given + Long wishProductId = 1L; + String url = "http://localhost:" + port + "/api/members/wishlist/" + wishProductId; + HttpHeaders httpHeaders = getHttpHeaders(); + + UpdateWishProductRequest request = new UpdateWishProductRequest(3); + + HttpEntity httpEntity = new HttpEntity(request, httpHeaders); + + //when + ResponseEntity response = restTemplate.exchange(url, + HttpMethod.PUT, httpEntity, UpdateWishProductResponse.class); + + //then + assertAll( + () -> assertTrue(response.getStatusCode().is2xxSuccessful()), + () -> assertThat(response.getBody().getQuantity()).isEqualTo(request.getQuantity()) + ); + } + + @Test + @DisplayName("위시 리스트 상품 삭제 요청에 대한 정상 응답") + void deleteWishProduct() { + //given + Long wishProductId = 2L; + String url = "http://localhost:" + port + "/api/members/wishlist/" + wishProductId; + HttpHeaders httpHeaders = getHttpHeaders(); + HttpEntity httpEntity = new HttpEntity(httpHeaders); + + //when + ResponseEntity response = restTemplate.exchange(url, HttpMethod.DELETE, httpEntity, + Void.class); + + //then + assertTrue(response.getStatusCode().is2xxSuccessful()); + } + + private HttpHeaders getHttpHeaders() { + HttpHeaders httpHeaders = new HttpHeaders(); + httpHeaders.setBearerAuth(token.getValue()); + return httpHeaders; + } +} \ No newline at end of file diff --git a/src/test/resources/application-test.yml b/src/test/resources/application-test.yml new file mode 100644 index 000000000..603334cd8 --- /dev/null +++ b/src/test/resources/application-test.yml @@ -0,0 +1,28 @@ +spring: + datasource: + url: jdbc:h2:mem:gift + username: sa + password: + driver-class-name: org.h2.Driver + + jpa: + hibernate: + ddl-auto: create + properties: + hibernate: + format_sql: true + defer-datasource-initialization: true #Hibernate 초기화 후 data.sql 실행 + +jwt: + expiration: 3600000 #1시간 + secretkey: 994e69b6df494d3e5f48fdb32693d5c772cc35a4d4f67cce4323fe256b6caf31b666d21db4b9f974935c7900bce11947edf8e266ef87aaa176db591b220860ff + +logging: + level: + gift.authentication: debug + org: + hibernate: + SQL: debug + orm: + jdbc: + bind: trace \ No newline at end of file From e9daf63170ddb8b08a79ad1914e9af6c241bca47 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=82=98=EC=A0=9C=EB=B2=95?= Date: Fri, 26 Jul 2024 16:00:50 +0900 Subject: [PATCH 002/138] =?UTF-8?q?docs:=20README.md=20Step5=201=EB=8B=A8?= =?UTF-8?q?=EA=B3=84=20=EC=9A=94=EA=B5=AC=EC=82=AC=ED=95=AD=20=EB=B0=98?= =?UTF-8?q?=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 8bbd0354f..42b1f59e0 100644 --- a/README.md +++ b/README.md @@ -1 +1,13 @@ -# spring-gift-order \ No newline at end of file +# spring-gift-order +## Step 5 +*** +### 🚀 1단계 - 기본 코드 준비 +*** +#### 기능 요구 사항 +카카오 로그인을 통해 인가 코드를 받고, 인가 코드를 사용해 토큰을 받은 후 향후 카카오 API 사용을 준비한다. + +* 카카오계정 로그인을 통해 인증 코드를 받는다. +* 토큰 받기를 읽고 액세스 토큰을 추출한다. +* 앱 키, 인가 코드가 절대 유출되지 않도록 한다. + * 특히 시크릿 키는 GitHub나 클라이언트 코드 등 외부에서 볼 수 있는 곳에 추가하지 않는다. +* (선택) 인가 코드를 받는 방법이 불편한 경우 카카오 로그인 화면을 구현한다. \ No newline at end of file From 7efd22997c904fdecc17084d1a0e219187c2aae1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=82=98=EC=A0=9C=EB=B2=95?= Date: Fri, 26 Jul 2024 16:16:39 +0900 Subject: [PATCH 003/138] =?UTF-8?q?chore:=20build.gradle=20OpenFeign=20?= =?UTF-8?q?=EC=9D=98=EC=A1=B4=EC=84=B1=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/build.gradle b/build.gradle index 9ffb4a4b1..3b37a3492 100644 --- a/build.gradle +++ b/build.gradle @@ -23,6 +23,8 @@ dependencies { implementation 'org.springframework.boot:spring-boot-starter-thymeleaf' implementation 'org.springframework.boot:spring-boot-starter-web' implementation 'org.springframework.boot:spring-boot-starter-validation' + // OpenFeign + implementation 'org.springframework.cloud:spring-cloud-starter-openfeign' //jwt implementation 'io.jsonwebtoken:jjwt-api:0.12.6' @@ -34,6 +36,12 @@ dependencies { testRuntimeOnly 'org.junit.platform:junit-platform-launcher' } +dependencyManagement { + imports { + mavenBom "org.springframework.cloud:spring-cloud-dependencies:2023.0.3" + } +} + tasks.named('test') { useJUnitPlatform() } From 0a0504e4305826b991623a35b3cd2ff2859f9350 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=82=98=EC=A0=9C=EB=B2=95?= Date: Fri, 26 Jul 2024 16:17:04 +0900 Subject: [PATCH 004/138] =?UTF-8?q?feat:=20KakaoClient.java=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/gift/web/client/KakaoClient.java | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 src/main/java/gift/web/client/KakaoClient.java diff --git a/src/main/java/gift/web/client/KakaoClient.java b/src/main/java/gift/web/client/KakaoClient.java new file mode 100644 index 000000000..401e1c28e --- /dev/null +++ b/src/main/java/gift/web/client/KakaoClient.java @@ -0,0 +1,26 @@ +package gift.web.client; + +import gift.authentication.token.KakaoToken; +import gift.web.client.dto.KakaoInfo; +import java.net.URI; +import org.springframework.cloud.openfeign.FeignClient; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestHeader; +import org.springframework.web.bind.annotation.RequestParam; + +@FeignClient(name = "kakaoClient") +public interface KakaoClient { + + @PostMapping + KakaoInfo getKakaoInfo( + URI uri, + @RequestHeader("Authorization") String accessToken); + + @PostMapping + KakaoToken getToken( + URI uri, + @RequestParam("code") String code, + @RequestParam("client_id") String clientId, + @RequestParam("redirect_uri") String redirectUrl, + @RequestParam("grant_type") String grantType); +} From 1d4ff4dd6c857bb3382682cb262325ba464feb87 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=82=98=EC=A0=9C=EB=B2=95?= Date: Fri, 26 Jul 2024 16:17:19 +0900 Subject: [PATCH 005/138] =?UTF-8?q?feat:=20KakaoInfo.java=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/gift/web/client/dto/KakaoInfo.java | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 src/main/java/gift/web/client/dto/KakaoInfo.java diff --git a/src/main/java/gift/web/client/dto/KakaoInfo.java b/src/main/java/gift/web/client/dto/KakaoInfo.java new file mode 100644 index 000000000..8452382e6 --- /dev/null +++ b/src/main/java/gift/web/client/dto/KakaoInfo.java @@ -0,0 +1,24 @@ +package gift.web.client.dto; + +import com.fasterxml.jackson.databind.PropertyNamingStrategies; +import com.fasterxml.jackson.databind.annotation.JsonNaming; + +@JsonNaming(value = PropertyNamingStrategies.SnakeCaseStrategy.class) +public class KakaoInfo { + + private Long id; + private KakaoAccount kakaoAccount; + + public KakaoInfo(Long id, KakaoAccount kakaoAccount) { + this.id = id; + this.kakaoAccount = kakaoAccount; + } + + public Long getId() { + return id; + } + + public KakaoAccount getKakaoAccount() { + return kakaoAccount; + } +} From 3e12882aecf973551dde873d21eea80b21a93a3f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=82=98=EC=A0=9C=EB=B2=95?= Date: Fri, 26 Jul 2024 16:17:25 +0900 Subject: [PATCH 006/138] =?UTF-8?q?feat:=20KakaoProfile.java=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/gift/web/client/dto/KakaoProfile.java | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 src/main/java/gift/web/client/dto/KakaoProfile.java diff --git a/src/main/java/gift/web/client/dto/KakaoProfile.java b/src/main/java/gift/web/client/dto/KakaoProfile.java new file mode 100644 index 000000000..b77e11e1f --- /dev/null +++ b/src/main/java/gift/web/client/dto/KakaoProfile.java @@ -0,0 +1,17 @@ +package gift.web.client.dto; + +import com.fasterxml.jackson.annotation.JsonCreator; + +public class KakaoProfile { + + private String nickname; + + @JsonCreator + public KakaoProfile(String nickname) { + this.nickname = nickname; + } + + public String getNickname() { + return nickname; + } +} From f9ebe69a2a3ba9513c151b73ba522e1dbe8190b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=82=98=EC=A0=9C=EB=B2=95?= Date: Fri, 26 Jul 2024 16:17:32 +0900 Subject: [PATCH 007/138] =?UTF-8?q?feat:=20KakaoProperties.java=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/gift/config/KakaoProperties.java | 56 +++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 src/main/java/gift/config/KakaoProperties.java diff --git a/src/main/java/gift/config/KakaoProperties.java b/src/main/java/gift/config/KakaoProperties.java new file mode 100644 index 000000000..baa7b457c --- /dev/null +++ b/src/main/java/gift/config/KakaoProperties.java @@ -0,0 +1,56 @@ +package gift.config; + +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.context.properties.bind.ConstructorBinding; + +@ConfigurationProperties(prefix = "kakao") +public class KakaoProperties { + + private final String clientId; + private final String redirectUri; + private final String contentType; + private final String grantType; + private final String userInfoUrl; + private final String tokenUrl; + private final String responseType; + + @ConstructorBinding + public KakaoProperties(String clientId, String redirectUri, String contentType, + String grantType, String userInfoUrl, String tokenUrl, String responseType) { + this.clientId = clientId; + this.redirectUri = redirectUri; + this.contentType = contentType; + this.grantType = grantType; + this.userInfoUrl = userInfoUrl; + this.tokenUrl = tokenUrl; + this.responseType = responseType; + } + + public String getClientId() { + return clientId; + } + + public String getRedirectUri() { + return redirectUri; + } + + public String getContentType() { + return contentType; + } + + public String getGrantType() { + return grantType; + } + + public String getUserInfoUrl() { + return userInfoUrl; + } + + public String getTokenUrl() { + return tokenUrl; + } + + public String getResponseType() { + return responseType; + } +} From e52db548562d33a3a575a02c72d629b87378a7f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=82=98=EC=A0=9C=EB=B2=95?= Date: Fri, 26 Jul 2024 16:17:39 +0900 Subject: [PATCH 008/138] =?UTF-8?q?feat:=20KakaoToken.java=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../gift/authentication/token/KakaoToken.java | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 src/main/java/gift/authentication/token/KakaoToken.java diff --git a/src/main/java/gift/authentication/token/KakaoToken.java b/src/main/java/gift/authentication/token/KakaoToken.java new file mode 100644 index 000000000..0d48863ed --- /dev/null +++ b/src/main/java/gift/authentication/token/KakaoToken.java @@ -0,0 +1,43 @@ +package gift.authentication.token; + +import com.fasterxml.jackson.databind.PropertyNamingStrategies; +import com.fasterxml.jackson.databind.annotation.JsonNaming; + +@JsonNaming(value = PropertyNamingStrategies.SnakeCaseStrategy.class) +public class KakaoToken { + + private final String tokenType; + private final String accessToken; + private final String refreshToken; + private final Long expiresIn; + private final Long refreshTokenExpiresIn; + + public KakaoToken(String tokenType, String accessToken, String refreshToken, Long expiresIn, + Long refreshTokenExpiresIn) { + this.tokenType = tokenType; + this.accessToken = accessToken; + this.refreshToken = refreshToken; + this.expiresIn = expiresIn; + this.refreshTokenExpiresIn = refreshTokenExpiresIn; + } + + public String getTokenType() { + return tokenType; + } + + public String getAccessToken() { + return accessToken; + } + + public String getRefreshToken() { + return refreshToken; + } + + public Long getExpiresIn() { + return expiresIn; + } + + public Long getRefreshTokenExpiresIn() { + return refreshTokenExpiresIn; + } +} From 6f3cf6754759e0c4b6a88231042c904c79b9e48c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=82=98=EC=A0=9C=EB=B2=95?= Date: Fri, 26 Jul 2024 16:18:18 +0900 Subject: [PATCH 009/138] =?UTF-8?q?feat:=20KakaoAccount.java=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../gift/web/client/dto/KakaoAccount.java | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 src/main/java/gift/web/client/dto/KakaoAccount.java diff --git a/src/main/java/gift/web/client/dto/KakaoAccount.java b/src/main/java/gift/web/client/dto/KakaoAccount.java new file mode 100644 index 000000000..c9c382c79 --- /dev/null +++ b/src/main/java/gift/web/client/dto/KakaoAccount.java @@ -0,0 +1,33 @@ +package gift.web.client.dto; + +public class KakaoAccount { + + private KakaoProfile profile; + private String email; + private Boolean isEmailValid; + private Boolean isEmailVerified; + + public KakaoAccount(KakaoProfile profile, String email, Boolean isEmailValid, + Boolean isEmailVerified) { + this.profile = profile; + this.email = email; + this.isEmailValid = isEmailValid; + this.isEmailVerified = isEmailVerified; + } + + public KakaoProfile getProfile() { + return profile; + } + + public String getEmail() { + return email; + } + + public Boolean getEmailValid() { + return isEmailValid; + } + + public Boolean getEmailVerified() { + return isEmailVerified; + } +} From e1ea3bb3b83ef1864cd87f953e7f46fdddca7477 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=82=98=EC=A0=9C=EB=B2=95?= Date: Fri, 26 Jul 2024 16:18:30 +0900 Subject: [PATCH 010/138] =?UTF-8?q?feat:=20LoginService.java=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/gift/service/LoginService.java | 76 ++++++++++++++++++++ 1 file changed, 76 insertions(+) create mode 100644 src/main/java/gift/service/LoginService.java diff --git a/src/main/java/gift/service/LoginService.java b/src/main/java/gift/service/LoginService.java new file mode 100644 index 000000000..03ec44394 --- /dev/null +++ b/src/main/java/gift/service/LoginService.java @@ -0,0 +1,76 @@ +package gift.service; + +import gift.authentication.token.JwtProvider; +import gift.authentication.token.KakaoToken; +import gift.authentication.token.Token; +import gift.config.KakaoProperties; +import gift.domain.Member; +import gift.web.client.KakaoClient; +import gift.web.client.dto.KakaoAccount; +import gift.web.client.dto.KakaoInfo; +import gift.web.dto.response.LoginResponse; +import gift.web.validation.exception.client.InvalidCredentialsException; +import java.net.URI; +import java.net.URISyntaxException; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +public class LoginService { + + private final KakaoClient kakaoClient; + + private final KakaoProperties kakaoProperties; + + private final MemberService memberService; + + private final JwtProvider jwtProvider; + + public LoginService(KakaoClient kakaoClient, KakaoProperties kakaoProperties, + MemberService memberService, JwtProvider jwtProvider) { + this.kakaoClient = kakaoClient; + this.kakaoProperties = kakaoProperties; + this.memberService = memberService; + this.jwtProvider = jwtProvider; + } + + @Transactional + public LoginResponse kakaoLogin(final String authorizationCode){ + final KakaoToken kakaoToken = getToken(authorizationCode); + final KakaoInfo kakaoInfo = getInfo(kakaoToken); + + final KakaoAccount kakaoAccount = kakaoInfo.getKakaoAccount(); + final Member member = memberService.findOrCreateMember(kakaoAccount); + + final Token accessToken = jwtProvider.generateToken(member, kakaoToken.getAccessToken()); + + return new LoginResponse(accessToken.getValue()); + } + + private KakaoToken getToken(String authorizationCode) { + try { + return kakaoClient.getToken( + new URI(kakaoProperties.getTokenUrl()), + authorizationCode, + kakaoProperties.getClientId(), + kakaoProperties.getRedirectUri(), + kakaoProperties.getGrantType()); + } catch (URISyntaxException e) { + throw new InvalidCredentialsException(e); + } + } + + private KakaoInfo getInfo(KakaoToken kakaoToken) { + try { + return kakaoClient.getKakaoInfo( + new URI(kakaoProperties.getUserInfoUrl()), + getBearerToken(kakaoToken)); + } catch (URISyntaxException e) { + throw new InvalidCredentialsException(e); + } + } + + private String getBearerToken(KakaoToken kakaoToken) { + return kakaoToken.getTokenType() + " " + kakaoToken.getAccessToken(); + } +} From 12cfa989e567ecb7a972103e399559175f7298e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=82=98=EC=A0=9C=EB=B2=95?= Date: Fri, 26 Jul 2024 16:19:09 +0900 Subject: [PATCH 011/138] =?UTF-8?q?feat:=20LoginResponse.java=20=ED=95=84?= =?UTF-8?q?=EB=93=9C=EB=AA=85=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/gift/web/dto/response/LoginResponse.java | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/main/java/gift/web/dto/response/LoginResponse.java b/src/main/java/gift/web/dto/response/LoginResponse.java index bdb8b444a..90173ab68 100644 --- a/src/main/java/gift/web/dto/response/LoginResponse.java +++ b/src/main/java/gift/web/dto/response/LoginResponse.java @@ -1,19 +1,18 @@ package gift.web.dto.response; -import gift.authentication.token.Token; - public class LoginResponse { - private Token token; + private String accessToken; private LoginResponse() { } - public LoginResponse(Token token) { - this.token = token; + public LoginResponse(String accessToken) { + this.accessToken = accessToken; } - public Token getToken() { - return token; + public String getAccessToken() { + return accessToken; } + } From 2fb0b8d7ee520ffccef354c694dbb5e65f328cca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=82=98=EC=A0=9C=EB=B2=95?= Date: Fri, 26 Jul 2024 16:19:51 +0900 Subject: [PATCH 012/138] =?UTF-8?q?feat:=20MemberService.java=20findOrCrea?= =?UTF-8?q?teMember()=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/gift/service/MemberService.java | 29 +++++-------------- 1 file changed, 8 insertions(+), 21 deletions(-) diff --git a/src/main/java/gift/service/MemberService.java b/src/main/java/gift/service/MemberService.java index a2aef65cd..f092f2130 100644 --- a/src/main/java/gift/service/MemberService.java +++ b/src/main/java/gift/service/MemberService.java @@ -1,17 +1,13 @@ package gift.service; -import gift.authentication.token.JwtProvider; -import gift.authentication.token.Token; import gift.domain.Member; import gift.domain.vo.Email; import gift.repository.MemberRepository; import gift.repository.WishProductRepository; -import gift.web.dto.request.LoginRequest; +import gift.web.client.dto.KakaoAccount; import gift.web.dto.request.member.CreateMemberRequest; -import gift.web.dto.response.LoginResponse; import gift.web.dto.response.member.CreateMemberResponse; import gift.web.dto.response.member.ReadMemberResponse; -import gift.web.validation.exception.client.IncorrectEmailException; import java.util.NoSuchElementException; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -22,14 +18,10 @@ public class MemberService { private final MemberRepository memberRepository; private final WishProductRepository wishProductRepository; - private final JwtProvider jwtProvider; - public MemberService(MemberRepository memberRepository, - WishProductRepository wishProductRepository, - JwtProvider jwtProvider) { + public MemberService(MemberRepository memberRepository, WishProductRepository wishProductRepository) { this.memberRepository = memberRepository; this.wishProductRepository = wishProductRepository; - this.jwtProvider = jwtProvider; } @Transactional @@ -45,20 +37,15 @@ public ReadMemberResponse readMember(Long id) { return ReadMemberResponse.fromEntity(member); } + public Member findOrCreateMember(KakaoAccount kakaoAccount) { + String email = kakaoAccount.getEmail(); + return memberRepository.findByEmail(Email.from(email)) + .orElseGet(() -> memberRepository.save(Member.from(kakaoAccount))); + } + public void deleteMember(Long id) { Member member = memberRepository.findById(id).orElseThrow(NoSuchElementException::new); wishProductRepository.deleteAllByMemberId(id); memberRepository.delete(member); } - - public LoginResponse login(LoginRequest request) { - Email email = Email.from(request.getEmail()); - Member member = memberRepository.findByEmail(email).orElseThrow(() -> new IncorrectEmailException(email.getValue())); - - member.matchPassword(request.getPassword()); - - Token token = jwtProvider.generateToken(member); - - return new LoginResponse(token); - } } From 6f2c4b10b536bc97019f4a522c526817c5bf82c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=82=98=EC=A0=9C=EB=B2=95?= Date: Fri, 26 Jul 2024 16:22:18 +0900 Subject: [PATCH 013/138] =?UTF-8?q?feat:=20Member.java=20Password=20?= =?UTF-8?q?=ED=95=84=EB=93=9C=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/gift/domain/Member.java | 22 ---------------------- 1 file changed, 22 deletions(-) diff --git a/src/main/java/gift/domain/Member.java b/src/main/java/gift/domain/Member.java index bdc25c983..9807a8417 100644 --- a/src/main/java/gift/domain/Member.java +++ b/src/main/java/gift/domain/Member.java @@ -2,8 +2,6 @@ import gift.domain.base.BaseTimeEntity; import gift.domain.vo.Email; -import gift.domain.vo.Password; -import gift.web.validation.exception.client.IncorrectPasswordException; import jakarta.persistence.Column; import jakarta.persistence.Embedded; import jakarta.persistence.Entity; @@ -14,9 +12,6 @@ public class Member extends BaseTimeEntity { @Embedded private Email email; - @Embedded - private Password password; - @Column(nullable = false) private String name; @@ -26,7 +21,6 @@ protected Member() { public static class Builder extends BaseTimeEntity.Builder { private Email email; - private Password password; private String name; public Builder email(Email email) { @@ -34,11 +28,6 @@ public Builder email(Email email) { return this; } - public Builder password(Password password) { - this.password = password; - return this; - } - public Builder name(String name) { this.name = name; return this; @@ -58,7 +47,6 @@ public Member build() { private Member(Builder builder) { super(builder); email = builder.email; - password = builder.password; name = builder.name; } @@ -66,17 +54,7 @@ public Email getEmail() { return email; } - public Password getPassword() { - return password; - } - public String getName() { return name; } - - public void matchPassword(String password) { - if (!this.password.matches(password)) { - throw new IncorrectPasswordException(); - } - } } From 1102c74dadbf24d14b7ee66f398505342a5a4a5c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=82=98=EC=A0=9C=EB=B2=95?= Date: Fri, 26 Jul 2024 16:22:37 +0900 Subject: [PATCH 014/138] =?UTF-8?q?feat:=20KakaoAccount.java=20toMember()?= =?UTF-8?q?=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/gift/web/client/dto/KakaoAccount.java | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/main/java/gift/web/client/dto/KakaoAccount.java b/src/main/java/gift/web/client/dto/KakaoAccount.java index c9c382c79..915204a4e 100644 --- a/src/main/java/gift/web/client/dto/KakaoAccount.java +++ b/src/main/java/gift/web/client/dto/KakaoAccount.java @@ -1,5 +1,8 @@ package gift.web.client.dto; +import gift.domain.Member; +import gift.domain.vo.Email; + public class KakaoAccount { private KakaoProfile profile; @@ -30,4 +33,11 @@ public Boolean getEmailValid() { public Boolean getEmailVerified() { return isEmailVerified; } + + public Member toMember() { + return new Member.Builder() + .name(profile.getNickname()) + .email(Email.from(email)) + .build(); + } } From a83c6544e601b9807d799fbf611e06e032dd3353 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=82=98=EC=A0=9C=EB=B2=95?= Date: Fri, 26 Jul 2024 16:23:28 +0900 Subject: [PATCH 015/138] =?UTF-8?q?feat:=20AuthenticationFilter.java=20ign?= =?UTF-8?q?orePaths=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - /api/login/oauth2/kakao 추가 --- .../java/gift/authentication/filter/AuthenticationFilter.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/gift/authentication/filter/AuthenticationFilter.java b/src/main/java/gift/authentication/filter/AuthenticationFilter.java index 19386bfe2..c95db07d2 100644 --- a/src/main/java/gift/authentication/filter/AuthenticationFilter.java +++ b/src/main/java/gift/authentication/filter/AuthenticationFilter.java @@ -16,7 +16,7 @@ public class AuthenticationFilter extends OncePerRequestFilter { - private final List ignorePaths = List.of("/api/members/login", "/api/members/register"); + private final List ignorePaths = List.of("/api/login/oauth2/kakao"); private final String AUTHORIZATION_HEADER = "Authorization"; private final String BEARER = "Bearer "; private final JwtResolver jwtResolver; From f2e2b9315df5c65a20b843d3bc860bc7855559d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=82=98=EC=A0=9C=EB=B2=95?= Date: Fri, 26 Jul 2024 16:23:44 +0900 Subject: [PATCH 016/138] =?UTF-8?q?feat:=20CreateMemberRequest.java=20Pass?= =?UTF-8?q?word=20=ED=95=84=EB=93=9C=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../web/dto/request/member/CreateMemberRequest.java | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/src/main/java/gift/web/dto/request/member/CreateMemberRequest.java b/src/main/java/gift/web/dto/request/member/CreateMemberRequest.java index 6c7d053ba..53ca2ab7d 100644 --- a/src/main/java/gift/web/dto/request/member/CreateMemberRequest.java +++ b/src/main/java/gift/web/dto/request/member/CreateMemberRequest.java @@ -1,7 +1,6 @@ package gift.web.dto.request.member; import gift.domain.Member; -import gift.web.validation.constraints.Password; import jakarta.validation.constraints.Email; public class CreateMemberRequest { @@ -9,14 +8,10 @@ public class CreateMemberRequest { @Email private String email; - @Password - private String password; - private String name; - public CreateMemberRequest(String email, String password, String name) { + public CreateMemberRequest(String email, String name) { this.email = email; - this.password = password; this.name = name; } @@ -24,10 +19,6 @@ public String getEmail() { return email; } - public String getPassword() { - return password; - } - public String getName() { return name; } @@ -35,7 +26,6 @@ public String getName() { public Member toEntity() { return new Member.Builder() .email(gift.domain.vo.Email.from(this.email)) - .password(gift.domain.vo.Password.from(this.password)) .name(this.name) .build(); } From 489f789f8da8c82133c76a3e8d09a1640844cf01 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=82=98=EC=A0=9C=EB=B2=95?= Date: Fri, 26 Jul 2024 16:24:09 +0900 Subject: [PATCH 017/138] =?UTF-8?q?feat:=20WebConfig.java=20feignClient=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/gift/config/WebConfig.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/main/java/gift/config/WebConfig.java b/src/main/java/gift/config/WebConfig.java index e5dc82b40..41ac4ada0 100644 --- a/src/main/java/gift/config/WebConfig.java +++ b/src/main/java/gift/config/WebConfig.java @@ -1,5 +1,6 @@ package gift.config; +import feign.Client; import gift.authentication.filter.AuthenticationExceptionHandlerFilter; import gift.authentication.filter.AuthenticationFilter; import gift.authentication.token.JwtResolver; @@ -43,4 +44,8 @@ public void addArgumentResolvers(List resolvers) resolvers.add(loginUserArgumentResolver); } + @Bean + public Client feignClient() { + return new Client.Default(null, null); + } } From 8734b45c7ff42e5f1099120d70f5506623057774 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=82=98=EC=A0=9C=EB=B2=95?= Date: Fri, 26 Jul 2024 16:24:44 +0900 Subject: [PATCH 018/138] =?UTF-8?q?feat:=20data.sql=20Member=20=ED=85=8C?= =?UTF-8?q?=EC=9D=B4=EB=B8=94=20password=20=ED=95=84=EB=93=9C=20=EC=82=AD?= =?UTF-8?q?=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/resources/sql/data.sql | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/resources/sql/data.sql b/src/main/resources/sql/data.sql index f596a8793..567759f52 100644 --- a/src/main/resources/sql/data.sql +++ b/src/main/resources/sql/data.sql @@ -51,11 +51,11 @@ VALUES ('Product 49', 49000, 'https://via.placeholder.com/4950', 3, NOW(), 1, NOW(), 1), ('Product 50', 50000, 'https://via.placeholder.com/5050', 3, NOW(), 1, NOW(), 1); -INSERT INTO member(name, email, password, created_at, updated_at) +INSERT INTO member(name, email, created_at, updated_at) VALUES - ('Member 1', 'member01@gmail.com', 'member010101', NOW(), NOW()), - ('Member 2', 'member02@gmail.com', 'member020202', NOW(), NOW()), - ('Member 3', 'member03@gmail.com', 'member030303', NOW(), NOW()); + ('Member 1', 'member01@gmail.com', NOW(), NOW()), + ('Member 2', 'member02@gmail.com', NOW(), NOW()), + ('Member 3', 'member03@gmail.com', NOW(), NOW()); INSERT INTO wish_product(member_id, product_id, quantity, created_at, created_by, updated_at, updated_by) VALUES From d712d3fc8d8a165d4df730deb6ce926247b51434 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=82=98=EC=A0=9C=EB=B2=95?= Date: Fri, 26 Jul 2024 16:25:15 +0900 Subject: [PATCH 019/138] =?UTF-8?q?feat:=20JwtProvider.java=20Token=20?= =?UTF-8?q?=EC=83=9D=EC=84=B1=20=EC=8B=9C,=20SocialToken=20=ED=81=B4?= =?UTF-8?q?=EB=A0=88=EC=9E=84=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../gift/authentication/token/JwtProvider.java | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/main/java/gift/authentication/token/JwtProvider.java b/src/main/java/gift/authentication/token/JwtProvider.java index 1080dd9c9..3c2003b83 100644 --- a/src/main/java/gift/authentication/token/JwtProvider.java +++ b/src/main/java/gift/authentication/token/JwtProvider.java @@ -18,6 +18,7 @@ public class JwtProvider { private long expirationTime; private final String MEMBER_ID_CLAIM_KEY = "memberId"; + private final String SOCIAL_TOKEN_CLAIM_KEY = "socialToken"; public JwtProvider(@Value("${jwt.secretkey}") String secret) { key = Keys.hmacShaKeyFor(Decoders.BASE64.decode(secret)); @@ -36,4 +37,18 @@ public Token generateToken(Member member) { .compact()); } + public Token generateToken(Member member, String socialToken) { + long nowMillis = System.currentTimeMillis(); + Date now = new Date(nowMillis); + Date expiration = new Date(nowMillis + expirationTime); + + return Token.from(Jwts.builder() + .claim(MEMBER_ID_CLAIM_KEY, member.getId()) + .claim(SOCIAL_TOKEN_CLAIM_KEY, socialToken) + .issuedAt(now) + .expiration(expiration) + .signWith(key) + .compact()); + } + } From db2e428e2dca015081424be231c0972186076f75 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=82=98=EC=A0=9C=EB=B2=95?= Date: Fri, 26 Jul 2024 16:25:34 +0900 Subject: [PATCH 020/138] =?UTF-8?q?feat:=20JwtResolver.java=20resolveSocia?= =?UTF-8?q?lToken=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/gift/authentication/token/JwtResolver.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/main/java/gift/authentication/token/JwtResolver.java b/src/main/java/gift/authentication/token/JwtResolver.java index 998484281..faeb82d42 100644 --- a/src/main/java/gift/authentication/token/JwtResolver.java +++ b/src/main/java/gift/authentication/token/JwtResolver.java @@ -14,6 +14,7 @@ public class JwtResolver { private final SecretKey key; private final String MEMBER_ID_CLAIM_KEY = "memberId"; + private final String SOCIAL_TOKEN_CLAIM_KEY = "socialToken"; public JwtResolver(@Value("${jwt.secretkey}") String secret) { this.key = Keys.hmacShaKeyFor(Decoders.BASE64.decode(secret)); @@ -31,4 +32,8 @@ public Optional resolveId(Token token) { return Optional.ofNullable(resolve(token).get(MEMBER_ID_CLAIM_KEY, Long.class)); } + public Optional resolveSocialToken(Token token) { + return Optional.ofNullable(resolve(token).get(SOCIAL_TOKEN_CLAIM_KEY, String.class)); + } + } From 6d6c1ae35d33fb2e519bc4b5629b1ac49ee33258 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=82=98=EC=A0=9C=EB=B2=95?= Date: Fri, 26 Jul 2024 16:26:16 +0900 Subject: [PATCH 021/138] =?UTF-8?q?refactor:=20MemberService.java=20findOr?= =?UTF-8?q?CreateMember=20=ED=9A=8C=EC=9B=90=20=EC=83=9D=EC=84=B1=20?= =?UTF-8?q?=EC=8B=9C=20dto=20=EC=82=AC=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/gift/service/MemberService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/gift/service/MemberService.java b/src/main/java/gift/service/MemberService.java index f092f2130..ec7699cee 100644 --- a/src/main/java/gift/service/MemberService.java +++ b/src/main/java/gift/service/MemberService.java @@ -40,7 +40,7 @@ public ReadMemberResponse readMember(Long id) { public Member findOrCreateMember(KakaoAccount kakaoAccount) { String email = kakaoAccount.getEmail(); return memberRepository.findByEmail(Email.from(email)) - .orElseGet(() -> memberRepository.save(Member.from(kakaoAccount))); + .orElseGet(() -> memberRepository.save(kakaoAccount.toMember())); } public void deleteMember(Long id) { From 60a31a455069e3d82d1fba46f725ec4d1ba0a72d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=82=98=EC=A0=9C=EB=B2=95?= Date: Fri, 26 Jul 2024 16:26:36 +0900 Subject: [PATCH 022/138] =?UTF-8?q?feat:=20ReadMemberResponse.java=20passw?= =?UTF-8?q?ord=20=ED=95=84=EB=93=9C=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../web/dto/response/member/ReadMemberResponse.java | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/src/main/java/gift/web/dto/response/member/ReadMemberResponse.java b/src/main/java/gift/web/dto/response/member/ReadMemberResponse.java index adfd869fd..1be0d317c 100644 --- a/src/main/java/gift/web/dto/response/member/ReadMemberResponse.java +++ b/src/main/java/gift/web/dto/response/member/ReadMemberResponse.java @@ -6,18 +6,16 @@ public class ReadMemberResponse { private Long id; private String email; - private String password; private String name; - private ReadMemberResponse(Long id, String email, String password, String name) { + private ReadMemberResponse(Long id, String email, String name) { this.id = id; this.email = email; - this.password = password; this.name = name; } public static ReadMemberResponse fromEntity(Member member) { - return new ReadMemberResponse(member.getId(), member.getEmail().getValue(), member.getPassword().getValue(), + return new ReadMemberResponse(member.getId(), member.getEmail().getValue(), member.getName()); } @@ -29,10 +27,6 @@ public String getEmail() { return email; } - public String getPassword() { - return password; - } - public String getName() { return name; } From f5e2c2da38b1a22221de08840300b644a9f5dce0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=82=98=EC=A0=9C=EB=B2=95?= Date: Fri, 26 Jul 2024 16:26:56 +0900 Subject: [PATCH 023/138] =?UTF-8?q?remove:=20=EC=82=AC=EC=9A=A9=ED=95=98?= =?UTF-8?q?=EC=A7=80=20=EC=95=8A=EB=8A=94=20=ED=8C=8C=EC=9D=BC=20=EC=A0=9C?= =?UTF-8?q?=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - LoginRequest.java - Password.java - schema.sql --- src/main/java/gift/domain/vo/Password.java | 48 ------------------- .../gift/web/dto/request/LoginRequest.java | 23 --------- src/main/resources/sql/schema.sql | 25 ---------- 3 files changed, 96 deletions(-) delete mode 100644 src/main/java/gift/domain/vo/Password.java delete mode 100644 src/main/java/gift/web/dto/request/LoginRequest.java delete mode 100644 src/main/resources/sql/schema.sql diff --git a/src/main/java/gift/domain/vo/Password.java b/src/main/java/gift/domain/vo/Password.java deleted file mode 100644 index d72f9804b..000000000 --- a/src/main/java/gift/domain/vo/Password.java +++ /dev/null @@ -1,48 +0,0 @@ -package gift.domain.vo; - -import jakarta.persistence.Column; -import jakarta.persistence.Embeddable; -import java.util.Objects; - -@Embeddable -public class Password { - - @Column(name = "password", nullable = false) - private String value; - - protected Password() { - } - - private Password(String value) { - this.value = value; - } - - public static Password from(String password) { - return new Password(password); - } - - public boolean matches(String password) { - return this.value.equals(password); - } - - public String getValue() { - return value; - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - Password p = (Password) o; - return Objects.equals(this.value, p.value); - } - - @Override - public int hashCode() { - return Objects.hash(value); - } -} diff --git a/src/main/java/gift/web/dto/request/LoginRequest.java b/src/main/java/gift/web/dto/request/LoginRequest.java deleted file mode 100644 index 7a152dbc1..000000000 --- a/src/main/java/gift/web/dto/request/LoginRequest.java +++ /dev/null @@ -1,23 +0,0 @@ -package gift.web.dto.request; - -import jakarta.validation.constraints.Email; - -public class LoginRequest { - - @Email - private final String email; - private final String password; - - public LoginRequest(String email, String password) { - this.email = email; - this.password = password; - } - - public String getEmail() { - return email; - } - - public String getPassword() { - return password; - } -} diff --git a/src/main/resources/sql/schema.sql b/src/main/resources/sql/schema.sql deleted file mode 100644 index 9eac7df36..000000000 --- a/src/main/resources/sql/schema.sql +++ /dev/null @@ -1,25 +0,0 @@ -DROP TABLE IF EXISTS Product; -CREATE TABLE product ( - product_id BIGINT AUTO_INCREMENT PRIMARY KEY, - name VARCHAR(255) NOT NULL, - price INT NOT NULL, - image_url VARCHAR(255) NOT NULL -); - -DROP TABLE IF EXISTS member; -CREATE TABLE member ( - member_id BIGINT AUTO_INCREMENT PRIMARY KEY, - name VARCHAR(255) NOT NULL, - email VARCHAR(255) NOT NULL UNIQUE, - password VARCHAR(255) NOT NULL -); - -DROP TABLE IF EXISTS wish_product; -CREATE TABLE wish_product ( - wish_product_id BIGINT AUTO_INCREMENT PRIMARY KEY, - member_id BIGINT NOT NULL, - product_id BIGINT NOT NULL, - quantity INT NOT NULL, - FOREIGN KEY (member_id) REFERENCES member(member_id), - FOREIGN KEY (product_id) REFERENCES product(product_id) -); \ No newline at end of file From 2fbf175da94a0acc7a146811f3646da1fd7244a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=82=98=EC=A0=9C=EB=B2=95?= Date: Fri, 26 Jul 2024 16:27:10 +0900 Subject: [PATCH 024/138] =?UTF-8?q?feat:=20LoginController.java=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../web/controller/api/LoginController.java | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 src/main/java/gift/web/controller/api/LoginController.java diff --git a/src/main/java/gift/web/controller/api/LoginController.java b/src/main/java/gift/web/controller/api/LoginController.java new file mode 100644 index 000000000..78ddfec64 --- /dev/null +++ b/src/main/java/gift/web/controller/api/LoginController.java @@ -0,0 +1,24 @@ +package gift.web.controller.api; + +import gift.service.LoginService; +import gift.web.dto.response.LoginResponse; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/api/login") +public class LoginController { + + private final LoginService loginService; + + public LoginController(LoginService loginService) { + this.loginService = loginService; + } + + @GetMapping("/oauth2/kakao") + public LoginResponse kakaoLogin(String code){ + return loginService.kakaoLogin(code); + } + +} From 4df68c13061b725a09ce0dac228e95c7d5379549 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=82=98=EC=A0=9C=EB=B2=95?= Date: Fri, 26 Jul 2024 16:27:36 +0900 Subject: [PATCH 025/138] =?UTF-8?q?feat:=20Application.java=20=EC=96=B4?= =?UTF-8?q?=EB=85=B8=ED=85=8C=EC=9D=B4=EC=85=98=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - @ConfigurationPropertiesScan - @EnableFeignClients --- src/main/java/gift/Application.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/main/java/gift/Application.java b/src/main/java/gift/Application.java index 037286d2c..62085aa31 100644 --- a/src/main/java/gift/Application.java +++ b/src/main/java/gift/Application.java @@ -2,9 +2,13 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.context.properties.ConfigurationPropertiesScan; +import org.springframework.cloud.openfeign.EnableFeignClients; import org.springframework.data.jpa.repository.config.EnableJpaAuditing; @EnableJpaAuditing(auditorAwareRef = "memberIdAuditorAware") +@ConfigurationPropertiesScan +@EnableFeignClients @SpringBootApplication public class Application { From 0e270a12ac8882eb67b226d0601a592dc1d92f56 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=82=98=EC=A0=9C=EB=B2=95?= Date: Fri, 26 Jul 2024 16:28:01 +0900 Subject: [PATCH 026/138] =?UTF-8?q?test:=20MemberDummyDataProvider.java=20?= =?UTF-8?q?Password=20=ED=95=84=EB=93=9C=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/test/java/gift/utils/MemberDummyDataProvider.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/test/java/gift/utils/MemberDummyDataProvider.java b/src/test/java/gift/utils/MemberDummyDataProvider.java index cf340e8c2..0d1856e94 100644 --- a/src/test/java/gift/utils/MemberDummyDataProvider.java +++ b/src/test/java/gift/utils/MemberDummyDataProvider.java @@ -24,7 +24,7 @@ public void run(int quantity) { } private void doRun(int quantity) { - String sql = "insert into member (name, email, password, created_at, updated_at) values (?, ?, ?, ?, ?)"; + String sql = "insert into member (name, email, created_at, updated_at) values (?, ?, ?, ?)"; jdbcTemplate.batchUpdate(sql, getBatchPreparedStatementSetter(quantity)); } @@ -34,7 +34,6 @@ private static BatchPreparedStatementSetter getBatchPreparedStatementSetter(int public void setValues(PreparedStatement ps, int i) throws SQLException { ps.setString(1, "member" + i); ps.setString(2, "member" + i + "@gmail.com"); - ps.setString(3, "member" + i + "0"); ps.setTimestamp(4, Timestamp.valueOf(LocalDateTime.now())); ps.setTimestamp(5, Timestamp.valueOf(LocalDateTime.now())); } From 512fb5c1663d690c6b107a61975c28489b233dc7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=82=98=EC=A0=9C=EB=B2=95?= Date: Fri, 26 Jul 2024 16:28:27 +0900 Subject: [PATCH 027/138] =?UTF-8?q?test:=20MemberServiceTest.java=20Member?= =?UTF-8?q?Service=20=EB=B3=80=EA=B2=BD=EC=97=90=20=EB=94=B0=EB=A5=B8=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/gift/service/MemberServiceTest.java | 32 ++----------------- 1 file changed, 3 insertions(+), 29 deletions(-) diff --git a/src/test/java/gift/service/MemberServiceTest.java b/src/test/java/gift/service/MemberServiceTest.java index 09c2e0db6..a43099776 100644 --- a/src/test/java/gift/service/MemberServiceTest.java +++ b/src/test/java/gift/service/MemberServiceTest.java @@ -7,15 +7,11 @@ import static org.mockito.BDDMockito.given; import gift.authentication.token.JwtProvider; -import gift.authentication.token.Token; import gift.domain.Member; import gift.domain.vo.Email; -import gift.domain.vo.Password; import gift.repository.MemberRepository; import gift.repository.WishProductRepository; -import gift.web.dto.request.LoginRequest; import gift.web.dto.request.member.CreateMemberRequest; -import gift.web.dto.response.LoginResponse; import gift.web.dto.response.member.CreateMemberResponse; import gift.web.dto.response.member.ReadMemberResponse; import java.util.Optional; @@ -45,7 +41,7 @@ class MemberServiceTest { @DisplayName("회원 생성 요청이 정상적일 때, 회원을 성공적으로 생성합니다.") void createMember() { //given - CreateMemberRequest request = new CreateMemberRequest("member01@naver.com", "password01", "이름"); + CreateMemberRequest request = new CreateMemberRequest("member01@naver.com", "이름"); given(memberRepository.save(any())).willReturn( new Member.Builder().id(1L).name(request.getName()).email(Email.from(request.getEmail())).build()); @@ -64,8 +60,7 @@ void createMember() { @DisplayName("회원 조회 요청이 정상적일 때, 회원을 성공적으로 조회합니다.") void readMember() { //given - Member member = new Member.Builder().id(1L).name("이름").email(Email.from("member01@naver.com")).password( - Password.from("password01")).build(); + Member member = new Member.Builder().id(1L).name("이름").email(Email.from("member01@naver.com")).build(); given(memberRepository.findById(1L)).willReturn(Optional.of(member)); //when @@ -83,8 +78,7 @@ void readMember() { @DisplayName("회원 삭제 요청이 정상적일 때, 회원을 성공적으로 삭제합니다.") void deleteMember() { //given - Member member = new Member.Builder().id(1L).name("이름").email(Email.from("member01@naver.com")).password( - Password.from("password01")).build(); + Member member = new Member.Builder().id(1L).name("이름").email(Email.from("member01@naver.com")).build(); given(memberRepository.findById(1L)).willReturn(Optional.of(member)); //when @@ -92,24 +86,4 @@ void deleteMember() { assertDoesNotThrow(() -> memberService.deleteMember(1L)); } - @Test - @DisplayName("로그인 요청이 정상적일 때, 로그인을 성공적으로 수행합니다.") - void login() { - //given - String email = "member01@naver.com"; - String password = "password01"; - Member member = new Member.Builder().id(1L).email(Email.from(email)).password( - Password.from(password)).build(); - - LoginRequest request = new LoginRequest(email, password); - - given(memberRepository.findByEmail(Email.from(email))).willReturn(Optional.of(member)); - given(jwtProvider.generateToken(member)).willReturn(Token.from("token")); - - //when - LoginResponse response = memberService.login(request); - - //then - assertThat(response.getToken()).isNotNull(); - } } \ No newline at end of file From 96c3bc591ece65f3c5df7bc473a707b380099db4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=82=98=EC=A0=9C=EB=B2=95?= Date: Fri, 26 Jul 2024 16:29:03 +0900 Subject: [PATCH 028/138] =?UTF-8?q?test:=20MemberApiControllerTest.java=20?= =?UTF-8?q?=EC=A3=BC=EC=84=9D=20=EC=B2=98=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Member의 password 필드 삭제로 인한 테스트 코드 수정이 필요함 --- .../api/MemberApiControllerTest.java | 413 ++++++++---------- 1 file changed, 187 insertions(+), 226 deletions(-) diff --git a/src/test/java/gift/web/controller/api/MemberApiControllerTest.java b/src/test/java/gift/web/controller/api/MemberApiControllerTest.java index 1dbea41d9..2518c5000 100644 --- a/src/test/java/gift/web/controller/api/MemberApiControllerTest.java +++ b/src/test/java/gift/web/controller/api/MemberApiControllerTest.java @@ -1,226 +1,187 @@ -package gift.web.controller.api; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertAll; -import static org.junit.jupiter.api.Assertions.assertIterableEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import gift.authentication.token.Token; -import gift.domain.Member; -import gift.repository.MemberRepository; -import gift.service.MemberService; -import gift.service.WishProductService; -import gift.utils.CategoryDummyDataProvider; -import gift.utils.DatabaseCleanup; -import gift.utils.MemberDummyDataProvider; -import gift.utils.ProductDummyDataProvider; -import gift.utils.WishProductDummyDataProvider; -import gift.web.dto.request.LoginRequest; -import gift.web.dto.request.member.CreateMemberRequest; -import gift.web.dto.request.wishproduct.UpdateWishProductRequest; -import gift.web.dto.response.LoginResponse; -import gift.web.dto.response.member.CreateMemberResponse; -import gift.web.dto.response.member.ReadMemberResponse; -import gift.web.dto.response.wishproduct.ReadAllWishProductsResponse; -import gift.web.dto.response.wishproduct.UpdateWishProductResponse; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; -import org.springframework.boot.test.web.client.TestRestTemplate; -import org.springframework.boot.test.web.server.LocalServerPort; -import org.springframework.data.domain.PageRequest; -import org.springframework.http.HttpEntity; -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpMethod; -import org.springframework.http.ResponseEntity; -import org.springframework.test.context.ActiveProfiles; - -@ActiveProfiles("test") -@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT) -class MemberApiControllerTest { - - @LocalServerPort - private int port; - - @Autowired - private TestRestTemplate restTemplate; - - @Autowired - private MemberDummyDataProvider memberDummyDataProvider; - - @Autowired - private ProductDummyDataProvider productDummyDataProvider; - - @Autowired - private WishProductDummyDataProvider wishProductDummyDataProvider; - - @Autowired - private CategoryDummyDataProvider categoryDummyDataProvider; - - @Autowired - private DatabaseCleanup databaseCleanup; - - @Autowired - private MemberService memberService; - - @Autowired - private MemberRepository memberRepository; - - @Autowired - private WishProductService wishProductService; - - //테스트용 회원 - private Member member; - private Token token; - - @BeforeEach - void setUp() { - insertDummyData(100); - member = getTestMember(1L); - token = getAccessToken(); - } - - private Member getTestMember(Long id) { - return memberRepository.findById(id) - .orElseThrow(() -> new IllegalArgumentException("ID: " + id +"인 회원이 존재하지 않습니다.")); - } - - private Token getAccessToken() { - LoginRequest loginRequest = new LoginRequest( - member.getEmail().getValue(), - member.getPassword().getValue() - ); - LoginResponse loginResponse = memberService.login(loginRequest); - return loginResponse.getToken(); - } - - private void insertDummyData(int quantity) { - if (quantity < 2) { - throw new IllegalArgumentException("quantity는 2 이상이어야 합니다."); - } - memberDummyDataProvider.run(quantity); - productDummyDataProvider.run(quantity); - wishProductDummyDataProvider.run(quantity); - categoryDummyDataProvider.run(quantity); - } - - @AfterEach - void tearDown() { - databaseCleanup.execute(); - } - - @Test - @DisplayName("회원 생성 요청에 대한 정상 응답") - void createMember() { - //given - CreateMemberRequest request = new CreateMemberRequest("test@gmail.com", "test1234", "test"); - String url = "http://localhost:" + port + "/api/members/register"; - - //when - ResponseEntity response = restTemplate.postForEntity(url, request, CreateMemberResponse.class); - - //then - Long newMemberId = response.getBody().getId(); - ReadMemberResponse findMember = memberService.readMember(newMemberId); - - assertAll( - () -> assertTrue(response.getStatusCode().is2xxSuccessful()), - () -> assertThat(newMemberId).isEqualTo(findMember.getId()), - () -> assertThat(request.getEmail()).isEqualTo(findMember.getEmail()), - () -> assertThat(request.getName()).isEqualTo(findMember.getName()), - () -> assertThat(request.getPassword()).isEqualTo(findMember.getPassword()) - ); - } - - @Test - @DisplayName("로그인 요청에 대한 정상 응답") - void login() { - //given - String url = "http://localhost:" + port + "/api/members/login"; - String email = member.getEmail().getValue(); - String password = member.getPassword().getValue(); - LoginRequest request = new LoginRequest(email, password); - - //when - ResponseEntity response = restTemplate.postForEntity(url, request, LoginResponse.class); - - //then - assertAll( - () -> assertTrue(response.getStatusCode().is2xxSuccessful()), - () -> assertThat(response.getBody().getToken()).isNotNull() - ); - } - - @Test - @DisplayName("위시 리스트 조회 요청에 대한 정상 응답") - void readWishProduct() { - //given - String url = "http://localhost:" + port + "/api/members/wishlist"; - HttpHeaders httpHeaders = getHttpHeaders(); - HttpEntity httpEntity = new HttpEntity(httpHeaders); - - PageRequest defaultPageRequest = PageRequest.of(0, 10); - ReadAllWishProductsResponse expectedWishProducts = wishProductService.readAllWishProducts(member.getId(), - defaultPageRequest); - - //when - ResponseEntity response = restTemplate.exchange(url, - HttpMethod.GET, httpEntity, ReadAllWishProductsResponse.class); - - //then - assertAll( - () -> assertTrue(response.getStatusCode().is2xxSuccessful()), - () -> assertIterableEquals(response.getBody().getWishlist(), expectedWishProducts.getWishlist()) - ); - } - - @Test - @DisplayName("위시 리스트 상품 수정 요청에 대한 정상 응답") - void updateWishProduct() { - //given - Long wishProductId = 1L; - String url = "http://localhost:" + port + "/api/members/wishlist/" + wishProductId; - HttpHeaders httpHeaders = getHttpHeaders(); - - UpdateWishProductRequest request = new UpdateWishProductRequest(3); - - HttpEntity httpEntity = new HttpEntity(request, httpHeaders); - - //when - ResponseEntity response = restTemplate.exchange(url, - HttpMethod.PUT, httpEntity, UpdateWishProductResponse.class); - - //then - assertAll( - () -> assertTrue(response.getStatusCode().is2xxSuccessful()), - () -> assertThat(response.getBody().getQuantity()).isEqualTo(request.getQuantity()) - ); - } - - @Test - @DisplayName("위시 리스트 상품 삭제 요청에 대한 정상 응답") - void deleteWishProduct() { - //given - Long wishProductId = 2L; - String url = "http://localhost:" + port + "/api/members/wishlist/" + wishProductId; - HttpHeaders httpHeaders = getHttpHeaders(); - HttpEntity httpEntity = new HttpEntity(httpHeaders); - - //when - ResponseEntity response = restTemplate.exchange(url, HttpMethod.DELETE, httpEntity, - Void.class); - - //then - assertTrue(response.getStatusCode().is2xxSuccessful()); - } - - private HttpHeaders getHttpHeaders() { - HttpHeaders httpHeaders = new HttpHeaders(); - httpHeaders.setBearerAuth(token.getValue()); - return httpHeaders; - } -} \ No newline at end of file +//package gift.web.controller.api; +// +//import static org.assertj.core.api.Assertions.assertThat; +//import static org.junit.jupiter.api.Assertions.assertAll; +//import static org.junit.jupiter.api.Assertions.assertIterableEquals; +//import static org.junit.jupiter.api.Assertions.assertTrue; +// +//import gift.authentication.token.Token; +//import gift.domain.Member; +//import gift.repository.MemberRepository; +//import gift.service.LoginService; +//import gift.service.MemberService; +//import gift.service.WishProductService; +//import gift.utils.CategoryDummyDataProvider; +//import gift.utils.DatabaseCleanup; +//import gift.utils.MemberDummyDataProvider; +//import gift.utils.ProductDummyDataProvider; +//import gift.utils.WishProductDummyDataProvider; +//import gift.web.dto.request.LoginRequest; +//import gift.web.dto.request.member.CreateMemberRequest; +//import gift.web.dto.request.wishproduct.UpdateWishProductRequest; +//import gift.web.dto.response.LoginResponse; +//import gift.web.dto.response.member.CreateMemberResponse; +//import gift.web.dto.response.member.ReadMemberResponse; +//import gift.web.dto.response.wishproduct.ReadAllWishProductsResponse; +//import gift.web.dto.response.wishproduct.UpdateWishProductResponse; +//import org.junit.jupiter.api.AfterEach; +//import org.junit.jupiter.api.BeforeEach; +//import org.junit.jupiter.api.DisplayName; +//import org.junit.jupiter.api.Test; +//import org.springframework.beans.factory.annotation.Autowired; +//import org.springframework.boot.test.context.SpringBootTest; +//import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; +//import org.springframework.boot.test.web.client.TestRestTemplate; +//import org.springframework.boot.test.web.server.LocalServerPort; +//import org.springframework.data.domain.PageRequest; +//import org.springframework.http.HttpEntity; +//import org.springframework.http.HttpHeaders; +//import org.springframework.http.HttpMethod; +//import org.springframework.http.ResponseEntity; +//import org.springframework.test.context.ActiveProfiles; +// +//@ActiveProfiles("test") +//@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT) +//class MemberApiControllerTest { +// +// @LocalServerPort +// private int port; +// +// @Autowired +// private TestRestTemplate restTemplate; +// +// @Autowired +// private MemberDummyDataProvider memberDummyDataProvider; +// +// @Autowired +// private ProductDummyDataProvider productDummyDataProvider; +// +// @Autowired +// private WishProductDummyDataProvider wishProductDummyDataProvider; +// +// @Autowired +// private CategoryDummyDataProvider categoryDummyDataProvider; +// +// @Autowired +// private DatabaseCleanup databaseCleanup; +// +// @Autowired +// private MemberService memberService; +// +// @Autowired +// private LoginService loginService; +// +// @Autowired +// private MemberRepository memberRepository; +// +// @Autowired +// private WishProductService wishProductService; +// +// //테스트용 회원 +// private Member member; +// private Token token; +// +// @BeforeEach +// void setUp() { +// insertDummyData(100); +// member = getTestMember(1L); +// token = getAccessToken(); +// } +// +// private Member getTestMember(Long id) { +// return memberRepository.findById(id) +// .orElseThrow(() -> new IllegalArgumentException("ID: " + id +"인 회원이 존재하지 않습니다.")); +// } +// +// private Token getAccessToken() { +// LoginRequest loginRequest = new LoginRequest( +// member.getEmail().getValue(), +// ); +// LoginResponse loginResponse = memberService.login(loginRequest); +// return loginResponse.getToken(); +// } +// +// private void insertDummyData(int quantity) { +// if (quantity < 2) { +// throw new IllegalArgumentException("quantity는 2 이상이어야 합니다."); +// } +// memberDummyDataProvider.run(quantity); +// productDummyDataProvider.run(quantity); +// wishProductDummyDataProvider.run(quantity); +// categoryDummyDataProvider.run(quantity); +// } +// +// @AfterEach +// void tearDown() { +// databaseCleanup.execute(); +// } +// +// @Test +// @DisplayName("위시 리스트 조회 요청에 대한 정상 응답") +// void readWishProduct() { +// //given +// String url = "http://localhost:" + port + "/api/members/wishlist"; +// HttpHeaders httpHeaders = getHttpHeaders(); +// HttpEntity httpEntity = new HttpEntity(httpHeaders); +// +// PageRequest defaultPageRequest = PageRequest.of(0, 10); +// ReadAllWishProductsResponse expectedWishProducts = wishProductService.readAllWishProducts(member.getId(), +// defaultPageRequest); +// +// //when +// ResponseEntity response = restTemplate.exchange(url, +// HttpMethod.GET, httpEntity, ReadAllWishProductsResponse.class); +// +// //then +// assertAll( +// () -> assertTrue(response.getStatusCode().is2xxSuccessful()), +// () -> assertIterableEquals(response.getBody().getWishlist(), expectedWishProducts.getWishlist()) +// ); +// } +// +// @Test +// @DisplayName("위시 리스트 상품 수정 요청에 대한 정상 응답") +// void updateWishProduct() { +// //given +// Long wishProductId = 1L; +// String url = "http://localhost:" + port + "/api/members/wishlist/" + wishProductId; +// HttpHeaders httpHeaders = getHttpHeaders(); +// +// UpdateWishProductRequest request = new UpdateWishProductRequest(3); +// +// HttpEntity httpEntity = new HttpEntity(request, httpHeaders); +// +// //when +// ResponseEntity response = restTemplate.exchange(url, +// HttpMethod.PUT, httpEntity, UpdateWishProductResponse.class); +// +// //then +// assertAll( +// () -> assertTrue(response.getStatusCode().is2xxSuccessful()), +// () -> assertThat(response.getBody().getQuantity()).isEqualTo(request.getQuantity()) +// ); +// } +// +// @Test +// @DisplayName("위시 리스트 상품 삭제 요청에 대한 정상 응답") +// void deleteWishProduct() { +// //given +// Long wishProductId = 2L; +// String url = "http://localhost:" + port + "/api/members/wishlist/" + wishProductId; +// HttpHeaders httpHeaders = getHttpHeaders(); +// HttpEntity httpEntity = new HttpEntity(httpHeaders); +// +// //when +// ResponseEntity response = restTemplate.exchange(url, HttpMethod.DELETE, httpEntity, +// Void.class); +// +// //then +// assertTrue(response.getStatusCode().is2xxSuccessful()); +// } +// +// private HttpHeaders getHttpHeaders() { +// HttpHeaders httpHeaders = new HttpHeaders(); +// httpHeaders.setBearerAuth(token.getValue()); +// return httpHeaders; +// } +//} \ No newline at end of file From 0fd0b11fafb4bdfcea82a4d0ed47b34a1d9d75c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=82=98=EC=A0=9C=EB=B2=95?= Date: Fri, 26 Jul 2024 16:57:25 +0900 Subject: [PATCH 029/138] =?UTF-8?q?feat:=20MemberApiController.java=20?= =?UTF-8?q?=EB=8B=A8=EC=9D=BC=20=ED=9A=8C=EC=9B=90=20=EC=A1=B0=ED=9A=8C=20?= =?UTF-8?q?API=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/api/MemberApiController.java | 24 ++++--------------- 1 file changed, 4 insertions(+), 20 deletions(-) diff --git a/src/main/java/gift/web/controller/api/MemberApiController.java b/src/main/java/gift/web/controller/api/MemberApiController.java index f0e468347..758d0e5fc 100644 --- a/src/main/java/gift/web/controller/api/MemberApiController.java +++ b/src/main/java/gift/web/controller/api/MemberApiController.java @@ -4,15 +4,10 @@ import gift.service.MemberService; import gift.service.WishProductService; import gift.web.dto.MemberDetails; -import gift.web.dto.request.LoginRequest; -import gift.web.dto.request.member.CreateMemberRequest; import gift.web.dto.request.wishproduct.UpdateWishProductRequest; -import gift.web.dto.response.LoginResponse; -import gift.web.dto.response.member.CreateMemberResponse; +import gift.web.dto.response.member.ReadMemberResponse; import gift.web.dto.response.wishproduct.ReadAllWishProductsResponse; import gift.web.dto.response.wishproduct.UpdateWishProductResponse; -import java.net.URI; -import java.net.URISyntaxException; import org.springframework.data.domain.Pageable; import org.springframework.data.web.PageableDefault; import org.springframework.http.ResponseEntity; @@ -20,7 +15,6 @@ import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PutMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; @@ -38,19 +32,9 @@ public MemberApiController(MemberService memberService, WishProductService wishP this.wishProductService = wishProductService; } - @PostMapping("/register") - public ResponseEntity createMember( - @Validated @RequestBody CreateMemberRequest request) - throws URISyntaxException { - CreateMemberResponse response = memberService.createMember(request); - - URI location = new URI("http://localhost:8080/api/members/" + response.getId()); - return ResponseEntity.created(location).body(response); - } - - @PostMapping("/login") - public ResponseEntity login(@Validated @RequestBody LoginRequest request) { - LoginResponse response = memberService.login(request); + @GetMapping("/{memberId}") + public ResponseEntity readMember(@PathVariable Long memberId) { + ReadMemberResponse response = memberService.readMember(memberId); return ResponseEntity.ok(response); } From 2df743f11ab91e42b267636a7ee38f2200700d3a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=82=98=EC=A0=9C=EB=B2=95?= Date: Fri, 26 Jul 2024 17:07:35 +0900 Subject: [PATCH 030/138] =?UTF-8?q?chore:=20.gitignore=20application-secre?= =?UTF-8?q?t.yml=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 281fb4a5b..701aa5216 100644 --- a/.gitignore +++ b/.gitignore @@ -40,4 +40,4 @@ out/ .DS_Store ### secret files ### -#src/main/resources/application-secret.yml \ No newline at end of file +src/main/resources/application-secret.yml \ No newline at end of file From 45ced4845e229f51c3c62d0bffdb7028482079d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=82=98=EC=A0=9C=EB=B2=95?= Date: Fri, 26 Jul 2024 17:07:51 +0900 Subject: [PATCH 031/138] removed cached --- gradlew.bat | 184 +++++++++++----------- src/main/resources/application-secret.yml | 8 - 2 files changed, 92 insertions(+), 100 deletions(-) delete mode 100644 src/main/resources/application-secret.yml diff --git a/gradlew.bat b/gradlew.bat index 6689b85be..93e3f59f1 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -1,92 +1,92 @@ -@rem -@rem Copyright 2015 the original author or authors. -@rem -@rem Licensed under the Apache License, Version 2.0 (the "License"); -@rem you may not use this file except in compliance with the License. -@rem You may obtain a copy of the License at -@rem -@rem https://www.apache.org/licenses/LICENSE-2.0 -@rem -@rem Unless required by applicable law or agreed to in writing, software -@rem distributed under the License is distributed on an "AS IS" BASIS, -@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -@rem See the License for the specific language governing permissions and -@rem limitations under the License. -@rem - -@if "%DEBUG%"=="" @echo off -@rem ########################################################################## -@rem -@rem Gradle startup script for Windows -@rem -@rem ########################################################################## - -@rem Set local scope for the variables with windows NT shell -if "%OS%"=="Windows_NT" setlocal - -set DIRNAME=%~dp0 -if "%DIRNAME%"=="" set DIRNAME=. -@rem This is normally unused -set APP_BASE_NAME=%~n0 -set APP_HOME=%DIRNAME% - -@rem Resolve any "." and ".." in APP_HOME to make it shorter. -for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi - -@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" - -@rem Find java.exe -if defined JAVA_HOME goto findJavaFromJavaHome - -set JAVA_EXE=java.exe -%JAVA_EXE% -version >NUL 2>&1 -if %ERRORLEVEL% equ 0 goto execute - -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:findJavaFromJavaHome -set JAVA_HOME=%JAVA_HOME:"=% -set JAVA_EXE=%JAVA_HOME%/bin/java.exe - -if exist "%JAVA_EXE%" goto execute - -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:execute -@rem Setup the command line - -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar - - -@rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* - -:end -@rem End local scope for the variables with windows NT shell -if %ERRORLEVEL% equ 0 goto mainEnd - -:fail -rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of -rem the _cmd.exe /c_ return code! -set EXIT_CODE=%ERRORLEVEL% -if %EXIT_CODE% equ 0 set EXIT_CODE=1 -if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% -exit /b %EXIT_CODE% - -:mainEnd -if "%OS%"=="Windows_NT" endlocal - -:omega +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 0 goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/src/main/resources/application-secret.yml b/src/main/resources/application-secret.yml deleted file mode 100644 index e776ae408..000000000 --- a/src/main/resources/application-secret.yml +++ /dev/null @@ -1,8 +0,0 @@ -spring: - datasource: - url: jdbc:h2:tcp://localhost/~/gift - username: sa - password: - -jwt: - secretKey: db8dc50b1bf35d72218b9961ed669ef3dabbd8ddd617c235baeb8a020c66179661e6e148b8c84163af02394a1c59a0af0c4566dc17325a03c77f2027f16d9b54 From f4a7c776179cf46d8263b021d4d455f48bd0b398 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=82=98=EC=A0=9C=EB=B2=95?= Date: Fri, 26 Jul 2024 17:49:41 +0900 Subject: [PATCH 032/138] =?UTF-8?q?docs:=20README.md=20step5=202=EB=8B=A8?= =?UTF-8?q?=EA=B3=84=20=EC=9A=94=EA=B5=AC=EC=82=AC=ED=95=AD=20=EB=B0=98?= =?UTF-8?q?=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 42 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 41 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 42b1f59e0..1ee7158b8 100644 --- a/README.md +++ b/README.md @@ -10,4 +10,44 @@ * 토큰 받기를 읽고 액세스 토큰을 추출한다. * 앱 키, 인가 코드가 절대 유출되지 않도록 한다. * 특히 시크릿 키는 GitHub나 클라이언트 코드 등 외부에서 볼 수 있는 곳에 추가하지 않는다. -* (선택) 인가 코드를 받는 방법이 불편한 경우 카카오 로그인 화면을 구현한다. \ No newline at end of file +* (선택) 인가 코드를 받는 방법이 불편한 경우 카카오 로그인 화면을 구현한다. + +### 🚀 2단계 - 주문하기 +*** +#### 기능 요구 사항 +카카오톡 메시지 API를 사용하여 주문하기 기능을 구현한다. + +* 주문할 때 수령인에게 보낼 메시지를 작성할 수 있다. +* 상품 옵션과 해당 수량을 선택하여 주문하면 해당 상품 옵션의 수량이 차감된다. +* 해당 상품이 위시 리스트에 있는 경우 위시 리스트에서 삭제한다. +* 나에게 보내기를 읽고 주문 내역을 카카오톡 메시지로 전송한다. + * 메시지는 메시지 템플릿의 기본 템플릿이나 사용자 정의 템플릿을 사용하여 자유롭게 작성한다. + +아래 예시와 같이 HTTP 메시지를 주고받도록 구현한다. + +##### Request +``` +POST /api/orders HTTP/1.1 +Authorization: Bearer {token} +Content-Type: application/json + +{ + "optionId": 1, + "quantity": 2, + "message": "Please handle this order with care." +} +``` + +##### Response +``` +HTTP/1.1 201 Created +Content-Type: application/json + +{ +"id": 1, +"optionId": 1, +"quantity": 2, +"orderDateTime": "2024-07-21T10:00:00", +"message": "Please handle this order with care." +} +``` From c2f5566feee991d5ddbf2f81552786fef9b9de33 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=82=98=EC=A0=9C=EB=B2=95?= Date: Sun, 28 Jul 2024 01:40:27 +0900 Subject: [PATCH 033/138] =?UTF-8?q?feat:=20Password.java=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/gift/domain/vo/Password.java | 48 ++++++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 src/main/java/gift/domain/vo/Password.java diff --git a/src/main/java/gift/domain/vo/Password.java b/src/main/java/gift/domain/vo/Password.java new file mode 100644 index 000000000..f63d335a2 --- /dev/null +++ b/src/main/java/gift/domain/vo/Password.java @@ -0,0 +1,48 @@ +package gift.domain.vo; + +import jakarta.persistence.Column; +import jakarta.persistence.Embeddable; +import java.util.Objects; + +@Embeddable +public class Password { + + @Column(name = "password") + private String value; + + protected Password() { + } + + private Password(String value) { + this.value = value; + } + + public static Password from(String password) { + return new Password(password); + } + + public boolean matches(String password) { + return this.value.equals(password); + } + + public String getValue() { + return value; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + Password p = (Password) o; + return Objects.equals(this.value, p.value); + } + + @Override + public int hashCode() { + return Objects.hash(value); + } +} From 1a0e449fa5137cc71b3940fdf7ff48e6534d4fd7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=82=98=EC=A0=9C=EB=B2=95?= Date: Sun, 28 Jul 2024 01:40:36 +0900 Subject: [PATCH 034/138] =?UTF-8?q?feat:=20Platform.java=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/gift/domain/constants/Platform.java | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 src/main/java/gift/domain/constants/Platform.java diff --git a/src/main/java/gift/domain/constants/Platform.java b/src/main/java/gift/domain/constants/Platform.java new file mode 100644 index 000000000..673c7e3c1 --- /dev/null +++ b/src/main/java/gift/domain/constants/Platform.java @@ -0,0 +1,7 @@ +package gift.domain.constants; + +public enum Platform { + + GIFT, KAKAO + +} From b52b12a54417362fad8b78b415b24aff2d81631e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=82=98=EC=A0=9C=EB=B2=95?= Date: Sun, 28 Jul 2024 01:40:53 +0900 Subject: [PATCH 035/138] =?UTF-8?q?feat:=20Member.java=20password,=20platf?= =?UTF-8?q?orm=20=ED=95=84=EB=93=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/gift/domain/Member.java | 44 +++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/src/main/java/gift/domain/Member.java b/src/main/java/gift/domain/Member.java index 9807a8417..0cacf9b9a 100644 --- a/src/main/java/gift/domain/Member.java +++ b/src/main/java/gift/domain/Member.java @@ -1,38 +1,66 @@ package gift.domain; import gift.domain.base.BaseTimeEntity; +import gift.domain.constants.Platform; import gift.domain.vo.Email; +import gift.domain.vo.Password; +import gift.web.validation.exception.client.IncorrectPasswordException; import jakarta.persistence.Column; import jakarta.persistence.Embedded; import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import org.hibernate.annotations.ColumnDefault; +import org.hibernate.annotations.DynamicInsert; +@DynamicInsert @Entity public class Member extends BaseTimeEntity { @Embedded private Email email; + @Embedded + private Password password; + @Column(nullable = false) private String name; + @Column(nullable = false) + @Enumerated(EnumType.STRING) + @ColumnDefault("'GIFT'") + private Platform platform; + protected Member() { } public static class Builder extends BaseTimeEntity.Builder { private Email email; + private Password password; private String name; + private Platform platform; public Builder email(Email email) { this.email = email; return this; } + public Builder password(Password password) { + this.password = password; + return this; + } + public Builder name(String name) { this.name = name; return this; } + public Builder platform(Platform platform) { + this.platform = platform; + return this; + } + @Override protected Builder self() { return this; @@ -47,14 +75,30 @@ public Member build() { private Member(Builder builder) { super(builder); email = builder.email; + password = builder.password; name = builder.name; + platform = builder.platform; } public Email getEmail() { return email; } + public Password getPassword() { + return password; + } + public String getName() { return name; } + + public Platform getPlatform() { + return platform; + } + + public void matchPassword(final String password) { + if (!this.password.matches(password)) { + throw new IncorrectPasswordException(); + } + } } From 595ce3dce3140267d04ac9d2a6a29bb1af2f8679 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=82=98=EC=A0=9C=EB=B2=95?= Date: Sun, 28 Jul 2024 01:41:12 +0900 Subject: [PATCH 036/138] =?UTF-8?q?feat:=20MemberDetails.java=20platform?= =?UTF-8?q?=20=ED=95=84=EB=93=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/gift/web/dto/MemberDetails.java | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/main/java/gift/web/dto/MemberDetails.java b/src/main/java/gift/web/dto/MemberDetails.java index 82b07bdfc..c6ccdbfb9 100644 --- a/src/main/java/gift/web/dto/MemberDetails.java +++ b/src/main/java/gift/web/dto/MemberDetails.java @@ -1,20 +1,23 @@ package gift.web.dto; import gift.domain.Member; +import gift.domain.constants.Platform; import gift.domain.vo.Email; public class MemberDetails { private Long id; private Email email; + private Platform platform; - public MemberDetails(Long id, Email email) { + public MemberDetails(Long id, Email email, Platform platform) { this.id = id; this.email = email; + this.platform = platform; } public static MemberDetails from(Member member) { - return new MemberDetails(member.getId(), member.getEmail()); + return new MemberDetails(member.getId(), member.getEmail(), member.getPlatform()); } public Long getId() { @@ -24,4 +27,8 @@ public Long getId() { public Email getEmail() { return email; } + + public Platform getPlatform() { + return platform; + } } From bdad4aa354c28b572ccd9a22731ae4f311037ff2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=82=98=EC=A0=9C=EB=B2=95?= Date: Sun, 28 Jul 2024 01:41:32 +0900 Subject: [PATCH 037/138] =?UTF-8?q?feat:=20LoginRequest.java=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../gift/web/dto/request/LoginRequest.java | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 src/main/java/gift/web/dto/request/LoginRequest.java diff --git a/src/main/java/gift/web/dto/request/LoginRequest.java b/src/main/java/gift/web/dto/request/LoginRequest.java new file mode 100644 index 000000000..48298041e --- /dev/null +++ b/src/main/java/gift/web/dto/request/LoginRequest.java @@ -0,0 +1,27 @@ +package gift.web.dto.request; + +import gift.web.validation.constraints.Password; +import jakarta.validation.constraints.Email; + +public class LoginRequest { + + @Email + private final String email; + + @Password + private final String password; + + public LoginRequest(String email, String password) { + this.email = email; + this.password = password; + } + + public String getEmail() { + return email; + } + + public String getPassword() { + return password; + } + +} \ No newline at end of file From 697f03d33019bdeaf608bef3d4f64f7c802e9085 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=82=98=EC=A0=9C=EB=B2=95?= Date: Sun, 28 Jul 2024 01:42:06 +0900 Subject: [PATCH 038/138] =?UTF-8?q?feat:=20MemberService.java=20login()=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/gift/service/MemberService.java | 21 ++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/src/main/java/gift/service/MemberService.java b/src/main/java/gift/service/MemberService.java index ec7699cee..4a86b1192 100644 --- a/src/main/java/gift/service/MemberService.java +++ b/src/main/java/gift/service/MemberService.java @@ -1,13 +1,18 @@ package gift.service; +import gift.authentication.token.JwtProvider; +import gift.authentication.token.Token; import gift.domain.Member; import gift.domain.vo.Email; import gift.repository.MemberRepository; import gift.repository.WishProductRepository; import gift.web.client.dto.KakaoAccount; +import gift.web.dto.request.LoginRequest; import gift.web.dto.request.member.CreateMemberRequest; +import gift.web.dto.response.LoginResponse; import gift.web.dto.response.member.CreateMemberResponse; import gift.web.dto.response.member.ReadMemberResponse; +import gift.web.validation.exception.client.IncorrectEmailException; import java.util.NoSuchElementException; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -18,10 +23,13 @@ public class MemberService { private final MemberRepository memberRepository; private final WishProductRepository wishProductRepository; + private final JwtProvider jwtProvider; - public MemberService(MemberRepository memberRepository, WishProductRepository wishProductRepository) { + public MemberService(MemberRepository memberRepository, WishProductRepository wishProductRepository, + JwtProvider jwtProvider) { this.memberRepository = memberRepository; this.wishProductRepository = wishProductRepository; + this.jwtProvider = jwtProvider; } @Transactional @@ -48,4 +56,15 @@ public void deleteMember(Long id) { wishProductRepository.deleteAllByMemberId(id); memberRepository.delete(member); } + + public LoginResponse login(LoginRequest request) { + Email email = Email.from(request.getEmail()); + Member member = memberRepository.findByEmail(email).orElseThrow(() -> new IncorrectEmailException(email.getValue())); + + member.matchPassword(request.getPassword()); + + Token token = jwtProvider.generateToken(member); + + return new LoginResponse(token.getValue()); + } } From 11130e830a7cb821c7f5ed0dad1498ceec191932 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=82=98=EC=A0=9C=EB=B2=95?= Date: Sun, 28 Jul 2024 01:42:39 +0900 Subject: [PATCH 039/138] =?UTF-8?q?refactor:=20Product.java=20=EC=82=AC?= =?UTF-8?q?=EC=9A=A9=ED=95=98=EC=A7=80=20=EC=95=8A=EB=8A=94=20=EB=A9=94?= =?UTF-8?q?=EC=84=9C=EB=93=9C=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Builder.validateProductOptionsPresence() --- src/main/java/gift/domain/Product.java | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/main/java/gift/domain/Product.java b/src/main/java/gift/domain/Product.java index dff5f0613..139156999 100644 --- a/src/main/java/gift/domain/Product.java +++ b/src/main/java/gift/domain/Product.java @@ -81,12 +81,6 @@ protected Builder self() { public Product build() { return new Product(this); } - - private void validateProductOptionsPresence(List productOptions) { - if (productOptions == null || productOptions.isEmpty()) { - throw new IllegalArgumentException("상품 옵션은 최소 1개 이상이어야 합니다."); - } - } } private Product(Builder builder) { From 3b0b7853fd1eb24019c8bf61787fcf47b668a547 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=82=98=EC=A0=9C=EB=B2=95?= Date: Sun, 28 Jul 2024 01:42:59 +0900 Subject: [PATCH 040/138] =?UTF-8?q?feat:=20CreateMemberRequest.java=20pass?= =?UTF-8?q?word=20=ED=95=84=EB=93=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../web/dto/request/member/CreateMemberRequest.java | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/main/java/gift/web/dto/request/member/CreateMemberRequest.java b/src/main/java/gift/web/dto/request/member/CreateMemberRequest.java index 53ca2ab7d..ce0973440 100644 --- a/src/main/java/gift/web/dto/request/member/CreateMemberRequest.java +++ b/src/main/java/gift/web/dto/request/member/CreateMemberRequest.java @@ -1,6 +1,7 @@ package gift.web.dto.request.member; import gift.domain.Member; +import gift.web.validation.constraints.Password; import jakarta.validation.constraints.Email; public class CreateMemberRequest { @@ -8,10 +9,14 @@ public class CreateMemberRequest { @Email private String email; + @Password + private String password; + private String name; - public CreateMemberRequest(String email, String name) { + public CreateMemberRequest(String email, String password, String name) { this.email = email; + this.password = password; this.name = name; } @@ -19,6 +24,10 @@ public String getEmail() { return email; } + public String getPassword() { + return password; + } + public String getName() { return name; } @@ -26,7 +35,9 @@ public String getName() { public Member toEntity() { return new Member.Builder() .email(gift.domain.vo.Email.from(this.email)) + .password(gift.domain.vo.Password.from(password)) .name(this.name) .build(); } + } From 7a68629306e78cc0c7ed56483ead53d264111e70 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=82=98=EC=A0=9C=EB=B2=95?= Date: Sun, 28 Jul 2024 01:43:15 +0900 Subject: [PATCH 041/138] =?UTF-8?q?feat:=20ReadMemberResponse.java=20passw?= =?UTF-8?q?ord=20=ED=95=84=EB=93=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../web/dto/response/member/ReadMemberResponse.java | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/main/java/gift/web/dto/response/member/ReadMemberResponse.java b/src/main/java/gift/web/dto/response/member/ReadMemberResponse.java index 1be0d317c..28dcd2c69 100644 --- a/src/main/java/gift/web/dto/response/member/ReadMemberResponse.java +++ b/src/main/java/gift/web/dto/response/member/ReadMemberResponse.java @@ -6,16 +6,18 @@ public class ReadMemberResponse { private Long id; private String email; + private String password; private String name; - private ReadMemberResponse(Long id, String email, String name) { + public ReadMemberResponse(Long id, String email, String password, String name) { this.id = id; this.email = email; + this.password = password; this.name = name; } public static ReadMemberResponse fromEntity(Member member) { - return new ReadMemberResponse(member.getId(), member.getEmail().getValue(), + return new ReadMemberResponse(member.getId(), member.getEmail().getValue(), member.getPassword().getValue(), member.getName()); } @@ -27,6 +29,10 @@ public String getEmail() { return email; } + public String getPassword() { + return password; + } + public String getName() { return name; } From e62bf41761dc647df2abc70304d189b374cb2403 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=82=98=EC=A0=9C=EB=B2=95?= Date: Sun, 28 Jul 2024 01:43:44 +0900 Subject: [PATCH 042/138] =?UTF-8?q?chore:=20data.sql=20member=EC=9D=98=20p?= =?UTF-8?q?assword=20=ED=95=84=EB=93=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/resources/sql/data.sql | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/resources/sql/data.sql b/src/main/resources/sql/data.sql index 567759f52..2418e5321 100644 --- a/src/main/resources/sql/data.sql +++ b/src/main/resources/sql/data.sql @@ -51,11 +51,11 @@ VALUES ('Product 49', 49000, 'https://via.placeholder.com/4950', 3, NOW(), 1, NOW(), 1), ('Product 50', 50000, 'https://via.placeholder.com/5050', 3, NOW(), 1, NOW(), 1); -INSERT INTO member(name, email, created_at, updated_at) +INSERT INTO member(name, password, email, created_at, updated_at) VALUES - ('Member 1', 'member01@gmail.com', NOW(), NOW()), - ('Member 2', 'member02@gmail.com', NOW(), NOW()), - ('Member 3', 'member03@gmail.com', NOW(), NOW()); + ('Member 1', 'password01', 'member01@gmail.com', NOW(), NOW()), + ('Member 2', 'password02', 'member02@gmail.com', NOW(), NOW()), + ('Member 3', 'password03', 'member03@gmail.com', NOW(), NOW()); INSERT INTO wish_product(member_id, product_id, quantity, created_at, created_by, updated_at, updated_by) VALUES From 8acbdebb7870bddab143e7296ce00c50e6e26580 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=82=98=EC=A0=9C=EB=B2=95?= Date: Sun, 28 Jul 2024 01:44:13 +0900 Subject: [PATCH 043/138] =?UTF-8?q?feat:=20AuthenticationFilter.java=20ign?= =?UTF-8?q?orePaths=20=EC=B6=94=EA=B0=80=20=EB=93=B1=EB=A1=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../gift/authentication/filter/AuthenticationFilter.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/main/java/gift/authentication/filter/AuthenticationFilter.java b/src/main/java/gift/authentication/filter/AuthenticationFilter.java index c95db07d2..934a09a3a 100644 --- a/src/main/java/gift/authentication/filter/AuthenticationFilter.java +++ b/src/main/java/gift/authentication/filter/AuthenticationFilter.java @@ -16,7 +16,10 @@ public class AuthenticationFilter extends OncePerRequestFilter { - private final List ignorePaths = List.of("/api/login/oauth2/kakao"); + private final List ignorePaths = List.of( + "/api/members/login", + "/api/members/register", + "/api/login/oauth2/kakao"); private final String AUTHORIZATION_HEADER = "Authorization"; private final String BEARER = "Bearer "; private final JwtResolver jwtResolver; From 9ef54f6af93051aa8fd1f9b6a5c537dbdef24ace Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=82=98=EC=A0=9C=EB=B2=95?= Date: Sun, 28 Jul 2024 01:44:46 +0900 Subject: [PATCH 044/138] =?UTF-8?q?refactor:=20KakaoAccount.java=20Member?= =?UTF-8?q?=20=EB=B3=80=ED=99=98=20=EB=A1=9C=EC=A7=81=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/gift/web/client/dto/KakaoAccount.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/java/gift/web/client/dto/KakaoAccount.java b/src/main/java/gift/web/client/dto/KakaoAccount.java index 915204a4e..2c06d3b29 100644 --- a/src/main/java/gift/web/client/dto/KakaoAccount.java +++ b/src/main/java/gift/web/client/dto/KakaoAccount.java @@ -1,6 +1,7 @@ package gift.web.client.dto; import gift.domain.Member; +import gift.domain.constants.Platform; import gift.domain.vo.Email; public class KakaoAccount { @@ -38,6 +39,7 @@ public Member toMember() { return new Member.Builder() .name(profile.getNickname()) .email(Email.from(email)) + .platform(Platform.KAKAO) .build(); } } From 9ef88e914287f851e6558703debc68716497f3ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=82=98=EC=A0=9C=EB=B2=95?= Date: Sun, 28 Jul 2024 01:45:13 +0900 Subject: [PATCH 045/138] =?UTF-8?q?test:=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EC=BD=94=EB=93=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/gift/service/MemberServiceTest.java | 32 +- .../gift/utils/MemberDummyDataProvider.java | 3 +- .../api/MemberApiControllerTest.java | 413 ++++++++++-------- 3 files changed, 257 insertions(+), 191 deletions(-) diff --git a/src/test/java/gift/service/MemberServiceTest.java b/src/test/java/gift/service/MemberServiceTest.java index a43099776..82e617073 100644 --- a/src/test/java/gift/service/MemberServiceTest.java +++ b/src/test/java/gift/service/MemberServiceTest.java @@ -7,11 +7,15 @@ import static org.mockito.BDDMockito.given; import gift.authentication.token.JwtProvider; +import gift.authentication.token.Token; import gift.domain.Member; import gift.domain.vo.Email; +import gift.domain.vo.Password; import gift.repository.MemberRepository; import gift.repository.WishProductRepository; +import gift.web.dto.request.LoginRequest; import gift.web.dto.request.member.CreateMemberRequest; +import gift.web.dto.response.LoginResponse; import gift.web.dto.response.member.CreateMemberResponse; import gift.web.dto.response.member.ReadMemberResponse; import java.util.Optional; @@ -41,7 +45,7 @@ class MemberServiceTest { @DisplayName("회원 생성 요청이 정상적일 때, 회원을 성공적으로 생성합니다.") void createMember() { //given - CreateMemberRequest request = new CreateMemberRequest("member01@naver.com", "이름"); + CreateMemberRequest request = new CreateMemberRequest("member01@naver.com", "password01", "이름"); given(memberRepository.save(any())).willReturn( new Member.Builder().id(1L).name(request.getName()).email(Email.from(request.getEmail())).build()); @@ -60,7 +64,8 @@ void createMember() { @DisplayName("회원 조회 요청이 정상적일 때, 회원을 성공적으로 조회합니다.") void readMember() { //given - Member member = new Member.Builder().id(1L).name("이름").email(Email.from("member01@naver.com")).build(); + Member member = new Member.Builder().id(1L).name("이름").email(Email.from("member01@naver.com")).password( + Password.from("password01")).build(); given(memberRepository.findById(1L)).willReturn(Optional.of(member)); //when @@ -78,7 +83,8 @@ void readMember() { @DisplayName("회원 삭제 요청이 정상적일 때, 회원을 성공적으로 삭제합니다.") void deleteMember() { //given - Member member = new Member.Builder().id(1L).name("이름").email(Email.from("member01@naver.com")).build(); + Member member = new Member.Builder().id(1L).name("이름").email(Email.from("member01@naver.com")).password( + Password.from("password01")).build(); given(memberRepository.findById(1L)).willReturn(Optional.of(member)); //when @@ -86,4 +92,24 @@ void deleteMember() { assertDoesNotThrow(() -> memberService.deleteMember(1L)); } + @Test + @DisplayName("로그인 요청이 정상적일 때, 로그인을 성공적으로 수행합니다.") + void login() { + //given + String email = "member01@naver.com"; + String password = "password01"; + Member member = new Member.Builder().id(1L).email(Email.from(email)).password( + Password.from(password)).build(); + + LoginRequest request = new LoginRequest(email, password); + + given(memberRepository.findByEmail(Email.from(email))).willReturn(Optional.of(member)); + given(jwtProvider.generateToken(member)).willReturn(Token.from("token")); + + //when + LoginResponse response = memberService.login(request); + + //then + assertThat(response.getAccessToken()).isNotNull(); + } } \ No newline at end of file diff --git a/src/test/java/gift/utils/MemberDummyDataProvider.java b/src/test/java/gift/utils/MemberDummyDataProvider.java index 0d1856e94..cf340e8c2 100644 --- a/src/test/java/gift/utils/MemberDummyDataProvider.java +++ b/src/test/java/gift/utils/MemberDummyDataProvider.java @@ -24,7 +24,7 @@ public void run(int quantity) { } private void doRun(int quantity) { - String sql = "insert into member (name, email, created_at, updated_at) values (?, ?, ?, ?)"; + String sql = "insert into member (name, email, password, created_at, updated_at) values (?, ?, ?, ?, ?)"; jdbcTemplate.batchUpdate(sql, getBatchPreparedStatementSetter(quantity)); } @@ -34,6 +34,7 @@ private static BatchPreparedStatementSetter getBatchPreparedStatementSetter(int public void setValues(PreparedStatement ps, int i) throws SQLException { ps.setString(1, "member" + i); ps.setString(2, "member" + i + "@gmail.com"); + ps.setString(3, "member" + i + "0"); ps.setTimestamp(4, Timestamp.valueOf(LocalDateTime.now())); ps.setTimestamp(5, Timestamp.valueOf(LocalDateTime.now())); } diff --git a/src/test/java/gift/web/controller/api/MemberApiControllerTest.java b/src/test/java/gift/web/controller/api/MemberApiControllerTest.java index 2518c5000..85204f1ea 100644 --- a/src/test/java/gift/web/controller/api/MemberApiControllerTest.java +++ b/src/test/java/gift/web/controller/api/MemberApiControllerTest.java @@ -1,187 +1,226 @@ -//package gift.web.controller.api; -// -//import static org.assertj.core.api.Assertions.assertThat; -//import static org.junit.jupiter.api.Assertions.assertAll; -//import static org.junit.jupiter.api.Assertions.assertIterableEquals; -//import static org.junit.jupiter.api.Assertions.assertTrue; -// -//import gift.authentication.token.Token; -//import gift.domain.Member; -//import gift.repository.MemberRepository; -//import gift.service.LoginService; -//import gift.service.MemberService; -//import gift.service.WishProductService; -//import gift.utils.CategoryDummyDataProvider; -//import gift.utils.DatabaseCleanup; -//import gift.utils.MemberDummyDataProvider; -//import gift.utils.ProductDummyDataProvider; -//import gift.utils.WishProductDummyDataProvider; -//import gift.web.dto.request.LoginRequest; -//import gift.web.dto.request.member.CreateMemberRequest; -//import gift.web.dto.request.wishproduct.UpdateWishProductRequest; -//import gift.web.dto.response.LoginResponse; -//import gift.web.dto.response.member.CreateMemberResponse; -//import gift.web.dto.response.member.ReadMemberResponse; -//import gift.web.dto.response.wishproduct.ReadAllWishProductsResponse; -//import gift.web.dto.response.wishproduct.UpdateWishProductResponse; -//import org.junit.jupiter.api.AfterEach; -//import org.junit.jupiter.api.BeforeEach; -//import org.junit.jupiter.api.DisplayName; -//import org.junit.jupiter.api.Test; -//import org.springframework.beans.factory.annotation.Autowired; -//import org.springframework.boot.test.context.SpringBootTest; -//import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; -//import org.springframework.boot.test.web.client.TestRestTemplate; -//import org.springframework.boot.test.web.server.LocalServerPort; -//import org.springframework.data.domain.PageRequest; -//import org.springframework.http.HttpEntity; -//import org.springframework.http.HttpHeaders; -//import org.springframework.http.HttpMethod; -//import org.springframework.http.ResponseEntity; -//import org.springframework.test.context.ActiveProfiles; -// -//@ActiveProfiles("test") -//@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT) -//class MemberApiControllerTest { -// -// @LocalServerPort -// private int port; -// -// @Autowired -// private TestRestTemplate restTemplate; -// -// @Autowired -// private MemberDummyDataProvider memberDummyDataProvider; -// -// @Autowired -// private ProductDummyDataProvider productDummyDataProvider; -// -// @Autowired -// private WishProductDummyDataProvider wishProductDummyDataProvider; -// -// @Autowired -// private CategoryDummyDataProvider categoryDummyDataProvider; -// -// @Autowired -// private DatabaseCleanup databaseCleanup; -// -// @Autowired -// private MemberService memberService; -// -// @Autowired -// private LoginService loginService; -// -// @Autowired -// private MemberRepository memberRepository; -// -// @Autowired -// private WishProductService wishProductService; -// -// //테스트용 회원 -// private Member member; -// private Token token; -// -// @BeforeEach -// void setUp() { -// insertDummyData(100); -// member = getTestMember(1L); -// token = getAccessToken(); -// } -// -// private Member getTestMember(Long id) { -// return memberRepository.findById(id) -// .orElseThrow(() -> new IllegalArgumentException("ID: " + id +"인 회원이 존재하지 않습니다.")); -// } -// -// private Token getAccessToken() { -// LoginRequest loginRequest = new LoginRequest( -// member.getEmail().getValue(), -// ); -// LoginResponse loginResponse = memberService.login(loginRequest); -// return loginResponse.getToken(); -// } -// -// private void insertDummyData(int quantity) { -// if (quantity < 2) { -// throw new IllegalArgumentException("quantity는 2 이상이어야 합니다."); -// } -// memberDummyDataProvider.run(quantity); -// productDummyDataProvider.run(quantity); -// wishProductDummyDataProvider.run(quantity); -// categoryDummyDataProvider.run(quantity); -// } -// -// @AfterEach -// void tearDown() { -// databaseCleanup.execute(); -// } -// -// @Test -// @DisplayName("위시 리스트 조회 요청에 대한 정상 응답") -// void readWishProduct() { -// //given -// String url = "http://localhost:" + port + "/api/members/wishlist"; -// HttpHeaders httpHeaders = getHttpHeaders(); -// HttpEntity httpEntity = new HttpEntity(httpHeaders); -// -// PageRequest defaultPageRequest = PageRequest.of(0, 10); -// ReadAllWishProductsResponse expectedWishProducts = wishProductService.readAllWishProducts(member.getId(), -// defaultPageRequest); -// -// //when -// ResponseEntity response = restTemplate.exchange(url, -// HttpMethod.GET, httpEntity, ReadAllWishProductsResponse.class); -// -// //then -// assertAll( -// () -> assertTrue(response.getStatusCode().is2xxSuccessful()), -// () -> assertIterableEquals(response.getBody().getWishlist(), expectedWishProducts.getWishlist()) -// ); -// } -// -// @Test -// @DisplayName("위시 리스트 상품 수정 요청에 대한 정상 응답") -// void updateWishProduct() { -// //given -// Long wishProductId = 1L; -// String url = "http://localhost:" + port + "/api/members/wishlist/" + wishProductId; -// HttpHeaders httpHeaders = getHttpHeaders(); -// -// UpdateWishProductRequest request = new UpdateWishProductRequest(3); -// -// HttpEntity httpEntity = new HttpEntity(request, httpHeaders); -// -// //when -// ResponseEntity response = restTemplate.exchange(url, -// HttpMethod.PUT, httpEntity, UpdateWishProductResponse.class); -// -// //then -// assertAll( -// () -> assertTrue(response.getStatusCode().is2xxSuccessful()), -// () -> assertThat(response.getBody().getQuantity()).isEqualTo(request.getQuantity()) -// ); -// } -// -// @Test -// @DisplayName("위시 리스트 상품 삭제 요청에 대한 정상 응답") -// void deleteWishProduct() { -// //given -// Long wishProductId = 2L; -// String url = "http://localhost:" + port + "/api/members/wishlist/" + wishProductId; -// HttpHeaders httpHeaders = getHttpHeaders(); -// HttpEntity httpEntity = new HttpEntity(httpHeaders); -// -// //when -// ResponseEntity response = restTemplate.exchange(url, HttpMethod.DELETE, httpEntity, -// Void.class); -// -// //then -// assertTrue(response.getStatusCode().is2xxSuccessful()); -// } -// -// private HttpHeaders getHttpHeaders() { -// HttpHeaders httpHeaders = new HttpHeaders(); -// httpHeaders.setBearerAuth(token.getValue()); -// return httpHeaders; -// } -//} \ No newline at end of file +package gift.web.controller.api; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertAll; +import static org.junit.jupiter.api.Assertions.assertIterableEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import gift.authentication.token.Token; +import gift.domain.Member; +import gift.repository.MemberRepository; +import gift.service.MemberService; +import gift.service.WishProductService; +import gift.utils.CategoryDummyDataProvider; +import gift.utils.DatabaseCleanup; +import gift.utils.MemberDummyDataProvider; +import gift.utils.ProductDummyDataProvider; +import gift.utils.WishProductDummyDataProvider; +import gift.web.dto.request.LoginRequest; +import gift.web.dto.request.member.CreateMemberRequest; +import gift.web.dto.request.wishproduct.UpdateWishProductRequest; +import gift.web.dto.response.LoginResponse; +import gift.web.dto.response.member.CreateMemberResponse; +import gift.web.dto.response.member.ReadMemberResponse; +import gift.web.dto.response.wishproduct.ReadAllWishProductsResponse; +import gift.web.dto.response.wishproduct.UpdateWishProductResponse; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; +import org.springframework.boot.test.web.client.TestRestTemplate; +import org.springframework.boot.test.web.server.LocalServerPort; +import org.springframework.data.domain.PageRequest; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.ResponseEntity; +import org.springframework.test.context.ActiveProfiles; + +@ActiveProfiles("test") +@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT) +class MemberApiControllerTest { + + @LocalServerPort + private int port; + + @Autowired + private TestRestTemplate restTemplate; + + @Autowired + private MemberDummyDataProvider memberDummyDataProvider; + + @Autowired + private ProductDummyDataProvider productDummyDataProvider; + + @Autowired + private WishProductDummyDataProvider wishProductDummyDataProvider; + + @Autowired + private CategoryDummyDataProvider categoryDummyDataProvider; + + @Autowired + private DatabaseCleanup databaseCleanup; + + @Autowired + private MemberService memberService; + + @Autowired + private MemberRepository memberRepository; + + @Autowired + private WishProductService wishProductService; + + //테스트용 회원 + private Member member; + private Token token; + + @BeforeEach + void setUp() { + insertDummyData(100); + member = getTestMember(1L); + token = getAccessToken(); + } + + private Member getTestMember(Long id) { + return memberRepository.findById(id) + .orElseThrow(() -> new IllegalArgumentException("ID: " + id +"인 회원이 존재하지 않습니다.")); + } + + private Token getAccessToken() { + LoginRequest loginRequest = new LoginRequest( + member.getEmail().getValue(), + member.getPassword().getValue() + ); + LoginResponse loginResponse = memberService.login(loginRequest); + return Token.from(loginResponse.getAccessToken()); + } + + private void insertDummyData(int quantity) { + if (quantity < 2) { + throw new IllegalArgumentException("quantity는 2 이상이어야 합니다."); + } + memberDummyDataProvider.run(quantity); + productDummyDataProvider.run(quantity); + wishProductDummyDataProvider.run(quantity); + categoryDummyDataProvider.run(quantity); + } + + @AfterEach + void tearDown() { + databaseCleanup.execute(); + } + + @Test + @DisplayName("회원 생성 요청에 대한 정상 응답") + void createMember() { + //given + CreateMemberRequest request = new CreateMemberRequest("test@gmail.com", "test1234", "test"); + String url = "http://localhost:" + port + "/api/members/register"; + + //when + ResponseEntity response = restTemplate.postForEntity(url, request, CreateMemberResponse.class); + + //then + Long newMemberId = response.getBody().getId(); + ReadMemberResponse findMember = memberService.readMember(newMemberId); + + assertAll( + () -> assertTrue(response.getStatusCode().is2xxSuccessful()), + () -> assertThat(newMemberId).isEqualTo(findMember.getId()), + () -> assertThat(request.getEmail()).isEqualTo(findMember.getEmail()), + () -> assertThat(request.getName()).isEqualTo(findMember.getName()), + () -> assertThat(request.getPassword()).isEqualTo(findMember.getPassword()) + ); + } + + @Test + @DisplayName("로그인 요청에 대한 정상 응답") + void login() { + //given + String url = "http://localhost:" + port + "/api/members/login"; + String email = member.getEmail().getValue(); + String password = member.getPassword().getValue(); + LoginRequest request = new LoginRequest(email, password); + + //when + ResponseEntity response = restTemplate.postForEntity(url, request, LoginResponse.class); + + //then + assertAll( + () -> assertTrue(response.getStatusCode().is2xxSuccessful()), + () -> assertThat(response.getBody().getAccessToken()).isNotNull() + ); + } + + @Test + @DisplayName("위시 리스트 조회 요청에 대한 정상 응답") + void readWishProduct() { + //given + String url = "http://localhost:" + port + "/api/members/wishlist"; + HttpHeaders httpHeaders = getHttpHeaders(); + HttpEntity httpEntity = new HttpEntity(httpHeaders); + + PageRequest defaultPageRequest = PageRequest.of(0, 10); + ReadAllWishProductsResponse expectedWishProducts = wishProductService.readAllWishProducts(member.getId(), + defaultPageRequest); + + //when + ResponseEntity response = restTemplate.exchange(url, + HttpMethod.GET, httpEntity, ReadAllWishProductsResponse.class); + + //then + assertAll( + () -> assertTrue(response.getStatusCode().is2xxSuccessful()), + () -> assertIterableEquals(response.getBody().getWishlist(), expectedWishProducts.getWishlist()) + ); + } + + @Test + @DisplayName("위시 리스트 상품 수정 요청에 대한 정상 응답") + void updateWishProduct() { + //given + Long wishProductId = 1L; + String url = "http://localhost:" + port + "/api/members/wishlist/" + wishProductId; + HttpHeaders httpHeaders = getHttpHeaders(); + + UpdateWishProductRequest request = new UpdateWishProductRequest(3); + + HttpEntity httpEntity = new HttpEntity(request, httpHeaders); + + //when + ResponseEntity response = restTemplate.exchange(url, + HttpMethod.PUT, httpEntity, UpdateWishProductResponse.class); + + //then + assertAll( + () -> assertTrue(response.getStatusCode().is2xxSuccessful()), + () -> assertThat(response.getBody().getQuantity()).isEqualTo(request.getQuantity()) + ); + } + + @Test + @DisplayName("위시 리스트 상품 삭제 요청에 대한 정상 응답") + void deleteWishProduct() { + //given + Long wishProductId = 2L; + String url = "http://localhost:" + port + "/api/members/wishlist/" + wishProductId; + HttpHeaders httpHeaders = getHttpHeaders(); + HttpEntity httpEntity = new HttpEntity(httpHeaders); + + //when + ResponseEntity response = restTemplate.exchange(url, HttpMethod.DELETE, httpEntity, + Void.class); + + //then + assertTrue(response.getStatusCode().is2xxSuccessful()); + } + + private HttpHeaders getHttpHeaders() { + HttpHeaders httpHeaders = new HttpHeaders(); + httpHeaders.setBearerAuth(token.getValue()); + return httpHeaders; + } +} \ No newline at end of file From e0b14df39256eb6ec5406c30447f939810636e34 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=82=98=EC=A0=9C=EB=B2=95?= Date: Sun, 28 Jul 2024 01:45:33 +0900 Subject: [PATCH 046/138] =?UTF-8?q?feat:=20MemberApiController.java=20?= =?UTF-8?q?=ED=9A=8C=EC=9B=90=EA=B0=80=EC=9E=85/=EB=A1=9C=EA=B7=B8?= =?UTF-8?q?=EC=9D=B8=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/api/MemberApiController.java | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/src/main/java/gift/web/controller/api/MemberApiController.java b/src/main/java/gift/web/controller/api/MemberApiController.java index 758d0e5fc..2db0cd4aa 100644 --- a/src/main/java/gift/web/controller/api/MemberApiController.java +++ b/src/main/java/gift/web/controller/api/MemberApiController.java @@ -4,10 +4,16 @@ import gift.service.MemberService; import gift.service.WishProductService; import gift.web.dto.MemberDetails; +import gift.web.dto.request.LoginRequest; +import gift.web.dto.request.member.CreateMemberRequest; import gift.web.dto.request.wishproduct.UpdateWishProductRequest; +import gift.web.dto.response.LoginResponse; +import gift.web.dto.response.member.CreateMemberResponse; import gift.web.dto.response.member.ReadMemberResponse; import gift.web.dto.response.wishproduct.ReadAllWishProductsResponse; import gift.web.dto.response.wishproduct.UpdateWishProductResponse; +import java.net.URI; +import java.net.URISyntaxException; import org.springframework.data.domain.Pageable; import org.springframework.data.web.PageableDefault; import org.springframework.http.ResponseEntity; @@ -15,6 +21,7 @@ import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PutMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; @@ -32,6 +39,22 @@ public MemberApiController(MemberService memberService, WishProductService wishP this.wishProductService = wishProductService; } + @PostMapping("/register") + public ResponseEntity createMember( + @Validated @RequestBody CreateMemberRequest request) + throws URISyntaxException { + CreateMemberResponse response = memberService.createMember(request); + + URI location = new URI("http://localhost:8080/api/members/" + response.getId()); + return ResponseEntity.created(location).body(response); + } + + @PostMapping("/login") + public ResponseEntity login(@Validated @RequestBody LoginRequest request) { + LoginResponse response = memberService.login(request); + return ResponseEntity.ok(response); + } + @GetMapping("/{memberId}") public ResponseEntity readMember(@PathVariable Long memberId) { ReadMemberResponse response = memberService.readMember(memberId); From 37cd143e67d32b6e5673c6a877fc1e11c0422f2e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=82=98=EC=A0=9C=EB=B2=95?= Date: Sun, 28 Jul 2024 11:09:36 +0900 Subject: [PATCH 047/138] =?UTF-8?q?feat:=20ProductViewController.java=20fo?= =?UTF-8?q?rm=20=ED=99=94=EB=A9=B4=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../view/ProductViewController.java | 26 ++++++++++++++++--- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/src/main/java/gift/web/controller/view/ProductViewController.java b/src/main/java/gift/web/controller/view/ProductViewController.java index 2806f7f5d..d13e7df69 100644 --- a/src/main/java/gift/web/controller/view/ProductViewController.java +++ b/src/main/java/gift/web/controller/view/ProductViewController.java @@ -1,6 +1,8 @@ package gift.web.controller.view; +import gift.authentication.annotation.LoginMember; import gift.service.ProductService; +import gift.web.dto.MemberDetails; import gift.web.dto.form.CreateProductForm; import gift.web.dto.response.product.ReadAllProductsResponse; import gift.web.dto.response.product.ReadProductResponse; @@ -12,7 +14,7 @@ import org.springframework.web.bind.annotation.RequestMapping; @Controller -@RequestMapping("/view/products") +@RequestMapping("/view") public class ProductViewController { private final ProductService productService; @@ -21,7 +23,7 @@ public ProductViewController(ProductService productService) { this.productService = productService; } - @GetMapping + @GetMapping("/products") public String readAdminPage(Model model) { ReadAllProductsResponse allProductsResponse = productService.readAllProducts(); List products = allProductsResponse.getProducts(); @@ -29,16 +31,32 @@ public String readAdminPage(Model model) { return "admin"; } - @GetMapping("/add") + @GetMapping("/products/add") public String addForm(Model model) { model.addAttribute("product", new CreateProductForm()); return "form/add-product-form"; } - @GetMapping("/{id}") + @GetMapping("/products/{id}") public String editForm(@PathVariable Long id, Model model) { ReadProductResponse product = productService.readProductById(id); model.addAttribute("product", product); return "form/edit-product-form"; } + + @GetMapping("/login") + public String loginForm() { + return "form/login-form"; + } + + @GetMapping("/register") + public String registerForm() { + return "form/register-form"; + } + + @GetMapping("/login-callback") + public String loginCallback(@LoginMember MemberDetails memberDetails, Model model) { + model.addAttribute("member", memberDetails); + return "login-callback"; + } } From 9732f2bd86bd77597405298e2dceb2960352f4e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=82=98=EC=A0=9C=EB=B2=95?= Date: Sun, 28 Jul 2024 11:10:05 +0900 Subject: [PATCH 048/138] =?UTF-8?q?feat:=20MemberDetails.java=20getEmailVa?= =?UTF-8?q?lue()=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 화면에서 Email이 주소값으로 나오는 현상 해결 --- src/main/java/gift/web/dto/MemberDetails.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/main/java/gift/web/dto/MemberDetails.java b/src/main/java/gift/web/dto/MemberDetails.java index c6ccdbfb9..ffbaeed40 100644 --- a/src/main/java/gift/web/dto/MemberDetails.java +++ b/src/main/java/gift/web/dto/MemberDetails.java @@ -28,6 +28,10 @@ public Email getEmail() { return email; } + public String getEmailValue() { + return email.getValue(); + } + public Platform getPlatform() { return platform; } From c8b954b4612918881f79fb1f8eb7d62691a7b6b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=82=98=EC=A0=9C=EB=B2=95?= Date: Sun, 28 Jul 2024 11:10:16 +0900 Subject: [PATCH 049/138] feat: script.js --- src/main/resources/static/js/script.js | 134 +++++++++++++++++++++++++ 1 file changed, 134 insertions(+) diff --git a/src/main/resources/static/js/script.js b/src/main/resources/static/js/script.js index 8738a2c8e..9fed7fed9 100644 --- a/src/main/resources/static/js/script.js +++ b/src/main/resources/static/js/script.js @@ -84,3 +84,137 @@ function deleteProductById(productId) { console.error('알 수 없는 에러가 발생했습니다! ', error); }); } + +function giftLogin() { + const form = document.getElementById('loginForm'); + const formData = new FormData(form); + + const data = {}; + formData.forEach((value, key) => { + data[key] = value; + }); + + console.log('Form data:', data); + + fetch('/api/members/login', { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify(data) + }) + .then(response => { + if (!response.ok) { + return response.json().then(errorData => { + throw new Error(errorData.description); + }); + } + return response.json(); + }) + .then(data => { + alert('로그인이 완료되었습니다!'); + console.log(data); + localStorage.setItem('accessToken', data.accessToken); + + fetch('/view/login-callback', { + method: 'GET', + headers: { + 'Authorization': 'Bearer ' + data.accessToken + } + }) + .then(response => { + if (!response.ok) { + throw new Error('페이지 로드 실패: ' + response.statusText); + } + return response.text(); + }) + .then(html => { + document.write(html); + }) + .catch(error => { + console.error('페이지 로드 실패: ', error); + }); + }) + .catch(error => { + console.error('알 수 없는 에러가 발생했습니다! ', error); + }); +} + +function kakaoLogin() { + fetch('https://kauth.kakao.com/oauth/authorize?client_id=c14cc9f825429533e917e1b1be966e08&redirect_uri=http://localhost:8080/api/login/oauth2/kakao&response_type=code') + .then(response => { + if (!response.ok) { + return response.json().then(errorData => { + throw new Error(errorData.description); + }); + } + return response.json(); + }) + .then(data => { + console.log(data); + localStorage.setItem('accessToken', data.accessToken); + + fetch('/view/login-callback', { + method: 'GET', + headers: { + 'Authorization': 'Bearer ' + data.accessToken + } + }) + .then(response => { + if (!response.ok) { + throw new Error('페이지 로드 실패: ' + response.statusText); + } + return response.text(); + }) + .then(html => { + document.write(html); + }) + .catch(error => { + console.error('페이지 로드 실패: ', error); + }); + }) + .catch(error => { + console.error('알 수 없는 에러가 발생했습니다! ', error); + }); +} + +function registerUser() { + const form = document.getElementById('registerForm'); + const formData = new FormData(form); + + const data = {}; + formData.forEach((value, key) => { + data[key] = value; + }); + + if (data.password !== data.confirmPassword) { + alert('비밀번호와 비밀번호 확인이 일치하지 않습니다.'); + return; + } + + console.log('Form data:', data); + + fetch('/api/members/register', { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify(data) + }) + .then(response => { + if (!response.ok) { + return response.json().then(errorData => { + throw new Error(errorData.description); + }); + } + return response.json(); + }) + .then(data => { + alert('회원가입이 완료되었습니다!'); + console.log(data); + window.location.href = '/view/login'; + }) + .catch(error => { + console.error('Error:', error); + }); +} \ No newline at end of file From b61599ca707962b76968bbe2267e4cc3b5c49f48 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=82=98=EC=A0=9C=EB=B2=95?= Date: Sun, 28 Jul 2024 11:10:35 +0900 Subject: [PATCH 050/138] =?UTF-8?q?feat:=20login-callback.html=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../resources/templates/login-callback.html | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 src/main/resources/templates/login-callback.html diff --git a/src/main/resources/templates/login-callback.html b/src/main/resources/templates/login-callback.html new file mode 100644 index 000000000..363765c6a --- /dev/null +++ b/src/main/resources/templates/login-callback.html @@ -0,0 +1,28 @@ + + + + + 사용자 정보 + + + +
+

사용자 정보

+
+
+ + +
+
+ + +
+
+ + +
+
+
+ + + \ No newline at end of file From 74dca820bfed6f8faae1ebee12947ddadbd6f0e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=82=98=EC=A0=9C=EB=B2=95?= Date: Sun, 28 Jul 2024 11:10:41 +0900 Subject: [PATCH 051/138] =?UTF-8?q?feat:=20login-call.html=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../resources/templates/form/login-form.html | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 src/main/resources/templates/form/login-form.html diff --git a/src/main/resources/templates/form/login-form.html b/src/main/resources/templates/form/login-form.html new file mode 100644 index 000000000..eeb3b6e3f --- /dev/null +++ b/src/main/resources/templates/form/login-form.html @@ -0,0 +1,31 @@ + + + + + + 로그인 + + + +
+

로그인

+
+
+ + +
+
+ + +
+ +
+
+ +
+ + + \ No newline at end of file From 9093d5a99fe232cb3d038acae5efbcd5b0488c0f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=82=98=EC=A0=9C=EB=B2=95?= Date: Sun, 28 Jul 2024 11:10:50 +0900 Subject: [PATCH 052/138] =?UTF-8?q?feat:=20register-form.html=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../templates/form/register-form.html | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 src/main/resources/templates/form/register-form.html diff --git a/src/main/resources/templates/form/register-form.html b/src/main/resources/templates/form/register-form.html new file mode 100644 index 000000000..e612a3d55 --- /dev/null +++ b/src/main/resources/templates/form/register-form.html @@ -0,0 +1,34 @@ + + + + + + 회원가입 + + + +
+

회원가입

+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+ +
+
+ + + \ No newline at end of file From c5436da1d599699e02a1bc9512f23673dde9850c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=82=98=EC=A0=9C=EB=B2=95?= Date: Sun, 28 Jul 2024 11:11:04 +0900 Subject: [PATCH 053/138] =?UTF-8?q?refactor:=20index.html=20=EC=A3=BC?= =?UTF-8?q?=EC=86=8C=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/resources/static/index.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/resources/static/index.html b/src/main/resources/static/index.html index ef654ad6c..a41323396 100644 --- a/src/main/resources/static/index.html +++ b/src/main/resources/static/index.html @@ -10,7 +10,7 @@ - + \ No newline at end of file From 5c338df8eca51ddf0c57adf1ecd3ed632acfdd34 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=82=98=EC=A0=9C=EB=B2=95?= Date: Fri, 26 Jul 2024 17:49:41 +0900 Subject: [PATCH 054/138] =?UTF-8?q?docs:=20README.md=20step5=202=EB=8B=A8?= =?UTF-8?q?=EA=B3=84=20=EC=9A=94=EA=B5=AC=EC=82=AC=ED=95=AD=20=EB=B0=98?= =?UTF-8?q?=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 42 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 41 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 42b1f59e0..1ee7158b8 100644 --- a/README.md +++ b/README.md @@ -10,4 +10,44 @@ * 토큰 받기를 읽고 액세스 토큰을 추출한다. * 앱 키, 인가 코드가 절대 유출되지 않도록 한다. * 특히 시크릿 키는 GitHub나 클라이언트 코드 등 외부에서 볼 수 있는 곳에 추가하지 않는다. -* (선택) 인가 코드를 받는 방법이 불편한 경우 카카오 로그인 화면을 구현한다. \ No newline at end of file +* (선택) 인가 코드를 받는 방법이 불편한 경우 카카오 로그인 화면을 구현한다. + +### 🚀 2단계 - 주문하기 +*** +#### 기능 요구 사항 +카카오톡 메시지 API를 사용하여 주문하기 기능을 구현한다. + +* 주문할 때 수령인에게 보낼 메시지를 작성할 수 있다. +* 상품 옵션과 해당 수량을 선택하여 주문하면 해당 상품 옵션의 수량이 차감된다. +* 해당 상품이 위시 리스트에 있는 경우 위시 리스트에서 삭제한다. +* 나에게 보내기를 읽고 주문 내역을 카카오톡 메시지로 전송한다. + * 메시지는 메시지 템플릿의 기본 템플릿이나 사용자 정의 템플릿을 사용하여 자유롭게 작성한다. + +아래 예시와 같이 HTTP 메시지를 주고받도록 구현한다. + +##### Request +``` +POST /api/orders HTTP/1.1 +Authorization: Bearer {token} +Content-Type: application/json + +{ + "optionId": 1, + "quantity": 2, + "message": "Please handle this order with care." +} +``` + +##### Response +``` +HTTP/1.1 201 Created +Content-Type: application/json + +{ +"id": 1, +"optionId": 1, +"quantity": 2, +"orderDateTime": "2024-07-21T10:00:00", +"message": "Please handle this order with care." +} +``` From 81385f09705ae8e0c1b75ad03d2bfae926af77d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=82=98=EC=A0=9C=EB=B2=95?= Date: Sun, 28 Jul 2024 21:16:55 +0900 Subject: [PATCH 055/138] =?UTF-8?q?feat:=20Token.java=20fromBearer()=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/gift/authentication/token/Token.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/main/java/gift/authentication/token/Token.java b/src/main/java/gift/authentication/token/Token.java index 8ff73f026..ba75ba991 100644 --- a/src/main/java/gift/authentication/token/Token.java +++ b/src/main/java/gift/authentication/token/Token.java @@ -15,6 +15,10 @@ public static Token from(String value) { return new Token(value); } + public static Token fromBearer(String value) { + return new Token(value.replace("Bearer ", "")); + } + public String getValue() { return value; } From 1bbf57bb9d390dfa6dafc4f2482f56b811dfe068 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=82=98=EC=A0=9C=EB=B2=95?= Date: Sun, 28 Jul 2024 21:18:40 +0900 Subject: [PATCH 056/138] =?UTF-8?q?feat:=20KakaoClient.java=20sendMessage(?= =?UTF-8?q?)=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/gift/web/client/KakaoClient.java | 20 ++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/src/main/java/gift/web/client/KakaoClient.java b/src/main/java/gift/web/client/KakaoClient.java index 401e1c28e..af3986a9d 100644 --- a/src/main/java/gift/web/client/KakaoClient.java +++ b/src/main/java/gift/web/client/KakaoClient.java @@ -1,10 +1,15 @@ package gift.web.client; +import static org.springframework.http.HttpHeaders.AUTHORIZATION; + import gift.authentication.token.KakaoToken; import gift.web.client.dto.KakaoInfo; +import gift.web.client.dto.KakaoMessageResult; import java.net.URI; import org.springframework.cloud.openfeign.FeignClient; +import org.springframework.http.MediaType; import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestHeader; import org.springframework.web.bind.annotation.RequestParam; @@ -14,7 +19,7 @@ public interface KakaoClient { @PostMapping KakaoInfo getKakaoInfo( URI uri, - @RequestHeader("Authorization") String accessToken); + @RequestHeader(AUTHORIZATION) String accessToken); @PostMapping KakaoToken getToken( @@ -23,4 +28,17 @@ KakaoToken getToken( @RequestParam("client_id") String clientId, @RequestParam("redirect_uri") String redirectUrl, @RequestParam("grant_type") String grantType); + + /** + * 카카오톡 메시지 - 나에게 보내기 + * @param uri {@link https://kapi.kakao.com/v2/api/talk/memo/default/send} 으로 고정 + * @param accessToken Bearer Token + * @param templateObject 메시지 구성 요소를 담은 객체(Object) 피드, 리스트, 위치, 커머스, 텍스트, 캘린더 중 하나 + */ + @PostMapping(consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE) + KakaoMessageResult sendMessage( + URI uri, + @RequestHeader(AUTHORIZATION) String accessToken, + @RequestBody String templateObject + ); } From b786ed06bb28934a92b4f37cfe4166b862e365cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=82=98=EC=A0=9C=EB=B2=95?= Date: Sun, 28 Jul 2024 21:19:51 +0900 Subject: [PATCH 057/138] =?UTF-8?q?feat:=20KakaoCommerce.java=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../gift/web/client/dto/KakaoCommerce.java | 207 ++++++++++++++++++ 1 file changed, 207 insertions(+) create mode 100644 src/main/java/gift/web/client/dto/KakaoCommerce.java diff --git a/src/main/java/gift/web/client/dto/KakaoCommerce.java b/src/main/java/gift/web/client/dto/KakaoCommerce.java new file mode 100644 index 000000000..aec159aab --- /dev/null +++ b/src/main/java/gift/web/client/dto/KakaoCommerce.java @@ -0,0 +1,207 @@ +package gift.web.client.dto; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.PropertyNamingStrategies; +import com.fasterxml.jackson.databind.annotation.JsonNaming; +import gift.web.dto.response.product.ReadProductResponse; + +@JsonNaming(value = PropertyNamingStrategies.SnakeCaseStrategy.class) +public class KakaoCommerce { + + private final String objectType = "commerce"; + private Content content; + private Commerce commerce; + + @JsonNaming(value = PropertyNamingStrategies.SnakeCaseStrategy.class) + static class Content { + private String title; + private String imageUrl; + private Integer imageWidth; + private Integer imageHeight; + private String description; + private Link link; + + public Content(String title, String imageUrl, Integer imageWidth, Integer imageHeight, + String description, Link link) { + this.title = title; + this.imageUrl = imageUrl; + this.imageWidth = imageWidth; + this.imageHeight = imageHeight; + this.description = description; + this.link = link; + } + + @JsonNaming(value = PropertyNamingStrategies.SnakeCaseStrategy.class) + static class Link { + private String webUrl; + private String mobileWebUrl; + private String androidExecutionParams; + private String iosExecutionParams; + + public Link(String webUrl, String mobileWebUrl, String androidExecutionParams, + String iosExecutionParams) { + this.webUrl = webUrl; + this.mobileWebUrl = mobileWebUrl; + this.androidExecutionParams = androidExecutionParams; + this.iosExecutionParams = iosExecutionParams; + } + + public String getWebUrl() { + return webUrl; + } + + public String getMobileWebUrl() { + return mobileWebUrl; + } + + public String getAndroidExecutionParams() { + return androidExecutionParams; + } + + public String getIosExecutionParams() { + return iosExecutionParams; + } + } + + public String getTitle() { + return title; + } + + public String getImageUrl() { + return imageUrl; + } + + public Integer getImageWidth() { + return imageWidth; + } + + public Integer getImageHeight() { + return imageHeight; + } + + public String getDescription() { + return description; + } + + public Link getLink() { + return link; + } + } + + @JsonNaming(value = PropertyNamingStrategies.SnakeCaseStrategy.class) + static class Commerce { + private Integer regularPrice; + private Integer discountPrice; + private Integer discountRate; + + public Commerce(Integer regularPrice, Integer discountPrice, Integer discountRate) { + this.regularPrice = regularPrice; + this.discountPrice = discountPrice; + this.discountRate = discountRate; + } + + public Commerce(Integer regularPrice) { + this.regularPrice = regularPrice; + } + + public Integer getRegularPrice() { + return regularPrice; + } + + public Integer getDiscountPrice() { + return discountPrice; + } + + public Integer getDiscountRate() { + return discountRate; + } + } + + public KakaoCommerce() { + + } + + public KakaoCommerce setCommerce(Integer regularPrice, Integer discountPrice, Integer discountRate) { + this.commerce = new Commerce(regularPrice, discountPrice, discountRate); + return this; + } + + public KakaoCommerce setCommerce(Integer regularPrice) { + this.commerce = new Commerce(regularPrice); + return this; + } + + public KakaoCommerce setContent(String title, String imageUrl, Integer imageWidth, Integer imageHeight, String description, String webUrl, String mobileWebUrl, String androidExecutionParams, String iosExecutionParams) { + this.content = new Content(title, imageUrl, imageWidth, imageHeight, description, + new Content.Link(webUrl, mobileWebUrl, androidExecutionParams, iosExecutionParams)); + return this; + } + + public String getObjectType() { + return objectType; + } + + public Content getContent() { + return content; + } + + public Commerce getCommerce() { + return commerce; + } + + /** + * 카카오 상거래 메시지 생성 - 할인 적용 + * @param product 상품 정보 + * @param discountRate 할인율 (0 ~ 100) + * @param message 메시지 + * @return + */ + public static KakaoCommerce of(ReadProductResponse product, int discountRate, String message) { + return new KakaoCommerce().setContent( + product.getName(), + product.getImageUrl().toString(), + 640, + 640, + message, + "https://localhost:8080", + "https://localhost:8080", + "contentId=" + product.getId(), + "contentId=" + product.getId() + ).setCommerce(product.getPrice(), product.getPrice(), 0); + } + + /** + * 카카오 상거래 메시지 생성 - 할인 없음 + * @param product 상품 정보 + * @param message 메시지 + * @return + */ + public static KakaoCommerce of(ReadProductResponse product, String message) { + return new KakaoCommerce().setContent( + product.getName(), + product.getImageUrl().toString(), + 640, + 640, + message, + "https://localhost:8080", + "https://localhost:8080", + "contentId=" + product.getId(), + "contentId=" + product.getId() + ).setCommerce(product.getPrice()); + } + + public String toJson() { + ObjectMapper mapper = new ObjectMapper(); + try { + return "template_object=" + mapper.writeValueAsString(this); + } catch (JsonProcessingException e) { + throw new RuntimeException("JSON 변환 실패", e); + } + } + + public String getContentDescription() { + return content.getDescription(); + } +} + From 859b8bbe79b234b156dd935043df22afe9c55ffc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=82=98=EC=A0=9C=EB=B2=95?= Date: Sun, 28 Jul 2024 21:20:03 +0900 Subject: [PATCH 058/138] =?UTF-8?q?feat:=20KakaoMessageResult.java=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../gift/web/client/dto/KakaoMessageResult.java | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 src/main/java/gift/web/client/dto/KakaoMessageResult.java diff --git a/src/main/java/gift/web/client/dto/KakaoMessageResult.java b/src/main/java/gift/web/client/dto/KakaoMessageResult.java new file mode 100644 index 000000000..5517133b0 --- /dev/null +++ b/src/main/java/gift/web/client/dto/KakaoMessageResult.java @@ -0,0 +1,17 @@ +package gift.web.client.dto; + +import com.fasterxml.jackson.annotation.JsonCreator; + +public class KakaoMessageResult { + + private final Integer resultCode; + + @JsonCreator + public KakaoMessageResult(Integer resultCode) { + this.resultCode = resultCode; + } + + public Integer getResultCode() { + return resultCode; + } +} From 099f45f6595a0dd631149e82db536504dd3cdcf2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=82=98=EC=A0=9C=EB=B2=95?= Date: Sun, 28 Jul 2024 21:20:32 +0900 Subject: [PATCH 059/138] =?UTF-8?q?feat:=20CreateOrderRequest.java=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dto/request/order/CreateOrderRequest.java | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 src/main/java/gift/web/dto/request/order/CreateOrderRequest.java diff --git a/src/main/java/gift/web/dto/request/order/CreateOrderRequest.java b/src/main/java/gift/web/dto/request/order/CreateOrderRequest.java new file mode 100644 index 000000000..f9d7f6d20 --- /dev/null +++ b/src/main/java/gift/web/dto/request/order/CreateOrderRequest.java @@ -0,0 +1,35 @@ +package gift.web.dto.request.order; + +import jakarta.validation.constraints.Min; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; + +public class CreateOrderRequest { + + @NotNull + private final Long optionId; + + @Min(1) + private final Integer quantity; + + @NotEmpty + private final String message; + + public CreateOrderRequest(Long optionId, Integer quantity, String message) { + this.optionId = optionId; + this.quantity = quantity; + this.message = message; + } + + public Long getOptionId() { + return optionId; + } + + public Integer getQuantity() { + return quantity; + } + + public String getMessage() { + return message; + } +} From 15fb1237477f73a9da3d9bc18fa74a4f380b4bd2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=82=98=EC=A0=9C=EB=B2=95?= Date: Sun, 28 Jul 2024 21:21:29 +0900 Subject: [PATCH 060/138] =?UTF-8?q?feat:=20KakaoProperties.java=20messageU?= =?UTF-8?q?rl=20=ED=95=84=EB=93=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - xxxUrlAsUri 편의 메서드 추가 --- .../java/gift/config/KakaoProperties.java | 22 ++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/src/main/java/gift/config/KakaoProperties.java b/src/main/java/gift/config/KakaoProperties.java index baa7b457c..04355b18b 100644 --- a/src/main/java/gift/config/KakaoProperties.java +++ b/src/main/java/gift/config/KakaoProperties.java @@ -1,5 +1,6 @@ package gift.config; +import java.net.URI; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.context.properties.bind.ConstructorBinding; @@ -13,10 +14,12 @@ public class KakaoProperties { private final String userInfoUrl; private final String tokenUrl; private final String responseType; + private final String messageUrl; @ConstructorBinding public KakaoProperties(String clientId, String redirectUri, String contentType, - String grantType, String userInfoUrl, String tokenUrl, String responseType) { + String grantType, String userInfoUrl, String tokenUrl, String responseType, + String messageUrl) { this.clientId = clientId; this.redirectUri = redirectUri; this.contentType = contentType; @@ -24,6 +27,7 @@ public KakaoProperties(String clientId, String redirectUri, String contentType, this.userInfoUrl = userInfoUrl; this.tokenUrl = tokenUrl; this.responseType = responseType; + this.messageUrl = messageUrl; } public String getClientId() { @@ -53,4 +57,20 @@ public String getTokenUrl() { public String getResponseType() { return responseType; } + + public String getMessageUrl() { + return messageUrl; + } + + public URI getUserInfoUrlAsUri() { + return URI.create(userInfoUrl); + } + + public URI getTokenUrlAsUri() { + return URI.create(tokenUrl); + } + + public URI getMessageUrlAsUri() { + return URI.create(messageUrl); + } } From 9fe6cb3e9dda7b1ee3bcaec97db09751ad5d6d01 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=82=98=EC=A0=9C=EB=B2=95?= Date: Sun, 28 Jul 2024 21:22:46 +0900 Subject: [PATCH 061/138] =?UTF-8?q?feat:=20OrderResponse.java=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../web/dto/response/order/OrderResponse.java | 50 +++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 src/main/java/gift/web/dto/response/order/OrderResponse.java diff --git a/src/main/java/gift/web/dto/response/order/OrderResponse.java b/src/main/java/gift/web/dto/response/order/OrderResponse.java new file mode 100644 index 000000000..a718cd79b --- /dev/null +++ b/src/main/java/gift/web/dto/response/order/OrderResponse.java @@ -0,0 +1,50 @@ +package gift.web.dto.response.order; + +public class OrderResponse { + + private Long productId; + + private Long optionId; + + private Integer optionStock; + + private Integer quantity; + + private String productName; + + private String message; + + public OrderResponse(Long productId, Long optionId, Integer optionStock, Integer quantity, + String productName, String message) { + this.productId = productId; + this.optionId = optionId; + this.optionStock = optionStock; + this.quantity = quantity; + this.productName = productName; + this.message = message; + } + + public Long getProductId() { + return productId; + } + + public Long getOptionId() { + return optionId; + } + + public Integer getOptionStock() { + return optionStock; + } + + public Integer getQuantity() { + return quantity; + } + + public String getProductName() { + return productName; + } + + public String getMessage() { + return message; + } +} \ No newline at end of file From 0fd1a92113ac3d0c4796cfbad19ae5583c5e793c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=82=98=EC=A0=9C=EB=B2=95?= Date: Sun, 28 Jul 2024 21:28:15 +0900 Subject: [PATCH 062/138] =?UTF-8?q?feat:=20ProductOptionService.java=20val?= =?UTF-8?q?idateExistsProduct()=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../gift/service/ProductOptionService.java | 21 ++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/src/main/java/gift/service/ProductOptionService.java b/src/main/java/gift/service/ProductOptionService.java index 9663b89a5..dc5f7650c 100644 --- a/src/main/java/gift/service/ProductOptionService.java +++ b/src/main/java/gift/service/ProductOptionService.java @@ -2,6 +2,8 @@ import gift.domain.ProductOption; import gift.repository.ProductOptionRepository; +import gift.repository.ProductRepository; +import gift.web.dto.request.order.CreateOrderRequest; import gift.web.dto.request.productoption.CreateProductOptionRequest; import gift.web.dto.request.productoption.SubtractProductOptionQuantityRequest; import gift.web.dto.request.productoption.UpdateProductOptionRequest; @@ -21,13 +23,17 @@ public class ProductOptionService { private final ProductOptionRepository productOptionRepository; + private final ProductRepository productRepository; - public ProductOptionService(ProductOptionRepository productOptionRepository) { + public ProductOptionService(ProductOptionRepository productOptionRepository, + ProductRepository productRepository) { this.productOptionRepository = productOptionRepository; + this.productRepository = productRepository; } @Transactional public CreateProductOptionResponse createOption(Long productId, CreateProductOptionRequest request) { + validateExistsProduct(productId); String optionName = request.getName(); validateOptionNameExists(productId, optionName); @@ -36,6 +42,15 @@ public CreateProductOptionResponse createOption(Long productId, CreateProductOpt return CreateProductOptionResponse.fromEntity(createdOption); } + /** + * 상품이 존재하는지 확인합니다. + * @param productId 상품 아이디 + */ + private void validateExistsProduct(Long productId) { + productRepository.findById(productId) + .orElseThrow(() -> new ResourceNotFoundException("상품 아이디: ", productId.toString())); + } + /** * 상품 옵션 이름이 이미 존재하는지 확인합니다.
* 이미 존재한다면 {@link AlreadyExistsException} 을 발생시킵니다. @@ -131,6 +146,10 @@ public SubtractProductOptionQuantityResponse subtractOptionStock(Long optionId, return SubtractProductOptionQuantityResponse.fromEntity(option); } + public SubtractProductOptionQuantityResponse subtractOptionStock(CreateOrderRequest request) { + return subtractOptionStock(request.getOptionId(), new SubtractProductOptionQuantityRequest(request.getQuantity())); + } + @Transactional public void deleteOption(Long optionId) { ProductOption option = productOptionRepository.findById(optionId) From 2fcfc261ba799f925944baa4b80754afbc1685e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=82=98=EC=A0=9C=EB=B2=95?= Date: Sun, 28 Jul 2024 21:28:39 +0900 Subject: [PATCH 063/138] =?UTF-8?q?feat:=20ReadProductResponse.java=20Opti?= =?UTF-8?q?ons=20=ED=95=84=EB=93=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../response/product/ReadProductResponse.java | 22 ++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/src/main/java/gift/web/dto/response/product/ReadProductResponse.java b/src/main/java/gift/web/dto/response/product/ReadProductResponse.java index 48ba51892..ec462447d 100644 --- a/src/main/java/gift/web/dto/response/product/ReadProductResponse.java +++ b/src/main/java/gift/web/dto/response/product/ReadProductResponse.java @@ -2,6 +2,9 @@ import gift.domain.Product; import gift.web.dto.response.category.ReadCategoryResponse; +import gift.web.dto.response.productoption.ReadProductOptionResponse; +import java.util.List; +import java.util.stream.Collectors; public class ReadProductResponse { @@ -9,21 +12,30 @@ public class ReadProductResponse { private final String name; private final Integer price; private final String imageUrl; + private final List productOptions; private final ReadCategoryResponse category; - private ReadProductResponse(Long id, String name, Integer price, String imageUrl, - ReadCategoryResponse category) { + public ReadProductResponse(Long id, String name, Integer price, String imageUrl, + List productOptions, ReadCategoryResponse category) { this.id = id; this.name = name; this.price = price; this.imageUrl = imageUrl; + this.productOptions = productOptions; this.category = category; } public static ReadProductResponse fromEntity(Product product) { - return new ReadProductResponse(product.getId(), product.getName(), product.getPrice(), product.getImageUrl().toString(), ReadCategoryResponse.fromEntity(product.getCategory())); + List productOptions = product.getProductOptions().stream() + .map(productOption -> ReadProductOptionResponse.fromEntity(productOption)) + .collect(Collectors.toList()); + + ReadCategoryResponse category = ReadCategoryResponse.fromEntity(product.getCategory()); + + return new ReadProductResponse(product.getId(), product.getName(), product.getPrice(), product.getImageUrl().toString(), productOptions, category); } + public Long getId() { return id; } @@ -40,6 +52,10 @@ public String getImageUrl() { return imageUrl; } + public List getProductOptions() { + return productOptions; + } + public ReadCategoryResponse getCategory() { return category; } From 08c2fef8ddff2aa39c087031f09ce01fcc93228c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=82=98=EC=A0=9C=EB=B2=95?= Date: Sun, 28 Jul 2024 21:28:59 +0900 Subject: [PATCH 064/138] =?UTF-8?q?feat:=20OrderService.java=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/gift/service/OrderService.java | 71 ++++++++++++++++++++ 1 file changed, 71 insertions(+) create mode 100644 src/main/java/gift/service/OrderService.java diff --git a/src/main/java/gift/service/OrderService.java b/src/main/java/gift/service/OrderService.java new file mode 100644 index 000000000..ddd967b1f --- /dev/null +++ b/src/main/java/gift/service/OrderService.java @@ -0,0 +1,71 @@ +package gift.service; + +import gift.authentication.token.JwtResolver; +import gift.authentication.token.Token; +import gift.config.KakaoProperties; +import gift.web.client.KakaoClient; +import gift.web.client.dto.KakaoCommerce; +import gift.web.dto.request.order.CreateOrderRequest; +import gift.web.dto.response.order.OrderResponse; +import gift.web.dto.response.product.ReadProductResponse; +import gift.web.dto.response.productoption.SubtractProductOptionQuantityResponse; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@Transactional(readOnly = true) +public class OrderService { + + private final KakaoClient kakaoClient; + private final JwtResolver jwtResolver; + private final ProductOptionService productOptionService; + private final ProductService productService; + private final KakaoProperties kakaoProperties; + + public OrderService(KakaoClient kakaoClient, JwtResolver jwtResolver, ProductOptionService productOptionService, + ProductService productService, KakaoProperties kakaoProperties) { + this.kakaoClient = kakaoClient; + this.jwtResolver = jwtResolver; + this.productOptionService = productOptionService; + this.productService = productService; + this.kakaoProperties = kakaoProperties; + } + + @Transactional + public OrderResponse createOrder(String accessToken, Long productId, CreateOrderRequest request) { + //상품 옵션 수량 차감 + SubtractProductOptionQuantityResponse subtractOptionStockResponse = productOptionService.subtractOptionStock(request); + + ReadProductResponse product = productService.readProductById(productId); + KakaoCommerce kakaoCommerce = KakaoCommerce.of(product, request.getMessage()); + + sendOrderMessageIfSocialMember(accessToken, kakaoCommerce); + return new OrderResponse( + productId, + request.getOptionId(), + subtractOptionStockResponse.getStock(), + request.getQuantity(), + product.getName(), + request.getMessage()); + } + + /** + * 소셜 로그인을 통해 주문한 경우 카카오톡 메시지를 전송합니다 + * @param accessToken Bearer Token + * @param kakaoCommerce 카카오 상거래 메시지 + */ + private void sendOrderMessageIfSocialMember(String accessToken, KakaoCommerce kakaoCommerce) { + jwtResolver.resolveSocialToken(Token.fromBearer(accessToken)) + .ifPresent(socialToken -> { + String json = kakaoCommerce.toJson(); + kakaoClient.sendMessage( + kakaoProperties.getMessageUrlAsUri(), + getBearerToken(socialToken), + json); + }); + } + + private String getBearerToken(String token) { + return "Bearer " + token; + } +} From d32f7a19ec23ff93c6791ccc21c90e4f61b7ece5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=82=98=EC=A0=9C=EB=B2=95?= Date: Sun, 28 Jul 2024 21:29:25 +0900 Subject: [PATCH 065/138] =?UTF-8?q?feat:=20ProductApiController.java=20?= =?UTF-8?q?=EC=83=81=ED=92=88=20=EC=A3=BC=EB=AC=B8=20API=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../web/controller/api/ProductApiController.java | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/main/java/gift/web/controller/api/ProductApiController.java b/src/main/java/gift/web/controller/api/ProductApiController.java index 2e2ec7181..5bcd0dee8 100644 --- a/src/main/java/gift/web/controller/api/ProductApiController.java +++ b/src/main/java/gift/web/controller/api/ProductApiController.java @@ -1,15 +1,18 @@ package gift.web.controller.api; import gift.authentication.annotation.LoginMember; +import gift.service.OrderService; import gift.service.ProductOptionService; import gift.service.ProductService; import gift.service.WishProductService; import gift.web.dto.MemberDetails; +import gift.web.dto.request.order.CreateOrderRequest; import gift.web.dto.request.product.CreateProductRequest; import gift.web.dto.request.product.UpdateProductRequest; import gift.web.dto.request.productoption.CreateProductOptionRequest; import gift.web.dto.request.productoption.UpdateProductOptionRequest; import gift.web.dto.request.wishproduct.CreateWishProductRequest; +import gift.web.dto.response.order.OrderResponse; import gift.web.dto.response.product.CreateProductResponse; import gift.web.dto.response.product.ReadAllProductsResponse; import gift.web.dto.response.product.ReadProductResponse; @@ -23,6 +26,7 @@ import java.util.NoSuchElementException; import org.springframework.data.domain.Pageable; import org.springframework.data.web.PageableDefault; +import org.springframework.http.HttpHeaders; import org.springframework.http.ResponseEntity; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.DeleteMapping; @@ -31,6 +35,7 @@ import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PutMapping; import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestHeader; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; @@ -42,11 +47,14 @@ public class ProductApiController { private final ProductService productService; private final WishProductService wishProductService; private final ProductOptionService productOptionService; + private final OrderService orderService; - public ProductApiController(ProductService productService, WishProductService wishProductService, ProductOptionService productOptionService) { + public ProductApiController(ProductService productService, WishProductService wishProductService, ProductOptionService productOptionService, + OrderService orderService) { this.productService = productService; this.wishProductService = wishProductService; this.productOptionService = productOptionService; + this.orderService = orderService; } @GetMapping @@ -114,6 +122,12 @@ public ResponseEntity createOption(@PathVariable Lo return ResponseEntity.created(location).body(response); } + @PostMapping("/{productId}/order") + public ResponseEntity orderProduct(@RequestHeader(HttpHeaders.AUTHORIZATION) String accessToken, @PathVariable Long productId, @Validated @RequestBody CreateOrderRequest request) { + OrderResponse response = orderService.createOrder(accessToken, productId, request); + return ResponseEntity.ok(response); + } + @PutMapping("/{productId}/options/{optionId}") public ResponseEntity updateOption(@PathVariable Long productId, @PathVariable Long optionId, @Validated @RequestBody UpdateProductOptionRequest request) { UpdateProductOptionResponse response = productOptionService.updateOption(optionId, productId, request); From a2a6af569351bf24f7310831da07102941521385 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=82=98=EC=A0=9C=EB=B2=95?= Date: Sun, 28 Jul 2024 21:29:55 +0900 Subject: [PATCH 066/138] =?UTF-8?q?refactor:=20LoginService.java=20?= =?UTF-8?q?=EB=A6=AC=ED=8C=A9=ED=86=A0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/gift/service/LoginService.java | 35 +++++++------------- 1 file changed, 12 insertions(+), 23 deletions(-) diff --git a/src/main/java/gift/service/LoginService.java b/src/main/java/gift/service/LoginService.java index 03ec44394..dc78e70f1 100644 --- a/src/main/java/gift/service/LoginService.java +++ b/src/main/java/gift/service/LoginService.java @@ -9,9 +9,6 @@ import gift.web.client.dto.KakaoAccount; import gift.web.client.dto.KakaoInfo; import gift.web.dto.response.LoginResponse; -import gift.web.validation.exception.client.InvalidCredentialsException; -import java.net.URI; -import java.net.URISyntaxException; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -47,30 +44,22 @@ public LoginResponse kakaoLogin(final String authorizationCode){ return new LoginResponse(accessToken.getValue()); } - private KakaoToken getToken(String authorizationCode) { - try { - return kakaoClient.getToken( - new URI(kakaoProperties.getTokenUrl()), - authorizationCode, - kakaoProperties.getClientId(), - kakaoProperties.getRedirectUri(), - kakaoProperties.getGrantType()); - } catch (URISyntaxException e) { - throw new InvalidCredentialsException(e); - } + private KakaoToken getToken(final String authorizationCode) { + return kakaoClient.getToken( + kakaoProperties.getTokenUrlAsUri(), + authorizationCode, + kakaoProperties.getClientId(), + kakaoProperties.getRedirectUri(), + kakaoProperties.getGrantType()); } - private KakaoInfo getInfo(KakaoToken kakaoToken) { - try { - return kakaoClient.getKakaoInfo( - new URI(kakaoProperties.getUserInfoUrl()), - getBearerToken(kakaoToken)); - } catch (URISyntaxException e) { - throw new InvalidCredentialsException(e); - } + private KakaoInfo getInfo(final KakaoToken kakaoToken) { + return kakaoClient.getKakaoInfo( + kakaoProperties.getUserInfoUrlAsUri(), + getBearerToken(kakaoToken)); } - private String getBearerToken(KakaoToken kakaoToken) { + private String getBearerToken(final KakaoToken kakaoToken) { return kakaoToken.getTokenType() + " " + kakaoToken.getAccessToken(); } } From 55a3efa687ec6a3fa7cf15b032f59dde09c7ae52 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=82=98=EC=A0=9C=EB=B2=95?= Date: Sun, 28 Jul 2024 21:41:40 +0900 Subject: [PATCH 067/138] =?UTF-8?q?feat:=20script.js=20=EC=82=AC=EC=9A=A9?= =?UTF-8?q?=ED=95=98=EC=A7=80=20=EC=95=8A=EB=8A=94=20=ED=95=A8=EC=88=98=20?= =?UTF-8?q?=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - kakaoLogin() --- src/main/resources/static/js/script.js | 38 -------------------------- 1 file changed, 38 deletions(-) diff --git a/src/main/resources/static/js/script.js b/src/main/resources/static/js/script.js index 9fed7fed9..98b615c83 100644 --- a/src/main/resources/static/js/script.js +++ b/src/main/resources/static/js/script.js @@ -140,44 +140,6 @@ function giftLogin() { }); } -function kakaoLogin() { - fetch('https://kauth.kakao.com/oauth/authorize?client_id=c14cc9f825429533e917e1b1be966e08&redirect_uri=http://localhost:8080/api/login/oauth2/kakao&response_type=code') - .then(response => { - if (!response.ok) { - return response.json().then(errorData => { - throw new Error(errorData.description); - }); - } - return response.json(); - }) - .then(data => { - console.log(data); - localStorage.setItem('accessToken', data.accessToken); - - fetch('/view/login-callback', { - method: 'GET', - headers: { - 'Authorization': 'Bearer ' + data.accessToken - } - }) - .then(response => { - if (!response.ok) { - throw new Error('페이지 로드 실패: ' + response.statusText); - } - return response.text(); - }) - .then(html => { - document.write(html); - }) - .catch(error => { - console.error('페이지 로드 실패: ', error); - }); - }) - .catch(error => { - console.error('알 수 없는 에러가 발생했습니다! ', error); - }); -} - function registerUser() { const form = document.getElementById('registerForm'); const formData = new FormData(form); From 6425fc14f10cdb39cdc506a6084c76aed3a77a83 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=82=98=EC=A0=9C=EB=B2=95?= Date: Sun, 28 Jul 2024 21:42:13 +0900 Subject: [PATCH 068/138] =?UTF-8?q?feat:=20ProductViewController.java=20lo?= =?UTF-8?q?ginForm()=20modelAttribute=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - clientId, redirectUrl --- .../web/controller/view/ProductViewController.java | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/main/java/gift/web/controller/view/ProductViewController.java b/src/main/java/gift/web/controller/view/ProductViewController.java index d13e7df69..44aa5e0ae 100644 --- a/src/main/java/gift/web/controller/view/ProductViewController.java +++ b/src/main/java/gift/web/controller/view/ProductViewController.java @@ -1,6 +1,7 @@ package gift.web.controller.view; import gift.authentication.annotation.LoginMember; +import gift.config.KakaoProperties; import gift.service.ProductService; import gift.web.dto.MemberDetails; import gift.web.dto.form.CreateProductForm; @@ -19,8 +20,11 @@ public class ProductViewController { private final ProductService productService; - public ProductViewController(ProductService productService) { + private final KakaoProperties kakaoProperties; + + public ProductViewController(ProductService productService, KakaoProperties kakaoProperties) { this.productService = productService; + this.kakaoProperties = kakaoProperties; } @GetMapping("/products") @@ -45,7 +49,9 @@ public String editForm(@PathVariable Long id, Model model) { } @GetMapping("/login") - public String loginForm() { + public String loginForm(Model model) { + model.addAttribute("clientId", kakaoProperties.getClientId()); + model.addAttribute("redirectUri", kakaoProperties.getRedirectUri()); return "form/login-form"; } From e34cbd4a0ebf2044f3871a22f71362676c683bdc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=82=98=EC=A0=9C=EB=B2=95?= Date: Sun, 28 Jul 2024 21:42:36 +0900 Subject: [PATCH 069/138] =?UTF-8?q?refactor:=20login-form.html=20=EB=AF=BC?= =?UTF-8?q?=EA=B0=90=ED=95=9C=20=EC=A0=95=EB=B3=B4=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/resources/templates/form/login-form.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/resources/templates/form/login-form.html b/src/main/resources/templates/form/login-form.html index eeb3b6e3f..ac13998d2 100644 --- a/src/main/resources/templates/form/login-form.html +++ b/src/main/resources/templates/form/login-form.html @@ -23,7 +23,7 @@

로그인


From 8c841f80db9538e970ae6109177e75550d6cad99 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=82=98=EC=A0=9C=EB=B2=95?= Date: Mon, 29 Jul 2024 01:28:18 +0900 Subject: [PATCH 070/138] =?UTF-8?q?docs:=20README.md=205=EC=A3=BC=EC=B0=A8?= =?UTF-8?q?=203=EB=8B=A8=EA=B3=84=20=EC=9A=94=EA=B5=AC=EC=82=AC=ED=95=AD?= =?UTF-8?q?=20=EB=B0=98=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index 1ee7158b8..b020d8438 100644 --- a/README.md +++ b/README.md @@ -51,3 +51,8 @@ Content-Type: application/json "message": "Please handle this order with care." } ``` +### 🚀 3단계 - API 문서 만들기 +*** +#### 기능 요구 사항 +API 사양에 관해 클라이언트와 어떻게 소통할 수 있을까? +어떻게 하면 편하게 소통할 수 있을지 고민해 보고 그 방법을 구현한다. \ No newline at end of file From 62be644656fedd1a864e1b84e0597a151c35f509 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=82=98=EC=A0=9C=EB=B2=95?= Date: Mon, 29 Jul 2024 14:55:23 +0900 Subject: [PATCH 071/138] =?UTF-8?q?chore:=20build.gradle=20springdoc-opena?= =?UTF-8?q?pi=20=EC=9D=98=EC=A1=B4=EC=84=B1=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 3 +++ 1 file changed, 3 insertions(+) diff --git a/build.gradle b/build.gradle index 3b37a3492..89aa9aad0 100644 --- a/build.gradle +++ b/build.gradle @@ -31,6 +31,9 @@ dependencies { runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.12.6' runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.12.6' + // Swagger + implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.0.4' + runtimeOnly 'com.h2database:h2' testImplementation 'org.springframework.boot:spring-boot-starter-test' testRuntimeOnly 'org.junit.platform:junit-platform-launcher' From 85af60e2b3785cb8bb1ead2e826834297ef8992d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=82=98=EC=A0=9C=EB=B2=95?= Date: Mon, 29 Jul 2024 22:17:04 +0900 Subject: [PATCH 072/138] =?UTF-8?q?chore:=20REST=20Docs=20=EC=9D=98?= =?UTF-8?q?=EC=A1=B4=EC=84=B1=20=EC=B6=94=EA=B0=80=20=EB=B0=8F=20=EC=84=A4?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 45 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/build.gradle b/build.gradle index 89aa9aad0..c3ebf4db9 100644 --- a/build.gradle +++ b/build.gradle @@ -2,6 +2,7 @@ plugins { id 'java' id 'org.springframework.boot' version '3.3.1' id 'io.spring.dependency-management' version '1.1.5' + id 'org.asciidoctor.jvm.convert' version '4.0.3' } group = 'camp.nextstep.edu' @@ -17,6 +18,13 @@ repositories { mavenCentral() } +configurations { + asciidoctorExt //asciidoctorExt를 configurations로 지정 + compileOnly { + extendsFrom annotationProcessor + } +} + dependencies { implementation 'org.springframework.boot:spring-boot-starter-jdbc' implementation 'org.springframework.boot:spring-boot-starter-data-jpa' @@ -34,6 +42,10 @@ dependencies { // Swagger implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.0.4' + //REST Docs + asciidoctorExt 'org.springframework.restdocs:spring-restdocs-asciidoctor' + testImplementation 'org.springframework.restdocs:spring-restdocs-mockmvc' + runtimeOnly 'com.h2database:h2' testImplementation 'org.springframework.boot:spring-boot-starter-test' testRuntimeOnly 'org.junit.platform:junit-platform-launcher' @@ -48,3 +60,36 @@ dependencyManagement { tasks.named('test') { useJUnitPlatform() } + +ext { + set('snippetsDir', file("build/generated-snippets")) //snippets 파일이 저장될 경로 snippetsDir로 변수 설정 +} + +test { + outputs.dir snippetsDir + useJUnitPlatform() +} + +asciidoctor { + configurations 'asciidoctorExt' //Asciidoctor에서 asciidoctorExt를 사용하도록 설정 + baseDirFollowsSourceFile() + inputs.dir snippetsDir + dependsOn test //gradle build 시 test -> asciiDoctor 순서로 실행 +} + +asciidoctor.doFirst { + delete file('src/main/resources/static/docs') //asciidoctor 실행 전 docs 폴더 삭제 +} + +task createDocument(type: Copy) { + dependsOn asciidoctor + from file("build/docs/asciidoc") + into file("src/main/resources/static") +} + +bootJar { + dependsOn createDocument //Gradle build 시, createDocument -> bootJar 순으로 실행 + from("${asciidoctor.outputDir}") { + into 'static/docs' + } +} \ No newline at end of file From db5c784c3c3637a1cef19fd6866ea5ebdb62fac6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=82=98=EC=A0=9C=EB=B2=95?= Date: Mon, 29 Jul 2024 22:18:05 +0900 Subject: [PATCH 073/138] =?UTF-8?q?style:=20ProductApiController.java=20?= =?UTF-8?q?=EB=A9=94=EC=84=9C=EB=93=9C=20=EC=88=9C=EC=84=9C=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../web/controller/api/ProductApiController.java | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/main/java/gift/web/controller/api/ProductApiController.java b/src/main/java/gift/web/controller/api/ProductApiController.java index 5bcd0dee8..2061e9649 100644 --- a/src/main/java/gift/web/controller/api/ProductApiController.java +++ b/src/main/java/gift/web/controller/api/ProductApiController.java @@ -63,6 +63,12 @@ public ResponseEntity readAllProducts(@PageableDefault return ResponseEntity.ok(response); } + @GetMapping(params = "categoryId") + public ResponseEntity readProductsByCategoryId(@PageableDefault Pageable pageable, @RequestParam Long categoryId) { + ReadAllProductsResponse response = productService.readProductsByCategoryId(categoryId, pageable); + return ResponseEntity.ok(response); + } + @PostMapping public ResponseEntity createProduct( @Validated @RequestBody CreateProductRequest request) throws URISyntaxException { @@ -72,12 +78,6 @@ public ResponseEntity createProduct( return ResponseEntity.created(location).body(response); } - @GetMapping(params = "categoryId") - public ResponseEntity readProductsByCategoryId(@PageableDefault Pageable pageable, @RequestParam Long categoryId) { - ReadAllProductsResponse response = productService.readProductsByCategoryId(categoryId, pageable); - return ResponseEntity.ok(response); - } - @GetMapping("/{productId}") public ResponseEntity readProduct(@PathVariable Long productId) { ReadProductResponse response; From eae05d084a28d1a6c790e4de264901d5c1de58cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=82=98=EC=A0=9C=EB=B2=95?= Date: Mon, 29 Jul 2024 22:18:33 +0900 Subject: [PATCH 074/138] =?UTF-8?q?refactor:=20ReadProductResponse.java=20?= =?UTF-8?q?productOptions=20->=20options=20=EB=B3=80=EC=88=98=EB=AA=85=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../web/dto/response/product/ReadProductResponse.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/main/java/gift/web/dto/response/product/ReadProductResponse.java b/src/main/java/gift/web/dto/response/product/ReadProductResponse.java index ec462447d..90f52e9dd 100644 --- a/src/main/java/gift/web/dto/response/product/ReadProductResponse.java +++ b/src/main/java/gift/web/dto/response/product/ReadProductResponse.java @@ -12,16 +12,16 @@ public class ReadProductResponse { private final String name; private final Integer price; private final String imageUrl; - private final List productOptions; + private final List options; private final ReadCategoryResponse category; public ReadProductResponse(Long id, String name, Integer price, String imageUrl, - List productOptions, ReadCategoryResponse category) { + List options, ReadCategoryResponse category) { this.id = id; this.name = name; this.price = price; this.imageUrl = imageUrl; - this.productOptions = productOptions; + this.options = options; this.category = category; } @@ -52,8 +52,8 @@ public String getImageUrl() { return imageUrl; } - public List getProductOptions() { - return productOptions; + public List getOptions() { + return options; } public ReadCategoryResponse getCategory() { From f2fa3c1fe63c08f4ed98f8f24a41ee10cb9973f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=82=98=EC=A0=9C=EB=B2=95?= Date: Mon, 29 Jul 2024 22:18:57 +0900 Subject: [PATCH 075/138] =?UTF-8?q?test:=20RestDocsConfiguration.java=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../gift/config/RestDocsConfiguration.java | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 src/test/java/gift/config/RestDocsConfiguration.java diff --git a/src/test/java/gift/config/RestDocsConfiguration.java b/src/test/java/gift/config/RestDocsConfiguration.java new file mode 100644 index 000000000..8682ea2d6 --- /dev/null +++ b/src/test/java/gift/config/RestDocsConfiguration.java @@ -0,0 +1,21 @@ +package gift.config; + +import org.springframework.boot.test.context.TestConfiguration; +import org.springframework.context.annotation.Bean; +import org.springframework.restdocs.mockmvc.MockMvcRestDocumentation; +import org.springframework.restdocs.mockmvc.RestDocumentationResultHandler; +import org.springframework.restdocs.operation.preprocess.Preprocessors; + +@TestConfiguration +public class RestDocsConfiguration { + + @Bean + public RestDocumentationResultHandler write() { + return MockMvcRestDocumentation.document( + "{class-name}/{method-name}", + Preprocessors.preprocessRequest(Preprocessors.prettyPrint()), //예쁘게 출력 + Preprocessors.preprocessResponse(Preprocessors.prettyPrint()) //예쁘게 출력 + ); + } + +} From 78bacb692fccc65efdcf2da2be5b724fbf47bf1d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=82=98=EC=A0=9C=EB=B2=95?= Date: Mon, 29 Jul 2024 22:19:21 +0900 Subject: [PATCH 076/138] =?UTF-8?q?test:=20ProductServiceTest.java=20?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=9C=A0=EC=A7=80=EB=B3=B4?= =?UTF-8?q?=EC=88=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/test/java/gift/service/ProductServiceTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/gift/service/ProductServiceTest.java b/src/test/java/gift/service/ProductServiceTest.java index ad43d05ca..c8332d0c2 100644 --- a/src/test/java/gift/service/ProductServiceTest.java +++ b/src/test/java/gift/service/ProductServiceTest.java @@ -55,7 +55,7 @@ class ProductServiceTest { @BeforeEach void setUp() { - productOptionService = new ProductOptionService(productOptionRepository); + productOptionService = new ProductOptionService(productOptionRepository, productRepository); productService = new ProductService(productRepository, categoryRepository, wishProductRepository, productOptionService); } From e83b7aa7048bcdbe1b0655d49f0623b4461265fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=82=98=EC=A0=9C=EB=B2=95?= Date: Mon, 29 Jul 2024 22:19:44 +0900 Subject: [PATCH 077/138] =?UTF-8?q?test:=20ProductOptionServiceTest.java?= =?UTF-8?q?=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=9C=A0=EC=A7=80=EB=B3=B4?= =?UTF-8?q?=EC=88=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/test/java/gift/service/ProductOptionServiceTest.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/test/java/gift/service/ProductOptionServiceTest.java b/src/test/java/gift/service/ProductOptionServiceTest.java index 69b413f0c..da01416cd 100644 --- a/src/test/java/gift/service/ProductOptionServiceTest.java +++ b/src/test/java/gift/service/ProductOptionServiceTest.java @@ -4,13 +4,14 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.Assertions.assertAll; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; -import static org.junit.jupiter.api.Assertions.assertThrows; import static org.mockito.ArgumentMatchers.any; import static org.mockito.BDDMockito.given; +import gift.domain.Product; import gift.domain.ProductOption; import gift.domain.ProductOption.Builder; import gift.repository.ProductOptionRepository; +import gift.repository.ProductRepository; import gift.web.dto.request.productoption.CreateProductOptionRequest; import gift.web.dto.request.productoption.SubtractProductOptionQuantityRequest; import gift.web.dto.request.productoption.UpdateProductOptionRequest; @@ -37,6 +38,9 @@ class ProductOptionServiceTest { @Mock private ProductOptionRepository productOptionRepository; + @Mock + private ProductRepository productRepository; + @Test @DisplayName("상품 옵션 생성 요청이 정상적일 때, 상품 옵션을 성공적으로 생성합니다.") void createOption() { @@ -44,6 +48,7 @@ void createOption() { Long productId = 1L; CreateProductOptionRequest request = new CreateProductOptionRequest("optionName", 1000); given(productOptionRepository.save(any())).willReturn(request.toEntity(productId)); + given(productRepository.findById(any())).willReturn(Optional.of(new Product.Builder().productOptions(List.of(request.toEntity(productId))).build())); //when CreateProductOptionResponse response = productOptionService.createOption(productId, request); From 717477d1f4db04e39f3390e2b807f70a3c2febed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=82=98=EC=A0=9C=EB=B2=95?= Date: Mon, 29 Jul 2024 22:20:01 +0900 Subject: [PATCH 078/138] =?UTF-8?q?test:=20MockLoginMemberArgumentResolver?= =?UTF-8?q?.java=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../mock/MockLoginMemberArgumentResolver.java | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 src/test/java/gift/mock/MockLoginMemberArgumentResolver.java diff --git a/src/test/java/gift/mock/MockLoginMemberArgumentResolver.java b/src/test/java/gift/mock/MockLoginMemberArgumentResolver.java new file mode 100644 index 000000000..5829a7d93 --- /dev/null +++ b/src/test/java/gift/mock/MockLoginMemberArgumentResolver.java @@ -0,0 +1,25 @@ +package gift.mock; + +import gift.domain.constants.Platform; +import gift.domain.vo.Email; +import gift.web.dto.MemberDetails; +import gift.web.resolver.LoginMemberArgumentResolver; +import org.springframework.boot.test.context.TestComponent; +import org.springframework.core.MethodParameter; +import org.springframework.web.bind.support.WebDataBinderFactory; +import org.springframework.web.context.request.NativeWebRequest; +import org.springframework.web.method.support.ModelAndViewContainer; + +@TestComponent +public class MockLoginMemberArgumentResolver extends LoginMemberArgumentResolver { + + public MockLoginMemberArgumentResolver() { + super(null, null); + } + + @Override + public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, + NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception { + return new MemberDetails(1L, Email.from("member01@gmail.com"), Platform.GIFT); + } +} From 7b676b3abaa04d7edc218a9ff7b28a11bba0865f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=82=98=EC=A0=9C=EB=B2=95?= Date: Mon, 29 Jul 2024 22:20:16 +0900 Subject: [PATCH 079/138] =?UTF-8?q?test:=20ProductApiControllerTest.java?= =?UTF-8?q?=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/ProductApiControllerTest.java | 524 ++++++++++++++++++ 1 file changed, 524 insertions(+) create mode 100644 src/test/java/gift/web/controller/api/ProductApiControllerTest.java diff --git a/src/test/java/gift/web/controller/api/ProductApiControllerTest.java b/src/test/java/gift/web/controller/api/ProductApiControllerTest.java new file mode 100644 index 000000000..62f6145f1 --- /dev/null +++ b/src/test/java/gift/web/controller/api/ProductApiControllerTest.java @@ -0,0 +1,524 @@ +package gift.web.controller.api; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.BDDMockito.given; +import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.delete; +import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.post; +import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.put; +import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath; +import static org.springframework.restdocs.payload.PayloadDocumentation.requestFields; +import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields; +import static org.springframework.restdocs.request.RequestDocumentation.parameterWithName; +import static org.springframework.restdocs.request.RequestDocumentation.queryParameters; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import com.fasterxml.jackson.databind.ObjectMapper; +import gift.authentication.token.JwtProvider; +import gift.config.RestDocsConfiguration; +import gift.converter.StringToUrlConverter; +import gift.domain.Category; +import gift.domain.Member; +import gift.domain.Member.Builder; +import gift.domain.Product; +import gift.domain.ProductOption; +import gift.domain.vo.Color; +import gift.domain.vo.Email; +import gift.mock.MockLoginMemberArgumentResolver; +import gift.service.OrderService; +import gift.service.ProductOptionService; +import gift.service.ProductService; +import gift.service.WishProductService; +import gift.web.dto.request.order.CreateOrderRequest; +import gift.web.dto.request.product.CreateProductRequest; +import gift.web.dto.request.product.UpdateProductRequest; +import gift.web.dto.request.productoption.CreateProductOptionRequest; +import gift.web.dto.request.productoption.UpdateProductOptionRequest; +import gift.web.dto.request.wishproduct.CreateWishProductRequest; +import gift.web.dto.response.category.ReadCategoryResponse; +import gift.web.dto.response.order.OrderResponse; +import gift.web.dto.response.product.CreateProductResponse; +import gift.web.dto.response.product.ReadAllProductsResponse; +import gift.web.dto.response.product.ReadProductResponse; +import gift.web.dto.response.product.UpdateProductResponse; +import gift.web.dto.response.productoption.CreateProductOptionResponse; +import gift.web.dto.response.productoption.ReadAllProductOptionsResponse; +import gift.web.dto.response.productoption.ReadProductOptionResponse; +import gift.web.dto.response.productoption.UpdateProductOptionResponse; +import gift.web.dto.response.wishproduct.CreateWishProductResponse; +import java.util.List; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.context.annotation.Import; +import org.springframework.data.domain.Pageable; +import org.springframework.data.web.PageableHandlerMethodArgumentResolver; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.restdocs.RestDocumentationContextProvider; +import org.springframework.restdocs.RestDocumentationExtension; +import org.springframework.restdocs.mockmvc.MockMvcRestDocumentation; +import org.springframework.restdocs.mockmvc.RestDocumentationResultHandler; +import org.springframework.restdocs.payload.JsonFieldType; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; + +@ActiveProfiles("test") +@SpringBootTest +@Import(RestDocsConfiguration.class) +@ExtendWith(RestDocumentationExtension.class) +class ProductApiControllerTest { + + private MockMvc mockMvc; + + @Autowired + protected RestDocumentationResultHandler restDocs; + + @Autowired + protected ObjectMapper objectMapper; + + @Autowired + private JwtProvider jwtProvider; + + @MockBean + private ProductService productService; + @MockBean + private WishProductService wishProductService; + @MockBean + private ProductOptionService productOptionService; + @MockBean + private OrderService orderService; + + private String accessToken; + + private static final String BASE_URL = "/api/products"; + + @BeforeEach + void setUp( + final RestDocumentationContextProvider provider + ) { + mockMvc = MockMvcBuilders + .standaloneSetup(new ProductApiController(productService, wishProductService, productOptionService, orderService)) + .setCustomArgumentResolvers(new MockLoginMemberArgumentResolver(), new PageableHandlerMethodArgumentResolver()) + .apply(MockMvcRestDocumentation.documentationConfiguration(provider)) + .alwaysDo(restDocs) + .build(); + + Member member = new Builder().id(1L).name("회원01").email(Email.from("member01@gmail.com")) + .build(); + accessToken = jwtProvider.generateToken(member).getValue(); + } + + @Test + @DisplayName("전체 상품 조회") + void readAllProducts() throws Exception { + given(productService.readAllProducts(any(Pageable.class))) + .willReturn(ReadAllProductsResponse.from( + List.of(new ReadProductResponse(1L, "상품01", 1000, "https://via.placeholder.com/150", + List.of(new ReadProductOptionResponse(1L, "상품 옵션 01", 100)), + new ReadCategoryResponse(1L, "카테고리01", "카테고리01 입니다", "https://via.placeholder.com/150", "#FFFFFF"))) + )); + + mockMvc + .perform( + get(BASE_URL) + .header(HttpHeaders.AUTHORIZATION, "Bearer " + accessToken) + ) + .andExpect(status().isOk()) + .andDo( + restDocs.document( + queryParameters( + parameterWithName("page").optional().description("페이지 번호"), + parameterWithName("size").optional().description("페이지 크기") + ), + responseFields( + fieldWithPath("products[].id").type(JsonFieldType.NUMBER).description("상품 ID"), + fieldWithPath("products[].name").type(JsonFieldType.STRING).description("상품명"), + fieldWithPath("products[].price").type(JsonFieldType.NUMBER).description("상품 가격"), + fieldWithPath("products[].imageUrl").type(JsonFieldType.STRING).description("상품 이미지 URL"), + fieldWithPath("products[].options").type(JsonFieldType.ARRAY).description("상품 옵션 목록"), + fieldWithPath("products[].options[].id").type(JsonFieldType.NUMBER).description("상품 옵션 ID"), + fieldWithPath("products[].options[].name").type(JsonFieldType.STRING).description("상품 옵션명"), + fieldWithPath("products[].options[].stock").type(JsonFieldType.NUMBER).description("상품 옵션 재고"), + fieldWithPath("products[].category.id").type(JsonFieldType.NUMBER).description("카테고리 ID"), + fieldWithPath("products[].category.name").type(JsonFieldType.STRING).description("카테고리명"), + fieldWithPath("products[].category.description").type(JsonFieldType.STRING).description("카테고리 설명"), + fieldWithPath("products[].category.imageUrl").type(JsonFieldType.STRING).description("카테고리 이미지 URL"), + fieldWithPath("products[].category.color").type(JsonFieldType.STRING).description("카테고리 색상") + ) + ) + ); + } + +// todo requestParam 만 다른 경로에 대해 테스트 코드 동작시키기 +// @Test +// @DisplayName("카테고리로 상품 조회") +// void readProductsByCategoryId() throws Exception { +// given(productService.readProductsByCategoryId(any(Long.class), any(Pageable.class))) +// .willReturn(ReadAllProductsResponse.from( +// List.of(new ReadProductResponse(1L, "상품01", 1000, "https://via.placeholder.com/150", +// List.of(new ReadProductOptionResponse(1L, "상품 옵션 01", 100)), +// new ReadCategoryResponse(1L, "카테고리01", "카테고리01 입니다", "https://via.placeholder.com/150", "#FFFFFF"))) +// )); +// +// mockMvc +// .perform( +// get(BASE_URL) +// .header(HttpHeaders.AUTHORIZATION, "Bearer " + accessToken) +// ) +// .andExpect(status().isOk()) +// .andDo( +// restDocs.document( +// queryParameters( +// parameterWithName("page").optional().description("페이지 번호"), +// parameterWithName("size").optional().description("페이지 크기"), +// parameterWithName("categoryId").description("카테고리 아이디") +// ), +// responseFields( +// fieldWithPath("products[].id").type(JsonFieldType.NUMBER).description("상품 ID"), +// fieldWithPath("products[].name").type(JsonFieldType.STRING).description("상품명"), +// fieldWithPath("products[].price").type(JsonFieldType.NUMBER).description("상품 가격"), +// fieldWithPath("products[].imageUrl").type(JsonFieldType.STRING).description("상품 이미지 URL"), +// fieldWithPath("products[].productOptions").type(JsonFieldType.ARRAY).description("상품 옵션 목록"), +// fieldWithPath("products[].productOptions[].id").type(JsonFieldType.NUMBER).description("상품 옵션 ID"), +// fieldWithPath("products[].productOptions[].name").type(JsonFieldType.STRING).description("상품 옵션명"), +// fieldWithPath("products[].productOptions[].stock").type(JsonFieldType.NUMBER).description("상품 옵션 재고"), +// fieldWithPath("products[].category.id").type(JsonFieldType.NUMBER).description("카테고리 ID"), +// fieldWithPath("products[].category.name").type(JsonFieldType.STRING).description("카테고리명"), +// fieldWithPath("products[].category.description").type(JsonFieldType.STRING).description("카테고리 설명"), +// fieldWithPath("products[].category.imageUrl").type(JsonFieldType.STRING).description("카테고리 이미지 URL"), +// fieldWithPath("products[].category.color").type(JsonFieldType.STRING).description("카테고리 색상") +// ) +// ) +// ); +// } + + @Test + @DisplayName("상품 생성") + void createProduct() throws Exception { + CreateProductRequest request = new CreateProductRequest("상품01", 1000, + "https://via.placeholder.com/150", 1L, + List.of(new CreateProductOptionRequest("상품 옵션 01", 100))); + String content = objectMapper.writeValueAsString(request); + + given(productService.createProduct(any(CreateProductRequest.class))) + .willReturn(new CreateProductResponse(1L, "상품01", 1000, "https://via.placeholder.com/150", List.of(new ProductOption.Builder().id(1L).productId(1L).name("상품 옵션 01").stock(100).build())) ); + + mockMvc + .perform( + post(BASE_URL) + .content(content) + .contentType(MediaType.APPLICATION_JSON) + .header(HttpHeaders.AUTHORIZATION, "Bearer " + accessToken) + ) + .andExpect(status().isCreated()) + .andDo( + restDocs.document( + requestFields( + fieldWithPath("name").type(JsonFieldType.STRING).description("상품명"), + fieldWithPath("price").type(JsonFieldType.NUMBER).description("상품 가격"), + fieldWithPath("imageUrl").type(JsonFieldType.STRING).description("상품 이미지 URL"), + fieldWithPath("categoryId").type(JsonFieldType.NUMBER).description("카테고리 ID"), + fieldWithPath("productOptions").type(JsonFieldType.ARRAY).description("상품 옵션 목록"), + fieldWithPath("productOptions[].name").type(JsonFieldType.STRING).description("상품 옵션명"), + fieldWithPath("productOptions[].stock").type(JsonFieldType.NUMBER).description("상품 옵션 재고") + ), + responseFields( + fieldWithPath("id").type(JsonFieldType.NUMBER).description("상품 ID"), + fieldWithPath("name").type(JsonFieldType.STRING).description("상품명"), + fieldWithPath("price").type(JsonFieldType.NUMBER).description("상품 가격"), + fieldWithPath("imageUrl").type(JsonFieldType.STRING).description("상품 이미지 URL"), + fieldWithPath("options").type(JsonFieldType.ARRAY).description("상품 옵션 목록"), + fieldWithPath("options[].id").type(JsonFieldType.NUMBER).description("상품 옵션 ID"), + fieldWithPath("options[].name").type(JsonFieldType.STRING).description("상품 옵션명"), + fieldWithPath("options[].stock").type(JsonFieldType.NUMBER).description("상품 옵션 재고"), + fieldWithPath("options[].productId").type(JsonFieldType.NUMBER).description("상품 옵션 상품 ID") + ) + ) + ); + } + + @Test + @DisplayName("단일 상품 조회") + void readProduct() throws Exception { + given(productService.readProductById(any(Long.class))) + .willReturn(new ReadProductResponse(1L, "상품01", 1000, "https://via.placeholder.com/150", + List.of(new ReadProductOptionResponse(1L, "상품 옵션 01", 100)), + new ReadCategoryResponse(1L, "카테고리01", "카테고리01 입니다", "https://via.placeholder.com/150", "#FFFFFF"))); + + mockMvc + .perform( + get(BASE_URL + "/{productId}", 1L) + .header(HttpHeaders.AUTHORIZATION, "Bearer " + accessToken) + ) + .andExpect(status().isOk()) + .andDo( + restDocs.document( + responseFields( + fieldWithPath("id").type(JsonFieldType.NUMBER).description("상품 ID"), + fieldWithPath("name").type(JsonFieldType.STRING).description("상품명"), + fieldWithPath("price").type(JsonFieldType.NUMBER).description("상품 가격"), + fieldWithPath("imageUrl").type(JsonFieldType.STRING).description("상품 이미지 URL"), + fieldWithPath("options").type(JsonFieldType.ARRAY).description("상품 옵션 목록"), + fieldWithPath("options[].id").type(JsonFieldType.NUMBER).description("상품 옵션 ID"), + fieldWithPath("options[].name").type(JsonFieldType.STRING).description("상품 옵션명"), + fieldWithPath("options[].stock").type(JsonFieldType.NUMBER).description("상품 옵션 재고"), + fieldWithPath("category.id").type(JsonFieldType.NUMBER).description("카테고리 ID"), + fieldWithPath("category.name").type(JsonFieldType.STRING).description("카테고리명"), + fieldWithPath("category.description").type(JsonFieldType.STRING).description("카테고리 설명"), + fieldWithPath("category.imageUrl").type(JsonFieldType.STRING).description("카테고리 이미지 URL"), + fieldWithPath("category.color").type(JsonFieldType.STRING).description("카테고리 색상") + ) + ) + ); + } + + @Test + @DisplayName("상품 수정") + void updateProduct() throws Exception { + UpdateProductRequest request = new UpdateProductRequest("상품01", 1000, "https://via.placeholder.com/150"); + + String content = objectMapper.writeValueAsString(request); + + given(productService.updateProduct(any(Long.class), any())) + .willReturn(UpdateProductResponse.from(new Product.Builder() + .id(1L) + .name("상품01") + .price(1000) + .productOptions( + List.of( + new ProductOption.Builder() + .id(1L) + .productId(1L) + .name("상품 옵션 01") + .stock(100) + .build() + )) + .imageUrl(StringToUrlConverter.convert("https://via.placeholder.com/150")) + .category(new Category.Builder() + .id(1L) + .name("카테고리01") + .color(Color.from("#FFFFFF")) + .description("카테고리01 입니다") + .imageUrl(StringToUrlConverter.convert("https://via.placeholder.com/150")) + .build()) + .build())); + + mockMvc + .perform( + put(BASE_URL + "/{productId}", 1L) + .content(content) + .contentType(MediaType.APPLICATION_JSON) + .header(HttpHeaders.AUTHORIZATION, "Bearer " + accessToken) + ) + .andExpect(status().isOk()) + .andDo( + restDocs.document( + requestFields( + fieldWithPath("name").type(JsonFieldType.STRING).description("상품명"), + fieldWithPath("price").type(JsonFieldType.NUMBER).description("상품 가격"), + fieldWithPath("imageUrl").type(JsonFieldType.STRING).description("상품 이미지 URL") + ), + responseFields( + fieldWithPath("id").type(JsonFieldType.NUMBER).description("상품 ID"), + fieldWithPath("name").type(JsonFieldType.STRING).description("상품명"), + fieldWithPath("price").type(JsonFieldType.NUMBER).description("상품 가격"), + fieldWithPath("imageUrl").type(JsonFieldType.STRING).description("상품 이미지 URL"), + fieldWithPath("category.id").type(JsonFieldType.NUMBER).description("카테고리 ID"), + fieldWithPath("category.name").type(JsonFieldType.STRING).description("카테고리명"), + fieldWithPath("category.description").type(JsonFieldType.STRING).description("카테고리 설명"), + fieldWithPath("category.imageUrl").type(JsonFieldType.STRING).description("카테고리 이미지 URL"), + fieldWithPath("category.color").type(JsonFieldType.STRING).description("카테고리 색상") + ) + ) + ); + } + + @Test + @DisplayName("상품 삭제") + void deleteProduct() throws Exception { + mockMvc + .perform( + delete(BASE_URL + "/{productId}", 1L) + .header(HttpHeaders.AUTHORIZATION, "Bearer " + accessToken) + ) + .andExpect(status().isNoContent()) + .andDo( + restDocs.document() + ); + } + + @Test + @DisplayName("위시 상품 추가") + void createWishProduct() throws Exception { + CreateWishProductRequest request = new CreateWishProductRequest(1L, 1); + String content = objectMapper.writeValueAsString(request); + + given(wishProductService.createWishProduct(any(Long.class), any())) + .willReturn(new CreateWishProductResponse(1L, 1)); + + mockMvc + .perform( + post(BASE_URL + "/wish") + .content(content) + .contentType(MediaType.APPLICATION_JSON) + .header(HttpHeaders.AUTHORIZATION, "Bearer " + accessToken) + ) + .andExpect(status().isOk()) + .andDo( + restDocs.document( + requestFields( + fieldWithPath("productId").type(JsonFieldType.NUMBER).description("상품 ID"), + fieldWithPath("quantity").type(JsonFieldType.NUMBER).description("수량") + ), + responseFields( + fieldWithPath("id").type(JsonFieldType.NUMBER).description("위시 상품 ID"), + fieldWithPath("quantity").type(JsonFieldType.NUMBER).description("수량") + ) + ) + ); + } + + @Test + @DisplayName("상품 옵션 조회") + void readOptions() throws Exception { + given(productOptionService.readAllOptions(any(Long.class))) + .willReturn(ReadAllProductOptionsResponse.from( + List.of(new ReadProductOptionResponse(1L, "상품 옵션 01", 100)) + )); + + mockMvc + .perform( + get(BASE_URL + "/{productId}/options", 1L) + .header(HttpHeaders.AUTHORIZATION, "Bearer " + accessToken) + ) + .andExpect(status().isOk()) + .andDo( + restDocs.document( + responseFields( + fieldWithPath("options[].id").type(JsonFieldType.NUMBER).description("상품 옵션 ID"), + fieldWithPath("options[].name").type(JsonFieldType.STRING).description("상품 옵션명"), + fieldWithPath("options[].stock").type(JsonFieldType.NUMBER).description("상품 옵션 재고") + ) + ) + ); + } + + @Test + @DisplayName("상품 옵션 생성") + void createOption() throws Exception { + CreateProductOptionRequest request = new CreateProductOptionRequest( + "상품 옵션 01", 100); + String content = objectMapper.writeValueAsString(request); + + given(productOptionService.createOption(any(Long.class), any())) + .willReturn(new CreateProductOptionResponse(1L, "상품 옵션 01", 100)); + + mockMvc + .perform( + post(BASE_URL + "/{productId}/options", 1L) + .content(content) + .contentType(MediaType.APPLICATION_JSON) + .header(HttpHeaders.AUTHORIZATION, "Bearer " + accessToken) + ) + .andExpect(status().isCreated()) + .andDo( + restDocs.document( + responseFields( + fieldWithPath("id").type(JsonFieldType.NUMBER).description("상품 옵션 ID"), + fieldWithPath("name").type(JsonFieldType.STRING).description("상품 옵션명"), + fieldWithPath("stock").type(JsonFieldType.NUMBER).description("상품 옵션 재고") + ) + ) + ); + } + + @Test + @DisplayName("상품 주문") + void orderProduct() throws Exception { + + CreateOrderRequest request = new CreateOrderRequest(1L, 1, "message"); + String content = objectMapper.writeValueAsString(request); + + given(orderService.createOrder(any(String.class), any(Long.class), any())) + .willReturn(new OrderResponse(1L, 1L, 10, 1, "상품01", "message")); + + mockMvc + .perform( + post(BASE_URL + "/{productId}/order", 1L) + .header(HttpHeaders.AUTHORIZATION, "Bearer " + accessToken) + .content(content) + .contentType(MediaType.APPLICATION_JSON) + ) + .andExpect(status().isOk()) + .andDo( + restDocs.document( + requestFields( + fieldWithPath("optionId").type(JsonFieldType.NUMBER).description("상품 옵션 ID"), + fieldWithPath("quantity").type(JsonFieldType.NUMBER).description("수량"), + fieldWithPath("message").type(JsonFieldType.STRING).description("메시지") + ), + responseFields( + fieldWithPath("productId").type(JsonFieldType.NUMBER).description("상품 ID"), + fieldWithPath("optionId").type(JsonFieldType.NUMBER).description("상품 옵션 ID"), + fieldWithPath("optionStock").type(JsonFieldType.NUMBER).description("상품 옵션 재고"), + fieldWithPath("quantity").type(JsonFieldType.NUMBER).description("수량"), + fieldWithPath("productName").type(JsonFieldType.STRING).description("상품명"), + fieldWithPath("message").type(JsonFieldType.STRING).description("메시지") + ) + ) + ); + } + + @Test + @DisplayName("상품 옵션 수정") + void updateOption() throws Exception { + UpdateProductOptionRequest request = new UpdateProductOptionRequest( + "상품 옵션 01", 100); + String content = objectMapper.writeValueAsString(request); + + given(productOptionService.updateOption(any(Long.class), any(Long.class), any())) + .willReturn(new UpdateProductOptionResponse(1L, "상품 옵션 01", 100)); + + mockMvc + .perform( + put(BASE_URL + "/{productId}/options/{optionId}", 1L, 1L) + .header(HttpHeaders.AUTHORIZATION, "Bearer " + accessToken) + .content(content) + .contentType(MediaType.APPLICATION_JSON) + ) + .andExpect(status().isOk()) + .andDo( + restDocs.document( + requestFields( + fieldWithPath("name").type(JsonFieldType.STRING).description("상품 옵션명"), + fieldWithPath("stock").type(JsonFieldType.NUMBER).description("상품 옵션 재고") + ), + responseFields( + fieldWithPath("id").type(JsonFieldType.NUMBER).description("상품 옵션 ID"), + fieldWithPath("name").type(JsonFieldType.STRING).description("상품 옵션명"), + fieldWithPath("stock").type(JsonFieldType.NUMBER).description("상품 옵션 재고") + ) + ) + ); + } + + @Test + @DisplayName("상품 옵션 삭제") + void deleteOption() throws Exception { + mockMvc + .perform( + delete(BASE_URL + "/{productId}/options/{optionId}", 1L, 1L) + .header(HttpHeaders.AUTHORIZATION, "Bearer " + accessToken) + ) + .andExpect(status().isNoContent()) + .andDo( + restDocs.document() + ); + } +} \ No newline at end of file From c8fb6e56634bff946bc5d0ac5cec44828cb55c70 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=82=98=EC=A0=9C=EB=B2=95?= Date: Mon, 29 Jul 2024 22:20:35 +0900 Subject: [PATCH 080/138] =?UTF-8?q?docs:=20product.adoc=20=EC=83=81?= =?UTF-8?q?=ED=92=88=20API=20=EB=AA=85=EC=84=B8=EC=84=9C=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/docs/asciidoc/product.adoc | 35 ++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 src/docs/asciidoc/product.adoc diff --git a/src/docs/asciidoc/product.adoc b/src/docs/asciidoc/product.adoc new file mode 100644 index 000000000..91caa46ef --- /dev/null +++ b/src/docs/asciidoc/product.adoc @@ -0,0 +1,35 @@ +== 상품 + +=== 전체 상품 조회 +operation::product-api-controller-test/read-all-products[snippets='http-request,http-response'] + +=== 단일 상품 조회 +operation::product-api-controller-test/read-product[snippets='http-request,http-response'] + +=== 상품 생성 +operation::product-api-controller-test/create-product[snippets='http-request,http-response'] + +=== 상품 수정 +operation::product-api-controller-test/update-product[snippets='http-request,http-response'] + +=== 상품 삭제 +operation::product-api-controller-test/delete-product[snippets='http-request,http-response'] + +=== 상품 주문 +operation::product-api-controller-test/order-product[snippets='http-request,http-response'] + +=== 위시 상품 추가 +operation::product-api-controller-test/create-wish-product[snippets='http-request,http-response'] + +=== 상품 옵션 생성 +operation::product-api-controller-test/create-option[snippets='http-request,http-response'] + +=== 상품 옵션 조회 +operation::product-api-controller-test/read-options[snippets='http-request,http-response'] + +=== 상품 옵션 수정 +operation::product-api-controller-test/update-option[snippets='http-request,http-response'] + +=== 상품 옵션 삭제 +operation::product-api-controller-test/delete-option[snippets='http-request,http-response'] + From a0a3b470214d1f3e716e720f8e543635d1ebc95f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=82=98=EC=A0=9C=EB=B2=95?= Date: Mon, 29 Jul 2024 22:20:48 +0900 Subject: [PATCH 081/138] =?UTF-8?q?docs:=20docs.adoc=20API=20=EB=AA=85?= =?UTF-8?q?=EC=84=B8=EC=84=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/docs/asciidoc/docs.adoc | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 src/docs/asciidoc/docs.adoc diff --git a/src/docs/asciidoc/docs.adoc b/src/docs/asciidoc/docs.adoc new file mode 100644 index 000000000..c3475228b --- /dev/null +++ b/src/docs/asciidoc/docs.adoc @@ -0,0 +1,8 @@ += 카카오 테크 캠퍼스 STEP 2 - REST Docs +:doctype: book +:source-highlighter: highlightjs +:toc: left +:toclevels: 3 +:sectlinks: + +include::product.adoc[] \ No newline at end of file From 18f07e4bf47f9a6e6d90cda5ba5fcf7d61577b00 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=82=98=EC=A0=9C=EB=B2=95?= Date: Mon, 29 Jul 2024 22:43:37 +0900 Subject: [PATCH 082/138] =?UTF-8?q?test:=20CategoryApiControllerTest.java?= =?UTF-8?q?=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/CategoryApiControllerTest.java | 242 ++++++++++++++++++ 1 file changed, 242 insertions(+) create mode 100644 src/test/java/gift/web/controller/api/CategoryApiControllerTest.java diff --git a/src/test/java/gift/web/controller/api/CategoryApiControllerTest.java b/src/test/java/gift/web/controller/api/CategoryApiControllerTest.java new file mode 100644 index 000000000..c904edd50 --- /dev/null +++ b/src/test/java/gift/web/controller/api/CategoryApiControllerTest.java @@ -0,0 +1,242 @@ +package gift.web.controller.api; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.BDDMockito.given; +import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.post; +import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.put; +import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath; +import static org.springframework.restdocs.payload.PayloadDocumentation.requestFields; +import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields; +import static org.springframework.restdocs.request.RequestDocumentation.parameterWithName; +import static org.springframework.restdocs.request.RequestDocumentation.queryParameters; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import com.fasterxml.jackson.databind.ObjectMapper; +import gift.authentication.token.JwtProvider; +import gift.config.RestDocsConfiguration; +import gift.domain.Member; +import gift.domain.Member.Builder; +import gift.domain.vo.Email; +import gift.mock.MockLoginMemberArgumentResolver; +import gift.service.CategoryService; +import gift.service.OrderService; +import gift.service.ProductOptionService; +import gift.service.ProductService; +import gift.service.WishProductService; +import gift.web.dto.request.category.CreateCategoryRequest; +import gift.web.dto.request.category.UpdateCategoryRequest; +import gift.web.dto.response.category.CreateCategoryResponse; +import gift.web.dto.response.category.ReadAllCategoriesResponse; +import gift.web.dto.response.category.ReadCategoryResponse; +import gift.web.dto.response.category.UpdateCategoryResponse; +import java.util.List; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.context.annotation.Import; +import org.springframework.data.web.PageableHandlerMethodArgumentResolver; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.restdocs.RestDocumentationContextProvider; +import org.springframework.restdocs.RestDocumentationExtension; +import org.springframework.restdocs.mockmvc.MockMvcRestDocumentation; +import org.springframework.restdocs.mockmvc.RestDocumentationResultHandler; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; + +@ActiveProfiles("test") +@SpringBootTest +@Import(RestDocsConfiguration.class) +@ExtendWith(RestDocumentationExtension.class) +class CategoryApiControllerTest { + + private MockMvc mockMvc; + + @Autowired + protected RestDocumentationResultHandler restDocs; + + @Autowired + protected ObjectMapper objectMapper; + + @Autowired + private JwtProvider jwtProvider; + + @MockBean + private CategoryService categoryService; + + private String accessToken; + + private static final String BASE_URL = "/api/categories"; + + @BeforeEach + void setUp( + final RestDocumentationContextProvider provider + ) { + mockMvc = MockMvcBuilders + .standaloneSetup(new CategoryApiController(categoryService)) + .setCustomArgumentResolvers(new PageableHandlerMethodArgumentResolver()) + .apply(MockMvcRestDocumentation.documentationConfiguration(provider)) + .alwaysDo(restDocs) + .build(); + + Member member = new Builder().id(1L).name("회원01").email(Email.from("member01@gmail.com")) + .build(); + accessToken = jwtProvider.generateToken(member).getValue(); + } + + + @Test + @DisplayName("카테고리 생성") + void createCategory() throws Exception { + CreateCategoryRequest request = new CreateCategoryRequest("카테고리01", + "카테고리01 설명", "https://via.placeholder.com/150", "#FFFFFF"); + + String content = objectMapper.writeValueAsString(request); + + given(categoryService.createCategory(any(CreateCategoryRequest.class))) + .willReturn(new CreateCategoryResponse(1L, "카테고리01", "카테고리01 설명", "https://via.placeholder.com/150", "#FFFFFF")); + + mockMvc + .perform( + post(BASE_URL) + .header(HttpHeaders.AUTHORIZATION, "Bearer " + accessToken) + .content(content) + .contentType(MediaType.APPLICATION_JSON) + ) + .andExpect(status().isCreated()) + .andDo( + restDocs.document( + requestFields( + fieldWithPath("name").description("카테고리명"), + fieldWithPath("description").description("카테고리 설명"), + fieldWithPath("imageUrl").description("이미지 URL"), + fieldWithPath("color").description("색상 코드") + ), + responseFields( + fieldWithPath("id").description("카테고리 ID"), + fieldWithPath("name").description("카테고리명"), + fieldWithPath("description").description("카테고리 설명"), + fieldWithPath("imageUrl").description("이미지 URL"), + fieldWithPath("color").description("색상 코드") + ) + ) + ); + } + + @Test + @DisplayName("모든 카테고리 조회") + void readAllCategories() throws Exception { + given(categoryService.readAllCategories(any())) + .willReturn(new ReadAllCategoriesResponse(List.of( + new ReadCategoryResponse(1L, "카테고리01", "카테고리01 설명", "https://via.placeholder.com/150", "#FFFFFF"), + new ReadCategoryResponse(2L, "카테고리02", "카테고리02 설명", "https://via.placeholder.com/150", "#FFFFFF") + ))); + + mockMvc + .perform( + get(BASE_URL) + .header(HttpHeaders.AUTHORIZATION, "Bearer " + accessToken) + ) + .andExpect(status().isOk()) + .andDo( + restDocs.document( + queryParameters( + parameterWithName("page").optional().description("페이지 번호"), + parameterWithName("size").optional().description("페이지 크기"), + parameterWithName("sort").optional().description("정렬 조건") + ), + responseFields( + fieldWithPath("categories[].id").description("카테고리 ID"), + fieldWithPath("categories[].name").description("카테고리명"), + fieldWithPath("categories[].description").description("카테고리 설명"), + fieldWithPath("categories[].imageUrl").description("이미지 URL"), + fieldWithPath("categories[].color").description("색상 코드") + ) + ) + ); + } + + @Test + @DisplayName("단일 카테고리 조회") + void readCategory() throws Exception { + given(categoryService.readCategory(any(Long.class))) + .willReturn(new ReadCategoryResponse(1L, "카테고리01", "카테고리01 설명", "https://via.placeholder.com/150", "#FFFFFF")); + + mockMvc + .perform( + get(BASE_URL + "/{id}", 1L) + .header(HttpHeaders.AUTHORIZATION, "Bearer " + accessToken) + ) + .andExpect(status().isOk()) + .andDo( + restDocs.document( + responseFields( + fieldWithPath("id").description("카테고리 ID"), + fieldWithPath("name").description("카테고리명"), + fieldWithPath("description").description("카테고리 설명"), + fieldWithPath("imageUrl").description("이미지 URL"), + fieldWithPath("color").description("색상 코드") + ) + ) + ); + } + + @Test + @DisplayName("카테고리 수정") + void updateCategory() throws Exception { + UpdateCategoryRequest request = new UpdateCategoryRequest("카테고리01", "카테고리01 설명", + "https://via.placeholder.com/150", "#FFFFFF"); + + String content = objectMapper.writeValueAsString(request); + + given(categoryService.updateCategory(any(Long.class), any(UpdateCategoryRequest.class))) + .willReturn(new UpdateCategoryResponse(1L, "카테고리01", "카테고리01 설명", "https://via.placeholder.com/150", "#FFFFFF")); + + mockMvc + .perform( + put(BASE_URL + "/{id}", 1L) + .header(HttpHeaders.AUTHORIZATION, "Bearer " + accessToken) + .content(content) + .contentType(MediaType.APPLICATION_JSON) + ) + .andExpect(status().isOk()) + .andDo( + restDocs.document( + requestFields( + fieldWithPath("name").description("카테고리명"), + fieldWithPath("description").description("카테고리 설명"), + fieldWithPath("imageUrl").description("이미지 URL"), + fieldWithPath("color").description("색상 코드") + ), + responseFields( + fieldWithPath("id").description("카테고리 ID"), + fieldWithPath("name").description("카테고리명"), + fieldWithPath("description").description("카테고리 설명"), + fieldWithPath("imageUrl").description("이미지 URL"), + fieldWithPath("color").description("색상 코드") + ) + ) + ); + } + + @Test + void deleteCategory() throws Exception { + mockMvc + .perform( + delete(BASE_URL + "/{id}", 1L) + .header(HttpHeaders.AUTHORIZATION, "Bearer " + accessToken) + ) + .andExpect(status().isNoContent()) + .andDo( + restDocs.document() + ); + } +} \ No newline at end of file From 9ce23f21a8fedbd020840b928d104f1c2cbf2204 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=82=98=EC=A0=9C=EB=B2=95?= Date: Mon, 29 Jul 2024 22:43:49 +0900 Subject: [PATCH 083/138] =?UTF-8?q?docs:=20category.adoc=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/docs/asciidoc/category.adoc | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 src/docs/asciidoc/category.adoc diff --git a/src/docs/asciidoc/category.adoc b/src/docs/asciidoc/category.adoc new file mode 100644 index 000000000..2818b185a --- /dev/null +++ b/src/docs/asciidoc/category.adoc @@ -0,0 +1,19 @@ +== 카테고리 API + +=== 카테고리 생성 +operation::category-api-controller-test/create-category[snippets='http-request,http-response'] + +=== 카테고리 수정 +operation::category-api-controller-test/update-category[snippets='http-request,http-response'] + +=== 카테고리 삭제 +operation::category-api-controller-test/delete-category[snippets='http-request,http-response'] + +=== 단일 카테고리 조회 +operation::category-api-controller-test/read-category[snippets='http-request,http-response'] + +=== 전체 카테고리 조회 +operation::category-api-controller-test/read-all-categories[snippets='http-request,http-response'] + + + From b8e0e8a1471840c45561e24a1f055f976c65e552 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=82=98=EC=A0=9C=EB=B2=95?= Date: Mon, 29 Jul 2024 22:43:56 +0900 Subject: [PATCH 084/138] =?UTF-8?q?docs:=20product.adoc=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/docs/asciidoc/product.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/docs/asciidoc/product.adoc b/src/docs/asciidoc/product.adoc index 91caa46ef..213535572 100644 --- a/src/docs/asciidoc/product.adoc +++ b/src/docs/asciidoc/product.adoc @@ -1,4 +1,4 @@ -== 상품 +== 상품 API === 전체 상품 조회 operation::product-api-controller-test/read-all-products[snippets='http-request,http-response'] From 2b14ef0e228c48bf999d374720b0518d49bac60e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=82=98=EC=A0=9C=EB=B2=95?= Date: Mon, 29 Jul 2024 22:44:05 +0900 Subject: [PATCH 085/138] =?UTF-8?q?docs:=20docs.adoc=20=EC=B9=B4=ED=85=8C?= =?UTF-8?q?=EA=B3=A0=EB=A6=AC=20API=20=EB=B0=98=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/docs/asciidoc/docs.adoc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/docs/asciidoc/docs.adoc b/src/docs/asciidoc/docs.adoc index c3475228b..fd74fdae9 100644 --- a/src/docs/asciidoc/docs.adoc +++ b/src/docs/asciidoc/docs.adoc @@ -5,4 +5,5 @@ :toclevels: 3 :sectlinks: -include::product.adoc[] \ No newline at end of file +include::product.adoc[] +include::category.adoc[] \ No newline at end of file From dd6de941c7a5a8e71a8fde842ec7863ae2a9afea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=82=98=EC=A0=9C=EB=B2=95?= Date: Mon, 29 Jul 2024 23:22:43 +0900 Subject: [PATCH 086/138] =?UTF-8?q?test:=20MemberApiControllerTest.java=20?= =?UTF-8?q?E2E=20=ED=85=8C=EC=8A=A4=ED=8A=B8=EC=97=90=EC=84=9C=20=EC=BB=A8?= =?UTF-8?q?=ED=8A=B8=EB=A1=A4=EB=9F=AC=20=EB=8B=A8=EC=9C=84=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=EB=A1=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/MemberApiControllerTest.java | 380 ++++++++++-------- 1 file changed, 211 insertions(+), 169 deletions(-) diff --git a/src/test/java/gift/web/controller/api/MemberApiControllerTest.java b/src/test/java/gift/web/controller/api/MemberApiControllerTest.java index 85204f1ea..e8cf83d98 100644 --- a/src/test/java/gift/web/controller/api/MemberApiControllerTest.java +++ b/src/test/java/gift/web/controller/api/MemberApiControllerTest.java @@ -1,20 +1,27 @@ package gift.web.controller.api; -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertAll; -import static org.junit.jupiter.api.Assertions.assertIterableEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import gift.authentication.token.Token; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.BDDMockito.given; +import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath; +import static org.springframework.restdocs.payload.PayloadDocumentation.requestFields; +import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields; +import static org.springframework.restdocs.request.RequestDocumentation.parameterWithName; +import static org.springframework.restdocs.request.RequestDocumentation.queryParameters; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import com.fasterxml.jackson.databind.ObjectMapper; +import gift.authentication.token.JwtProvider; +import gift.config.RestDocsConfiguration; import gift.domain.Member; -import gift.repository.MemberRepository; +import gift.domain.Member.Builder; +import gift.domain.vo.Email; +import gift.mock.MockLoginMemberArgumentResolver; import gift.service.MemberService; import gift.service.WishProductService; -import gift.utils.CategoryDummyDataProvider; -import gift.utils.DatabaseCleanup; -import gift.utils.MemberDummyDataProvider; -import gift.utils.ProductDummyDataProvider; -import gift.utils.WishProductDummyDataProvider; import gift.web.dto.request.LoginRequest; import gift.web.dto.request.member.CreateMemberRequest; import gift.web.dto.request.wishproduct.UpdateWishProductRequest; @@ -22,205 +29,240 @@ import gift.web.dto.response.member.CreateMemberResponse; import gift.web.dto.response.member.ReadMemberResponse; import gift.web.dto.response.wishproduct.ReadAllWishProductsResponse; +import gift.web.dto.response.wishproduct.ReadWishProductResponse; import gift.web.dto.response.wishproduct.UpdateWishProductResponse; -import org.junit.jupiter.api.AfterEach; +import io.swagger.v3.core.util.Json; +import java.util.List; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; -import org.springframework.boot.test.web.client.TestRestTemplate; -import org.springframework.boot.test.web.server.LocalServerPort; -import org.springframework.data.domain.PageRequest; -import org.springframework.http.HttpEntity; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.context.annotation.Import; +import org.springframework.data.web.PageableHandlerMethodArgumentResolver; import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpMethod; -import org.springframework.http.ResponseEntity; +import org.springframework.http.MediaType; +import org.springframework.restdocs.RestDocumentationContextProvider; +import org.springframework.restdocs.RestDocumentationExtension; +import org.springframework.restdocs.mockmvc.MockMvcRestDocumentation; +import org.springframework.restdocs.mockmvc.RestDocumentationResultHandler; +import org.springframework.restdocs.payload.JsonFieldType; import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; @ActiveProfiles("test") -@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT) +@SpringBootTest +@Import(RestDocsConfiguration.class) +@ExtendWith(RestDocumentationExtension.class) class MemberApiControllerTest { - @LocalServerPort - private int port; - - @Autowired - private TestRestTemplate restTemplate; - - @Autowired - private MemberDummyDataProvider memberDummyDataProvider; - - @Autowired - private ProductDummyDataProvider productDummyDataProvider; + private MockMvc mockMvc; @Autowired - private WishProductDummyDataProvider wishProductDummyDataProvider; + protected RestDocumentationResultHandler restDocs; @Autowired - private CategoryDummyDataProvider categoryDummyDataProvider; + protected ObjectMapper objectMapper; @Autowired - private DatabaseCleanup databaseCleanup; + private JwtProvider jwtProvider; - @Autowired + @MockBean private MemberService memberService; - @Autowired - private MemberRepository memberRepository; - - @Autowired + @MockBean private WishProductService wishProductService; - //테스트용 회원 - private Member member; - private Token token; - - @BeforeEach - void setUp() { - insertDummyData(100); - member = getTestMember(1L); - token = getAccessToken(); - } - - private Member getTestMember(Long id) { - return memberRepository.findById(id) - .orElseThrow(() -> new IllegalArgumentException("ID: " + id +"인 회원이 존재하지 않습니다.")); - } - - private Token getAccessToken() { - LoginRequest loginRequest = new LoginRequest( - member.getEmail().getValue(), - member.getPassword().getValue() - ); - LoginResponse loginResponse = memberService.login(loginRequest); - return Token.from(loginResponse.getAccessToken()); - } + private String accessToken; - private void insertDummyData(int quantity) { - if (quantity < 2) { - throw new IllegalArgumentException("quantity는 2 이상이어야 합니다."); - } - memberDummyDataProvider.run(quantity); - productDummyDataProvider.run(quantity); - wishProductDummyDataProvider.run(quantity); - categoryDummyDataProvider.run(quantity); - } + private static final String BASE_URL = "/api/members"; - @AfterEach - void tearDown() { - databaseCleanup.execute(); + @BeforeEach + void setUp( + final RestDocumentationContextProvider provider + ) { + mockMvc = MockMvcBuilders + .standaloneSetup(new MemberApiController(memberService, wishProductService)) + .setCustomArgumentResolvers(new MockLoginMemberArgumentResolver(), new PageableHandlerMethodArgumentResolver()) + .apply(MockMvcRestDocumentation.documentationConfiguration(provider)) + .alwaysDo(restDocs) + .build(); + + Member member = new Builder().id(1L).name("회원01").email(Email.from("member01@gmail.com")) + .build(); + accessToken = jwtProvider.generateToken(member).getValue(); } @Test - @DisplayName("회원 생성 요청에 대한 정상 응답") - void createMember() { - //given - CreateMemberRequest request = new CreateMemberRequest("test@gmail.com", "test1234", "test"); - String url = "http://localhost:" + port + "/api/members/register"; - - //when - ResponseEntity response = restTemplate.postForEntity(url, request, CreateMemberResponse.class); - - //then - Long newMemberId = response.getBody().getId(); - ReadMemberResponse findMember = memberService.readMember(newMemberId); - - assertAll( - () -> assertTrue(response.getStatusCode().is2xxSuccessful()), - () -> assertThat(newMemberId).isEqualTo(findMember.getId()), - () -> assertThat(request.getEmail()).isEqualTo(findMember.getEmail()), - () -> assertThat(request.getName()).isEqualTo(findMember.getName()), - () -> assertThat(request.getPassword()).isEqualTo(findMember.getPassword()) - ); + @DisplayName("회원 가입") + void createMember() throws Exception { + CreateMemberRequest request = new CreateMemberRequest("member01@gmail.com", "password01", + "member01"); + + String content = objectMapper.writeValueAsString(request); + + given(memberService.createMember(any())) + .willReturn(new CreateMemberResponse(1L, "member01@gmail.com", "member01")); + + mockMvc + .perform( + post(BASE_URL + "/register") + .header(HttpHeaders.AUTHORIZATION, "Bearer " + accessToken) + .contentType(MediaType.APPLICATION_JSON) + .content(content) + ) + .andExpect(status().isCreated()) + .andDo( + restDocs.document( + requestFields( + fieldWithPath("email").type(JsonFieldType.STRING).description("이메일"), + fieldWithPath("password").type(JsonFieldType.STRING).description("비밀번호"), + fieldWithPath("name").type(JsonFieldType.STRING).description("이름") + ), + responseFields( + fieldWithPath("id").type(JsonFieldType.NUMBER).description("회원 식별자"), + fieldWithPath("email").type(JsonFieldType.STRING).description("이메일"), + fieldWithPath("name").type(JsonFieldType.STRING).description("이름") + ) + ) + ); } @Test - @DisplayName("로그인 요청에 대한 정상 응답") - void login() { - //given - String url = "http://localhost:" + port + "/api/members/login"; - String email = member.getEmail().getValue(); - String password = member.getPassword().getValue(); - LoginRequest request = new LoginRequest(email, password); - - //when - ResponseEntity response = restTemplate.postForEntity(url, request, LoginResponse.class); - - //then - assertAll( - () -> assertTrue(response.getStatusCode().is2xxSuccessful()), - () -> assertThat(response.getBody().getAccessToken()).isNotNull() - ); + @DisplayName("로그인") + void login() throws Exception { + LoginRequest request = new LoginRequest("member01@gmail.com", "password01"); + String content = objectMapper.writeValueAsString(request); + + given(memberService.login(any())) + .willReturn(new LoginResponse(accessToken)); + + mockMvc + .perform( + post(BASE_URL + "/login") + .contentType(MediaType.APPLICATION_JSON) + .content(content) + ) + .andExpect(status().isOk()) + .andDo( + restDocs.document( + requestFields( + fieldWithPath("email").type(JsonFieldType.STRING).description("이메일"), + fieldWithPath("password").type(JsonFieldType.STRING).description("비밀번호") + ), + responseFields( + fieldWithPath("accessToken").type(JsonFieldType.STRING).description("Bearer Token") + ) + ) + ); } @Test - @DisplayName("위시 리스트 조회 요청에 대한 정상 응답") - void readWishProduct() { - //given - String url = "http://localhost:" + port + "/api/members/wishlist"; - HttpHeaders httpHeaders = getHttpHeaders(); - HttpEntity httpEntity = new HttpEntity(httpHeaders); - - PageRequest defaultPageRequest = PageRequest.of(0, 10); - ReadAllWishProductsResponse expectedWishProducts = wishProductService.readAllWishProducts(member.getId(), - defaultPageRequest); - - //when - ResponseEntity response = restTemplate.exchange(url, - HttpMethod.GET, httpEntity, ReadAllWishProductsResponse.class); - - //then - assertAll( - () -> assertTrue(response.getStatusCode().is2xxSuccessful()), - () -> assertIterableEquals(response.getBody().getWishlist(), expectedWishProducts.getWishlist()) - ); + @DisplayName("회원 조회") + void readMember() throws Exception { + given(memberService.readMember(any(Long.class))) + .willReturn(new ReadMemberResponse(1L, "member01@gmail.com", "password01", "member01")); + + mockMvc + .perform( + get(BASE_URL + "/{memberId}", 1L) + .header(HttpHeaders.AUTHORIZATION, "Bearer " + accessToken) + ) + .andExpect(status().isOk()) + .andDo( + restDocs.document( + responseFields( + fieldWithPath("id").type(JsonFieldType.NUMBER).description("회원 식별자"), + fieldWithPath("email").type(JsonFieldType.STRING).description("이메일"), + fieldWithPath("password").type(JsonFieldType.STRING).description("비밀번호"), + fieldWithPath("name").type(JsonFieldType.STRING).description("이름") + ) + ) + ); } @Test - @DisplayName("위시 리스트 상품 수정 요청에 대한 정상 응답") - void updateWishProduct() { - //given - Long wishProductId = 1L; - String url = "http://localhost:" + port + "/api/members/wishlist/" + wishProductId; - HttpHeaders httpHeaders = getHttpHeaders(); - - UpdateWishProductRequest request = new UpdateWishProductRequest(3); - - HttpEntity httpEntity = new HttpEntity(request, httpHeaders); - - //when - ResponseEntity response = restTemplate.exchange(url, - HttpMethod.PUT, httpEntity, UpdateWishProductResponse.class); - - //then - assertAll( - () -> assertTrue(response.getStatusCode().is2xxSuccessful()), - () -> assertThat(response.getBody().getQuantity()).isEqualTo(request.getQuantity()) + @DisplayName("위시 상품 조회") + void readWishProduct() throws Exception { + ReadAllWishProductsResponse response = new ReadAllWishProductsResponse( + List.of(new ReadWishProductResponse(1L, 1L, "product01", 1000, 5, "https://via.placeholder.com/150")) ); + + given(wishProductService.readAllWishProducts(any(Long.class), any())) + .willReturn(response); + + mockMvc + .perform( + get(BASE_URL + "/wishlist") + .header(HttpHeaders.AUTHORIZATION, "Bearer " + accessToken) + ) + .andExpect(status().isOk()) + .andDo( + restDocs.document( + queryParameters( + parameterWithName("page").optional().description("페이지 번호"), + parameterWithName("size").optional().description("페이지 크기") + ), + responseFields( + fieldWithPath("wishlist[].id").type(JsonFieldType.NUMBER).description("위시 상품 식별자"), + fieldWithPath("wishlist[].productId").type(JsonFieldType.NUMBER).description("상품 식별자"), + fieldWithPath("wishlist[].name").type(JsonFieldType.STRING).description("상품명"), + fieldWithPath("wishlist[].price").type(JsonFieldType.NUMBER).description("가격"), + fieldWithPath("wishlist[].quantity").type(JsonFieldType.NUMBER).description("재고 수량"), + fieldWithPath("wishlist[].imageUrl").type(JsonFieldType.STRING).description("이미지 URL") + ) + ) + ); } @Test - @DisplayName("위시 리스트 상품 삭제 요청에 대한 정상 응답") - void deleteWishProduct() { - //given - Long wishProductId = 2L; - String url = "http://localhost:" + port + "/api/members/wishlist/" + wishProductId; - HttpHeaders httpHeaders = getHttpHeaders(); - HttpEntity httpEntity = new HttpEntity(httpHeaders); - - //when - ResponseEntity response = restTemplate.exchange(url, HttpMethod.DELETE, httpEntity, - Void.class); - - //then - assertTrue(response.getStatusCode().is2xxSuccessful()); + @DisplayName("위시 상품 수정") + void updateWishProduct() throws Exception { + UpdateWishProductRequest request = new UpdateWishProductRequest(5); + String content = Json.mapper().writeValueAsString(request); + + given(wishProductService.updateWishProduct(any(Long.class), any())) + .willReturn(new UpdateWishProductResponse(1L, 1L, "product01", 1000, 5, "https://via.placeholder.com/150")); + + mockMvc + .perform( + put(BASE_URL + "/wishlist/{wishProductId}", 1L) + .header(HttpHeaders.AUTHORIZATION, "Bearer " + accessToken) + .contentType(MediaType.APPLICATION_JSON) + .content(content) + ) + .andExpect(status().isOk()) + .andDo( + restDocs.document( + requestFields( + fieldWithPath("quantity").type(JsonFieldType.NUMBER).description("수량") + ), + responseFields( + fieldWithPath("id").type(JsonFieldType.NUMBER).description("위시 상품 식별자"), + fieldWithPath("productId").type(JsonFieldType.NUMBER).description("상품 식별자"), + fieldWithPath("name").type(JsonFieldType.STRING).description("상품명"), + fieldWithPath("price").type(JsonFieldType.NUMBER).description("가격"), + fieldWithPath("quantity").type(JsonFieldType.NUMBER).description("재고 수량"), + fieldWithPath("imageUrl").type(JsonFieldType.STRING).description("이미지 URL") + ) + ) + ); } - private HttpHeaders getHttpHeaders() { - HttpHeaders httpHeaders = new HttpHeaders(); - httpHeaders.setBearerAuth(token.getValue()); - return httpHeaders; + @Test + @DisplayName("위시 상품 삭제") + void deleteWishProduct() throws Exception { + mockMvc + .perform( + delete(BASE_URL + "/wishlist/{wishProductId}", 1L) + .header(HttpHeaders.AUTHORIZATION, "Bearer " + accessToken) + ) + .andExpect(status().isNoContent()) + .andDo( + restDocs.document() + ); } } \ No newline at end of file From 2c3a5e06d6742a14de8357d1dfba9b0d8bdce7a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=82=98=EC=A0=9C=EB=B2=95?= Date: Mon, 29 Jul 2024 23:22:56 +0900 Subject: [PATCH 087/138] =?UTF-8?q?docs:=20member.adoc=20=ED=9A=8C?= =?UTF-8?q?=EC=9B=90=20API=20=EB=AA=85=EC=84=B8=EC=84=9C=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/docs/asciidoc/member.adoc | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 src/docs/asciidoc/member.adoc diff --git a/src/docs/asciidoc/member.adoc b/src/docs/asciidoc/member.adoc new file mode 100644 index 000000000..63caf96ba --- /dev/null +++ b/src/docs/asciidoc/member.adoc @@ -0,0 +1,19 @@ +== 회원 API + +=== 회원가입 +operation::member-api-controller-test/create-member[snippets='http-request,http-response'] + +=== 로그인 +operation::member-api-controller-test/login[snippets='http-request,http-response'] + +=== 회원 조회 +operation::member-api-controller-test/read-member[snippets='http-request,http-response'] + +=== 위시 상품 조회 +operation::member-api-controller-test/read-wish-product[snippets='http-request,http-response'] + +=== 위시 상품 수정 +operation::member-api-controller-test/update-wish-product[snippets='http-request,http-response'] + +=== 위시 상품 삭제 +operation::member-api-controller-test/delete-wish-product[snippets='http-request,http-response'] From 3e74d549e01d1028d3c2037f57f8cc978fce4508 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=82=98=EC=A0=9C=EB=B2=95?= Date: Mon, 29 Jul 2024 23:23:09 +0900 Subject: [PATCH 088/138] =?UTF-8?q?docs:=20docs.adoc=20=ED=9A=8C=EC=9B=90?= =?UTF-8?q?=20API=20=EB=AA=85=EC=84=B8=EC=84=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/docs/asciidoc/docs.adoc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/docs/asciidoc/docs.adoc b/src/docs/asciidoc/docs.adoc index fd74fdae9..7f300bc95 100644 --- a/src/docs/asciidoc/docs.adoc +++ b/src/docs/asciidoc/docs.adoc @@ -6,4 +6,5 @@ :sectlinks: include::product.adoc[] -include::category.adoc[] \ No newline at end of file +include::category.adoc[] +include::member.adoc[] \ No newline at end of file From 0a2644da224ccb5b3efb8015a09d7bfe989de4bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=82=98=EC=A0=9C=EB=B2=95?= Date: Mon, 29 Jul 2024 23:23:32 +0900 Subject: [PATCH 089/138] =?UTF-8?q?refactor:=20CategoryApiControllerTest.j?= =?UTF-8?q?ava=20=EC=9A=94=EC=B2=AD/=EC=9D=91=EB=8B=B5=20=ED=83=80?= =?UTF-8?q?=EC=9E=85=20=EC=A7=80=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/CategoryApiControllerTest.java | 57 ++++++++++--------- 1 file changed, 29 insertions(+), 28 deletions(-) diff --git a/src/test/java/gift/web/controller/api/CategoryApiControllerTest.java b/src/test/java/gift/web/controller/api/CategoryApiControllerTest.java index c904edd50..b8a0d2acc 100644 --- a/src/test/java/gift/web/controller/api/CategoryApiControllerTest.java +++ b/src/test/java/gift/web/controller/api/CategoryApiControllerTest.java @@ -48,6 +48,7 @@ import org.springframework.restdocs.RestDocumentationExtension; import org.springframework.restdocs.mockmvc.MockMvcRestDocumentation; import org.springframework.restdocs.mockmvc.RestDocumentationResultHandler; +import org.springframework.restdocs.payload.JsonFieldType; import org.springframework.test.context.ActiveProfiles; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.setup.MockMvcBuilders; @@ -115,17 +116,17 @@ void createCategory() throws Exception { .andDo( restDocs.document( requestFields( - fieldWithPath("name").description("카테고리명"), - fieldWithPath("description").description("카테고리 설명"), - fieldWithPath("imageUrl").description("이미지 URL"), - fieldWithPath("color").description("색상 코드") + fieldWithPath("name").type(JsonFieldType.STRING).description("카테고리명"), + fieldWithPath("description").type(JsonFieldType.STRING).description("카테고리 설명"), + fieldWithPath("imageUrl").type(JsonFieldType.STRING).description("이미지 URL"), + fieldWithPath("color").type(JsonFieldType.STRING).description("색상 코드") ), responseFields( - fieldWithPath("id").description("카테고리 ID"), - fieldWithPath("name").description("카테고리명"), - fieldWithPath("description").description("카테고리 설명"), - fieldWithPath("imageUrl").description("이미지 URL"), - fieldWithPath("color").description("색상 코드") + fieldWithPath("id").type(JsonFieldType.NUMBER).description("카테고리 ID"), + fieldWithPath("name").type(JsonFieldType.STRING).description("카테고리명"), + fieldWithPath("description").type(JsonFieldType.STRING).description("카테고리 설명"), + fieldWithPath("imageUrl").type(JsonFieldType.STRING).description("이미지 URL"), + fieldWithPath("color").type(JsonFieldType.STRING).description("색상 코드") ) ) ); @@ -154,11 +155,11 @@ void readAllCategories() throws Exception { parameterWithName("sort").optional().description("정렬 조건") ), responseFields( - fieldWithPath("categories[].id").description("카테고리 ID"), - fieldWithPath("categories[].name").description("카테고리명"), - fieldWithPath("categories[].description").description("카테고리 설명"), - fieldWithPath("categories[].imageUrl").description("이미지 URL"), - fieldWithPath("categories[].color").description("색상 코드") + fieldWithPath("categories[].id").type(JsonFieldType.NUMBER).description("카테고리 ID"), + fieldWithPath("categories[].name").type(JsonFieldType.STRING).description("카테고리명"), + fieldWithPath("categories[].description").type(JsonFieldType.STRING).description("카테고리 설명"), + fieldWithPath("categories[].imageUrl").type(JsonFieldType.STRING).description("이미지 URL"), + fieldWithPath("categories[].color").type(JsonFieldType.STRING).description("색상 코드") ) ) ); @@ -179,11 +180,11 @@ void readCategory() throws Exception { .andDo( restDocs.document( responseFields( - fieldWithPath("id").description("카테고리 ID"), - fieldWithPath("name").description("카테고리명"), - fieldWithPath("description").description("카테고리 설명"), - fieldWithPath("imageUrl").description("이미지 URL"), - fieldWithPath("color").description("색상 코드") + fieldWithPath("id").type(JsonFieldType.NUMBER).description("카테고리 ID"), + fieldWithPath("name").type(JsonFieldType.STRING).description("카테고리명"), + fieldWithPath("description").type(JsonFieldType.STRING).description("카테고리 설명"), + fieldWithPath("imageUrl").type(JsonFieldType.STRING).description("이미지 URL"), + fieldWithPath("color").type(JsonFieldType.STRING).description("색상 코드") ) ) ); @@ -211,17 +212,17 @@ void updateCategory() throws Exception { .andDo( restDocs.document( requestFields( - fieldWithPath("name").description("카테고리명"), - fieldWithPath("description").description("카테고리 설명"), - fieldWithPath("imageUrl").description("이미지 URL"), - fieldWithPath("color").description("색상 코드") + fieldWithPath("name").type(JsonFieldType.STRING).description("카테고리명"), + fieldWithPath("description").type(JsonFieldType.STRING).description("카테고리 설명"), + fieldWithPath("imageUrl").type(JsonFieldType.STRING).description("이미지 URL"), + fieldWithPath("color").type(JsonFieldType.STRING).description("색상 코드") ), responseFields( - fieldWithPath("id").description("카테고리 ID"), - fieldWithPath("name").description("카테고리명"), - fieldWithPath("description").description("카테고리 설명"), - fieldWithPath("imageUrl").description("이미지 URL"), - fieldWithPath("color").description("색상 코드") + fieldWithPath("id").type(JsonFieldType.NUMBER).description("카테고리 ID"), + fieldWithPath("name").type(JsonFieldType.STRING).description("카테고리명"), + fieldWithPath("description").type(JsonFieldType.STRING).description("카테고리 설명"), + fieldWithPath("imageUrl").type(JsonFieldType.STRING).description("이미지 URL"), + fieldWithPath("color").type(JsonFieldType.STRING).description("색상 코드") ) ) ); From 38020ea488946d2322ff6a58e293f0c3324d65ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=82=98=EC=A0=9C=EB=B2=95?= Date: Mon, 29 Jul 2024 23:53:51 +0900 Subject: [PATCH 090/138] =?UTF-8?q?docs:=20login.adoc=20=EC=86=8C=EC=85=9C?= =?UTF-8?q?=20=EB=A1=9C=EA=B7=B8=EC=9D=B8=20API=20=EB=AA=85=EC=84=B8=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/docs/asciidoc/login.adoc | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 src/docs/asciidoc/login.adoc diff --git a/src/docs/asciidoc/login.adoc b/src/docs/asciidoc/login.adoc new file mode 100644 index 000000000..a31559600 --- /dev/null +++ b/src/docs/asciidoc/login.adoc @@ -0,0 +1,4 @@ +== 소셜 로그인 API + +=== 카카오 로그인 +operation::login-controller-test/kakao-login[] \ No newline at end of file From ccfe3532e38890a8e4744682a10bb0bc777cdd60 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=82=98=EC=A0=9C=EB=B2=95?= Date: Mon, 29 Jul 2024 23:54:24 +0900 Subject: [PATCH 091/138] =?UTF-8?q?docs:=20API=20=EB=AA=85=EC=84=B8?= =?UTF-8?q?=EC=84=9C=20snippet=20=EB=AA=A8=EB=91=90=20=EB=B3=B4=EC=9D=B4?= =?UTF-8?q?=EB=8F=84=EB=A1=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/docs/asciidoc/category.adoc | 10 +++++----- src/docs/asciidoc/member.adoc | 12 ++++++------ src/docs/asciidoc/product.adoc | 22 +++++++++++----------- 3 files changed, 22 insertions(+), 22 deletions(-) diff --git a/src/docs/asciidoc/category.adoc b/src/docs/asciidoc/category.adoc index 2818b185a..76b342098 100644 --- a/src/docs/asciidoc/category.adoc +++ b/src/docs/asciidoc/category.adoc @@ -1,19 +1,19 @@ == 카테고리 API === 카테고리 생성 -operation::category-api-controller-test/create-category[snippets='http-request,http-response'] +operation::category-api-controller-test/create-category[] === 카테고리 수정 -operation::category-api-controller-test/update-category[snippets='http-request,http-response'] +operation::category-api-controller-test/update-category[] === 카테고리 삭제 -operation::category-api-controller-test/delete-category[snippets='http-request,http-response'] +operation::category-api-controller-test/delete-category[] === 단일 카테고리 조회 -operation::category-api-controller-test/read-category[snippets='http-request,http-response'] +operation::category-api-controller-test/read-category[] === 전체 카테고리 조회 -operation::category-api-controller-test/read-all-categories[snippets='http-request,http-response'] +operation::category-api-controller-test/read-all-categories[] diff --git a/src/docs/asciidoc/member.adoc b/src/docs/asciidoc/member.adoc index 63caf96ba..5676d7a93 100644 --- a/src/docs/asciidoc/member.adoc +++ b/src/docs/asciidoc/member.adoc @@ -1,19 +1,19 @@ == 회원 API === 회원가입 -operation::member-api-controller-test/create-member[snippets='http-request,http-response'] +operation::member-api-controller-test/create-member[] === 로그인 -operation::member-api-controller-test/login[snippets='http-request,http-response'] +operation::member-api-controller-test/login[] === 회원 조회 -operation::member-api-controller-test/read-member[snippets='http-request,http-response'] +operation::member-api-controller-test/read-member[] === 위시 상품 조회 -operation::member-api-controller-test/read-wish-product[snippets='http-request,http-response'] +operation::member-api-controller-test/read-wish-product[] === 위시 상품 수정 -operation::member-api-controller-test/update-wish-product[snippets='http-request,http-response'] +operation::member-api-controller-test/update-wish-product[] === 위시 상품 삭제 -operation::member-api-controller-test/delete-wish-product[snippets='http-request,http-response'] +operation::member-api-controller-test/delete-wish-product[] diff --git a/src/docs/asciidoc/product.adoc b/src/docs/asciidoc/product.adoc index 213535572..65987e449 100644 --- a/src/docs/asciidoc/product.adoc +++ b/src/docs/asciidoc/product.adoc @@ -1,35 +1,35 @@ == 상품 API === 전체 상품 조회 -operation::product-api-controller-test/read-all-products[snippets='http-request,http-response'] +operation::product-api-controller-test/read-all-products[] === 단일 상품 조회 -operation::product-api-controller-test/read-product[snippets='http-request,http-response'] +operation::product-api-controller-test/read-product[] === 상품 생성 -operation::product-api-controller-test/create-product[snippets='http-request,http-response'] +operation::product-api-controller-test/create-product[] === 상품 수정 -operation::product-api-controller-test/update-product[snippets='http-request,http-response'] +operation::product-api-controller-test/update-product[] === 상품 삭제 -operation::product-api-controller-test/delete-product[snippets='http-request,http-response'] +operation::product-api-controller-test/delete-product[] === 상품 주문 -operation::product-api-controller-test/order-product[snippets='http-request,http-response'] +operation::product-api-controller-test/order-product[] === 위시 상품 추가 -operation::product-api-controller-test/create-wish-product[snippets='http-request,http-response'] +operation::product-api-controller-test/create-wish-product[] === 상품 옵션 생성 -operation::product-api-controller-test/create-option[snippets='http-request,http-response'] +operation::product-api-controller-test/create-option[] === 상품 옵션 조회 -operation::product-api-controller-test/read-options[snippets='http-request,http-response'] +operation::product-api-controller-test/read-options[] === 상품 옵션 수정 -operation::product-api-controller-test/update-option[snippets='http-request,http-response'] +operation::product-api-controller-test/update-option[] === 상품 옵션 삭제 -operation::product-api-controller-test/delete-option[snippets='http-request,http-response'] +operation::product-api-controller-test/delete-option[] From d35b4fbfa741653a7e0f916c4a37baf5d4229611 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=82=98=EC=A0=9C=EB=B2=95?= Date: Mon, 29 Jul 2024 23:54:48 +0900 Subject: [PATCH 092/138] =?UTF-8?q?test:=20MemberApiControllerTest.java=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/gift/web/controller/api/MemberApiControllerTest.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/test/java/gift/web/controller/api/MemberApiControllerTest.java b/src/test/java/gift/web/controller/api/MemberApiControllerTest.java index e8cf83d98..3cd366a8d 100644 --- a/src/test/java/gift/web/controller/api/MemberApiControllerTest.java +++ b/src/test/java/gift/web/controller/api/MemberApiControllerTest.java @@ -110,7 +110,6 @@ void createMember() throws Exception { mockMvc .perform( post(BASE_URL + "/register") - .header(HttpHeaders.AUTHORIZATION, "Bearer " + accessToken) .contentType(MediaType.APPLICATION_JSON) .content(content) ) From b82dc7646fd7340ce27c407532be5ecb82533463 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=82=98=EC=A0=9C=EB=B2=95?= Date: Mon, 29 Jul 2024 23:55:05 +0900 Subject: [PATCH 093/138] style: CategoryApiControllerTest.java remove unused import --- .../gift/web/controller/api/CategoryApiControllerTest.java | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/test/java/gift/web/controller/api/CategoryApiControllerTest.java b/src/test/java/gift/web/controller/api/CategoryApiControllerTest.java index b8a0d2acc..20563b011 100644 --- a/src/test/java/gift/web/controller/api/CategoryApiControllerTest.java +++ b/src/test/java/gift/web/controller/api/CategoryApiControllerTest.java @@ -1,6 +1,5 @@ package gift.web.controller.api; -import static org.junit.jupiter.api.Assertions.*; import static org.mockito.ArgumentMatchers.any; import static org.mockito.BDDMockito.given; import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.post; @@ -20,12 +19,7 @@ import gift.domain.Member; import gift.domain.Member.Builder; import gift.domain.vo.Email; -import gift.mock.MockLoginMemberArgumentResolver; import gift.service.CategoryService; -import gift.service.OrderService; -import gift.service.ProductOptionService; -import gift.service.ProductService; -import gift.service.WishProductService; import gift.web.dto.request.category.CreateCategoryRequest; import gift.web.dto.request.category.UpdateCategoryRequest; import gift.web.dto.response.category.CreateCategoryResponse; From 58603deedd60297751637cecc537504328dcd871 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=82=98=EC=A0=9C=EB=B2=95?= Date: Mon, 29 Jul 2024 23:55:25 +0900 Subject: [PATCH 094/138] =?UTF-8?q?docs:=20docs.adoc=20=EC=86=8C=EC=85=9C?= =?UTF-8?q?=20=EB=A1=9C=EA=B7=B8=EC=9D=B8=20API=20=EB=AA=85=EC=84=B8=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/docs/asciidoc/docs.adoc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/docs/asciidoc/docs.adoc b/src/docs/asciidoc/docs.adoc index 7f300bc95..5ebd45ef9 100644 --- a/src/docs/asciidoc/docs.adoc +++ b/src/docs/asciidoc/docs.adoc @@ -7,4 +7,5 @@ include::product.adoc[] include::category.adoc[] -include::member.adoc[] \ No newline at end of file +include::member.adoc[] +include::login.adoc[] \ No newline at end of file From 627e3ce5865fab5c5c44d22c6ae7d951a2d1576e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=82=98=EC=A0=9C=EB=B2=95?= Date: Mon, 29 Jul 2024 23:59:53 +0900 Subject: [PATCH 095/138] =?UTF-8?q?docs:=20LoginControllerTest.java=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/api/LoginControllerTest.java | 84 +++++++++++++++++++ 1 file changed, 84 insertions(+) create mode 100644 src/test/java/gift/web/controller/api/LoginControllerTest.java diff --git a/src/test/java/gift/web/controller/api/LoginControllerTest.java b/src/test/java/gift/web/controller/api/LoginControllerTest.java new file mode 100644 index 000000000..0e08217f7 --- /dev/null +++ b/src/test/java/gift/web/controller/api/LoginControllerTest.java @@ -0,0 +1,84 @@ +package gift.web.controller.api; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.BDDMockito.given; +import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath; +import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields; +import static org.springframework.restdocs.request.RequestDocumentation.parameterWithName; +import static org.springframework.restdocs.request.RequestDocumentation.queryParameters; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import com.fasterxml.jackson.databind.ObjectMapper; +import gift.config.RestDocsConfiguration; +import gift.service.LoginService; +import gift.web.dto.response.LoginResponse; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.context.annotation.Import; +import org.springframework.restdocs.RestDocumentationContextProvider; +import org.springframework.restdocs.RestDocumentationExtension; +import org.springframework.restdocs.mockmvc.MockMvcRestDocumentation; +import org.springframework.restdocs.mockmvc.RestDocumentationResultHandler; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; + +@ActiveProfiles("test") +@SpringBootTest +@Import(RestDocsConfiguration.class) +@ExtendWith(RestDocumentationExtension.class) +class LoginControllerTest { + + private MockMvc mockMvc; + + @Autowired + protected RestDocumentationResultHandler restDocs; + + @Autowired + protected ObjectMapper objectMapper; + + @MockBean + private LoginService loginService; + + private static final String BASE_URL = "/api/login"; + + @BeforeEach + void setUp( + final RestDocumentationContextProvider provider + ) { + mockMvc = MockMvcBuilders + .standaloneSetup(new LoginController(loginService)) + .apply(MockMvcRestDocumentation.documentationConfiguration(provider)) + .alwaysDo(restDocs) + .build(); + } + + + @Test + void kakaoLogin() throws Exception { + given(loginService.kakaoLogin(any())) + .willReturn(new LoginResponse("Bearer {{access_token}}")); + + mockMvc + .perform( + get(BASE_URL + "/oauth2/kakao") + .param("code", "kakao-auth-code") + ) + .andExpect(status().isOk()) + .andDo( + restDocs.document( + queryParameters( + parameterWithName("code").optional().description("kakao-auth-code") + ), + responseFields( + fieldWithPath("accessToken").type(String.class).description("access-token(BEARER)") + ) + ) + ); + } +} \ No newline at end of file From ed871182d96fde68cfbaf2ae5db96629e8c3108f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=82=98=EC=A0=9C=EB=B2=95?= Date: Tue, 30 Jul 2024 00:00:14 +0900 Subject: [PATCH 096/138] =?UTF-8?q?fest:=20API=20=EB=AA=85=EC=84=B8=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/resources/static/category.html | 1149 +++++++ src/main/resources/static/docs.html | 3964 +++++++++++++++++++++++ src/main/resources/static/login.html | 561 ++++ src/main/resources/static/member.html | 1219 +++++++ src/main/resources/static/product.html | 2092 ++++++++++++ 5 files changed, 8985 insertions(+) create mode 100644 src/main/resources/static/category.html create mode 100644 src/main/resources/static/docs.html create mode 100644 src/main/resources/static/login.html create mode 100644 src/main/resources/static/member.html create mode 100644 src/main/resources/static/product.html diff --git a/src/main/resources/static/category.html b/src/main/resources/static/category.html new file mode 100644 index 000000000..ebb723429 --- /dev/null +++ b/src/main/resources/static/category.html @@ -0,0 +1,1149 @@ + + + + + + + +카테고리 API + + + + + +
+
+

카테고리 API

+
+
+

카테고리 생성

+
+

Curl request

+
+
+
$ curl 'http://localhost:8080/api/categories' -i -X POST \
+    -H 'Content-Type: application/json' \
+    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTU3LCJleHAiOjE3MjIyNjg3NTd9.TJZznQ-W-PQIhpYNTuIE6Frb4r9ZAK0C5c84c8TKJCTk7fFIPupn7RH2vdijJXlsTvpwid8QzsMwLBoKKQaCaQ' \
+    -d '{
+  "name" : "카테고리01",
+  "description" : "카테고리01 설명",
+  "imageUrl" : "https://via.placeholder.com/150",
+  "color" : "#FFFFFF"
+}'
+
+
+
+
+

HTTP request

+
+
+
POST /api/categories HTTP/1.1
+Content-Type: application/json
+Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTU3LCJleHAiOjE3MjIyNjg3NTd9.TJZznQ-W-PQIhpYNTuIE6Frb4r9ZAK0C5c84c8TKJCTk7fFIPupn7RH2vdijJXlsTvpwid8QzsMwLBoKKQaCaQ
+Content-Length: 147
+Host: localhost:8080
+
+{
+  "name" : "카테고리01",
+  "description" : "카테고리01 설명",
+  "imageUrl" : "https://via.placeholder.com/150",
+  "color" : "#FFFFFF"
+}
+
+
+
+
+

HTTP response

+
+
+
HTTP/1.1 201 Created
+Location: http://localhost:8080/api/categories/1
+Content-Type: application/json
+Content-Length: 159
+
+{
+  "id" : 1,
+  "name" : "카테고리01",
+  "description" : "카테고리01 설명",
+  "imageUrl" : "https://via.placeholder.com/150",
+  "color" : "#FFFFFF"
+}
+
+
+
+
+

HTTPie request

+
+
+
$ echo '{
+  "name" : "카테고리01",
+  "description" : "카테고리01 설명",
+  "imageUrl" : "https://via.placeholder.com/150",
+  "color" : "#FFFFFF"
+}' | http POST 'http://localhost:8080/api/categories' \
+    'Content-Type:application/json' \
+    'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTU3LCJleHAiOjE3MjIyNjg3NTd9.TJZznQ-W-PQIhpYNTuIE6Frb4r9ZAK0C5c84c8TKJCTk7fFIPupn7RH2vdijJXlsTvpwid8QzsMwLBoKKQaCaQ'
+
+
+
+
+

Request body

+
+
+
{
+  "name" : "카테고리01",
+  "description" : "카테고리01 설명",
+  "imageUrl" : "https://via.placeholder.com/150",
+  "color" : "#FFFFFF"
+}
+
+
+
+
+

Request fields

+ +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
PathTypeDescription

name

String

카테고리명

description

String

카테고리 설명

imageUrl

String

이미지 URL

color

String

색상 코드

+
+
+

Response body

+
+
+
{
+  "id" : 1,
+  "name" : "카테고리01",
+  "description" : "카테고리01 설명",
+  "imageUrl" : "https://via.placeholder.com/150",
+  "color" : "#FFFFFF"
+}
+
+
+
+
+

Response fields

+ +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
PathTypeDescription

id

Number

카테고리 ID

name

String

카테고리명

description

String

카테고리 설명

imageUrl

String

이미지 URL

color

String

색상 코드

+
+
+
+

카테고리 수정

+
+

Curl request

+
+
+
$ curl 'http://localhost:8080/api/categories/1' -i -X PUT \
+    -H 'Content-Type: application/json' \
+    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTU3LCJleHAiOjE3MjIyNjg3NTd9.TJZznQ-W-PQIhpYNTuIE6Frb4r9ZAK0C5c84c8TKJCTk7fFIPupn7RH2vdijJXlsTvpwid8QzsMwLBoKKQaCaQ' \
+    -d '{
+  "name" : "카테고리01",
+  "description" : "카테고리01 설명",
+  "imageUrl" : "https://via.placeholder.com/150",
+  "color" : "#FFFFFF"
+}'
+
+
+
+
+

HTTP request

+
+
+
PUT /api/categories/1 HTTP/1.1
+Content-Type: application/json
+Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTU3LCJleHAiOjE3MjIyNjg3NTd9.TJZznQ-W-PQIhpYNTuIE6Frb4r9ZAK0C5c84c8TKJCTk7fFIPupn7RH2vdijJXlsTvpwid8QzsMwLBoKKQaCaQ
+Content-Length: 147
+Host: localhost:8080
+
+{
+  "name" : "카테고리01",
+  "description" : "카테고리01 설명",
+  "imageUrl" : "https://via.placeholder.com/150",
+  "color" : "#FFFFFF"
+}
+
+
+
+
+

HTTP response

+
+
+
HTTP/1.1 200 OK
+Content-Type: application/json
+Content-Length: 159
+
+{
+  "id" : 1,
+  "name" : "카테고리01",
+  "description" : "카테고리01 설명",
+  "imageUrl" : "https://via.placeholder.com/150",
+  "color" : "#FFFFFF"
+}
+
+
+
+
+

HTTPie request

+
+
+
$ echo '{
+  "name" : "카테고리01",
+  "description" : "카테고리01 설명",
+  "imageUrl" : "https://via.placeholder.com/150",
+  "color" : "#FFFFFF"
+}' | http PUT 'http://localhost:8080/api/categories/1' \
+    'Content-Type:application/json' \
+    'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTU3LCJleHAiOjE3MjIyNjg3NTd9.TJZznQ-W-PQIhpYNTuIE6Frb4r9ZAK0C5c84c8TKJCTk7fFIPupn7RH2vdijJXlsTvpwid8QzsMwLBoKKQaCaQ'
+
+
+
+
+

Request body

+
+
+
{
+  "name" : "카테고리01",
+  "description" : "카테고리01 설명",
+  "imageUrl" : "https://via.placeholder.com/150",
+  "color" : "#FFFFFF"
+}
+
+
+
+
+

Request fields

+ +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
PathTypeDescription

name

String

카테고리명

description

String

카테고리 설명

imageUrl

String

이미지 URL

color

String

색상 코드

+
+
+

Response body

+
+
+
{
+  "id" : 1,
+  "name" : "카테고리01",
+  "description" : "카테고리01 설명",
+  "imageUrl" : "https://via.placeholder.com/150",
+  "color" : "#FFFFFF"
+}
+
+
+
+
+

Response fields

+ +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
PathTypeDescription

id

Number

카테고리 ID

name

String

카테고리명

description

String

카테고리 설명

imageUrl

String

이미지 URL

color

String

색상 코드

+
+
+
+

카테고리 삭제

+
+

Curl request

+
+
+
$ curl 'http://localhost:8080/api/categories/1' -i -X DELETE \
+    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTU3LCJleHAiOjE3MjIyNjg3NTd9.TJZznQ-W-PQIhpYNTuIE6Frb4r9ZAK0C5c84c8TKJCTk7fFIPupn7RH2vdijJXlsTvpwid8QzsMwLBoKKQaCaQ'
+
+
+
+
+

HTTP request

+
+
+
DELETE /api/categories/1 HTTP/1.1
+Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTU3LCJleHAiOjE3MjIyNjg3NTd9.TJZznQ-W-PQIhpYNTuIE6Frb4r9ZAK0C5c84c8TKJCTk7fFIPupn7RH2vdijJXlsTvpwid8QzsMwLBoKKQaCaQ
+Host: localhost:8080
+
+
+
+
+

HTTP response

+
+
+
HTTP/1.1 204 No Content
+
+
+
+
+

HTTPie request

+
+
+
$ http DELETE 'http://localhost:8080/api/categories/1' \
+    'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTU3LCJleHAiOjE3MjIyNjg3NTd9.TJZznQ-W-PQIhpYNTuIE6Frb4r9ZAK0C5c84c8TKJCTk7fFIPupn7RH2vdijJXlsTvpwid8QzsMwLBoKKQaCaQ'
+
+
+
+
+

Request body

+
+
+
+
+
+
+
+

Response body

+
+
+
+
+
+
+
+
+

단일 카테고리 조회

+
+

Curl request

+
+
+
$ curl 'http://localhost:8080/api/categories/1' -i -X GET \
+    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTU3LCJleHAiOjE3MjIyNjg3NTd9.TJZznQ-W-PQIhpYNTuIE6Frb4r9ZAK0C5c84c8TKJCTk7fFIPupn7RH2vdijJXlsTvpwid8QzsMwLBoKKQaCaQ'
+
+
+
+
+

HTTP request

+
+
+
GET /api/categories/1 HTTP/1.1
+Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTU3LCJleHAiOjE3MjIyNjg3NTd9.TJZznQ-W-PQIhpYNTuIE6Frb4r9ZAK0C5c84c8TKJCTk7fFIPupn7RH2vdijJXlsTvpwid8QzsMwLBoKKQaCaQ
+Host: localhost:8080
+
+
+
+
+

HTTP response

+
+
+
HTTP/1.1 200 OK
+Content-Type: application/json
+Content-Length: 159
+
+{
+  "id" : 1,
+  "name" : "카테고리01",
+  "description" : "카테고리01 설명",
+  "imageUrl" : "https://via.placeholder.com/150",
+  "color" : "#FFFFFF"
+}
+
+
+
+
+

HTTPie request

+
+
+
$ http GET 'http://localhost:8080/api/categories/1' \
+    'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTU3LCJleHAiOjE3MjIyNjg3NTd9.TJZznQ-W-PQIhpYNTuIE6Frb4r9ZAK0C5c84c8TKJCTk7fFIPupn7RH2vdijJXlsTvpwid8QzsMwLBoKKQaCaQ'
+
+
+
+
+

Request body

+
+
+
+
+
+
+
+

Response body

+
+
+
{
+  "id" : 1,
+  "name" : "카테고리01",
+  "description" : "카테고리01 설명",
+  "imageUrl" : "https://via.placeholder.com/150",
+  "color" : "#FFFFFF"
+}
+
+
+
+
+

Response fields

+ +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
PathTypeDescription

id

Number

카테고리 ID

name

String

카테고리명

description

String

카테고리 설명

imageUrl

String

이미지 URL

color

String

색상 코드

+
+
+
+

전체 카테고리 조회

+
+

Curl request

+
+
+
$ curl 'http://localhost:8080/api/categories' -i -X GET \
+    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTU3LCJleHAiOjE3MjIyNjg3NTd9.TJZznQ-W-PQIhpYNTuIE6Frb4r9ZAK0C5c84c8TKJCTk7fFIPupn7RH2vdijJXlsTvpwid8QzsMwLBoKKQaCaQ'
+
+
+
+
+

HTTP request

+
+
+
GET /api/categories HTTP/1.1
+Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTU3LCJleHAiOjE3MjIyNjg3NTd9.TJZznQ-W-PQIhpYNTuIE6Frb4r9ZAK0C5c84c8TKJCTk7fFIPupn7RH2vdijJXlsTvpwid8QzsMwLBoKKQaCaQ
+Host: localhost:8080
+
+
+
+
+

HTTP response

+
+
+
HTTP/1.1 200 OK
+Content-Type: application/json
+Content-Length: 369
+
+{
+  "categories" : [ {
+    "id" : 1,
+    "name" : "카테고리01",
+    "description" : "카테고리01 설명",
+    "imageUrl" : "https://via.placeholder.com/150",
+    "color" : "#FFFFFF"
+  }, {
+    "id" : 2,
+    "name" : "카테고리02",
+    "description" : "카테고리02 설명",
+    "imageUrl" : "https://via.placeholder.com/150",
+    "color" : "#FFFFFF"
+  } ]
+}
+
+
+
+
+

HTTPie request

+
+
+
$ http GET 'http://localhost:8080/api/categories' \
+    'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTU3LCJleHAiOjE3MjIyNjg3NTd9.TJZznQ-W-PQIhpYNTuIE6Frb4r9ZAK0C5c84c8TKJCTk7fFIPupn7RH2vdijJXlsTvpwid8QzsMwLBoKKQaCaQ'
+
+
+
+
+

Query parameters

+ ++++ + + + + + + + + + + + + + + + + + + + + +
ParameterDescription

page

페이지 번호

size

페이지 크기

sort

정렬 조건

+
+
+

Request body

+
+
+
+
+
+
+
+

Response body

+
+
+
{
+  "categories" : [ {
+    "id" : 1,
+    "name" : "카테고리01",
+    "description" : "카테고리01 설명",
+    "imageUrl" : "https://via.placeholder.com/150",
+    "color" : "#FFFFFF"
+  }, {
+    "id" : 2,
+    "name" : "카테고리02",
+    "description" : "카테고리02 설명",
+    "imageUrl" : "https://via.placeholder.com/150",
+    "color" : "#FFFFFF"
+  } ]
+}
+
+
+
+
+

Response fields

+ +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
PathTypeDescription

categories[].id

Number

카테고리 ID

categories[].name

String

카테고리명

categories[].description

String

카테고리 설명

categories[].imageUrl

String

이미지 URL

categories[].color

String

색상 코드

+
+
+
+
+
+ + + \ No newline at end of file diff --git a/src/main/resources/static/docs.html b/src/main/resources/static/docs.html new file mode 100644 index 000000000..c2274bc74 --- /dev/null +++ b/src/main/resources/static/docs.html @@ -0,0 +1,3964 @@ + + + + + + + +카카오 테크 캠퍼스 STEP 2 - REST Docs + + + + + + +
+
+

상품 API

+
+
+

전체 상품 조회

+
+

Curl request

+
+
+
$ curl 'http://localhost:8080/api/products' -i -X GET \
+    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w'
+
+
+
+
+

HTTP request

+
+
+
GET /api/products HTTP/1.1
+Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w
+Host: localhost:8080
+
+
+
+
+

HTTP response

+
+
+
HTTP/1.1 200 OK
+Content-Type: application/json
+Content-Length: 443
+
+{
+  "products" : [ {
+    "id" : 1,
+    "name" : "상품01",
+    "price" : 1000,
+    "imageUrl" : "https://via.placeholder.com/150",
+    "options" : [ {
+      "id" : 1,
+      "name" : "상품 옵션 01",
+      "stock" : 100
+    } ],
+    "category" : {
+      "id" : 1,
+      "name" : "카테고리01",
+      "description" : "카테고리01 입니다",
+      "imageUrl" : "https://via.placeholder.com/150",
+      "color" : "#FFFFFF"
+    }
+  } ]
+}
+
+
+
+
+

HTTPie request

+
+
+
$ http GET 'http://localhost:8080/api/products' \
+    'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w'
+
+
+
+
+

Query parameters

+ ++++ + + + + + + + + + + + + + + + + +
ParameterDescription

page

페이지 번호

size

페이지 크기

+
+
+

Request body

+
+
+
+
+
+
+
+

Response body

+
+
+
{
+  "products" : [ {
+    "id" : 1,
+    "name" : "상품01",
+    "price" : 1000,
+    "imageUrl" : "https://via.placeholder.com/150",
+    "options" : [ {
+      "id" : 1,
+      "name" : "상품 옵션 01",
+      "stock" : 100
+    } ],
+    "category" : {
+      "id" : 1,
+      "name" : "카테고리01",
+      "description" : "카테고리01 입니다",
+      "imageUrl" : "https://via.placeholder.com/150",
+      "color" : "#FFFFFF"
+    }
+  } ]
+}
+
+
+
+
+

Response fields

+ +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
PathTypeDescription

products[].id

Number

상품 ID

products[].name

String

상품명

products[].price

Number

상품 가격

products[].imageUrl

String

상품 이미지 URL

products[].options

Array

상품 옵션 목록

products[].options[].id

Number

상품 옵션 ID

products[].options[].name

String

상품 옵션명

products[].options[].stock

Number

상품 옵션 재고

products[].category.id

Number

카테고리 ID

products[].category.name

String

카테고리명

products[].category.description

String

카테고리 설명

products[].category.imageUrl

String

카테고리 이미지 URL

products[].category.color

String

카테고리 색상

+
+
+
+

단일 상품 조회

+
+

Curl request

+
+
+
$ curl 'http://localhost:8080/api/products/1' -i -X GET \
+    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w'
+
+
+
+
+

HTTP request

+
+
+
GET /api/products/1 HTTP/1.1
+Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w
+Host: localhost:8080
+
+
+
+
+

HTTP response

+
+
+
HTTP/1.1 200 OK
+Content-Type: application/json
+Content-Length: 386
+
+{
+  "id" : 1,
+  "name" : "상품01",
+  "price" : 1000,
+  "imageUrl" : "https://via.placeholder.com/150",
+  "options" : [ {
+    "id" : 1,
+    "name" : "상품 옵션 01",
+    "stock" : 100
+  } ],
+  "category" : {
+    "id" : 1,
+    "name" : "카테고리01",
+    "description" : "카테고리01 입니다",
+    "imageUrl" : "https://via.placeholder.com/150",
+    "color" : "#FFFFFF"
+  }
+}
+
+
+
+
+

HTTPie request

+
+
+
$ http GET 'http://localhost:8080/api/products/1' \
+    'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w'
+
+
+
+
+

Request body

+
+
+
+
+
+
+
+

Response body

+
+
+
{
+  "id" : 1,
+  "name" : "상품01",
+  "price" : 1000,
+  "imageUrl" : "https://via.placeholder.com/150",
+  "options" : [ {
+    "id" : 1,
+    "name" : "상품 옵션 01",
+    "stock" : 100
+  } ],
+  "category" : {
+    "id" : 1,
+    "name" : "카테고리01",
+    "description" : "카테고리01 입니다",
+    "imageUrl" : "https://via.placeholder.com/150",
+    "color" : "#FFFFFF"
+  }
+}
+
+
+
+
+

Response fields

+ +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
PathTypeDescription

id

Number

상품 ID

name

String

상품명

price

Number

상품 가격

imageUrl

String

상품 이미지 URL

options

Array

상품 옵션 목록

options[].id

Number

상품 옵션 ID

options[].name

String

상품 옵션명

options[].stock

Number

상품 옵션 재고

category.id

Number

카테고리 ID

category.name

String

카테고리명

category.description

String

카테고리 설명

category.imageUrl

String

카테고리 이미지 URL

category.color

String

카테고리 색상

+
+
+
+

상품 생성

+
+

Curl request

+
+
+
$ curl 'http://localhost:8080/api/products' -i -X POST \
+    -H 'Content-Type: application/json' \
+    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w' \
+    -d '{
+  "name" : "상품01",
+  "price" : 1000,
+  "imageUrl" : "https://via.placeholder.com/150",
+  "categoryId" : 1,
+  "productOptions" : [ {
+    "name" : "상품 옵션 01",
+    "stock" : 100
+  } ]
+}'
+
+
+
+
+

HTTP request

+
+
+
POST /api/products HTTP/1.1
+Content-Type: application/json
+Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w
+Content-Length: 196
+Host: localhost:8080
+
+{
+  "name" : "상품01",
+  "price" : 1000,
+  "imageUrl" : "https://via.placeholder.com/150",
+  "categoryId" : 1,
+  "productOptions" : [ {
+    "name" : "상품 옵션 01",
+    "stock" : 100
+  } ]
+}
+
+
+
+
+

HTTP response

+
+
+
HTTP/1.1 201 Created
+Location: http://localhost:8080/api/products/1
+Content-Type: application/json
+Content-Length: 216
+
+{
+  "id" : 1,
+  "name" : "상품01",
+  "price" : 1000,
+  "imageUrl" : "https://via.placeholder.com/150",
+  "options" : [ {
+    "id" : 1,
+    "name" : "상품 옵션 01",
+    "stock" : 100,
+    "productId" : 1
+  } ]
+}
+
+
+
+
+

HTTPie request

+
+
+
$ echo '{
+  "name" : "상품01",
+  "price" : 1000,
+  "imageUrl" : "https://via.placeholder.com/150",
+  "categoryId" : 1,
+  "productOptions" : [ {
+    "name" : "상품 옵션 01",
+    "stock" : 100
+  } ]
+}' | http POST 'http://localhost:8080/api/products' \
+    'Content-Type:application/json' \
+    'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w'
+
+
+
+
+

Request body

+
+
+
{
+  "name" : "상품01",
+  "price" : 1000,
+  "imageUrl" : "https://via.placeholder.com/150",
+  "categoryId" : 1,
+  "productOptions" : [ {
+    "name" : "상품 옵션 01",
+    "stock" : 100
+  } ]
+}
+
+
+
+
+

Request fields

+ +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
PathTypeDescription

name

String

상품명

price

Number

상품 가격

imageUrl

String

상품 이미지 URL

categoryId

Number

카테고리 ID

productOptions

Array

상품 옵션 목록

productOptions[].name

String

상품 옵션명

productOptions[].stock

Number

상품 옵션 재고

+
+
+

Response body

+
+
+
{
+  "id" : 1,
+  "name" : "상품01",
+  "price" : 1000,
+  "imageUrl" : "https://via.placeholder.com/150",
+  "options" : [ {
+    "id" : 1,
+    "name" : "상품 옵션 01",
+    "stock" : 100,
+    "productId" : 1
+  } ]
+}
+
+
+
+
+

Response fields

+ +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
PathTypeDescription

id

Number

상품 ID

name

String

상품명

price

Number

상품 가격

imageUrl

String

상품 이미지 URL

options

Array

상품 옵션 목록

options[].id

Number

상품 옵션 ID

options[].name

String

상품 옵션명

options[].stock

Number

상품 옵션 재고

options[].productId

Number

상품 옵션 상품 ID

+
+
+
+

상품 수정

+
+

Curl request

+
+
+
$ curl 'http://localhost:8080/api/products/1' -i -X PUT \
+    -H 'Content-Type: application/json' \
+    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w' \
+    -d '{
+  "name" : "상품01",
+  "price" : 1000,
+  "imageUrl" : "https://via.placeholder.com/150"
+}'
+
+
+
+
+

HTTP request

+
+
+
PUT /api/products/1 HTTP/1.1
+Content-Type: application/json
+Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w
+Content-Length: 93
+Host: localhost:8080
+
+{
+  "name" : "상품01",
+  "price" : 1000,
+  "imageUrl" : "https://via.placeholder.com/150"
+}
+
+
+
+
+

HTTP response

+
+
+
HTTP/1.1 200 OK
+Content-Type: application/json
+Content-Length: 296
+
+{
+  "id" : 1,
+  "name" : "상품01",
+  "price" : 1000,
+  "imageUrl" : "https://via.placeholder.com/150",
+  "category" : {
+    "id" : 1,
+    "name" : "카테고리01",
+    "description" : "카테고리01 입니다",
+    "imageUrl" : "https://via.placeholder.com/150",
+    "color" : "#FFFFFF"
+  }
+}
+
+
+
+
+

HTTPie request

+
+
+
$ echo '{
+  "name" : "상품01",
+  "price" : 1000,
+  "imageUrl" : "https://via.placeholder.com/150"
+}' | http PUT 'http://localhost:8080/api/products/1' \
+    'Content-Type:application/json' \
+    'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w'
+
+
+
+
+

Request body

+
+
+
{
+  "name" : "상품01",
+  "price" : 1000,
+  "imageUrl" : "https://via.placeholder.com/150"
+}
+
+
+
+
+

Request fields

+ +++++ + + + + + + + + + + + + + + + + + + + + + + + + +
PathTypeDescription

name

String

상품명

price

Number

상품 가격

imageUrl

String

상품 이미지 URL

+
+
+

Response body

+
+
+
{
+  "id" : 1,
+  "name" : "상품01",
+  "price" : 1000,
+  "imageUrl" : "https://via.placeholder.com/150",
+  "category" : {
+    "id" : 1,
+    "name" : "카테고리01",
+    "description" : "카테고리01 입니다",
+    "imageUrl" : "https://via.placeholder.com/150",
+    "color" : "#FFFFFF"
+  }
+}
+
+
+
+
+

Response fields

+ +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
PathTypeDescription

id

Number

상품 ID

name

String

상품명

price

Number

상품 가격

imageUrl

String

상품 이미지 URL

category.id

Number

카테고리 ID

category.name

String

카테고리명

category.description

String

카테고리 설명

category.imageUrl

String

카테고리 이미지 URL

category.color

String

카테고리 색상

+
+
+
+

상품 삭제

+
+

Curl request

+
+
+
$ curl 'http://localhost:8080/api/products/1' -i -X DELETE \
+    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w'
+
+
+
+
+

HTTP request

+
+
+
DELETE /api/products/1 HTTP/1.1
+Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w
+Host: localhost:8080
+
+
+
+
+

HTTP response

+
+
+
HTTP/1.1 204 No Content
+
+
+
+
+

HTTPie request

+
+
+
$ http DELETE 'http://localhost:8080/api/products/1' \
+    'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w'
+
+
+
+
+

Request body

+
+
+
+
+
+
+
+

Response body

+
+
+
+
+
+
+
+
+

상품 주문

+
+

Curl request

+
+
+
$ curl 'http://localhost:8080/api/products/1/order' -i -X POST \
+    -H 'Content-Type: application/json' \
+    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w' \
+    -d '{
+  "optionId" : 1,
+  "quantity" : 1,
+  "message" : "message"
+}'
+
+
+
+
+

HTTP request

+
+
+
POST /api/products/1/order HTTP/1.1
+Content-Type: application/json
+Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w
+Content-Length: 63
+Host: localhost:8080
+
+{
+  "optionId" : 1,
+  "quantity" : 1,
+  "message" : "message"
+}
+
+
+
+
+

HTTP response

+
+
+
HTTP/1.1 200 OK
+Content-Type: application/json
+Content-Length: 134
+
+{
+  "productId" : 1,
+  "optionId" : 1,
+  "optionStock" : 10,
+  "quantity" : 1,
+  "productName" : "상품01",
+  "message" : "message"
+}
+
+
+
+
+

HTTPie request

+
+
+
$ echo '{
+  "optionId" : 1,
+  "quantity" : 1,
+  "message" : "message"
+}' | http POST 'http://localhost:8080/api/products/1/order' \
+    'Content-Type:application/json' \
+    'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w'
+
+
+
+
+

Request body

+
+
+
{
+  "optionId" : 1,
+  "quantity" : 1,
+  "message" : "message"
+}
+
+
+
+
+

Request fields

+ +++++ + + + + + + + + + + + + + + + + + + + + + + + + +
PathTypeDescription

optionId

Number

상품 옵션 ID

quantity

Number

수량

message

String

메시지

+
+
+

Response body

+
+
+
{
+  "productId" : 1,
+  "optionId" : 1,
+  "optionStock" : 10,
+  "quantity" : 1,
+  "productName" : "상품01",
+  "message" : "message"
+}
+
+
+
+
+

Response fields

+ +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
PathTypeDescription

productId

Number

상품 ID

optionId

Number

상품 옵션 ID

optionStock

Number

상품 옵션 재고

quantity

Number

수량

productName

String

상품명

message

String

메시지

+
+
+
+

위시 상품 추가

+
+

Curl request

+
+
+
$ curl 'http://localhost:8080/api/products/wish' -i -X POST \
+    -H 'Content-Type: application/json' \
+    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w' \
+    -d '{
+  "productId" : 1,
+  "quantity" : 1
+}'
+
+
+
+
+

HTTP request

+
+
+
POST /api/products/wish HTTP/1.1
+Content-Type: application/json
+Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w
+Content-Length: 39
+Host: localhost:8080
+
+{
+  "productId" : 1,
+  "quantity" : 1
+}
+
+
+
+
+

HTTP response

+
+
+
HTTP/1.1 200 OK
+Content-Type: application/json
+Content-Length: 32
+
+{
+  "id" : 1,
+  "quantity" : 1
+}
+
+
+
+
+

HTTPie request

+
+
+
$ echo '{
+  "productId" : 1,
+  "quantity" : 1
+}' | http POST 'http://localhost:8080/api/products/wish' \
+    'Content-Type:application/json' \
+    'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w'
+
+
+
+
+

Request body

+
+
+
{
+  "productId" : 1,
+  "quantity" : 1
+}
+
+
+
+
+

Request fields

+ +++++ + + + + + + + + + + + + + + + + + + + +
PathTypeDescription

productId

Number

상품 ID

quantity

Number

수량

+
+
+

Response body

+
+
+
{
+  "id" : 1,
+  "quantity" : 1
+}
+
+
+
+
+

Response fields

+ +++++ + + + + + + + + + + + + + + + + + + + +
PathTypeDescription

id

Number

위시 상품 ID

quantity

Number

수량

+
+
+
+

상품 옵션 생성

+
+

Curl request

+
+
+
$ curl 'http://localhost:8080/api/products/1/options' -i -X POST \
+    -H 'Content-Type: application/json' \
+    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w' \
+    -d '{
+  "name" : "상품 옵션 01",
+  "stock" : 100
+}'
+
+
+
+
+

HTTP request

+
+
+
POST /api/products/1/options HTTP/1.1
+Content-Type: application/json
+Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w
+Content-Length: 50
+Host: localhost:8080
+
+{
+  "name" : "상품 옵션 01",
+  "stock" : 100
+}
+
+
+
+
+

HTTP response

+
+
+
HTTP/1.1 201 Created
+Location: http://localhost:8080/api/products/1/options/1
+Content-Type: application/json
+Content-Length: 62
+
+{
+  "id" : 1,
+  "name" : "상품 옵션 01",
+  "stock" : 100
+}
+
+
+
+
+

HTTPie request

+
+
+
$ echo '{
+  "name" : "상품 옵션 01",
+  "stock" : 100
+}' | http POST 'http://localhost:8080/api/products/1/options' \
+    'Content-Type:application/json' \
+    'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w'
+
+
+
+
+

Request body

+
+
+
{
+  "name" : "상품 옵션 01",
+  "stock" : 100
+}
+
+
+
+
+

Response body

+
+
+
{
+  "id" : 1,
+  "name" : "상품 옵션 01",
+  "stock" : 100
+}
+
+
+
+
+

Response fields

+ +++++ + + + + + + + + + + + + + + + + + + + + + + + + +
PathTypeDescription

id

Number

상품 옵션 ID

name

String

상품 옵션명

stock

Number

상품 옵션 재고

+
+
+
+

상품 옵션 조회

+
+

Curl request

+
+
+
$ curl 'http://localhost:8080/api/products/1/options' -i -X GET \
+    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w'
+
+
+
+
+

HTTP request

+
+
+
GET /api/products/1/options HTTP/1.1
+Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w
+Host: localhost:8080
+
+
+
+
+

HTTP response

+
+
+
HTTP/1.1 200 OK
+Content-Type: application/json
+Content-Length: 92
+
+{
+  "options" : [ {
+    "id" : 1,
+    "name" : "상품 옵션 01",
+    "stock" : 100
+  } ]
+}
+
+
+
+
+

HTTPie request

+
+
+
$ http GET 'http://localhost:8080/api/products/1/options' \
+    'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w'
+
+
+
+
+

Request body

+
+
+
+
+
+
+
+

Response body

+
+
+
{
+  "options" : [ {
+    "id" : 1,
+    "name" : "상품 옵션 01",
+    "stock" : 100
+  } ]
+}
+
+
+
+
+

Response fields

+ +++++ + + + + + + + + + + + + + + + + + + + + + + + + +
PathTypeDescription

options[].id

Number

상품 옵션 ID

options[].name

String

상품 옵션명

options[].stock

Number

상품 옵션 재고

+
+
+
+

상품 옵션 수정

+
+

Curl request

+
+
+
$ curl 'http://localhost:8080/api/products/1/options/1' -i -X PUT \
+    -H 'Content-Type: application/json' \
+    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w' \
+    -d '{
+  "name" : "상품 옵션 01",
+  "stock" : 100
+}'
+
+
+
+
+

HTTP request

+
+
+
PUT /api/products/1/options/1 HTTP/1.1
+Content-Type: application/json
+Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w
+Content-Length: 50
+Host: localhost:8080
+
+{
+  "name" : "상품 옵션 01",
+  "stock" : 100
+}
+
+
+
+
+

HTTP response

+
+
+
HTTP/1.1 200 OK
+Content-Type: application/json
+Content-Length: 62
+
+{
+  "id" : 1,
+  "name" : "상품 옵션 01",
+  "stock" : 100
+}
+
+
+
+
+

HTTPie request

+
+
+
$ echo '{
+  "name" : "상품 옵션 01",
+  "stock" : 100
+}' | http PUT 'http://localhost:8080/api/products/1/options/1' \
+    'Content-Type:application/json' \
+    'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w'
+
+
+
+
+

Request body

+
+
+
{
+  "name" : "상품 옵션 01",
+  "stock" : 100
+}
+
+
+
+
+

Request fields

+ +++++ + + + + + + + + + + + + + + + + + + + +
PathTypeDescription

name

String

상품 옵션명

stock

Number

상품 옵션 재고

+
+
+

Response body

+
+
+
{
+  "id" : 1,
+  "name" : "상품 옵션 01",
+  "stock" : 100
+}
+
+
+
+
+

Response fields

+ +++++ + + + + + + + + + + + + + + + + + + + + + + + + +
PathTypeDescription

id

Number

상품 옵션 ID

name

String

상품 옵션명

stock

Number

상품 옵션 재고

+
+
+
+

상품 옵션 삭제

+
+

Curl request

+
+
+
$ curl 'http://localhost:8080/api/products/1/options/1' -i -X DELETE \
+    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w'
+
+
+
+
+

HTTP request

+
+
+
DELETE /api/products/1/options/1 HTTP/1.1
+Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w
+Host: localhost:8080
+
+
+
+
+

HTTP response

+
+
+
HTTP/1.1 204 No Content
+
+
+
+
+

HTTPie request

+
+
+
$ http DELETE 'http://localhost:8080/api/products/1/options/1' \
+    'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w'
+
+
+
+
+

Request body

+
+
+
+
+
+
+
+

Response body

+
+
+
+
+
+
+
+
+
+
+

카테고리 API

+
+
+

카테고리 생성

+
+

Curl request

+
+
+
$ curl 'http://localhost:8080/api/categories' -i -X POST \
+    -H 'Content-Type: application/json' \
+    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTU3LCJleHAiOjE3MjIyNjg3NTd9.TJZznQ-W-PQIhpYNTuIE6Frb4r9ZAK0C5c84c8TKJCTk7fFIPupn7RH2vdijJXlsTvpwid8QzsMwLBoKKQaCaQ' \
+    -d '{
+  "name" : "카테고리01",
+  "description" : "카테고리01 설명",
+  "imageUrl" : "https://via.placeholder.com/150",
+  "color" : "#FFFFFF"
+}'
+
+
+
+
+

HTTP request

+
+
+
POST /api/categories HTTP/1.1
+Content-Type: application/json
+Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTU3LCJleHAiOjE3MjIyNjg3NTd9.TJZznQ-W-PQIhpYNTuIE6Frb4r9ZAK0C5c84c8TKJCTk7fFIPupn7RH2vdijJXlsTvpwid8QzsMwLBoKKQaCaQ
+Content-Length: 147
+Host: localhost:8080
+
+{
+  "name" : "카테고리01",
+  "description" : "카테고리01 설명",
+  "imageUrl" : "https://via.placeholder.com/150",
+  "color" : "#FFFFFF"
+}
+
+
+
+
+

HTTP response

+
+
+
HTTP/1.1 201 Created
+Location: http://localhost:8080/api/categories/1
+Content-Type: application/json
+Content-Length: 159
+
+{
+  "id" : 1,
+  "name" : "카테고리01",
+  "description" : "카테고리01 설명",
+  "imageUrl" : "https://via.placeholder.com/150",
+  "color" : "#FFFFFF"
+}
+
+
+
+
+

HTTPie request

+
+
+
$ echo '{
+  "name" : "카테고리01",
+  "description" : "카테고리01 설명",
+  "imageUrl" : "https://via.placeholder.com/150",
+  "color" : "#FFFFFF"
+}' | http POST 'http://localhost:8080/api/categories' \
+    'Content-Type:application/json' \
+    'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTU3LCJleHAiOjE3MjIyNjg3NTd9.TJZznQ-W-PQIhpYNTuIE6Frb4r9ZAK0C5c84c8TKJCTk7fFIPupn7RH2vdijJXlsTvpwid8QzsMwLBoKKQaCaQ'
+
+
+
+
+

Request body

+
+
+
{
+  "name" : "카테고리01",
+  "description" : "카테고리01 설명",
+  "imageUrl" : "https://via.placeholder.com/150",
+  "color" : "#FFFFFF"
+}
+
+
+
+
+

Request fields

+ +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
PathTypeDescription

name

String

카테고리명

description

String

카테고리 설명

imageUrl

String

이미지 URL

color

String

색상 코드

+
+
+

Response body

+
+
+
{
+  "id" : 1,
+  "name" : "카테고리01",
+  "description" : "카테고리01 설명",
+  "imageUrl" : "https://via.placeholder.com/150",
+  "color" : "#FFFFFF"
+}
+
+
+
+
+

Response fields

+ +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
PathTypeDescription

id

Number

카테고리 ID

name

String

카테고리명

description

String

카테고리 설명

imageUrl

String

이미지 URL

color

String

색상 코드

+
+
+
+

카테고리 수정

+
+

Curl request

+
+
+
$ curl 'http://localhost:8080/api/categories/1' -i -X PUT \
+    -H 'Content-Type: application/json' \
+    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTU3LCJleHAiOjE3MjIyNjg3NTd9.TJZznQ-W-PQIhpYNTuIE6Frb4r9ZAK0C5c84c8TKJCTk7fFIPupn7RH2vdijJXlsTvpwid8QzsMwLBoKKQaCaQ' \
+    -d '{
+  "name" : "카테고리01",
+  "description" : "카테고리01 설명",
+  "imageUrl" : "https://via.placeholder.com/150",
+  "color" : "#FFFFFF"
+}'
+
+
+
+
+

HTTP request

+
+
+
PUT /api/categories/1 HTTP/1.1
+Content-Type: application/json
+Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTU3LCJleHAiOjE3MjIyNjg3NTd9.TJZznQ-W-PQIhpYNTuIE6Frb4r9ZAK0C5c84c8TKJCTk7fFIPupn7RH2vdijJXlsTvpwid8QzsMwLBoKKQaCaQ
+Content-Length: 147
+Host: localhost:8080
+
+{
+  "name" : "카테고리01",
+  "description" : "카테고리01 설명",
+  "imageUrl" : "https://via.placeholder.com/150",
+  "color" : "#FFFFFF"
+}
+
+
+
+
+

HTTP response

+
+
+
HTTP/1.1 200 OK
+Content-Type: application/json
+Content-Length: 159
+
+{
+  "id" : 1,
+  "name" : "카테고리01",
+  "description" : "카테고리01 설명",
+  "imageUrl" : "https://via.placeholder.com/150",
+  "color" : "#FFFFFF"
+}
+
+
+
+
+

HTTPie request

+
+
+
$ echo '{
+  "name" : "카테고리01",
+  "description" : "카테고리01 설명",
+  "imageUrl" : "https://via.placeholder.com/150",
+  "color" : "#FFFFFF"
+}' | http PUT 'http://localhost:8080/api/categories/1' \
+    'Content-Type:application/json' \
+    'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTU3LCJleHAiOjE3MjIyNjg3NTd9.TJZznQ-W-PQIhpYNTuIE6Frb4r9ZAK0C5c84c8TKJCTk7fFIPupn7RH2vdijJXlsTvpwid8QzsMwLBoKKQaCaQ'
+
+
+
+
+

Request body

+
+
+
{
+  "name" : "카테고리01",
+  "description" : "카테고리01 설명",
+  "imageUrl" : "https://via.placeholder.com/150",
+  "color" : "#FFFFFF"
+}
+
+
+
+
+

Request fields

+ +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
PathTypeDescription

name

String

카테고리명

description

String

카테고리 설명

imageUrl

String

이미지 URL

color

String

색상 코드

+
+
+

Response body

+
+
+
{
+  "id" : 1,
+  "name" : "카테고리01",
+  "description" : "카테고리01 설명",
+  "imageUrl" : "https://via.placeholder.com/150",
+  "color" : "#FFFFFF"
+}
+
+
+
+
+

Response fields

+ +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
PathTypeDescription

id

Number

카테고리 ID

name

String

카테고리명

description

String

카테고리 설명

imageUrl

String

이미지 URL

color

String

색상 코드

+
+
+
+

카테고리 삭제

+
+

Curl request

+
+
+
$ curl 'http://localhost:8080/api/categories/1' -i -X DELETE \
+    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTU3LCJleHAiOjE3MjIyNjg3NTd9.TJZznQ-W-PQIhpYNTuIE6Frb4r9ZAK0C5c84c8TKJCTk7fFIPupn7RH2vdijJXlsTvpwid8QzsMwLBoKKQaCaQ'
+
+
+
+
+

HTTP request

+
+
+
DELETE /api/categories/1 HTTP/1.1
+Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTU3LCJleHAiOjE3MjIyNjg3NTd9.TJZznQ-W-PQIhpYNTuIE6Frb4r9ZAK0C5c84c8TKJCTk7fFIPupn7RH2vdijJXlsTvpwid8QzsMwLBoKKQaCaQ
+Host: localhost:8080
+
+
+
+
+

HTTP response

+
+
+
HTTP/1.1 204 No Content
+
+
+
+
+

HTTPie request

+
+
+
$ http DELETE 'http://localhost:8080/api/categories/1' \
+    'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTU3LCJleHAiOjE3MjIyNjg3NTd9.TJZznQ-W-PQIhpYNTuIE6Frb4r9ZAK0C5c84c8TKJCTk7fFIPupn7RH2vdijJXlsTvpwid8QzsMwLBoKKQaCaQ'
+
+
+
+
+

Request body

+
+
+
+
+
+
+
+

Response body

+
+
+
+
+
+
+
+
+

단일 카테고리 조회

+
+

Curl request

+
+
+
$ curl 'http://localhost:8080/api/categories/1' -i -X GET \
+    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTU3LCJleHAiOjE3MjIyNjg3NTd9.TJZznQ-W-PQIhpYNTuIE6Frb4r9ZAK0C5c84c8TKJCTk7fFIPupn7RH2vdijJXlsTvpwid8QzsMwLBoKKQaCaQ'
+
+
+
+
+

HTTP request

+
+
+
GET /api/categories/1 HTTP/1.1
+Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTU3LCJleHAiOjE3MjIyNjg3NTd9.TJZznQ-W-PQIhpYNTuIE6Frb4r9ZAK0C5c84c8TKJCTk7fFIPupn7RH2vdijJXlsTvpwid8QzsMwLBoKKQaCaQ
+Host: localhost:8080
+
+
+
+
+

HTTP response

+
+
+
HTTP/1.1 200 OK
+Content-Type: application/json
+Content-Length: 159
+
+{
+  "id" : 1,
+  "name" : "카테고리01",
+  "description" : "카테고리01 설명",
+  "imageUrl" : "https://via.placeholder.com/150",
+  "color" : "#FFFFFF"
+}
+
+
+
+
+

HTTPie request

+
+
+
$ http GET 'http://localhost:8080/api/categories/1' \
+    'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTU3LCJleHAiOjE3MjIyNjg3NTd9.TJZznQ-W-PQIhpYNTuIE6Frb4r9ZAK0C5c84c8TKJCTk7fFIPupn7RH2vdijJXlsTvpwid8QzsMwLBoKKQaCaQ'
+
+
+
+
+

Request body

+
+
+
+
+
+
+
+

Response body

+
+
+
{
+  "id" : 1,
+  "name" : "카테고리01",
+  "description" : "카테고리01 설명",
+  "imageUrl" : "https://via.placeholder.com/150",
+  "color" : "#FFFFFF"
+}
+
+
+
+
+

Response fields

+ +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
PathTypeDescription

id

Number

카테고리 ID

name

String

카테고리명

description

String

카테고리 설명

imageUrl

String

이미지 URL

color

String

색상 코드

+
+
+
+

전체 카테고리 조회

+
+

Curl request

+
+
+
$ curl 'http://localhost:8080/api/categories' -i -X GET \
+    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTU3LCJleHAiOjE3MjIyNjg3NTd9.TJZznQ-W-PQIhpYNTuIE6Frb4r9ZAK0C5c84c8TKJCTk7fFIPupn7RH2vdijJXlsTvpwid8QzsMwLBoKKQaCaQ'
+
+
+
+
+

HTTP request

+
+
+
GET /api/categories HTTP/1.1
+Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTU3LCJleHAiOjE3MjIyNjg3NTd9.TJZznQ-W-PQIhpYNTuIE6Frb4r9ZAK0C5c84c8TKJCTk7fFIPupn7RH2vdijJXlsTvpwid8QzsMwLBoKKQaCaQ
+Host: localhost:8080
+
+
+
+
+

HTTP response

+
+
+
HTTP/1.1 200 OK
+Content-Type: application/json
+Content-Length: 369
+
+{
+  "categories" : [ {
+    "id" : 1,
+    "name" : "카테고리01",
+    "description" : "카테고리01 설명",
+    "imageUrl" : "https://via.placeholder.com/150",
+    "color" : "#FFFFFF"
+  }, {
+    "id" : 2,
+    "name" : "카테고리02",
+    "description" : "카테고리02 설명",
+    "imageUrl" : "https://via.placeholder.com/150",
+    "color" : "#FFFFFF"
+  } ]
+}
+
+
+
+
+

HTTPie request

+
+
+
$ http GET 'http://localhost:8080/api/categories' \
+    'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTU3LCJleHAiOjE3MjIyNjg3NTd9.TJZznQ-W-PQIhpYNTuIE6Frb4r9ZAK0C5c84c8TKJCTk7fFIPupn7RH2vdijJXlsTvpwid8QzsMwLBoKKQaCaQ'
+
+
+
+
+

Query parameters

+ ++++ + + + + + + + + + + + + + + + + + + + + +
ParameterDescription

page

페이지 번호

size

페이지 크기

sort

정렬 조건

+
+
+

Request body

+
+
+
+
+
+
+
+

Response body

+
+
+
{
+  "categories" : [ {
+    "id" : 1,
+    "name" : "카테고리01",
+    "description" : "카테고리01 설명",
+    "imageUrl" : "https://via.placeholder.com/150",
+    "color" : "#FFFFFF"
+  }, {
+    "id" : 2,
+    "name" : "카테고리02",
+    "description" : "카테고리02 설명",
+    "imageUrl" : "https://via.placeholder.com/150",
+    "color" : "#FFFFFF"
+  } ]
+}
+
+
+
+
+

Response fields

+ +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
PathTypeDescription

categories[].id

Number

카테고리 ID

categories[].name

String

카테고리명

categories[].description

String

카테고리 설명

categories[].imageUrl

String

이미지 URL

categories[].color

String

색상 코드

+
+
+
+
+
+

회원 API

+
+
+

회원가입

+
+

Curl request

+
+
+
$ curl 'http://localhost:8080/api/members/register' -i -X POST \
+    -H 'Content-Type: application/json' \
+    -d '{
+  "email" : "member01@gmail.com",
+  "password" : "password01",
+  "name" : "member01"
+}'
+
+
+
+
+

HTTP request

+
+
+
POST /api/members/register HTTP/1.1
+Content-Type: application/json
+Content-Length: 88
+Host: localhost:8080
+
+{
+  "email" : "member01@gmail.com",
+  "password" : "password01",
+  "name" : "member01"
+}
+
+
+
+
+

HTTP response

+
+
+
HTTP/1.1 201 Created
+Location: http://localhost:8080/api/members/1
+Content-Type: application/json
+Content-Length: 71
+
+{
+  "id" : 1,
+  "email" : "member01@gmail.com",
+  "name" : "member01"
+}
+
+
+
+
+

HTTPie request

+
+
+
$ echo '{
+  "email" : "member01@gmail.com",
+  "password" : "password01",
+  "name" : "member01"
+}' | http POST 'http://localhost:8080/api/members/register' \
+    'Content-Type:application/json'
+
+
+
+
+

Request body

+
+
+
{
+  "email" : "member01@gmail.com",
+  "password" : "password01",
+  "name" : "member01"
+}
+
+
+
+
+

Request fields

+ +++++ + + + + + + + + + + + + + + + + + + + + + + + + +
PathTypeDescription

email

String

이메일

password

String

비밀번호

name

String

이름

+
+
+

Response body

+
+
+
{
+  "id" : 1,
+  "email" : "member01@gmail.com",
+  "name" : "member01"
+}
+
+
+
+
+

Response fields

+ +++++ + + + + + + + + + + + + + + + + + + + + + + + + +
PathTypeDescription

id

Number

회원 식별자

email

String

이메일

name

String

이름

+
+
+
+

로그인

+
+

Curl request

+
+
+
$ curl 'http://localhost:8080/api/members/login' -i -X POST \
+    -H 'Content-Type: application/json' \
+    -d '{
+  "email" : "member01@gmail.com",
+  "password" : "password01"
+}'
+
+
+
+
+

HTTP request

+
+
+
POST /api/members/login HTTP/1.1
+Content-Type: application/json
+Content-Length: 65
+Host: localhost:8080
+
+{
+  "email" : "member01@gmail.com",
+  "password" : "password01"
+}
+
+
+
+
+

HTTP response

+
+
+
HTTP/1.1 200 OK
+Content-Type: application/json
+Content-Length: 196
+
+{
+  "accessToken" : "eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTU5LCJleHAiOjE3MjIyNjg3NTl9.iuNnicN0kjQYKx7fNiJBtH_2merHROfg_1P7mjBVrFmTi9l9PLkpGOerNzS9ni8vhJ-6fx0cwdbPhYXt7zqaWw"
+}
+
+
+
+
+

HTTPie request

+
+
+
$ echo '{
+  "email" : "member01@gmail.com",
+  "password" : "password01"
+}' | http POST 'http://localhost:8080/api/members/login' \
+    'Content-Type:application/json'
+
+
+
+
+

Request body

+
+
+
{
+  "email" : "member01@gmail.com",
+  "password" : "password01"
+}
+
+
+
+
+

Request fields

+ +++++ + + + + + + + + + + + + + + + + + + + +
PathTypeDescription

email

String

이메일

password

String

비밀번호

+
+
+

Response body

+
+
+
{
+  "accessToken" : "eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTU5LCJleHAiOjE3MjIyNjg3NTl9.iuNnicN0kjQYKx7fNiJBtH_2merHROfg_1P7mjBVrFmTi9l9PLkpGOerNzS9ni8vhJ-6fx0cwdbPhYXt7zqaWw"
+}
+
+
+
+
+

Response fields

+ +++++ + + + + + + + + + + + + + + +
PathTypeDescription

accessToken

String

Bearer Token

+
+
+
+

회원 조회

+
+

Curl request

+
+
+
$ curl 'http://localhost:8080/api/members/1' -i -X GET \
+    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTU5LCJleHAiOjE3MjIyNjg3NTl9.iuNnicN0kjQYKx7fNiJBtH_2merHROfg_1P7mjBVrFmTi9l9PLkpGOerNzS9ni8vhJ-6fx0cwdbPhYXt7zqaWw'
+
+
+
+
+

HTTP request

+
+
+
GET /api/members/1 HTTP/1.1
+Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTU5LCJleHAiOjE3MjIyNjg3NTl9.iuNnicN0kjQYKx7fNiJBtH_2merHROfg_1P7mjBVrFmTi9l9PLkpGOerNzS9ni8vhJ-6fx0cwdbPhYXt7zqaWw
+Host: localhost:8080
+
+
+
+
+

HTTP response

+
+
+
HTTP/1.1 200 OK
+Content-Type: application/json
+Content-Length: 100
+
+{
+  "id" : 1,
+  "email" : "member01@gmail.com",
+  "password" : "password01",
+  "name" : "member01"
+}
+
+
+
+
+

HTTPie request

+
+
+
$ http GET 'http://localhost:8080/api/members/1' \
+    'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTU5LCJleHAiOjE3MjIyNjg3NTl9.iuNnicN0kjQYKx7fNiJBtH_2merHROfg_1P7mjBVrFmTi9l9PLkpGOerNzS9ni8vhJ-6fx0cwdbPhYXt7zqaWw'
+
+
+
+
+

Request body

+
+
+
+
+
+
+
+

Response body

+
+
+
{
+  "id" : 1,
+  "email" : "member01@gmail.com",
+  "password" : "password01",
+  "name" : "member01"
+}
+
+
+
+
+

Response fields

+ +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
PathTypeDescription

id

Number

회원 식별자

email

String

이메일

password

String

비밀번호

name

String

이름

+
+
+
+

위시 상품 조회

+
+

Curl request

+
+
+
$ curl 'http://localhost:8080/api/members/wishlist' -i -X GET \
+    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTU5LCJleHAiOjE3MjIyNjg3NTl9.iuNnicN0kjQYKx7fNiJBtH_2merHROfg_1P7mjBVrFmTi9l9PLkpGOerNzS9ni8vhJ-6fx0cwdbPhYXt7zqaWw'
+
+
+
+
+

HTTP request

+
+
+
GET /api/members/wishlist HTTP/1.1
+Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTU5LCJleHAiOjE3MjIyNjg3NTl9.iuNnicN0kjQYKx7fNiJBtH_2merHROfg_1P7mjBVrFmTi9l9PLkpGOerNzS9ni8vhJ-6fx0cwdbPhYXt7zqaWw
+Host: localhost:8080
+
+
+
+
+

HTTP response

+
+
+
HTTP/1.1 200 OK
+Content-Type: application/json
+Content-Length: 180
+
+{
+  "wishlist" : [ {
+    "id" : 1,
+    "productId" : 1,
+    "name" : "product01",
+    "price" : 1000,
+    "quantity" : 5,
+    "imageUrl" : "https://via.placeholder.com/150"
+  } ]
+}
+
+
+
+
+

HTTPie request

+
+
+
$ http GET 'http://localhost:8080/api/members/wishlist' \
+    'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTU5LCJleHAiOjE3MjIyNjg3NTl9.iuNnicN0kjQYKx7fNiJBtH_2merHROfg_1P7mjBVrFmTi9l9PLkpGOerNzS9ni8vhJ-6fx0cwdbPhYXt7zqaWw'
+
+
+
+
+

Query parameters

+ ++++ + + + + + + + + + + + + + + + + +
ParameterDescription

page

페이지 번호

size

페이지 크기

+
+
+

Request body

+
+
+
+
+
+
+
+

Response body

+
+
+
{
+  "wishlist" : [ {
+    "id" : 1,
+    "productId" : 1,
+    "name" : "product01",
+    "price" : 1000,
+    "quantity" : 5,
+    "imageUrl" : "https://via.placeholder.com/150"
+  } ]
+}
+
+
+
+
+

Response fields

+ +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
PathTypeDescription

wishlist[].id

Number

위시 상품 식별자

wishlist[].productId

Number

상품 식별자

wishlist[].name

String

상품명

wishlist[].price

Number

가격

wishlist[].quantity

Number

재고 수량

wishlist[].imageUrl

String

이미지 URL

+
+
+
+

위시 상품 수정

+
+

Curl request

+
+
+
$ curl 'http://localhost:8080/api/members/wishlist/1' -i -X PUT \
+    -H 'Content-Type: application/json' \
+    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTU5LCJleHAiOjE3MjIyNjg3NTl9.iuNnicN0kjQYKx7fNiJBtH_2merHROfg_1P7mjBVrFmTi9l9PLkpGOerNzS9ni8vhJ-6fx0cwdbPhYXt7zqaWw' \
+    -d '{
+  "quantity" : 5
+}'
+
+
+
+
+

HTTP request

+
+
+
PUT /api/members/wishlist/1 HTTP/1.1
+Content-Type: application/json
+Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTU5LCJleHAiOjE3MjIyNjg3NTl9.iuNnicN0kjQYKx7fNiJBtH_2merHROfg_1P7mjBVrFmTi9l9PLkpGOerNzS9ni8vhJ-6fx0cwdbPhYXt7zqaWw
+Content-Length: 20
+Host: localhost:8080
+
+{
+  "quantity" : 5
+}
+
+
+
+
+

HTTP response

+
+
+
HTTP/1.1 200 OK
+Content-Type: application/json
+Content-Length: 143
+
+{
+  "id" : 1,
+  "productId" : 1,
+  "name" : "product01",
+  "price" : 1000,
+  "quantity" : 5,
+  "imageUrl" : "https://via.placeholder.com/150"
+}
+
+
+
+
+

HTTPie request

+
+
+
$ echo '{
+  "quantity" : 5
+}' | http PUT 'http://localhost:8080/api/members/wishlist/1' \
+    'Content-Type:application/json' \
+    'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTU5LCJleHAiOjE3MjIyNjg3NTl9.iuNnicN0kjQYKx7fNiJBtH_2merHROfg_1P7mjBVrFmTi9l9PLkpGOerNzS9ni8vhJ-6fx0cwdbPhYXt7zqaWw'
+
+
+
+
+

Request body

+
+
+
{
+  "quantity" : 5
+}
+
+
+
+
+

Request fields

+ +++++ + + + + + + + + + + + + + + +
PathTypeDescription

quantity

Number

수량

+
+
+

Response body

+
+
+
{
+  "id" : 1,
+  "productId" : 1,
+  "name" : "product01",
+  "price" : 1000,
+  "quantity" : 5,
+  "imageUrl" : "https://via.placeholder.com/150"
+}
+
+
+
+
+

Response fields

+ +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
PathTypeDescription

id

Number

위시 상품 식별자

productId

Number

상품 식별자

name

String

상품명

price

Number

가격

quantity

Number

재고 수량

imageUrl

String

이미지 URL

+
+
+
+

위시 상품 삭제

+
+

Curl request

+
+
+
$ curl 'http://localhost:8080/api/members/wishlist/1' -i -X DELETE \
+    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTU5LCJleHAiOjE3MjIyNjg3NTl9.iuNnicN0kjQYKx7fNiJBtH_2merHROfg_1P7mjBVrFmTi9l9PLkpGOerNzS9ni8vhJ-6fx0cwdbPhYXt7zqaWw'
+
+
+
+
+

HTTP request

+
+
+
DELETE /api/members/wishlist/1 HTTP/1.1
+Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTU5LCJleHAiOjE3MjIyNjg3NTl9.iuNnicN0kjQYKx7fNiJBtH_2merHROfg_1P7mjBVrFmTi9l9PLkpGOerNzS9ni8vhJ-6fx0cwdbPhYXt7zqaWw
+Host: localhost:8080
+
+
+
+
+

HTTP response

+
+
+
HTTP/1.1 204 No Content
+
+
+
+
+

HTTPie request

+
+
+
$ http DELETE 'http://localhost:8080/api/members/wishlist/1' \
+    'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTU5LCJleHAiOjE3MjIyNjg3NTl9.iuNnicN0kjQYKx7fNiJBtH_2merHROfg_1P7mjBVrFmTi9l9PLkpGOerNzS9ni8vhJ-6fx0cwdbPhYXt7zqaWw'
+
+
+
+
+

Request body

+
+
+
+
+
+
+
+

Response body

+
+
+
+
+
+
+
+
+
+
+

소셜 로그인 API

+
+
+

카카오 로그인

+
+

Curl request

+
+
+
$ curl 'http://localhost:8080/api/login/oauth2/kakao?code=kakao-auth-code' -i -X GET
+
+
+
+
+

HTTP request

+
+
+
GET /api/login/oauth2/kakao?code=kakao-auth-code HTTP/1.1
+Host: localhost:8080
+
+
+
+
+

HTTP response

+
+
+
HTTP/1.1 200 OK
+Content-Type: application/json
+Content-Length: 47
+
+{
+  "accessToken" : "Bearer {{access_token}}"
+}
+
+
+
+
+

HTTPie request

+
+
+
$ http GET 'http://localhost:8080/api/login/oauth2/kakao?code=kakao-auth-code'
+
+
+
+
+

Query parameters

+ ++++ + + + + + + + + + + + + +
ParameterDescription

code

kakao-auth-code

+
+
+

Request body

+
+
+
+
+
+
+
+

Response body

+
+
+
{
+  "accessToken" : "Bearer {{access_token}}"
+}
+
+
+
+
+

Response fields

+ +++++ + + + + + + + + + + + + + + +
PathTypeDescription

accessToken

class java.lang.String

access-token(BEARER)

+
+
+
+
+
+ + + + + \ No newline at end of file diff --git a/src/main/resources/static/login.html b/src/main/resources/static/login.html new file mode 100644 index 000000000..df96232d6 --- /dev/null +++ b/src/main/resources/static/login.html @@ -0,0 +1,561 @@ + + + + + + + +소셜 로그인 API + + + + + +
+
+

소셜 로그인 API

+
+
+

카카오 로그인

+
+

Curl request

+
+
+
$ curl 'http://localhost:8080/api/login/oauth2/kakao?code=kakao-auth-code' -i -X GET
+
+
+
+
+

HTTP request

+
+
+
GET /api/login/oauth2/kakao?code=kakao-auth-code HTTP/1.1
+Host: localhost:8080
+
+
+
+
+

HTTP response

+
+
+
HTTP/1.1 200 OK
+Content-Type: application/json
+Content-Length: 47
+
+{
+  "accessToken" : "Bearer {{access_token}}"
+}
+
+
+
+
+

HTTPie request

+
+
+
$ http GET 'http://localhost:8080/api/login/oauth2/kakao?code=kakao-auth-code'
+
+
+
+
+

Query parameters

+ ++++ + + + + + + + + + + + + +
ParameterDescription

code

kakao-auth-code

+
+
+

Request body

+
+
+
+
+
+
+
+

Response body

+
+
+
{
+  "accessToken" : "Bearer {{access_token}}"
+}
+
+
+
+
+

Response fields

+ +++++ + + + + + + + + + + + + + + +
PathTypeDescription

accessToken

class java.lang.String

access-token(BEARER)

+
+
+
+
+
+ + + \ No newline at end of file diff --git a/src/main/resources/static/member.html b/src/main/resources/static/member.html new file mode 100644 index 000000000..54b2ad67b --- /dev/null +++ b/src/main/resources/static/member.html @@ -0,0 +1,1219 @@ + + + + + + + +회원 API + + + + + +
+
+

회원 API

+
+
+

회원가입

+
+

Curl request

+
+
+
$ curl 'http://localhost:8080/api/members/register' -i -X POST \
+    -H 'Content-Type: application/json' \
+    -d '{
+  "email" : "member01@gmail.com",
+  "password" : "password01",
+  "name" : "member01"
+}'
+
+
+
+
+

HTTP request

+
+
+
POST /api/members/register HTTP/1.1
+Content-Type: application/json
+Content-Length: 88
+Host: localhost:8080
+
+{
+  "email" : "member01@gmail.com",
+  "password" : "password01",
+  "name" : "member01"
+}
+
+
+
+
+

HTTP response

+
+
+
HTTP/1.1 201 Created
+Location: http://localhost:8080/api/members/1
+Content-Type: application/json
+Content-Length: 71
+
+{
+  "id" : 1,
+  "email" : "member01@gmail.com",
+  "name" : "member01"
+}
+
+
+
+
+

HTTPie request

+
+
+
$ echo '{
+  "email" : "member01@gmail.com",
+  "password" : "password01",
+  "name" : "member01"
+}' | http POST 'http://localhost:8080/api/members/register' \
+    'Content-Type:application/json'
+
+
+
+
+

Request body

+
+
+
{
+  "email" : "member01@gmail.com",
+  "password" : "password01",
+  "name" : "member01"
+}
+
+
+
+
+

Request fields

+ +++++ + + + + + + + + + + + + + + + + + + + + + + + + +
PathTypeDescription

email

String

이메일

password

String

비밀번호

name

String

이름

+
+
+

Response body

+
+
+
{
+  "id" : 1,
+  "email" : "member01@gmail.com",
+  "name" : "member01"
+}
+
+
+
+
+

Response fields

+ +++++ + + + + + + + + + + + + + + + + + + + + + + + + +
PathTypeDescription

id

Number

회원 식별자

email

String

이메일

name

String

이름

+
+
+
+

로그인

+
+

Curl request

+
+
+
$ curl 'http://localhost:8080/api/members/login' -i -X POST \
+    -H 'Content-Type: application/json' \
+    -d '{
+  "email" : "member01@gmail.com",
+  "password" : "password01"
+}'
+
+
+
+
+

HTTP request

+
+
+
POST /api/members/login HTTP/1.1
+Content-Type: application/json
+Content-Length: 65
+Host: localhost:8080
+
+{
+  "email" : "member01@gmail.com",
+  "password" : "password01"
+}
+
+
+
+
+

HTTP response

+
+
+
HTTP/1.1 200 OK
+Content-Type: application/json
+Content-Length: 196
+
+{
+  "accessToken" : "eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTU5LCJleHAiOjE3MjIyNjg3NTl9.iuNnicN0kjQYKx7fNiJBtH_2merHROfg_1P7mjBVrFmTi9l9PLkpGOerNzS9ni8vhJ-6fx0cwdbPhYXt7zqaWw"
+}
+
+
+
+
+

HTTPie request

+
+
+
$ echo '{
+  "email" : "member01@gmail.com",
+  "password" : "password01"
+}' | http POST 'http://localhost:8080/api/members/login' \
+    'Content-Type:application/json'
+
+
+
+
+

Request body

+
+
+
{
+  "email" : "member01@gmail.com",
+  "password" : "password01"
+}
+
+
+
+
+

Request fields

+ +++++ + + + + + + + + + + + + + + + + + + + +
PathTypeDescription

email

String

이메일

password

String

비밀번호

+
+
+

Response body

+
+
+
{
+  "accessToken" : "eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTU5LCJleHAiOjE3MjIyNjg3NTl9.iuNnicN0kjQYKx7fNiJBtH_2merHROfg_1P7mjBVrFmTi9l9PLkpGOerNzS9ni8vhJ-6fx0cwdbPhYXt7zqaWw"
+}
+
+
+
+
+

Response fields

+ +++++ + + + + + + + + + + + + + + +
PathTypeDescription

accessToken

String

Bearer Token

+
+
+
+

회원 조회

+
+

Curl request

+
+
+
$ curl 'http://localhost:8080/api/members/1' -i -X GET \
+    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTU5LCJleHAiOjE3MjIyNjg3NTl9.iuNnicN0kjQYKx7fNiJBtH_2merHROfg_1P7mjBVrFmTi9l9PLkpGOerNzS9ni8vhJ-6fx0cwdbPhYXt7zqaWw'
+
+
+
+
+

HTTP request

+
+
+
GET /api/members/1 HTTP/1.1
+Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTU5LCJleHAiOjE3MjIyNjg3NTl9.iuNnicN0kjQYKx7fNiJBtH_2merHROfg_1P7mjBVrFmTi9l9PLkpGOerNzS9ni8vhJ-6fx0cwdbPhYXt7zqaWw
+Host: localhost:8080
+
+
+
+
+

HTTP response

+
+
+
HTTP/1.1 200 OK
+Content-Type: application/json
+Content-Length: 100
+
+{
+  "id" : 1,
+  "email" : "member01@gmail.com",
+  "password" : "password01",
+  "name" : "member01"
+}
+
+
+
+
+

HTTPie request

+
+
+
$ http GET 'http://localhost:8080/api/members/1' \
+    'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTU5LCJleHAiOjE3MjIyNjg3NTl9.iuNnicN0kjQYKx7fNiJBtH_2merHROfg_1P7mjBVrFmTi9l9PLkpGOerNzS9ni8vhJ-6fx0cwdbPhYXt7zqaWw'
+
+
+
+
+

Request body

+
+
+
+
+
+
+
+

Response body

+
+
+
{
+  "id" : 1,
+  "email" : "member01@gmail.com",
+  "password" : "password01",
+  "name" : "member01"
+}
+
+
+
+
+

Response fields

+ +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
PathTypeDescription

id

Number

회원 식별자

email

String

이메일

password

String

비밀번호

name

String

이름

+
+
+
+

위시 상품 조회

+
+

Curl request

+
+
+
$ curl 'http://localhost:8080/api/members/wishlist' -i -X GET \
+    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTU5LCJleHAiOjE3MjIyNjg3NTl9.iuNnicN0kjQYKx7fNiJBtH_2merHROfg_1P7mjBVrFmTi9l9PLkpGOerNzS9ni8vhJ-6fx0cwdbPhYXt7zqaWw'
+
+
+
+
+

HTTP request

+
+
+
GET /api/members/wishlist HTTP/1.1
+Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTU5LCJleHAiOjE3MjIyNjg3NTl9.iuNnicN0kjQYKx7fNiJBtH_2merHROfg_1P7mjBVrFmTi9l9PLkpGOerNzS9ni8vhJ-6fx0cwdbPhYXt7zqaWw
+Host: localhost:8080
+
+
+
+
+

HTTP response

+
+
+
HTTP/1.1 200 OK
+Content-Type: application/json
+Content-Length: 180
+
+{
+  "wishlist" : [ {
+    "id" : 1,
+    "productId" : 1,
+    "name" : "product01",
+    "price" : 1000,
+    "quantity" : 5,
+    "imageUrl" : "https://via.placeholder.com/150"
+  } ]
+}
+
+
+
+
+

HTTPie request

+
+
+
$ http GET 'http://localhost:8080/api/members/wishlist' \
+    'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTU5LCJleHAiOjE3MjIyNjg3NTl9.iuNnicN0kjQYKx7fNiJBtH_2merHROfg_1P7mjBVrFmTi9l9PLkpGOerNzS9ni8vhJ-6fx0cwdbPhYXt7zqaWw'
+
+
+
+
+

Query parameters

+ ++++ + + + + + + + + + + + + + + + + +
ParameterDescription

page

페이지 번호

size

페이지 크기

+
+
+

Request body

+
+
+
+
+
+
+
+

Response body

+
+
+
{
+  "wishlist" : [ {
+    "id" : 1,
+    "productId" : 1,
+    "name" : "product01",
+    "price" : 1000,
+    "quantity" : 5,
+    "imageUrl" : "https://via.placeholder.com/150"
+  } ]
+}
+
+
+
+
+

Response fields

+ +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
PathTypeDescription

wishlist[].id

Number

위시 상품 식별자

wishlist[].productId

Number

상품 식별자

wishlist[].name

String

상품명

wishlist[].price

Number

가격

wishlist[].quantity

Number

재고 수량

wishlist[].imageUrl

String

이미지 URL

+
+
+
+

위시 상품 수정

+
+

Curl request

+
+
+
$ curl 'http://localhost:8080/api/members/wishlist/1' -i -X PUT \
+    -H 'Content-Type: application/json' \
+    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTU5LCJleHAiOjE3MjIyNjg3NTl9.iuNnicN0kjQYKx7fNiJBtH_2merHROfg_1P7mjBVrFmTi9l9PLkpGOerNzS9ni8vhJ-6fx0cwdbPhYXt7zqaWw' \
+    -d '{
+  "quantity" : 5
+}'
+
+
+
+
+

HTTP request

+
+
+
PUT /api/members/wishlist/1 HTTP/1.1
+Content-Type: application/json
+Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTU5LCJleHAiOjE3MjIyNjg3NTl9.iuNnicN0kjQYKx7fNiJBtH_2merHROfg_1P7mjBVrFmTi9l9PLkpGOerNzS9ni8vhJ-6fx0cwdbPhYXt7zqaWw
+Content-Length: 20
+Host: localhost:8080
+
+{
+  "quantity" : 5
+}
+
+
+
+
+

HTTP response

+
+
+
HTTP/1.1 200 OK
+Content-Type: application/json
+Content-Length: 143
+
+{
+  "id" : 1,
+  "productId" : 1,
+  "name" : "product01",
+  "price" : 1000,
+  "quantity" : 5,
+  "imageUrl" : "https://via.placeholder.com/150"
+}
+
+
+
+
+

HTTPie request

+
+
+
$ echo '{
+  "quantity" : 5
+}' | http PUT 'http://localhost:8080/api/members/wishlist/1' \
+    'Content-Type:application/json' \
+    'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTU5LCJleHAiOjE3MjIyNjg3NTl9.iuNnicN0kjQYKx7fNiJBtH_2merHROfg_1P7mjBVrFmTi9l9PLkpGOerNzS9ni8vhJ-6fx0cwdbPhYXt7zqaWw'
+
+
+
+
+

Request body

+
+
+
{
+  "quantity" : 5
+}
+
+
+
+
+

Request fields

+ +++++ + + + + + + + + + + + + + + +
PathTypeDescription

quantity

Number

수량

+
+
+

Response body

+
+
+
{
+  "id" : 1,
+  "productId" : 1,
+  "name" : "product01",
+  "price" : 1000,
+  "quantity" : 5,
+  "imageUrl" : "https://via.placeholder.com/150"
+}
+
+
+
+
+

Response fields

+ +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
PathTypeDescription

id

Number

위시 상품 식별자

productId

Number

상품 식별자

name

String

상품명

price

Number

가격

quantity

Number

재고 수량

imageUrl

String

이미지 URL

+
+
+
+

위시 상품 삭제

+
+

Curl request

+
+
+
$ curl 'http://localhost:8080/api/members/wishlist/1' -i -X DELETE \
+    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTU5LCJleHAiOjE3MjIyNjg3NTl9.iuNnicN0kjQYKx7fNiJBtH_2merHROfg_1P7mjBVrFmTi9l9PLkpGOerNzS9ni8vhJ-6fx0cwdbPhYXt7zqaWw'
+
+
+
+
+

HTTP request

+
+
+
DELETE /api/members/wishlist/1 HTTP/1.1
+Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTU5LCJleHAiOjE3MjIyNjg3NTl9.iuNnicN0kjQYKx7fNiJBtH_2merHROfg_1P7mjBVrFmTi9l9PLkpGOerNzS9ni8vhJ-6fx0cwdbPhYXt7zqaWw
+Host: localhost:8080
+
+
+
+
+

HTTP response

+
+
+
HTTP/1.1 204 No Content
+
+
+
+
+

HTTPie request

+
+
+
$ http DELETE 'http://localhost:8080/api/members/wishlist/1' \
+    'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTU5LCJleHAiOjE3MjIyNjg3NTl9.iuNnicN0kjQYKx7fNiJBtH_2merHROfg_1P7mjBVrFmTi9l9PLkpGOerNzS9ni8vhJ-6fx0cwdbPhYXt7zqaWw'
+
+
+
+
+

Request body

+
+
+
+
+
+
+
+

Response body

+
+
+
+
+
+
+
+
+
+
+ + + \ No newline at end of file diff --git a/src/main/resources/static/product.html b/src/main/resources/static/product.html new file mode 100644 index 000000000..98285e773 --- /dev/null +++ b/src/main/resources/static/product.html @@ -0,0 +1,2092 @@ + + + + + + + +상품 API + + + + + +
+
+

상품 API

+
+
+

전체 상품 조회

+
+

Curl request

+
+
+
$ curl 'http://localhost:8080/api/products' -i -X GET \
+    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w'
+
+
+
+
+

HTTP request

+
+
+
GET /api/products HTTP/1.1
+Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w
+Host: localhost:8080
+
+
+
+
+

HTTP response

+
+
+
HTTP/1.1 200 OK
+Content-Type: application/json
+Content-Length: 443
+
+{
+  "products" : [ {
+    "id" : 1,
+    "name" : "상품01",
+    "price" : 1000,
+    "imageUrl" : "https://via.placeholder.com/150",
+    "options" : [ {
+      "id" : 1,
+      "name" : "상품 옵션 01",
+      "stock" : 100
+    } ],
+    "category" : {
+      "id" : 1,
+      "name" : "카테고리01",
+      "description" : "카테고리01 입니다",
+      "imageUrl" : "https://via.placeholder.com/150",
+      "color" : "#FFFFFF"
+    }
+  } ]
+}
+
+
+
+
+

HTTPie request

+
+
+
$ http GET 'http://localhost:8080/api/products' \
+    'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w'
+
+
+
+
+

Query parameters

+ ++++ + + + + + + + + + + + + + + + + +
ParameterDescription

page

페이지 번호

size

페이지 크기

+
+
+

Request body

+
+
+
+
+
+
+
+

Response body

+
+
+
{
+  "products" : [ {
+    "id" : 1,
+    "name" : "상품01",
+    "price" : 1000,
+    "imageUrl" : "https://via.placeholder.com/150",
+    "options" : [ {
+      "id" : 1,
+      "name" : "상품 옵션 01",
+      "stock" : 100
+    } ],
+    "category" : {
+      "id" : 1,
+      "name" : "카테고리01",
+      "description" : "카테고리01 입니다",
+      "imageUrl" : "https://via.placeholder.com/150",
+      "color" : "#FFFFFF"
+    }
+  } ]
+}
+
+
+
+
+

Response fields

+ +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
PathTypeDescription

products[].id

Number

상품 ID

products[].name

String

상품명

products[].price

Number

상품 가격

products[].imageUrl

String

상품 이미지 URL

products[].options

Array

상품 옵션 목록

products[].options[].id

Number

상품 옵션 ID

products[].options[].name

String

상품 옵션명

products[].options[].stock

Number

상품 옵션 재고

products[].category.id

Number

카테고리 ID

products[].category.name

String

카테고리명

products[].category.description

String

카테고리 설명

products[].category.imageUrl

String

카테고리 이미지 URL

products[].category.color

String

카테고리 색상

+
+
+
+

단일 상품 조회

+
+

Curl request

+
+
+
$ curl 'http://localhost:8080/api/products/1' -i -X GET \
+    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w'
+
+
+
+
+

HTTP request

+
+
+
GET /api/products/1 HTTP/1.1
+Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w
+Host: localhost:8080
+
+
+
+
+

HTTP response

+
+
+
HTTP/1.1 200 OK
+Content-Type: application/json
+Content-Length: 386
+
+{
+  "id" : 1,
+  "name" : "상품01",
+  "price" : 1000,
+  "imageUrl" : "https://via.placeholder.com/150",
+  "options" : [ {
+    "id" : 1,
+    "name" : "상품 옵션 01",
+    "stock" : 100
+  } ],
+  "category" : {
+    "id" : 1,
+    "name" : "카테고리01",
+    "description" : "카테고리01 입니다",
+    "imageUrl" : "https://via.placeholder.com/150",
+    "color" : "#FFFFFF"
+  }
+}
+
+
+
+
+

HTTPie request

+
+
+
$ http GET 'http://localhost:8080/api/products/1' \
+    'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w'
+
+
+
+
+

Request body

+
+
+
+
+
+
+
+

Response body

+
+
+
{
+  "id" : 1,
+  "name" : "상품01",
+  "price" : 1000,
+  "imageUrl" : "https://via.placeholder.com/150",
+  "options" : [ {
+    "id" : 1,
+    "name" : "상품 옵션 01",
+    "stock" : 100
+  } ],
+  "category" : {
+    "id" : 1,
+    "name" : "카테고리01",
+    "description" : "카테고리01 입니다",
+    "imageUrl" : "https://via.placeholder.com/150",
+    "color" : "#FFFFFF"
+  }
+}
+
+
+
+
+

Response fields

+ +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
PathTypeDescription

id

Number

상품 ID

name

String

상품명

price

Number

상품 가격

imageUrl

String

상품 이미지 URL

options

Array

상품 옵션 목록

options[].id

Number

상품 옵션 ID

options[].name

String

상품 옵션명

options[].stock

Number

상품 옵션 재고

category.id

Number

카테고리 ID

category.name

String

카테고리명

category.description

String

카테고리 설명

category.imageUrl

String

카테고리 이미지 URL

category.color

String

카테고리 색상

+
+
+
+

상품 생성

+
+

Curl request

+
+
+
$ curl 'http://localhost:8080/api/products' -i -X POST \
+    -H 'Content-Type: application/json' \
+    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w' \
+    -d '{
+  "name" : "상품01",
+  "price" : 1000,
+  "imageUrl" : "https://via.placeholder.com/150",
+  "categoryId" : 1,
+  "productOptions" : [ {
+    "name" : "상품 옵션 01",
+    "stock" : 100
+  } ]
+}'
+
+
+
+
+

HTTP request

+
+
+
POST /api/products HTTP/1.1
+Content-Type: application/json
+Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w
+Content-Length: 196
+Host: localhost:8080
+
+{
+  "name" : "상품01",
+  "price" : 1000,
+  "imageUrl" : "https://via.placeholder.com/150",
+  "categoryId" : 1,
+  "productOptions" : [ {
+    "name" : "상품 옵션 01",
+    "stock" : 100
+  } ]
+}
+
+
+
+
+

HTTP response

+
+
+
HTTP/1.1 201 Created
+Location: http://localhost:8080/api/products/1
+Content-Type: application/json
+Content-Length: 216
+
+{
+  "id" : 1,
+  "name" : "상품01",
+  "price" : 1000,
+  "imageUrl" : "https://via.placeholder.com/150",
+  "options" : [ {
+    "id" : 1,
+    "name" : "상품 옵션 01",
+    "stock" : 100,
+    "productId" : 1
+  } ]
+}
+
+
+
+
+

HTTPie request

+
+
+
$ echo '{
+  "name" : "상품01",
+  "price" : 1000,
+  "imageUrl" : "https://via.placeholder.com/150",
+  "categoryId" : 1,
+  "productOptions" : [ {
+    "name" : "상품 옵션 01",
+    "stock" : 100
+  } ]
+}' | http POST 'http://localhost:8080/api/products' \
+    'Content-Type:application/json' \
+    'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w'
+
+
+
+
+

Request body

+
+
+
{
+  "name" : "상품01",
+  "price" : 1000,
+  "imageUrl" : "https://via.placeholder.com/150",
+  "categoryId" : 1,
+  "productOptions" : [ {
+    "name" : "상품 옵션 01",
+    "stock" : 100
+  } ]
+}
+
+
+
+
+

Request fields

+ +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
PathTypeDescription

name

String

상품명

price

Number

상품 가격

imageUrl

String

상품 이미지 URL

categoryId

Number

카테고리 ID

productOptions

Array

상품 옵션 목록

productOptions[].name

String

상품 옵션명

productOptions[].stock

Number

상품 옵션 재고

+
+
+

Response body

+
+
+
{
+  "id" : 1,
+  "name" : "상품01",
+  "price" : 1000,
+  "imageUrl" : "https://via.placeholder.com/150",
+  "options" : [ {
+    "id" : 1,
+    "name" : "상품 옵션 01",
+    "stock" : 100,
+    "productId" : 1
+  } ]
+}
+
+
+
+
+

Response fields

+ +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
PathTypeDescription

id

Number

상품 ID

name

String

상품명

price

Number

상품 가격

imageUrl

String

상품 이미지 URL

options

Array

상품 옵션 목록

options[].id

Number

상품 옵션 ID

options[].name

String

상품 옵션명

options[].stock

Number

상품 옵션 재고

options[].productId

Number

상품 옵션 상품 ID

+
+
+
+

상품 수정

+
+

Curl request

+
+
+
$ curl 'http://localhost:8080/api/products/1' -i -X PUT \
+    -H 'Content-Type: application/json' \
+    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w' \
+    -d '{
+  "name" : "상품01",
+  "price" : 1000,
+  "imageUrl" : "https://via.placeholder.com/150"
+}'
+
+
+
+
+

HTTP request

+
+
+
PUT /api/products/1 HTTP/1.1
+Content-Type: application/json
+Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w
+Content-Length: 93
+Host: localhost:8080
+
+{
+  "name" : "상품01",
+  "price" : 1000,
+  "imageUrl" : "https://via.placeholder.com/150"
+}
+
+
+
+
+

HTTP response

+
+
+
HTTP/1.1 200 OK
+Content-Type: application/json
+Content-Length: 296
+
+{
+  "id" : 1,
+  "name" : "상품01",
+  "price" : 1000,
+  "imageUrl" : "https://via.placeholder.com/150",
+  "category" : {
+    "id" : 1,
+    "name" : "카테고리01",
+    "description" : "카테고리01 입니다",
+    "imageUrl" : "https://via.placeholder.com/150",
+    "color" : "#FFFFFF"
+  }
+}
+
+
+
+
+

HTTPie request

+
+
+
$ echo '{
+  "name" : "상품01",
+  "price" : 1000,
+  "imageUrl" : "https://via.placeholder.com/150"
+}' | http PUT 'http://localhost:8080/api/products/1' \
+    'Content-Type:application/json' \
+    'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w'
+
+
+
+
+

Request body

+
+
+
{
+  "name" : "상품01",
+  "price" : 1000,
+  "imageUrl" : "https://via.placeholder.com/150"
+}
+
+
+
+
+

Request fields

+ +++++ + + + + + + + + + + + + + + + + + + + + + + + + +
PathTypeDescription

name

String

상품명

price

Number

상품 가격

imageUrl

String

상품 이미지 URL

+
+
+

Response body

+
+
+
{
+  "id" : 1,
+  "name" : "상품01",
+  "price" : 1000,
+  "imageUrl" : "https://via.placeholder.com/150",
+  "category" : {
+    "id" : 1,
+    "name" : "카테고리01",
+    "description" : "카테고리01 입니다",
+    "imageUrl" : "https://via.placeholder.com/150",
+    "color" : "#FFFFFF"
+  }
+}
+
+
+
+
+

Response fields

+ +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
PathTypeDescription

id

Number

상품 ID

name

String

상품명

price

Number

상품 가격

imageUrl

String

상품 이미지 URL

category.id

Number

카테고리 ID

category.name

String

카테고리명

category.description

String

카테고리 설명

category.imageUrl

String

카테고리 이미지 URL

category.color

String

카테고리 색상

+
+
+
+

상품 삭제

+
+

Curl request

+
+
+
$ curl 'http://localhost:8080/api/products/1' -i -X DELETE \
+    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w'
+
+
+
+
+

HTTP request

+
+
+
DELETE /api/products/1 HTTP/1.1
+Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w
+Host: localhost:8080
+
+
+
+
+

HTTP response

+
+
+
HTTP/1.1 204 No Content
+
+
+
+
+

HTTPie request

+
+
+
$ http DELETE 'http://localhost:8080/api/products/1' \
+    'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w'
+
+
+
+
+

Request body

+
+
+
+
+
+
+
+

Response body

+
+
+
+
+
+
+
+
+

상품 주문

+
+

Curl request

+
+
+
$ curl 'http://localhost:8080/api/products/1/order' -i -X POST \
+    -H 'Content-Type: application/json' \
+    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w' \
+    -d '{
+  "optionId" : 1,
+  "quantity" : 1,
+  "message" : "message"
+}'
+
+
+
+
+

HTTP request

+
+
+
POST /api/products/1/order HTTP/1.1
+Content-Type: application/json
+Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w
+Content-Length: 63
+Host: localhost:8080
+
+{
+  "optionId" : 1,
+  "quantity" : 1,
+  "message" : "message"
+}
+
+
+
+
+

HTTP response

+
+
+
HTTP/1.1 200 OK
+Content-Type: application/json
+Content-Length: 134
+
+{
+  "productId" : 1,
+  "optionId" : 1,
+  "optionStock" : 10,
+  "quantity" : 1,
+  "productName" : "상품01",
+  "message" : "message"
+}
+
+
+
+
+

HTTPie request

+
+
+
$ echo '{
+  "optionId" : 1,
+  "quantity" : 1,
+  "message" : "message"
+}' | http POST 'http://localhost:8080/api/products/1/order' \
+    'Content-Type:application/json' \
+    'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w'
+
+
+
+
+

Request body

+
+
+
{
+  "optionId" : 1,
+  "quantity" : 1,
+  "message" : "message"
+}
+
+
+
+
+

Request fields

+ +++++ + + + + + + + + + + + + + + + + + + + + + + + + +
PathTypeDescription

optionId

Number

상품 옵션 ID

quantity

Number

수량

message

String

메시지

+
+
+

Response body

+
+
+
{
+  "productId" : 1,
+  "optionId" : 1,
+  "optionStock" : 10,
+  "quantity" : 1,
+  "productName" : "상품01",
+  "message" : "message"
+}
+
+
+
+
+

Response fields

+ +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
PathTypeDescription

productId

Number

상품 ID

optionId

Number

상품 옵션 ID

optionStock

Number

상품 옵션 재고

quantity

Number

수량

productName

String

상품명

message

String

메시지

+
+
+
+

위시 상품 추가

+
+

Curl request

+
+
+
$ curl 'http://localhost:8080/api/products/wish' -i -X POST \
+    -H 'Content-Type: application/json' \
+    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w' \
+    -d '{
+  "productId" : 1,
+  "quantity" : 1
+}'
+
+
+
+
+

HTTP request

+
+
+
POST /api/products/wish HTTP/1.1
+Content-Type: application/json
+Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w
+Content-Length: 39
+Host: localhost:8080
+
+{
+  "productId" : 1,
+  "quantity" : 1
+}
+
+
+
+
+

HTTP response

+
+
+
HTTP/1.1 200 OK
+Content-Type: application/json
+Content-Length: 32
+
+{
+  "id" : 1,
+  "quantity" : 1
+}
+
+
+
+
+

HTTPie request

+
+
+
$ echo '{
+  "productId" : 1,
+  "quantity" : 1
+}' | http POST 'http://localhost:8080/api/products/wish' \
+    'Content-Type:application/json' \
+    'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w'
+
+
+
+
+

Request body

+
+
+
{
+  "productId" : 1,
+  "quantity" : 1
+}
+
+
+
+
+

Request fields

+ +++++ + + + + + + + + + + + + + + + + + + + +
PathTypeDescription

productId

Number

상품 ID

quantity

Number

수량

+
+
+

Response body

+
+
+
{
+  "id" : 1,
+  "quantity" : 1
+}
+
+
+
+
+

Response fields

+ +++++ + + + + + + + + + + + + + + + + + + + +
PathTypeDescription

id

Number

위시 상품 ID

quantity

Number

수량

+
+
+
+

상품 옵션 생성

+
+

Curl request

+
+
+
$ curl 'http://localhost:8080/api/products/1/options' -i -X POST \
+    -H 'Content-Type: application/json' \
+    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w' \
+    -d '{
+  "name" : "상품 옵션 01",
+  "stock" : 100
+}'
+
+
+
+
+

HTTP request

+
+
+
POST /api/products/1/options HTTP/1.1
+Content-Type: application/json
+Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w
+Content-Length: 50
+Host: localhost:8080
+
+{
+  "name" : "상품 옵션 01",
+  "stock" : 100
+}
+
+
+
+
+

HTTP response

+
+
+
HTTP/1.1 201 Created
+Location: http://localhost:8080/api/products/1/options/1
+Content-Type: application/json
+Content-Length: 62
+
+{
+  "id" : 1,
+  "name" : "상품 옵션 01",
+  "stock" : 100
+}
+
+
+
+
+

HTTPie request

+
+
+
$ echo '{
+  "name" : "상품 옵션 01",
+  "stock" : 100
+}' | http POST 'http://localhost:8080/api/products/1/options' \
+    'Content-Type:application/json' \
+    'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w'
+
+
+
+
+

Request body

+
+
+
{
+  "name" : "상품 옵션 01",
+  "stock" : 100
+}
+
+
+
+
+

Response body

+
+
+
{
+  "id" : 1,
+  "name" : "상품 옵션 01",
+  "stock" : 100
+}
+
+
+
+
+

Response fields

+ +++++ + + + + + + + + + + + + + + + + + + + + + + + + +
PathTypeDescription

id

Number

상품 옵션 ID

name

String

상품 옵션명

stock

Number

상품 옵션 재고

+
+
+
+

상품 옵션 조회

+
+

Curl request

+
+
+
$ curl 'http://localhost:8080/api/products/1/options' -i -X GET \
+    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w'
+
+
+
+
+

HTTP request

+
+
+
GET /api/products/1/options HTTP/1.1
+Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w
+Host: localhost:8080
+
+
+
+
+

HTTP response

+
+
+
HTTP/1.1 200 OK
+Content-Type: application/json
+Content-Length: 92
+
+{
+  "options" : [ {
+    "id" : 1,
+    "name" : "상품 옵션 01",
+    "stock" : 100
+  } ]
+}
+
+
+
+
+

HTTPie request

+
+
+
$ http GET 'http://localhost:8080/api/products/1/options' \
+    'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w'
+
+
+
+
+

Request body

+
+
+
+
+
+
+
+

Response body

+
+
+
{
+  "options" : [ {
+    "id" : 1,
+    "name" : "상품 옵션 01",
+    "stock" : 100
+  } ]
+}
+
+
+
+
+

Response fields

+ +++++ + + + + + + + + + + + + + + + + + + + + + + + + +
PathTypeDescription

options[].id

Number

상품 옵션 ID

options[].name

String

상품 옵션명

options[].stock

Number

상품 옵션 재고

+
+
+
+

상품 옵션 수정

+
+

Curl request

+
+
+
$ curl 'http://localhost:8080/api/products/1/options/1' -i -X PUT \
+    -H 'Content-Type: application/json' \
+    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w' \
+    -d '{
+  "name" : "상품 옵션 01",
+  "stock" : 100
+}'
+
+
+
+
+

HTTP request

+
+
+
PUT /api/products/1/options/1 HTTP/1.1
+Content-Type: application/json
+Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w
+Content-Length: 50
+Host: localhost:8080
+
+{
+  "name" : "상품 옵션 01",
+  "stock" : 100
+}
+
+
+
+
+

HTTP response

+
+
+
HTTP/1.1 200 OK
+Content-Type: application/json
+Content-Length: 62
+
+{
+  "id" : 1,
+  "name" : "상품 옵션 01",
+  "stock" : 100
+}
+
+
+
+
+

HTTPie request

+
+
+
$ echo '{
+  "name" : "상품 옵션 01",
+  "stock" : 100
+}' | http PUT 'http://localhost:8080/api/products/1/options/1' \
+    'Content-Type:application/json' \
+    'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w'
+
+
+
+
+

Request body

+
+
+
{
+  "name" : "상품 옵션 01",
+  "stock" : 100
+}
+
+
+
+
+

Request fields

+ +++++ + + + + + + + + + + + + + + + + + + + +
PathTypeDescription

name

String

상품 옵션명

stock

Number

상품 옵션 재고

+
+
+

Response body

+
+
+
{
+  "id" : 1,
+  "name" : "상품 옵션 01",
+  "stock" : 100
+}
+
+
+
+
+

Response fields

+ +++++ + + + + + + + + + + + + + + + + + + + + + + + + +
PathTypeDescription

id

Number

상품 옵션 ID

name

String

상품 옵션명

stock

Number

상품 옵션 재고

+
+
+
+

상품 옵션 삭제

+
+

Curl request

+
+
+
$ curl 'http://localhost:8080/api/products/1/options/1' -i -X DELETE \
+    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w'
+
+
+
+
+

HTTP request

+
+
+
DELETE /api/products/1/options/1 HTTP/1.1
+Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w
+Host: localhost:8080
+
+
+
+
+

HTTP response

+
+
+
HTTP/1.1 204 No Content
+
+
+
+
+

HTTPie request

+
+
+
$ http DELETE 'http://localhost:8080/api/products/1/options/1' \
+    'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w'
+
+
+
+
+

Request body

+
+
+
+
+
+
+
+

Response body

+
+
+
+
+
+
+
+
+
+
+ + + \ No newline at end of file From 9f5786f90443678d7e76ba1870dc94c6dcef6093 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=82=98=EC=A0=9C=EB=B2=95?= Date: Tue, 30 Jul 2024 00:14:02 +0900 Subject: [PATCH 097/138] =?UTF-8?q?fest:=20API=20=EB=AA=85=EC=84=B8=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/resources/static/category.html | 30 ++-- src/main/resources/static/docs.html | 130 +++++++++--------- src/main/resources/static/member.html | 28 ++-- src/main/resources/static/product.html | 72 +++++----- .../api/ProductApiControllerTest.java | 2 + 5 files changed, 132 insertions(+), 130 deletions(-) diff --git a/src/main/resources/static/category.html b/src/main/resources/static/category.html index ebb723429..6de14f60d 100644 --- a/src/main/resources/static/category.html +++ b/src/main/resources/static/category.html @@ -451,7 +451,7 @@

Curl request

$ curl 'http://localhost:8080/api/categories' -i -X POST \
     -H 'Content-Type: application/json' \
-    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTU3LCJleHAiOjE3MjIyNjg3NTd9.TJZznQ-W-PQIhpYNTuIE6Frb4r9ZAK0C5c84c8TKJCTk7fFIPupn7RH2vdijJXlsTvpwid8QzsMwLBoKKQaCaQ' \
+    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDEwLCJleHAiOjE3MjIyNjk2MTB9.URlH4KvMFjYZGvtIUJxxR7CmW2mZFc6-cYuy0Hj9C7En67-0nNt3EgOJ-9XWRBPhFUGvbhnbKCzao2At2lOncw' \
     -d '{
   "name" : "카테고리01",
   "description" : "카테고리01 설명",
@@ -467,7 +467,7 @@ 

HTTP request

POST /api/categories HTTP/1.1
 Content-Type: application/json
-Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTU3LCJleHAiOjE3MjIyNjg3NTd9.TJZznQ-W-PQIhpYNTuIE6Frb4r9ZAK0C5c84c8TKJCTk7fFIPupn7RH2vdijJXlsTvpwid8QzsMwLBoKKQaCaQ
+Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDEwLCJleHAiOjE3MjIyNjk2MTB9.URlH4KvMFjYZGvtIUJxxR7CmW2mZFc6-cYuy0Hj9C7En67-0nNt3EgOJ-9XWRBPhFUGvbhnbKCzao2At2lOncw
 Content-Length: 147
 Host: localhost:8080
 
@@ -510,7 +510,7 @@ 

HTTPie request

"color" : "#FFFFFF" }' | http POST 'http://localhost:8080/api/categories' \ 'Content-Type:application/json' \ - 'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTU3LCJleHAiOjE3MjIyNjg3NTd9.TJZznQ-W-PQIhpYNTuIE6Frb4r9ZAK0C5c84c8TKJCTk7fFIPupn7RH2vdijJXlsTvpwid8QzsMwLBoKKQaCaQ'
+ 'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDEwLCJleHAiOjE3MjIyNjk2MTB9.URlH4KvMFjYZGvtIUJxxR7CmW2mZFc6-cYuy0Hj9C7En67-0nNt3EgOJ-9XWRBPhFUGvbhnbKCzao2At2lOncw'
@@ -633,7 +633,7 @@

Curl request

$ curl 'http://localhost:8080/api/categories/1' -i -X PUT \
     -H 'Content-Type: application/json' \
-    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTU3LCJleHAiOjE3MjIyNjg3NTd9.TJZznQ-W-PQIhpYNTuIE6Frb4r9ZAK0C5c84c8TKJCTk7fFIPupn7RH2vdijJXlsTvpwid8QzsMwLBoKKQaCaQ' \
+    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDEwLCJleHAiOjE3MjIyNjk2MTB9.URlH4KvMFjYZGvtIUJxxR7CmW2mZFc6-cYuy0Hj9C7En67-0nNt3EgOJ-9XWRBPhFUGvbhnbKCzao2At2lOncw' \
     -d '{
   "name" : "카테고리01",
   "description" : "카테고리01 설명",
@@ -649,7 +649,7 @@ 

HTTP request

PUT /api/categories/1 HTTP/1.1
 Content-Type: application/json
-Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTU3LCJleHAiOjE3MjIyNjg3NTd9.TJZznQ-W-PQIhpYNTuIE6Frb4r9ZAK0C5c84c8TKJCTk7fFIPupn7RH2vdijJXlsTvpwid8QzsMwLBoKKQaCaQ
+Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDEwLCJleHAiOjE3MjIyNjk2MTB9.URlH4KvMFjYZGvtIUJxxR7CmW2mZFc6-cYuy0Hj9C7En67-0nNt3EgOJ-9XWRBPhFUGvbhnbKCzao2At2lOncw
 Content-Length: 147
 Host: localhost:8080
 
@@ -691,7 +691,7 @@ 

HTTPie request

"color" : "#FFFFFF" }' | http PUT 'http://localhost:8080/api/categories/1' \ 'Content-Type:application/json' \ - 'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTU3LCJleHAiOjE3MjIyNjg3NTd9.TJZznQ-W-PQIhpYNTuIE6Frb4r9ZAK0C5c84c8TKJCTk7fFIPupn7RH2vdijJXlsTvpwid8QzsMwLBoKKQaCaQ'
+ 'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDEwLCJleHAiOjE3MjIyNjk2MTB9.URlH4KvMFjYZGvtIUJxxR7CmW2mZFc6-cYuy0Hj9C7En67-0nNt3EgOJ-9XWRBPhFUGvbhnbKCzao2At2lOncw'
@@ -813,7 +813,7 @@

Curl request

$ curl 'http://localhost:8080/api/categories/1' -i -X DELETE \
-    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTU3LCJleHAiOjE3MjIyNjg3NTd9.TJZznQ-W-PQIhpYNTuIE6Frb4r9ZAK0C5c84c8TKJCTk7fFIPupn7RH2vdijJXlsTvpwid8QzsMwLBoKKQaCaQ'
+ -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDEwLCJleHAiOjE3MjIyNjk2MTB9.URlH4KvMFjYZGvtIUJxxR7CmW2mZFc6-cYuy0Hj9C7En67-0nNt3EgOJ-9XWRBPhFUGvbhnbKCzao2At2lOncw'
@@ -822,7 +822,7 @@

HTTP request

DELETE /api/categories/1 HTTP/1.1
-Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTU3LCJleHAiOjE3MjIyNjg3NTd9.TJZznQ-W-PQIhpYNTuIE6Frb4r9ZAK0C5c84c8TKJCTk7fFIPupn7RH2vdijJXlsTvpwid8QzsMwLBoKKQaCaQ
+Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDEwLCJleHAiOjE3MjIyNjk2MTB9.URlH4KvMFjYZGvtIUJxxR7CmW2mZFc6-cYuy0Hj9C7En67-0nNt3EgOJ-9XWRBPhFUGvbhnbKCzao2At2lOncw
 Host: localhost:8080
@@ -840,7 +840,7 @@

HTTPie request

$ http DELETE 'http://localhost:8080/api/categories/1' \
-    'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTU3LCJleHAiOjE3MjIyNjg3NTd9.TJZznQ-W-PQIhpYNTuIE6Frb4r9ZAK0C5c84c8TKJCTk7fFIPupn7RH2vdijJXlsTvpwid8QzsMwLBoKKQaCaQ'
+ 'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDEwLCJleHAiOjE3MjIyNjk2MTB9.URlH4KvMFjYZGvtIUJxxR7CmW2mZFc6-cYuy0Hj9C7En67-0nNt3EgOJ-9XWRBPhFUGvbhnbKCzao2At2lOncw'
@@ -868,7 +868,7 @@

Curl request

$ curl 'http://localhost:8080/api/categories/1' -i -X GET \
-    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTU3LCJleHAiOjE3MjIyNjg3NTd9.TJZznQ-W-PQIhpYNTuIE6Frb4r9ZAK0C5c84c8TKJCTk7fFIPupn7RH2vdijJXlsTvpwid8QzsMwLBoKKQaCaQ'
+ -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDEwLCJleHAiOjE3MjIyNjk2MTB9.URlH4KvMFjYZGvtIUJxxR7CmW2mZFc6-cYuy0Hj9C7En67-0nNt3EgOJ-9XWRBPhFUGvbhnbKCzao2At2lOncw'
@@ -877,7 +877,7 @@

HTTP request

GET /api/categories/1 HTTP/1.1
-Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTU3LCJleHAiOjE3MjIyNjg3NTd9.TJZznQ-W-PQIhpYNTuIE6Frb4r9ZAK0C5c84c8TKJCTk7fFIPupn7RH2vdijJXlsTvpwid8QzsMwLBoKKQaCaQ
+Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDEwLCJleHAiOjE3MjIyNjk2MTB9.URlH4KvMFjYZGvtIUJxxR7CmW2mZFc6-cYuy0Hj9C7En67-0nNt3EgOJ-9XWRBPhFUGvbhnbKCzao2At2lOncw
 Host: localhost:8080
@@ -905,7 +905,7 @@

HTTPie request

$ http GET 'http://localhost:8080/api/categories/1' \
-    'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTU3LCJleHAiOjE3MjIyNjg3NTd9.TJZznQ-W-PQIhpYNTuIE6Frb4r9ZAK0C5c84c8TKJCTk7fFIPupn7RH2vdijJXlsTvpwid8QzsMwLBoKKQaCaQ'
+ 'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDEwLCJleHAiOjE3MjIyNjk2MTB9.URlH4KvMFjYZGvtIUJxxR7CmW2mZFc6-cYuy0Hj9C7En67-0nNt3EgOJ-9XWRBPhFUGvbhnbKCzao2At2lOncw'
@@ -983,7 +983,7 @@

Curl request

$ curl 'http://localhost:8080/api/categories' -i -X GET \
-    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTU3LCJleHAiOjE3MjIyNjg3NTd9.TJZznQ-W-PQIhpYNTuIE6Frb4r9ZAK0C5c84c8TKJCTk7fFIPupn7RH2vdijJXlsTvpwid8QzsMwLBoKKQaCaQ'
+ -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDEwLCJleHAiOjE3MjIyNjk2MTB9.URlH4KvMFjYZGvtIUJxxR7CmW2mZFc6-cYuy0Hj9C7En67-0nNt3EgOJ-9XWRBPhFUGvbhnbKCzao2At2lOncw'
@@ -992,7 +992,7 @@

HTTP request

GET /api/categories HTTP/1.1
-Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTU3LCJleHAiOjE3MjIyNjg3NTd9.TJZznQ-W-PQIhpYNTuIE6Frb4r9ZAK0C5c84c8TKJCTk7fFIPupn7RH2vdijJXlsTvpwid8QzsMwLBoKKQaCaQ
+Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDEwLCJleHAiOjE3MjIyNjk2MTB9.URlH4KvMFjYZGvtIUJxxR7CmW2mZFc6-cYuy0Hj9C7En67-0nNt3EgOJ-9XWRBPhFUGvbhnbKCzao2At2lOncw
 Host: localhost:8080
@@ -1028,7 +1028,7 @@

HTTPie request

$ http GET 'http://localhost:8080/api/categories' \
-    'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTU3LCJleHAiOjE3MjIyNjg3NTd9.TJZznQ-W-PQIhpYNTuIE6Frb4r9ZAK0C5c84c8TKJCTk7fFIPupn7RH2vdijJXlsTvpwid8QzsMwLBoKKQaCaQ'
+ 'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDEwLCJleHAiOjE3MjIyNjk2MTB9.URlH4KvMFjYZGvtIUJxxR7CmW2mZFc6-cYuy0Hj9C7En67-0nNt3EgOJ-9XWRBPhFUGvbhnbKCzao2At2lOncw'
diff --git a/src/main/resources/static/docs.html b/src/main/resources/static/docs.html index c2274bc74..f1c33b491 100644 --- a/src/main/resources/static/docs.html +++ b/src/main/resources/static/docs.html @@ -738,8 +738,8 @@

Curl request

-
$ curl 'http://localhost:8080/api/products' -i -X GET \
-    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w'
+
$ curl 'http://localhost:8080/api/products?page=0&size=10' -i -X GET \
+    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDExLCJleHAiOjE3MjIyNjk2MTF9.P8FNBO9WBDAGlz6J4ccAZRed-x7mhAg4h8YVxOvveyuH5v84ZOBtqBmXS4vEBy9o0l53d1Rx8mqdH7IQVfWcww'
@@ -747,8 +747,8 @@

HTTP request

-
GET /api/products HTTP/1.1
-Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w
+
GET /api/products?page=0&size=10 HTTP/1.1
+Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDExLCJleHAiOjE3MjIyNjk2MTF9.P8FNBO9WBDAGlz6J4ccAZRed-x7mhAg4h8YVxOvveyuH5v84ZOBtqBmXS4vEBy9o0l53d1Rx8mqdH7IQVfWcww
 Host: localhost:8080
@@ -788,8 +788,8 @@

HTTPie request

-
$ http GET 'http://localhost:8080/api/products' \
-    'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w'
+
$ http GET 'http://localhost:8080/api/products?page=0&size=10' \
+    'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDExLCJleHAiOjE3MjIyNjk2MTF9.P8FNBO9WBDAGlz6J4ccAZRed-x7mhAg4h8YVxOvveyuH5v84ZOBtqBmXS4vEBy9o0l53d1Rx8mqdH7IQVfWcww'
@@ -945,7 +945,7 @@

$ curl 'http://localhost:8080/api/products/1' -i -X GET \
-    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w'
+ -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDExLCJleHAiOjE3MjIyNjk2MTF9.P8FNBO9WBDAGlz6J4ccAZRed-x7mhAg4h8YVxOvveyuH5v84ZOBtqBmXS4vEBy9o0l53d1Rx8mqdH7IQVfWcww'
@@ -954,7 +954,7 @@

GET /api/products/1 HTTP/1.1
-Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w
+Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDExLCJleHAiOjE3MjIyNjk2MTF9.P8FNBO9WBDAGlz6J4ccAZRed-x7mhAg4h8YVxOvveyuH5v84ZOBtqBmXS4vEBy9o0l53d1Rx8mqdH7IQVfWcww
 Host: localhost:8080
@@ -993,7 +993,7 @@

$ http GET 'http://localhost:8080/api/products/1' \
-    'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w'
+ 'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDExLCJleHAiOjE3MjIyNjk2MTF9.P8FNBO9WBDAGlz6J4ccAZRed-x7mhAg4h8YVxOvveyuH5v84ZOBtqBmXS4vEBy9o0l53d1Rx8mqdH7IQVfWcww'
@@ -1123,7 +1123,7 @@

$ curl 'http://localhost:8080/api/products' -i -X POST \
     -H 'Content-Type: application/json' \
-    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w' \
+    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDEyLCJleHAiOjE3MjIyNjk2MTJ9.51JkouAk3os1RvXpWPbMB15kEfjb6-N4hCgmFJDyx41jc0e2-BaohTOL7LFxElg8n1NNpO3n7S4d9hg_C90V7w' \
     -d '{
   "name" : "상품01",
   "price" : 1000,
@@ -1143,7 +1143,7 @@ 

POST /api/products HTTP/1.1
 Content-Type: application/json
-Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w
+Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDEyLCJleHAiOjE3MjIyNjk2MTJ9.51JkouAk3os1RvXpWPbMB15kEfjb6-N4hCgmFJDyx41jc0e2-BaohTOL7LFxElg8n1NNpO3n7S4d9hg_C90V7w
 Content-Length: 196
 Host: localhost:8080
 
@@ -1199,7 +1199,7 @@ 

$ curl 'http://localhost:8080/api/products/1' -i -X PUT \
     -H 'Content-Type: application/json' \
-    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w' \
+    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDExLCJleHAiOjE3MjIyNjk2MTF9.P8FNBO9WBDAGlz6J4ccAZRed-x7mhAg4h8YVxOvveyuH5v84ZOBtqBmXS4vEBy9o0l53d1Rx8mqdH7IQVfWcww' \
     -d '{
   "name" : "상품01",
   "price" : 1000,
@@ -1381,7 +1381,7 @@ 

PUT /api/products/1 HTTP/1.1
 Content-Type: application/json
-Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w
+Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDExLCJleHAiOjE3MjIyNjk2MTF9.P8FNBO9WBDAGlz6J4ccAZRed-x7mhAg4h8YVxOvveyuH5v84ZOBtqBmXS4vEBy9o0l53d1Rx8mqdH7IQVfWcww
 Content-Length: 93
 Host: localhost:8080
 
@@ -1427,7 +1427,7 @@ 

+ 'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDExLCJleHAiOjE3MjIyNjk2MTF9.P8FNBO9WBDAGlz6J4ccAZRed-x7mhAg4h8YVxOvveyuH5v84ZOBtqBmXS4vEBy9o0l53d1Rx8mqdH7IQVfWcww'

@@ -1569,7 +1569,7 @@

$ curl 'http://localhost:8080/api/products/1' -i -X DELETE \
-    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w'
+ -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDExLCJleHAiOjE3MjIyNjk2MTF9.P8FNBO9WBDAGlz6J4ccAZRed-x7mhAg4h8YVxOvveyuH5v84ZOBtqBmXS4vEBy9o0l53d1Rx8mqdH7IQVfWcww'

@@ -1578,7 +1578,7 @@

DELETE /api/products/1 HTTP/1.1
-Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w
+Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDExLCJleHAiOjE3MjIyNjk2MTF9.P8FNBO9WBDAGlz6J4ccAZRed-x7mhAg4h8YVxOvveyuH5v84ZOBtqBmXS4vEBy9o0l53d1Rx8mqdH7IQVfWcww
 Host: localhost:8080
@@ -1596,7 +1596,7 @@

$ http DELETE 'http://localhost:8080/api/products/1' \
-    'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w'
+ 'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDExLCJleHAiOjE3MjIyNjk2MTF9.P8FNBO9WBDAGlz6J4ccAZRed-x7mhAg4h8YVxOvveyuH5v84ZOBtqBmXS4vEBy9o0l53d1Rx8mqdH7IQVfWcww'

@@ -1625,7 +1625,7 @@

$ curl 'http://localhost:8080/api/products/1/order' -i -X POST \
     -H 'Content-Type: application/json' \
-    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w' \
+    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDEyLCJleHAiOjE3MjIyNjk2MTJ9.51JkouAk3os1RvXpWPbMB15kEfjb6-N4hCgmFJDyx41jc0e2-BaohTOL7LFxElg8n1NNpO3n7S4d9hg_C90V7w' \
     -d '{
   "optionId" : 1,
   "quantity" : 1,
@@ -1640,7 +1640,7 @@ 

POST /api/products/1/order HTTP/1.1
 Content-Type: application/json
-Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w
+Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDEyLCJleHAiOjE3MjIyNjk2MTJ9.51JkouAk3os1RvXpWPbMB15kEfjb6-N4hCgmFJDyx41jc0e2-BaohTOL7LFxElg8n1NNpO3n7S4d9hg_C90V7w
 Content-Length: 63
 Host: localhost:8080
 
@@ -1681,7 +1681,7 @@ 

+ 'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDEyLCJleHAiOjE3MjIyNjk2MTJ9.51JkouAk3os1RvXpWPbMB15kEfjb6-N4hCgmFJDyx41jc0e2-BaohTOL7LFxElg8n1NNpO3n7S4d9hg_C90V7w'

@@ -1804,7 +1804,7 @@

$ curl 'http://localhost:8080/api/products/wish' -i -X POST \
     -H 'Content-Type: application/json' \
-    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w' \
+    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDExLCJleHAiOjE3MjIyNjk2MTF9.P8FNBO9WBDAGlz6J4ccAZRed-x7mhAg4h8YVxOvveyuH5v84ZOBtqBmXS4vEBy9o0l53d1Rx8mqdH7IQVfWcww' \
     -d '{
   "productId" : 1,
   "quantity" : 1
@@ -1818,7 +1818,7 @@ 

POST /api/products/wish HTTP/1.1
 Content-Type: application/json
-Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w
+Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDExLCJleHAiOjE3MjIyNjk2MTF9.P8FNBO9WBDAGlz6J4ccAZRed-x7mhAg4h8YVxOvveyuH5v84ZOBtqBmXS4vEBy9o0l53d1Rx8mqdH7IQVfWcww
 Content-Length: 39
 Host: localhost:8080
 
@@ -1853,7 +1853,7 @@ 

+ 'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDExLCJleHAiOjE3MjIyNjk2MTF9.P8FNBO9WBDAGlz6J4ccAZRed-x7mhAg4h8YVxOvveyuH5v84ZOBtqBmXS4vEBy9o0l53d1Rx8mqdH7IQVfWcww'

@@ -1946,7 +1946,7 @@

$ curl 'http://localhost:8080/api/products/1/options' -i -X POST \
     -H 'Content-Type: application/json' \
-    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w' \
+    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDEyLCJleHAiOjE3MjIyNjk2MTJ9.51JkouAk3os1RvXpWPbMB15kEfjb6-N4hCgmFJDyx41jc0e2-BaohTOL7LFxElg8n1NNpO3n7S4d9hg_C90V7w' \
     -d '{
   "name" : "상품 옵션 01",
   "stock" : 100
@@ -1960,7 +1960,7 @@ 

POST /api/products/1/options HTTP/1.1
 Content-Type: application/json
-Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w
+Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDEyLCJleHAiOjE3MjIyNjk2MTJ9.51JkouAk3os1RvXpWPbMB15kEfjb6-N4hCgmFJDyx41jc0e2-BaohTOL7LFxElg8n1NNpO3n7S4d9hg_C90V7w
 Content-Length: 50
 Host: localhost:8080
 
@@ -1997,7 +1997,7 @@ 

+ 'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDEyLCJleHAiOjE3MjIyNjk2MTJ9.51JkouAk3os1RvXpWPbMB15kEfjb6-N4hCgmFJDyx41jc0e2-BaohTOL7LFxElg8n1NNpO3n7S4d9hg_C90V7w'

@@ -2066,7 +2066,7 @@

$ curl 'http://localhost:8080/api/products/1/options' -i -X GET \
-    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w'
+ -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDExLCJleHAiOjE3MjIyNjk2MTF9.P8FNBO9WBDAGlz6J4ccAZRed-x7mhAg4h8YVxOvveyuH5v84ZOBtqBmXS4vEBy9o0l53d1Rx8mqdH7IQVfWcww'
@@ -2075,7 +2075,7 @@

GET /api/products/1/options HTTP/1.1
-Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w
+Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDExLCJleHAiOjE3MjIyNjk2MTF9.P8FNBO9WBDAGlz6J4ccAZRed-x7mhAg4h8YVxOvveyuH5v84ZOBtqBmXS4vEBy9o0l53d1Rx8mqdH7IQVfWcww
 Host: localhost:8080
@@ -2103,7 +2103,7 @@

$ http GET 'http://localhost:8080/api/products/1/options' \
-    'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w'
+ 'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDExLCJleHAiOjE3MjIyNjk2MTF9.P8FNBO9WBDAGlz6J4ccAZRed-x7mhAg4h8YVxOvveyuH5v84ZOBtqBmXS4vEBy9o0l53d1Rx8mqdH7IQVfWcww'
@@ -2172,7 +2172,7 @@

$ curl 'http://localhost:8080/api/products/1/options/1' -i -X PUT \
     -H 'Content-Type: application/json' \
-    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w' \
+    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDExLCJleHAiOjE3MjIyNjk2MTF9.P8FNBO9WBDAGlz6J4ccAZRed-x7mhAg4h8YVxOvveyuH5v84ZOBtqBmXS4vEBy9o0l53d1Rx8mqdH7IQVfWcww' \
     -d '{
   "name" : "상품 옵션 01",
   "stock" : 100
@@ -2186,7 +2186,7 @@ 

PUT /api/products/1/options/1 HTTP/1.1
 Content-Type: application/json
-Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w
+Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDExLCJleHAiOjE3MjIyNjk2MTF9.P8FNBO9WBDAGlz6J4ccAZRed-x7mhAg4h8YVxOvveyuH5v84ZOBtqBmXS4vEBy9o0l53d1Rx8mqdH7IQVfWcww
 Content-Length: 50
 Host: localhost:8080
 
@@ -2222,7 +2222,7 @@ 

+ 'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDExLCJleHAiOjE3MjIyNjk2MTF9.P8FNBO9WBDAGlz6J4ccAZRed-x7mhAg4h8YVxOvveyuH5v84ZOBtqBmXS4vEBy9o0l53d1Rx8mqdH7IQVfWcww'

@@ -2320,7 +2320,7 @@

$ curl 'http://localhost:8080/api/products/1/options/1' -i -X DELETE \
-    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w'
+ -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDExLCJleHAiOjE3MjIyNjk2MTF9.P8FNBO9WBDAGlz6J4ccAZRed-x7mhAg4h8YVxOvveyuH5v84ZOBtqBmXS4vEBy9o0l53d1Rx8mqdH7IQVfWcww'
@@ -2329,7 +2329,7 @@

DELETE /api/products/1/options/1 HTTP/1.1
-Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w
+Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDExLCJleHAiOjE3MjIyNjk2MTF9.P8FNBO9WBDAGlz6J4ccAZRed-x7mhAg4h8YVxOvveyuH5v84ZOBtqBmXS4vEBy9o0l53d1Rx8mqdH7IQVfWcww
 Host: localhost:8080
@@ -2347,7 +2347,7 @@

$ http DELETE 'http://localhost:8080/api/products/1/options/1' \
-    'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w'
+ 'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDExLCJleHAiOjE3MjIyNjk2MTF9.P8FNBO9WBDAGlz6J4ccAZRed-x7mhAg4h8YVxOvveyuH5v84ZOBtqBmXS4vEBy9o0l53d1Rx8mqdH7IQVfWcww'
@@ -2381,7 +2381,7 @@

$ curl 'http://localhost:8080/api/categories' -i -X POST \
     -H 'Content-Type: application/json' \
-    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTU3LCJleHAiOjE3MjIyNjg3NTd9.TJZznQ-W-PQIhpYNTuIE6Frb4r9ZAK0C5c84c8TKJCTk7fFIPupn7RH2vdijJXlsTvpwid8QzsMwLBoKKQaCaQ' \
+    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDEwLCJleHAiOjE3MjIyNjk2MTB9.URlH4KvMFjYZGvtIUJxxR7CmW2mZFc6-cYuy0Hj9C7En67-0nNt3EgOJ-9XWRBPhFUGvbhnbKCzao2At2lOncw' \
     -d '{
   "name" : "카테고리01",
   "description" : "카테고리01 설명",
@@ -2397,7 +2397,7 @@ 

POST /api/categories HTTP/1.1
 Content-Type: application/json
-Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTU3LCJleHAiOjE3MjIyNjg3NTd9.TJZznQ-W-PQIhpYNTuIE6Frb4r9ZAK0C5c84c8TKJCTk7fFIPupn7RH2vdijJXlsTvpwid8QzsMwLBoKKQaCaQ
+Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDEwLCJleHAiOjE3MjIyNjk2MTB9.URlH4KvMFjYZGvtIUJxxR7CmW2mZFc6-cYuy0Hj9C7En67-0nNt3EgOJ-9XWRBPhFUGvbhnbKCzao2At2lOncw
 Content-Length: 147
 Host: localhost:8080
 
@@ -2440,7 +2440,7 @@ 

+ 'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDEwLCJleHAiOjE3MjIyNjk2MTB9.URlH4KvMFjYZGvtIUJxxR7CmW2mZFc6-cYuy0Hj9C7En67-0nNt3EgOJ-9XWRBPhFUGvbhnbKCzao2At2lOncw'

@@ -2563,7 +2563,7 @@

$ curl 'http://localhost:8080/api/categories/1' -i -X PUT \
     -H 'Content-Type: application/json' \
-    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTU3LCJleHAiOjE3MjIyNjg3NTd9.TJZznQ-W-PQIhpYNTuIE6Frb4r9ZAK0C5c84c8TKJCTk7fFIPupn7RH2vdijJXlsTvpwid8QzsMwLBoKKQaCaQ' \
+    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDEwLCJleHAiOjE3MjIyNjk2MTB9.URlH4KvMFjYZGvtIUJxxR7CmW2mZFc6-cYuy0Hj9C7En67-0nNt3EgOJ-9XWRBPhFUGvbhnbKCzao2At2lOncw' \
     -d '{
   "name" : "카테고리01",
   "description" : "카테고리01 설명",
@@ -2579,7 +2579,7 @@ 

PUT /api/categories/1 HTTP/1.1
 Content-Type: application/json
-Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTU3LCJleHAiOjE3MjIyNjg3NTd9.TJZznQ-W-PQIhpYNTuIE6Frb4r9ZAK0C5c84c8TKJCTk7fFIPupn7RH2vdijJXlsTvpwid8QzsMwLBoKKQaCaQ
+Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDEwLCJleHAiOjE3MjIyNjk2MTB9.URlH4KvMFjYZGvtIUJxxR7CmW2mZFc6-cYuy0Hj9C7En67-0nNt3EgOJ-9XWRBPhFUGvbhnbKCzao2At2lOncw
 Content-Length: 147
 Host: localhost:8080
 
@@ -2621,7 +2621,7 @@ 

+ 'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDEwLCJleHAiOjE3MjIyNjk2MTB9.URlH4KvMFjYZGvtIUJxxR7CmW2mZFc6-cYuy0Hj9C7En67-0nNt3EgOJ-9XWRBPhFUGvbhnbKCzao2At2lOncw'

@@ -2743,7 +2743,7 @@

$ curl 'http://localhost:8080/api/categories/1' -i -X DELETE \
-    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTU3LCJleHAiOjE3MjIyNjg3NTd9.TJZznQ-W-PQIhpYNTuIE6Frb4r9ZAK0C5c84c8TKJCTk7fFIPupn7RH2vdijJXlsTvpwid8QzsMwLBoKKQaCaQ'
+ -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDEwLCJleHAiOjE3MjIyNjk2MTB9.URlH4KvMFjYZGvtIUJxxR7CmW2mZFc6-cYuy0Hj9C7En67-0nNt3EgOJ-9XWRBPhFUGvbhnbKCzao2At2lOncw'
@@ -2752,7 +2752,7 @@

DELETE /api/categories/1 HTTP/1.1
-Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTU3LCJleHAiOjE3MjIyNjg3NTd9.TJZznQ-W-PQIhpYNTuIE6Frb4r9ZAK0C5c84c8TKJCTk7fFIPupn7RH2vdijJXlsTvpwid8QzsMwLBoKKQaCaQ
+Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDEwLCJleHAiOjE3MjIyNjk2MTB9.URlH4KvMFjYZGvtIUJxxR7CmW2mZFc6-cYuy0Hj9C7En67-0nNt3EgOJ-9XWRBPhFUGvbhnbKCzao2At2lOncw
 Host: localhost:8080
@@ -2770,7 +2770,7 @@

$ http DELETE 'http://localhost:8080/api/categories/1' \
-    'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTU3LCJleHAiOjE3MjIyNjg3NTd9.TJZznQ-W-PQIhpYNTuIE6Frb4r9ZAK0C5c84c8TKJCTk7fFIPupn7RH2vdijJXlsTvpwid8QzsMwLBoKKQaCaQ'
+ 'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDEwLCJleHAiOjE3MjIyNjk2MTB9.URlH4KvMFjYZGvtIUJxxR7CmW2mZFc6-cYuy0Hj9C7En67-0nNt3EgOJ-9XWRBPhFUGvbhnbKCzao2At2lOncw'
@@ -2798,7 +2798,7 @@

$ curl 'http://localhost:8080/api/categories/1' -i -X GET \
-    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTU3LCJleHAiOjE3MjIyNjg3NTd9.TJZznQ-W-PQIhpYNTuIE6Frb4r9ZAK0C5c84c8TKJCTk7fFIPupn7RH2vdijJXlsTvpwid8QzsMwLBoKKQaCaQ'
+ -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDEwLCJleHAiOjE3MjIyNjk2MTB9.URlH4KvMFjYZGvtIUJxxR7CmW2mZFc6-cYuy0Hj9C7En67-0nNt3EgOJ-9XWRBPhFUGvbhnbKCzao2At2lOncw'
@@ -2807,7 +2807,7 @@

GET /api/categories/1 HTTP/1.1
-Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTU3LCJleHAiOjE3MjIyNjg3NTd9.TJZznQ-W-PQIhpYNTuIE6Frb4r9ZAK0C5c84c8TKJCTk7fFIPupn7RH2vdijJXlsTvpwid8QzsMwLBoKKQaCaQ
+Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDEwLCJleHAiOjE3MjIyNjk2MTB9.URlH4KvMFjYZGvtIUJxxR7CmW2mZFc6-cYuy0Hj9C7En67-0nNt3EgOJ-9XWRBPhFUGvbhnbKCzao2At2lOncw
 Host: localhost:8080
@@ -2835,7 +2835,7 @@

$ http GET 'http://localhost:8080/api/categories/1' \
-    'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTU3LCJleHAiOjE3MjIyNjg3NTd9.TJZznQ-W-PQIhpYNTuIE6Frb4r9ZAK0C5c84c8TKJCTk7fFIPupn7RH2vdijJXlsTvpwid8QzsMwLBoKKQaCaQ'
+ 'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDEwLCJleHAiOjE3MjIyNjk2MTB9.URlH4KvMFjYZGvtIUJxxR7CmW2mZFc6-cYuy0Hj9C7En67-0nNt3EgOJ-9XWRBPhFUGvbhnbKCzao2At2lOncw'
@@ -2913,7 +2913,7 @@

$ curl 'http://localhost:8080/api/categories' -i -X GET \
-    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTU3LCJleHAiOjE3MjIyNjg3NTd9.TJZznQ-W-PQIhpYNTuIE6Frb4r9ZAK0C5c84c8TKJCTk7fFIPupn7RH2vdijJXlsTvpwid8QzsMwLBoKKQaCaQ'
+ -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDEwLCJleHAiOjE3MjIyNjk2MTB9.URlH4KvMFjYZGvtIUJxxR7CmW2mZFc6-cYuy0Hj9C7En67-0nNt3EgOJ-9XWRBPhFUGvbhnbKCzao2At2lOncw'
@@ -2922,7 +2922,7 @@

GET /api/categories HTTP/1.1
-Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTU3LCJleHAiOjE3MjIyNjg3NTd9.TJZznQ-W-PQIhpYNTuIE6Frb4r9ZAK0C5c84c8TKJCTk7fFIPupn7RH2vdijJXlsTvpwid8QzsMwLBoKKQaCaQ
+Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDEwLCJleHAiOjE3MjIyNjk2MTB9.URlH4KvMFjYZGvtIUJxxR7CmW2mZFc6-cYuy0Hj9C7En67-0nNt3EgOJ-9XWRBPhFUGvbhnbKCzao2At2lOncw
 Host: localhost:8080
@@ -2958,7 +2958,7 @@

$ http GET 'http://localhost:8080/api/categories' \
-    'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTU3LCJleHAiOjE3MjIyNjg3NTd9.TJZznQ-W-PQIhpYNTuIE6Frb4r9ZAK0C5c84c8TKJCTk7fFIPupn7RH2vdijJXlsTvpwid8QzsMwLBoKKQaCaQ'
+ 'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDEwLCJleHAiOjE3MjIyNjk2MTB9.URlH4KvMFjYZGvtIUJxxR7CmW2mZFc6-cYuy0Hj9C7En67-0nNt3EgOJ-9XWRBPhFUGvbhnbKCzao2At2lOncw'
@@ -3267,7 +3267,7 @@

@@ -3329,7 +3329,7 @@

{
-  "accessToken" : "eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTU5LCJleHAiOjE3MjIyNjg3NTl9.iuNnicN0kjQYKx7fNiJBtH_2merHROfg_1P7mjBVrFmTi9l9PLkpGOerNzS9ni8vhJ-6fx0cwdbPhYXt7zqaWw"
+  "accessToken" : "eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDExLCJleHAiOjE3MjIyNjk2MTF9.P8FNBO9WBDAGlz6J4ccAZRed-x7mhAg4h8YVxOvveyuH5v84ZOBtqBmXS4vEBy9o0l53d1Rx8mqdH7IQVfWcww"
 }
@@ -3366,7 +3366,7 @@

$ curl 'http://localhost:8080/api/members/1' -i -X GET \
-    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTU5LCJleHAiOjE3MjIyNjg3NTl9.iuNnicN0kjQYKx7fNiJBtH_2merHROfg_1P7mjBVrFmTi9l9PLkpGOerNzS9ni8vhJ-6fx0cwdbPhYXt7zqaWw'
+ -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDExLCJleHAiOjE3MjIyNjk2MTF9.P8FNBO9WBDAGlz6J4ccAZRed-x7mhAg4h8YVxOvveyuH5v84ZOBtqBmXS4vEBy9o0l53d1Rx8mqdH7IQVfWcww'
@@ -3375,7 +3375,7 @@

GET /api/members/1 HTTP/1.1
-Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTU5LCJleHAiOjE3MjIyNjg3NTl9.iuNnicN0kjQYKx7fNiJBtH_2merHROfg_1P7mjBVrFmTi9l9PLkpGOerNzS9ni8vhJ-6fx0cwdbPhYXt7zqaWw
+Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDExLCJleHAiOjE3MjIyNjk2MTF9.P8FNBO9WBDAGlz6J4ccAZRed-x7mhAg4h8YVxOvveyuH5v84ZOBtqBmXS4vEBy9o0l53d1Rx8mqdH7IQVfWcww
 Host: localhost:8080
@@ -3402,7 +3402,7 @@

$ http GET 'http://localhost:8080/api/members/1' \
-    'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTU5LCJleHAiOjE3MjIyNjg3NTl9.iuNnicN0kjQYKx7fNiJBtH_2merHROfg_1P7mjBVrFmTi9l9PLkpGOerNzS9ni8vhJ-6fx0cwdbPhYXt7zqaWw'
+ 'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDExLCJleHAiOjE3MjIyNjk2MTF9.P8FNBO9WBDAGlz6J4ccAZRed-x7mhAg4h8YVxOvveyuH5v84ZOBtqBmXS4vEBy9o0l53d1Rx8mqdH7IQVfWcww'
@@ -3474,7 +3474,7 @@

$ curl 'http://localhost:8080/api/members/wishlist' -i -X GET \
-    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTU5LCJleHAiOjE3MjIyNjg3NTl9.iuNnicN0kjQYKx7fNiJBtH_2merHROfg_1P7mjBVrFmTi9l9PLkpGOerNzS9ni8vhJ-6fx0cwdbPhYXt7zqaWw'
+ -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDExLCJleHAiOjE3MjIyNjk2MTF9.P8FNBO9WBDAGlz6J4ccAZRed-x7mhAg4h8YVxOvveyuH5v84ZOBtqBmXS4vEBy9o0l53d1Rx8mqdH7IQVfWcww'
@@ -3483,7 +3483,7 @@

GET /api/members/wishlist HTTP/1.1
-Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTU5LCJleHAiOjE3MjIyNjg3NTl9.iuNnicN0kjQYKx7fNiJBtH_2merHROfg_1P7mjBVrFmTi9l9PLkpGOerNzS9ni8vhJ-6fx0cwdbPhYXt7zqaWw
+Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDExLCJleHAiOjE3MjIyNjk2MTF9.P8FNBO9WBDAGlz6J4ccAZRed-x7mhAg4h8YVxOvveyuH5v84ZOBtqBmXS4vEBy9o0l53d1Rx8mqdH7IQVfWcww
 Host: localhost:8080
@@ -3514,7 +3514,7 @@

$ http GET 'http://localhost:8080/api/members/wishlist' \
-    'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTU5LCJleHAiOjE3MjIyNjg3NTl9.iuNnicN0kjQYKx7fNiJBtH_2merHROfg_1P7mjBVrFmTi9l9PLkpGOerNzS9ni8vhJ-6fx0cwdbPhYXt7zqaWw'
+ 'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDExLCJleHAiOjE3MjIyNjk2MTF9.P8FNBO9WBDAGlz6J4ccAZRed-x7mhAg4h8YVxOvveyuH5v84ZOBtqBmXS4vEBy9o0l53d1Rx8mqdH7IQVfWcww'
@@ -3626,7 +3626,7 @@

$ curl 'http://localhost:8080/api/members/wishlist/1' -i -X PUT \
     -H 'Content-Type: application/json' \
-    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTU5LCJleHAiOjE3MjIyNjg3NTl9.iuNnicN0kjQYKx7fNiJBtH_2merHROfg_1P7mjBVrFmTi9l9PLkpGOerNzS9ni8vhJ-6fx0cwdbPhYXt7zqaWw' \
+    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDExLCJleHAiOjE3MjIyNjk2MTF9.P8FNBO9WBDAGlz6J4ccAZRed-x7mhAg4h8YVxOvveyuH5v84ZOBtqBmXS4vEBy9o0l53d1Rx8mqdH7IQVfWcww' \
     -d '{
   "quantity" : 5
 }'
@@ -3639,7 +3639,7 @@

PUT /api/members/wishlist/1 HTTP/1.1
 Content-Type: application/json
-Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTU5LCJleHAiOjE3MjIyNjg3NTl9.iuNnicN0kjQYKx7fNiJBtH_2merHROfg_1P7mjBVrFmTi9l9PLkpGOerNzS9ni8vhJ-6fx0cwdbPhYXt7zqaWw
+Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDExLCJleHAiOjE3MjIyNjk2MTF9.P8FNBO9WBDAGlz6J4ccAZRed-x7mhAg4h8YVxOvveyuH5v84ZOBtqBmXS4vEBy9o0l53d1Rx8mqdH7IQVfWcww
 Content-Length: 20
 Host: localhost:8080
 
@@ -3676,7 +3676,7 @@ 

+ 'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDExLCJleHAiOjE3MjIyNjk2MTF9.P8FNBO9WBDAGlz6J4ccAZRed-x7mhAg4h8YVxOvveyuH5v84ZOBtqBmXS4vEBy9o0l53d1Rx8mqdH7IQVfWcww' @@ -3786,7 +3786,7 @@

$ curl 'http://localhost:8080/api/members/wishlist/1' -i -X DELETE \
-    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTU5LCJleHAiOjE3MjIyNjg3NTl9.iuNnicN0kjQYKx7fNiJBtH_2merHROfg_1P7mjBVrFmTi9l9PLkpGOerNzS9ni8vhJ-6fx0cwdbPhYXt7zqaWw'
+ -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDExLCJleHAiOjE3MjIyNjk2MTF9.P8FNBO9WBDAGlz6J4ccAZRed-x7mhAg4h8YVxOvveyuH5v84ZOBtqBmXS4vEBy9o0l53d1Rx8mqdH7IQVfWcww'
@@ -3795,7 +3795,7 @@

DELETE /api/members/wishlist/1 HTTP/1.1
-Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTU5LCJleHAiOjE3MjIyNjg3NTl9.iuNnicN0kjQYKx7fNiJBtH_2merHROfg_1P7mjBVrFmTi9l9PLkpGOerNzS9ni8vhJ-6fx0cwdbPhYXt7zqaWw
+Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDExLCJleHAiOjE3MjIyNjk2MTF9.P8FNBO9WBDAGlz6J4ccAZRed-x7mhAg4h8YVxOvveyuH5v84ZOBtqBmXS4vEBy9o0l53d1Rx8mqdH7IQVfWcww
 Host: localhost:8080
@@ -3813,7 +3813,7 @@

$ http DELETE 'http://localhost:8080/api/members/wishlist/1' \
-    'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTU5LCJleHAiOjE3MjIyNjg3NTl9.iuNnicN0kjQYKx7fNiJBtH_2merHROfg_1P7mjBVrFmTi9l9PLkpGOerNzS9ni8vhJ-6fx0cwdbPhYXt7zqaWw'
+ 'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDExLCJleHAiOjE3MjIyNjk2MTF9.P8FNBO9WBDAGlz6J4ccAZRed-x7mhAg4h8YVxOvveyuH5v84ZOBtqBmXS4vEBy9o0l53d1Rx8mqdH7IQVfWcww'
diff --git a/src/main/resources/static/member.html b/src/main/resources/static/member.html index 54b2ad67b..fc385f586 100644 --- a/src/main/resources/static/member.html +++ b/src/main/resources/static/member.html @@ -639,7 +639,7 @@

HTTP response

Content-Length: 196 { - "accessToken" : "eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTU5LCJleHAiOjE3MjIyNjg3NTl9.iuNnicN0kjQYKx7fNiJBtH_2merHROfg_1P7mjBVrFmTi9l9PLkpGOerNzS9ni8vhJ-6fx0cwdbPhYXt7zqaWw" + "accessToken" : "eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDExLCJleHAiOjE3MjIyNjk2MTF9.P8FNBO9WBDAGlz6J4ccAZRed-x7mhAg4h8YVxOvveyuH5v84ZOBtqBmXS4vEBy9o0l53d1Rx8mqdH7IQVfWcww" } @@ -701,7 +701,7 @@

Response body

{
-  "accessToken" : "eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTU5LCJleHAiOjE3MjIyNjg3NTl9.iuNnicN0kjQYKx7fNiJBtH_2merHROfg_1P7mjBVrFmTi9l9PLkpGOerNzS9ni8vhJ-6fx0cwdbPhYXt7zqaWw"
+  "accessToken" : "eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDExLCJleHAiOjE3MjIyNjk2MTF9.P8FNBO9WBDAGlz6J4ccAZRed-x7mhAg4h8YVxOvveyuH5v84ZOBtqBmXS4vEBy9o0l53d1Rx8mqdH7IQVfWcww"
 }
@@ -738,7 +738,7 @@

Curl request

$ curl 'http://localhost:8080/api/members/1' -i -X GET \
-    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTU5LCJleHAiOjE3MjIyNjg3NTl9.iuNnicN0kjQYKx7fNiJBtH_2merHROfg_1P7mjBVrFmTi9l9PLkpGOerNzS9ni8vhJ-6fx0cwdbPhYXt7zqaWw'
+ -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDExLCJleHAiOjE3MjIyNjk2MTF9.P8FNBO9WBDAGlz6J4ccAZRed-x7mhAg4h8YVxOvveyuH5v84ZOBtqBmXS4vEBy9o0l53d1Rx8mqdH7IQVfWcww'
@@ -747,7 +747,7 @@

HTTP request

GET /api/members/1 HTTP/1.1
-Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTU5LCJleHAiOjE3MjIyNjg3NTl9.iuNnicN0kjQYKx7fNiJBtH_2merHROfg_1P7mjBVrFmTi9l9PLkpGOerNzS9ni8vhJ-6fx0cwdbPhYXt7zqaWw
+Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDExLCJleHAiOjE3MjIyNjk2MTF9.P8FNBO9WBDAGlz6J4ccAZRed-x7mhAg4h8YVxOvveyuH5v84ZOBtqBmXS4vEBy9o0l53d1Rx8mqdH7IQVfWcww
 Host: localhost:8080
@@ -774,7 +774,7 @@

HTTPie request

$ http GET 'http://localhost:8080/api/members/1' \
-    'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTU5LCJleHAiOjE3MjIyNjg3NTl9.iuNnicN0kjQYKx7fNiJBtH_2merHROfg_1P7mjBVrFmTi9l9PLkpGOerNzS9ni8vhJ-6fx0cwdbPhYXt7zqaWw'
+ 'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDExLCJleHAiOjE3MjIyNjk2MTF9.P8FNBO9WBDAGlz6J4ccAZRed-x7mhAg4h8YVxOvveyuH5v84ZOBtqBmXS4vEBy9o0l53d1Rx8mqdH7IQVfWcww'
@@ -846,7 +846,7 @@

Curl request

$ curl 'http://localhost:8080/api/members/wishlist' -i -X GET \
-    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTU5LCJleHAiOjE3MjIyNjg3NTl9.iuNnicN0kjQYKx7fNiJBtH_2merHROfg_1P7mjBVrFmTi9l9PLkpGOerNzS9ni8vhJ-6fx0cwdbPhYXt7zqaWw'
+ -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDExLCJleHAiOjE3MjIyNjk2MTF9.P8FNBO9WBDAGlz6J4ccAZRed-x7mhAg4h8YVxOvveyuH5v84ZOBtqBmXS4vEBy9o0l53d1Rx8mqdH7IQVfWcww'
@@ -855,7 +855,7 @@

HTTP request

GET /api/members/wishlist HTTP/1.1
-Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTU5LCJleHAiOjE3MjIyNjg3NTl9.iuNnicN0kjQYKx7fNiJBtH_2merHROfg_1P7mjBVrFmTi9l9PLkpGOerNzS9ni8vhJ-6fx0cwdbPhYXt7zqaWw
+Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDExLCJleHAiOjE3MjIyNjk2MTF9.P8FNBO9WBDAGlz6J4ccAZRed-x7mhAg4h8YVxOvveyuH5v84ZOBtqBmXS4vEBy9o0l53d1Rx8mqdH7IQVfWcww
 Host: localhost:8080
@@ -886,7 +886,7 @@

HTTPie request

$ http GET 'http://localhost:8080/api/members/wishlist' \
-    'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTU5LCJleHAiOjE3MjIyNjg3NTl9.iuNnicN0kjQYKx7fNiJBtH_2merHROfg_1P7mjBVrFmTi9l9PLkpGOerNzS9ni8vhJ-6fx0cwdbPhYXt7zqaWw'
+ 'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDExLCJleHAiOjE3MjIyNjk2MTF9.P8FNBO9WBDAGlz6J4ccAZRed-x7mhAg4h8YVxOvveyuH5v84ZOBtqBmXS4vEBy9o0l53d1Rx8mqdH7IQVfWcww'
@@ -998,7 +998,7 @@

Curl request

$ curl 'http://localhost:8080/api/members/wishlist/1' -i -X PUT \
     -H 'Content-Type: application/json' \
-    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTU5LCJleHAiOjE3MjIyNjg3NTl9.iuNnicN0kjQYKx7fNiJBtH_2merHROfg_1P7mjBVrFmTi9l9PLkpGOerNzS9ni8vhJ-6fx0cwdbPhYXt7zqaWw' \
+    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDExLCJleHAiOjE3MjIyNjk2MTF9.P8FNBO9WBDAGlz6J4ccAZRed-x7mhAg4h8YVxOvveyuH5v84ZOBtqBmXS4vEBy9o0l53d1Rx8mqdH7IQVfWcww' \
     -d '{
   "quantity" : 5
 }'
@@ -1011,7 +1011,7 @@

HTTP request

PUT /api/members/wishlist/1 HTTP/1.1
 Content-Type: application/json
-Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTU5LCJleHAiOjE3MjIyNjg3NTl9.iuNnicN0kjQYKx7fNiJBtH_2merHROfg_1P7mjBVrFmTi9l9PLkpGOerNzS9ni8vhJ-6fx0cwdbPhYXt7zqaWw
+Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDExLCJleHAiOjE3MjIyNjk2MTF9.P8FNBO9WBDAGlz6J4ccAZRed-x7mhAg4h8YVxOvveyuH5v84ZOBtqBmXS4vEBy9o0l53d1Rx8mqdH7IQVfWcww
 Content-Length: 20
 Host: localhost:8080
 
@@ -1048,7 +1048,7 @@ 

HTTPie request

"quantity" : 5 }' | http PUT 'http://localhost:8080/api/members/wishlist/1' \ 'Content-Type:application/json' \ - 'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTU5LCJleHAiOjE3MjIyNjg3NTl9.iuNnicN0kjQYKx7fNiJBtH_2merHROfg_1P7mjBVrFmTi9l9PLkpGOerNzS9ni8vhJ-6fx0cwdbPhYXt7zqaWw'
+ 'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDExLCJleHAiOjE3MjIyNjk2MTF9.P8FNBO9WBDAGlz6J4ccAZRed-x7mhAg4h8YVxOvveyuH5v84ZOBtqBmXS4vEBy9o0l53d1Rx8mqdH7IQVfWcww'
@@ -1158,7 +1158,7 @@

Curl request

$ curl 'http://localhost:8080/api/members/wishlist/1' -i -X DELETE \
-    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTU5LCJleHAiOjE3MjIyNjg3NTl9.iuNnicN0kjQYKx7fNiJBtH_2merHROfg_1P7mjBVrFmTi9l9PLkpGOerNzS9ni8vhJ-6fx0cwdbPhYXt7zqaWw'
+ -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDExLCJleHAiOjE3MjIyNjk2MTF9.P8FNBO9WBDAGlz6J4ccAZRed-x7mhAg4h8YVxOvveyuH5v84ZOBtqBmXS4vEBy9o0l53d1Rx8mqdH7IQVfWcww'
@@ -1167,7 +1167,7 @@

HTTP request

DELETE /api/members/wishlist/1 HTTP/1.1
-Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTU5LCJleHAiOjE3MjIyNjg3NTl9.iuNnicN0kjQYKx7fNiJBtH_2merHROfg_1P7mjBVrFmTi9l9PLkpGOerNzS9ni8vhJ-6fx0cwdbPhYXt7zqaWw
+Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDExLCJleHAiOjE3MjIyNjk2MTF9.P8FNBO9WBDAGlz6J4ccAZRed-x7mhAg4h8YVxOvveyuH5v84ZOBtqBmXS4vEBy9o0l53d1Rx8mqdH7IQVfWcww
 Host: localhost:8080
@@ -1185,7 +1185,7 @@

HTTPie request

$ http DELETE 'http://localhost:8080/api/members/wishlist/1' \
-    'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTU5LCJleHAiOjE3MjIyNjg3NTl9.iuNnicN0kjQYKx7fNiJBtH_2merHROfg_1P7mjBVrFmTi9l9PLkpGOerNzS9ni8vhJ-6fx0cwdbPhYXt7zqaWw'
+ 'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDExLCJleHAiOjE3MjIyNjk2MTF9.P8FNBO9WBDAGlz6J4ccAZRed-x7mhAg4h8YVxOvveyuH5v84ZOBtqBmXS4vEBy9o0l53d1Rx8mqdH7IQVfWcww'
diff --git a/src/main/resources/static/product.html b/src/main/resources/static/product.html index 98285e773..a4e58ab2a 100644 --- a/src/main/resources/static/product.html +++ b/src/main/resources/static/product.html @@ -449,8 +449,8 @@

전체 상품 조회

Curl request

-
$ curl 'http://localhost:8080/api/products' -i -X GET \
-    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w'
+
$ curl 'http://localhost:8080/api/products?page=0&size=10' -i -X GET \
+    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDExLCJleHAiOjE3MjIyNjk2MTF9.P8FNBO9WBDAGlz6J4ccAZRed-x7mhAg4h8YVxOvveyuH5v84ZOBtqBmXS4vEBy9o0l53d1Rx8mqdH7IQVfWcww'
@@ -458,8 +458,8 @@

Curl request

HTTP request

-
GET /api/products HTTP/1.1
-Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w
+
GET /api/products?page=0&size=10 HTTP/1.1
+Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDExLCJleHAiOjE3MjIyNjk2MTF9.P8FNBO9WBDAGlz6J4ccAZRed-x7mhAg4h8YVxOvveyuH5v84ZOBtqBmXS4vEBy9o0l53d1Rx8mqdH7IQVfWcww
 Host: localhost:8080
@@ -499,8 +499,8 @@

HTTP response

HTTPie request

-
$ http GET 'http://localhost:8080/api/products' \
-    'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w'
+
$ http GET 'http://localhost:8080/api/products?page=0&size=10' \
+    'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDExLCJleHAiOjE3MjIyNjk2MTF9.P8FNBO9WBDAGlz6J4ccAZRed-x7mhAg4h8YVxOvveyuH5v84ZOBtqBmXS4vEBy9o0l53d1Rx8mqdH7IQVfWcww'
@@ -656,7 +656,7 @@

Curl request

$ curl 'http://localhost:8080/api/products/1' -i -X GET \
-    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w'
+ -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDExLCJleHAiOjE3MjIyNjk2MTF9.P8FNBO9WBDAGlz6J4ccAZRed-x7mhAg4h8YVxOvveyuH5v84ZOBtqBmXS4vEBy9o0l53d1Rx8mqdH7IQVfWcww'
@@ -665,7 +665,7 @@

HTTP request

GET /api/products/1 HTTP/1.1
-Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w
+Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDExLCJleHAiOjE3MjIyNjk2MTF9.P8FNBO9WBDAGlz6J4ccAZRed-x7mhAg4h8YVxOvveyuH5v84ZOBtqBmXS4vEBy9o0l53d1Rx8mqdH7IQVfWcww
 Host: localhost:8080
@@ -704,7 +704,7 @@

HTTPie request

$ http GET 'http://localhost:8080/api/products/1' \
-    'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w'
+ 'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDExLCJleHAiOjE3MjIyNjk2MTF9.P8FNBO9WBDAGlz6J4ccAZRed-x7mhAg4h8YVxOvveyuH5v84ZOBtqBmXS4vEBy9o0l53d1Rx8mqdH7IQVfWcww'
@@ -834,7 +834,7 @@

Curl request

$ curl 'http://localhost:8080/api/products' -i -X POST \
     -H 'Content-Type: application/json' \
-    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w' \
+    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDEyLCJleHAiOjE3MjIyNjk2MTJ9.51JkouAk3os1RvXpWPbMB15kEfjb6-N4hCgmFJDyx41jc0e2-BaohTOL7LFxElg8n1NNpO3n7S4d9hg_C90V7w' \
     -d '{
   "name" : "상품01",
   "price" : 1000,
@@ -854,7 +854,7 @@ 

HTTP request

POST /api/products HTTP/1.1
 Content-Type: application/json
-Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w
+Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDEyLCJleHAiOjE3MjIyNjk2MTJ9.51JkouAk3os1RvXpWPbMB15kEfjb6-N4hCgmFJDyx41jc0e2-BaohTOL7LFxElg8n1NNpO3n7S4d9hg_C90V7w
 Content-Length: 196
 Host: localhost:8080
 
@@ -910,7 +910,7 @@ 

HTTPie request

} ] }' | http POST 'http://localhost:8080/api/products' \ 'Content-Type:application/json' \ - 'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w'
+ 'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDEyLCJleHAiOjE3MjIyNjk2MTJ9.51JkouAk3os1RvXpWPbMB15kEfjb6-N4hCgmFJDyx41jc0e2-BaohTOL7LFxElg8n1NNpO3n7S4d9hg_C90V7w'
@@ -1077,7 +1077,7 @@

Curl request

$ curl 'http://localhost:8080/api/products/1' -i -X PUT \
     -H 'Content-Type: application/json' \
-    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w' \
+    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDExLCJleHAiOjE3MjIyNjk2MTF9.P8FNBO9WBDAGlz6J4ccAZRed-x7mhAg4h8YVxOvveyuH5v84ZOBtqBmXS4vEBy9o0l53d1Rx8mqdH7IQVfWcww' \
     -d '{
   "name" : "상품01",
   "price" : 1000,
@@ -1092,7 +1092,7 @@ 

HTTP request

PUT /api/products/1 HTTP/1.1
 Content-Type: application/json
-Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w
+Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDExLCJleHAiOjE3MjIyNjk2MTF9.P8FNBO9WBDAGlz6J4ccAZRed-x7mhAg4h8YVxOvveyuH5v84ZOBtqBmXS4vEBy9o0l53d1Rx8mqdH7IQVfWcww
 Content-Length: 93
 Host: localhost:8080
 
@@ -1138,7 +1138,7 @@ 

HTTPie request

"imageUrl" : "https://via.placeholder.com/150" }' | http PUT 'http://localhost:8080/api/products/1' \ 'Content-Type:application/json' \ - 'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w'
+ 'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDExLCJleHAiOjE3MjIyNjk2MTF9.P8FNBO9WBDAGlz6J4ccAZRed-x7mhAg4h8YVxOvveyuH5v84ZOBtqBmXS4vEBy9o0l53d1Rx8mqdH7IQVfWcww'
@@ -1280,7 +1280,7 @@

Curl request

$ curl 'http://localhost:8080/api/products/1' -i -X DELETE \
-    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w'
+ -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDExLCJleHAiOjE3MjIyNjk2MTF9.P8FNBO9WBDAGlz6J4ccAZRed-x7mhAg4h8YVxOvveyuH5v84ZOBtqBmXS4vEBy9o0l53d1Rx8mqdH7IQVfWcww'
@@ -1289,7 +1289,7 @@

HTTP request

DELETE /api/products/1 HTTP/1.1
-Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w
+Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDExLCJleHAiOjE3MjIyNjk2MTF9.P8FNBO9WBDAGlz6J4ccAZRed-x7mhAg4h8YVxOvveyuH5v84ZOBtqBmXS4vEBy9o0l53d1Rx8mqdH7IQVfWcww
 Host: localhost:8080
@@ -1307,7 +1307,7 @@

HTTPie request

$ http DELETE 'http://localhost:8080/api/products/1' \
-    'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w'
+ 'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDExLCJleHAiOjE3MjIyNjk2MTF9.P8FNBO9WBDAGlz6J4ccAZRed-x7mhAg4h8YVxOvveyuH5v84ZOBtqBmXS4vEBy9o0l53d1Rx8mqdH7IQVfWcww'
@@ -1336,7 +1336,7 @@

Curl request

$ curl 'http://localhost:8080/api/products/1/order' -i -X POST \
     -H 'Content-Type: application/json' \
-    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w' \
+    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDEyLCJleHAiOjE3MjIyNjk2MTJ9.51JkouAk3os1RvXpWPbMB15kEfjb6-N4hCgmFJDyx41jc0e2-BaohTOL7LFxElg8n1NNpO3n7S4d9hg_C90V7w' \
     -d '{
   "optionId" : 1,
   "quantity" : 1,
@@ -1351,7 +1351,7 @@ 

HTTP request

POST /api/products/1/order HTTP/1.1
 Content-Type: application/json
-Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w
+Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDEyLCJleHAiOjE3MjIyNjk2MTJ9.51JkouAk3os1RvXpWPbMB15kEfjb6-N4hCgmFJDyx41jc0e2-BaohTOL7LFxElg8n1NNpO3n7S4d9hg_C90V7w
 Content-Length: 63
 Host: localhost:8080
 
@@ -1392,7 +1392,7 @@ 

HTTPie request

"message" : "message" }' | http POST 'http://localhost:8080/api/products/1/order' \ 'Content-Type:application/json' \ - 'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w'
+ 'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDEyLCJleHAiOjE3MjIyNjk2MTJ9.51JkouAk3os1RvXpWPbMB15kEfjb6-N4hCgmFJDyx41jc0e2-BaohTOL7LFxElg8n1NNpO3n7S4d9hg_C90V7w'
@@ -1515,7 +1515,7 @@

Curl request

$ curl 'http://localhost:8080/api/products/wish' -i -X POST \
     -H 'Content-Type: application/json' \
-    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w' \
+    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDExLCJleHAiOjE3MjIyNjk2MTF9.P8FNBO9WBDAGlz6J4ccAZRed-x7mhAg4h8YVxOvveyuH5v84ZOBtqBmXS4vEBy9o0l53d1Rx8mqdH7IQVfWcww' \
     -d '{
   "productId" : 1,
   "quantity" : 1
@@ -1529,7 +1529,7 @@ 

HTTP request

POST /api/products/wish HTTP/1.1
 Content-Type: application/json
-Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w
+Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDExLCJleHAiOjE3MjIyNjk2MTF9.P8FNBO9WBDAGlz6J4ccAZRed-x7mhAg4h8YVxOvveyuH5v84ZOBtqBmXS4vEBy9o0l53d1Rx8mqdH7IQVfWcww
 Content-Length: 39
 Host: localhost:8080
 
@@ -1564,7 +1564,7 @@ 

HTTPie request

"quantity" : 1 }' | http POST 'http://localhost:8080/api/products/wish' \ 'Content-Type:application/json' \ - 'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w'
+ 'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDExLCJleHAiOjE3MjIyNjk2MTF9.P8FNBO9WBDAGlz6J4ccAZRed-x7mhAg4h8YVxOvveyuH5v84ZOBtqBmXS4vEBy9o0l53d1Rx8mqdH7IQVfWcww'
@@ -1657,7 +1657,7 @@

Curl request

$ curl 'http://localhost:8080/api/products/1/options' -i -X POST \
     -H 'Content-Type: application/json' \
-    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w' \
+    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDEyLCJleHAiOjE3MjIyNjk2MTJ9.51JkouAk3os1RvXpWPbMB15kEfjb6-N4hCgmFJDyx41jc0e2-BaohTOL7LFxElg8n1NNpO3n7S4d9hg_C90V7w' \
     -d '{
   "name" : "상품 옵션 01",
   "stock" : 100
@@ -1671,7 +1671,7 @@ 

HTTP request

POST /api/products/1/options HTTP/1.1
 Content-Type: application/json
-Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w
+Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDEyLCJleHAiOjE3MjIyNjk2MTJ9.51JkouAk3os1RvXpWPbMB15kEfjb6-N4hCgmFJDyx41jc0e2-BaohTOL7LFxElg8n1NNpO3n7S4d9hg_C90V7w
 Content-Length: 50
 Host: localhost:8080
 
@@ -1708,7 +1708,7 @@ 

HTTPie request

"stock" : 100 }' | http POST 'http://localhost:8080/api/products/1/options' \ 'Content-Type:application/json' \ - 'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w'
+ 'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDEyLCJleHAiOjE3MjIyNjk2MTJ9.51JkouAk3os1RvXpWPbMB15kEfjb6-N4hCgmFJDyx41jc0e2-BaohTOL7LFxElg8n1NNpO3n7S4d9hg_C90V7w'
@@ -1777,7 +1777,7 @@

Curl request

$ curl 'http://localhost:8080/api/products/1/options' -i -X GET \
-    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w'
+ -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDExLCJleHAiOjE3MjIyNjk2MTF9.P8FNBO9WBDAGlz6J4ccAZRed-x7mhAg4h8YVxOvveyuH5v84ZOBtqBmXS4vEBy9o0l53d1Rx8mqdH7IQVfWcww'
@@ -1786,7 +1786,7 @@

HTTP request

GET /api/products/1/options HTTP/1.1
-Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w
+Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDExLCJleHAiOjE3MjIyNjk2MTF9.P8FNBO9WBDAGlz6J4ccAZRed-x7mhAg4h8YVxOvveyuH5v84ZOBtqBmXS4vEBy9o0l53d1Rx8mqdH7IQVfWcww
 Host: localhost:8080
@@ -1814,7 +1814,7 @@

HTTPie request

$ http GET 'http://localhost:8080/api/products/1/options' \
-    'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w'
+ 'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDExLCJleHAiOjE3MjIyNjk2MTF9.P8FNBO9WBDAGlz6J4ccAZRed-x7mhAg4h8YVxOvveyuH5v84ZOBtqBmXS4vEBy9o0l53d1Rx8mqdH7IQVfWcww'
@@ -1883,7 +1883,7 @@

Curl request

$ curl 'http://localhost:8080/api/products/1/options/1' -i -X PUT \
     -H 'Content-Type: application/json' \
-    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w' \
+    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDExLCJleHAiOjE3MjIyNjk2MTF9.P8FNBO9WBDAGlz6J4ccAZRed-x7mhAg4h8YVxOvveyuH5v84ZOBtqBmXS4vEBy9o0l53d1Rx8mqdH7IQVfWcww' \
     -d '{
   "name" : "상품 옵션 01",
   "stock" : 100
@@ -1897,7 +1897,7 @@ 

HTTP request

PUT /api/products/1/options/1 HTTP/1.1
 Content-Type: application/json
-Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w
+Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDExLCJleHAiOjE3MjIyNjk2MTF9.P8FNBO9WBDAGlz6J4ccAZRed-x7mhAg4h8YVxOvveyuH5v84ZOBtqBmXS4vEBy9o0l53d1Rx8mqdH7IQVfWcww
 Content-Length: 50
 Host: localhost:8080
 
@@ -1933,7 +1933,7 @@ 

HTTPie request

"stock" : 100 }' | http PUT 'http://localhost:8080/api/products/1/options/1' \ 'Content-Type:application/json' \ - 'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w'
+ 'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDExLCJleHAiOjE3MjIyNjk2MTF9.P8FNBO9WBDAGlz6J4ccAZRed-x7mhAg4h8YVxOvveyuH5v84ZOBtqBmXS4vEBy9o0l53d1Rx8mqdH7IQVfWcww'
@@ -2031,7 +2031,7 @@

Curl request

$ curl 'http://localhost:8080/api/products/1/options/1' -i -X DELETE \
-    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w'
+ -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDExLCJleHAiOjE3MjIyNjk2MTF9.P8FNBO9WBDAGlz6J4ccAZRed-x7mhAg4h8YVxOvveyuH5v84ZOBtqBmXS4vEBy9o0l53d1Rx8mqdH7IQVfWcww'
@@ -2040,7 +2040,7 @@

HTTP request

DELETE /api/products/1/options/1 HTTP/1.1
-Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w
+Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDExLCJleHAiOjE3MjIyNjk2MTF9.P8FNBO9WBDAGlz6J4ccAZRed-x7mhAg4h8YVxOvveyuH5v84ZOBtqBmXS4vEBy9o0l53d1Rx8mqdH7IQVfWcww
 Host: localhost:8080
@@ -2058,7 +2058,7 @@

HTTPie request

$ http DELETE 'http://localhost:8080/api/products/1/options/1' \
-    'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w'
+ 'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDExLCJleHAiOjE3MjIyNjk2MTF9.P8FNBO9WBDAGlz6J4ccAZRed-x7mhAg4h8YVxOvveyuH5v84ZOBtqBmXS4vEBy9o0l53d1Rx8mqdH7IQVfWcww'
diff --git a/src/test/java/gift/web/controller/api/ProductApiControllerTest.java b/src/test/java/gift/web/controller/api/ProductApiControllerTest.java index 62f6145f1..2dcacd46f 100644 --- a/src/test/java/gift/web/controller/api/ProductApiControllerTest.java +++ b/src/test/java/gift/web/controller/api/ProductApiControllerTest.java @@ -128,6 +128,8 @@ void readAllProducts() throws Exception { .perform( get(BASE_URL) .header(HttpHeaders.AUTHORIZATION, "Bearer " + accessToken) + .param("page", "0") + .param("size", "10") ) .andExpect(status().isOk()) .andDo( From abcf464d14b61e5ee48c1d8d128126d6fcb28081 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=82=98=EC=A0=9C=EB=B2=95?= Date: Tue, 30 Jul 2024 19:11:02 +0900 Subject: [PATCH 098/138] =?UTF-8?q?refactor:=20ReadProductResponse.java=20?= =?UTF-8?q?fromEntity=20=EC=BD=94=EB=93=9C=20=EB=A6=AC=ED=8C=A9=ED=86=A0?= =?UTF-8?q?=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../gift/web/dto/response/product/ReadProductResponse.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/gift/web/dto/response/product/ReadProductResponse.java b/src/main/java/gift/web/dto/response/product/ReadProductResponse.java index ec462447d..85e1af85f 100644 --- a/src/main/java/gift/web/dto/response/product/ReadProductResponse.java +++ b/src/main/java/gift/web/dto/response/product/ReadProductResponse.java @@ -27,8 +27,8 @@ public ReadProductResponse(Long id, String name, Integer price, String imageUrl, public static ReadProductResponse fromEntity(Product product) { List productOptions = product.getProductOptions().stream() - .map(productOption -> ReadProductOptionResponse.fromEntity(productOption)) - .collect(Collectors.toList()); + .map(ReadProductOptionResponse::fromEntity) + .toList(); ReadCategoryResponse category = ReadCategoryResponse.fromEntity(product.getCategory()); From 3da64465d81f849eb0d892cb5f65b6363eadc46a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=82=98=EC=A0=9C=EB=B2=95?= Date: Wed, 31 Jul 2024 00:17:56 +0900 Subject: [PATCH 099/138] =?UTF-8?q?feat:=20Order.java=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/gift/domain/Order.java | 132 +++++++++++++++++++++++++++ 1 file changed, 132 insertions(+) create mode 100644 src/main/java/gift/domain/Order.java diff --git a/src/main/java/gift/domain/Order.java b/src/main/java/gift/domain/Order.java new file mode 100644 index 000000000..6595048f2 --- /dev/null +++ b/src/main/java/gift/domain/Order.java @@ -0,0 +1,132 @@ +package gift.domain; + +import gift.domain.base.BaseEntity; +import gift.domain.base.BaseTimeEntity; +import gift.domain.constants.OrderStatus; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.Table; +import org.hibernate.annotations.ColumnDefault; +import org.hibernate.annotations.DynamicInsert; + +@Entity +@DynamicInsert +@Table(name = "orders") +public class Order extends BaseEntity { + + @Column(nullable = false) + private Long memberId; + + @Column(nullable = false) + private Long productId; + + @Column(nullable = false) + private Long productOptionId; + + @Column(nullable = false) + private Integer quantity; + + @Column(nullable = false) + @ColumnDefault("''") + private String message; + + @Column(nullable = false) + @Enumerated(EnumType.STRING) + @ColumnDefault("'ORDERED'") + private OrderStatus orderStatus; + + protected Order() {} + + public static class Builder extends BaseTimeEntity.Builder { + + private Long memberId; + + private Long productId; + + private Long productOptionId; + + private Integer quantity; + + private String message; + + public Builder memberId(Long memberId) { + this.memberId = memberId; + return this; + } + + public Builder productId(Long productId) { + this.productId = productId; + return this; + } + + public Builder productOptionId(Long productOptionId) { + this.productOptionId = productOptionId; + return this; + } + + public Builder quantity(Integer quantity) { + this.quantity = quantity; + return this; + } + + public Builder message(String message) { + this.message = message; + return this; + } + + @Override + protected Builder self() { + return this; + } + + @Override + public Order build() { + return new Order(this); + } + } + + public Order(Builder builder) { + super(builder); + this.memberId = builder.memberId; + this.productId = builder.productId; + this.productOptionId = builder.productOptionId; + this.quantity = builder.quantity; + this.message = builder.message; + } + + public Long getMemberId() { + return memberId; + } + + public Long getProductId() { + return productId; + } + + public Long getProductOptionId() { + return productOptionId; + } + + public Integer getQuantity() { + return quantity; + } + + public String getMessage() { + return message; + } + + public OrderStatus getOrderStatus() { + return orderStatus; + } + + public Order complete() { + this.orderStatus = OrderStatus.COMPLETED; + return this; + } + + public Order cancel() { + this.orderStatus = OrderStatus.CANCELED; + return this; + } +} From 7564f1a4368f3075e80bf1e95f1667819c748784 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=82=98=EC=A0=9C=EB=B2=95?= Date: Wed, 31 Jul 2024 00:18:05 +0900 Subject: [PATCH 100/138] =?UTF-8?q?feat:=20OrderRepository.java=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/gift/repository/OrderRepository.java | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 src/main/java/gift/repository/OrderRepository.java diff --git a/src/main/java/gift/repository/OrderRepository.java b/src/main/java/gift/repository/OrderRepository.java new file mode 100644 index 000000000..d2b83b7ec --- /dev/null +++ b/src/main/java/gift/repository/OrderRepository.java @@ -0,0 +1,8 @@ +package gift.repository; + +import gift.domain.Order; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface OrderRepository extends JpaRepository { + +} From 3b5f4ff602eea9c8ce0e753aba183a88c88c980b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=82=98=EC=A0=9C=EB=B2=95?= Date: Wed, 31 Jul 2024 00:18:40 +0900 Subject: [PATCH 101/138] =?UTF-8?q?feat:=20OrderResponse.java=20=EB=B6=88?= =?UTF-8?q?=ED=95=84=EC=9A=94=ED=95=9C=20=ED=95=84=EB=93=9C=20=EC=A0=9C?= =?UTF-8?q?=EA=B1=B0=20=EB=B0=8F=20fromEntity=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../web/dto/response/order/OrderResponse.java | 23 ++++++------------- 1 file changed, 7 insertions(+), 16 deletions(-) diff --git a/src/main/java/gift/web/dto/response/order/OrderResponse.java b/src/main/java/gift/web/dto/response/order/OrderResponse.java index a718cd79b..6601555e4 100644 --- a/src/main/java/gift/web/dto/response/order/OrderResponse.java +++ b/src/main/java/gift/web/dto/response/order/OrderResponse.java @@ -1,29 +1,28 @@ package gift.web.dto.response.order; +import gift.domain.Order; + public class OrderResponse { private Long productId; private Long optionId; - private Integer optionStock; - private Integer quantity; - private String productName; - private String message; - public OrderResponse(Long productId, Long optionId, Integer optionStock, Integer quantity, - String productName, String message) { + public OrderResponse(Long productId, Long optionId, Integer quantity, String message) { this.productId = productId; this.optionId = optionId; - this.optionStock = optionStock; this.quantity = quantity; - this.productName = productName; this.message = message; } + public static OrderResponse from(Order order) { + return new OrderResponse(order.getProductId(), order.getProductOptionId(), order.getQuantity(), order.getMessage()); + } + public Long getProductId() { return productId; } @@ -32,18 +31,10 @@ public Long getOptionId() { return optionId; } - public Integer getOptionStock() { - return optionStock; - } - public Integer getQuantity() { return quantity; } - public String getProductName() { - return productName; - } - public String getMessage() { return message; } From 0ce5bb7f291b4d6b717eda0d4e4c364b204bd551 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=82=98=EC=A0=9C=EB=B2=95?= Date: Wed, 31 Jul 2024 00:18:48 +0900 Subject: [PATCH 102/138] =?UTF-8?q?feat:=20OrderStatus.java=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/gift/domain/constants/OrderStatus.java | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 src/main/java/gift/domain/constants/OrderStatus.java diff --git a/src/main/java/gift/domain/constants/OrderStatus.java b/src/main/java/gift/domain/constants/OrderStatus.java new file mode 100644 index 000000000..069537257 --- /dev/null +++ b/src/main/java/gift/domain/constants/OrderStatus.java @@ -0,0 +1,9 @@ +package gift.domain.constants; + +public enum OrderStatus { + + ORDERED, //주문됨 + CANCELED, //취소됨 + COMPLETED //배송완료 + +} From b304b28927767306b80fd24a6457835f9fbe1da1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=82=98=EC=A0=9C=EB=B2=95?= Date: Wed, 31 Jul 2024 00:19:05 +0900 Subject: [PATCH 103/138] =?UTF-8?q?feat:=20CreateOrderRequest.java=20toEnt?= =?UTF-8?q?ity()=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../web/dto/request/order/CreateOrderRequest.java | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/main/java/gift/web/dto/request/order/CreateOrderRequest.java b/src/main/java/gift/web/dto/request/order/CreateOrderRequest.java index f9d7f6d20..13e28791f 100644 --- a/src/main/java/gift/web/dto/request/order/CreateOrderRequest.java +++ b/src/main/java/gift/web/dto/request/order/CreateOrderRequest.java @@ -1,5 +1,6 @@ package gift.web.dto.request.order; +import gift.domain.Order; import jakarta.validation.constraints.Min; import jakarta.validation.constraints.NotEmpty; import jakarta.validation.constraints.NotNull; @@ -21,6 +22,16 @@ public CreateOrderRequest(Long optionId, Integer quantity, String message) { this.message = message; } + public Order toEntity(Long memberId, Long productId) { + return new Order.Builder() + .memberId(memberId) + .productId(productId) + .productOptionId(optionId) + .quantity(quantity) + .message(message) + .build(); + } + public Long getOptionId() { return optionId; } From eed7e4e4666f47c1cd59e39edf683cc14ecd9306 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=82=98=EC=A0=9C=EB=B2=95?= Date: Wed, 31 Jul 2024 00:20:58 +0900 Subject: [PATCH 104/138] =?UTF-8?q?feat:=20OrderService.java=20=EC=BD=94?= =?UTF-8?q?=EB=93=9C=20=EB=A6=AC=ED=8C=A9=ED=86=A0=EB=A7=81=20=EB=B0=8F=20?= =?UTF-8?q?=EC=A3=BC=EB=AC=B8=20=EC=A0=95=EB=B3=B4=20=EC=A0=80=EC=9E=A5=20?= =?UTF-8?q?=EB=A1=9C=EC=A7=81=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/gift/service/OrderService.java | 58 ++++++++++++-------- 1 file changed, 36 insertions(+), 22 deletions(-) diff --git a/src/main/java/gift/service/OrderService.java b/src/main/java/gift/service/OrderService.java index ddd967b1f..48705a18b 100644 --- a/src/main/java/gift/service/OrderService.java +++ b/src/main/java/gift/service/OrderService.java @@ -3,12 +3,13 @@ import gift.authentication.token.JwtResolver; import gift.authentication.token.Token; import gift.config.KakaoProperties; +import gift.domain.Order; +import gift.repository.OrderRepository; import gift.web.client.KakaoClient; import gift.web.client.dto.KakaoCommerce; import gift.web.dto.request.order.CreateOrderRequest; import gift.web.dto.response.order.OrderResponse; import gift.web.dto.response.product.ReadProductResponse; -import gift.web.dto.response.productoption.SubtractProductOptionQuantityResponse; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -17,52 +18,65 @@ public class OrderService { private final KakaoClient kakaoClient; + private final KakaoProperties kakaoProperties; + private final JwtResolver jwtResolver; + private final ProductOptionService productOptionService; private final ProductService productService; - private final KakaoProperties kakaoProperties; + private final OrderRepository orderRepository; public OrderService(KakaoClient kakaoClient, JwtResolver jwtResolver, ProductOptionService productOptionService, - ProductService productService, KakaoProperties kakaoProperties) { + ProductService productService, KakaoProperties kakaoProperties, + OrderRepository orderRepository) { this.kakaoClient = kakaoClient; this.jwtResolver = jwtResolver; this.productOptionService = productOptionService; this.productService = productService; this.kakaoProperties = kakaoProperties; + this.orderRepository = orderRepository; } + /** + * 주문을 생성합니다
+ * 카카오 로그인을 통해 서비스를 이용 중인 회원은 나에게 보내기를 통해 알림을 전송합니다. + * @param accessToken 우리 서비스의 토큰 + * @param productId 구매할 상품 ID + * @param memberId 구매자 ID + * @param request 주문 요청 + * @return + */ @Transactional - public OrderResponse createOrder(String accessToken, Long productId, CreateOrderRequest request) { + public OrderResponse createOrder(String accessToken, Long productId, Long memberId, CreateOrderRequest request) { //상품 옵션 수량 차감 - SubtractProductOptionQuantityResponse subtractOptionStockResponse = productOptionService.subtractOptionStock(request); + productOptionService.subtractOptionStock(request); - ReadProductResponse product = productService.readProductById(productId); - KakaoCommerce kakaoCommerce = KakaoCommerce.of(product, request.getMessage()); + //주문 정보 저장 + Order order = orderRepository.save(request.toEntity(memberId, productId)); - sendOrderMessageIfSocialMember(accessToken, kakaoCommerce); - return new OrderResponse( - productId, - request.getOptionId(), - subtractOptionStockResponse.getStock(), - request.getQuantity(), - product.getName(), - request.getMessage()); + sendOrderMessageIfSocialMember(accessToken, productId, request); + return OrderResponse.from(order); } /** * 소셜 로그인을 통해 주문한 경우 카카오톡 메시지를 전송합니다 - * @param accessToken Bearer Token - * @param kakaoCommerce 카카오 상거래 메시지 + * @param accessToken 우리 서비스의 토큰 + * @param productId 상품 ID + * @param request 주문 요청 */ - private void sendOrderMessageIfSocialMember(String accessToken, KakaoCommerce kakaoCommerce) { + private void sendOrderMessageIfSocialMember(String accessToken, Long productId, CreateOrderRequest request) { jwtResolver.resolveSocialToken(Token.fromBearer(accessToken)) - .ifPresent(socialToken -> { - String json = kakaoCommerce.toJson(); + .ifPresent(socialToken -> kakaoClient.sendMessage( kakaoProperties.getMessageUrlAsUri(), getBearerToken(socialToken), - json); - }); + generateKakaoCommerce(productId, request).toJson() + )); + } + + private KakaoCommerce generateKakaoCommerce(Long productId, CreateOrderRequest request) { + ReadProductResponse productResponse = productService.readProductById(productId); + return KakaoCommerce.of(productResponse, request.getMessage()); } private String getBearerToken(String token) { From 9c32ba41163cdd06ddaff68cd6f117212d01cd32 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=82=98=EC=A0=9C=EB=B2=95?= Date: Wed, 31 Jul 2024 00:21:29 +0900 Subject: [PATCH 105/138] =?UTF-8?q?feat:=20ProductApiController.java=20?= =?UTF-8?q?=EC=A3=BC=EB=AC=B8=20=EC=83=9D=EC=84=B1=20API=20MemberDetail=20?= =?UTF-8?q?=ED=8C=8C=EB=9D=BC=EB=AF=B8=ED=84=B0=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../gift/web/controller/api/ProductApiController.java | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/main/java/gift/web/controller/api/ProductApiController.java b/src/main/java/gift/web/controller/api/ProductApiController.java index 5bcd0dee8..5b176fbd5 100644 --- a/src/main/java/gift/web/controller/api/ProductApiController.java +++ b/src/main/java/gift/web/controller/api/ProductApiController.java @@ -123,8 +123,13 @@ public ResponseEntity createOption(@PathVariable Lo } @PostMapping("/{productId}/order") - public ResponseEntity orderProduct(@RequestHeader(HttpHeaders.AUTHORIZATION) String accessToken, @PathVariable Long productId, @Validated @RequestBody CreateOrderRequest request) { - OrderResponse response = orderService.createOrder(accessToken, productId, request); + public ResponseEntity orderProduct( + @RequestHeader(HttpHeaders.AUTHORIZATION) String accessToken, + @PathVariable Long productId, + @RequestBody @Validated CreateOrderRequest request, + @LoginMember MemberDetails memberDetails + ) { + OrderResponse response = orderService.createOrder(accessToken, productId, memberDetails.getId(), request); return ResponseEntity.ok(response); } From 0ecae3fda649962b40ba69ddff8c2b92e1f03a46 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=82=98=EC=A0=9C=EB=B2=95?= Date: Wed, 31 Jul 2024 00:22:23 +0900 Subject: [PATCH 106/138] =?UTF-8?q?style:=20ReadProductResponse.java=20?= =?UTF-8?q?=EC=A4=84=EB=B0=94=EA=BF=88=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../gift/web/dto/response/product/ReadProductResponse.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/main/java/gift/web/dto/response/product/ReadProductResponse.java b/src/main/java/gift/web/dto/response/product/ReadProductResponse.java index 85e1af85f..a9fc4e3c9 100644 --- a/src/main/java/gift/web/dto/response/product/ReadProductResponse.java +++ b/src/main/java/gift/web/dto/response/product/ReadProductResponse.java @@ -4,7 +4,6 @@ import gift.web.dto.response.category.ReadCategoryResponse; import gift.web.dto.response.productoption.ReadProductOptionResponse; import java.util.List; -import java.util.stream.Collectors; public class ReadProductResponse { @@ -26,7 +25,8 @@ public ReadProductResponse(Long id, String name, Integer price, String imageUrl, } public static ReadProductResponse fromEntity(Product product) { - List productOptions = product.getProductOptions().stream() + List productOptions = product.getProductOptions() + .stream() .map(ReadProductOptionResponse::fromEntity) .toList(); @@ -35,7 +35,6 @@ public static ReadProductResponse fromEntity(Product product) { return new ReadProductResponse(product.getId(), product.getName(), product.getPrice(), product.getImageUrl().toString(), productOptions, category); } - public Long getId() { return id; } From 15ca80839d130612e1b3a1e6e7f7b7896596f01c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=82=98=EC=A0=9C=EB=B2=95?= Date: Wed, 31 Jul 2024 00:33:04 +0900 Subject: [PATCH 107/138] =?UTF-8?q?feat:=20OrderRepository.java=20@Reposit?= =?UTF-8?q?ory=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/gift/repository/OrderRepository.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/java/gift/repository/OrderRepository.java b/src/main/java/gift/repository/OrderRepository.java index d2b83b7ec..2cd02aa2b 100644 --- a/src/main/java/gift/repository/OrderRepository.java +++ b/src/main/java/gift/repository/OrderRepository.java @@ -2,7 +2,9 @@ import gift.domain.Order; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; +@Repository public interface OrderRepository extends JpaRepository { } From 0a7c5652a2462641cbe9c079812974502a4295b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=82=98=EC=A0=9C=EB=B2=95?= Date: Wed, 31 Jul 2024 00:33:34 +0900 Subject: [PATCH 108/138] =?UTF-8?q?feat:=20OrderService.java=20@Transactio?= =?UTF-8?q?nal(readOnly=20=3D=20true)=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/gift/service/OrderService.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/gift/service/OrderService.java b/src/main/java/gift/service/OrderService.java index 48705a18b..3534ae8b6 100644 --- a/src/main/java/gift/service/OrderService.java +++ b/src/main/java/gift/service/OrderService.java @@ -14,7 +14,6 @@ import org.springframework.transaction.annotation.Transactional; @Service -@Transactional(readOnly = true) public class OrderService { private final KakaoClient kakaoClient; From ef0166dbb429d223b577f2bcce440e5b28bbf74c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=82=98=EC=A0=9C=EB=B2=95?= Date: Mon, 29 Jul 2024 01:28:18 +0900 Subject: [PATCH 109/138] =?UTF-8?q?docs:=20README.md=205=EC=A3=BC=EC=B0=A8?= =?UTF-8?q?=203=EB=8B=A8=EA=B3=84=20=EC=9A=94=EA=B5=AC=EC=82=AC=ED=95=AD?= =?UTF-8?q?=20=EB=B0=98=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index 1ee7158b8..b020d8438 100644 --- a/README.md +++ b/README.md @@ -51,3 +51,8 @@ Content-Type: application/json "message": "Please handle this order with care." } ``` +### 🚀 3단계 - API 문서 만들기 +*** +#### 기능 요구 사항 +API 사양에 관해 클라이언트와 어떻게 소통할 수 있을까? +어떻게 하면 편하게 소통할 수 있을지 고민해 보고 그 방법을 구현한다. \ No newline at end of file From c58f50ebc770ffda8205db3ec054fd3b3efbee7b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=82=98=EC=A0=9C=EB=B2=95?= Date: Mon, 29 Jul 2024 14:55:23 +0900 Subject: [PATCH 110/138] =?UTF-8?q?chore:=20build.gradle=20springdoc-opena?= =?UTF-8?q?pi=20=EC=9D=98=EC=A1=B4=EC=84=B1=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 3 +++ 1 file changed, 3 insertions(+) diff --git a/build.gradle b/build.gradle index 3b37a3492..89aa9aad0 100644 --- a/build.gradle +++ b/build.gradle @@ -31,6 +31,9 @@ dependencies { runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.12.6' runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.12.6' + // Swagger + implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.0.4' + runtimeOnly 'com.h2database:h2' testImplementation 'org.springframework.boot:spring-boot-starter-test' testRuntimeOnly 'org.junit.platform:junit-platform-launcher' From f196b775800e7d11fcb5fd412ea775222cab78c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=82=98=EC=A0=9C=EB=B2=95?= Date: Mon, 29 Jul 2024 22:17:04 +0900 Subject: [PATCH 111/138] =?UTF-8?q?chore:=20REST=20Docs=20=EC=9D=98?= =?UTF-8?q?=EC=A1=B4=EC=84=B1=20=EC=B6=94=EA=B0=80=20=EB=B0=8F=20=EC=84=A4?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 45 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/build.gradle b/build.gradle index 89aa9aad0..c3ebf4db9 100644 --- a/build.gradle +++ b/build.gradle @@ -2,6 +2,7 @@ plugins { id 'java' id 'org.springframework.boot' version '3.3.1' id 'io.spring.dependency-management' version '1.1.5' + id 'org.asciidoctor.jvm.convert' version '4.0.3' } group = 'camp.nextstep.edu' @@ -17,6 +18,13 @@ repositories { mavenCentral() } +configurations { + asciidoctorExt //asciidoctorExt를 configurations로 지정 + compileOnly { + extendsFrom annotationProcessor + } +} + dependencies { implementation 'org.springframework.boot:spring-boot-starter-jdbc' implementation 'org.springframework.boot:spring-boot-starter-data-jpa' @@ -34,6 +42,10 @@ dependencies { // Swagger implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.0.4' + //REST Docs + asciidoctorExt 'org.springframework.restdocs:spring-restdocs-asciidoctor' + testImplementation 'org.springframework.restdocs:spring-restdocs-mockmvc' + runtimeOnly 'com.h2database:h2' testImplementation 'org.springframework.boot:spring-boot-starter-test' testRuntimeOnly 'org.junit.platform:junit-platform-launcher' @@ -48,3 +60,36 @@ dependencyManagement { tasks.named('test') { useJUnitPlatform() } + +ext { + set('snippetsDir', file("build/generated-snippets")) //snippets 파일이 저장될 경로 snippetsDir로 변수 설정 +} + +test { + outputs.dir snippetsDir + useJUnitPlatform() +} + +asciidoctor { + configurations 'asciidoctorExt' //Asciidoctor에서 asciidoctorExt를 사용하도록 설정 + baseDirFollowsSourceFile() + inputs.dir snippetsDir + dependsOn test //gradle build 시 test -> asciiDoctor 순서로 실행 +} + +asciidoctor.doFirst { + delete file('src/main/resources/static/docs') //asciidoctor 실행 전 docs 폴더 삭제 +} + +task createDocument(type: Copy) { + dependsOn asciidoctor + from file("build/docs/asciidoc") + into file("src/main/resources/static") +} + +bootJar { + dependsOn createDocument //Gradle build 시, createDocument -> bootJar 순으로 실행 + from("${asciidoctor.outputDir}") { + into 'static/docs' + } +} \ No newline at end of file From 4e336e7e6c0f6a9f8602b8bf75243af8e4c1834a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=82=98=EC=A0=9C=EB=B2=95?= Date: Mon, 29 Jul 2024 22:18:05 +0900 Subject: [PATCH 112/138] =?UTF-8?q?style:=20ProductApiController.java=20?= =?UTF-8?q?=EB=A9=94=EC=84=9C=EB=93=9C=20=EC=88=9C=EC=84=9C=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../web/controller/api/ProductApiController.java | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/main/java/gift/web/controller/api/ProductApiController.java b/src/main/java/gift/web/controller/api/ProductApiController.java index 5b176fbd5..45c4aad4e 100644 --- a/src/main/java/gift/web/controller/api/ProductApiController.java +++ b/src/main/java/gift/web/controller/api/ProductApiController.java @@ -63,6 +63,12 @@ public ResponseEntity readAllProducts(@PageableDefault return ResponseEntity.ok(response); } + @GetMapping(params = "categoryId") + public ResponseEntity readProductsByCategoryId(@PageableDefault Pageable pageable, @RequestParam Long categoryId) { + ReadAllProductsResponse response = productService.readProductsByCategoryId(categoryId, pageable); + return ResponseEntity.ok(response); + } + @PostMapping public ResponseEntity createProduct( @Validated @RequestBody CreateProductRequest request) throws URISyntaxException { @@ -72,12 +78,6 @@ public ResponseEntity createProduct( return ResponseEntity.created(location).body(response); } - @GetMapping(params = "categoryId") - public ResponseEntity readProductsByCategoryId(@PageableDefault Pageable pageable, @RequestParam Long categoryId) { - ReadAllProductsResponse response = productService.readProductsByCategoryId(categoryId, pageable); - return ResponseEntity.ok(response); - } - @GetMapping("/{productId}") public ResponseEntity readProduct(@PathVariable Long productId) { ReadProductResponse response; From 05b021315f63a9a33139bb4ba8ef0e841fe4c507 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=82=98=EC=A0=9C=EB=B2=95?= Date: Mon, 29 Jul 2024 22:18:33 +0900 Subject: [PATCH 113/138] =?UTF-8?q?refactor:=20ReadProductResponse.java=20?= =?UTF-8?q?productOptions=20->=20options=20=EB=B3=80=EC=88=98=EB=AA=85=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../web/dto/response/product/ReadProductResponse.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/main/java/gift/web/dto/response/product/ReadProductResponse.java b/src/main/java/gift/web/dto/response/product/ReadProductResponse.java index a9fc4e3c9..c9346ef41 100644 --- a/src/main/java/gift/web/dto/response/product/ReadProductResponse.java +++ b/src/main/java/gift/web/dto/response/product/ReadProductResponse.java @@ -11,16 +11,16 @@ public class ReadProductResponse { private final String name; private final Integer price; private final String imageUrl; - private final List productOptions; + private final List options; private final ReadCategoryResponse category; public ReadProductResponse(Long id, String name, Integer price, String imageUrl, - List productOptions, ReadCategoryResponse category) { + List options, ReadCategoryResponse category) { this.id = id; this.name = name; this.price = price; this.imageUrl = imageUrl; - this.productOptions = productOptions; + this.options = options; this.category = category; } @@ -51,8 +51,8 @@ public String getImageUrl() { return imageUrl; } - public List getProductOptions() { - return productOptions; + public List getOptions() { + return options; } public ReadCategoryResponse getCategory() { From 869fea672c5270d0513965a65ea2cb7091208351 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=82=98=EC=A0=9C=EB=B2=95?= Date: Mon, 29 Jul 2024 22:18:57 +0900 Subject: [PATCH 114/138] =?UTF-8?q?test:=20RestDocsConfiguration.java=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../gift/config/RestDocsConfiguration.java | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 src/test/java/gift/config/RestDocsConfiguration.java diff --git a/src/test/java/gift/config/RestDocsConfiguration.java b/src/test/java/gift/config/RestDocsConfiguration.java new file mode 100644 index 000000000..8682ea2d6 --- /dev/null +++ b/src/test/java/gift/config/RestDocsConfiguration.java @@ -0,0 +1,21 @@ +package gift.config; + +import org.springframework.boot.test.context.TestConfiguration; +import org.springframework.context.annotation.Bean; +import org.springframework.restdocs.mockmvc.MockMvcRestDocumentation; +import org.springframework.restdocs.mockmvc.RestDocumentationResultHandler; +import org.springframework.restdocs.operation.preprocess.Preprocessors; + +@TestConfiguration +public class RestDocsConfiguration { + + @Bean + public RestDocumentationResultHandler write() { + return MockMvcRestDocumentation.document( + "{class-name}/{method-name}", + Preprocessors.preprocessRequest(Preprocessors.prettyPrint()), //예쁘게 출력 + Preprocessors.preprocessResponse(Preprocessors.prettyPrint()) //예쁘게 출력 + ); + } + +} From b73a091e7879860216fd67a8c53102399bd1a608 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=82=98=EC=A0=9C=EB=B2=95?= Date: Mon, 29 Jul 2024 22:19:21 +0900 Subject: [PATCH 115/138] =?UTF-8?q?test:=20ProductServiceTest.java=20?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=9C=A0=EC=A7=80=EB=B3=B4?= =?UTF-8?q?=EC=88=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/test/java/gift/service/ProductServiceTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/gift/service/ProductServiceTest.java b/src/test/java/gift/service/ProductServiceTest.java index ad43d05ca..c8332d0c2 100644 --- a/src/test/java/gift/service/ProductServiceTest.java +++ b/src/test/java/gift/service/ProductServiceTest.java @@ -55,7 +55,7 @@ class ProductServiceTest { @BeforeEach void setUp() { - productOptionService = new ProductOptionService(productOptionRepository); + productOptionService = new ProductOptionService(productOptionRepository, productRepository); productService = new ProductService(productRepository, categoryRepository, wishProductRepository, productOptionService); } From 27a730eafb48df0f283ab0379b25d471358ad650 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=82=98=EC=A0=9C=EB=B2=95?= Date: Mon, 29 Jul 2024 22:19:44 +0900 Subject: [PATCH 116/138] =?UTF-8?q?test:=20ProductOptionServiceTest.java?= =?UTF-8?q?=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=9C=A0=EC=A7=80=EB=B3=B4?= =?UTF-8?q?=EC=88=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/test/java/gift/service/ProductOptionServiceTest.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/test/java/gift/service/ProductOptionServiceTest.java b/src/test/java/gift/service/ProductOptionServiceTest.java index 69b413f0c..da01416cd 100644 --- a/src/test/java/gift/service/ProductOptionServiceTest.java +++ b/src/test/java/gift/service/ProductOptionServiceTest.java @@ -4,13 +4,14 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.Assertions.assertAll; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; -import static org.junit.jupiter.api.Assertions.assertThrows; import static org.mockito.ArgumentMatchers.any; import static org.mockito.BDDMockito.given; +import gift.domain.Product; import gift.domain.ProductOption; import gift.domain.ProductOption.Builder; import gift.repository.ProductOptionRepository; +import gift.repository.ProductRepository; import gift.web.dto.request.productoption.CreateProductOptionRequest; import gift.web.dto.request.productoption.SubtractProductOptionQuantityRequest; import gift.web.dto.request.productoption.UpdateProductOptionRequest; @@ -37,6 +38,9 @@ class ProductOptionServiceTest { @Mock private ProductOptionRepository productOptionRepository; + @Mock + private ProductRepository productRepository; + @Test @DisplayName("상품 옵션 생성 요청이 정상적일 때, 상품 옵션을 성공적으로 생성합니다.") void createOption() { @@ -44,6 +48,7 @@ void createOption() { Long productId = 1L; CreateProductOptionRequest request = new CreateProductOptionRequest("optionName", 1000); given(productOptionRepository.save(any())).willReturn(request.toEntity(productId)); + given(productRepository.findById(any())).willReturn(Optional.of(new Product.Builder().productOptions(List.of(request.toEntity(productId))).build())); //when CreateProductOptionResponse response = productOptionService.createOption(productId, request); From c75c5e150cdb8bcda5a36c1a8b50f2f5af94aa9d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=82=98=EC=A0=9C=EB=B2=95?= Date: Mon, 29 Jul 2024 22:20:01 +0900 Subject: [PATCH 117/138] =?UTF-8?q?test:=20MockLoginMemberArgumentResolver?= =?UTF-8?q?.java=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../mock/MockLoginMemberArgumentResolver.java | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 src/test/java/gift/mock/MockLoginMemberArgumentResolver.java diff --git a/src/test/java/gift/mock/MockLoginMemberArgumentResolver.java b/src/test/java/gift/mock/MockLoginMemberArgumentResolver.java new file mode 100644 index 000000000..5829a7d93 --- /dev/null +++ b/src/test/java/gift/mock/MockLoginMemberArgumentResolver.java @@ -0,0 +1,25 @@ +package gift.mock; + +import gift.domain.constants.Platform; +import gift.domain.vo.Email; +import gift.web.dto.MemberDetails; +import gift.web.resolver.LoginMemberArgumentResolver; +import org.springframework.boot.test.context.TestComponent; +import org.springframework.core.MethodParameter; +import org.springframework.web.bind.support.WebDataBinderFactory; +import org.springframework.web.context.request.NativeWebRequest; +import org.springframework.web.method.support.ModelAndViewContainer; + +@TestComponent +public class MockLoginMemberArgumentResolver extends LoginMemberArgumentResolver { + + public MockLoginMemberArgumentResolver() { + super(null, null); + } + + @Override + public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, + NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception { + return new MemberDetails(1L, Email.from("member01@gmail.com"), Platform.GIFT); + } +} From 123bcd67260c443fd2a4c705d185846ce67542a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=82=98=EC=A0=9C=EB=B2=95?= Date: Mon, 29 Jul 2024 22:20:16 +0900 Subject: [PATCH 118/138] =?UTF-8?q?test:=20ProductApiControllerTest.java?= =?UTF-8?q?=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/ProductApiControllerTest.java | 524 ++++++++++++++++++ 1 file changed, 524 insertions(+) create mode 100644 src/test/java/gift/web/controller/api/ProductApiControllerTest.java diff --git a/src/test/java/gift/web/controller/api/ProductApiControllerTest.java b/src/test/java/gift/web/controller/api/ProductApiControllerTest.java new file mode 100644 index 000000000..62f6145f1 --- /dev/null +++ b/src/test/java/gift/web/controller/api/ProductApiControllerTest.java @@ -0,0 +1,524 @@ +package gift.web.controller.api; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.BDDMockito.given; +import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.delete; +import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.post; +import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.put; +import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath; +import static org.springframework.restdocs.payload.PayloadDocumentation.requestFields; +import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields; +import static org.springframework.restdocs.request.RequestDocumentation.parameterWithName; +import static org.springframework.restdocs.request.RequestDocumentation.queryParameters; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import com.fasterxml.jackson.databind.ObjectMapper; +import gift.authentication.token.JwtProvider; +import gift.config.RestDocsConfiguration; +import gift.converter.StringToUrlConverter; +import gift.domain.Category; +import gift.domain.Member; +import gift.domain.Member.Builder; +import gift.domain.Product; +import gift.domain.ProductOption; +import gift.domain.vo.Color; +import gift.domain.vo.Email; +import gift.mock.MockLoginMemberArgumentResolver; +import gift.service.OrderService; +import gift.service.ProductOptionService; +import gift.service.ProductService; +import gift.service.WishProductService; +import gift.web.dto.request.order.CreateOrderRequest; +import gift.web.dto.request.product.CreateProductRequest; +import gift.web.dto.request.product.UpdateProductRequest; +import gift.web.dto.request.productoption.CreateProductOptionRequest; +import gift.web.dto.request.productoption.UpdateProductOptionRequest; +import gift.web.dto.request.wishproduct.CreateWishProductRequest; +import gift.web.dto.response.category.ReadCategoryResponse; +import gift.web.dto.response.order.OrderResponse; +import gift.web.dto.response.product.CreateProductResponse; +import gift.web.dto.response.product.ReadAllProductsResponse; +import gift.web.dto.response.product.ReadProductResponse; +import gift.web.dto.response.product.UpdateProductResponse; +import gift.web.dto.response.productoption.CreateProductOptionResponse; +import gift.web.dto.response.productoption.ReadAllProductOptionsResponse; +import gift.web.dto.response.productoption.ReadProductOptionResponse; +import gift.web.dto.response.productoption.UpdateProductOptionResponse; +import gift.web.dto.response.wishproduct.CreateWishProductResponse; +import java.util.List; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.context.annotation.Import; +import org.springframework.data.domain.Pageable; +import org.springframework.data.web.PageableHandlerMethodArgumentResolver; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.restdocs.RestDocumentationContextProvider; +import org.springframework.restdocs.RestDocumentationExtension; +import org.springframework.restdocs.mockmvc.MockMvcRestDocumentation; +import org.springframework.restdocs.mockmvc.RestDocumentationResultHandler; +import org.springframework.restdocs.payload.JsonFieldType; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; + +@ActiveProfiles("test") +@SpringBootTest +@Import(RestDocsConfiguration.class) +@ExtendWith(RestDocumentationExtension.class) +class ProductApiControllerTest { + + private MockMvc mockMvc; + + @Autowired + protected RestDocumentationResultHandler restDocs; + + @Autowired + protected ObjectMapper objectMapper; + + @Autowired + private JwtProvider jwtProvider; + + @MockBean + private ProductService productService; + @MockBean + private WishProductService wishProductService; + @MockBean + private ProductOptionService productOptionService; + @MockBean + private OrderService orderService; + + private String accessToken; + + private static final String BASE_URL = "/api/products"; + + @BeforeEach + void setUp( + final RestDocumentationContextProvider provider + ) { + mockMvc = MockMvcBuilders + .standaloneSetup(new ProductApiController(productService, wishProductService, productOptionService, orderService)) + .setCustomArgumentResolvers(new MockLoginMemberArgumentResolver(), new PageableHandlerMethodArgumentResolver()) + .apply(MockMvcRestDocumentation.documentationConfiguration(provider)) + .alwaysDo(restDocs) + .build(); + + Member member = new Builder().id(1L).name("회원01").email(Email.from("member01@gmail.com")) + .build(); + accessToken = jwtProvider.generateToken(member).getValue(); + } + + @Test + @DisplayName("전체 상품 조회") + void readAllProducts() throws Exception { + given(productService.readAllProducts(any(Pageable.class))) + .willReturn(ReadAllProductsResponse.from( + List.of(new ReadProductResponse(1L, "상품01", 1000, "https://via.placeholder.com/150", + List.of(new ReadProductOptionResponse(1L, "상품 옵션 01", 100)), + new ReadCategoryResponse(1L, "카테고리01", "카테고리01 입니다", "https://via.placeholder.com/150", "#FFFFFF"))) + )); + + mockMvc + .perform( + get(BASE_URL) + .header(HttpHeaders.AUTHORIZATION, "Bearer " + accessToken) + ) + .andExpect(status().isOk()) + .andDo( + restDocs.document( + queryParameters( + parameterWithName("page").optional().description("페이지 번호"), + parameterWithName("size").optional().description("페이지 크기") + ), + responseFields( + fieldWithPath("products[].id").type(JsonFieldType.NUMBER).description("상품 ID"), + fieldWithPath("products[].name").type(JsonFieldType.STRING).description("상품명"), + fieldWithPath("products[].price").type(JsonFieldType.NUMBER).description("상품 가격"), + fieldWithPath("products[].imageUrl").type(JsonFieldType.STRING).description("상품 이미지 URL"), + fieldWithPath("products[].options").type(JsonFieldType.ARRAY).description("상품 옵션 목록"), + fieldWithPath("products[].options[].id").type(JsonFieldType.NUMBER).description("상품 옵션 ID"), + fieldWithPath("products[].options[].name").type(JsonFieldType.STRING).description("상품 옵션명"), + fieldWithPath("products[].options[].stock").type(JsonFieldType.NUMBER).description("상품 옵션 재고"), + fieldWithPath("products[].category.id").type(JsonFieldType.NUMBER).description("카테고리 ID"), + fieldWithPath("products[].category.name").type(JsonFieldType.STRING).description("카테고리명"), + fieldWithPath("products[].category.description").type(JsonFieldType.STRING).description("카테고리 설명"), + fieldWithPath("products[].category.imageUrl").type(JsonFieldType.STRING).description("카테고리 이미지 URL"), + fieldWithPath("products[].category.color").type(JsonFieldType.STRING).description("카테고리 색상") + ) + ) + ); + } + +// todo requestParam 만 다른 경로에 대해 테스트 코드 동작시키기 +// @Test +// @DisplayName("카테고리로 상품 조회") +// void readProductsByCategoryId() throws Exception { +// given(productService.readProductsByCategoryId(any(Long.class), any(Pageable.class))) +// .willReturn(ReadAllProductsResponse.from( +// List.of(new ReadProductResponse(1L, "상품01", 1000, "https://via.placeholder.com/150", +// List.of(new ReadProductOptionResponse(1L, "상품 옵션 01", 100)), +// new ReadCategoryResponse(1L, "카테고리01", "카테고리01 입니다", "https://via.placeholder.com/150", "#FFFFFF"))) +// )); +// +// mockMvc +// .perform( +// get(BASE_URL) +// .header(HttpHeaders.AUTHORIZATION, "Bearer " + accessToken) +// ) +// .andExpect(status().isOk()) +// .andDo( +// restDocs.document( +// queryParameters( +// parameterWithName("page").optional().description("페이지 번호"), +// parameterWithName("size").optional().description("페이지 크기"), +// parameterWithName("categoryId").description("카테고리 아이디") +// ), +// responseFields( +// fieldWithPath("products[].id").type(JsonFieldType.NUMBER).description("상품 ID"), +// fieldWithPath("products[].name").type(JsonFieldType.STRING).description("상품명"), +// fieldWithPath("products[].price").type(JsonFieldType.NUMBER).description("상품 가격"), +// fieldWithPath("products[].imageUrl").type(JsonFieldType.STRING).description("상품 이미지 URL"), +// fieldWithPath("products[].productOptions").type(JsonFieldType.ARRAY).description("상품 옵션 목록"), +// fieldWithPath("products[].productOptions[].id").type(JsonFieldType.NUMBER).description("상품 옵션 ID"), +// fieldWithPath("products[].productOptions[].name").type(JsonFieldType.STRING).description("상품 옵션명"), +// fieldWithPath("products[].productOptions[].stock").type(JsonFieldType.NUMBER).description("상품 옵션 재고"), +// fieldWithPath("products[].category.id").type(JsonFieldType.NUMBER).description("카테고리 ID"), +// fieldWithPath("products[].category.name").type(JsonFieldType.STRING).description("카테고리명"), +// fieldWithPath("products[].category.description").type(JsonFieldType.STRING).description("카테고리 설명"), +// fieldWithPath("products[].category.imageUrl").type(JsonFieldType.STRING).description("카테고리 이미지 URL"), +// fieldWithPath("products[].category.color").type(JsonFieldType.STRING).description("카테고리 색상") +// ) +// ) +// ); +// } + + @Test + @DisplayName("상품 생성") + void createProduct() throws Exception { + CreateProductRequest request = new CreateProductRequest("상품01", 1000, + "https://via.placeholder.com/150", 1L, + List.of(new CreateProductOptionRequest("상품 옵션 01", 100))); + String content = objectMapper.writeValueAsString(request); + + given(productService.createProduct(any(CreateProductRequest.class))) + .willReturn(new CreateProductResponse(1L, "상품01", 1000, "https://via.placeholder.com/150", List.of(new ProductOption.Builder().id(1L).productId(1L).name("상품 옵션 01").stock(100).build())) ); + + mockMvc + .perform( + post(BASE_URL) + .content(content) + .contentType(MediaType.APPLICATION_JSON) + .header(HttpHeaders.AUTHORIZATION, "Bearer " + accessToken) + ) + .andExpect(status().isCreated()) + .andDo( + restDocs.document( + requestFields( + fieldWithPath("name").type(JsonFieldType.STRING).description("상품명"), + fieldWithPath("price").type(JsonFieldType.NUMBER).description("상품 가격"), + fieldWithPath("imageUrl").type(JsonFieldType.STRING).description("상품 이미지 URL"), + fieldWithPath("categoryId").type(JsonFieldType.NUMBER).description("카테고리 ID"), + fieldWithPath("productOptions").type(JsonFieldType.ARRAY).description("상품 옵션 목록"), + fieldWithPath("productOptions[].name").type(JsonFieldType.STRING).description("상품 옵션명"), + fieldWithPath("productOptions[].stock").type(JsonFieldType.NUMBER).description("상품 옵션 재고") + ), + responseFields( + fieldWithPath("id").type(JsonFieldType.NUMBER).description("상품 ID"), + fieldWithPath("name").type(JsonFieldType.STRING).description("상품명"), + fieldWithPath("price").type(JsonFieldType.NUMBER).description("상품 가격"), + fieldWithPath("imageUrl").type(JsonFieldType.STRING).description("상품 이미지 URL"), + fieldWithPath("options").type(JsonFieldType.ARRAY).description("상품 옵션 목록"), + fieldWithPath("options[].id").type(JsonFieldType.NUMBER).description("상품 옵션 ID"), + fieldWithPath("options[].name").type(JsonFieldType.STRING).description("상품 옵션명"), + fieldWithPath("options[].stock").type(JsonFieldType.NUMBER).description("상품 옵션 재고"), + fieldWithPath("options[].productId").type(JsonFieldType.NUMBER).description("상품 옵션 상품 ID") + ) + ) + ); + } + + @Test + @DisplayName("단일 상품 조회") + void readProduct() throws Exception { + given(productService.readProductById(any(Long.class))) + .willReturn(new ReadProductResponse(1L, "상품01", 1000, "https://via.placeholder.com/150", + List.of(new ReadProductOptionResponse(1L, "상품 옵션 01", 100)), + new ReadCategoryResponse(1L, "카테고리01", "카테고리01 입니다", "https://via.placeholder.com/150", "#FFFFFF"))); + + mockMvc + .perform( + get(BASE_URL + "/{productId}", 1L) + .header(HttpHeaders.AUTHORIZATION, "Bearer " + accessToken) + ) + .andExpect(status().isOk()) + .andDo( + restDocs.document( + responseFields( + fieldWithPath("id").type(JsonFieldType.NUMBER).description("상품 ID"), + fieldWithPath("name").type(JsonFieldType.STRING).description("상품명"), + fieldWithPath("price").type(JsonFieldType.NUMBER).description("상품 가격"), + fieldWithPath("imageUrl").type(JsonFieldType.STRING).description("상품 이미지 URL"), + fieldWithPath("options").type(JsonFieldType.ARRAY).description("상품 옵션 목록"), + fieldWithPath("options[].id").type(JsonFieldType.NUMBER).description("상품 옵션 ID"), + fieldWithPath("options[].name").type(JsonFieldType.STRING).description("상품 옵션명"), + fieldWithPath("options[].stock").type(JsonFieldType.NUMBER).description("상품 옵션 재고"), + fieldWithPath("category.id").type(JsonFieldType.NUMBER).description("카테고리 ID"), + fieldWithPath("category.name").type(JsonFieldType.STRING).description("카테고리명"), + fieldWithPath("category.description").type(JsonFieldType.STRING).description("카테고리 설명"), + fieldWithPath("category.imageUrl").type(JsonFieldType.STRING).description("카테고리 이미지 URL"), + fieldWithPath("category.color").type(JsonFieldType.STRING).description("카테고리 색상") + ) + ) + ); + } + + @Test + @DisplayName("상품 수정") + void updateProduct() throws Exception { + UpdateProductRequest request = new UpdateProductRequest("상품01", 1000, "https://via.placeholder.com/150"); + + String content = objectMapper.writeValueAsString(request); + + given(productService.updateProduct(any(Long.class), any())) + .willReturn(UpdateProductResponse.from(new Product.Builder() + .id(1L) + .name("상품01") + .price(1000) + .productOptions( + List.of( + new ProductOption.Builder() + .id(1L) + .productId(1L) + .name("상품 옵션 01") + .stock(100) + .build() + )) + .imageUrl(StringToUrlConverter.convert("https://via.placeholder.com/150")) + .category(new Category.Builder() + .id(1L) + .name("카테고리01") + .color(Color.from("#FFFFFF")) + .description("카테고리01 입니다") + .imageUrl(StringToUrlConverter.convert("https://via.placeholder.com/150")) + .build()) + .build())); + + mockMvc + .perform( + put(BASE_URL + "/{productId}", 1L) + .content(content) + .contentType(MediaType.APPLICATION_JSON) + .header(HttpHeaders.AUTHORIZATION, "Bearer " + accessToken) + ) + .andExpect(status().isOk()) + .andDo( + restDocs.document( + requestFields( + fieldWithPath("name").type(JsonFieldType.STRING).description("상품명"), + fieldWithPath("price").type(JsonFieldType.NUMBER).description("상품 가격"), + fieldWithPath("imageUrl").type(JsonFieldType.STRING).description("상품 이미지 URL") + ), + responseFields( + fieldWithPath("id").type(JsonFieldType.NUMBER).description("상품 ID"), + fieldWithPath("name").type(JsonFieldType.STRING).description("상품명"), + fieldWithPath("price").type(JsonFieldType.NUMBER).description("상품 가격"), + fieldWithPath("imageUrl").type(JsonFieldType.STRING).description("상품 이미지 URL"), + fieldWithPath("category.id").type(JsonFieldType.NUMBER).description("카테고리 ID"), + fieldWithPath("category.name").type(JsonFieldType.STRING).description("카테고리명"), + fieldWithPath("category.description").type(JsonFieldType.STRING).description("카테고리 설명"), + fieldWithPath("category.imageUrl").type(JsonFieldType.STRING).description("카테고리 이미지 URL"), + fieldWithPath("category.color").type(JsonFieldType.STRING).description("카테고리 색상") + ) + ) + ); + } + + @Test + @DisplayName("상품 삭제") + void deleteProduct() throws Exception { + mockMvc + .perform( + delete(BASE_URL + "/{productId}", 1L) + .header(HttpHeaders.AUTHORIZATION, "Bearer " + accessToken) + ) + .andExpect(status().isNoContent()) + .andDo( + restDocs.document() + ); + } + + @Test + @DisplayName("위시 상품 추가") + void createWishProduct() throws Exception { + CreateWishProductRequest request = new CreateWishProductRequest(1L, 1); + String content = objectMapper.writeValueAsString(request); + + given(wishProductService.createWishProduct(any(Long.class), any())) + .willReturn(new CreateWishProductResponse(1L, 1)); + + mockMvc + .perform( + post(BASE_URL + "/wish") + .content(content) + .contentType(MediaType.APPLICATION_JSON) + .header(HttpHeaders.AUTHORIZATION, "Bearer " + accessToken) + ) + .andExpect(status().isOk()) + .andDo( + restDocs.document( + requestFields( + fieldWithPath("productId").type(JsonFieldType.NUMBER).description("상품 ID"), + fieldWithPath("quantity").type(JsonFieldType.NUMBER).description("수량") + ), + responseFields( + fieldWithPath("id").type(JsonFieldType.NUMBER).description("위시 상품 ID"), + fieldWithPath("quantity").type(JsonFieldType.NUMBER).description("수량") + ) + ) + ); + } + + @Test + @DisplayName("상품 옵션 조회") + void readOptions() throws Exception { + given(productOptionService.readAllOptions(any(Long.class))) + .willReturn(ReadAllProductOptionsResponse.from( + List.of(new ReadProductOptionResponse(1L, "상품 옵션 01", 100)) + )); + + mockMvc + .perform( + get(BASE_URL + "/{productId}/options", 1L) + .header(HttpHeaders.AUTHORIZATION, "Bearer " + accessToken) + ) + .andExpect(status().isOk()) + .andDo( + restDocs.document( + responseFields( + fieldWithPath("options[].id").type(JsonFieldType.NUMBER).description("상품 옵션 ID"), + fieldWithPath("options[].name").type(JsonFieldType.STRING).description("상품 옵션명"), + fieldWithPath("options[].stock").type(JsonFieldType.NUMBER).description("상품 옵션 재고") + ) + ) + ); + } + + @Test + @DisplayName("상품 옵션 생성") + void createOption() throws Exception { + CreateProductOptionRequest request = new CreateProductOptionRequest( + "상품 옵션 01", 100); + String content = objectMapper.writeValueAsString(request); + + given(productOptionService.createOption(any(Long.class), any())) + .willReturn(new CreateProductOptionResponse(1L, "상품 옵션 01", 100)); + + mockMvc + .perform( + post(BASE_URL + "/{productId}/options", 1L) + .content(content) + .contentType(MediaType.APPLICATION_JSON) + .header(HttpHeaders.AUTHORIZATION, "Bearer " + accessToken) + ) + .andExpect(status().isCreated()) + .andDo( + restDocs.document( + responseFields( + fieldWithPath("id").type(JsonFieldType.NUMBER).description("상품 옵션 ID"), + fieldWithPath("name").type(JsonFieldType.STRING).description("상품 옵션명"), + fieldWithPath("stock").type(JsonFieldType.NUMBER).description("상품 옵션 재고") + ) + ) + ); + } + + @Test + @DisplayName("상품 주문") + void orderProduct() throws Exception { + + CreateOrderRequest request = new CreateOrderRequest(1L, 1, "message"); + String content = objectMapper.writeValueAsString(request); + + given(orderService.createOrder(any(String.class), any(Long.class), any())) + .willReturn(new OrderResponse(1L, 1L, 10, 1, "상품01", "message")); + + mockMvc + .perform( + post(BASE_URL + "/{productId}/order", 1L) + .header(HttpHeaders.AUTHORIZATION, "Bearer " + accessToken) + .content(content) + .contentType(MediaType.APPLICATION_JSON) + ) + .andExpect(status().isOk()) + .andDo( + restDocs.document( + requestFields( + fieldWithPath("optionId").type(JsonFieldType.NUMBER).description("상품 옵션 ID"), + fieldWithPath("quantity").type(JsonFieldType.NUMBER).description("수량"), + fieldWithPath("message").type(JsonFieldType.STRING).description("메시지") + ), + responseFields( + fieldWithPath("productId").type(JsonFieldType.NUMBER).description("상품 ID"), + fieldWithPath("optionId").type(JsonFieldType.NUMBER).description("상품 옵션 ID"), + fieldWithPath("optionStock").type(JsonFieldType.NUMBER).description("상품 옵션 재고"), + fieldWithPath("quantity").type(JsonFieldType.NUMBER).description("수량"), + fieldWithPath("productName").type(JsonFieldType.STRING).description("상품명"), + fieldWithPath("message").type(JsonFieldType.STRING).description("메시지") + ) + ) + ); + } + + @Test + @DisplayName("상품 옵션 수정") + void updateOption() throws Exception { + UpdateProductOptionRequest request = new UpdateProductOptionRequest( + "상품 옵션 01", 100); + String content = objectMapper.writeValueAsString(request); + + given(productOptionService.updateOption(any(Long.class), any(Long.class), any())) + .willReturn(new UpdateProductOptionResponse(1L, "상품 옵션 01", 100)); + + mockMvc + .perform( + put(BASE_URL + "/{productId}/options/{optionId}", 1L, 1L) + .header(HttpHeaders.AUTHORIZATION, "Bearer " + accessToken) + .content(content) + .contentType(MediaType.APPLICATION_JSON) + ) + .andExpect(status().isOk()) + .andDo( + restDocs.document( + requestFields( + fieldWithPath("name").type(JsonFieldType.STRING).description("상품 옵션명"), + fieldWithPath("stock").type(JsonFieldType.NUMBER).description("상품 옵션 재고") + ), + responseFields( + fieldWithPath("id").type(JsonFieldType.NUMBER).description("상품 옵션 ID"), + fieldWithPath("name").type(JsonFieldType.STRING).description("상품 옵션명"), + fieldWithPath("stock").type(JsonFieldType.NUMBER).description("상품 옵션 재고") + ) + ) + ); + } + + @Test + @DisplayName("상품 옵션 삭제") + void deleteOption() throws Exception { + mockMvc + .perform( + delete(BASE_URL + "/{productId}/options/{optionId}", 1L, 1L) + .header(HttpHeaders.AUTHORIZATION, "Bearer " + accessToken) + ) + .andExpect(status().isNoContent()) + .andDo( + restDocs.document() + ); + } +} \ No newline at end of file From a6adc671492581e3df39342a04571433744c6209 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=82=98=EC=A0=9C=EB=B2=95?= Date: Mon, 29 Jul 2024 22:20:35 +0900 Subject: [PATCH 119/138] =?UTF-8?q?docs:=20product.adoc=20=EC=83=81?= =?UTF-8?q?=ED=92=88=20API=20=EB=AA=85=EC=84=B8=EC=84=9C=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/docs/asciidoc/product.adoc | 35 ++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 src/docs/asciidoc/product.adoc diff --git a/src/docs/asciidoc/product.adoc b/src/docs/asciidoc/product.adoc new file mode 100644 index 000000000..91caa46ef --- /dev/null +++ b/src/docs/asciidoc/product.adoc @@ -0,0 +1,35 @@ +== 상품 + +=== 전체 상품 조회 +operation::product-api-controller-test/read-all-products[snippets='http-request,http-response'] + +=== 단일 상품 조회 +operation::product-api-controller-test/read-product[snippets='http-request,http-response'] + +=== 상품 생성 +operation::product-api-controller-test/create-product[snippets='http-request,http-response'] + +=== 상품 수정 +operation::product-api-controller-test/update-product[snippets='http-request,http-response'] + +=== 상품 삭제 +operation::product-api-controller-test/delete-product[snippets='http-request,http-response'] + +=== 상품 주문 +operation::product-api-controller-test/order-product[snippets='http-request,http-response'] + +=== 위시 상품 추가 +operation::product-api-controller-test/create-wish-product[snippets='http-request,http-response'] + +=== 상품 옵션 생성 +operation::product-api-controller-test/create-option[snippets='http-request,http-response'] + +=== 상품 옵션 조회 +operation::product-api-controller-test/read-options[snippets='http-request,http-response'] + +=== 상품 옵션 수정 +operation::product-api-controller-test/update-option[snippets='http-request,http-response'] + +=== 상품 옵션 삭제 +operation::product-api-controller-test/delete-option[snippets='http-request,http-response'] + From 1868db66c8f27dd6cc17bb965d8e98efd713d458 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=82=98=EC=A0=9C=EB=B2=95?= Date: Mon, 29 Jul 2024 22:20:48 +0900 Subject: [PATCH 120/138] =?UTF-8?q?docs:=20docs.adoc=20API=20=EB=AA=85?= =?UTF-8?q?=EC=84=B8=EC=84=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/docs/asciidoc/docs.adoc | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 src/docs/asciidoc/docs.adoc diff --git a/src/docs/asciidoc/docs.adoc b/src/docs/asciidoc/docs.adoc new file mode 100644 index 000000000..c3475228b --- /dev/null +++ b/src/docs/asciidoc/docs.adoc @@ -0,0 +1,8 @@ += 카카오 테크 캠퍼스 STEP 2 - REST Docs +:doctype: book +:source-highlighter: highlightjs +:toc: left +:toclevels: 3 +:sectlinks: + +include::product.adoc[] \ No newline at end of file From 0be61268005b0465314a93f35b27f8da880d88e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=82=98=EC=A0=9C=EB=B2=95?= Date: Mon, 29 Jul 2024 22:43:37 +0900 Subject: [PATCH 121/138] =?UTF-8?q?test:=20CategoryApiControllerTest.java?= =?UTF-8?q?=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/CategoryApiControllerTest.java | 242 ++++++++++++++++++ 1 file changed, 242 insertions(+) create mode 100644 src/test/java/gift/web/controller/api/CategoryApiControllerTest.java diff --git a/src/test/java/gift/web/controller/api/CategoryApiControllerTest.java b/src/test/java/gift/web/controller/api/CategoryApiControllerTest.java new file mode 100644 index 000000000..c904edd50 --- /dev/null +++ b/src/test/java/gift/web/controller/api/CategoryApiControllerTest.java @@ -0,0 +1,242 @@ +package gift.web.controller.api; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.BDDMockito.given; +import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.post; +import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.put; +import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath; +import static org.springframework.restdocs.payload.PayloadDocumentation.requestFields; +import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields; +import static org.springframework.restdocs.request.RequestDocumentation.parameterWithName; +import static org.springframework.restdocs.request.RequestDocumentation.queryParameters; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import com.fasterxml.jackson.databind.ObjectMapper; +import gift.authentication.token.JwtProvider; +import gift.config.RestDocsConfiguration; +import gift.domain.Member; +import gift.domain.Member.Builder; +import gift.domain.vo.Email; +import gift.mock.MockLoginMemberArgumentResolver; +import gift.service.CategoryService; +import gift.service.OrderService; +import gift.service.ProductOptionService; +import gift.service.ProductService; +import gift.service.WishProductService; +import gift.web.dto.request.category.CreateCategoryRequest; +import gift.web.dto.request.category.UpdateCategoryRequest; +import gift.web.dto.response.category.CreateCategoryResponse; +import gift.web.dto.response.category.ReadAllCategoriesResponse; +import gift.web.dto.response.category.ReadCategoryResponse; +import gift.web.dto.response.category.UpdateCategoryResponse; +import java.util.List; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.context.annotation.Import; +import org.springframework.data.web.PageableHandlerMethodArgumentResolver; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.restdocs.RestDocumentationContextProvider; +import org.springframework.restdocs.RestDocumentationExtension; +import org.springframework.restdocs.mockmvc.MockMvcRestDocumentation; +import org.springframework.restdocs.mockmvc.RestDocumentationResultHandler; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; + +@ActiveProfiles("test") +@SpringBootTest +@Import(RestDocsConfiguration.class) +@ExtendWith(RestDocumentationExtension.class) +class CategoryApiControllerTest { + + private MockMvc mockMvc; + + @Autowired + protected RestDocumentationResultHandler restDocs; + + @Autowired + protected ObjectMapper objectMapper; + + @Autowired + private JwtProvider jwtProvider; + + @MockBean + private CategoryService categoryService; + + private String accessToken; + + private static final String BASE_URL = "/api/categories"; + + @BeforeEach + void setUp( + final RestDocumentationContextProvider provider + ) { + mockMvc = MockMvcBuilders + .standaloneSetup(new CategoryApiController(categoryService)) + .setCustomArgumentResolvers(new PageableHandlerMethodArgumentResolver()) + .apply(MockMvcRestDocumentation.documentationConfiguration(provider)) + .alwaysDo(restDocs) + .build(); + + Member member = new Builder().id(1L).name("회원01").email(Email.from("member01@gmail.com")) + .build(); + accessToken = jwtProvider.generateToken(member).getValue(); + } + + + @Test + @DisplayName("카테고리 생성") + void createCategory() throws Exception { + CreateCategoryRequest request = new CreateCategoryRequest("카테고리01", + "카테고리01 설명", "https://via.placeholder.com/150", "#FFFFFF"); + + String content = objectMapper.writeValueAsString(request); + + given(categoryService.createCategory(any(CreateCategoryRequest.class))) + .willReturn(new CreateCategoryResponse(1L, "카테고리01", "카테고리01 설명", "https://via.placeholder.com/150", "#FFFFFF")); + + mockMvc + .perform( + post(BASE_URL) + .header(HttpHeaders.AUTHORIZATION, "Bearer " + accessToken) + .content(content) + .contentType(MediaType.APPLICATION_JSON) + ) + .andExpect(status().isCreated()) + .andDo( + restDocs.document( + requestFields( + fieldWithPath("name").description("카테고리명"), + fieldWithPath("description").description("카테고리 설명"), + fieldWithPath("imageUrl").description("이미지 URL"), + fieldWithPath("color").description("색상 코드") + ), + responseFields( + fieldWithPath("id").description("카테고리 ID"), + fieldWithPath("name").description("카테고리명"), + fieldWithPath("description").description("카테고리 설명"), + fieldWithPath("imageUrl").description("이미지 URL"), + fieldWithPath("color").description("색상 코드") + ) + ) + ); + } + + @Test + @DisplayName("모든 카테고리 조회") + void readAllCategories() throws Exception { + given(categoryService.readAllCategories(any())) + .willReturn(new ReadAllCategoriesResponse(List.of( + new ReadCategoryResponse(1L, "카테고리01", "카테고리01 설명", "https://via.placeholder.com/150", "#FFFFFF"), + new ReadCategoryResponse(2L, "카테고리02", "카테고리02 설명", "https://via.placeholder.com/150", "#FFFFFF") + ))); + + mockMvc + .perform( + get(BASE_URL) + .header(HttpHeaders.AUTHORIZATION, "Bearer " + accessToken) + ) + .andExpect(status().isOk()) + .andDo( + restDocs.document( + queryParameters( + parameterWithName("page").optional().description("페이지 번호"), + parameterWithName("size").optional().description("페이지 크기"), + parameterWithName("sort").optional().description("정렬 조건") + ), + responseFields( + fieldWithPath("categories[].id").description("카테고리 ID"), + fieldWithPath("categories[].name").description("카테고리명"), + fieldWithPath("categories[].description").description("카테고리 설명"), + fieldWithPath("categories[].imageUrl").description("이미지 URL"), + fieldWithPath("categories[].color").description("색상 코드") + ) + ) + ); + } + + @Test + @DisplayName("단일 카테고리 조회") + void readCategory() throws Exception { + given(categoryService.readCategory(any(Long.class))) + .willReturn(new ReadCategoryResponse(1L, "카테고리01", "카테고리01 설명", "https://via.placeholder.com/150", "#FFFFFF")); + + mockMvc + .perform( + get(BASE_URL + "/{id}", 1L) + .header(HttpHeaders.AUTHORIZATION, "Bearer " + accessToken) + ) + .andExpect(status().isOk()) + .andDo( + restDocs.document( + responseFields( + fieldWithPath("id").description("카테고리 ID"), + fieldWithPath("name").description("카테고리명"), + fieldWithPath("description").description("카테고리 설명"), + fieldWithPath("imageUrl").description("이미지 URL"), + fieldWithPath("color").description("색상 코드") + ) + ) + ); + } + + @Test + @DisplayName("카테고리 수정") + void updateCategory() throws Exception { + UpdateCategoryRequest request = new UpdateCategoryRequest("카테고리01", "카테고리01 설명", + "https://via.placeholder.com/150", "#FFFFFF"); + + String content = objectMapper.writeValueAsString(request); + + given(categoryService.updateCategory(any(Long.class), any(UpdateCategoryRequest.class))) + .willReturn(new UpdateCategoryResponse(1L, "카테고리01", "카테고리01 설명", "https://via.placeholder.com/150", "#FFFFFF")); + + mockMvc + .perform( + put(BASE_URL + "/{id}", 1L) + .header(HttpHeaders.AUTHORIZATION, "Bearer " + accessToken) + .content(content) + .contentType(MediaType.APPLICATION_JSON) + ) + .andExpect(status().isOk()) + .andDo( + restDocs.document( + requestFields( + fieldWithPath("name").description("카테고리명"), + fieldWithPath("description").description("카테고리 설명"), + fieldWithPath("imageUrl").description("이미지 URL"), + fieldWithPath("color").description("색상 코드") + ), + responseFields( + fieldWithPath("id").description("카테고리 ID"), + fieldWithPath("name").description("카테고리명"), + fieldWithPath("description").description("카테고리 설명"), + fieldWithPath("imageUrl").description("이미지 URL"), + fieldWithPath("color").description("색상 코드") + ) + ) + ); + } + + @Test + void deleteCategory() throws Exception { + mockMvc + .perform( + delete(BASE_URL + "/{id}", 1L) + .header(HttpHeaders.AUTHORIZATION, "Bearer " + accessToken) + ) + .andExpect(status().isNoContent()) + .andDo( + restDocs.document() + ); + } +} \ No newline at end of file From 2654826c18eec8159d1d102129f7cec946c47eb7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=82=98=EC=A0=9C=EB=B2=95?= Date: Mon, 29 Jul 2024 22:43:49 +0900 Subject: [PATCH 122/138] =?UTF-8?q?docs:=20category.adoc=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/docs/asciidoc/category.adoc | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 src/docs/asciidoc/category.adoc diff --git a/src/docs/asciidoc/category.adoc b/src/docs/asciidoc/category.adoc new file mode 100644 index 000000000..2818b185a --- /dev/null +++ b/src/docs/asciidoc/category.adoc @@ -0,0 +1,19 @@ +== 카테고리 API + +=== 카테고리 생성 +operation::category-api-controller-test/create-category[snippets='http-request,http-response'] + +=== 카테고리 수정 +operation::category-api-controller-test/update-category[snippets='http-request,http-response'] + +=== 카테고리 삭제 +operation::category-api-controller-test/delete-category[snippets='http-request,http-response'] + +=== 단일 카테고리 조회 +operation::category-api-controller-test/read-category[snippets='http-request,http-response'] + +=== 전체 카테고리 조회 +operation::category-api-controller-test/read-all-categories[snippets='http-request,http-response'] + + + From 5354c0e851f4da1bbbd2ffad12ee1b68c69c1561 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=82=98=EC=A0=9C=EB=B2=95?= Date: Mon, 29 Jul 2024 22:43:56 +0900 Subject: [PATCH 123/138] =?UTF-8?q?docs:=20product.adoc=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/docs/asciidoc/product.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/docs/asciidoc/product.adoc b/src/docs/asciidoc/product.adoc index 91caa46ef..213535572 100644 --- a/src/docs/asciidoc/product.adoc +++ b/src/docs/asciidoc/product.adoc @@ -1,4 +1,4 @@ -== 상품 +== 상품 API === 전체 상품 조회 operation::product-api-controller-test/read-all-products[snippets='http-request,http-response'] From 8b8479cac7231141ede696cd775558b80b527deb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=82=98=EC=A0=9C=EB=B2=95?= Date: Mon, 29 Jul 2024 22:44:05 +0900 Subject: [PATCH 124/138] =?UTF-8?q?docs:=20docs.adoc=20=EC=B9=B4=ED=85=8C?= =?UTF-8?q?=EA=B3=A0=EB=A6=AC=20API=20=EB=B0=98=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/docs/asciidoc/docs.adoc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/docs/asciidoc/docs.adoc b/src/docs/asciidoc/docs.adoc index c3475228b..fd74fdae9 100644 --- a/src/docs/asciidoc/docs.adoc +++ b/src/docs/asciidoc/docs.adoc @@ -5,4 +5,5 @@ :toclevels: 3 :sectlinks: -include::product.adoc[] \ No newline at end of file +include::product.adoc[] +include::category.adoc[] \ No newline at end of file From 6d0e6ec4be8758a3678128648f033cf397fbf495 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=82=98=EC=A0=9C=EB=B2=95?= Date: Mon, 29 Jul 2024 23:22:43 +0900 Subject: [PATCH 125/138] =?UTF-8?q?test:=20MemberApiControllerTest.java=20?= =?UTF-8?q?E2E=20=ED=85=8C=EC=8A=A4=ED=8A=B8=EC=97=90=EC=84=9C=20=EC=BB=A8?= =?UTF-8?q?=ED=8A=B8=EB=A1=A4=EB=9F=AC=20=EB=8B=A8=EC=9C=84=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=EB=A1=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/MemberApiControllerTest.java | 380 ++++++++++-------- 1 file changed, 211 insertions(+), 169 deletions(-) diff --git a/src/test/java/gift/web/controller/api/MemberApiControllerTest.java b/src/test/java/gift/web/controller/api/MemberApiControllerTest.java index 85204f1ea..e8cf83d98 100644 --- a/src/test/java/gift/web/controller/api/MemberApiControllerTest.java +++ b/src/test/java/gift/web/controller/api/MemberApiControllerTest.java @@ -1,20 +1,27 @@ package gift.web.controller.api; -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertAll; -import static org.junit.jupiter.api.Assertions.assertIterableEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import gift.authentication.token.Token; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.BDDMockito.given; +import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath; +import static org.springframework.restdocs.payload.PayloadDocumentation.requestFields; +import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields; +import static org.springframework.restdocs.request.RequestDocumentation.parameterWithName; +import static org.springframework.restdocs.request.RequestDocumentation.queryParameters; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import com.fasterxml.jackson.databind.ObjectMapper; +import gift.authentication.token.JwtProvider; +import gift.config.RestDocsConfiguration; import gift.domain.Member; -import gift.repository.MemberRepository; +import gift.domain.Member.Builder; +import gift.domain.vo.Email; +import gift.mock.MockLoginMemberArgumentResolver; import gift.service.MemberService; import gift.service.WishProductService; -import gift.utils.CategoryDummyDataProvider; -import gift.utils.DatabaseCleanup; -import gift.utils.MemberDummyDataProvider; -import gift.utils.ProductDummyDataProvider; -import gift.utils.WishProductDummyDataProvider; import gift.web.dto.request.LoginRequest; import gift.web.dto.request.member.CreateMemberRequest; import gift.web.dto.request.wishproduct.UpdateWishProductRequest; @@ -22,205 +29,240 @@ import gift.web.dto.response.member.CreateMemberResponse; import gift.web.dto.response.member.ReadMemberResponse; import gift.web.dto.response.wishproduct.ReadAllWishProductsResponse; +import gift.web.dto.response.wishproduct.ReadWishProductResponse; import gift.web.dto.response.wishproduct.UpdateWishProductResponse; -import org.junit.jupiter.api.AfterEach; +import io.swagger.v3.core.util.Json; +import java.util.List; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; -import org.springframework.boot.test.web.client.TestRestTemplate; -import org.springframework.boot.test.web.server.LocalServerPort; -import org.springframework.data.domain.PageRequest; -import org.springframework.http.HttpEntity; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.context.annotation.Import; +import org.springframework.data.web.PageableHandlerMethodArgumentResolver; import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpMethod; -import org.springframework.http.ResponseEntity; +import org.springframework.http.MediaType; +import org.springframework.restdocs.RestDocumentationContextProvider; +import org.springframework.restdocs.RestDocumentationExtension; +import org.springframework.restdocs.mockmvc.MockMvcRestDocumentation; +import org.springframework.restdocs.mockmvc.RestDocumentationResultHandler; +import org.springframework.restdocs.payload.JsonFieldType; import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; @ActiveProfiles("test") -@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT) +@SpringBootTest +@Import(RestDocsConfiguration.class) +@ExtendWith(RestDocumentationExtension.class) class MemberApiControllerTest { - @LocalServerPort - private int port; - - @Autowired - private TestRestTemplate restTemplate; - - @Autowired - private MemberDummyDataProvider memberDummyDataProvider; - - @Autowired - private ProductDummyDataProvider productDummyDataProvider; + private MockMvc mockMvc; @Autowired - private WishProductDummyDataProvider wishProductDummyDataProvider; + protected RestDocumentationResultHandler restDocs; @Autowired - private CategoryDummyDataProvider categoryDummyDataProvider; + protected ObjectMapper objectMapper; @Autowired - private DatabaseCleanup databaseCleanup; + private JwtProvider jwtProvider; - @Autowired + @MockBean private MemberService memberService; - @Autowired - private MemberRepository memberRepository; - - @Autowired + @MockBean private WishProductService wishProductService; - //테스트용 회원 - private Member member; - private Token token; - - @BeforeEach - void setUp() { - insertDummyData(100); - member = getTestMember(1L); - token = getAccessToken(); - } - - private Member getTestMember(Long id) { - return memberRepository.findById(id) - .orElseThrow(() -> new IllegalArgumentException("ID: " + id +"인 회원이 존재하지 않습니다.")); - } - - private Token getAccessToken() { - LoginRequest loginRequest = new LoginRequest( - member.getEmail().getValue(), - member.getPassword().getValue() - ); - LoginResponse loginResponse = memberService.login(loginRequest); - return Token.from(loginResponse.getAccessToken()); - } + private String accessToken; - private void insertDummyData(int quantity) { - if (quantity < 2) { - throw new IllegalArgumentException("quantity는 2 이상이어야 합니다."); - } - memberDummyDataProvider.run(quantity); - productDummyDataProvider.run(quantity); - wishProductDummyDataProvider.run(quantity); - categoryDummyDataProvider.run(quantity); - } + private static final String BASE_URL = "/api/members"; - @AfterEach - void tearDown() { - databaseCleanup.execute(); + @BeforeEach + void setUp( + final RestDocumentationContextProvider provider + ) { + mockMvc = MockMvcBuilders + .standaloneSetup(new MemberApiController(memberService, wishProductService)) + .setCustomArgumentResolvers(new MockLoginMemberArgumentResolver(), new PageableHandlerMethodArgumentResolver()) + .apply(MockMvcRestDocumentation.documentationConfiguration(provider)) + .alwaysDo(restDocs) + .build(); + + Member member = new Builder().id(1L).name("회원01").email(Email.from("member01@gmail.com")) + .build(); + accessToken = jwtProvider.generateToken(member).getValue(); } @Test - @DisplayName("회원 생성 요청에 대한 정상 응답") - void createMember() { - //given - CreateMemberRequest request = new CreateMemberRequest("test@gmail.com", "test1234", "test"); - String url = "http://localhost:" + port + "/api/members/register"; - - //when - ResponseEntity response = restTemplate.postForEntity(url, request, CreateMemberResponse.class); - - //then - Long newMemberId = response.getBody().getId(); - ReadMemberResponse findMember = memberService.readMember(newMemberId); - - assertAll( - () -> assertTrue(response.getStatusCode().is2xxSuccessful()), - () -> assertThat(newMemberId).isEqualTo(findMember.getId()), - () -> assertThat(request.getEmail()).isEqualTo(findMember.getEmail()), - () -> assertThat(request.getName()).isEqualTo(findMember.getName()), - () -> assertThat(request.getPassword()).isEqualTo(findMember.getPassword()) - ); + @DisplayName("회원 가입") + void createMember() throws Exception { + CreateMemberRequest request = new CreateMemberRequest("member01@gmail.com", "password01", + "member01"); + + String content = objectMapper.writeValueAsString(request); + + given(memberService.createMember(any())) + .willReturn(new CreateMemberResponse(1L, "member01@gmail.com", "member01")); + + mockMvc + .perform( + post(BASE_URL + "/register") + .header(HttpHeaders.AUTHORIZATION, "Bearer " + accessToken) + .contentType(MediaType.APPLICATION_JSON) + .content(content) + ) + .andExpect(status().isCreated()) + .andDo( + restDocs.document( + requestFields( + fieldWithPath("email").type(JsonFieldType.STRING).description("이메일"), + fieldWithPath("password").type(JsonFieldType.STRING).description("비밀번호"), + fieldWithPath("name").type(JsonFieldType.STRING).description("이름") + ), + responseFields( + fieldWithPath("id").type(JsonFieldType.NUMBER).description("회원 식별자"), + fieldWithPath("email").type(JsonFieldType.STRING).description("이메일"), + fieldWithPath("name").type(JsonFieldType.STRING).description("이름") + ) + ) + ); } @Test - @DisplayName("로그인 요청에 대한 정상 응답") - void login() { - //given - String url = "http://localhost:" + port + "/api/members/login"; - String email = member.getEmail().getValue(); - String password = member.getPassword().getValue(); - LoginRequest request = new LoginRequest(email, password); - - //when - ResponseEntity response = restTemplate.postForEntity(url, request, LoginResponse.class); - - //then - assertAll( - () -> assertTrue(response.getStatusCode().is2xxSuccessful()), - () -> assertThat(response.getBody().getAccessToken()).isNotNull() - ); + @DisplayName("로그인") + void login() throws Exception { + LoginRequest request = new LoginRequest("member01@gmail.com", "password01"); + String content = objectMapper.writeValueAsString(request); + + given(memberService.login(any())) + .willReturn(new LoginResponse(accessToken)); + + mockMvc + .perform( + post(BASE_URL + "/login") + .contentType(MediaType.APPLICATION_JSON) + .content(content) + ) + .andExpect(status().isOk()) + .andDo( + restDocs.document( + requestFields( + fieldWithPath("email").type(JsonFieldType.STRING).description("이메일"), + fieldWithPath("password").type(JsonFieldType.STRING).description("비밀번호") + ), + responseFields( + fieldWithPath("accessToken").type(JsonFieldType.STRING).description("Bearer Token") + ) + ) + ); } @Test - @DisplayName("위시 리스트 조회 요청에 대한 정상 응답") - void readWishProduct() { - //given - String url = "http://localhost:" + port + "/api/members/wishlist"; - HttpHeaders httpHeaders = getHttpHeaders(); - HttpEntity httpEntity = new HttpEntity(httpHeaders); - - PageRequest defaultPageRequest = PageRequest.of(0, 10); - ReadAllWishProductsResponse expectedWishProducts = wishProductService.readAllWishProducts(member.getId(), - defaultPageRequest); - - //when - ResponseEntity response = restTemplate.exchange(url, - HttpMethod.GET, httpEntity, ReadAllWishProductsResponse.class); - - //then - assertAll( - () -> assertTrue(response.getStatusCode().is2xxSuccessful()), - () -> assertIterableEquals(response.getBody().getWishlist(), expectedWishProducts.getWishlist()) - ); + @DisplayName("회원 조회") + void readMember() throws Exception { + given(memberService.readMember(any(Long.class))) + .willReturn(new ReadMemberResponse(1L, "member01@gmail.com", "password01", "member01")); + + mockMvc + .perform( + get(BASE_URL + "/{memberId}", 1L) + .header(HttpHeaders.AUTHORIZATION, "Bearer " + accessToken) + ) + .andExpect(status().isOk()) + .andDo( + restDocs.document( + responseFields( + fieldWithPath("id").type(JsonFieldType.NUMBER).description("회원 식별자"), + fieldWithPath("email").type(JsonFieldType.STRING).description("이메일"), + fieldWithPath("password").type(JsonFieldType.STRING).description("비밀번호"), + fieldWithPath("name").type(JsonFieldType.STRING).description("이름") + ) + ) + ); } @Test - @DisplayName("위시 리스트 상품 수정 요청에 대한 정상 응답") - void updateWishProduct() { - //given - Long wishProductId = 1L; - String url = "http://localhost:" + port + "/api/members/wishlist/" + wishProductId; - HttpHeaders httpHeaders = getHttpHeaders(); - - UpdateWishProductRequest request = new UpdateWishProductRequest(3); - - HttpEntity httpEntity = new HttpEntity(request, httpHeaders); - - //when - ResponseEntity response = restTemplate.exchange(url, - HttpMethod.PUT, httpEntity, UpdateWishProductResponse.class); - - //then - assertAll( - () -> assertTrue(response.getStatusCode().is2xxSuccessful()), - () -> assertThat(response.getBody().getQuantity()).isEqualTo(request.getQuantity()) + @DisplayName("위시 상품 조회") + void readWishProduct() throws Exception { + ReadAllWishProductsResponse response = new ReadAllWishProductsResponse( + List.of(new ReadWishProductResponse(1L, 1L, "product01", 1000, 5, "https://via.placeholder.com/150")) ); + + given(wishProductService.readAllWishProducts(any(Long.class), any())) + .willReturn(response); + + mockMvc + .perform( + get(BASE_URL + "/wishlist") + .header(HttpHeaders.AUTHORIZATION, "Bearer " + accessToken) + ) + .andExpect(status().isOk()) + .andDo( + restDocs.document( + queryParameters( + parameterWithName("page").optional().description("페이지 번호"), + parameterWithName("size").optional().description("페이지 크기") + ), + responseFields( + fieldWithPath("wishlist[].id").type(JsonFieldType.NUMBER).description("위시 상품 식별자"), + fieldWithPath("wishlist[].productId").type(JsonFieldType.NUMBER).description("상품 식별자"), + fieldWithPath("wishlist[].name").type(JsonFieldType.STRING).description("상품명"), + fieldWithPath("wishlist[].price").type(JsonFieldType.NUMBER).description("가격"), + fieldWithPath("wishlist[].quantity").type(JsonFieldType.NUMBER).description("재고 수량"), + fieldWithPath("wishlist[].imageUrl").type(JsonFieldType.STRING).description("이미지 URL") + ) + ) + ); } @Test - @DisplayName("위시 리스트 상품 삭제 요청에 대한 정상 응답") - void deleteWishProduct() { - //given - Long wishProductId = 2L; - String url = "http://localhost:" + port + "/api/members/wishlist/" + wishProductId; - HttpHeaders httpHeaders = getHttpHeaders(); - HttpEntity httpEntity = new HttpEntity(httpHeaders); - - //when - ResponseEntity response = restTemplate.exchange(url, HttpMethod.DELETE, httpEntity, - Void.class); - - //then - assertTrue(response.getStatusCode().is2xxSuccessful()); + @DisplayName("위시 상품 수정") + void updateWishProduct() throws Exception { + UpdateWishProductRequest request = new UpdateWishProductRequest(5); + String content = Json.mapper().writeValueAsString(request); + + given(wishProductService.updateWishProduct(any(Long.class), any())) + .willReturn(new UpdateWishProductResponse(1L, 1L, "product01", 1000, 5, "https://via.placeholder.com/150")); + + mockMvc + .perform( + put(BASE_URL + "/wishlist/{wishProductId}", 1L) + .header(HttpHeaders.AUTHORIZATION, "Bearer " + accessToken) + .contentType(MediaType.APPLICATION_JSON) + .content(content) + ) + .andExpect(status().isOk()) + .andDo( + restDocs.document( + requestFields( + fieldWithPath("quantity").type(JsonFieldType.NUMBER).description("수량") + ), + responseFields( + fieldWithPath("id").type(JsonFieldType.NUMBER).description("위시 상품 식별자"), + fieldWithPath("productId").type(JsonFieldType.NUMBER).description("상품 식별자"), + fieldWithPath("name").type(JsonFieldType.STRING).description("상품명"), + fieldWithPath("price").type(JsonFieldType.NUMBER).description("가격"), + fieldWithPath("quantity").type(JsonFieldType.NUMBER).description("재고 수량"), + fieldWithPath("imageUrl").type(JsonFieldType.STRING).description("이미지 URL") + ) + ) + ); } - private HttpHeaders getHttpHeaders() { - HttpHeaders httpHeaders = new HttpHeaders(); - httpHeaders.setBearerAuth(token.getValue()); - return httpHeaders; + @Test + @DisplayName("위시 상품 삭제") + void deleteWishProduct() throws Exception { + mockMvc + .perform( + delete(BASE_URL + "/wishlist/{wishProductId}", 1L) + .header(HttpHeaders.AUTHORIZATION, "Bearer " + accessToken) + ) + .andExpect(status().isNoContent()) + .andDo( + restDocs.document() + ); } } \ No newline at end of file From 058d2691b27bf15fea7a91d36e5361b5d0ee9929 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=82=98=EC=A0=9C=EB=B2=95?= Date: Mon, 29 Jul 2024 23:22:56 +0900 Subject: [PATCH 126/138] =?UTF-8?q?docs:=20member.adoc=20=ED=9A=8C?= =?UTF-8?q?=EC=9B=90=20API=20=EB=AA=85=EC=84=B8=EC=84=9C=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/docs/asciidoc/member.adoc | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 src/docs/asciidoc/member.adoc diff --git a/src/docs/asciidoc/member.adoc b/src/docs/asciidoc/member.adoc new file mode 100644 index 000000000..63caf96ba --- /dev/null +++ b/src/docs/asciidoc/member.adoc @@ -0,0 +1,19 @@ +== 회원 API + +=== 회원가입 +operation::member-api-controller-test/create-member[snippets='http-request,http-response'] + +=== 로그인 +operation::member-api-controller-test/login[snippets='http-request,http-response'] + +=== 회원 조회 +operation::member-api-controller-test/read-member[snippets='http-request,http-response'] + +=== 위시 상품 조회 +operation::member-api-controller-test/read-wish-product[snippets='http-request,http-response'] + +=== 위시 상품 수정 +operation::member-api-controller-test/update-wish-product[snippets='http-request,http-response'] + +=== 위시 상품 삭제 +operation::member-api-controller-test/delete-wish-product[snippets='http-request,http-response'] From 47cfda3c0f1c09bdace13e0b8f009e5cae336b59 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=82=98=EC=A0=9C=EB=B2=95?= Date: Mon, 29 Jul 2024 23:23:09 +0900 Subject: [PATCH 127/138] =?UTF-8?q?docs:=20docs.adoc=20=ED=9A=8C=EC=9B=90?= =?UTF-8?q?=20API=20=EB=AA=85=EC=84=B8=EC=84=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/docs/asciidoc/docs.adoc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/docs/asciidoc/docs.adoc b/src/docs/asciidoc/docs.adoc index fd74fdae9..7f300bc95 100644 --- a/src/docs/asciidoc/docs.adoc +++ b/src/docs/asciidoc/docs.adoc @@ -6,4 +6,5 @@ :sectlinks: include::product.adoc[] -include::category.adoc[] \ No newline at end of file +include::category.adoc[] +include::member.adoc[] \ No newline at end of file From cb6fcaf24e2855d6c66dbeba0aef2deed287d0cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=82=98=EC=A0=9C=EB=B2=95?= Date: Mon, 29 Jul 2024 23:23:32 +0900 Subject: [PATCH 128/138] =?UTF-8?q?refactor:=20CategoryApiControllerTest.j?= =?UTF-8?q?ava=20=EC=9A=94=EC=B2=AD/=EC=9D=91=EB=8B=B5=20=ED=83=80?= =?UTF-8?q?=EC=9E=85=20=EC=A7=80=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/CategoryApiControllerTest.java | 57 ++++++++++--------- 1 file changed, 29 insertions(+), 28 deletions(-) diff --git a/src/test/java/gift/web/controller/api/CategoryApiControllerTest.java b/src/test/java/gift/web/controller/api/CategoryApiControllerTest.java index c904edd50..b8a0d2acc 100644 --- a/src/test/java/gift/web/controller/api/CategoryApiControllerTest.java +++ b/src/test/java/gift/web/controller/api/CategoryApiControllerTest.java @@ -48,6 +48,7 @@ import org.springframework.restdocs.RestDocumentationExtension; import org.springframework.restdocs.mockmvc.MockMvcRestDocumentation; import org.springframework.restdocs.mockmvc.RestDocumentationResultHandler; +import org.springframework.restdocs.payload.JsonFieldType; import org.springframework.test.context.ActiveProfiles; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.setup.MockMvcBuilders; @@ -115,17 +116,17 @@ void createCategory() throws Exception { .andDo( restDocs.document( requestFields( - fieldWithPath("name").description("카테고리명"), - fieldWithPath("description").description("카테고리 설명"), - fieldWithPath("imageUrl").description("이미지 URL"), - fieldWithPath("color").description("색상 코드") + fieldWithPath("name").type(JsonFieldType.STRING).description("카테고리명"), + fieldWithPath("description").type(JsonFieldType.STRING).description("카테고리 설명"), + fieldWithPath("imageUrl").type(JsonFieldType.STRING).description("이미지 URL"), + fieldWithPath("color").type(JsonFieldType.STRING).description("색상 코드") ), responseFields( - fieldWithPath("id").description("카테고리 ID"), - fieldWithPath("name").description("카테고리명"), - fieldWithPath("description").description("카테고리 설명"), - fieldWithPath("imageUrl").description("이미지 URL"), - fieldWithPath("color").description("색상 코드") + fieldWithPath("id").type(JsonFieldType.NUMBER).description("카테고리 ID"), + fieldWithPath("name").type(JsonFieldType.STRING).description("카테고리명"), + fieldWithPath("description").type(JsonFieldType.STRING).description("카테고리 설명"), + fieldWithPath("imageUrl").type(JsonFieldType.STRING).description("이미지 URL"), + fieldWithPath("color").type(JsonFieldType.STRING).description("색상 코드") ) ) ); @@ -154,11 +155,11 @@ void readAllCategories() throws Exception { parameterWithName("sort").optional().description("정렬 조건") ), responseFields( - fieldWithPath("categories[].id").description("카테고리 ID"), - fieldWithPath("categories[].name").description("카테고리명"), - fieldWithPath("categories[].description").description("카테고리 설명"), - fieldWithPath("categories[].imageUrl").description("이미지 URL"), - fieldWithPath("categories[].color").description("색상 코드") + fieldWithPath("categories[].id").type(JsonFieldType.NUMBER).description("카테고리 ID"), + fieldWithPath("categories[].name").type(JsonFieldType.STRING).description("카테고리명"), + fieldWithPath("categories[].description").type(JsonFieldType.STRING).description("카테고리 설명"), + fieldWithPath("categories[].imageUrl").type(JsonFieldType.STRING).description("이미지 URL"), + fieldWithPath("categories[].color").type(JsonFieldType.STRING).description("색상 코드") ) ) ); @@ -179,11 +180,11 @@ void readCategory() throws Exception { .andDo( restDocs.document( responseFields( - fieldWithPath("id").description("카테고리 ID"), - fieldWithPath("name").description("카테고리명"), - fieldWithPath("description").description("카테고리 설명"), - fieldWithPath("imageUrl").description("이미지 URL"), - fieldWithPath("color").description("색상 코드") + fieldWithPath("id").type(JsonFieldType.NUMBER).description("카테고리 ID"), + fieldWithPath("name").type(JsonFieldType.STRING).description("카테고리명"), + fieldWithPath("description").type(JsonFieldType.STRING).description("카테고리 설명"), + fieldWithPath("imageUrl").type(JsonFieldType.STRING).description("이미지 URL"), + fieldWithPath("color").type(JsonFieldType.STRING).description("색상 코드") ) ) ); @@ -211,17 +212,17 @@ void updateCategory() throws Exception { .andDo( restDocs.document( requestFields( - fieldWithPath("name").description("카테고리명"), - fieldWithPath("description").description("카테고리 설명"), - fieldWithPath("imageUrl").description("이미지 URL"), - fieldWithPath("color").description("색상 코드") + fieldWithPath("name").type(JsonFieldType.STRING).description("카테고리명"), + fieldWithPath("description").type(JsonFieldType.STRING).description("카테고리 설명"), + fieldWithPath("imageUrl").type(JsonFieldType.STRING).description("이미지 URL"), + fieldWithPath("color").type(JsonFieldType.STRING).description("색상 코드") ), responseFields( - fieldWithPath("id").description("카테고리 ID"), - fieldWithPath("name").description("카테고리명"), - fieldWithPath("description").description("카테고리 설명"), - fieldWithPath("imageUrl").description("이미지 URL"), - fieldWithPath("color").description("색상 코드") + fieldWithPath("id").type(JsonFieldType.NUMBER).description("카테고리 ID"), + fieldWithPath("name").type(JsonFieldType.STRING).description("카테고리명"), + fieldWithPath("description").type(JsonFieldType.STRING).description("카테고리 설명"), + fieldWithPath("imageUrl").type(JsonFieldType.STRING).description("이미지 URL"), + fieldWithPath("color").type(JsonFieldType.STRING).description("색상 코드") ) ) ); From d1ad051bed8a1df6d81a6eea6dc920104bd86c0f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=82=98=EC=A0=9C=EB=B2=95?= Date: Mon, 29 Jul 2024 23:53:51 +0900 Subject: [PATCH 129/138] =?UTF-8?q?docs:=20login.adoc=20=EC=86=8C=EC=85=9C?= =?UTF-8?q?=20=EB=A1=9C=EA=B7=B8=EC=9D=B8=20API=20=EB=AA=85=EC=84=B8=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/docs/asciidoc/login.adoc | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 src/docs/asciidoc/login.adoc diff --git a/src/docs/asciidoc/login.adoc b/src/docs/asciidoc/login.adoc new file mode 100644 index 000000000..a31559600 --- /dev/null +++ b/src/docs/asciidoc/login.adoc @@ -0,0 +1,4 @@ +== 소셜 로그인 API + +=== 카카오 로그인 +operation::login-controller-test/kakao-login[] \ No newline at end of file From fba74634dc67b7e0fe76a1f4adfa6c1ececeac92 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=82=98=EC=A0=9C=EB=B2=95?= Date: Mon, 29 Jul 2024 23:54:24 +0900 Subject: [PATCH 130/138] =?UTF-8?q?docs:=20API=20=EB=AA=85=EC=84=B8?= =?UTF-8?q?=EC=84=9C=20snippet=20=EB=AA=A8=EB=91=90=20=EB=B3=B4=EC=9D=B4?= =?UTF-8?q?=EB=8F=84=EB=A1=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/docs/asciidoc/category.adoc | 10 +++++----- src/docs/asciidoc/member.adoc | 12 ++++++------ src/docs/asciidoc/product.adoc | 22 +++++++++++----------- 3 files changed, 22 insertions(+), 22 deletions(-) diff --git a/src/docs/asciidoc/category.adoc b/src/docs/asciidoc/category.adoc index 2818b185a..76b342098 100644 --- a/src/docs/asciidoc/category.adoc +++ b/src/docs/asciidoc/category.adoc @@ -1,19 +1,19 @@ == 카테고리 API === 카테고리 생성 -operation::category-api-controller-test/create-category[snippets='http-request,http-response'] +operation::category-api-controller-test/create-category[] === 카테고리 수정 -operation::category-api-controller-test/update-category[snippets='http-request,http-response'] +operation::category-api-controller-test/update-category[] === 카테고리 삭제 -operation::category-api-controller-test/delete-category[snippets='http-request,http-response'] +operation::category-api-controller-test/delete-category[] === 단일 카테고리 조회 -operation::category-api-controller-test/read-category[snippets='http-request,http-response'] +operation::category-api-controller-test/read-category[] === 전체 카테고리 조회 -operation::category-api-controller-test/read-all-categories[snippets='http-request,http-response'] +operation::category-api-controller-test/read-all-categories[] diff --git a/src/docs/asciidoc/member.adoc b/src/docs/asciidoc/member.adoc index 63caf96ba..5676d7a93 100644 --- a/src/docs/asciidoc/member.adoc +++ b/src/docs/asciidoc/member.adoc @@ -1,19 +1,19 @@ == 회원 API === 회원가입 -operation::member-api-controller-test/create-member[snippets='http-request,http-response'] +operation::member-api-controller-test/create-member[] === 로그인 -operation::member-api-controller-test/login[snippets='http-request,http-response'] +operation::member-api-controller-test/login[] === 회원 조회 -operation::member-api-controller-test/read-member[snippets='http-request,http-response'] +operation::member-api-controller-test/read-member[] === 위시 상품 조회 -operation::member-api-controller-test/read-wish-product[snippets='http-request,http-response'] +operation::member-api-controller-test/read-wish-product[] === 위시 상품 수정 -operation::member-api-controller-test/update-wish-product[snippets='http-request,http-response'] +operation::member-api-controller-test/update-wish-product[] === 위시 상품 삭제 -operation::member-api-controller-test/delete-wish-product[snippets='http-request,http-response'] +operation::member-api-controller-test/delete-wish-product[] diff --git a/src/docs/asciidoc/product.adoc b/src/docs/asciidoc/product.adoc index 213535572..65987e449 100644 --- a/src/docs/asciidoc/product.adoc +++ b/src/docs/asciidoc/product.adoc @@ -1,35 +1,35 @@ == 상품 API === 전체 상품 조회 -operation::product-api-controller-test/read-all-products[snippets='http-request,http-response'] +operation::product-api-controller-test/read-all-products[] === 단일 상품 조회 -operation::product-api-controller-test/read-product[snippets='http-request,http-response'] +operation::product-api-controller-test/read-product[] === 상품 생성 -operation::product-api-controller-test/create-product[snippets='http-request,http-response'] +operation::product-api-controller-test/create-product[] === 상품 수정 -operation::product-api-controller-test/update-product[snippets='http-request,http-response'] +operation::product-api-controller-test/update-product[] === 상품 삭제 -operation::product-api-controller-test/delete-product[snippets='http-request,http-response'] +operation::product-api-controller-test/delete-product[] === 상품 주문 -operation::product-api-controller-test/order-product[snippets='http-request,http-response'] +operation::product-api-controller-test/order-product[] === 위시 상품 추가 -operation::product-api-controller-test/create-wish-product[snippets='http-request,http-response'] +operation::product-api-controller-test/create-wish-product[] === 상품 옵션 생성 -operation::product-api-controller-test/create-option[snippets='http-request,http-response'] +operation::product-api-controller-test/create-option[] === 상품 옵션 조회 -operation::product-api-controller-test/read-options[snippets='http-request,http-response'] +operation::product-api-controller-test/read-options[] === 상품 옵션 수정 -operation::product-api-controller-test/update-option[snippets='http-request,http-response'] +operation::product-api-controller-test/update-option[] === 상품 옵션 삭제 -operation::product-api-controller-test/delete-option[snippets='http-request,http-response'] +operation::product-api-controller-test/delete-option[] From aee03e19fec93cf23a0561a82fde556f5ee96da5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=82=98=EC=A0=9C=EB=B2=95?= Date: Mon, 29 Jul 2024 23:54:48 +0900 Subject: [PATCH 131/138] =?UTF-8?q?test:=20MemberApiControllerTest.java=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/gift/web/controller/api/MemberApiControllerTest.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/test/java/gift/web/controller/api/MemberApiControllerTest.java b/src/test/java/gift/web/controller/api/MemberApiControllerTest.java index e8cf83d98..3cd366a8d 100644 --- a/src/test/java/gift/web/controller/api/MemberApiControllerTest.java +++ b/src/test/java/gift/web/controller/api/MemberApiControllerTest.java @@ -110,7 +110,6 @@ void createMember() throws Exception { mockMvc .perform( post(BASE_URL + "/register") - .header(HttpHeaders.AUTHORIZATION, "Bearer " + accessToken) .contentType(MediaType.APPLICATION_JSON) .content(content) ) From f209b7e1fdb82f9c959ef1c960bd959a0bee0d44 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=82=98=EC=A0=9C=EB=B2=95?= Date: Mon, 29 Jul 2024 23:55:05 +0900 Subject: [PATCH 132/138] style: CategoryApiControllerTest.java remove unused import --- .../gift/web/controller/api/CategoryApiControllerTest.java | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/test/java/gift/web/controller/api/CategoryApiControllerTest.java b/src/test/java/gift/web/controller/api/CategoryApiControllerTest.java index b8a0d2acc..20563b011 100644 --- a/src/test/java/gift/web/controller/api/CategoryApiControllerTest.java +++ b/src/test/java/gift/web/controller/api/CategoryApiControllerTest.java @@ -1,6 +1,5 @@ package gift.web.controller.api; -import static org.junit.jupiter.api.Assertions.*; import static org.mockito.ArgumentMatchers.any; import static org.mockito.BDDMockito.given; import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.post; @@ -20,12 +19,7 @@ import gift.domain.Member; import gift.domain.Member.Builder; import gift.domain.vo.Email; -import gift.mock.MockLoginMemberArgumentResolver; import gift.service.CategoryService; -import gift.service.OrderService; -import gift.service.ProductOptionService; -import gift.service.ProductService; -import gift.service.WishProductService; import gift.web.dto.request.category.CreateCategoryRequest; import gift.web.dto.request.category.UpdateCategoryRequest; import gift.web.dto.response.category.CreateCategoryResponse; From 83d2d998d17da0876b8fe53d91562ff07b4fd9be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=82=98=EC=A0=9C=EB=B2=95?= Date: Mon, 29 Jul 2024 23:55:25 +0900 Subject: [PATCH 133/138] =?UTF-8?q?docs:=20docs.adoc=20=EC=86=8C=EC=85=9C?= =?UTF-8?q?=20=EB=A1=9C=EA=B7=B8=EC=9D=B8=20API=20=EB=AA=85=EC=84=B8=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/docs/asciidoc/docs.adoc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/docs/asciidoc/docs.adoc b/src/docs/asciidoc/docs.adoc index 7f300bc95..5ebd45ef9 100644 --- a/src/docs/asciidoc/docs.adoc +++ b/src/docs/asciidoc/docs.adoc @@ -7,4 +7,5 @@ include::product.adoc[] include::category.adoc[] -include::member.adoc[] \ No newline at end of file +include::member.adoc[] +include::login.adoc[] \ No newline at end of file From 7cac263a88b79b9c9b08f4fc0e7e9288c90ce3b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=82=98=EC=A0=9C=EB=B2=95?= Date: Mon, 29 Jul 2024 23:59:53 +0900 Subject: [PATCH 134/138] =?UTF-8?q?docs:=20LoginControllerTest.java=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/api/LoginControllerTest.java | 84 +++++++++++++++++++ 1 file changed, 84 insertions(+) create mode 100644 src/test/java/gift/web/controller/api/LoginControllerTest.java diff --git a/src/test/java/gift/web/controller/api/LoginControllerTest.java b/src/test/java/gift/web/controller/api/LoginControllerTest.java new file mode 100644 index 000000000..0e08217f7 --- /dev/null +++ b/src/test/java/gift/web/controller/api/LoginControllerTest.java @@ -0,0 +1,84 @@ +package gift.web.controller.api; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.BDDMockito.given; +import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath; +import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields; +import static org.springframework.restdocs.request.RequestDocumentation.parameterWithName; +import static org.springframework.restdocs.request.RequestDocumentation.queryParameters; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import com.fasterxml.jackson.databind.ObjectMapper; +import gift.config.RestDocsConfiguration; +import gift.service.LoginService; +import gift.web.dto.response.LoginResponse; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.context.annotation.Import; +import org.springframework.restdocs.RestDocumentationContextProvider; +import org.springframework.restdocs.RestDocumentationExtension; +import org.springframework.restdocs.mockmvc.MockMvcRestDocumentation; +import org.springframework.restdocs.mockmvc.RestDocumentationResultHandler; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; + +@ActiveProfiles("test") +@SpringBootTest +@Import(RestDocsConfiguration.class) +@ExtendWith(RestDocumentationExtension.class) +class LoginControllerTest { + + private MockMvc mockMvc; + + @Autowired + protected RestDocumentationResultHandler restDocs; + + @Autowired + protected ObjectMapper objectMapper; + + @MockBean + private LoginService loginService; + + private static final String BASE_URL = "/api/login"; + + @BeforeEach + void setUp( + final RestDocumentationContextProvider provider + ) { + mockMvc = MockMvcBuilders + .standaloneSetup(new LoginController(loginService)) + .apply(MockMvcRestDocumentation.documentationConfiguration(provider)) + .alwaysDo(restDocs) + .build(); + } + + + @Test + void kakaoLogin() throws Exception { + given(loginService.kakaoLogin(any())) + .willReturn(new LoginResponse("Bearer {{access_token}}")); + + mockMvc + .perform( + get(BASE_URL + "/oauth2/kakao") + .param("code", "kakao-auth-code") + ) + .andExpect(status().isOk()) + .andDo( + restDocs.document( + queryParameters( + parameterWithName("code").optional().description("kakao-auth-code") + ), + responseFields( + fieldWithPath("accessToken").type(String.class).description("access-token(BEARER)") + ) + ) + ); + } +} \ No newline at end of file From f9ebc4d8bdf1283ddcf969219f72fb39a9f4cbce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=82=98=EC=A0=9C=EB=B2=95?= Date: Tue, 30 Jul 2024 00:00:14 +0900 Subject: [PATCH 135/138] =?UTF-8?q?fest:=20API=20=EB=AA=85=EC=84=B8=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/resources/static/category.html | 1149 +++++++ src/main/resources/static/docs.html | 3964 +++++++++++++++++++++++ src/main/resources/static/login.html | 561 ++++ src/main/resources/static/member.html | 1219 +++++++ src/main/resources/static/product.html | 2092 ++++++++++++ 5 files changed, 8985 insertions(+) create mode 100644 src/main/resources/static/category.html create mode 100644 src/main/resources/static/docs.html create mode 100644 src/main/resources/static/login.html create mode 100644 src/main/resources/static/member.html create mode 100644 src/main/resources/static/product.html diff --git a/src/main/resources/static/category.html b/src/main/resources/static/category.html new file mode 100644 index 000000000..ebb723429 --- /dev/null +++ b/src/main/resources/static/category.html @@ -0,0 +1,1149 @@ + + + + + + + +카테고리 API + + + + + +
+
+

카테고리 API

+
+
+

카테고리 생성

+
+

Curl request

+
+
+
$ curl 'http://localhost:8080/api/categories' -i -X POST \
+    -H 'Content-Type: application/json' \
+    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTU3LCJleHAiOjE3MjIyNjg3NTd9.TJZznQ-W-PQIhpYNTuIE6Frb4r9ZAK0C5c84c8TKJCTk7fFIPupn7RH2vdijJXlsTvpwid8QzsMwLBoKKQaCaQ' \
+    -d '{
+  "name" : "카테고리01",
+  "description" : "카테고리01 설명",
+  "imageUrl" : "https://via.placeholder.com/150",
+  "color" : "#FFFFFF"
+}'
+
+
+
+
+

HTTP request

+
+
+
POST /api/categories HTTP/1.1
+Content-Type: application/json
+Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTU3LCJleHAiOjE3MjIyNjg3NTd9.TJZznQ-W-PQIhpYNTuIE6Frb4r9ZAK0C5c84c8TKJCTk7fFIPupn7RH2vdijJXlsTvpwid8QzsMwLBoKKQaCaQ
+Content-Length: 147
+Host: localhost:8080
+
+{
+  "name" : "카테고리01",
+  "description" : "카테고리01 설명",
+  "imageUrl" : "https://via.placeholder.com/150",
+  "color" : "#FFFFFF"
+}
+
+
+
+
+

HTTP response

+
+
+
HTTP/1.1 201 Created
+Location: http://localhost:8080/api/categories/1
+Content-Type: application/json
+Content-Length: 159
+
+{
+  "id" : 1,
+  "name" : "카테고리01",
+  "description" : "카테고리01 설명",
+  "imageUrl" : "https://via.placeholder.com/150",
+  "color" : "#FFFFFF"
+}
+
+
+
+
+

HTTPie request

+
+
+
$ echo '{
+  "name" : "카테고리01",
+  "description" : "카테고리01 설명",
+  "imageUrl" : "https://via.placeholder.com/150",
+  "color" : "#FFFFFF"
+}' | http POST 'http://localhost:8080/api/categories' \
+    'Content-Type:application/json' \
+    'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTU3LCJleHAiOjE3MjIyNjg3NTd9.TJZznQ-W-PQIhpYNTuIE6Frb4r9ZAK0C5c84c8TKJCTk7fFIPupn7RH2vdijJXlsTvpwid8QzsMwLBoKKQaCaQ'
+
+
+
+
+

Request body

+
+
+
{
+  "name" : "카테고리01",
+  "description" : "카테고리01 설명",
+  "imageUrl" : "https://via.placeholder.com/150",
+  "color" : "#FFFFFF"
+}
+
+
+
+
+

Request fields

+ +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
PathTypeDescription

name

String

카테고리명

description

String

카테고리 설명

imageUrl

String

이미지 URL

color

String

색상 코드

+
+
+

Response body

+
+
+
{
+  "id" : 1,
+  "name" : "카테고리01",
+  "description" : "카테고리01 설명",
+  "imageUrl" : "https://via.placeholder.com/150",
+  "color" : "#FFFFFF"
+}
+
+
+
+
+

Response fields

+ +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
PathTypeDescription

id

Number

카테고리 ID

name

String

카테고리명

description

String

카테고리 설명

imageUrl

String

이미지 URL

color

String

색상 코드

+
+
+
+

카테고리 수정

+
+

Curl request

+
+
+
$ curl 'http://localhost:8080/api/categories/1' -i -X PUT \
+    -H 'Content-Type: application/json' \
+    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTU3LCJleHAiOjE3MjIyNjg3NTd9.TJZznQ-W-PQIhpYNTuIE6Frb4r9ZAK0C5c84c8TKJCTk7fFIPupn7RH2vdijJXlsTvpwid8QzsMwLBoKKQaCaQ' \
+    -d '{
+  "name" : "카테고리01",
+  "description" : "카테고리01 설명",
+  "imageUrl" : "https://via.placeholder.com/150",
+  "color" : "#FFFFFF"
+}'
+
+
+
+
+

HTTP request

+
+
+
PUT /api/categories/1 HTTP/1.1
+Content-Type: application/json
+Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTU3LCJleHAiOjE3MjIyNjg3NTd9.TJZznQ-W-PQIhpYNTuIE6Frb4r9ZAK0C5c84c8TKJCTk7fFIPupn7RH2vdijJXlsTvpwid8QzsMwLBoKKQaCaQ
+Content-Length: 147
+Host: localhost:8080
+
+{
+  "name" : "카테고리01",
+  "description" : "카테고리01 설명",
+  "imageUrl" : "https://via.placeholder.com/150",
+  "color" : "#FFFFFF"
+}
+
+
+
+
+

HTTP response

+
+
+
HTTP/1.1 200 OK
+Content-Type: application/json
+Content-Length: 159
+
+{
+  "id" : 1,
+  "name" : "카테고리01",
+  "description" : "카테고리01 설명",
+  "imageUrl" : "https://via.placeholder.com/150",
+  "color" : "#FFFFFF"
+}
+
+
+
+
+

HTTPie request

+
+
+
$ echo '{
+  "name" : "카테고리01",
+  "description" : "카테고리01 설명",
+  "imageUrl" : "https://via.placeholder.com/150",
+  "color" : "#FFFFFF"
+}' | http PUT 'http://localhost:8080/api/categories/1' \
+    'Content-Type:application/json' \
+    'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTU3LCJleHAiOjE3MjIyNjg3NTd9.TJZznQ-W-PQIhpYNTuIE6Frb4r9ZAK0C5c84c8TKJCTk7fFIPupn7RH2vdijJXlsTvpwid8QzsMwLBoKKQaCaQ'
+
+
+
+
+

Request body

+
+
+
{
+  "name" : "카테고리01",
+  "description" : "카테고리01 설명",
+  "imageUrl" : "https://via.placeholder.com/150",
+  "color" : "#FFFFFF"
+}
+
+
+
+
+

Request fields

+ +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
PathTypeDescription

name

String

카테고리명

description

String

카테고리 설명

imageUrl

String

이미지 URL

color

String

색상 코드

+
+
+

Response body

+
+
+
{
+  "id" : 1,
+  "name" : "카테고리01",
+  "description" : "카테고리01 설명",
+  "imageUrl" : "https://via.placeholder.com/150",
+  "color" : "#FFFFFF"
+}
+
+
+
+
+

Response fields

+ +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
PathTypeDescription

id

Number

카테고리 ID

name

String

카테고리명

description

String

카테고리 설명

imageUrl

String

이미지 URL

color

String

색상 코드

+
+
+
+

카테고리 삭제

+
+

Curl request

+
+
+
$ curl 'http://localhost:8080/api/categories/1' -i -X DELETE \
+    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTU3LCJleHAiOjE3MjIyNjg3NTd9.TJZznQ-W-PQIhpYNTuIE6Frb4r9ZAK0C5c84c8TKJCTk7fFIPupn7RH2vdijJXlsTvpwid8QzsMwLBoKKQaCaQ'
+
+
+
+
+

HTTP request

+
+
+
DELETE /api/categories/1 HTTP/1.1
+Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTU3LCJleHAiOjE3MjIyNjg3NTd9.TJZznQ-W-PQIhpYNTuIE6Frb4r9ZAK0C5c84c8TKJCTk7fFIPupn7RH2vdijJXlsTvpwid8QzsMwLBoKKQaCaQ
+Host: localhost:8080
+
+
+
+
+

HTTP response

+
+
+
HTTP/1.1 204 No Content
+
+
+
+
+

HTTPie request

+
+
+
$ http DELETE 'http://localhost:8080/api/categories/1' \
+    'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTU3LCJleHAiOjE3MjIyNjg3NTd9.TJZznQ-W-PQIhpYNTuIE6Frb4r9ZAK0C5c84c8TKJCTk7fFIPupn7RH2vdijJXlsTvpwid8QzsMwLBoKKQaCaQ'
+
+
+
+
+

Request body

+
+
+
+
+
+
+
+

Response body

+
+
+
+
+
+
+
+
+

단일 카테고리 조회

+
+

Curl request

+
+
+
$ curl 'http://localhost:8080/api/categories/1' -i -X GET \
+    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTU3LCJleHAiOjE3MjIyNjg3NTd9.TJZznQ-W-PQIhpYNTuIE6Frb4r9ZAK0C5c84c8TKJCTk7fFIPupn7RH2vdijJXlsTvpwid8QzsMwLBoKKQaCaQ'
+
+
+
+
+

HTTP request

+
+
+
GET /api/categories/1 HTTP/1.1
+Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTU3LCJleHAiOjE3MjIyNjg3NTd9.TJZznQ-W-PQIhpYNTuIE6Frb4r9ZAK0C5c84c8TKJCTk7fFIPupn7RH2vdijJXlsTvpwid8QzsMwLBoKKQaCaQ
+Host: localhost:8080
+
+
+
+
+

HTTP response

+
+
+
HTTP/1.1 200 OK
+Content-Type: application/json
+Content-Length: 159
+
+{
+  "id" : 1,
+  "name" : "카테고리01",
+  "description" : "카테고리01 설명",
+  "imageUrl" : "https://via.placeholder.com/150",
+  "color" : "#FFFFFF"
+}
+
+
+
+
+

HTTPie request

+
+
+
$ http GET 'http://localhost:8080/api/categories/1' \
+    'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTU3LCJleHAiOjE3MjIyNjg3NTd9.TJZznQ-W-PQIhpYNTuIE6Frb4r9ZAK0C5c84c8TKJCTk7fFIPupn7RH2vdijJXlsTvpwid8QzsMwLBoKKQaCaQ'
+
+
+
+
+

Request body

+
+
+
+
+
+
+
+

Response body

+
+
+
{
+  "id" : 1,
+  "name" : "카테고리01",
+  "description" : "카테고리01 설명",
+  "imageUrl" : "https://via.placeholder.com/150",
+  "color" : "#FFFFFF"
+}
+
+
+
+
+

Response fields

+ +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
PathTypeDescription

id

Number

카테고리 ID

name

String

카테고리명

description

String

카테고리 설명

imageUrl

String

이미지 URL

color

String

색상 코드

+
+
+
+

전체 카테고리 조회

+
+

Curl request

+
+
+
$ curl 'http://localhost:8080/api/categories' -i -X GET \
+    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTU3LCJleHAiOjE3MjIyNjg3NTd9.TJZznQ-W-PQIhpYNTuIE6Frb4r9ZAK0C5c84c8TKJCTk7fFIPupn7RH2vdijJXlsTvpwid8QzsMwLBoKKQaCaQ'
+
+
+
+
+

HTTP request

+
+
+
GET /api/categories HTTP/1.1
+Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTU3LCJleHAiOjE3MjIyNjg3NTd9.TJZznQ-W-PQIhpYNTuIE6Frb4r9ZAK0C5c84c8TKJCTk7fFIPupn7RH2vdijJXlsTvpwid8QzsMwLBoKKQaCaQ
+Host: localhost:8080
+
+
+
+
+

HTTP response

+
+
+
HTTP/1.1 200 OK
+Content-Type: application/json
+Content-Length: 369
+
+{
+  "categories" : [ {
+    "id" : 1,
+    "name" : "카테고리01",
+    "description" : "카테고리01 설명",
+    "imageUrl" : "https://via.placeholder.com/150",
+    "color" : "#FFFFFF"
+  }, {
+    "id" : 2,
+    "name" : "카테고리02",
+    "description" : "카테고리02 설명",
+    "imageUrl" : "https://via.placeholder.com/150",
+    "color" : "#FFFFFF"
+  } ]
+}
+
+
+
+
+

HTTPie request

+
+
+
$ http GET 'http://localhost:8080/api/categories' \
+    'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTU3LCJleHAiOjE3MjIyNjg3NTd9.TJZznQ-W-PQIhpYNTuIE6Frb4r9ZAK0C5c84c8TKJCTk7fFIPupn7RH2vdijJXlsTvpwid8QzsMwLBoKKQaCaQ'
+
+
+
+
+

Query parameters

+ ++++ + + + + + + + + + + + + + + + + + + + + +
ParameterDescription

page

페이지 번호

size

페이지 크기

sort

정렬 조건

+
+
+

Request body

+
+
+
+
+
+
+
+

Response body

+
+
+
{
+  "categories" : [ {
+    "id" : 1,
+    "name" : "카테고리01",
+    "description" : "카테고리01 설명",
+    "imageUrl" : "https://via.placeholder.com/150",
+    "color" : "#FFFFFF"
+  }, {
+    "id" : 2,
+    "name" : "카테고리02",
+    "description" : "카테고리02 설명",
+    "imageUrl" : "https://via.placeholder.com/150",
+    "color" : "#FFFFFF"
+  } ]
+}
+
+
+
+
+

Response fields

+ +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
PathTypeDescription

categories[].id

Number

카테고리 ID

categories[].name

String

카테고리명

categories[].description

String

카테고리 설명

categories[].imageUrl

String

이미지 URL

categories[].color

String

색상 코드

+
+
+
+
+
+ + + \ No newline at end of file diff --git a/src/main/resources/static/docs.html b/src/main/resources/static/docs.html new file mode 100644 index 000000000..c2274bc74 --- /dev/null +++ b/src/main/resources/static/docs.html @@ -0,0 +1,3964 @@ + + + + + + + +카카오 테크 캠퍼스 STEP 2 - REST Docs + + + + + +
+
+
+

상품 API

+
+
+

전체 상품 조회

+
+

Curl request

+
+
+
$ curl 'http://localhost:8080/api/products' -i -X GET \
+    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w'
+
+
+
+
+

HTTP request

+
+
+
GET /api/products HTTP/1.1
+Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w
+Host: localhost:8080
+
+
+
+
+

HTTP response

+
+
+
HTTP/1.1 200 OK
+Content-Type: application/json
+Content-Length: 443
+
+{
+  "products" : [ {
+    "id" : 1,
+    "name" : "상품01",
+    "price" : 1000,
+    "imageUrl" : "https://via.placeholder.com/150",
+    "options" : [ {
+      "id" : 1,
+      "name" : "상품 옵션 01",
+      "stock" : 100
+    } ],
+    "category" : {
+      "id" : 1,
+      "name" : "카테고리01",
+      "description" : "카테고리01 입니다",
+      "imageUrl" : "https://via.placeholder.com/150",
+      "color" : "#FFFFFF"
+    }
+  } ]
+}
+
+
+
+
+

HTTPie request

+
+
+
$ http GET 'http://localhost:8080/api/products' \
+    'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w'
+
+
+
+
+

Query parameters

+ ++++ + + + + + + + + + + + + + + + + +
ParameterDescription

page

페이지 번호

size

페이지 크기

+
+
+

Request body

+
+
+
+
+
+
+
+

Response body

+
+
+
{
+  "products" : [ {
+    "id" : 1,
+    "name" : "상품01",
+    "price" : 1000,
+    "imageUrl" : "https://via.placeholder.com/150",
+    "options" : [ {
+      "id" : 1,
+      "name" : "상품 옵션 01",
+      "stock" : 100
+    } ],
+    "category" : {
+      "id" : 1,
+      "name" : "카테고리01",
+      "description" : "카테고리01 입니다",
+      "imageUrl" : "https://via.placeholder.com/150",
+      "color" : "#FFFFFF"
+    }
+  } ]
+}
+
+
+
+
+

Response fields

+ +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
PathTypeDescription

products[].id

Number

상품 ID

products[].name

String

상품명

products[].price

Number

상품 가격

products[].imageUrl

String

상품 이미지 URL

products[].options

Array

상품 옵션 목록

products[].options[].id

Number

상품 옵션 ID

products[].options[].name

String

상품 옵션명

products[].options[].stock

Number

상품 옵션 재고

products[].category.id

Number

카테고리 ID

products[].category.name

String

카테고리명

products[].category.description

String

카테고리 설명

products[].category.imageUrl

String

카테고리 이미지 URL

products[].category.color

String

카테고리 색상

+
+
+
+

단일 상품 조회

+
+

Curl request

+
+
+
$ curl 'http://localhost:8080/api/products/1' -i -X GET \
+    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w'
+
+
+
+
+

HTTP request

+
+
+
GET /api/products/1 HTTP/1.1
+Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w
+Host: localhost:8080
+
+
+
+
+

HTTP response

+
+
+
HTTP/1.1 200 OK
+Content-Type: application/json
+Content-Length: 386
+
+{
+  "id" : 1,
+  "name" : "상품01",
+  "price" : 1000,
+  "imageUrl" : "https://via.placeholder.com/150",
+  "options" : [ {
+    "id" : 1,
+    "name" : "상품 옵션 01",
+    "stock" : 100
+  } ],
+  "category" : {
+    "id" : 1,
+    "name" : "카테고리01",
+    "description" : "카테고리01 입니다",
+    "imageUrl" : "https://via.placeholder.com/150",
+    "color" : "#FFFFFF"
+  }
+}
+
+
+
+
+

HTTPie request

+
+
+
$ http GET 'http://localhost:8080/api/products/1' \
+    'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w'
+
+
+
+
+

Request body

+
+
+
+
+
+
+
+

Response body

+
+
+
{
+  "id" : 1,
+  "name" : "상품01",
+  "price" : 1000,
+  "imageUrl" : "https://via.placeholder.com/150",
+  "options" : [ {
+    "id" : 1,
+    "name" : "상품 옵션 01",
+    "stock" : 100
+  } ],
+  "category" : {
+    "id" : 1,
+    "name" : "카테고리01",
+    "description" : "카테고리01 입니다",
+    "imageUrl" : "https://via.placeholder.com/150",
+    "color" : "#FFFFFF"
+  }
+}
+
+
+
+
+

Response fields

+ +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
PathTypeDescription

id

Number

상품 ID

name

String

상품명

price

Number

상품 가격

imageUrl

String

상품 이미지 URL

options

Array

상품 옵션 목록

options[].id

Number

상품 옵션 ID

options[].name

String

상품 옵션명

options[].stock

Number

상품 옵션 재고

category.id

Number

카테고리 ID

category.name

String

카테고리명

category.description

String

카테고리 설명

category.imageUrl

String

카테고리 이미지 URL

category.color

String

카테고리 색상

+
+
+
+

상품 생성

+
+

Curl request

+
+
+
$ curl 'http://localhost:8080/api/products' -i -X POST \
+    -H 'Content-Type: application/json' \
+    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w' \
+    -d '{
+  "name" : "상품01",
+  "price" : 1000,
+  "imageUrl" : "https://via.placeholder.com/150",
+  "categoryId" : 1,
+  "productOptions" : [ {
+    "name" : "상품 옵션 01",
+    "stock" : 100
+  } ]
+}'
+
+
+
+
+

HTTP request

+
+
+
POST /api/products HTTP/1.1
+Content-Type: application/json
+Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w
+Content-Length: 196
+Host: localhost:8080
+
+{
+  "name" : "상품01",
+  "price" : 1000,
+  "imageUrl" : "https://via.placeholder.com/150",
+  "categoryId" : 1,
+  "productOptions" : [ {
+    "name" : "상품 옵션 01",
+    "stock" : 100
+  } ]
+}
+
+
+
+
+

HTTP response

+
+
+
HTTP/1.1 201 Created
+Location: http://localhost:8080/api/products/1
+Content-Type: application/json
+Content-Length: 216
+
+{
+  "id" : 1,
+  "name" : "상품01",
+  "price" : 1000,
+  "imageUrl" : "https://via.placeholder.com/150",
+  "options" : [ {
+    "id" : 1,
+    "name" : "상품 옵션 01",
+    "stock" : 100,
+    "productId" : 1
+  } ]
+}
+
+
+
+
+

HTTPie request

+
+
+
$ echo '{
+  "name" : "상품01",
+  "price" : 1000,
+  "imageUrl" : "https://via.placeholder.com/150",
+  "categoryId" : 1,
+  "productOptions" : [ {
+    "name" : "상품 옵션 01",
+    "stock" : 100
+  } ]
+}' | http POST 'http://localhost:8080/api/products' \
+    'Content-Type:application/json' \
+    'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w'
+
+
+
+
+

Request body

+
+
+
{
+  "name" : "상품01",
+  "price" : 1000,
+  "imageUrl" : "https://via.placeholder.com/150",
+  "categoryId" : 1,
+  "productOptions" : [ {
+    "name" : "상품 옵션 01",
+    "stock" : 100
+  } ]
+}
+
+
+
+
+

Request fields

+ +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
PathTypeDescription

name

String

상품명

price

Number

상품 가격

imageUrl

String

상품 이미지 URL

categoryId

Number

카테고리 ID

productOptions

Array

상품 옵션 목록

productOptions[].name

String

상품 옵션명

productOptions[].stock

Number

상품 옵션 재고

+
+
+

Response body

+
+
+
{
+  "id" : 1,
+  "name" : "상품01",
+  "price" : 1000,
+  "imageUrl" : "https://via.placeholder.com/150",
+  "options" : [ {
+    "id" : 1,
+    "name" : "상품 옵션 01",
+    "stock" : 100,
+    "productId" : 1
+  } ]
+}
+
+
+
+
+

Response fields

+ +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
PathTypeDescription

id

Number

상품 ID

name

String

상품명

price

Number

상품 가격

imageUrl

String

상품 이미지 URL

options

Array

상품 옵션 목록

options[].id

Number

상품 옵션 ID

options[].name

String

상품 옵션명

options[].stock

Number

상품 옵션 재고

options[].productId

Number

상품 옵션 상품 ID

+
+
+
+

상품 수정

+
+

Curl request

+
+
+
$ curl 'http://localhost:8080/api/products/1' -i -X PUT \
+    -H 'Content-Type: application/json' \
+    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w' \
+    -d '{
+  "name" : "상품01",
+  "price" : 1000,
+  "imageUrl" : "https://via.placeholder.com/150"
+}'
+
+
+
+
+

HTTP request

+
+
+
PUT /api/products/1 HTTP/1.1
+Content-Type: application/json
+Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w
+Content-Length: 93
+Host: localhost:8080
+
+{
+  "name" : "상품01",
+  "price" : 1000,
+  "imageUrl" : "https://via.placeholder.com/150"
+}
+
+
+
+
+

HTTP response

+
+
+
HTTP/1.1 200 OK
+Content-Type: application/json
+Content-Length: 296
+
+{
+  "id" : 1,
+  "name" : "상품01",
+  "price" : 1000,
+  "imageUrl" : "https://via.placeholder.com/150",
+  "category" : {
+    "id" : 1,
+    "name" : "카테고리01",
+    "description" : "카테고리01 입니다",
+    "imageUrl" : "https://via.placeholder.com/150",
+    "color" : "#FFFFFF"
+  }
+}
+
+
+
+
+

HTTPie request

+
+
+
$ echo '{
+  "name" : "상품01",
+  "price" : 1000,
+  "imageUrl" : "https://via.placeholder.com/150"
+}' | http PUT 'http://localhost:8080/api/products/1' \
+    'Content-Type:application/json' \
+    'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w'
+
+
+
+
+

Request body

+
+
+
{
+  "name" : "상품01",
+  "price" : 1000,
+  "imageUrl" : "https://via.placeholder.com/150"
+}
+
+
+
+
+

Request fields

+ +++++ + + + + + + + + + + + + + + + + + + + + + + + + +
PathTypeDescription

name

String

상품명

price

Number

상품 가격

imageUrl

String

상품 이미지 URL

+
+
+

Response body

+
+
+
{
+  "id" : 1,
+  "name" : "상품01",
+  "price" : 1000,
+  "imageUrl" : "https://via.placeholder.com/150",
+  "category" : {
+    "id" : 1,
+    "name" : "카테고리01",
+    "description" : "카테고리01 입니다",
+    "imageUrl" : "https://via.placeholder.com/150",
+    "color" : "#FFFFFF"
+  }
+}
+
+
+
+
+

Response fields

+ +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
PathTypeDescription

id

Number

상품 ID

name

String

상품명

price

Number

상품 가격

imageUrl

String

상품 이미지 URL

category.id

Number

카테고리 ID

category.name

String

카테고리명

category.description

String

카테고리 설명

category.imageUrl

String

카테고리 이미지 URL

category.color

String

카테고리 색상

+
+
+
+

상품 삭제

+
+

Curl request

+
+
+
$ curl 'http://localhost:8080/api/products/1' -i -X DELETE \
+    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w'
+
+
+
+
+

HTTP request

+
+
+
DELETE /api/products/1 HTTP/1.1
+Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w
+Host: localhost:8080
+
+
+
+
+

HTTP response

+
+
+
HTTP/1.1 204 No Content
+
+
+
+
+

HTTPie request

+
+
+
$ http DELETE 'http://localhost:8080/api/products/1' \
+    'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w'
+
+
+
+
+

Request body

+
+
+
+
+
+
+
+

Response body

+
+
+
+
+
+
+
+
+

상품 주문

+
+

Curl request

+
+
+
$ curl 'http://localhost:8080/api/products/1/order' -i -X POST \
+    -H 'Content-Type: application/json' \
+    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w' \
+    -d '{
+  "optionId" : 1,
+  "quantity" : 1,
+  "message" : "message"
+}'
+
+
+
+
+

HTTP request

+
+
+
POST /api/products/1/order HTTP/1.1
+Content-Type: application/json
+Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w
+Content-Length: 63
+Host: localhost:8080
+
+{
+  "optionId" : 1,
+  "quantity" : 1,
+  "message" : "message"
+}
+
+
+
+
+

HTTP response

+
+
+
HTTP/1.1 200 OK
+Content-Type: application/json
+Content-Length: 134
+
+{
+  "productId" : 1,
+  "optionId" : 1,
+  "optionStock" : 10,
+  "quantity" : 1,
+  "productName" : "상품01",
+  "message" : "message"
+}
+
+
+
+
+

HTTPie request

+
+
+
$ echo '{
+  "optionId" : 1,
+  "quantity" : 1,
+  "message" : "message"
+}' | http POST 'http://localhost:8080/api/products/1/order' \
+    'Content-Type:application/json' \
+    'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w'
+
+
+
+
+

Request body

+
+
+
{
+  "optionId" : 1,
+  "quantity" : 1,
+  "message" : "message"
+}
+
+
+
+
+

Request fields

+ +++++ + + + + + + + + + + + + + + + + + + + + + + + + +
PathTypeDescription

optionId

Number

상품 옵션 ID

quantity

Number

수량

message

String

메시지

+
+
+

Response body

+
+
+
{
+  "productId" : 1,
+  "optionId" : 1,
+  "optionStock" : 10,
+  "quantity" : 1,
+  "productName" : "상품01",
+  "message" : "message"
+}
+
+
+
+
+

Response fields

+ +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
PathTypeDescription

productId

Number

상품 ID

optionId

Number

상품 옵션 ID

optionStock

Number

상품 옵션 재고

quantity

Number

수량

productName

String

상품명

message

String

메시지

+
+
+
+

위시 상품 추가

+
+

Curl request

+
+
+
$ curl 'http://localhost:8080/api/products/wish' -i -X POST \
+    -H 'Content-Type: application/json' \
+    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w' \
+    -d '{
+  "productId" : 1,
+  "quantity" : 1
+}'
+
+
+
+
+

HTTP request

+
+
+
POST /api/products/wish HTTP/1.1
+Content-Type: application/json
+Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w
+Content-Length: 39
+Host: localhost:8080
+
+{
+  "productId" : 1,
+  "quantity" : 1
+}
+
+
+
+
+

HTTP response

+
+
+
HTTP/1.1 200 OK
+Content-Type: application/json
+Content-Length: 32
+
+{
+  "id" : 1,
+  "quantity" : 1
+}
+
+
+
+
+

HTTPie request

+
+
+
$ echo '{
+  "productId" : 1,
+  "quantity" : 1
+}' | http POST 'http://localhost:8080/api/products/wish' \
+    'Content-Type:application/json' \
+    'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w'
+
+
+
+
+

Request body

+
+
+
{
+  "productId" : 1,
+  "quantity" : 1
+}
+
+
+
+
+

Request fields

+ +++++ + + + + + + + + + + + + + + + + + + + +
PathTypeDescription

productId

Number

상품 ID

quantity

Number

수량

+
+
+

Response body

+
+
+
{
+  "id" : 1,
+  "quantity" : 1
+}
+
+
+
+
+

Response fields

+ +++++ + + + + + + + + + + + + + + + + + + + +
PathTypeDescription

id

Number

위시 상품 ID

quantity

Number

수량

+
+
+
+

상품 옵션 생성

+
+

Curl request

+
+
+
$ curl 'http://localhost:8080/api/products/1/options' -i -X POST \
+    -H 'Content-Type: application/json' \
+    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w' \
+    -d '{
+  "name" : "상품 옵션 01",
+  "stock" : 100
+}'
+
+
+
+
+

HTTP request

+
+
+
POST /api/products/1/options HTTP/1.1
+Content-Type: application/json
+Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w
+Content-Length: 50
+Host: localhost:8080
+
+{
+  "name" : "상품 옵션 01",
+  "stock" : 100
+}
+
+
+
+
+

HTTP response

+
+
+
HTTP/1.1 201 Created
+Location: http://localhost:8080/api/products/1/options/1
+Content-Type: application/json
+Content-Length: 62
+
+{
+  "id" : 1,
+  "name" : "상품 옵션 01",
+  "stock" : 100
+}
+
+
+
+
+

HTTPie request

+
+
+
$ echo '{
+  "name" : "상품 옵션 01",
+  "stock" : 100
+}' | http POST 'http://localhost:8080/api/products/1/options' \
+    'Content-Type:application/json' \
+    'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w'
+
+
+
+
+

Request body

+
+
+
{
+  "name" : "상품 옵션 01",
+  "stock" : 100
+}
+
+
+
+
+

Response body

+
+
+
{
+  "id" : 1,
+  "name" : "상품 옵션 01",
+  "stock" : 100
+}
+
+
+
+
+

Response fields

+ +++++ + + + + + + + + + + + + + + + + + + + + + + + + +
PathTypeDescription

id

Number

상품 옵션 ID

name

String

상품 옵션명

stock

Number

상품 옵션 재고

+
+
+
+

상품 옵션 조회

+
+

Curl request

+
+
+
$ curl 'http://localhost:8080/api/products/1/options' -i -X GET \
+    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w'
+
+
+
+
+

HTTP request

+
+
+
GET /api/products/1/options HTTP/1.1
+Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w
+Host: localhost:8080
+
+
+
+
+

HTTP response

+
+
+
HTTP/1.1 200 OK
+Content-Type: application/json
+Content-Length: 92
+
+{
+  "options" : [ {
+    "id" : 1,
+    "name" : "상품 옵션 01",
+    "stock" : 100
+  } ]
+}
+
+
+
+
+

HTTPie request

+
+
+
$ http GET 'http://localhost:8080/api/products/1/options' \
+    'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w'
+
+
+
+
+

Request body

+
+
+
+
+
+
+
+

Response body

+
+
+
{
+  "options" : [ {
+    "id" : 1,
+    "name" : "상품 옵션 01",
+    "stock" : 100
+  } ]
+}
+
+
+
+
+

Response fields

+ +++++ + + + + + + + + + + + + + + + + + + + + + + + + +
PathTypeDescription

options[].id

Number

상품 옵션 ID

options[].name

String

상품 옵션명

options[].stock

Number

상품 옵션 재고

+
+
+
+

상품 옵션 수정

+
+

Curl request

+
+
+
$ curl 'http://localhost:8080/api/products/1/options/1' -i -X PUT \
+    -H 'Content-Type: application/json' \
+    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w' \
+    -d '{
+  "name" : "상품 옵션 01",
+  "stock" : 100
+}'
+
+
+
+
+

HTTP request

+
+
+
PUT /api/products/1/options/1 HTTP/1.1
+Content-Type: application/json
+Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w
+Content-Length: 50
+Host: localhost:8080
+
+{
+  "name" : "상품 옵션 01",
+  "stock" : 100
+}
+
+
+
+
+

HTTP response

+
+
+
HTTP/1.1 200 OK
+Content-Type: application/json
+Content-Length: 62
+
+{
+  "id" : 1,
+  "name" : "상품 옵션 01",
+  "stock" : 100
+}
+
+
+
+
+

HTTPie request

+
+
+
$ echo '{
+  "name" : "상품 옵션 01",
+  "stock" : 100
+}' | http PUT 'http://localhost:8080/api/products/1/options/1' \
+    'Content-Type:application/json' \
+    'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w'
+
+
+
+
+

Request body

+
+
+
{
+  "name" : "상품 옵션 01",
+  "stock" : 100
+}
+
+
+
+
+

Request fields

+ +++++ + + + + + + + + + + + + + + + + + + + +
PathTypeDescription

name

String

상품 옵션명

stock

Number

상품 옵션 재고

+
+
+

Response body

+
+
+
{
+  "id" : 1,
+  "name" : "상품 옵션 01",
+  "stock" : 100
+}
+
+
+
+
+

Response fields

+ +++++ + + + + + + + + + + + + + + + + + + + + + + + + +
PathTypeDescription

id

Number

상품 옵션 ID

name

String

상품 옵션명

stock

Number

상품 옵션 재고

+
+
+
+

상품 옵션 삭제

+
+

Curl request

+
+
+
$ curl 'http://localhost:8080/api/products/1/options/1' -i -X DELETE \
+    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w'
+
+
+
+
+

HTTP request

+
+
+
DELETE /api/products/1/options/1 HTTP/1.1
+Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w
+Host: localhost:8080
+
+
+
+
+

HTTP response

+
+
+
HTTP/1.1 204 No Content
+
+
+
+
+

HTTPie request

+
+
+
$ http DELETE 'http://localhost:8080/api/products/1/options/1' \
+    'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w'
+
+
+
+
+

Request body

+
+
+
+
+
+
+
+

Response body

+
+
+
+
+
+
+
+
+
+
+

카테고리 API

+
+
+

카테고리 생성

+
+

Curl request

+
+
+
$ curl 'http://localhost:8080/api/categories' -i -X POST \
+    -H 'Content-Type: application/json' \
+    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTU3LCJleHAiOjE3MjIyNjg3NTd9.TJZznQ-W-PQIhpYNTuIE6Frb4r9ZAK0C5c84c8TKJCTk7fFIPupn7RH2vdijJXlsTvpwid8QzsMwLBoKKQaCaQ' \
+    -d '{
+  "name" : "카테고리01",
+  "description" : "카테고리01 설명",
+  "imageUrl" : "https://via.placeholder.com/150",
+  "color" : "#FFFFFF"
+}'
+
+
+
+
+

HTTP request

+
+
+
POST /api/categories HTTP/1.1
+Content-Type: application/json
+Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTU3LCJleHAiOjE3MjIyNjg3NTd9.TJZznQ-W-PQIhpYNTuIE6Frb4r9ZAK0C5c84c8TKJCTk7fFIPupn7RH2vdijJXlsTvpwid8QzsMwLBoKKQaCaQ
+Content-Length: 147
+Host: localhost:8080
+
+{
+  "name" : "카테고리01",
+  "description" : "카테고리01 설명",
+  "imageUrl" : "https://via.placeholder.com/150",
+  "color" : "#FFFFFF"
+}
+
+
+
+
+

HTTP response

+
+
+
HTTP/1.1 201 Created
+Location: http://localhost:8080/api/categories/1
+Content-Type: application/json
+Content-Length: 159
+
+{
+  "id" : 1,
+  "name" : "카테고리01",
+  "description" : "카테고리01 설명",
+  "imageUrl" : "https://via.placeholder.com/150",
+  "color" : "#FFFFFF"
+}
+
+
+
+
+

HTTPie request

+
+
+
$ echo '{
+  "name" : "카테고리01",
+  "description" : "카테고리01 설명",
+  "imageUrl" : "https://via.placeholder.com/150",
+  "color" : "#FFFFFF"
+}' | http POST 'http://localhost:8080/api/categories' \
+    'Content-Type:application/json' \
+    'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTU3LCJleHAiOjE3MjIyNjg3NTd9.TJZznQ-W-PQIhpYNTuIE6Frb4r9ZAK0C5c84c8TKJCTk7fFIPupn7RH2vdijJXlsTvpwid8QzsMwLBoKKQaCaQ'
+
+
+
+
+

Request body

+
+
+
{
+  "name" : "카테고리01",
+  "description" : "카테고리01 설명",
+  "imageUrl" : "https://via.placeholder.com/150",
+  "color" : "#FFFFFF"
+}
+
+
+
+
+

Request fields

+ +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
PathTypeDescription

name

String

카테고리명

description

String

카테고리 설명

imageUrl

String

이미지 URL

color

String

색상 코드

+
+
+

Response body

+
+
+
{
+  "id" : 1,
+  "name" : "카테고리01",
+  "description" : "카테고리01 설명",
+  "imageUrl" : "https://via.placeholder.com/150",
+  "color" : "#FFFFFF"
+}
+
+
+
+
+

Response fields

+ +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
PathTypeDescription

id

Number

카테고리 ID

name

String

카테고리명

description

String

카테고리 설명

imageUrl

String

이미지 URL

color

String

색상 코드

+
+
+
+

카테고리 수정

+
+

Curl request

+
+
+
$ curl 'http://localhost:8080/api/categories/1' -i -X PUT \
+    -H 'Content-Type: application/json' \
+    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTU3LCJleHAiOjE3MjIyNjg3NTd9.TJZznQ-W-PQIhpYNTuIE6Frb4r9ZAK0C5c84c8TKJCTk7fFIPupn7RH2vdijJXlsTvpwid8QzsMwLBoKKQaCaQ' \
+    -d '{
+  "name" : "카테고리01",
+  "description" : "카테고리01 설명",
+  "imageUrl" : "https://via.placeholder.com/150",
+  "color" : "#FFFFFF"
+}'
+
+
+
+
+

HTTP request

+
+
+
PUT /api/categories/1 HTTP/1.1
+Content-Type: application/json
+Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTU3LCJleHAiOjE3MjIyNjg3NTd9.TJZznQ-W-PQIhpYNTuIE6Frb4r9ZAK0C5c84c8TKJCTk7fFIPupn7RH2vdijJXlsTvpwid8QzsMwLBoKKQaCaQ
+Content-Length: 147
+Host: localhost:8080
+
+{
+  "name" : "카테고리01",
+  "description" : "카테고리01 설명",
+  "imageUrl" : "https://via.placeholder.com/150",
+  "color" : "#FFFFFF"
+}
+
+
+
+
+

HTTP response

+
+
+
HTTP/1.1 200 OK
+Content-Type: application/json
+Content-Length: 159
+
+{
+  "id" : 1,
+  "name" : "카테고리01",
+  "description" : "카테고리01 설명",
+  "imageUrl" : "https://via.placeholder.com/150",
+  "color" : "#FFFFFF"
+}
+
+
+
+
+

HTTPie request

+
+
+
$ echo '{
+  "name" : "카테고리01",
+  "description" : "카테고리01 설명",
+  "imageUrl" : "https://via.placeholder.com/150",
+  "color" : "#FFFFFF"
+}' | http PUT 'http://localhost:8080/api/categories/1' \
+    'Content-Type:application/json' \
+    'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTU3LCJleHAiOjE3MjIyNjg3NTd9.TJZznQ-W-PQIhpYNTuIE6Frb4r9ZAK0C5c84c8TKJCTk7fFIPupn7RH2vdijJXlsTvpwid8QzsMwLBoKKQaCaQ'
+
+
+
+
+

Request body

+
+
+
{
+  "name" : "카테고리01",
+  "description" : "카테고리01 설명",
+  "imageUrl" : "https://via.placeholder.com/150",
+  "color" : "#FFFFFF"
+}
+
+
+
+
+

Request fields

+ +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
PathTypeDescription

name

String

카테고리명

description

String

카테고리 설명

imageUrl

String

이미지 URL

color

String

색상 코드

+
+
+

Response body

+
+
+
{
+  "id" : 1,
+  "name" : "카테고리01",
+  "description" : "카테고리01 설명",
+  "imageUrl" : "https://via.placeholder.com/150",
+  "color" : "#FFFFFF"
+}
+
+
+
+
+

Response fields

+ +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
PathTypeDescription

id

Number

카테고리 ID

name

String

카테고리명

description

String

카테고리 설명

imageUrl

String

이미지 URL

color

String

색상 코드

+
+
+
+

카테고리 삭제

+
+

Curl request

+
+
+
$ curl 'http://localhost:8080/api/categories/1' -i -X DELETE \
+    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTU3LCJleHAiOjE3MjIyNjg3NTd9.TJZznQ-W-PQIhpYNTuIE6Frb4r9ZAK0C5c84c8TKJCTk7fFIPupn7RH2vdijJXlsTvpwid8QzsMwLBoKKQaCaQ'
+
+
+
+
+

HTTP request

+
+
+
DELETE /api/categories/1 HTTP/1.1
+Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTU3LCJleHAiOjE3MjIyNjg3NTd9.TJZznQ-W-PQIhpYNTuIE6Frb4r9ZAK0C5c84c8TKJCTk7fFIPupn7RH2vdijJXlsTvpwid8QzsMwLBoKKQaCaQ
+Host: localhost:8080
+
+
+
+
+

HTTP response

+
+
+
HTTP/1.1 204 No Content
+
+
+
+
+

HTTPie request

+
+
+
$ http DELETE 'http://localhost:8080/api/categories/1' \
+    'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTU3LCJleHAiOjE3MjIyNjg3NTd9.TJZznQ-W-PQIhpYNTuIE6Frb4r9ZAK0C5c84c8TKJCTk7fFIPupn7RH2vdijJXlsTvpwid8QzsMwLBoKKQaCaQ'
+
+
+
+
+

Request body

+
+
+
+
+
+
+
+

Response body

+
+
+
+
+
+
+
+
+

단일 카테고리 조회

+
+

Curl request

+
+
+
$ curl 'http://localhost:8080/api/categories/1' -i -X GET \
+    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTU3LCJleHAiOjE3MjIyNjg3NTd9.TJZznQ-W-PQIhpYNTuIE6Frb4r9ZAK0C5c84c8TKJCTk7fFIPupn7RH2vdijJXlsTvpwid8QzsMwLBoKKQaCaQ'
+
+
+
+
+

HTTP request

+
+
+
GET /api/categories/1 HTTP/1.1
+Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTU3LCJleHAiOjE3MjIyNjg3NTd9.TJZznQ-W-PQIhpYNTuIE6Frb4r9ZAK0C5c84c8TKJCTk7fFIPupn7RH2vdijJXlsTvpwid8QzsMwLBoKKQaCaQ
+Host: localhost:8080
+
+
+
+
+

HTTP response

+
+
+
HTTP/1.1 200 OK
+Content-Type: application/json
+Content-Length: 159
+
+{
+  "id" : 1,
+  "name" : "카테고리01",
+  "description" : "카테고리01 설명",
+  "imageUrl" : "https://via.placeholder.com/150",
+  "color" : "#FFFFFF"
+}
+
+
+
+
+

HTTPie request

+
+
+
$ http GET 'http://localhost:8080/api/categories/1' \
+    'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTU3LCJleHAiOjE3MjIyNjg3NTd9.TJZznQ-W-PQIhpYNTuIE6Frb4r9ZAK0C5c84c8TKJCTk7fFIPupn7RH2vdijJXlsTvpwid8QzsMwLBoKKQaCaQ'
+
+
+
+
+

Request body

+
+
+
+
+
+
+
+

Response body

+
+
+
{
+  "id" : 1,
+  "name" : "카테고리01",
+  "description" : "카테고리01 설명",
+  "imageUrl" : "https://via.placeholder.com/150",
+  "color" : "#FFFFFF"
+}
+
+
+
+
+

Response fields

+ +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
PathTypeDescription

id

Number

카테고리 ID

name

String

카테고리명

description

String

카테고리 설명

imageUrl

String

이미지 URL

color

String

색상 코드

+
+
+
+

전체 카테고리 조회

+
+

Curl request

+
+
+
$ curl 'http://localhost:8080/api/categories' -i -X GET \
+    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTU3LCJleHAiOjE3MjIyNjg3NTd9.TJZznQ-W-PQIhpYNTuIE6Frb4r9ZAK0C5c84c8TKJCTk7fFIPupn7RH2vdijJXlsTvpwid8QzsMwLBoKKQaCaQ'
+
+
+
+
+

HTTP request

+
+
+
GET /api/categories HTTP/1.1
+Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTU3LCJleHAiOjE3MjIyNjg3NTd9.TJZznQ-W-PQIhpYNTuIE6Frb4r9ZAK0C5c84c8TKJCTk7fFIPupn7RH2vdijJXlsTvpwid8QzsMwLBoKKQaCaQ
+Host: localhost:8080
+
+
+
+
+

HTTP response

+
+
+
HTTP/1.1 200 OK
+Content-Type: application/json
+Content-Length: 369
+
+{
+  "categories" : [ {
+    "id" : 1,
+    "name" : "카테고리01",
+    "description" : "카테고리01 설명",
+    "imageUrl" : "https://via.placeholder.com/150",
+    "color" : "#FFFFFF"
+  }, {
+    "id" : 2,
+    "name" : "카테고리02",
+    "description" : "카테고리02 설명",
+    "imageUrl" : "https://via.placeholder.com/150",
+    "color" : "#FFFFFF"
+  } ]
+}
+
+
+
+
+

HTTPie request

+
+
+
$ http GET 'http://localhost:8080/api/categories' \
+    'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTU3LCJleHAiOjE3MjIyNjg3NTd9.TJZznQ-W-PQIhpYNTuIE6Frb4r9ZAK0C5c84c8TKJCTk7fFIPupn7RH2vdijJXlsTvpwid8QzsMwLBoKKQaCaQ'
+
+
+
+
+

Query parameters

+ ++++ + + + + + + + + + + + + + + + + + + + + +
ParameterDescription

page

페이지 번호

size

페이지 크기

sort

정렬 조건

+
+
+

Request body

+
+
+
+
+
+
+
+

Response body

+
+
+
{
+  "categories" : [ {
+    "id" : 1,
+    "name" : "카테고리01",
+    "description" : "카테고리01 설명",
+    "imageUrl" : "https://via.placeholder.com/150",
+    "color" : "#FFFFFF"
+  }, {
+    "id" : 2,
+    "name" : "카테고리02",
+    "description" : "카테고리02 설명",
+    "imageUrl" : "https://via.placeholder.com/150",
+    "color" : "#FFFFFF"
+  } ]
+}
+
+
+
+
+

Response fields

+ +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
PathTypeDescription

categories[].id

Number

카테고리 ID

categories[].name

String

카테고리명

categories[].description

String

카테고리 설명

categories[].imageUrl

String

이미지 URL

categories[].color

String

색상 코드

+
+
+
+
+
+

회원 API

+
+
+

회원가입

+
+

Curl request

+
+
+
$ curl 'http://localhost:8080/api/members/register' -i -X POST \
+    -H 'Content-Type: application/json' \
+    -d '{
+  "email" : "member01@gmail.com",
+  "password" : "password01",
+  "name" : "member01"
+}'
+
+
+
+
+

HTTP request

+
+
+
POST /api/members/register HTTP/1.1
+Content-Type: application/json
+Content-Length: 88
+Host: localhost:8080
+
+{
+  "email" : "member01@gmail.com",
+  "password" : "password01",
+  "name" : "member01"
+}
+
+
+
+
+

HTTP response

+
+
+
HTTP/1.1 201 Created
+Location: http://localhost:8080/api/members/1
+Content-Type: application/json
+Content-Length: 71
+
+{
+  "id" : 1,
+  "email" : "member01@gmail.com",
+  "name" : "member01"
+}
+
+
+
+
+

HTTPie request

+
+
+
$ echo '{
+  "email" : "member01@gmail.com",
+  "password" : "password01",
+  "name" : "member01"
+}' | http POST 'http://localhost:8080/api/members/register' \
+    'Content-Type:application/json'
+
+
+
+
+

Request body

+
+
+
{
+  "email" : "member01@gmail.com",
+  "password" : "password01",
+  "name" : "member01"
+}
+
+
+
+
+

Request fields

+ +++++ + + + + + + + + + + + + + + + + + + + + + + + + +
PathTypeDescription

email

String

이메일

password

String

비밀번호

name

String

이름

+
+
+

Response body

+
+
+
{
+  "id" : 1,
+  "email" : "member01@gmail.com",
+  "name" : "member01"
+}
+
+
+
+
+

Response fields

+ +++++ + + + + + + + + + + + + + + + + + + + + + + + + +
PathTypeDescription

id

Number

회원 식별자

email

String

이메일

name

String

이름

+
+
+
+

로그인

+
+

Curl request

+
+
+
$ curl 'http://localhost:8080/api/members/login' -i -X POST \
+    -H 'Content-Type: application/json' \
+    -d '{
+  "email" : "member01@gmail.com",
+  "password" : "password01"
+}'
+
+
+
+
+

HTTP request

+
+
+
POST /api/members/login HTTP/1.1
+Content-Type: application/json
+Content-Length: 65
+Host: localhost:8080
+
+{
+  "email" : "member01@gmail.com",
+  "password" : "password01"
+}
+
+
+
+
+

HTTP response

+
+
+
HTTP/1.1 200 OK
+Content-Type: application/json
+Content-Length: 196
+
+{
+  "accessToken" : "eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTU5LCJleHAiOjE3MjIyNjg3NTl9.iuNnicN0kjQYKx7fNiJBtH_2merHROfg_1P7mjBVrFmTi9l9PLkpGOerNzS9ni8vhJ-6fx0cwdbPhYXt7zqaWw"
+}
+
+
+
+
+

HTTPie request

+
+
+
$ echo '{
+  "email" : "member01@gmail.com",
+  "password" : "password01"
+}' | http POST 'http://localhost:8080/api/members/login' \
+    'Content-Type:application/json'
+
+
+
+
+

Request body

+
+
+
{
+  "email" : "member01@gmail.com",
+  "password" : "password01"
+}
+
+
+
+
+

Request fields

+ +++++ + + + + + + + + + + + + + + + + + + + +
PathTypeDescription

email

String

이메일

password

String

비밀번호

+
+
+

Response body

+
+
+
{
+  "accessToken" : "eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTU5LCJleHAiOjE3MjIyNjg3NTl9.iuNnicN0kjQYKx7fNiJBtH_2merHROfg_1P7mjBVrFmTi9l9PLkpGOerNzS9ni8vhJ-6fx0cwdbPhYXt7zqaWw"
+}
+
+
+
+
+

Response fields

+ +++++ + + + + + + + + + + + + + + +
PathTypeDescription

accessToken

String

Bearer Token

+
+
+
+

회원 조회

+
+

Curl request

+
+
+
$ curl 'http://localhost:8080/api/members/1' -i -X GET \
+    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTU5LCJleHAiOjE3MjIyNjg3NTl9.iuNnicN0kjQYKx7fNiJBtH_2merHROfg_1P7mjBVrFmTi9l9PLkpGOerNzS9ni8vhJ-6fx0cwdbPhYXt7zqaWw'
+
+
+
+
+

HTTP request

+
+
+
GET /api/members/1 HTTP/1.1
+Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTU5LCJleHAiOjE3MjIyNjg3NTl9.iuNnicN0kjQYKx7fNiJBtH_2merHROfg_1P7mjBVrFmTi9l9PLkpGOerNzS9ni8vhJ-6fx0cwdbPhYXt7zqaWw
+Host: localhost:8080
+
+
+
+
+

HTTP response

+
+
+
HTTP/1.1 200 OK
+Content-Type: application/json
+Content-Length: 100
+
+{
+  "id" : 1,
+  "email" : "member01@gmail.com",
+  "password" : "password01",
+  "name" : "member01"
+}
+
+
+
+
+

HTTPie request

+
+
+
$ http GET 'http://localhost:8080/api/members/1' \
+    'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTU5LCJleHAiOjE3MjIyNjg3NTl9.iuNnicN0kjQYKx7fNiJBtH_2merHROfg_1P7mjBVrFmTi9l9PLkpGOerNzS9ni8vhJ-6fx0cwdbPhYXt7zqaWw'
+
+
+
+
+

Request body

+
+
+
+
+
+
+
+

Response body

+
+
+
{
+  "id" : 1,
+  "email" : "member01@gmail.com",
+  "password" : "password01",
+  "name" : "member01"
+}
+
+
+
+
+

Response fields

+ +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
PathTypeDescription

id

Number

회원 식별자

email

String

이메일

password

String

비밀번호

name

String

이름

+
+
+
+

위시 상품 조회

+
+

Curl request

+
+
+
$ curl 'http://localhost:8080/api/members/wishlist' -i -X GET \
+    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTU5LCJleHAiOjE3MjIyNjg3NTl9.iuNnicN0kjQYKx7fNiJBtH_2merHROfg_1P7mjBVrFmTi9l9PLkpGOerNzS9ni8vhJ-6fx0cwdbPhYXt7zqaWw'
+
+
+
+
+

HTTP request

+
+
+
GET /api/members/wishlist HTTP/1.1
+Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTU5LCJleHAiOjE3MjIyNjg3NTl9.iuNnicN0kjQYKx7fNiJBtH_2merHROfg_1P7mjBVrFmTi9l9PLkpGOerNzS9ni8vhJ-6fx0cwdbPhYXt7zqaWw
+Host: localhost:8080
+
+
+
+
+

HTTP response

+
+
+
HTTP/1.1 200 OK
+Content-Type: application/json
+Content-Length: 180
+
+{
+  "wishlist" : [ {
+    "id" : 1,
+    "productId" : 1,
+    "name" : "product01",
+    "price" : 1000,
+    "quantity" : 5,
+    "imageUrl" : "https://via.placeholder.com/150"
+  } ]
+}
+
+
+
+
+

HTTPie request

+
+
+
$ http GET 'http://localhost:8080/api/members/wishlist' \
+    'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTU5LCJleHAiOjE3MjIyNjg3NTl9.iuNnicN0kjQYKx7fNiJBtH_2merHROfg_1P7mjBVrFmTi9l9PLkpGOerNzS9ni8vhJ-6fx0cwdbPhYXt7zqaWw'
+
+
+
+
+

Query parameters

+ ++++ + + + + + + + + + + + + + + + + +
ParameterDescription

page

페이지 번호

size

페이지 크기

+
+
+

Request body

+
+
+
+
+
+
+
+

Response body

+
+
+
{
+  "wishlist" : [ {
+    "id" : 1,
+    "productId" : 1,
+    "name" : "product01",
+    "price" : 1000,
+    "quantity" : 5,
+    "imageUrl" : "https://via.placeholder.com/150"
+  } ]
+}
+
+
+
+
+

Response fields

+ +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
PathTypeDescription

wishlist[].id

Number

위시 상품 식별자

wishlist[].productId

Number

상품 식별자

wishlist[].name

String

상품명

wishlist[].price

Number

가격

wishlist[].quantity

Number

재고 수량

wishlist[].imageUrl

String

이미지 URL

+
+
+
+

위시 상품 수정

+
+

Curl request

+
+
+
$ curl 'http://localhost:8080/api/members/wishlist/1' -i -X PUT \
+    -H 'Content-Type: application/json' \
+    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTU5LCJleHAiOjE3MjIyNjg3NTl9.iuNnicN0kjQYKx7fNiJBtH_2merHROfg_1P7mjBVrFmTi9l9PLkpGOerNzS9ni8vhJ-6fx0cwdbPhYXt7zqaWw' \
+    -d '{
+  "quantity" : 5
+}'
+
+
+
+
+

HTTP request

+
+
+
PUT /api/members/wishlist/1 HTTP/1.1
+Content-Type: application/json
+Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTU5LCJleHAiOjE3MjIyNjg3NTl9.iuNnicN0kjQYKx7fNiJBtH_2merHROfg_1P7mjBVrFmTi9l9PLkpGOerNzS9ni8vhJ-6fx0cwdbPhYXt7zqaWw
+Content-Length: 20
+Host: localhost:8080
+
+{
+  "quantity" : 5
+}
+
+
+
+
+

HTTP response

+
+
+
HTTP/1.1 200 OK
+Content-Type: application/json
+Content-Length: 143
+
+{
+  "id" : 1,
+  "productId" : 1,
+  "name" : "product01",
+  "price" : 1000,
+  "quantity" : 5,
+  "imageUrl" : "https://via.placeholder.com/150"
+}
+
+
+
+
+

HTTPie request

+
+
+
$ echo '{
+  "quantity" : 5
+}' | http PUT 'http://localhost:8080/api/members/wishlist/1' \
+    'Content-Type:application/json' \
+    'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTU5LCJleHAiOjE3MjIyNjg3NTl9.iuNnicN0kjQYKx7fNiJBtH_2merHROfg_1P7mjBVrFmTi9l9PLkpGOerNzS9ni8vhJ-6fx0cwdbPhYXt7zqaWw'
+
+
+
+
+

Request body

+
+
+
{
+  "quantity" : 5
+}
+
+
+
+
+

Request fields

+ +++++ + + + + + + + + + + + + + + +
PathTypeDescription

quantity

Number

수량

+
+
+

Response body

+
+
+
{
+  "id" : 1,
+  "productId" : 1,
+  "name" : "product01",
+  "price" : 1000,
+  "quantity" : 5,
+  "imageUrl" : "https://via.placeholder.com/150"
+}
+
+
+
+
+

Response fields

+ +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
PathTypeDescription

id

Number

위시 상품 식별자

productId

Number

상품 식별자

name

String

상품명

price

Number

가격

quantity

Number

재고 수량

imageUrl

String

이미지 URL

+
+
+
+

위시 상품 삭제

+
+

Curl request

+
+
+
$ curl 'http://localhost:8080/api/members/wishlist/1' -i -X DELETE \
+    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTU5LCJleHAiOjE3MjIyNjg3NTl9.iuNnicN0kjQYKx7fNiJBtH_2merHROfg_1P7mjBVrFmTi9l9PLkpGOerNzS9ni8vhJ-6fx0cwdbPhYXt7zqaWw'
+
+
+
+
+

HTTP request

+
+
+
DELETE /api/members/wishlist/1 HTTP/1.1
+Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTU5LCJleHAiOjE3MjIyNjg3NTl9.iuNnicN0kjQYKx7fNiJBtH_2merHROfg_1P7mjBVrFmTi9l9PLkpGOerNzS9ni8vhJ-6fx0cwdbPhYXt7zqaWw
+Host: localhost:8080
+
+
+
+
+

HTTP response

+
+
+
HTTP/1.1 204 No Content
+
+
+
+
+

HTTPie request

+
+
+
$ http DELETE 'http://localhost:8080/api/members/wishlist/1' \
+    'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTU5LCJleHAiOjE3MjIyNjg3NTl9.iuNnicN0kjQYKx7fNiJBtH_2merHROfg_1P7mjBVrFmTi9l9PLkpGOerNzS9ni8vhJ-6fx0cwdbPhYXt7zqaWw'
+
+
+
+
+

Request body

+
+
+
+
+
+
+
+

Response body

+
+
+
+
+
+
+
+
+
+
+

소셜 로그인 API

+
+
+

카카오 로그인

+
+

Curl request

+
+
+
$ curl 'http://localhost:8080/api/login/oauth2/kakao?code=kakao-auth-code' -i -X GET
+
+
+
+
+

HTTP request

+
+
+
GET /api/login/oauth2/kakao?code=kakao-auth-code HTTP/1.1
+Host: localhost:8080
+
+
+
+
+

HTTP response

+
+
+
HTTP/1.1 200 OK
+Content-Type: application/json
+Content-Length: 47
+
+{
+  "accessToken" : "Bearer {{access_token}}"
+}
+
+
+
+
+

HTTPie request

+
+
+
$ http GET 'http://localhost:8080/api/login/oauth2/kakao?code=kakao-auth-code'
+
+
+
+
+

Query parameters

+ ++++ + + + + + + + + + + + + +
ParameterDescription

code

kakao-auth-code

+
+
+

Request body

+
+
+
+
+
+
+
+

Response body

+
+
+
{
+  "accessToken" : "Bearer {{access_token}}"
+}
+
+
+
+
+

Response fields

+ +++++ + + + + + + + + + + + + + + +
PathTypeDescription

accessToken

class java.lang.String

access-token(BEARER)

+
+
+
+
+
+ + + + + \ No newline at end of file diff --git a/src/main/resources/static/login.html b/src/main/resources/static/login.html new file mode 100644 index 000000000..df96232d6 --- /dev/null +++ b/src/main/resources/static/login.html @@ -0,0 +1,561 @@ + + + + + + + +소셜 로그인 API + + + + + +
+
+

소셜 로그인 API

+
+
+

카카오 로그인

+
+

Curl request

+
+
+
$ curl 'http://localhost:8080/api/login/oauth2/kakao?code=kakao-auth-code' -i -X GET
+
+
+
+
+

HTTP request

+
+
+
GET /api/login/oauth2/kakao?code=kakao-auth-code HTTP/1.1
+Host: localhost:8080
+
+
+
+
+

HTTP response

+
+
+
HTTP/1.1 200 OK
+Content-Type: application/json
+Content-Length: 47
+
+{
+  "accessToken" : "Bearer {{access_token}}"
+}
+
+
+
+
+

HTTPie request

+
+
+
$ http GET 'http://localhost:8080/api/login/oauth2/kakao?code=kakao-auth-code'
+
+
+
+
+

Query parameters

+ ++++ + + + + + + + + + + + + +
ParameterDescription

code

kakao-auth-code

+
+
+

Request body

+
+
+
+
+
+
+
+

Response body

+
+
+
{
+  "accessToken" : "Bearer {{access_token}}"
+}
+
+
+
+
+

Response fields

+ +++++ + + + + + + + + + + + + + + +
PathTypeDescription

accessToken

class java.lang.String

access-token(BEARER)

+
+
+
+
+
+ + + \ No newline at end of file diff --git a/src/main/resources/static/member.html b/src/main/resources/static/member.html new file mode 100644 index 000000000..54b2ad67b --- /dev/null +++ b/src/main/resources/static/member.html @@ -0,0 +1,1219 @@ + + + + + + + +회원 API + + + + + +
+
+

회원 API

+
+
+

회원가입

+
+

Curl request

+
+
+
$ curl 'http://localhost:8080/api/members/register' -i -X POST \
+    -H 'Content-Type: application/json' \
+    -d '{
+  "email" : "member01@gmail.com",
+  "password" : "password01",
+  "name" : "member01"
+}'
+
+
+
+
+

HTTP request

+
+
+
POST /api/members/register HTTP/1.1
+Content-Type: application/json
+Content-Length: 88
+Host: localhost:8080
+
+{
+  "email" : "member01@gmail.com",
+  "password" : "password01",
+  "name" : "member01"
+}
+
+
+
+
+

HTTP response

+
+
+
HTTP/1.1 201 Created
+Location: http://localhost:8080/api/members/1
+Content-Type: application/json
+Content-Length: 71
+
+{
+  "id" : 1,
+  "email" : "member01@gmail.com",
+  "name" : "member01"
+}
+
+
+
+
+

HTTPie request

+
+
+
$ echo '{
+  "email" : "member01@gmail.com",
+  "password" : "password01",
+  "name" : "member01"
+}' | http POST 'http://localhost:8080/api/members/register' \
+    'Content-Type:application/json'
+
+
+
+
+

Request body

+
+
+
{
+  "email" : "member01@gmail.com",
+  "password" : "password01",
+  "name" : "member01"
+}
+
+
+
+
+

Request fields

+ +++++ + + + + + + + + + + + + + + + + + + + + + + + + +
PathTypeDescription

email

String

이메일

password

String

비밀번호

name

String

이름

+
+
+

Response body

+
+
+
{
+  "id" : 1,
+  "email" : "member01@gmail.com",
+  "name" : "member01"
+}
+
+
+
+
+

Response fields

+ +++++ + + + + + + + + + + + + + + + + + + + + + + + + +
PathTypeDescription

id

Number

회원 식별자

email

String

이메일

name

String

이름

+
+
+
+

로그인

+
+

Curl request

+
+
+
$ curl 'http://localhost:8080/api/members/login' -i -X POST \
+    -H 'Content-Type: application/json' \
+    -d '{
+  "email" : "member01@gmail.com",
+  "password" : "password01"
+}'
+
+
+
+
+

HTTP request

+
+
+
POST /api/members/login HTTP/1.1
+Content-Type: application/json
+Content-Length: 65
+Host: localhost:8080
+
+{
+  "email" : "member01@gmail.com",
+  "password" : "password01"
+}
+
+
+
+
+

HTTP response

+
+
+
HTTP/1.1 200 OK
+Content-Type: application/json
+Content-Length: 196
+
+{
+  "accessToken" : "eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTU5LCJleHAiOjE3MjIyNjg3NTl9.iuNnicN0kjQYKx7fNiJBtH_2merHROfg_1P7mjBVrFmTi9l9PLkpGOerNzS9ni8vhJ-6fx0cwdbPhYXt7zqaWw"
+}
+
+
+
+
+

HTTPie request

+
+
+
$ echo '{
+  "email" : "member01@gmail.com",
+  "password" : "password01"
+}' | http POST 'http://localhost:8080/api/members/login' \
+    'Content-Type:application/json'
+
+
+
+
+

Request body

+
+
+
{
+  "email" : "member01@gmail.com",
+  "password" : "password01"
+}
+
+
+
+
+

Request fields

+ +++++ + + + + + + + + + + + + + + + + + + + +
PathTypeDescription

email

String

이메일

password

String

비밀번호

+
+
+

Response body

+
+
+
{
+  "accessToken" : "eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTU5LCJleHAiOjE3MjIyNjg3NTl9.iuNnicN0kjQYKx7fNiJBtH_2merHROfg_1P7mjBVrFmTi9l9PLkpGOerNzS9ni8vhJ-6fx0cwdbPhYXt7zqaWw"
+}
+
+
+
+
+

Response fields

+ +++++ + + + + + + + + + + + + + + +
PathTypeDescription

accessToken

String

Bearer Token

+
+
+
+

회원 조회

+
+

Curl request

+
+
+
$ curl 'http://localhost:8080/api/members/1' -i -X GET \
+    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTU5LCJleHAiOjE3MjIyNjg3NTl9.iuNnicN0kjQYKx7fNiJBtH_2merHROfg_1P7mjBVrFmTi9l9PLkpGOerNzS9ni8vhJ-6fx0cwdbPhYXt7zqaWw'
+
+
+
+
+

HTTP request

+
+
+
GET /api/members/1 HTTP/1.1
+Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTU5LCJleHAiOjE3MjIyNjg3NTl9.iuNnicN0kjQYKx7fNiJBtH_2merHROfg_1P7mjBVrFmTi9l9PLkpGOerNzS9ni8vhJ-6fx0cwdbPhYXt7zqaWw
+Host: localhost:8080
+
+
+
+
+

HTTP response

+
+
+
HTTP/1.1 200 OK
+Content-Type: application/json
+Content-Length: 100
+
+{
+  "id" : 1,
+  "email" : "member01@gmail.com",
+  "password" : "password01",
+  "name" : "member01"
+}
+
+
+
+
+

HTTPie request

+
+
+
$ http GET 'http://localhost:8080/api/members/1' \
+    'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTU5LCJleHAiOjE3MjIyNjg3NTl9.iuNnicN0kjQYKx7fNiJBtH_2merHROfg_1P7mjBVrFmTi9l9PLkpGOerNzS9ni8vhJ-6fx0cwdbPhYXt7zqaWw'
+
+
+
+
+

Request body

+
+
+
+
+
+
+
+

Response body

+
+
+
{
+  "id" : 1,
+  "email" : "member01@gmail.com",
+  "password" : "password01",
+  "name" : "member01"
+}
+
+
+
+
+

Response fields

+ +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
PathTypeDescription

id

Number

회원 식별자

email

String

이메일

password

String

비밀번호

name

String

이름

+
+
+
+

위시 상품 조회

+
+

Curl request

+
+
+
$ curl 'http://localhost:8080/api/members/wishlist' -i -X GET \
+    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTU5LCJleHAiOjE3MjIyNjg3NTl9.iuNnicN0kjQYKx7fNiJBtH_2merHROfg_1P7mjBVrFmTi9l9PLkpGOerNzS9ni8vhJ-6fx0cwdbPhYXt7zqaWw'
+
+
+
+
+

HTTP request

+
+
+
GET /api/members/wishlist HTTP/1.1
+Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTU5LCJleHAiOjE3MjIyNjg3NTl9.iuNnicN0kjQYKx7fNiJBtH_2merHROfg_1P7mjBVrFmTi9l9PLkpGOerNzS9ni8vhJ-6fx0cwdbPhYXt7zqaWw
+Host: localhost:8080
+
+
+
+
+

HTTP response

+
+
+
HTTP/1.1 200 OK
+Content-Type: application/json
+Content-Length: 180
+
+{
+  "wishlist" : [ {
+    "id" : 1,
+    "productId" : 1,
+    "name" : "product01",
+    "price" : 1000,
+    "quantity" : 5,
+    "imageUrl" : "https://via.placeholder.com/150"
+  } ]
+}
+
+
+
+
+

HTTPie request

+
+
+
$ http GET 'http://localhost:8080/api/members/wishlist' \
+    'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTU5LCJleHAiOjE3MjIyNjg3NTl9.iuNnicN0kjQYKx7fNiJBtH_2merHROfg_1P7mjBVrFmTi9l9PLkpGOerNzS9ni8vhJ-6fx0cwdbPhYXt7zqaWw'
+
+
+
+
+

Query parameters

+ ++++ + + + + + + + + + + + + + + + + +
ParameterDescription

page

페이지 번호

size

페이지 크기

+
+
+

Request body

+
+
+
+
+
+
+
+

Response body

+
+
+
{
+  "wishlist" : [ {
+    "id" : 1,
+    "productId" : 1,
+    "name" : "product01",
+    "price" : 1000,
+    "quantity" : 5,
+    "imageUrl" : "https://via.placeholder.com/150"
+  } ]
+}
+
+
+
+
+

Response fields

+ +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
PathTypeDescription

wishlist[].id

Number

위시 상품 식별자

wishlist[].productId

Number

상품 식별자

wishlist[].name

String

상품명

wishlist[].price

Number

가격

wishlist[].quantity

Number

재고 수량

wishlist[].imageUrl

String

이미지 URL

+
+
+
+

위시 상품 수정

+
+

Curl request

+
+
+
$ curl 'http://localhost:8080/api/members/wishlist/1' -i -X PUT \
+    -H 'Content-Type: application/json' \
+    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTU5LCJleHAiOjE3MjIyNjg3NTl9.iuNnicN0kjQYKx7fNiJBtH_2merHROfg_1P7mjBVrFmTi9l9PLkpGOerNzS9ni8vhJ-6fx0cwdbPhYXt7zqaWw' \
+    -d '{
+  "quantity" : 5
+}'
+
+
+
+
+

HTTP request

+
+
+
PUT /api/members/wishlist/1 HTTP/1.1
+Content-Type: application/json
+Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTU5LCJleHAiOjE3MjIyNjg3NTl9.iuNnicN0kjQYKx7fNiJBtH_2merHROfg_1P7mjBVrFmTi9l9PLkpGOerNzS9ni8vhJ-6fx0cwdbPhYXt7zqaWw
+Content-Length: 20
+Host: localhost:8080
+
+{
+  "quantity" : 5
+}
+
+
+
+
+

HTTP response

+
+
+
HTTP/1.1 200 OK
+Content-Type: application/json
+Content-Length: 143
+
+{
+  "id" : 1,
+  "productId" : 1,
+  "name" : "product01",
+  "price" : 1000,
+  "quantity" : 5,
+  "imageUrl" : "https://via.placeholder.com/150"
+}
+
+
+
+
+

HTTPie request

+
+
+
$ echo '{
+  "quantity" : 5
+}' | http PUT 'http://localhost:8080/api/members/wishlist/1' \
+    'Content-Type:application/json' \
+    'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTU5LCJleHAiOjE3MjIyNjg3NTl9.iuNnicN0kjQYKx7fNiJBtH_2merHROfg_1P7mjBVrFmTi9l9PLkpGOerNzS9ni8vhJ-6fx0cwdbPhYXt7zqaWw'
+
+
+
+
+

Request body

+
+
+
{
+  "quantity" : 5
+}
+
+
+
+
+

Request fields

+ +++++ + + + + + + + + + + + + + + +
PathTypeDescription

quantity

Number

수량

+
+
+

Response body

+
+
+
{
+  "id" : 1,
+  "productId" : 1,
+  "name" : "product01",
+  "price" : 1000,
+  "quantity" : 5,
+  "imageUrl" : "https://via.placeholder.com/150"
+}
+
+
+
+
+

Response fields

+ +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
PathTypeDescription

id

Number

위시 상품 식별자

productId

Number

상품 식별자

name

String

상품명

price

Number

가격

quantity

Number

재고 수량

imageUrl

String

이미지 URL

+
+
+
+

위시 상품 삭제

+
+

Curl request

+
+
+
$ curl 'http://localhost:8080/api/members/wishlist/1' -i -X DELETE \
+    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTU5LCJleHAiOjE3MjIyNjg3NTl9.iuNnicN0kjQYKx7fNiJBtH_2merHROfg_1P7mjBVrFmTi9l9PLkpGOerNzS9ni8vhJ-6fx0cwdbPhYXt7zqaWw'
+
+
+
+
+

HTTP request

+
+
+
DELETE /api/members/wishlist/1 HTTP/1.1
+Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTU5LCJleHAiOjE3MjIyNjg3NTl9.iuNnicN0kjQYKx7fNiJBtH_2merHROfg_1P7mjBVrFmTi9l9PLkpGOerNzS9ni8vhJ-6fx0cwdbPhYXt7zqaWw
+Host: localhost:8080
+
+
+
+
+

HTTP response

+
+
+
HTTP/1.1 204 No Content
+
+
+
+
+

HTTPie request

+
+
+
$ http DELETE 'http://localhost:8080/api/members/wishlist/1' \
+    'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTU5LCJleHAiOjE3MjIyNjg3NTl9.iuNnicN0kjQYKx7fNiJBtH_2merHROfg_1P7mjBVrFmTi9l9PLkpGOerNzS9ni8vhJ-6fx0cwdbPhYXt7zqaWw'
+
+
+
+
+

Request body

+
+
+
+
+
+
+
+

Response body

+
+
+
+
+
+
+
+
+
+
+ + + \ No newline at end of file diff --git a/src/main/resources/static/product.html b/src/main/resources/static/product.html new file mode 100644 index 000000000..98285e773 --- /dev/null +++ b/src/main/resources/static/product.html @@ -0,0 +1,2092 @@ + + + + + + + +상품 API + + + + + +
+
+

상품 API

+
+
+

전체 상품 조회

+
+

Curl request

+
+
+
$ curl 'http://localhost:8080/api/products' -i -X GET \
+    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w'
+
+
+
+
+

HTTP request

+
+
+
GET /api/products HTTP/1.1
+Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w
+Host: localhost:8080
+
+
+
+
+

HTTP response

+
+
+
HTTP/1.1 200 OK
+Content-Type: application/json
+Content-Length: 443
+
+{
+  "products" : [ {
+    "id" : 1,
+    "name" : "상품01",
+    "price" : 1000,
+    "imageUrl" : "https://via.placeholder.com/150",
+    "options" : [ {
+      "id" : 1,
+      "name" : "상품 옵션 01",
+      "stock" : 100
+    } ],
+    "category" : {
+      "id" : 1,
+      "name" : "카테고리01",
+      "description" : "카테고리01 입니다",
+      "imageUrl" : "https://via.placeholder.com/150",
+      "color" : "#FFFFFF"
+    }
+  } ]
+}
+
+
+
+
+

HTTPie request

+
+
+
$ http GET 'http://localhost:8080/api/products' \
+    'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w'
+
+
+
+
+

Query parameters

+ ++++ + + + + + + + + + + + + + + + + +
ParameterDescription

page

페이지 번호

size

페이지 크기

+
+
+

Request body

+
+
+
+
+
+
+
+

Response body

+
+
+
{
+  "products" : [ {
+    "id" : 1,
+    "name" : "상품01",
+    "price" : 1000,
+    "imageUrl" : "https://via.placeholder.com/150",
+    "options" : [ {
+      "id" : 1,
+      "name" : "상품 옵션 01",
+      "stock" : 100
+    } ],
+    "category" : {
+      "id" : 1,
+      "name" : "카테고리01",
+      "description" : "카테고리01 입니다",
+      "imageUrl" : "https://via.placeholder.com/150",
+      "color" : "#FFFFFF"
+    }
+  } ]
+}
+
+
+
+
+

Response fields

+ +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
PathTypeDescription

products[].id

Number

상품 ID

products[].name

String

상품명

products[].price

Number

상품 가격

products[].imageUrl

String

상품 이미지 URL

products[].options

Array

상품 옵션 목록

products[].options[].id

Number

상품 옵션 ID

products[].options[].name

String

상품 옵션명

products[].options[].stock

Number

상품 옵션 재고

products[].category.id

Number

카테고리 ID

products[].category.name

String

카테고리명

products[].category.description

String

카테고리 설명

products[].category.imageUrl

String

카테고리 이미지 URL

products[].category.color

String

카테고리 색상

+
+
+
+

단일 상품 조회

+
+

Curl request

+
+
+
$ curl 'http://localhost:8080/api/products/1' -i -X GET \
+    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w'
+
+
+
+
+

HTTP request

+
+
+
GET /api/products/1 HTTP/1.1
+Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w
+Host: localhost:8080
+
+
+
+
+

HTTP response

+
+
+
HTTP/1.1 200 OK
+Content-Type: application/json
+Content-Length: 386
+
+{
+  "id" : 1,
+  "name" : "상품01",
+  "price" : 1000,
+  "imageUrl" : "https://via.placeholder.com/150",
+  "options" : [ {
+    "id" : 1,
+    "name" : "상품 옵션 01",
+    "stock" : 100
+  } ],
+  "category" : {
+    "id" : 1,
+    "name" : "카테고리01",
+    "description" : "카테고리01 입니다",
+    "imageUrl" : "https://via.placeholder.com/150",
+    "color" : "#FFFFFF"
+  }
+}
+
+
+
+
+

HTTPie request

+
+
+
$ http GET 'http://localhost:8080/api/products/1' \
+    'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w'
+
+
+
+
+

Request body

+
+
+
+
+
+
+
+

Response body

+
+
+
{
+  "id" : 1,
+  "name" : "상품01",
+  "price" : 1000,
+  "imageUrl" : "https://via.placeholder.com/150",
+  "options" : [ {
+    "id" : 1,
+    "name" : "상품 옵션 01",
+    "stock" : 100
+  } ],
+  "category" : {
+    "id" : 1,
+    "name" : "카테고리01",
+    "description" : "카테고리01 입니다",
+    "imageUrl" : "https://via.placeholder.com/150",
+    "color" : "#FFFFFF"
+  }
+}
+
+
+
+
+

Response fields

+ +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
PathTypeDescription

id

Number

상품 ID

name

String

상품명

price

Number

상품 가격

imageUrl

String

상품 이미지 URL

options

Array

상품 옵션 목록

options[].id

Number

상품 옵션 ID

options[].name

String

상품 옵션명

options[].stock

Number

상품 옵션 재고

category.id

Number

카테고리 ID

category.name

String

카테고리명

category.description

String

카테고리 설명

category.imageUrl

String

카테고리 이미지 URL

category.color

String

카테고리 색상

+
+
+
+

상품 생성

+
+

Curl request

+
+
+
$ curl 'http://localhost:8080/api/products' -i -X POST \
+    -H 'Content-Type: application/json' \
+    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w' \
+    -d '{
+  "name" : "상품01",
+  "price" : 1000,
+  "imageUrl" : "https://via.placeholder.com/150",
+  "categoryId" : 1,
+  "productOptions" : [ {
+    "name" : "상품 옵션 01",
+    "stock" : 100
+  } ]
+}'
+
+
+
+
+

HTTP request

+
+
+
POST /api/products HTTP/1.1
+Content-Type: application/json
+Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w
+Content-Length: 196
+Host: localhost:8080
+
+{
+  "name" : "상품01",
+  "price" : 1000,
+  "imageUrl" : "https://via.placeholder.com/150",
+  "categoryId" : 1,
+  "productOptions" : [ {
+    "name" : "상품 옵션 01",
+    "stock" : 100
+  } ]
+}
+
+
+
+
+

HTTP response

+
+
+
HTTP/1.1 201 Created
+Location: http://localhost:8080/api/products/1
+Content-Type: application/json
+Content-Length: 216
+
+{
+  "id" : 1,
+  "name" : "상품01",
+  "price" : 1000,
+  "imageUrl" : "https://via.placeholder.com/150",
+  "options" : [ {
+    "id" : 1,
+    "name" : "상품 옵션 01",
+    "stock" : 100,
+    "productId" : 1
+  } ]
+}
+
+
+
+
+

HTTPie request

+
+
+
$ echo '{
+  "name" : "상품01",
+  "price" : 1000,
+  "imageUrl" : "https://via.placeholder.com/150",
+  "categoryId" : 1,
+  "productOptions" : [ {
+    "name" : "상품 옵션 01",
+    "stock" : 100
+  } ]
+}' | http POST 'http://localhost:8080/api/products' \
+    'Content-Type:application/json' \
+    'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w'
+
+
+
+
+

Request body

+
+
+
{
+  "name" : "상품01",
+  "price" : 1000,
+  "imageUrl" : "https://via.placeholder.com/150",
+  "categoryId" : 1,
+  "productOptions" : [ {
+    "name" : "상품 옵션 01",
+    "stock" : 100
+  } ]
+}
+
+
+
+
+

Request fields

+ +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
PathTypeDescription

name

String

상품명

price

Number

상품 가격

imageUrl

String

상품 이미지 URL

categoryId

Number

카테고리 ID

productOptions

Array

상품 옵션 목록

productOptions[].name

String

상품 옵션명

productOptions[].stock

Number

상품 옵션 재고

+
+
+

Response body

+
+
+
{
+  "id" : 1,
+  "name" : "상품01",
+  "price" : 1000,
+  "imageUrl" : "https://via.placeholder.com/150",
+  "options" : [ {
+    "id" : 1,
+    "name" : "상품 옵션 01",
+    "stock" : 100,
+    "productId" : 1
+  } ]
+}
+
+
+
+
+

Response fields

+ +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
PathTypeDescription

id

Number

상품 ID

name

String

상품명

price

Number

상품 가격

imageUrl

String

상품 이미지 URL

options

Array

상품 옵션 목록

options[].id

Number

상품 옵션 ID

options[].name

String

상품 옵션명

options[].stock

Number

상품 옵션 재고

options[].productId

Number

상품 옵션 상품 ID

+
+
+
+

상품 수정

+
+

Curl request

+
+
+
$ curl 'http://localhost:8080/api/products/1' -i -X PUT \
+    -H 'Content-Type: application/json' \
+    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w' \
+    -d '{
+  "name" : "상품01",
+  "price" : 1000,
+  "imageUrl" : "https://via.placeholder.com/150"
+}'
+
+
+
+
+

HTTP request

+
+
+
PUT /api/products/1 HTTP/1.1
+Content-Type: application/json
+Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w
+Content-Length: 93
+Host: localhost:8080
+
+{
+  "name" : "상품01",
+  "price" : 1000,
+  "imageUrl" : "https://via.placeholder.com/150"
+}
+
+
+
+
+

HTTP response

+
+
+
HTTP/1.1 200 OK
+Content-Type: application/json
+Content-Length: 296
+
+{
+  "id" : 1,
+  "name" : "상품01",
+  "price" : 1000,
+  "imageUrl" : "https://via.placeholder.com/150",
+  "category" : {
+    "id" : 1,
+    "name" : "카테고리01",
+    "description" : "카테고리01 입니다",
+    "imageUrl" : "https://via.placeholder.com/150",
+    "color" : "#FFFFFF"
+  }
+}
+
+
+
+
+

HTTPie request

+
+
+
$ echo '{
+  "name" : "상품01",
+  "price" : 1000,
+  "imageUrl" : "https://via.placeholder.com/150"
+}' | http PUT 'http://localhost:8080/api/products/1' \
+    'Content-Type:application/json' \
+    'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w'
+
+
+
+
+

Request body

+
+
+
{
+  "name" : "상품01",
+  "price" : 1000,
+  "imageUrl" : "https://via.placeholder.com/150"
+}
+
+
+
+
+

Request fields

+ +++++ + + + + + + + + + + + + + + + + + + + + + + + + +
PathTypeDescription

name

String

상품명

price

Number

상품 가격

imageUrl

String

상품 이미지 URL

+
+
+

Response body

+
+
+
{
+  "id" : 1,
+  "name" : "상품01",
+  "price" : 1000,
+  "imageUrl" : "https://via.placeholder.com/150",
+  "category" : {
+    "id" : 1,
+    "name" : "카테고리01",
+    "description" : "카테고리01 입니다",
+    "imageUrl" : "https://via.placeholder.com/150",
+    "color" : "#FFFFFF"
+  }
+}
+
+
+
+
+

Response fields

+ +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
PathTypeDescription

id

Number

상품 ID

name

String

상품명

price

Number

상품 가격

imageUrl

String

상품 이미지 URL

category.id

Number

카테고리 ID

category.name

String

카테고리명

category.description

String

카테고리 설명

category.imageUrl

String

카테고리 이미지 URL

category.color

String

카테고리 색상

+
+
+
+

상품 삭제

+
+

Curl request

+
+
+
$ curl 'http://localhost:8080/api/products/1' -i -X DELETE \
+    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w'
+
+
+
+
+

HTTP request

+
+
+
DELETE /api/products/1 HTTP/1.1
+Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w
+Host: localhost:8080
+
+
+
+
+

HTTP response

+
+
+
HTTP/1.1 204 No Content
+
+
+
+
+

HTTPie request

+
+
+
$ http DELETE 'http://localhost:8080/api/products/1' \
+    'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w'
+
+
+
+
+

Request body

+
+
+
+
+
+
+
+

Response body

+
+
+
+
+
+
+
+
+

상품 주문

+
+

Curl request

+
+
+
$ curl 'http://localhost:8080/api/products/1/order' -i -X POST \
+    -H 'Content-Type: application/json' \
+    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w' \
+    -d '{
+  "optionId" : 1,
+  "quantity" : 1,
+  "message" : "message"
+}'
+
+
+
+
+

HTTP request

+
+
+
POST /api/products/1/order HTTP/1.1
+Content-Type: application/json
+Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w
+Content-Length: 63
+Host: localhost:8080
+
+{
+  "optionId" : 1,
+  "quantity" : 1,
+  "message" : "message"
+}
+
+
+
+
+

HTTP response

+
+
+
HTTP/1.1 200 OK
+Content-Type: application/json
+Content-Length: 134
+
+{
+  "productId" : 1,
+  "optionId" : 1,
+  "optionStock" : 10,
+  "quantity" : 1,
+  "productName" : "상품01",
+  "message" : "message"
+}
+
+
+
+
+

HTTPie request

+
+
+
$ echo '{
+  "optionId" : 1,
+  "quantity" : 1,
+  "message" : "message"
+}' | http POST 'http://localhost:8080/api/products/1/order' \
+    'Content-Type:application/json' \
+    'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w'
+
+
+
+
+

Request body

+
+
+
{
+  "optionId" : 1,
+  "quantity" : 1,
+  "message" : "message"
+}
+
+
+
+
+

Request fields

+ +++++ + + + + + + + + + + + + + + + + + + + + + + + + +
PathTypeDescription

optionId

Number

상품 옵션 ID

quantity

Number

수량

message

String

메시지

+
+
+

Response body

+
+
+
{
+  "productId" : 1,
+  "optionId" : 1,
+  "optionStock" : 10,
+  "quantity" : 1,
+  "productName" : "상품01",
+  "message" : "message"
+}
+
+
+
+
+

Response fields

+ +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
PathTypeDescription

productId

Number

상품 ID

optionId

Number

상품 옵션 ID

optionStock

Number

상품 옵션 재고

quantity

Number

수량

productName

String

상품명

message

String

메시지

+
+
+
+

위시 상품 추가

+
+

Curl request

+
+
+
$ curl 'http://localhost:8080/api/products/wish' -i -X POST \
+    -H 'Content-Type: application/json' \
+    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w' \
+    -d '{
+  "productId" : 1,
+  "quantity" : 1
+}'
+
+
+
+
+

HTTP request

+
+
+
POST /api/products/wish HTTP/1.1
+Content-Type: application/json
+Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w
+Content-Length: 39
+Host: localhost:8080
+
+{
+  "productId" : 1,
+  "quantity" : 1
+}
+
+
+
+
+

HTTP response

+
+
+
HTTP/1.1 200 OK
+Content-Type: application/json
+Content-Length: 32
+
+{
+  "id" : 1,
+  "quantity" : 1
+}
+
+
+
+
+

HTTPie request

+
+
+
$ echo '{
+  "productId" : 1,
+  "quantity" : 1
+}' | http POST 'http://localhost:8080/api/products/wish' \
+    'Content-Type:application/json' \
+    'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w'
+
+
+
+
+

Request body

+
+
+
{
+  "productId" : 1,
+  "quantity" : 1
+}
+
+
+
+
+

Request fields

+ +++++ + + + + + + + + + + + + + + + + + + + +
PathTypeDescription

productId

Number

상품 ID

quantity

Number

수량

+
+
+

Response body

+
+
+
{
+  "id" : 1,
+  "quantity" : 1
+}
+
+
+
+
+

Response fields

+ +++++ + + + + + + + + + + + + + + + + + + + +
PathTypeDescription

id

Number

위시 상품 ID

quantity

Number

수량

+
+
+
+

상품 옵션 생성

+
+

Curl request

+
+
+
$ curl 'http://localhost:8080/api/products/1/options' -i -X POST \
+    -H 'Content-Type: application/json' \
+    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w' \
+    -d '{
+  "name" : "상품 옵션 01",
+  "stock" : 100
+}'
+
+
+
+
+

HTTP request

+
+
+
POST /api/products/1/options HTTP/1.1
+Content-Type: application/json
+Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w
+Content-Length: 50
+Host: localhost:8080
+
+{
+  "name" : "상품 옵션 01",
+  "stock" : 100
+}
+
+
+
+
+

HTTP response

+
+
+
HTTP/1.1 201 Created
+Location: http://localhost:8080/api/products/1/options/1
+Content-Type: application/json
+Content-Length: 62
+
+{
+  "id" : 1,
+  "name" : "상품 옵션 01",
+  "stock" : 100
+}
+
+
+
+
+

HTTPie request

+
+
+
$ echo '{
+  "name" : "상품 옵션 01",
+  "stock" : 100
+}' | http POST 'http://localhost:8080/api/products/1/options' \
+    'Content-Type:application/json' \
+    'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w'
+
+
+
+
+

Request body

+
+
+
{
+  "name" : "상품 옵션 01",
+  "stock" : 100
+}
+
+
+
+
+

Response body

+
+
+
{
+  "id" : 1,
+  "name" : "상품 옵션 01",
+  "stock" : 100
+}
+
+
+
+
+

Response fields

+ +++++ + + + + + + + + + + + + + + + + + + + + + + + + +
PathTypeDescription

id

Number

상품 옵션 ID

name

String

상품 옵션명

stock

Number

상품 옵션 재고

+
+
+
+

상품 옵션 조회

+
+

Curl request

+
+
+
$ curl 'http://localhost:8080/api/products/1/options' -i -X GET \
+    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w'
+
+
+
+
+

HTTP request

+
+
+
GET /api/products/1/options HTTP/1.1
+Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w
+Host: localhost:8080
+
+
+
+
+

HTTP response

+
+
+
HTTP/1.1 200 OK
+Content-Type: application/json
+Content-Length: 92
+
+{
+  "options" : [ {
+    "id" : 1,
+    "name" : "상품 옵션 01",
+    "stock" : 100
+  } ]
+}
+
+
+
+
+

HTTPie request

+
+
+
$ http GET 'http://localhost:8080/api/products/1/options' \
+    'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w'
+
+
+
+
+

Request body

+
+
+
+
+
+
+
+

Response body

+
+
+
{
+  "options" : [ {
+    "id" : 1,
+    "name" : "상품 옵션 01",
+    "stock" : 100
+  } ]
+}
+
+
+
+
+

Response fields

+ +++++ + + + + + + + + + + + + + + + + + + + + + + + + +
PathTypeDescription

options[].id

Number

상품 옵션 ID

options[].name

String

상품 옵션명

options[].stock

Number

상품 옵션 재고

+
+
+
+

상품 옵션 수정

+
+

Curl request

+
+
+
$ curl 'http://localhost:8080/api/products/1/options/1' -i -X PUT \
+    -H 'Content-Type: application/json' \
+    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w' \
+    -d '{
+  "name" : "상품 옵션 01",
+  "stock" : 100
+}'
+
+
+
+
+

HTTP request

+
+
+
PUT /api/products/1/options/1 HTTP/1.1
+Content-Type: application/json
+Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w
+Content-Length: 50
+Host: localhost:8080
+
+{
+  "name" : "상품 옵션 01",
+  "stock" : 100
+}
+
+
+
+
+

HTTP response

+
+
+
HTTP/1.1 200 OK
+Content-Type: application/json
+Content-Length: 62
+
+{
+  "id" : 1,
+  "name" : "상품 옵션 01",
+  "stock" : 100
+}
+
+
+
+
+

HTTPie request

+
+
+
$ echo '{
+  "name" : "상품 옵션 01",
+  "stock" : 100
+}' | http PUT 'http://localhost:8080/api/products/1/options/1' \
+    'Content-Type:application/json' \
+    'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w'
+
+
+
+
+

Request body

+
+
+
{
+  "name" : "상품 옵션 01",
+  "stock" : 100
+}
+
+
+
+
+

Request fields

+ +++++ + + + + + + + + + + + + + + + + + + + +
PathTypeDescription

name

String

상품 옵션명

stock

Number

상품 옵션 재고

+
+
+

Response body

+
+
+
{
+  "id" : 1,
+  "name" : "상품 옵션 01",
+  "stock" : 100
+}
+
+
+
+
+

Response fields

+ +++++ + + + + + + + + + + + + + + + + + + + + + + + + +
PathTypeDescription

id

Number

상품 옵션 ID

name

String

상품 옵션명

stock

Number

상품 옵션 재고

+
+
+
+

상품 옵션 삭제

+
+

Curl request

+
+
+
$ curl 'http://localhost:8080/api/products/1/options/1' -i -X DELETE \
+    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w'
+
+
+
+
+

HTTP request

+
+
+
DELETE /api/products/1/options/1 HTTP/1.1
+Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w
+Host: localhost:8080
+
+
+
+
+

HTTP response

+
+
+
HTTP/1.1 204 No Content
+
+
+
+
+

HTTPie request

+
+
+
$ http DELETE 'http://localhost:8080/api/products/1/options/1' \
+    'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w'
+
+
+
+
+

Request body

+
+
+
+
+
+
+
+

Response body

+
+
+
+
+
+
+
+
+
+
+ + + \ No newline at end of file From 5968669528b3d1fa3f460ef4bb8381f158e811d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=82=98=EC=A0=9C=EB=B2=95?= Date: Tue, 30 Jul 2024 00:14:02 +0900 Subject: [PATCH 136/138] =?UTF-8?q?fest:=20API=20=EB=AA=85=EC=84=B8=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/resources/static/category.html | 30 ++-- src/main/resources/static/docs.html | 130 +++++++++--------- src/main/resources/static/member.html | 28 ++-- src/main/resources/static/product.html | 72 +++++----- .../api/ProductApiControllerTest.java | 2 + 5 files changed, 132 insertions(+), 130 deletions(-) diff --git a/src/main/resources/static/category.html b/src/main/resources/static/category.html index ebb723429..6de14f60d 100644 --- a/src/main/resources/static/category.html +++ b/src/main/resources/static/category.html @@ -451,7 +451,7 @@

Curl request

$ curl 'http://localhost:8080/api/categories' -i -X POST \
     -H 'Content-Type: application/json' \
-    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTU3LCJleHAiOjE3MjIyNjg3NTd9.TJZznQ-W-PQIhpYNTuIE6Frb4r9ZAK0C5c84c8TKJCTk7fFIPupn7RH2vdijJXlsTvpwid8QzsMwLBoKKQaCaQ' \
+    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDEwLCJleHAiOjE3MjIyNjk2MTB9.URlH4KvMFjYZGvtIUJxxR7CmW2mZFc6-cYuy0Hj9C7En67-0nNt3EgOJ-9XWRBPhFUGvbhnbKCzao2At2lOncw' \
     -d '{
   "name" : "카테고리01",
   "description" : "카테고리01 설명",
@@ -467,7 +467,7 @@ 

HTTP request

POST /api/categories HTTP/1.1
 Content-Type: application/json
-Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTU3LCJleHAiOjE3MjIyNjg3NTd9.TJZznQ-W-PQIhpYNTuIE6Frb4r9ZAK0C5c84c8TKJCTk7fFIPupn7RH2vdijJXlsTvpwid8QzsMwLBoKKQaCaQ
+Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDEwLCJleHAiOjE3MjIyNjk2MTB9.URlH4KvMFjYZGvtIUJxxR7CmW2mZFc6-cYuy0Hj9C7En67-0nNt3EgOJ-9XWRBPhFUGvbhnbKCzao2At2lOncw
 Content-Length: 147
 Host: localhost:8080
 
@@ -510,7 +510,7 @@ 

HTTPie request

"color" : "#FFFFFF" }' | http POST 'http://localhost:8080/api/categories' \ 'Content-Type:application/json' \ - 'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTU3LCJleHAiOjE3MjIyNjg3NTd9.TJZznQ-W-PQIhpYNTuIE6Frb4r9ZAK0C5c84c8TKJCTk7fFIPupn7RH2vdijJXlsTvpwid8QzsMwLBoKKQaCaQ'
+ 'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDEwLCJleHAiOjE3MjIyNjk2MTB9.URlH4KvMFjYZGvtIUJxxR7CmW2mZFc6-cYuy0Hj9C7En67-0nNt3EgOJ-9XWRBPhFUGvbhnbKCzao2At2lOncw'
@@ -633,7 +633,7 @@

Curl request

$ curl 'http://localhost:8080/api/categories/1' -i -X PUT \
     -H 'Content-Type: application/json' \
-    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTU3LCJleHAiOjE3MjIyNjg3NTd9.TJZznQ-W-PQIhpYNTuIE6Frb4r9ZAK0C5c84c8TKJCTk7fFIPupn7RH2vdijJXlsTvpwid8QzsMwLBoKKQaCaQ' \
+    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDEwLCJleHAiOjE3MjIyNjk2MTB9.URlH4KvMFjYZGvtIUJxxR7CmW2mZFc6-cYuy0Hj9C7En67-0nNt3EgOJ-9XWRBPhFUGvbhnbKCzao2At2lOncw' \
     -d '{
   "name" : "카테고리01",
   "description" : "카테고리01 설명",
@@ -649,7 +649,7 @@ 

HTTP request

PUT /api/categories/1 HTTP/1.1
 Content-Type: application/json
-Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTU3LCJleHAiOjE3MjIyNjg3NTd9.TJZznQ-W-PQIhpYNTuIE6Frb4r9ZAK0C5c84c8TKJCTk7fFIPupn7RH2vdijJXlsTvpwid8QzsMwLBoKKQaCaQ
+Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDEwLCJleHAiOjE3MjIyNjk2MTB9.URlH4KvMFjYZGvtIUJxxR7CmW2mZFc6-cYuy0Hj9C7En67-0nNt3EgOJ-9XWRBPhFUGvbhnbKCzao2At2lOncw
 Content-Length: 147
 Host: localhost:8080
 
@@ -691,7 +691,7 @@ 

HTTPie request

"color" : "#FFFFFF" }' | http PUT 'http://localhost:8080/api/categories/1' \ 'Content-Type:application/json' \ - 'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTU3LCJleHAiOjE3MjIyNjg3NTd9.TJZznQ-W-PQIhpYNTuIE6Frb4r9ZAK0C5c84c8TKJCTk7fFIPupn7RH2vdijJXlsTvpwid8QzsMwLBoKKQaCaQ'
+ 'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDEwLCJleHAiOjE3MjIyNjk2MTB9.URlH4KvMFjYZGvtIUJxxR7CmW2mZFc6-cYuy0Hj9C7En67-0nNt3EgOJ-9XWRBPhFUGvbhnbKCzao2At2lOncw'
@@ -813,7 +813,7 @@

Curl request

$ curl 'http://localhost:8080/api/categories/1' -i -X DELETE \
-    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTU3LCJleHAiOjE3MjIyNjg3NTd9.TJZznQ-W-PQIhpYNTuIE6Frb4r9ZAK0C5c84c8TKJCTk7fFIPupn7RH2vdijJXlsTvpwid8QzsMwLBoKKQaCaQ'
+ -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDEwLCJleHAiOjE3MjIyNjk2MTB9.URlH4KvMFjYZGvtIUJxxR7CmW2mZFc6-cYuy0Hj9C7En67-0nNt3EgOJ-9XWRBPhFUGvbhnbKCzao2At2lOncw'
@@ -822,7 +822,7 @@

HTTP request

DELETE /api/categories/1 HTTP/1.1
-Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTU3LCJleHAiOjE3MjIyNjg3NTd9.TJZznQ-W-PQIhpYNTuIE6Frb4r9ZAK0C5c84c8TKJCTk7fFIPupn7RH2vdijJXlsTvpwid8QzsMwLBoKKQaCaQ
+Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDEwLCJleHAiOjE3MjIyNjk2MTB9.URlH4KvMFjYZGvtIUJxxR7CmW2mZFc6-cYuy0Hj9C7En67-0nNt3EgOJ-9XWRBPhFUGvbhnbKCzao2At2lOncw
 Host: localhost:8080
@@ -840,7 +840,7 @@

HTTPie request

$ http DELETE 'http://localhost:8080/api/categories/1' \
-    'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTU3LCJleHAiOjE3MjIyNjg3NTd9.TJZznQ-W-PQIhpYNTuIE6Frb4r9ZAK0C5c84c8TKJCTk7fFIPupn7RH2vdijJXlsTvpwid8QzsMwLBoKKQaCaQ'
+ 'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDEwLCJleHAiOjE3MjIyNjk2MTB9.URlH4KvMFjYZGvtIUJxxR7CmW2mZFc6-cYuy0Hj9C7En67-0nNt3EgOJ-9XWRBPhFUGvbhnbKCzao2At2lOncw'
@@ -868,7 +868,7 @@

Curl request

$ curl 'http://localhost:8080/api/categories/1' -i -X GET \
-    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTU3LCJleHAiOjE3MjIyNjg3NTd9.TJZznQ-W-PQIhpYNTuIE6Frb4r9ZAK0C5c84c8TKJCTk7fFIPupn7RH2vdijJXlsTvpwid8QzsMwLBoKKQaCaQ'
+ -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDEwLCJleHAiOjE3MjIyNjk2MTB9.URlH4KvMFjYZGvtIUJxxR7CmW2mZFc6-cYuy0Hj9C7En67-0nNt3EgOJ-9XWRBPhFUGvbhnbKCzao2At2lOncw'
@@ -877,7 +877,7 @@

HTTP request

GET /api/categories/1 HTTP/1.1
-Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTU3LCJleHAiOjE3MjIyNjg3NTd9.TJZznQ-W-PQIhpYNTuIE6Frb4r9ZAK0C5c84c8TKJCTk7fFIPupn7RH2vdijJXlsTvpwid8QzsMwLBoKKQaCaQ
+Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDEwLCJleHAiOjE3MjIyNjk2MTB9.URlH4KvMFjYZGvtIUJxxR7CmW2mZFc6-cYuy0Hj9C7En67-0nNt3EgOJ-9XWRBPhFUGvbhnbKCzao2At2lOncw
 Host: localhost:8080
@@ -905,7 +905,7 @@

HTTPie request

$ http GET 'http://localhost:8080/api/categories/1' \
-    'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTU3LCJleHAiOjE3MjIyNjg3NTd9.TJZznQ-W-PQIhpYNTuIE6Frb4r9ZAK0C5c84c8TKJCTk7fFIPupn7RH2vdijJXlsTvpwid8QzsMwLBoKKQaCaQ'
+ 'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDEwLCJleHAiOjE3MjIyNjk2MTB9.URlH4KvMFjYZGvtIUJxxR7CmW2mZFc6-cYuy0Hj9C7En67-0nNt3EgOJ-9XWRBPhFUGvbhnbKCzao2At2lOncw'
@@ -983,7 +983,7 @@

Curl request

$ curl 'http://localhost:8080/api/categories' -i -X GET \
-    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTU3LCJleHAiOjE3MjIyNjg3NTd9.TJZznQ-W-PQIhpYNTuIE6Frb4r9ZAK0C5c84c8TKJCTk7fFIPupn7RH2vdijJXlsTvpwid8QzsMwLBoKKQaCaQ'
+ -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDEwLCJleHAiOjE3MjIyNjk2MTB9.URlH4KvMFjYZGvtIUJxxR7CmW2mZFc6-cYuy0Hj9C7En67-0nNt3EgOJ-9XWRBPhFUGvbhnbKCzao2At2lOncw'
@@ -992,7 +992,7 @@

HTTP request

GET /api/categories HTTP/1.1
-Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTU3LCJleHAiOjE3MjIyNjg3NTd9.TJZznQ-W-PQIhpYNTuIE6Frb4r9ZAK0C5c84c8TKJCTk7fFIPupn7RH2vdijJXlsTvpwid8QzsMwLBoKKQaCaQ
+Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDEwLCJleHAiOjE3MjIyNjk2MTB9.URlH4KvMFjYZGvtIUJxxR7CmW2mZFc6-cYuy0Hj9C7En67-0nNt3EgOJ-9XWRBPhFUGvbhnbKCzao2At2lOncw
 Host: localhost:8080
@@ -1028,7 +1028,7 @@

HTTPie request

$ http GET 'http://localhost:8080/api/categories' \
-    'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTU3LCJleHAiOjE3MjIyNjg3NTd9.TJZznQ-W-PQIhpYNTuIE6Frb4r9ZAK0C5c84c8TKJCTk7fFIPupn7RH2vdijJXlsTvpwid8QzsMwLBoKKQaCaQ'
+ 'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDEwLCJleHAiOjE3MjIyNjk2MTB9.URlH4KvMFjYZGvtIUJxxR7CmW2mZFc6-cYuy0Hj9C7En67-0nNt3EgOJ-9XWRBPhFUGvbhnbKCzao2At2lOncw'
diff --git a/src/main/resources/static/docs.html b/src/main/resources/static/docs.html index c2274bc74..f1c33b491 100644 --- a/src/main/resources/static/docs.html +++ b/src/main/resources/static/docs.html @@ -738,8 +738,8 @@

Curl request

-
$ curl 'http://localhost:8080/api/products' -i -X GET \
-    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w'
+
$ curl 'http://localhost:8080/api/products?page=0&size=10' -i -X GET \
+    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDExLCJleHAiOjE3MjIyNjk2MTF9.P8FNBO9WBDAGlz6J4ccAZRed-x7mhAg4h8YVxOvveyuH5v84ZOBtqBmXS4vEBy9o0l53d1Rx8mqdH7IQVfWcww'
@@ -747,8 +747,8 @@

HTTP request

-
GET /api/products HTTP/1.1
-Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w
+
GET /api/products?page=0&size=10 HTTP/1.1
+Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDExLCJleHAiOjE3MjIyNjk2MTF9.P8FNBO9WBDAGlz6J4ccAZRed-x7mhAg4h8YVxOvveyuH5v84ZOBtqBmXS4vEBy9o0l53d1Rx8mqdH7IQVfWcww
 Host: localhost:8080
@@ -788,8 +788,8 @@

HTTPie request

-
$ http GET 'http://localhost:8080/api/products' \
-    'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w'
+
$ http GET 'http://localhost:8080/api/products?page=0&size=10' \
+    'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDExLCJleHAiOjE3MjIyNjk2MTF9.P8FNBO9WBDAGlz6J4ccAZRed-x7mhAg4h8YVxOvveyuH5v84ZOBtqBmXS4vEBy9o0l53d1Rx8mqdH7IQVfWcww'
@@ -945,7 +945,7 @@

$ curl 'http://localhost:8080/api/products/1' -i -X GET \
-    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w'
+ -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDExLCJleHAiOjE3MjIyNjk2MTF9.P8FNBO9WBDAGlz6J4ccAZRed-x7mhAg4h8YVxOvveyuH5v84ZOBtqBmXS4vEBy9o0l53d1Rx8mqdH7IQVfWcww'
@@ -954,7 +954,7 @@

GET /api/products/1 HTTP/1.1
-Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w
+Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDExLCJleHAiOjE3MjIyNjk2MTF9.P8FNBO9WBDAGlz6J4ccAZRed-x7mhAg4h8YVxOvveyuH5v84ZOBtqBmXS4vEBy9o0l53d1Rx8mqdH7IQVfWcww
 Host: localhost:8080
@@ -993,7 +993,7 @@

$ http GET 'http://localhost:8080/api/products/1' \
-    'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w'
+ 'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDExLCJleHAiOjE3MjIyNjk2MTF9.P8FNBO9WBDAGlz6J4ccAZRed-x7mhAg4h8YVxOvveyuH5v84ZOBtqBmXS4vEBy9o0l53d1Rx8mqdH7IQVfWcww'
@@ -1123,7 +1123,7 @@

$ curl 'http://localhost:8080/api/products' -i -X POST \
     -H 'Content-Type: application/json' \
-    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w' \
+    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDEyLCJleHAiOjE3MjIyNjk2MTJ9.51JkouAk3os1RvXpWPbMB15kEfjb6-N4hCgmFJDyx41jc0e2-BaohTOL7LFxElg8n1NNpO3n7S4d9hg_C90V7w' \
     -d '{
   "name" : "상품01",
   "price" : 1000,
@@ -1143,7 +1143,7 @@ 

POST /api/products HTTP/1.1
 Content-Type: application/json
-Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w
+Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDEyLCJleHAiOjE3MjIyNjk2MTJ9.51JkouAk3os1RvXpWPbMB15kEfjb6-N4hCgmFJDyx41jc0e2-BaohTOL7LFxElg8n1NNpO3n7S4d9hg_C90V7w
 Content-Length: 196
 Host: localhost:8080
 
@@ -1199,7 +1199,7 @@ 

$ curl 'http://localhost:8080/api/products/1' -i -X PUT \
     -H 'Content-Type: application/json' \
-    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w' \
+    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDExLCJleHAiOjE3MjIyNjk2MTF9.P8FNBO9WBDAGlz6J4ccAZRed-x7mhAg4h8YVxOvveyuH5v84ZOBtqBmXS4vEBy9o0l53d1Rx8mqdH7IQVfWcww' \
     -d '{
   "name" : "상품01",
   "price" : 1000,
@@ -1381,7 +1381,7 @@ 

PUT /api/products/1 HTTP/1.1
 Content-Type: application/json
-Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w
+Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDExLCJleHAiOjE3MjIyNjk2MTF9.P8FNBO9WBDAGlz6J4ccAZRed-x7mhAg4h8YVxOvveyuH5v84ZOBtqBmXS4vEBy9o0l53d1Rx8mqdH7IQVfWcww
 Content-Length: 93
 Host: localhost:8080
 
@@ -1427,7 +1427,7 @@ 

+ 'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDExLCJleHAiOjE3MjIyNjk2MTF9.P8FNBO9WBDAGlz6J4ccAZRed-x7mhAg4h8YVxOvveyuH5v84ZOBtqBmXS4vEBy9o0l53d1Rx8mqdH7IQVfWcww'

@@ -1569,7 +1569,7 @@

$ curl 'http://localhost:8080/api/products/1' -i -X DELETE \
-    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w'
+ -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDExLCJleHAiOjE3MjIyNjk2MTF9.P8FNBO9WBDAGlz6J4ccAZRed-x7mhAg4h8YVxOvveyuH5v84ZOBtqBmXS4vEBy9o0l53d1Rx8mqdH7IQVfWcww'

@@ -1578,7 +1578,7 @@

DELETE /api/products/1 HTTP/1.1
-Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w
+Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDExLCJleHAiOjE3MjIyNjk2MTF9.P8FNBO9WBDAGlz6J4ccAZRed-x7mhAg4h8YVxOvveyuH5v84ZOBtqBmXS4vEBy9o0l53d1Rx8mqdH7IQVfWcww
 Host: localhost:8080
@@ -1596,7 +1596,7 @@

$ http DELETE 'http://localhost:8080/api/products/1' \
-    'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w'
+ 'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDExLCJleHAiOjE3MjIyNjk2MTF9.P8FNBO9WBDAGlz6J4ccAZRed-x7mhAg4h8YVxOvveyuH5v84ZOBtqBmXS4vEBy9o0l53d1Rx8mqdH7IQVfWcww'

@@ -1625,7 +1625,7 @@

$ curl 'http://localhost:8080/api/products/1/order' -i -X POST \
     -H 'Content-Type: application/json' \
-    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w' \
+    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDEyLCJleHAiOjE3MjIyNjk2MTJ9.51JkouAk3os1RvXpWPbMB15kEfjb6-N4hCgmFJDyx41jc0e2-BaohTOL7LFxElg8n1NNpO3n7S4d9hg_C90V7w' \
     -d '{
   "optionId" : 1,
   "quantity" : 1,
@@ -1640,7 +1640,7 @@ 

POST /api/products/1/order HTTP/1.1
 Content-Type: application/json
-Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w
+Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDEyLCJleHAiOjE3MjIyNjk2MTJ9.51JkouAk3os1RvXpWPbMB15kEfjb6-N4hCgmFJDyx41jc0e2-BaohTOL7LFxElg8n1NNpO3n7S4d9hg_C90V7w
 Content-Length: 63
 Host: localhost:8080
 
@@ -1681,7 +1681,7 @@ 

+ 'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDEyLCJleHAiOjE3MjIyNjk2MTJ9.51JkouAk3os1RvXpWPbMB15kEfjb6-N4hCgmFJDyx41jc0e2-BaohTOL7LFxElg8n1NNpO3n7S4d9hg_C90V7w'

@@ -1804,7 +1804,7 @@

$ curl 'http://localhost:8080/api/products/wish' -i -X POST \
     -H 'Content-Type: application/json' \
-    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w' \
+    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDExLCJleHAiOjE3MjIyNjk2MTF9.P8FNBO9WBDAGlz6J4ccAZRed-x7mhAg4h8YVxOvveyuH5v84ZOBtqBmXS4vEBy9o0l53d1Rx8mqdH7IQVfWcww' \
     -d '{
   "productId" : 1,
   "quantity" : 1
@@ -1818,7 +1818,7 @@ 

POST /api/products/wish HTTP/1.1
 Content-Type: application/json
-Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w
+Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDExLCJleHAiOjE3MjIyNjk2MTF9.P8FNBO9WBDAGlz6J4ccAZRed-x7mhAg4h8YVxOvveyuH5v84ZOBtqBmXS4vEBy9o0l53d1Rx8mqdH7IQVfWcww
 Content-Length: 39
 Host: localhost:8080
 
@@ -1853,7 +1853,7 @@ 

+ 'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDExLCJleHAiOjE3MjIyNjk2MTF9.P8FNBO9WBDAGlz6J4ccAZRed-x7mhAg4h8YVxOvveyuH5v84ZOBtqBmXS4vEBy9o0l53d1Rx8mqdH7IQVfWcww'

@@ -1946,7 +1946,7 @@

$ curl 'http://localhost:8080/api/products/1/options' -i -X POST \
     -H 'Content-Type: application/json' \
-    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w' \
+    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDEyLCJleHAiOjE3MjIyNjk2MTJ9.51JkouAk3os1RvXpWPbMB15kEfjb6-N4hCgmFJDyx41jc0e2-BaohTOL7LFxElg8n1NNpO3n7S4d9hg_C90V7w' \
     -d '{
   "name" : "상품 옵션 01",
   "stock" : 100
@@ -1960,7 +1960,7 @@ 

POST /api/products/1/options HTTP/1.1
 Content-Type: application/json
-Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w
+Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDEyLCJleHAiOjE3MjIyNjk2MTJ9.51JkouAk3os1RvXpWPbMB15kEfjb6-N4hCgmFJDyx41jc0e2-BaohTOL7LFxElg8n1NNpO3n7S4d9hg_C90V7w
 Content-Length: 50
 Host: localhost:8080
 
@@ -1997,7 +1997,7 @@ 

+ 'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDEyLCJleHAiOjE3MjIyNjk2MTJ9.51JkouAk3os1RvXpWPbMB15kEfjb6-N4hCgmFJDyx41jc0e2-BaohTOL7LFxElg8n1NNpO3n7S4d9hg_C90V7w'

@@ -2066,7 +2066,7 @@

$ curl 'http://localhost:8080/api/products/1/options' -i -X GET \
-    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w'
+ -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDExLCJleHAiOjE3MjIyNjk2MTF9.P8FNBO9WBDAGlz6J4ccAZRed-x7mhAg4h8YVxOvveyuH5v84ZOBtqBmXS4vEBy9o0l53d1Rx8mqdH7IQVfWcww'
@@ -2075,7 +2075,7 @@

GET /api/products/1/options HTTP/1.1
-Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w
+Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDExLCJleHAiOjE3MjIyNjk2MTF9.P8FNBO9WBDAGlz6J4ccAZRed-x7mhAg4h8YVxOvveyuH5v84ZOBtqBmXS4vEBy9o0l53d1Rx8mqdH7IQVfWcww
 Host: localhost:8080
@@ -2103,7 +2103,7 @@

$ http GET 'http://localhost:8080/api/products/1/options' \
-    'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w'
+ 'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDExLCJleHAiOjE3MjIyNjk2MTF9.P8FNBO9WBDAGlz6J4ccAZRed-x7mhAg4h8YVxOvveyuH5v84ZOBtqBmXS4vEBy9o0l53d1Rx8mqdH7IQVfWcww'
@@ -2172,7 +2172,7 @@

$ curl 'http://localhost:8080/api/products/1/options/1' -i -X PUT \
     -H 'Content-Type: application/json' \
-    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w' \
+    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDExLCJleHAiOjE3MjIyNjk2MTF9.P8FNBO9WBDAGlz6J4ccAZRed-x7mhAg4h8YVxOvveyuH5v84ZOBtqBmXS4vEBy9o0l53d1Rx8mqdH7IQVfWcww' \
     -d '{
   "name" : "상품 옵션 01",
   "stock" : 100
@@ -2186,7 +2186,7 @@ 

PUT /api/products/1/options/1 HTTP/1.1
 Content-Type: application/json
-Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w
+Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDExLCJleHAiOjE3MjIyNjk2MTF9.P8FNBO9WBDAGlz6J4ccAZRed-x7mhAg4h8YVxOvveyuH5v84ZOBtqBmXS4vEBy9o0l53d1Rx8mqdH7IQVfWcww
 Content-Length: 50
 Host: localhost:8080
 
@@ -2222,7 +2222,7 @@ 

+ 'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDExLCJleHAiOjE3MjIyNjk2MTF9.P8FNBO9WBDAGlz6J4ccAZRed-x7mhAg4h8YVxOvveyuH5v84ZOBtqBmXS4vEBy9o0l53d1Rx8mqdH7IQVfWcww'

@@ -2320,7 +2320,7 @@

$ curl 'http://localhost:8080/api/products/1/options/1' -i -X DELETE \
-    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w'
+ -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDExLCJleHAiOjE3MjIyNjk2MTF9.P8FNBO9WBDAGlz6J4ccAZRed-x7mhAg4h8YVxOvveyuH5v84ZOBtqBmXS4vEBy9o0l53d1Rx8mqdH7IQVfWcww'
@@ -2329,7 +2329,7 @@

DELETE /api/products/1/options/1 HTTP/1.1
-Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w
+Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDExLCJleHAiOjE3MjIyNjk2MTF9.P8FNBO9WBDAGlz6J4ccAZRed-x7mhAg4h8YVxOvveyuH5v84ZOBtqBmXS4vEBy9o0l53d1Rx8mqdH7IQVfWcww
 Host: localhost:8080
@@ -2347,7 +2347,7 @@

$ http DELETE 'http://localhost:8080/api/products/1/options/1' \
-    'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w'
+ 'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDExLCJleHAiOjE3MjIyNjk2MTF9.P8FNBO9WBDAGlz6J4ccAZRed-x7mhAg4h8YVxOvveyuH5v84ZOBtqBmXS4vEBy9o0l53d1Rx8mqdH7IQVfWcww'
@@ -2381,7 +2381,7 @@

$ curl 'http://localhost:8080/api/categories' -i -X POST \
     -H 'Content-Type: application/json' \
-    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTU3LCJleHAiOjE3MjIyNjg3NTd9.TJZznQ-W-PQIhpYNTuIE6Frb4r9ZAK0C5c84c8TKJCTk7fFIPupn7RH2vdijJXlsTvpwid8QzsMwLBoKKQaCaQ' \
+    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDEwLCJleHAiOjE3MjIyNjk2MTB9.URlH4KvMFjYZGvtIUJxxR7CmW2mZFc6-cYuy0Hj9C7En67-0nNt3EgOJ-9XWRBPhFUGvbhnbKCzao2At2lOncw' \
     -d '{
   "name" : "카테고리01",
   "description" : "카테고리01 설명",
@@ -2397,7 +2397,7 @@ 

POST /api/categories HTTP/1.1
 Content-Type: application/json
-Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTU3LCJleHAiOjE3MjIyNjg3NTd9.TJZznQ-W-PQIhpYNTuIE6Frb4r9ZAK0C5c84c8TKJCTk7fFIPupn7RH2vdijJXlsTvpwid8QzsMwLBoKKQaCaQ
+Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDEwLCJleHAiOjE3MjIyNjk2MTB9.URlH4KvMFjYZGvtIUJxxR7CmW2mZFc6-cYuy0Hj9C7En67-0nNt3EgOJ-9XWRBPhFUGvbhnbKCzao2At2lOncw
 Content-Length: 147
 Host: localhost:8080
 
@@ -2440,7 +2440,7 @@ 

+ 'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDEwLCJleHAiOjE3MjIyNjk2MTB9.URlH4KvMFjYZGvtIUJxxR7CmW2mZFc6-cYuy0Hj9C7En67-0nNt3EgOJ-9XWRBPhFUGvbhnbKCzao2At2lOncw'

@@ -2563,7 +2563,7 @@

$ curl 'http://localhost:8080/api/categories/1' -i -X PUT \
     -H 'Content-Type: application/json' \
-    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTU3LCJleHAiOjE3MjIyNjg3NTd9.TJZznQ-W-PQIhpYNTuIE6Frb4r9ZAK0C5c84c8TKJCTk7fFIPupn7RH2vdijJXlsTvpwid8QzsMwLBoKKQaCaQ' \
+    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDEwLCJleHAiOjE3MjIyNjk2MTB9.URlH4KvMFjYZGvtIUJxxR7CmW2mZFc6-cYuy0Hj9C7En67-0nNt3EgOJ-9XWRBPhFUGvbhnbKCzao2At2lOncw' \
     -d '{
   "name" : "카테고리01",
   "description" : "카테고리01 설명",
@@ -2579,7 +2579,7 @@ 

PUT /api/categories/1 HTTP/1.1
 Content-Type: application/json
-Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTU3LCJleHAiOjE3MjIyNjg3NTd9.TJZznQ-W-PQIhpYNTuIE6Frb4r9ZAK0C5c84c8TKJCTk7fFIPupn7RH2vdijJXlsTvpwid8QzsMwLBoKKQaCaQ
+Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDEwLCJleHAiOjE3MjIyNjk2MTB9.URlH4KvMFjYZGvtIUJxxR7CmW2mZFc6-cYuy0Hj9C7En67-0nNt3EgOJ-9XWRBPhFUGvbhnbKCzao2At2lOncw
 Content-Length: 147
 Host: localhost:8080
 
@@ -2621,7 +2621,7 @@ 

+ 'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDEwLCJleHAiOjE3MjIyNjk2MTB9.URlH4KvMFjYZGvtIUJxxR7CmW2mZFc6-cYuy0Hj9C7En67-0nNt3EgOJ-9XWRBPhFUGvbhnbKCzao2At2lOncw'

@@ -2743,7 +2743,7 @@

$ curl 'http://localhost:8080/api/categories/1' -i -X DELETE \
-    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTU3LCJleHAiOjE3MjIyNjg3NTd9.TJZznQ-W-PQIhpYNTuIE6Frb4r9ZAK0C5c84c8TKJCTk7fFIPupn7RH2vdijJXlsTvpwid8QzsMwLBoKKQaCaQ'
+ -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDEwLCJleHAiOjE3MjIyNjk2MTB9.URlH4KvMFjYZGvtIUJxxR7CmW2mZFc6-cYuy0Hj9C7En67-0nNt3EgOJ-9XWRBPhFUGvbhnbKCzao2At2lOncw'
@@ -2752,7 +2752,7 @@

DELETE /api/categories/1 HTTP/1.1
-Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTU3LCJleHAiOjE3MjIyNjg3NTd9.TJZznQ-W-PQIhpYNTuIE6Frb4r9ZAK0C5c84c8TKJCTk7fFIPupn7RH2vdijJXlsTvpwid8QzsMwLBoKKQaCaQ
+Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDEwLCJleHAiOjE3MjIyNjk2MTB9.URlH4KvMFjYZGvtIUJxxR7CmW2mZFc6-cYuy0Hj9C7En67-0nNt3EgOJ-9XWRBPhFUGvbhnbKCzao2At2lOncw
 Host: localhost:8080
@@ -2770,7 +2770,7 @@

$ http DELETE 'http://localhost:8080/api/categories/1' \
-    'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTU3LCJleHAiOjE3MjIyNjg3NTd9.TJZznQ-W-PQIhpYNTuIE6Frb4r9ZAK0C5c84c8TKJCTk7fFIPupn7RH2vdijJXlsTvpwid8QzsMwLBoKKQaCaQ'
+ 'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDEwLCJleHAiOjE3MjIyNjk2MTB9.URlH4KvMFjYZGvtIUJxxR7CmW2mZFc6-cYuy0Hj9C7En67-0nNt3EgOJ-9XWRBPhFUGvbhnbKCzao2At2lOncw'
@@ -2798,7 +2798,7 @@

$ curl 'http://localhost:8080/api/categories/1' -i -X GET \
-    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTU3LCJleHAiOjE3MjIyNjg3NTd9.TJZznQ-W-PQIhpYNTuIE6Frb4r9ZAK0C5c84c8TKJCTk7fFIPupn7RH2vdijJXlsTvpwid8QzsMwLBoKKQaCaQ'
+ -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDEwLCJleHAiOjE3MjIyNjk2MTB9.URlH4KvMFjYZGvtIUJxxR7CmW2mZFc6-cYuy0Hj9C7En67-0nNt3EgOJ-9XWRBPhFUGvbhnbKCzao2At2lOncw'
@@ -2807,7 +2807,7 @@

GET /api/categories/1 HTTP/1.1
-Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTU3LCJleHAiOjE3MjIyNjg3NTd9.TJZznQ-W-PQIhpYNTuIE6Frb4r9ZAK0C5c84c8TKJCTk7fFIPupn7RH2vdijJXlsTvpwid8QzsMwLBoKKQaCaQ
+Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDEwLCJleHAiOjE3MjIyNjk2MTB9.URlH4KvMFjYZGvtIUJxxR7CmW2mZFc6-cYuy0Hj9C7En67-0nNt3EgOJ-9XWRBPhFUGvbhnbKCzao2At2lOncw
 Host: localhost:8080
@@ -2835,7 +2835,7 @@

$ http GET 'http://localhost:8080/api/categories/1' \
-    'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTU3LCJleHAiOjE3MjIyNjg3NTd9.TJZznQ-W-PQIhpYNTuIE6Frb4r9ZAK0C5c84c8TKJCTk7fFIPupn7RH2vdijJXlsTvpwid8QzsMwLBoKKQaCaQ'
+ 'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDEwLCJleHAiOjE3MjIyNjk2MTB9.URlH4KvMFjYZGvtIUJxxR7CmW2mZFc6-cYuy0Hj9C7En67-0nNt3EgOJ-9XWRBPhFUGvbhnbKCzao2At2lOncw'
@@ -2913,7 +2913,7 @@

$ curl 'http://localhost:8080/api/categories' -i -X GET \
-    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTU3LCJleHAiOjE3MjIyNjg3NTd9.TJZznQ-W-PQIhpYNTuIE6Frb4r9ZAK0C5c84c8TKJCTk7fFIPupn7RH2vdijJXlsTvpwid8QzsMwLBoKKQaCaQ'
+ -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDEwLCJleHAiOjE3MjIyNjk2MTB9.URlH4KvMFjYZGvtIUJxxR7CmW2mZFc6-cYuy0Hj9C7En67-0nNt3EgOJ-9XWRBPhFUGvbhnbKCzao2At2lOncw'
@@ -2922,7 +2922,7 @@

GET /api/categories HTTP/1.1
-Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTU3LCJleHAiOjE3MjIyNjg3NTd9.TJZznQ-W-PQIhpYNTuIE6Frb4r9ZAK0C5c84c8TKJCTk7fFIPupn7RH2vdijJXlsTvpwid8QzsMwLBoKKQaCaQ
+Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDEwLCJleHAiOjE3MjIyNjk2MTB9.URlH4KvMFjYZGvtIUJxxR7CmW2mZFc6-cYuy0Hj9C7En67-0nNt3EgOJ-9XWRBPhFUGvbhnbKCzao2At2lOncw
 Host: localhost:8080
@@ -2958,7 +2958,7 @@

$ http GET 'http://localhost:8080/api/categories' \
-    'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTU3LCJleHAiOjE3MjIyNjg3NTd9.TJZznQ-W-PQIhpYNTuIE6Frb4r9ZAK0C5c84c8TKJCTk7fFIPupn7RH2vdijJXlsTvpwid8QzsMwLBoKKQaCaQ'
+ 'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDEwLCJleHAiOjE3MjIyNjk2MTB9.URlH4KvMFjYZGvtIUJxxR7CmW2mZFc6-cYuy0Hj9C7En67-0nNt3EgOJ-9XWRBPhFUGvbhnbKCzao2At2lOncw'
@@ -3267,7 +3267,7 @@

@@ -3329,7 +3329,7 @@

{
-  "accessToken" : "eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTU5LCJleHAiOjE3MjIyNjg3NTl9.iuNnicN0kjQYKx7fNiJBtH_2merHROfg_1P7mjBVrFmTi9l9PLkpGOerNzS9ni8vhJ-6fx0cwdbPhYXt7zqaWw"
+  "accessToken" : "eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDExLCJleHAiOjE3MjIyNjk2MTF9.P8FNBO9WBDAGlz6J4ccAZRed-x7mhAg4h8YVxOvveyuH5v84ZOBtqBmXS4vEBy9o0l53d1Rx8mqdH7IQVfWcww"
 }
@@ -3366,7 +3366,7 @@

$ curl 'http://localhost:8080/api/members/1' -i -X GET \
-    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTU5LCJleHAiOjE3MjIyNjg3NTl9.iuNnicN0kjQYKx7fNiJBtH_2merHROfg_1P7mjBVrFmTi9l9PLkpGOerNzS9ni8vhJ-6fx0cwdbPhYXt7zqaWw'
+ -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDExLCJleHAiOjE3MjIyNjk2MTF9.P8FNBO9WBDAGlz6J4ccAZRed-x7mhAg4h8YVxOvveyuH5v84ZOBtqBmXS4vEBy9o0l53d1Rx8mqdH7IQVfWcww'
@@ -3375,7 +3375,7 @@

GET /api/members/1 HTTP/1.1
-Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTU5LCJleHAiOjE3MjIyNjg3NTl9.iuNnicN0kjQYKx7fNiJBtH_2merHROfg_1P7mjBVrFmTi9l9PLkpGOerNzS9ni8vhJ-6fx0cwdbPhYXt7zqaWw
+Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDExLCJleHAiOjE3MjIyNjk2MTF9.P8FNBO9WBDAGlz6J4ccAZRed-x7mhAg4h8YVxOvveyuH5v84ZOBtqBmXS4vEBy9o0l53d1Rx8mqdH7IQVfWcww
 Host: localhost:8080
@@ -3402,7 +3402,7 @@

$ http GET 'http://localhost:8080/api/members/1' \
-    'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTU5LCJleHAiOjE3MjIyNjg3NTl9.iuNnicN0kjQYKx7fNiJBtH_2merHROfg_1P7mjBVrFmTi9l9PLkpGOerNzS9ni8vhJ-6fx0cwdbPhYXt7zqaWw'
+ 'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDExLCJleHAiOjE3MjIyNjk2MTF9.P8FNBO9WBDAGlz6J4ccAZRed-x7mhAg4h8YVxOvveyuH5v84ZOBtqBmXS4vEBy9o0l53d1Rx8mqdH7IQVfWcww'
@@ -3474,7 +3474,7 @@

$ curl 'http://localhost:8080/api/members/wishlist' -i -X GET \
-    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTU5LCJleHAiOjE3MjIyNjg3NTl9.iuNnicN0kjQYKx7fNiJBtH_2merHROfg_1P7mjBVrFmTi9l9PLkpGOerNzS9ni8vhJ-6fx0cwdbPhYXt7zqaWw'
+ -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDExLCJleHAiOjE3MjIyNjk2MTF9.P8FNBO9WBDAGlz6J4ccAZRed-x7mhAg4h8YVxOvveyuH5v84ZOBtqBmXS4vEBy9o0l53d1Rx8mqdH7IQVfWcww'
@@ -3483,7 +3483,7 @@

GET /api/members/wishlist HTTP/1.1
-Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTU5LCJleHAiOjE3MjIyNjg3NTl9.iuNnicN0kjQYKx7fNiJBtH_2merHROfg_1P7mjBVrFmTi9l9PLkpGOerNzS9ni8vhJ-6fx0cwdbPhYXt7zqaWw
+Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDExLCJleHAiOjE3MjIyNjk2MTF9.P8FNBO9WBDAGlz6J4ccAZRed-x7mhAg4h8YVxOvveyuH5v84ZOBtqBmXS4vEBy9o0l53d1Rx8mqdH7IQVfWcww
 Host: localhost:8080
@@ -3514,7 +3514,7 @@

$ http GET 'http://localhost:8080/api/members/wishlist' \
-    'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTU5LCJleHAiOjE3MjIyNjg3NTl9.iuNnicN0kjQYKx7fNiJBtH_2merHROfg_1P7mjBVrFmTi9l9PLkpGOerNzS9ni8vhJ-6fx0cwdbPhYXt7zqaWw'
+ 'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDExLCJleHAiOjE3MjIyNjk2MTF9.P8FNBO9WBDAGlz6J4ccAZRed-x7mhAg4h8YVxOvveyuH5v84ZOBtqBmXS4vEBy9o0l53d1Rx8mqdH7IQVfWcww'
@@ -3626,7 +3626,7 @@

$ curl 'http://localhost:8080/api/members/wishlist/1' -i -X PUT \
     -H 'Content-Type: application/json' \
-    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTU5LCJleHAiOjE3MjIyNjg3NTl9.iuNnicN0kjQYKx7fNiJBtH_2merHROfg_1P7mjBVrFmTi9l9PLkpGOerNzS9ni8vhJ-6fx0cwdbPhYXt7zqaWw' \
+    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDExLCJleHAiOjE3MjIyNjk2MTF9.P8FNBO9WBDAGlz6J4ccAZRed-x7mhAg4h8YVxOvveyuH5v84ZOBtqBmXS4vEBy9o0l53d1Rx8mqdH7IQVfWcww' \
     -d '{
   "quantity" : 5
 }'
@@ -3639,7 +3639,7 @@

PUT /api/members/wishlist/1 HTTP/1.1
 Content-Type: application/json
-Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTU5LCJleHAiOjE3MjIyNjg3NTl9.iuNnicN0kjQYKx7fNiJBtH_2merHROfg_1P7mjBVrFmTi9l9PLkpGOerNzS9ni8vhJ-6fx0cwdbPhYXt7zqaWw
+Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDExLCJleHAiOjE3MjIyNjk2MTF9.P8FNBO9WBDAGlz6J4ccAZRed-x7mhAg4h8YVxOvveyuH5v84ZOBtqBmXS4vEBy9o0l53d1Rx8mqdH7IQVfWcww
 Content-Length: 20
 Host: localhost:8080
 
@@ -3676,7 +3676,7 @@ 

+ 'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDExLCJleHAiOjE3MjIyNjk2MTF9.P8FNBO9WBDAGlz6J4ccAZRed-x7mhAg4h8YVxOvveyuH5v84ZOBtqBmXS4vEBy9o0l53d1Rx8mqdH7IQVfWcww' @@ -3786,7 +3786,7 @@

$ curl 'http://localhost:8080/api/members/wishlist/1' -i -X DELETE \
-    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTU5LCJleHAiOjE3MjIyNjg3NTl9.iuNnicN0kjQYKx7fNiJBtH_2merHROfg_1P7mjBVrFmTi9l9PLkpGOerNzS9ni8vhJ-6fx0cwdbPhYXt7zqaWw'
+ -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDExLCJleHAiOjE3MjIyNjk2MTF9.P8FNBO9WBDAGlz6J4ccAZRed-x7mhAg4h8YVxOvveyuH5v84ZOBtqBmXS4vEBy9o0l53d1Rx8mqdH7IQVfWcww'
@@ -3795,7 +3795,7 @@

DELETE /api/members/wishlist/1 HTTP/1.1
-Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTU5LCJleHAiOjE3MjIyNjg3NTl9.iuNnicN0kjQYKx7fNiJBtH_2merHROfg_1P7mjBVrFmTi9l9PLkpGOerNzS9ni8vhJ-6fx0cwdbPhYXt7zqaWw
+Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDExLCJleHAiOjE3MjIyNjk2MTF9.P8FNBO9WBDAGlz6J4ccAZRed-x7mhAg4h8YVxOvveyuH5v84ZOBtqBmXS4vEBy9o0l53d1Rx8mqdH7IQVfWcww
 Host: localhost:8080
@@ -3813,7 +3813,7 @@

$ http DELETE 'http://localhost:8080/api/members/wishlist/1' \
-    'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTU5LCJleHAiOjE3MjIyNjg3NTl9.iuNnicN0kjQYKx7fNiJBtH_2merHROfg_1P7mjBVrFmTi9l9PLkpGOerNzS9ni8vhJ-6fx0cwdbPhYXt7zqaWw'
+ 'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDExLCJleHAiOjE3MjIyNjk2MTF9.P8FNBO9WBDAGlz6J4ccAZRed-x7mhAg4h8YVxOvveyuH5v84ZOBtqBmXS4vEBy9o0l53d1Rx8mqdH7IQVfWcww'
diff --git a/src/main/resources/static/member.html b/src/main/resources/static/member.html index 54b2ad67b..fc385f586 100644 --- a/src/main/resources/static/member.html +++ b/src/main/resources/static/member.html @@ -639,7 +639,7 @@

HTTP response

Content-Length: 196 { - "accessToken" : "eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTU5LCJleHAiOjE3MjIyNjg3NTl9.iuNnicN0kjQYKx7fNiJBtH_2merHROfg_1P7mjBVrFmTi9l9PLkpGOerNzS9ni8vhJ-6fx0cwdbPhYXt7zqaWw" + "accessToken" : "eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDExLCJleHAiOjE3MjIyNjk2MTF9.P8FNBO9WBDAGlz6J4ccAZRed-x7mhAg4h8YVxOvveyuH5v84ZOBtqBmXS4vEBy9o0l53d1Rx8mqdH7IQVfWcww" } @@ -701,7 +701,7 @@

Response body

{
-  "accessToken" : "eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTU5LCJleHAiOjE3MjIyNjg3NTl9.iuNnicN0kjQYKx7fNiJBtH_2merHROfg_1P7mjBVrFmTi9l9PLkpGOerNzS9ni8vhJ-6fx0cwdbPhYXt7zqaWw"
+  "accessToken" : "eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDExLCJleHAiOjE3MjIyNjk2MTF9.P8FNBO9WBDAGlz6J4ccAZRed-x7mhAg4h8YVxOvveyuH5v84ZOBtqBmXS4vEBy9o0l53d1Rx8mqdH7IQVfWcww"
 }
@@ -738,7 +738,7 @@

Curl request

$ curl 'http://localhost:8080/api/members/1' -i -X GET \
-    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTU5LCJleHAiOjE3MjIyNjg3NTl9.iuNnicN0kjQYKx7fNiJBtH_2merHROfg_1P7mjBVrFmTi9l9PLkpGOerNzS9ni8vhJ-6fx0cwdbPhYXt7zqaWw'
+ -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDExLCJleHAiOjE3MjIyNjk2MTF9.P8FNBO9WBDAGlz6J4ccAZRed-x7mhAg4h8YVxOvveyuH5v84ZOBtqBmXS4vEBy9o0l53d1Rx8mqdH7IQVfWcww'
@@ -747,7 +747,7 @@

HTTP request

GET /api/members/1 HTTP/1.1
-Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTU5LCJleHAiOjE3MjIyNjg3NTl9.iuNnicN0kjQYKx7fNiJBtH_2merHROfg_1P7mjBVrFmTi9l9PLkpGOerNzS9ni8vhJ-6fx0cwdbPhYXt7zqaWw
+Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDExLCJleHAiOjE3MjIyNjk2MTF9.P8FNBO9WBDAGlz6J4ccAZRed-x7mhAg4h8YVxOvveyuH5v84ZOBtqBmXS4vEBy9o0l53d1Rx8mqdH7IQVfWcww
 Host: localhost:8080
@@ -774,7 +774,7 @@

HTTPie request

$ http GET 'http://localhost:8080/api/members/1' \
-    'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTU5LCJleHAiOjE3MjIyNjg3NTl9.iuNnicN0kjQYKx7fNiJBtH_2merHROfg_1P7mjBVrFmTi9l9PLkpGOerNzS9ni8vhJ-6fx0cwdbPhYXt7zqaWw'
+ 'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDExLCJleHAiOjE3MjIyNjk2MTF9.P8FNBO9WBDAGlz6J4ccAZRed-x7mhAg4h8YVxOvveyuH5v84ZOBtqBmXS4vEBy9o0l53d1Rx8mqdH7IQVfWcww'
@@ -846,7 +846,7 @@

Curl request

$ curl 'http://localhost:8080/api/members/wishlist' -i -X GET \
-    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTU5LCJleHAiOjE3MjIyNjg3NTl9.iuNnicN0kjQYKx7fNiJBtH_2merHROfg_1P7mjBVrFmTi9l9PLkpGOerNzS9ni8vhJ-6fx0cwdbPhYXt7zqaWw'
+ -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDExLCJleHAiOjE3MjIyNjk2MTF9.P8FNBO9WBDAGlz6J4ccAZRed-x7mhAg4h8YVxOvveyuH5v84ZOBtqBmXS4vEBy9o0l53d1Rx8mqdH7IQVfWcww'
@@ -855,7 +855,7 @@

HTTP request

GET /api/members/wishlist HTTP/1.1
-Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTU5LCJleHAiOjE3MjIyNjg3NTl9.iuNnicN0kjQYKx7fNiJBtH_2merHROfg_1P7mjBVrFmTi9l9PLkpGOerNzS9ni8vhJ-6fx0cwdbPhYXt7zqaWw
+Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDExLCJleHAiOjE3MjIyNjk2MTF9.P8FNBO9WBDAGlz6J4ccAZRed-x7mhAg4h8YVxOvveyuH5v84ZOBtqBmXS4vEBy9o0l53d1Rx8mqdH7IQVfWcww
 Host: localhost:8080
@@ -886,7 +886,7 @@

HTTPie request

$ http GET 'http://localhost:8080/api/members/wishlist' \
-    'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTU5LCJleHAiOjE3MjIyNjg3NTl9.iuNnicN0kjQYKx7fNiJBtH_2merHROfg_1P7mjBVrFmTi9l9PLkpGOerNzS9ni8vhJ-6fx0cwdbPhYXt7zqaWw'
+ 'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDExLCJleHAiOjE3MjIyNjk2MTF9.P8FNBO9WBDAGlz6J4ccAZRed-x7mhAg4h8YVxOvveyuH5v84ZOBtqBmXS4vEBy9o0l53d1Rx8mqdH7IQVfWcww'
@@ -998,7 +998,7 @@

Curl request

$ curl 'http://localhost:8080/api/members/wishlist/1' -i -X PUT \
     -H 'Content-Type: application/json' \
-    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTU5LCJleHAiOjE3MjIyNjg3NTl9.iuNnicN0kjQYKx7fNiJBtH_2merHROfg_1P7mjBVrFmTi9l9PLkpGOerNzS9ni8vhJ-6fx0cwdbPhYXt7zqaWw' \
+    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDExLCJleHAiOjE3MjIyNjk2MTF9.P8FNBO9WBDAGlz6J4ccAZRed-x7mhAg4h8YVxOvveyuH5v84ZOBtqBmXS4vEBy9o0l53d1Rx8mqdH7IQVfWcww' \
     -d '{
   "quantity" : 5
 }'
@@ -1011,7 +1011,7 @@

HTTP request

PUT /api/members/wishlist/1 HTTP/1.1
 Content-Type: application/json
-Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTU5LCJleHAiOjE3MjIyNjg3NTl9.iuNnicN0kjQYKx7fNiJBtH_2merHROfg_1P7mjBVrFmTi9l9PLkpGOerNzS9ni8vhJ-6fx0cwdbPhYXt7zqaWw
+Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDExLCJleHAiOjE3MjIyNjk2MTF9.P8FNBO9WBDAGlz6J4ccAZRed-x7mhAg4h8YVxOvveyuH5v84ZOBtqBmXS4vEBy9o0l53d1Rx8mqdH7IQVfWcww
 Content-Length: 20
 Host: localhost:8080
 
@@ -1048,7 +1048,7 @@ 

HTTPie request

"quantity" : 5 }' | http PUT 'http://localhost:8080/api/members/wishlist/1' \ 'Content-Type:application/json' \ - 'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTU5LCJleHAiOjE3MjIyNjg3NTl9.iuNnicN0kjQYKx7fNiJBtH_2merHROfg_1P7mjBVrFmTi9l9PLkpGOerNzS9ni8vhJ-6fx0cwdbPhYXt7zqaWw'
+ 'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDExLCJleHAiOjE3MjIyNjk2MTF9.P8FNBO9WBDAGlz6J4ccAZRed-x7mhAg4h8YVxOvveyuH5v84ZOBtqBmXS4vEBy9o0l53d1Rx8mqdH7IQVfWcww'
@@ -1158,7 +1158,7 @@

Curl request

$ curl 'http://localhost:8080/api/members/wishlist/1' -i -X DELETE \
-    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTU5LCJleHAiOjE3MjIyNjg3NTl9.iuNnicN0kjQYKx7fNiJBtH_2merHROfg_1P7mjBVrFmTi9l9PLkpGOerNzS9ni8vhJ-6fx0cwdbPhYXt7zqaWw'
+ -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDExLCJleHAiOjE3MjIyNjk2MTF9.P8FNBO9WBDAGlz6J4ccAZRed-x7mhAg4h8YVxOvveyuH5v84ZOBtqBmXS4vEBy9o0l53d1Rx8mqdH7IQVfWcww'
@@ -1167,7 +1167,7 @@

HTTP request

DELETE /api/members/wishlist/1 HTTP/1.1
-Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTU5LCJleHAiOjE3MjIyNjg3NTl9.iuNnicN0kjQYKx7fNiJBtH_2merHROfg_1P7mjBVrFmTi9l9PLkpGOerNzS9ni8vhJ-6fx0cwdbPhYXt7zqaWw
+Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDExLCJleHAiOjE3MjIyNjk2MTF9.P8FNBO9WBDAGlz6J4ccAZRed-x7mhAg4h8YVxOvveyuH5v84ZOBtqBmXS4vEBy9o0l53d1Rx8mqdH7IQVfWcww
 Host: localhost:8080
@@ -1185,7 +1185,7 @@

HTTPie request

$ http DELETE 'http://localhost:8080/api/members/wishlist/1' \
-    'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTU5LCJleHAiOjE3MjIyNjg3NTl9.iuNnicN0kjQYKx7fNiJBtH_2merHROfg_1P7mjBVrFmTi9l9PLkpGOerNzS9ni8vhJ-6fx0cwdbPhYXt7zqaWw'
+ 'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDExLCJleHAiOjE3MjIyNjk2MTF9.P8FNBO9WBDAGlz6J4ccAZRed-x7mhAg4h8YVxOvveyuH5v84ZOBtqBmXS4vEBy9o0l53d1Rx8mqdH7IQVfWcww'
diff --git a/src/main/resources/static/product.html b/src/main/resources/static/product.html index 98285e773..a4e58ab2a 100644 --- a/src/main/resources/static/product.html +++ b/src/main/resources/static/product.html @@ -449,8 +449,8 @@

전체 상품 조회

Curl request

-
$ curl 'http://localhost:8080/api/products' -i -X GET \
-    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w'
+
$ curl 'http://localhost:8080/api/products?page=0&size=10' -i -X GET \
+    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDExLCJleHAiOjE3MjIyNjk2MTF9.P8FNBO9WBDAGlz6J4ccAZRed-x7mhAg4h8YVxOvveyuH5v84ZOBtqBmXS4vEBy9o0l53d1Rx8mqdH7IQVfWcww'
@@ -458,8 +458,8 @@

Curl request

HTTP request

-
GET /api/products HTTP/1.1
-Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w
+
GET /api/products?page=0&size=10 HTTP/1.1
+Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDExLCJleHAiOjE3MjIyNjk2MTF9.P8FNBO9WBDAGlz6J4ccAZRed-x7mhAg4h8YVxOvveyuH5v84ZOBtqBmXS4vEBy9o0l53d1Rx8mqdH7IQVfWcww
 Host: localhost:8080
@@ -499,8 +499,8 @@

HTTP response

HTTPie request

-
$ http GET 'http://localhost:8080/api/products' \
-    'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w'
+
$ http GET 'http://localhost:8080/api/products?page=0&size=10' \
+    'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDExLCJleHAiOjE3MjIyNjk2MTF9.P8FNBO9WBDAGlz6J4ccAZRed-x7mhAg4h8YVxOvveyuH5v84ZOBtqBmXS4vEBy9o0l53d1Rx8mqdH7IQVfWcww'
@@ -656,7 +656,7 @@

Curl request

$ curl 'http://localhost:8080/api/products/1' -i -X GET \
-    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w'
+ -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDExLCJleHAiOjE3MjIyNjk2MTF9.P8FNBO9WBDAGlz6J4ccAZRed-x7mhAg4h8YVxOvveyuH5v84ZOBtqBmXS4vEBy9o0l53d1Rx8mqdH7IQVfWcww'
@@ -665,7 +665,7 @@

HTTP request

GET /api/products/1 HTTP/1.1
-Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w
+Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDExLCJleHAiOjE3MjIyNjk2MTF9.P8FNBO9WBDAGlz6J4ccAZRed-x7mhAg4h8YVxOvveyuH5v84ZOBtqBmXS4vEBy9o0l53d1Rx8mqdH7IQVfWcww
 Host: localhost:8080
@@ -704,7 +704,7 @@

HTTPie request

$ http GET 'http://localhost:8080/api/products/1' \
-    'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w'
+ 'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDExLCJleHAiOjE3MjIyNjk2MTF9.P8FNBO9WBDAGlz6J4ccAZRed-x7mhAg4h8YVxOvveyuH5v84ZOBtqBmXS4vEBy9o0l53d1Rx8mqdH7IQVfWcww'
@@ -834,7 +834,7 @@

Curl request

$ curl 'http://localhost:8080/api/products' -i -X POST \
     -H 'Content-Type: application/json' \
-    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w' \
+    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDEyLCJleHAiOjE3MjIyNjk2MTJ9.51JkouAk3os1RvXpWPbMB15kEfjb6-N4hCgmFJDyx41jc0e2-BaohTOL7LFxElg8n1NNpO3n7S4d9hg_C90V7w' \
     -d '{
   "name" : "상품01",
   "price" : 1000,
@@ -854,7 +854,7 @@ 

HTTP request

POST /api/products HTTP/1.1
 Content-Type: application/json
-Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w
+Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDEyLCJleHAiOjE3MjIyNjk2MTJ9.51JkouAk3os1RvXpWPbMB15kEfjb6-N4hCgmFJDyx41jc0e2-BaohTOL7LFxElg8n1NNpO3n7S4d9hg_C90V7w
 Content-Length: 196
 Host: localhost:8080
 
@@ -910,7 +910,7 @@ 

HTTPie request

} ] }' | http POST 'http://localhost:8080/api/products' \ 'Content-Type:application/json' \ - 'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w'
+ 'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDEyLCJleHAiOjE3MjIyNjk2MTJ9.51JkouAk3os1RvXpWPbMB15kEfjb6-N4hCgmFJDyx41jc0e2-BaohTOL7LFxElg8n1NNpO3n7S4d9hg_C90V7w'
@@ -1077,7 +1077,7 @@

Curl request

$ curl 'http://localhost:8080/api/products/1' -i -X PUT \
     -H 'Content-Type: application/json' \
-    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w' \
+    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDExLCJleHAiOjE3MjIyNjk2MTF9.P8FNBO9WBDAGlz6J4ccAZRed-x7mhAg4h8YVxOvveyuH5v84ZOBtqBmXS4vEBy9o0l53d1Rx8mqdH7IQVfWcww' \
     -d '{
   "name" : "상품01",
   "price" : 1000,
@@ -1092,7 +1092,7 @@ 

HTTP request

PUT /api/products/1 HTTP/1.1
 Content-Type: application/json
-Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w
+Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDExLCJleHAiOjE3MjIyNjk2MTF9.P8FNBO9WBDAGlz6J4ccAZRed-x7mhAg4h8YVxOvveyuH5v84ZOBtqBmXS4vEBy9o0l53d1Rx8mqdH7IQVfWcww
 Content-Length: 93
 Host: localhost:8080
 
@@ -1138,7 +1138,7 @@ 

HTTPie request

"imageUrl" : "https://via.placeholder.com/150" }' | http PUT 'http://localhost:8080/api/products/1' \ 'Content-Type:application/json' \ - 'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w'
+ 'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDExLCJleHAiOjE3MjIyNjk2MTF9.P8FNBO9WBDAGlz6J4ccAZRed-x7mhAg4h8YVxOvveyuH5v84ZOBtqBmXS4vEBy9o0l53d1Rx8mqdH7IQVfWcww'
@@ -1280,7 +1280,7 @@

Curl request

$ curl 'http://localhost:8080/api/products/1' -i -X DELETE \
-    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w'
+ -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDExLCJleHAiOjE3MjIyNjk2MTF9.P8FNBO9WBDAGlz6J4ccAZRed-x7mhAg4h8YVxOvveyuH5v84ZOBtqBmXS4vEBy9o0l53d1Rx8mqdH7IQVfWcww'
@@ -1289,7 +1289,7 @@

HTTP request

DELETE /api/products/1 HTTP/1.1
-Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w
+Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDExLCJleHAiOjE3MjIyNjk2MTF9.P8FNBO9WBDAGlz6J4ccAZRed-x7mhAg4h8YVxOvveyuH5v84ZOBtqBmXS4vEBy9o0l53d1Rx8mqdH7IQVfWcww
 Host: localhost:8080
@@ -1307,7 +1307,7 @@

HTTPie request

$ http DELETE 'http://localhost:8080/api/products/1' \
-    'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w'
+ 'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDExLCJleHAiOjE3MjIyNjk2MTF9.P8FNBO9WBDAGlz6J4ccAZRed-x7mhAg4h8YVxOvveyuH5v84ZOBtqBmXS4vEBy9o0l53d1Rx8mqdH7IQVfWcww'
@@ -1336,7 +1336,7 @@

Curl request

$ curl 'http://localhost:8080/api/products/1/order' -i -X POST \
     -H 'Content-Type: application/json' \
-    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w' \
+    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDEyLCJleHAiOjE3MjIyNjk2MTJ9.51JkouAk3os1RvXpWPbMB15kEfjb6-N4hCgmFJDyx41jc0e2-BaohTOL7LFxElg8n1NNpO3n7S4d9hg_C90V7w' \
     -d '{
   "optionId" : 1,
   "quantity" : 1,
@@ -1351,7 +1351,7 @@ 

HTTP request

POST /api/products/1/order HTTP/1.1
 Content-Type: application/json
-Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w
+Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDEyLCJleHAiOjE3MjIyNjk2MTJ9.51JkouAk3os1RvXpWPbMB15kEfjb6-N4hCgmFJDyx41jc0e2-BaohTOL7LFxElg8n1NNpO3n7S4d9hg_C90V7w
 Content-Length: 63
 Host: localhost:8080
 
@@ -1392,7 +1392,7 @@ 

HTTPie request

"message" : "message" }' | http POST 'http://localhost:8080/api/products/1/order' \ 'Content-Type:application/json' \ - 'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w'
+ 'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDEyLCJleHAiOjE3MjIyNjk2MTJ9.51JkouAk3os1RvXpWPbMB15kEfjb6-N4hCgmFJDyx41jc0e2-BaohTOL7LFxElg8n1NNpO3n7S4d9hg_C90V7w'
@@ -1515,7 +1515,7 @@

Curl request

$ curl 'http://localhost:8080/api/products/wish' -i -X POST \
     -H 'Content-Type: application/json' \
-    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w' \
+    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDExLCJleHAiOjE3MjIyNjk2MTF9.P8FNBO9WBDAGlz6J4ccAZRed-x7mhAg4h8YVxOvveyuH5v84ZOBtqBmXS4vEBy9o0l53d1Rx8mqdH7IQVfWcww' \
     -d '{
   "productId" : 1,
   "quantity" : 1
@@ -1529,7 +1529,7 @@ 

HTTP request

POST /api/products/wish HTTP/1.1
 Content-Type: application/json
-Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w
+Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDExLCJleHAiOjE3MjIyNjk2MTF9.P8FNBO9WBDAGlz6J4ccAZRed-x7mhAg4h8YVxOvveyuH5v84ZOBtqBmXS4vEBy9o0l53d1Rx8mqdH7IQVfWcww
 Content-Length: 39
 Host: localhost:8080
 
@@ -1564,7 +1564,7 @@ 

HTTPie request

"quantity" : 1 }' | http POST 'http://localhost:8080/api/products/wish' \ 'Content-Type:application/json' \ - 'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w'
+ 'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDExLCJleHAiOjE3MjIyNjk2MTF9.P8FNBO9WBDAGlz6J4ccAZRed-x7mhAg4h8YVxOvveyuH5v84ZOBtqBmXS4vEBy9o0l53d1Rx8mqdH7IQVfWcww'
@@ -1657,7 +1657,7 @@

Curl request

$ curl 'http://localhost:8080/api/products/1/options' -i -X POST \
     -H 'Content-Type: application/json' \
-    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w' \
+    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDEyLCJleHAiOjE3MjIyNjk2MTJ9.51JkouAk3os1RvXpWPbMB15kEfjb6-N4hCgmFJDyx41jc0e2-BaohTOL7LFxElg8n1NNpO3n7S4d9hg_C90V7w' \
     -d '{
   "name" : "상품 옵션 01",
   "stock" : 100
@@ -1671,7 +1671,7 @@ 

HTTP request

POST /api/products/1/options HTTP/1.1
 Content-Type: application/json
-Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w
+Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDEyLCJleHAiOjE3MjIyNjk2MTJ9.51JkouAk3os1RvXpWPbMB15kEfjb6-N4hCgmFJDyx41jc0e2-BaohTOL7LFxElg8n1NNpO3n7S4d9hg_C90V7w
 Content-Length: 50
 Host: localhost:8080
 
@@ -1708,7 +1708,7 @@ 

HTTPie request

"stock" : 100 }' | http POST 'http://localhost:8080/api/products/1/options' \ 'Content-Type:application/json' \ - 'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w'
+ 'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDEyLCJleHAiOjE3MjIyNjk2MTJ9.51JkouAk3os1RvXpWPbMB15kEfjb6-N4hCgmFJDyx41jc0e2-BaohTOL7LFxElg8n1NNpO3n7S4d9hg_C90V7w'
@@ -1777,7 +1777,7 @@

Curl request

$ curl 'http://localhost:8080/api/products/1/options' -i -X GET \
-    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w'
+ -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDExLCJleHAiOjE3MjIyNjk2MTF9.P8FNBO9WBDAGlz6J4ccAZRed-x7mhAg4h8YVxOvveyuH5v84ZOBtqBmXS4vEBy9o0l53d1Rx8mqdH7IQVfWcww'
@@ -1786,7 +1786,7 @@

HTTP request

GET /api/products/1/options HTTP/1.1
-Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w
+Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDExLCJleHAiOjE3MjIyNjk2MTF9.P8FNBO9WBDAGlz6J4ccAZRed-x7mhAg4h8YVxOvveyuH5v84ZOBtqBmXS4vEBy9o0l53d1Rx8mqdH7IQVfWcww
 Host: localhost:8080
@@ -1814,7 +1814,7 @@

HTTPie request

$ http GET 'http://localhost:8080/api/products/1/options' \
-    'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w'
+ 'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDExLCJleHAiOjE3MjIyNjk2MTF9.P8FNBO9WBDAGlz6J4ccAZRed-x7mhAg4h8YVxOvveyuH5v84ZOBtqBmXS4vEBy9o0l53d1Rx8mqdH7IQVfWcww'
@@ -1883,7 +1883,7 @@

Curl request

$ curl 'http://localhost:8080/api/products/1/options/1' -i -X PUT \
     -H 'Content-Type: application/json' \
-    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w' \
+    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDExLCJleHAiOjE3MjIyNjk2MTF9.P8FNBO9WBDAGlz6J4ccAZRed-x7mhAg4h8YVxOvveyuH5v84ZOBtqBmXS4vEBy9o0l53d1Rx8mqdH7IQVfWcww' \
     -d '{
   "name" : "상품 옵션 01",
   "stock" : 100
@@ -1897,7 +1897,7 @@ 

HTTP request

PUT /api/products/1/options/1 HTTP/1.1
 Content-Type: application/json
-Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w
+Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDExLCJleHAiOjE3MjIyNjk2MTF9.P8FNBO9WBDAGlz6J4ccAZRed-x7mhAg4h8YVxOvveyuH5v84ZOBtqBmXS4vEBy9o0l53d1Rx8mqdH7IQVfWcww
 Content-Length: 50
 Host: localhost:8080
 
@@ -1933,7 +1933,7 @@ 

HTTPie request

"stock" : 100 }' | http PUT 'http://localhost:8080/api/products/1/options/1' \ 'Content-Type:application/json' \ - 'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w'
+ 'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDExLCJleHAiOjE3MjIyNjk2MTF9.P8FNBO9WBDAGlz6J4ccAZRed-x7mhAg4h8YVxOvveyuH5v84ZOBtqBmXS4vEBy9o0l53d1Rx8mqdH7IQVfWcww'
@@ -2031,7 +2031,7 @@

Curl request

$ curl 'http://localhost:8080/api/products/1/options/1' -i -X DELETE \
-    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w'
+ -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDExLCJleHAiOjE3MjIyNjk2MTF9.P8FNBO9WBDAGlz6J4ccAZRed-x7mhAg4h8YVxOvveyuH5v84ZOBtqBmXS4vEBy9o0l53d1Rx8mqdH7IQVfWcww'
@@ -2040,7 +2040,7 @@

HTTP request

DELETE /api/products/1/options/1 HTTP/1.1
-Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w
+Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDExLCJleHAiOjE3MjIyNjk2MTF9.P8FNBO9WBDAGlz6J4ccAZRed-x7mhAg4h8YVxOvveyuH5v84ZOBtqBmXS4vEBy9o0l53d1Rx8mqdH7IQVfWcww
 Host: localhost:8080
@@ -2058,7 +2058,7 @@

HTTPie request

$ http DELETE 'http://localhost:8080/api/products/1/options/1' \
-    'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY1MTYwLCJleHAiOjE3MjIyNjg3NjB9.rykPsQfe6CBEFD8w-aWasgLANkgUiudC5CGN5WoOU6KFbbJJQ22-kzPMMflak1o_yUWagKOkDRC_SGO_u2469w'
+ 'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDExLCJleHAiOjE3MjIyNjk2MTF9.P8FNBO9WBDAGlz6J4ccAZRed-x7mhAg4h8YVxOvveyuH5v84ZOBtqBmXS4vEBy9o0l53d1Rx8mqdH7IQVfWcww'
diff --git a/src/test/java/gift/web/controller/api/ProductApiControllerTest.java b/src/test/java/gift/web/controller/api/ProductApiControllerTest.java index 62f6145f1..2dcacd46f 100644 --- a/src/test/java/gift/web/controller/api/ProductApiControllerTest.java +++ b/src/test/java/gift/web/controller/api/ProductApiControllerTest.java @@ -128,6 +128,8 @@ void readAllProducts() throws Exception { .perform( get(BASE_URL) .header(HttpHeaders.AUTHORIZATION, "Bearer " + accessToken) + .param("page", "0") + .param("size", "10") ) .andExpect(status().isOk()) .andDo( From b94600b2dba1c228bd2d994d977007da5cfc7aaf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=82=98=EC=A0=9C=EB=B2=95?= Date: Wed, 31 Jul 2024 00:59:00 +0900 Subject: [PATCH 137/138] =?UTF-8?q?test:=20ProductApiControllerTest.java?= =?UTF-8?q?=20=EC=9C=A0=EC=A7=80=EB=B3=B4=EC=88=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../web/controller/api/ProductApiControllerTest.java | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/test/java/gift/web/controller/api/ProductApiControllerTest.java b/src/test/java/gift/web/controller/api/ProductApiControllerTest.java index 2dcacd46f..24d5347a3 100644 --- a/src/test/java/gift/web/controller/api/ProductApiControllerTest.java +++ b/src/test/java/gift/web/controller/api/ProductApiControllerTest.java @@ -447,8 +447,8 @@ void orderProduct() throws Exception { CreateOrderRequest request = new CreateOrderRequest(1L, 1, "message"); String content = objectMapper.writeValueAsString(request); - given(orderService.createOrder(any(String.class), any(Long.class), any())) - .willReturn(new OrderResponse(1L, 1L, 10, 1, "상품01", "message")); + given(orderService.createOrder(any(String.class), any(Long.class), any(Long.class), any())) + .willReturn(new OrderResponse(1L, 1L, 10, "message")); mockMvc .perform( @@ -462,15 +462,13 @@ void orderProduct() throws Exception { restDocs.document( requestFields( fieldWithPath("optionId").type(JsonFieldType.NUMBER).description("상품 옵션 ID"), - fieldWithPath("quantity").type(JsonFieldType.NUMBER).description("수량"), + fieldWithPath("quantity").type(JsonFieldType.NUMBER).description("주문 수량"), fieldWithPath("message").type(JsonFieldType.STRING).description("메시지") ), responseFields( fieldWithPath("productId").type(JsonFieldType.NUMBER).description("상품 ID"), fieldWithPath("optionId").type(JsonFieldType.NUMBER).description("상품 옵션 ID"), - fieldWithPath("optionStock").type(JsonFieldType.NUMBER).description("상품 옵션 재고"), - fieldWithPath("quantity").type(JsonFieldType.NUMBER).description("수량"), - fieldWithPath("productName").type(JsonFieldType.STRING).description("상품명"), + fieldWithPath("quantity").type(JsonFieldType.NUMBER).description("주문 수량"), fieldWithPath("message").type(JsonFieldType.STRING).description("메시지") ) ) From fc7af74d6dbff95164d72f3bbb80ddfe7ee07cd9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=82=98=EC=A0=9C=EB=B2=95?= Date: Wed, 31 Jul 2024 00:59:15 +0900 Subject: [PATCH 138/138] =?UTF-8?q?docs:=20API=20=EB=AC=B8=EC=84=9C=20?= =?UTF-8?q?=EC=B5=9C=EC=8B=A0=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/resources/static/category.html | 32 ++--- src/main/resources/static/docs.html | 150 +++++++++++------------- src/main/resources/static/login.html | 2 +- src/main/resources/static/member.html | 30 ++--- src/main/resources/static/product.html | 92 ++++++--------- 5 files changed, 139 insertions(+), 167 deletions(-) diff --git a/src/main/resources/static/category.html b/src/main/resources/static/category.html index 6de14f60d..e4686ff75 100644 --- a/src/main/resources/static/category.html +++ b/src/main/resources/static/category.html @@ -451,7 +451,7 @@

Curl request

$ curl 'http://localhost:8080/api/categories' -i -X POST \
     -H 'Content-Type: application/json' \
-    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDEwLCJleHAiOjE3MjIyNjk2MTB9.URlH4KvMFjYZGvtIUJxxR7CmW2mZFc6-cYuy0Hj9C7En67-0nNt3EgOJ-9XWRBPhFUGvbhnbKCzao2At2lOncw' \
+    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMzU1MTAyLCJleHAiOjE3MjIzNTg3MDJ9.Ui6qD8vxUswbaz_RdFW-pU7XmGPFxaXS0DU7FBlBJwB_-yuL9WVCc7bq5yOezJltGK77L6tu3653lFLyt6zZsA' \
     -d '{
   "name" : "카테고리01",
   "description" : "카테고리01 설명",
@@ -467,7 +467,7 @@ 

HTTP request

POST /api/categories HTTP/1.1
 Content-Type: application/json
-Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDEwLCJleHAiOjE3MjIyNjk2MTB9.URlH4KvMFjYZGvtIUJxxR7CmW2mZFc6-cYuy0Hj9C7En67-0nNt3EgOJ-9XWRBPhFUGvbhnbKCzao2At2lOncw
+Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMzU1MTAyLCJleHAiOjE3MjIzNTg3MDJ9.Ui6qD8vxUswbaz_RdFW-pU7XmGPFxaXS0DU7FBlBJwB_-yuL9WVCc7bq5yOezJltGK77L6tu3653lFLyt6zZsA
 Content-Length: 147
 Host: localhost:8080
 
@@ -510,7 +510,7 @@ 

HTTPie request

"color" : "#FFFFFF" }' | http POST 'http://localhost:8080/api/categories' \ 'Content-Type:application/json' \ - 'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDEwLCJleHAiOjE3MjIyNjk2MTB9.URlH4KvMFjYZGvtIUJxxR7CmW2mZFc6-cYuy0Hj9C7En67-0nNt3EgOJ-9XWRBPhFUGvbhnbKCzao2At2lOncw'
+ 'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMzU1MTAyLCJleHAiOjE3MjIzNTg3MDJ9.Ui6qD8vxUswbaz_RdFW-pU7XmGPFxaXS0DU7FBlBJwB_-yuL9WVCc7bq5yOezJltGK77L6tu3653lFLyt6zZsA'
@@ -633,7 +633,7 @@

Curl request

$ curl 'http://localhost:8080/api/categories/1' -i -X PUT \
     -H 'Content-Type: application/json' \
-    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDEwLCJleHAiOjE3MjIyNjk2MTB9.URlH4KvMFjYZGvtIUJxxR7CmW2mZFc6-cYuy0Hj9C7En67-0nNt3EgOJ-9XWRBPhFUGvbhnbKCzao2At2lOncw' \
+    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMzU1MTAyLCJleHAiOjE3MjIzNTg3MDJ9.Ui6qD8vxUswbaz_RdFW-pU7XmGPFxaXS0DU7FBlBJwB_-yuL9WVCc7bq5yOezJltGK77L6tu3653lFLyt6zZsA' \
     -d '{
   "name" : "카테고리01",
   "description" : "카테고리01 설명",
@@ -649,7 +649,7 @@ 

HTTP request

PUT /api/categories/1 HTTP/1.1
 Content-Type: application/json
-Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDEwLCJleHAiOjE3MjIyNjk2MTB9.URlH4KvMFjYZGvtIUJxxR7CmW2mZFc6-cYuy0Hj9C7En67-0nNt3EgOJ-9XWRBPhFUGvbhnbKCzao2At2lOncw
+Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMzU1MTAyLCJleHAiOjE3MjIzNTg3MDJ9.Ui6qD8vxUswbaz_RdFW-pU7XmGPFxaXS0DU7FBlBJwB_-yuL9WVCc7bq5yOezJltGK77L6tu3653lFLyt6zZsA
 Content-Length: 147
 Host: localhost:8080
 
@@ -691,7 +691,7 @@ 

HTTPie request

"color" : "#FFFFFF" }' | http PUT 'http://localhost:8080/api/categories/1' \ 'Content-Type:application/json' \ - 'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDEwLCJleHAiOjE3MjIyNjk2MTB9.URlH4KvMFjYZGvtIUJxxR7CmW2mZFc6-cYuy0Hj9C7En67-0nNt3EgOJ-9XWRBPhFUGvbhnbKCzao2At2lOncw'
+ 'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMzU1MTAyLCJleHAiOjE3MjIzNTg3MDJ9.Ui6qD8vxUswbaz_RdFW-pU7XmGPFxaXS0DU7FBlBJwB_-yuL9WVCc7bq5yOezJltGK77L6tu3653lFLyt6zZsA'
@@ -813,7 +813,7 @@

Curl request

$ curl 'http://localhost:8080/api/categories/1' -i -X DELETE \
-    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDEwLCJleHAiOjE3MjIyNjk2MTB9.URlH4KvMFjYZGvtIUJxxR7CmW2mZFc6-cYuy0Hj9C7En67-0nNt3EgOJ-9XWRBPhFUGvbhnbKCzao2At2lOncw'
+ -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMzU1MTAyLCJleHAiOjE3MjIzNTg3MDJ9.Ui6qD8vxUswbaz_RdFW-pU7XmGPFxaXS0DU7FBlBJwB_-yuL9WVCc7bq5yOezJltGK77L6tu3653lFLyt6zZsA'
@@ -822,7 +822,7 @@

HTTP request

DELETE /api/categories/1 HTTP/1.1
-Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDEwLCJleHAiOjE3MjIyNjk2MTB9.URlH4KvMFjYZGvtIUJxxR7CmW2mZFc6-cYuy0Hj9C7En67-0nNt3EgOJ-9XWRBPhFUGvbhnbKCzao2At2lOncw
+Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMzU1MTAyLCJleHAiOjE3MjIzNTg3MDJ9.Ui6qD8vxUswbaz_RdFW-pU7XmGPFxaXS0DU7FBlBJwB_-yuL9WVCc7bq5yOezJltGK77L6tu3653lFLyt6zZsA
 Host: localhost:8080
@@ -840,7 +840,7 @@

HTTPie request

$ http DELETE 'http://localhost:8080/api/categories/1' \
-    'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDEwLCJleHAiOjE3MjIyNjk2MTB9.URlH4KvMFjYZGvtIUJxxR7CmW2mZFc6-cYuy0Hj9C7En67-0nNt3EgOJ-9XWRBPhFUGvbhnbKCzao2At2lOncw'
+ 'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMzU1MTAyLCJleHAiOjE3MjIzNTg3MDJ9.Ui6qD8vxUswbaz_RdFW-pU7XmGPFxaXS0DU7FBlBJwB_-yuL9WVCc7bq5yOezJltGK77L6tu3653lFLyt6zZsA'
@@ -868,7 +868,7 @@

Curl request

$ curl 'http://localhost:8080/api/categories/1' -i -X GET \
-    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDEwLCJleHAiOjE3MjIyNjk2MTB9.URlH4KvMFjYZGvtIUJxxR7CmW2mZFc6-cYuy0Hj9C7En67-0nNt3EgOJ-9XWRBPhFUGvbhnbKCzao2At2lOncw'
+ -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMzU1MTAyLCJleHAiOjE3MjIzNTg3MDJ9.Ui6qD8vxUswbaz_RdFW-pU7XmGPFxaXS0DU7FBlBJwB_-yuL9WVCc7bq5yOezJltGK77L6tu3653lFLyt6zZsA'
@@ -877,7 +877,7 @@

HTTP request

GET /api/categories/1 HTTP/1.1
-Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDEwLCJleHAiOjE3MjIyNjk2MTB9.URlH4KvMFjYZGvtIUJxxR7CmW2mZFc6-cYuy0Hj9C7En67-0nNt3EgOJ-9XWRBPhFUGvbhnbKCzao2At2lOncw
+Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMzU1MTAyLCJleHAiOjE3MjIzNTg3MDJ9.Ui6qD8vxUswbaz_RdFW-pU7XmGPFxaXS0DU7FBlBJwB_-yuL9WVCc7bq5yOezJltGK77L6tu3653lFLyt6zZsA
 Host: localhost:8080
@@ -905,7 +905,7 @@

HTTPie request

$ http GET 'http://localhost:8080/api/categories/1' \
-    'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDEwLCJleHAiOjE3MjIyNjk2MTB9.URlH4KvMFjYZGvtIUJxxR7CmW2mZFc6-cYuy0Hj9C7En67-0nNt3EgOJ-9XWRBPhFUGvbhnbKCzao2At2lOncw'
+ 'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMzU1MTAyLCJleHAiOjE3MjIzNTg3MDJ9.Ui6qD8vxUswbaz_RdFW-pU7XmGPFxaXS0DU7FBlBJwB_-yuL9WVCc7bq5yOezJltGK77L6tu3653lFLyt6zZsA'
@@ -983,7 +983,7 @@

Curl request

$ curl 'http://localhost:8080/api/categories' -i -X GET \
-    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDEwLCJleHAiOjE3MjIyNjk2MTB9.URlH4KvMFjYZGvtIUJxxR7CmW2mZFc6-cYuy0Hj9C7En67-0nNt3EgOJ-9XWRBPhFUGvbhnbKCzao2At2lOncw'
+ -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMzU1MTAyLCJleHAiOjE3MjIzNTg3MDJ9.Ui6qD8vxUswbaz_RdFW-pU7XmGPFxaXS0DU7FBlBJwB_-yuL9WVCc7bq5yOezJltGK77L6tu3653lFLyt6zZsA'
@@ -992,7 +992,7 @@

HTTP request

GET /api/categories HTTP/1.1
-Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDEwLCJleHAiOjE3MjIyNjk2MTB9.URlH4KvMFjYZGvtIUJxxR7CmW2mZFc6-cYuy0Hj9C7En67-0nNt3EgOJ-9XWRBPhFUGvbhnbKCzao2At2lOncw
+Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMzU1MTAyLCJleHAiOjE3MjIzNTg3MDJ9.Ui6qD8vxUswbaz_RdFW-pU7XmGPFxaXS0DU7FBlBJwB_-yuL9WVCc7bq5yOezJltGK77L6tu3653lFLyt6zZsA
 Host: localhost:8080
@@ -1028,7 +1028,7 @@

HTTPie request

$ http GET 'http://localhost:8080/api/categories' \
-    'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDEwLCJleHAiOjE3MjIyNjk2MTB9.URlH4KvMFjYZGvtIUJxxR7CmW2mZFc6-cYuy0Hj9C7En67-0nNt3EgOJ-9XWRBPhFUGvbhnbKCzao2At2lOncw'
+ 'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMzU1MTAyLCJleHAiOjE3MjIzNTg3MDJ9.Ui6qD8vxUswbaz_RdFW-pU7XmGPFxaXS0DU7FBlBJwB_-yuL9WVCc7bq5yOezJltGK77L6tu3653lFLyt6zZsA'
@@ -1142,7 +1142,7 @@

Response fields

diff --git a/src/main/resources/static/docs.html b/src/main/resources/static/docs.html index f1c33b491..a53ac9af9 100644 --- a/src/main/resources/static/docs.html +++ b/src/main/resources/static/docs.html @@ -739,7 +739,7 @@

$ curl 'http://localhost:8080/api/products?page=0&size=10' -i -X GET \
-    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDExLCJleHAiOjE3MjIyNjk2MTF9.P8FNBO9WBDAGlz6J4ccAZRed-x7mhAg4h8YVxOvveyuH5v84ZOBtqBmXS4vEBy9o0l53d1Rx8mqdH7IQVfWcww'
+ -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMzU1MTA0LCJleHAiOjE3MjIzNTg3MDR9.89Yf45yjhDeU0k6CYLtjklTXJTJZ8K1aDtqgdLnpIzLkpfVMlu-JX84iwIzn47WsywraIJ-7AsJSS_5zs4TaQw'
@@ -748,7 +748,7 @@

GET /api/products?page=0&size=10 HTTP/1.1
-Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDExLCJleHAiOjE3MjIyNjk2MTF9.P8FNBO9WBDAGlz6J4ccAZRed-x7mhAg4h8YVxOvveyuH5v84ZOBtqBmXS4vEBy9o0l53d1Rx8mqdH7IQVfWcww
+Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMzU1MTA0LCJleHAiOjE3MjIzNTg3MDR9.89Yf45yjhDeU0k6CYLtjklTXJTJZ8K1aDtqgdLnpIzLkpfVMlu-JX84iwIzn47WsywraIJ-7AsJSS_5zs4TaQw
 Host: localhost:8080
@@ -789,7 +789,7 @@

$ http GET 'http://localhost:8080/api/products?page=0&size=10' \
-    'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDExLCJleHAiOjE3MjIyNjk2MTF9.P8FNBO9WBDAGlz6J4ccAZRed-x7mhAg4h8YVxOvveyuH5v84ZOBtqBmXS4vEBy9o0l53d1Rx8mqdH7IQVfWcww'
+ 'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMzU1MTA0LCJleHAiOjE3MjIzNTg3MDR9.89Yf45yjhDeU0k6CYLtjklTXJTJZ8K1aDtqgdLnpIzLkpfVMlu-JX84iwIzn47WsywraIJ-7AsJSS_5zs4TaQw'
@@ -945,7 +945,7 @@

$ curl 'http://localhost:8080/api/products/1' -i -X GET \
-    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDExLCJleHAiOjE3MjIyNjk2MTF9.P8FNBO9WBDAGlz6J4ccAZRed-x7mhAg4h8YVxOvveyuH5v84ZOBtqBmXS4vEBy9o0l53d1Rx8mqdH7IQVfWcww'
+ -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMzU1MTA0LCJleHAiOjE3MjIzNTg3MDR9.89Yf45yjhDeU0k6CYLtjklTXJTJZ8K1aDtqgdLnpIzLkpfVMlu-JX84iwIzn47WsywraIJ-7AsJSS_5zs4TaQw'
@@ -954,7 +954,7 @@

GET /api/products/1 HTTP/1.1
-Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDExLCJleHAiOjE3MjIyNjk2MTF9.P8FNBO9WBDAGlz6J4ccAZRed-x7mhAg4h8YVxOvveyuH5v84ZOBtqBmXS4vEBy9o0l53d1Rx8mqdH7IQVfWcww
+Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMzU1MTA0LCJleHAiOjE3MjIzNTg3MDR9.89Yf45yjhDeU0k6CYLtjklTXJTJZ8K1aDtqgdLnpIzLkpfVMlu-JX84iwIzn47WsywraIJ-7AsJSS_5zs4TaQw
 Host: localhost:8080
@@ -993,7 +993,7 @@

$ http GET 'http://localhost:8080/api/products/1' \
-    'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDExLCJleHAiOjE3MjIyNjk2MTF9.P8FNBO9WBDAGlz6J4ccAZRed-x7mhAg4h8YVxOvveyuH5v84ZOBtqBmXS4vEBy9o0l53d1Rx8mqdH7IQVfWcww'
+ 'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMzU1MTA0LCJleHAiOjE3MjIzNTg3MDR9.89Yf45yjhDeU0k6CYLtjklTXJTJZ8K1aDtqgdLnpIzLkpfVMlu-JX84iwIzn47WsywraIJ-7AsJSS_5zs4TaQw'
@@ -1123,7 +1123,7 @@

$ curl 'http://localhost:8080/api/products' -i -X POST \
     -H 'Content-Type: application/json' \
-    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDEyLCJleHAiOjE3MjIyNjk2MTJ9.51JkouAk3os1RvXpWPbMB15kEfjb6-N4hCgmFJDyx41jc0e2-BaohTOL7LFxElg8n1NNpO3n7S4d9hg_C90V7w' \
+    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMzU1MTA0LCJleHAiOjE3MjIzNTg3MDR9.89Yf45yjhDeU0k6CYLtjklTXJTJZ8K1aDtqgdLnpIzLkpfVMlu-JX84iwIzn47WsywraIJ-7AsJSS_5zs4TaQw' \
     -d '{
   "name" : "상품01",
   "price" : 1000,
@@ -1143,7 +1143,7 @@ 

POST /api/products HTTP/1.1
 Content-Type: application/json
-Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDEyLCJleHAiOjE3MjIyNjk2MTJ9.51JkouAk3os1RvXpWPbMB15kEfjb6-N4hCgmFJDyx41jc0e2-BaohTOL7LFxElg8n1NNpO3n7S4d9hg_C90V7w
+Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMzU1MTA0LCJleHAiOjE3MjIzNTg3MDR9.89Yf45yjhDeU0k6CYLtjklTXJTJZ8K1aDtqgdLnpIzLkpfVMlu-JX84iwIzn47WsywraIJ-7AsJSS_5zs4TaQw
 Content-Length: 196
 Host: localhost:8080
 
@@ -1199,7 +1199,7 @@ 

$ curl 'http://localhost:8080/api/products/1' -i -X PUT \
     -H 'Content-Type: application/json' \
-    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDExLCJleHAiOjE3MjIyNjk2MTF9.P8FNBO9WBDAGlz6J4ccAZRed-x7mhAg4h8YVxOvveyuH5v84ZOBtqBmXS4vEBy9o0l53d1Rx8mqdH7IQVfWcww' \
+    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMzU1MTA0LCJleHAiOjE3MjIzNTg3MDR9.89Yf45yjhDeU0k6CYLtjklTXJTJZ8K1aDtqgdLnpIzLkpfVMlu-JX84iwIzn47WsywraIJ-7AsJSS_5zs4TaQw' \
     -d '{
   "name" : "상품01",
   "price" : 1000,
@@ -1381,7 +1381,7 @@ 

PUT /api/products/1 HTTP/1.1
 Content-Type: application/json
-Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDExLCJleHAiOjE3MjIyNjk2MTF9.P8FNBO9WBDAGlz6J4ccAZRed-x7mhAg4h8YVxOvveyuH5v84ZOBtqBmXS4vEBy9o0l53d1Rx8mqdH7IQVfWcww
+Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMzU1MTA0LCJleHAiOjE3MjIzNTg3MDR9.89Yf45yjhDeU0k6CYLtjklTXJTJZ8K1aDtqgdLnpIzLkpfVMlu-JX84iwIzn47WsywraIJ-7AsJSS_5zs4TaQw
 Content-Length: 93
 Host: localhost:8080
 
@@ -1427,7 +1427,7 @@ 

+ 'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMzU1MTA0LCJleHAiOjE3MjIzNTg3MDR9.89Yf45yjhDeU0k6CYLtjklTXJTJZ8K1aDtqgdLnpIzLkpfVMlu-JX84iwIzn47WsywraIJ-7AsJSS_5zs4TaQw'

@@ -1569,7 +1569,7 @@

$ curl 'http://localhost:8080/api/products/1' -i -X DELETE \
-    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDExLCJleHAiOjE3MjIyNjk2MTF9.P8FNBO9WBDAGlz6J4ccAZRed-x7mhAg4h8YVxOvveyuH5v84ZOBtqBmXS4vEBy9o0l53d1Rx8mqdH7IQVfWcww'
+ -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMzU1MTA0LCJleHAiOjE3MjIzNTg3MDR9.89Yf45yjhDeU0k6CYLtjklTXJTJZ8K1aDtqgdLnpIzLkpfVMlu-JX84iwIzn47WsywraIJ-7AsJSS_5zs4TaQw'

@@ -1578,7 +1578,7 @@

DELETE /api/products/1 HTTP/1.1
-Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDExLCJleHAiOjE3MjIyNjk2MTF9.P8FNBO9WBDAGlz6J4ccAZRed-x7mhAg4h8YVxOvveyuH5v84ZOBtqBmXS4vEBy9o0l53d1Rx8mqdH7IQVfWcww
+Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMzU1MTA0LCJleHAiOjE3MjIzNTg3MDR9.89Yf45yjhDeU0k6CYLtjklTXJTJZ8K1aDtqgdLnpIzLkpfVMlu-JX84iwIzn47WsywraIJ-7AsJSS_5zs4TaQw
 Host: localhost:8080
@@ -1596,7 +1596,7 @@

$ http DELETE 'http://localhost:8080/api/products/1' \
-    'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDExLCJleHAiOjE3MjIyNjk2MTF9.P8FNBO9WBDAGlz6J4ccAZRed-x7mhAg4h8YVxOvveyuH5v84ZOBtqBmXS4vEBy9o0l53d1Rx8mqdH7IQVfWcww'
+ 'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMzU1MTA0LCJleHAiOjE3MjIzNTg3MDR9.89Yf45yjhDeU0k6CYLtjklTXJTJZ8K1aDtqgdLnpIzLkpfVMlu-JX84iwIzn47WsywraIJ-7AsJSS_5zs4TaQw'

@@ -1625,7 +1625,7 @@

$ curl 'http://localhost:8080/api/products/1/order' -i -X POST \
     -H 'Content-Type: application/json' \
-    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDEyLCJleHAiOjE3MjIyNjk2MTJ9.51JkouAk3os1RvXpWPbMB15kEfjb6-N4hCgmFJDyx41jc0e2-BaohTOL7LFxElg8n1NNpO3n7S4d9hg_C90V7w' \
+    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMzU1MTA0LCJleHAiOjE3MjIzNTg3MDR9.89Yf45yjhDeU0k6CYLtjklTXJTJZ8K1aDtqgdLnpIzLkpfVMlu-JX84iwIzn47WsywraIJ-7AsJSS_5zs4TaQw' \
     -d '{
   "optionId" : 1,
   "quantity" : 1,
@@ -1640,7 +1640,7 @@ 

POST /api/products/1/order HTTP/1.1
 Content-Type: application/json
-Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDEyLCJleHAiOjE3MjIyNjk2MTJ9.51JkouAk3os1RvXpWPbMB15kEfjb6-N4hCgmFJDyx41jc0e2-BaohTOL7LFxElg8n1NNpO3n7S4d9hg_C90V7w
+Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMzU1MTA0LCJleHAiOjE3MjIzNTg3MDR9.89Yf45yjhDeU0k6CYLtjklTXJTJZ8K1aDtqgdLnpIzLkpfVMlu-JX84iwIzn47WsywraIJ-7AsJSS_5zs4TaQw
 Content-Length: 63
 Host: localhost:8080
 
@@ -1658,14 +1658,12 @@ 

HTTP/1.1 200 OK
 Content-Type: application/json
-Content-Length: 134
+Content-Length: 83
 
 {
   "productId" : 1,
   "optionId" : 1,
-  "optionStock" : 10,
-  "quantity" : 1,
-  "productName" : "상품01",
+  "quantity" : 10,
   "message" : "message"
 }
@@ -1681,7 +1679,7 @@

+ 'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMzU1MTA0LCJleHAiOjE3MjIzNTg3MDR9.89Yf45yjhDeU0k6CYLtjklTXJTJZ8K1aDtqgdLnpIzLkpfVMlu-JX84iwIzn47WsywraIJ-7AsJSS_5zs4TaQw'

@@ -1721,7 +1719,7 @@

quantity

Number

-

수량

+

주문 수량

message

@@ -1738,9 +1736,7 @@

{ "productId" : 1, "optionId" : 1, - "optionStock" : 10, - "quantity" : 1, - "productName" : "상품01", + "quantity" : 10, "message" : "message" } @@ -1773,19 +1769,9 @@

상품 옵션 ID

-

optionStock

-

Number

-

상품 옵션 재고

- -

quantity

Number

-

수량

- - -

productName

-

String

-

상품명

+

주문 수량

message

@@ -1804,7 +1790,7 @@

$ curl 'http://localhost:8080/api/products/wish' -i -X POST \
     -H 'Content-Type: application/json' \
-    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDExLCJleHAiOjE3MjIyNjk2MTF9.P8FNBO9WBDAGlz6J4ccAZRed-x7mhAg4h8YVxOvveyuH5v84ZOBtqBmXS4vEBy9o0l53d1Rx8mqdH7IQVfWcww' \
+    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMzU1MTA0LCJleHAiOjE3MjIzNTg3MDR9.89Yf45yjhDeU0k6CYLtjklTXJTJZ8K1aDtqgdLnpIzLkpfVMlu-JX84iwIzn47WsywraIJ-7AsJSS_5zs4TaQw' \
     -d '{
   "productId" : 1,
   "quantity" : 1
@@ -1818,7 +1804,7 @@ 

POST /api/products/wish HTTP/1.1
 Content-Type: application/json
-Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDExLCJleHAiOjE3MjIyNjk2MTF9.P8FNBO9WBDAGlz6J4ccAZRed-x7mhAg4h8YVxOvveyuH5v84ZOBtqBmXS4vEBy9o0l53d1Rx8mqdH7IQVfWcww
+Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMzU1MTA0LCJleHAiOjE3MjIzNTg3MDR9.89Yf45yjhDeU0k6CYLtjklTXJTJZ8K1aDtqgdLnpIzLkpfVMlu-JX84iwIzn47WsywraIJ-7AsJSS_5zs4TaQw
 Content-Length: 39
 Host: localhost:8080
 
@@ -1853,7 +1839,7 @@ 

+ 'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMzU1MTA0LCJleHAiOjE3MjIzNTg3MDR9.89Yf45yjhDeU0k6CYLtjklTXJTJZ8K1aDtqgdLnpIzLkpfVMlu-JX84iwIzn47WsywraIJ-7AsJSS_5zs4TaQw'

@@ -1946,7 +1932,7 @@

$ curl 'http://localhost:8080/api/products/1/options' -i -X POST \
     -H 'Content-Type: application/json' \
-    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDEyLCJleHAiOjE3MjIyNjk2MTJ9.51JkouAk3os1RvXpWPbMB15kEfjb6-N4hCgmFJDyx41jc0e2-BaohTOL7LFxElg8n1NNpO3n7S4d9hg_C90V7w' \
+    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMzU1MTA0LCJleHAiOjE3MjIzNTg3MDR9.89Yf45yjhDeU0k6CYLtjklTXJTJZ8K1aDtqgdLnpIzLkpfVMlu-JX84iwIzn47WsywraIJ-7AsJSS_5zs4TaQw' \
     -d '{
   "name" : "상품 옵션 01",
   "stock" : 100
@@ -1960,7 +1946,7 @@ 

POST /api/products/1/options HTTP/1.1
 Content-Type: application/json
-Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDEyLCJleHAiOjE3MjIyNjk2MTJ9.51JkouAk3os1RvXpWPbMB15kEfjb6-N4hCgmFJDyx41jc0e2-BaohTOL7LFxElg8n1NNpO3n7S4d9hg_C90V7w
+Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMzU1MTA0LCJleHAiOjE3MjIzNTg3MDR9.89Yf45yjhDeU0k6CYLtjklTXJTJZ8K1aDtqgdLnpIzLkpfVMlu-JX84iwIzn47WsywraIJ-7AsJSS_5zs4TaQw
 Content-Length: 50
 Host: localhost:8080
 
@@ -1997,7 +1983,7 @@ 

+ 'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMzU1MTA0LCJleHAiOjE3MjIzNTg3MDR9.89Yf45yjhDeU0k6CYLtjklTXJTJZ8K1aDtqgdLnpIzLkpfVMlu-JX84iwIzn47WsywraIJ-7AsJSS_5zs4TaQw'

@@ -2066,7 +2052,7 @@

$ curl 'http://localhost:8080/api/products/1/options' -i -X GET \
-    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDExLCJleHAiOjE3MjIyNjk2MTF9.P8FNBO9WBDAGlz6J4ccAZRed-x7mhAg4h8YVxOvveyuH5v84ZOBtqBmXS4vEBy9o0l53d1Rx8mqdH7IQVfWcww'
+ -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMzU1MTA0LCJleHAiOjE3MjIzNTg3MDR9.89Yf45yjhDeU0k6CYLtjklTXJTJZ8K1aDtqgdLnpIzLkpfVMlu-JX84iwIzn47WsywraIJ-7AsJSS_5zs4TaQw'
@@ -2075,7 +2061,7 @@

GET /api/products/1/options HTTP/1.1
-Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDExLCJleHAiOjE3MjIyNjk2MTF9.P8FNBO9WBDAGlz6J4ccAZRed-x7mhAg4h8YVxOvveyuH5v84ZOBtqBmXS4vEBy9o0l53d1Rx8mqdH7IQVfWcww
+Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMzU1MTA0LCJleHAiOjE3MjIzNTg3MDR9.89Yf45yjhDeU0k6CYLtjklTXJTJZ8K1aDtqgdLnpIzLkpfVMlu-JX84iwIzn47WsywraIJ-7AsJSS_5zs4TaQw
 Host: localhost:8080
@@ -2103,7 +2089,7 @@

$ http GET 'http://localhost:8080/api/products/1/options' \
-    'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDExLCJleHAiOjE3MjIyNjk2MTF9.P8FNBO9WBDAGlz6J4ccAZRed-x7mhAg4h8YVxOvveyuH5v84ZOBtqBmXS4vEBy9o0l53d1Rx8mqdH7IQVfWcww'
+ 'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMzU1MTA0LCJleHAiOjE3MjIzNTg3MDR9.89Yf45yjhDeU0k6CYLtjklTXJTJZ8K1aDtqgdLnpIzLkpfVMlu-JX84iwIzn47WsywraIJ-7AsJSS_5zs4TaQw'
@@ -2172,7 +2158,7 @@

$ curl 'http://localhost:8080/api/products/1/options/1' -i -X PUT \
     -H 'Content-Type: application/json' \
-    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDExLCJleHAiOjE3MjIyNjk2MTF9.P8FNBO9WBDAGlz6J4ccAZRed-x7mhAg4h8YVxOvveyuH5v84ZOBtqBmXS4vEBy9o0l53d1Rx8mqdH7IQVfWcww' \
+    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMzU1MTA0LCJleHAiOjE3MjIzNTg3MDR9.89Yf45yjhDeU0k6CYLtjklTXJTJZ8K1aDtqgdLnpIzLkpfVMlu-JX84iwIzn47WsywraIJ-7AsJSS_5zs4TaQw' \
     -d '{
   "name" : "상품 옵션 01",
   "stock" : 100
@@ -2186,7 +2172,7 @@ 

PUT /api/products/1/options/1 HTTP/1.1
 Content-Type: application/json
-Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDExLCJleHAiOjE3MjIyNjk2MTF9.P8FNBO9WBDAGlz6J4ccAZRed-x7mhAg4h8YVxOvveyuH5v84ZOBtqBmXS4vEBy9o0l53d1Rx8mqdH7IQVfWcww
+Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMzU1MTA0LCJleHAiOjE3MjIzNTg3MDR9.89Yf45yjhDeU0k6CYLtjklTXJTJZ8K1aDtqgdLnpIzLkpfVMlu-JX84iwIzn47WsywraIJ-7AsJSS_5zs4TaQw
 Content-Length: 50
 Host: localhost:8080
 
@@ -2222,7 +2208,7 @@ 

+ 'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMzU1MTA0LCJleHAiOjE3MjIzNTg3MDR9.89Yf45yjhDeU0k6CYLtjklTXJTJZ8K1aDtqgdLnpIzLkpfVMlu-JX84iwIzn47WsywraIJ-7AsJSS_5zs4TaQw'

@@ -2320,7 +2306,7 @@

$ curl 'http://localhost:8080/api/products/1/options/1' -i -X DELETE \
-    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDExLCJleHAiOjE3MjIyNjk2MTF9.P8FNBO9WBDAGlz6J4ccAZRed-x7mhAg4h8YVxOvveyuH5v84ZOBtqBmXS4vEBy9o0l53d1Rx8mqdH7IQVfWcww'
+ -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMzU1MTA0LCJleHAiOjE3MjIzNTg3MDR9.89Yf45yjhDeU0k6CYLtjklTXJTJZ8K1aDtqgdLnpIzLkpfVMlu-JX84iwIzn47WsywraIJ-7AsJSS_5zs4TaQw'
@@ -2329,7 +2315,7 @@

DELETE /api/products/1/options/1 HTTP/1.1
-Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDExLCJleHAiOjE3MjIyNjk2MTF9.P8FNBO9WBDAGlz6J4ccAZRed-x7mhAg4h8YVxOvveyuH5v84ZOBtqBmXS4vEBy9o0l53d1Rx8mqdH7IQVfWcww
+Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMzU1MTA0LCJleHAiOjE3MjIzNTg3MDR9.89Yf45yjhDeU0k6CYLtjklTXJTJZ8K1aDtqgdLnpIzLkpfVMlu-JX84iwIzn47WsywraIJ-7AsJSS_5zs4TaQw
 Host: localhost:8080
@@ -2347,7 +2333,7 @@

$ http DELETE 'http://localhost:8080/api/products/1/options/1' \
-    'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDExLCJleHAiOjE3MjIyNjk2MTF9.P8FNBO9WBDAGlz6J4ccAZRed-x7mhAg4h8YVxOvveyuH5v84ZOBtqBmXS4vEBy9o0l53d1Rx8mqdH7IQVfWcww'
+ 'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMzU1MTA0LCJleHAiOjE3MjIzNTg3MDR9.89Yf45yjhDeU0k6CYLtjklTXJTJZ8K1aDtqgdLnpIzLkpfVMlu-JX84iwIzn47WsywraIJ-7AsJSS_5zs4TaQw'
@@ -2381,7 +2367,7 @@

$ curl 'http://localhost:8080/api/categories' -i -X POST \
     -H 'Content-Type: application/json' \
-    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDEwLCJleHAiOjE3MjIyNjk2MTB9.URlH4KvMFjYZGvtIUJxxR7CmW2mZFc6-cYuy0Hj9C7En67-0nNt3EgOJ-9XWRBPhFUGvbhnbKCzao2At2lOncw' \
+    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMzU1MTAyLCJleHAiOjE3MjIzNTg3MDJ9.Ui6qD8vxUswbaz_RdFW-pU7XmGPFxaXS0DU7FBlBJwB_-yuL9WVCc7bq5yOezJltGK77L6tu3653lFLyt6zZsA' \
     -d '{
   "name" : "카테고리01",
   "description" : "카테고리01 설명",
@@ -2397,7 +2383,7 @@ 

POST /api/categories HTTP/1.1
 Content-Type: application/json
-Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDEwLCJleHAiOjE3MjIyNjk2MTB9.URlH4KvMFjYZGvtIUJxxR7CmW2mZFc6-cYuy0Hj9C7En67-0nNt3EgOJ-9XWRBPhFUGvbhnbKCzao2At2lOncw
+Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMzU1MTAyLCJleHAiOjE3MjIzNTg3MDJ9.Ui6qD8vxUswbaz_RdFW-pU7XmGPFxaXS0DU7FBlBJwB_-yuL9WVCc7bq5yOezJltGK77L6tu3653lFLyt6zZsA
 Content-Length: 147
 Host: localhost:8080
 
@@ -2440,7 +2426,7 @@ 

+ 'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMzU1MTAyLCJleHAiOjE3MjIzNTg3MDJ9.Ui6qD8vxUswbaz_RdFW-pU7XmGPFxaXS0DU7FBlBJwB_-yuL9WVCc7bq5yOezJltGK77L6tu3653lFLyt6zZsA'

@@ -2563,7 +2549,7 @@

$ curl 'http://localhost:8080/api/categories/1' -i -X PUT \
     -H 'Content-Type: application/json' \
-    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDEwLCJleHAiOjE3MjIyNjk2MTB9.URlH4KvMFjYZGvtIUJxxR7CmW2mZFc6-cYuy0Hj9C7En67-0nNt3EgOJ-9XWRBPhFUGvbhnbKCzao2At2lOncw' \
+    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMzU1MTAyLCJleHAiOjE3MjIzNTg3MDJ9.Ui6qD8vxUswbaz_RdFW-pU7XmGPFxaXS0DU7FBlBJwB_-yuL9WVCc7bq5yOezJltGK77L6tu3653lFLyt6zZsA' \
     -d '{
   "name" : "카테고리01",
   "description" : "카테고리01 설명",
@@ -2579,7 +2565,7 @@ 

PUT /api/categories/1 HTTP/1.1
 Content-Type: application/json
-Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDEwLCJleHAiOjE3MjIyNjk2MTB9.URlH4KvMFjYZGvtIUJxxR7CmW2mZFc6-cYuy0Hj9C7En67-0nNt3EgOJ-9XWRBPhFUGvbhnbKCzao2At2lOncw
+Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMzU1MTAyLCJleHAiOjE3MjIzNTg3MDJ9.Ui6qD8vxUswbaz_RdFW-pU7XmGPFxaXS0DU7FBlBJwB_-yuL9WVCc7bq5yOezJltGK77L6tu3653lFLyt6zZsA
 Content-Length: 147
 Host: localhost:8080
 
@@ -2621,7 +2607,7 @@ 

+ 'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMzU1MTAyLCJleHAiOjE3MjIzNTg3MDJ9.Ui6qD8vxUswbaz_RdFW-pU7XmGPFxaXS0DU7FBlBJwB_-yuL9WVCc7bq5yOezJltGK77L6tu3653lFLyt6zZsA'

@@ -2743,7 +2729,7 @@

$ curl 'http://localhost:8080/api/categories/1' -i -X DELETE \
-    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDEwLCJleHAiOjE3MjIyNjk2MTB9.URlH4KvMFjYZGvtIUJxxR7CmW2mZFc6-cYuy0Hj9C7En67-0nNt3EgOJ-9XWRBPhFUGvbhnbKCzao2At2lOncw'
+ -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMzU1MTAyLCJleHAiOjE3MjIzNTg3MDJ9.Ui6qD8vxUswbaz_RdFW-pU7XmGPFxaXS0DU7FBlBJwB_-yuL9WVCc7bq5yOezJltGK77L6tu3653lFLyt6zZsA'
@@ -2752,7 +2738,7 @@

DELETE /api/categories/1 HTTP/1.1
-Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDEwLCJleHAiOjE3MjIyNjk2MTB9.URlH4KvMFjYZGvtIUJxxR7CmW2mZFc6-cYuy0Hj9C7En67-0nNt3EgOJ-9XWRBPhFUGvbhnbKCzao2At2lOncw
+Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMzU1MTAyLCJleHAiOjE3MjIzNTg3MDJ9.Ui6qD8vxUswbaz_RdFW-pU7XmGPFxaXS0DU7FBlBJwB_-yuL9WVCc7bq5yOezJltGK77L6tu3653lFLyt6zZsA
 Host: localhost:8080
@@ -2770,7 +2756,7 @@

$ http DELETE 'http://localhost:8080/api/categories/1' \
-    'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDEwLCJleHAiOjE3MjIyNjk2MTB9.URlH4KvMFjYZGvtIUJxxR7CmW2mZFc6-cYuy0Hj9C7En67-0nNt3EgOJ-9XWRBPhFUGvbhnbKCzao2At2lOncw'
+ 'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMzU1MTAyLCJleHAiOjE3MjIzNTg3MDJ9.Ui6qD8vxUswbaz_RdFW-pU7XmGPFxaXS0DU7FBlBJwB_-yuL9WVCc7bq5yOezJltGK77L6tu3653lFLyt6zZsA'
@@ -2798,7 +2784,7 @@

$ curl 'http://localhost:8080/api/categories/1' -i -X GET \
-    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDEwLCJleHAiOjE3MjIyNjk2MTB9.URlH4KvMFjYZGvtIUJxxR7CmW2mZFc6-cYuy0Hj9C7En67-0nNt3EgOJ-9XWRBPhFUGvbhnbKCzao2At2lOncw'
+ -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMzU1MTAyLCJleHAiOjE3MjIzNTg3MDJ9.Ui6qD8vxUswbaz_RdFW-pU7XmGPFxaXS0DU7FBlBJwB_-yuL9WVCc7bq5yOezJltGK77L6tu3653lFLyt6zZsA'
@@ -2807,7 +2793,7 @@

GET /api/categories/1 HTTP/1.1
-Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDEwLCJleHAiOjE3MjIyNjk2MTB9.URlH4KvMFjYZGvtIUJxxR7CmW2mZFc6-cYuy0Hj9C7En67-0nNt3EgOJ-9XWRBPhFUGvbhnbKCzao2At2lOncw
+Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMzU1MTAyLCJleHAiOjE3MjIzNTg3MDJ9.Ui6qD8vxUswbaz_RdFW-pU7XmGPFxaXS0DU7FBlBJwB_-yuL9WVCc7bq5yOezJltGK77L6tu3653lFLyt6zZsA
 Host: localhost:8080
@@ -2835,7 +2821,7 @@

$ http GET 'http://localhost:8080/api/categories/1' \
-    'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDEwLCJleHAiOjE3MjIyNjk2MTB9.URlH4KvMFjYZGvtIUJxxR7CmW2mZFc6-cYuy0Hj9C7En67-0nNt3EgOJ-9XWRBPhFUGvbhnbKCzao2At2lOncw'
+ 'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMzU1MTAyLCJleHAiOjE3MjIzNTg3MDJ9.Ui6qD8vxUswbaz_RdFW-pU7XmGPFxaXS0DU7FBlBJwB_-yuL9WVCc7bq5yOezJltGK77L6tu3653lFLyt6zZsA'
@@ -2913,7 +2899,7 @@

$ curl 'http://localhost:8080/api/categories' -i -X GET \
-    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDEwLCJleHAiOjE3MjIyNjk2MTB9.URlH4KvMFjYZGvtIUJxxR7CmW2mZFc6-cYuy0Hj9C7En67-0nNt3EgOJ-9XWRBPhFUGvbhnbKCzao2At2lOncw'
+ -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMzU1MTAyLCJleHAiOjE3MjIzNTg3MDJ9.Ui6qD8vxUswbaz_RdFW-pU7XmGPFxaXS0DU7FBlBJwB_-yuL9WVCc7bq5yOezJltGK77L6tu3653lFLyt6zZsA'
@@ -2922,7 +2908,7 @@

GET /api/categories HTTP/1.1
-Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDEwLCJleHAiOjE3MjIyNjk2MTB9.URlH4KvMFjYZGvtIUJxxR7CmW2mZFc6-cYuy0Hj9C7En67-0nNt3EgOJ-9XWRBPhFUGvbhnbKCzao2At2lOncw
+Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMzU1MTAyLCJleHAiOjE3MjIzNTg3MDJ9.Ui6qD8vxUswbaz_RdFW-pU7XmGPFxaXS0DU7FBlBJwB_-yuL9WVCc7bq5yOezJltGK77L6tu3653lFLyt6zZsA
 Host: localhost:8080
@@ -2958,7 +2944,7 @@

$ http GET 'http://localhost:8080/api/categories' \
-    'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDEwLCJleHAiOjE3MjIyNjk2MTB9.URlH4KvMFjYZGvtIUJxxR7CmW2mZFc6-cYuy0Hj9C7En67-0nNt3EgOJ-9XWRBPhFUGvbhnbKCzao2At2lOncw'
+ 'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMzU1MTAyLCJleHAiOjE3MjIzNTg3MDJ9.Ui6qD8vxUswbaz_RdFW-pU7XmGPFxaXS0DU7FBlBJwB_-yuL9WVCc7bq5yOezJltGK77L6tu3653lFLyt6zZsA'
@@ -3267,7 +3253,7 @@

@@ -3329,7 +3315,7 @@

{
-  "accessToken" : "eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDExLCJleHAiOjE3MjIyNjk2MTF9.P8FNBO9WBDAGlz6J4ccAZRed-x7mhAg4h8YVxOvveyuH5v84ZOBtqBmXS4vEBy9o0l53d1Rx8mqdH7IQVfWcww"
+  "accessToken" : "eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMzU1MTA0LCJleHAiOjE3MjIzNTg3MDR9.89Yf45yjhDeU0k6CYLtjklTXJTJZ8K1aDtqgdLnpIzLkpfVMlu-JX84iwIzn47WsywraIJ-7AsJSS_5zs4TaQw"
 }
@@ -3366,7 +3352,7 @@

$ curl 'http://localhost:8080/api/members/1' -i -X GET \
-    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDExLCJleHAiOjE3MjIyNjk2MTF9.P8FNBO9WBDAGlz6J4ccAZRed-x7mhAg4h8YVxOvveyuH5v84ZOBtqBmXS4vEBy9o0l53d1Rx8mqdH7IQVfWcww'
+ -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMzU1MTA0LCJleHAiOjE3MjIzNTg3MDR9.89Yf45yjhDeU0k6CYLtjklTXJTJZ8K1aDtqgdLnpIzLkpfVMlu-JX84iwIzn47WsywraIJ-7AsJSS_5zs4TaQw'
@@ -3375,7 +3361,7 @@

GET /api/members/1 HTTP/1.1
-Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDExLCJleHAiOjE3MjIyNjk2MTF9.P8FNBO9WBDAGlz6J4ccAZRed-x7mhAg4h8YVxOvveyuH5v84ZOBtqBmXS4vEBy9o0l53d1Rx8mqdH7IQVfWcww
+Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMzU1MTA0LCJleHAiOjE3MjIzNTg3MDR9.89Yf45yjhDeU0k6CYLtjklTXJTJZ8K1aDtqgdLnpIzLkpfVMlu-JX84iwIzn47WsywraIJ-7AsJSS_5zs4TaQw
 Host: localhost:8080
@@ -3402,7 +3388,7 @@

$ http GET 'http://localhost:8080/api/members/1' \
-    'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDExLCJleHAiOjE3MjIyNjk2MTF9.P8FNBO9WBDAGlz6J4ccAZRed-x7mhAg4h8YVxOvveyuH5v84ZOBtqBmXS4vEBy9o0l53d1Rx8mqdH7IQVfWcww'
+ 'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMzU1MTA0LCJleHAiOjE3MjIzNTg3MDR9.89Yf45yjhDeU0k6CYLtjklTXJTJZ8K1aDtqgdLnpIzLkpfVMlu-JX84iwIzn47WsywraIJ-7AsJSS_5zs4TaQw'
@@ -3474,7 +3460,7 @@

$ curl 'http://localhost:8080/api/members/wishlist' -i -X GET \
-    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDExLCJleHAiOjE3MjIyNjk2MTF9.P8FNBO9WBDAGlz6J4ccAZRed-x7mhAg4h8YVxOvveyuH5v84ZOBtqBmXS4vEBy9o0l53d1Rx8mqdH7IQVfWcww'
+ -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMzU1MTA0LCJleHAiOjE3MjIzNTg3MDR9.89Yf45yjhDeU0k6CYLtjklTXJTJZ8K1aDtqgdLnpIzLkpfVMlu-JX84iwIzn47WsywraIJ-7AsJSS_5zs4TaQw'
@@ -3483,7 +3469,7 @@

GET /api/members/wishlist HTTP/1.1
-Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDExLCJleHAiOjE3MjIyNjk2MTF9.P8FNBO9WBDAGlz6J4ccAZRed-x7mhAg4h8YVxOvveyuH5v84ZOBtqBmXS4vEBy9o0l53d1Rx8mqdH7IQVfWcww
+Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMzU1MTA0LCJleHAiOjE3MjIzNTg3MDR9.89Yf45yjhDeU0k6CYLtjklTXJTJZ8K1aDtqgdLnpIzLkpfVMlu-JX84iwIzn47WsywraIJ-7AsJSS_5zs4TaQw
 Host: localhost:8080
@@ -3514,7 +3500,7 @@

$ http GET 'http://localhost:8080/api/members/wishlist' \
-    'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDExLCJleHAiOjE3MjIyNjk2MTF9.P8FNBO9WBDAGlz6J4ccAZRed-x7mhAg4h8YVxOvveyuH5v84ZOBtqBmXS4vEBy9o0l53d1Rx8mqdH7IQVfWcww'
+ 'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMzU1MTA0LCJleHAiOjE3MjIzNTg3MDR9.89Yf45yjhDeU0k6CYLtjklTXJTJZ8K1aDtqgdLnpIzLkpfVMlu-JX84iwIzn47WsywraIJ-7AsJSS_5zs4TaQw'
@@ -3626,7 +3612,7 @@

$ curl 'http://localhost:8080/api/members/wishlist/1' -i -X PUT \
     -H 'Content-Type: application/json' \
-    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDExLCJleHAiOjE3MjIyNjk2MTF9.P8FNBO9WBDAGlz6J4ccAZRed-x7mhAg4h8YVxOvveyuH5v84ZOBtqBmXS4vEBy9o0l53d1Rx8mqdH7IQVfWcww' \
+    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMzU1MTA0LCJleHAiOjE3MjIzNTg3MDR9.89Yf45yjhDeU0k6CYLtjklTXJTJZ8K1aDtqgdLnpIzLkpfVMlu-JX84iwIzn47WsywraIJ-7AsJSS_5zs4TaQw' \
     -d '{
   "quantity" : 5
 }'
@@ -3639,7 +3625,7 @@

PUT /api/members/wishlist/1 HTTP/1.1
 Content-Type: application/json
-Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDExLCJleHAiOjE3MjIyNjk2MTF9.P8FNBO9WBDAGlz6J4ccAZRed-x7mhAg4h8YVxOvveyuH5v84ZOBtqBmXS4vEBy9o0l53d1Rx8mqdH7IQVfWcww
+Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMzU1MTA0LCJleHAiOjE3MjIzNTg3MDR9.89Yf45yjhDeU0k6CYLtjklTXJTJZ8K1aDtqgdLnpIzLkpfVMlu-JX84iwIzn47WsywraIJ-7AsJSS_5zs4TaQw
 Content-Length: 20
 Host: localhost:8080
 
@@ -3676,7 +3662,7 @@ 

+ 'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMzU1MTA0LCJleHAiOjE3MjIzNTg3MDR9.89Yf45yjhDeU0k6CYLtjklTXJTJZ8K1aDtqgdLnpIzLkpfVMlu-JX84iwIzn47WsywraIJ-7AsJSS_5zs4TaQw' @@ -3786,7 +3772,7 @@

$ curl 'http://localhost:8080/api/members/wishlist/1' -i -X DELETE \
-    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDExLCJleHAiOjE3MjIyNjk2MTF9.P8FNBO9WBDAGlz6J4ccAZRed-x7mhAg4h8YVxOvveyuH5v84ZOBtqBmXS4vEBy9o0l53d1Rx8mqdH7IQVfWcww'
+ -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMzU1MTA0LCJleHAiOjE3MjIzNTg3MDR9.89Yf45yjhDeU0k6CYLtjklTXJTJZ8K1aDtqgdLnpIzLkpfVMlu-JX84iwIzn47WsywraIJ-7AsJSS_5zs4TaQw'
@@ -3795,7 +3781,7 @@

DELETE /api/members/wishlist/1 HTTP/1.1
-Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDExLCJleHAiOjE3MjIyNjk2MTF9.P8FNBO9WBDAGlz6J4ccAZRed-x7mhAg4h8YVxOvveyuH5v84ZOBtqBmXS4vEBy9o0l53d1Rx8mqdH7IQVfWcww
+Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMzU1MTA0LCJleHAiOjE3MjIzNTg3MDR9.89Yf45yjhDeU0k6CYLtjklTXJTJZ8K1aDtqgdLnpIzLkpfVMlu-JX84iwIzn47WsywraIJ-7AsJSS_5zs4TaQw
 Host: localhost:8080
@@ -3813,7 +3799,7 @@

$ http DELETE 'http://localhost:8080/api/members/wishlist/1' \
-    'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDExLCJleHAiOjE3MjIyNjk2MTF9.P8FNBO9WBDAGlz6J4ccAZRed-x7mhAg4h8YVxOvveyuH5v84ZOBtqBmXS4vEBy9o0l53d1Rx8mqdH7IQVfWcww'
+ 'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMzU1MTA0LCJleHAiOjE3MjIzNTg3MDR9.89Yf45yjhDeU0k6CYLtjklTXJTJZ8K1aDtqgdLnpIzLkpfVMlu-JX84iwIzn47WsywraIJ-7AsJSS_5zs4TaQw'
@@ -3950,7 +3936,7 @@

diff --git a/src/main/resources/static/login.html b/src/main/resources/static/login.html index df96232d6..1c3079433 100644 --- a/src/main/resources/static/login.html +++ b/src/main/resources/static/login.html @@ -554,7 +554,7 @@

Response fields

diff --git a/src/main/resources/static/member.html b/src/main/resources/static/member.html index fc385f586..d64053781 100644 --- a/src/main/resources/static/member.html +++ b/src/main/resources/static/member.html @@ -639,7 +639,7 @@

HTTP response

Content-Length: 196 { - "accessToken" : "eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDExLCJleHAiOjE3MjIyNjk2MTF9.P8FNBO9WBDAGlz6J4ccAZRed-x7mhAg4h8YVxOvveyuH5v84ZOBtqBmXS4vEBy9o0l53d1Rx8mqdH7IQVfWcww" + "accessToken" : "eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMzU1MTA0LCJleHAiOjE3MjIzNTg3MDR9.89Yf45yjhDeU0k6CYLtjklTXJTJZ8K1aDtqgdLnpIzLkpfVMlu-JX84iwIzn47WsywraIJ-7AsJSS_5zs4TaQw" } @@ -701,7 +701,7 @@

Response body

{
-  "accessToken" : "eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDExLCJleHAiOjE3MjIyNjk2MTF9.P8FNBO9WBDAGlz6J4ccAZRed-x7mhAg4h8YVxOvveyuH5v84ZOBtqBmXS4vEBy9o0l53d1Rx8mqdH7IQVfWcww"
+  "accessToken" : "eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMzU1MTA0LCJleHAiOjE3MjIzNTg3MDR9.89Yf45yjhDeU0k6CYLtjklTXJTJZ8K1aDtqgdLnpIzLkpfVMlu-JX84iwIzn47WsywraIJ-7AsJSS_5zs4TaQw"
 }
@@ -738,7 +738,7 @@

Curl request

$ curl 'http://localhost:8080/api/members/1' -i -X GET \
-    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDExLCJleHAiOjE3MjIyNjk2MTF9.P8FNBO9WBDAGlz6J4ccAZRed-x7mhAg4h8YVxOvveyuH5v84ZOBtqBmXS4vEBy9o0l53d1Rx8mqdH7IQVfWcww'
+ -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMzU1MTA0LCJleHAiOjE3MjIzNTg3MDR9.89Yf45yjhDeU0k6CYLtjklTXJTJZ8K1aDtqgdLnpIzLkpfVMlu-JX84iwIzn47WsywraIJ-7AsJSS_5zs4TaQw'
@@ -747,7 +747,7 @@

HTTP request

GET /api/members/1 HTTP/1.1
-Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDExLCJleHAiOjE3MjIyNjk2MTF9.P8FNBO9WBDAGlz6J4ccAZRed-x7mhAg4h8YVxOvveyuH5v84ZOBtqBmXS4vEBy9o0l53d1Rx8mqdH7IQVfWcww
+Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMzU1MTA0LCJleHAiOjE3MjIzNTg3MDR9.89Yf45yjhDeU0k6CYLtjklTXJTJZ8K1aDtqgdLnpIzLkpfVMlu-JX84iwIzn47WsywraIJ-7AsJSS_5zs4TaQw
 Host: localhost:8080
@@ -774,7 +774,7 @@

HTTPie request

$ http GET 'http://localhost:8080/api/members/1' \
-    'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDExLCJleHAiOjE3MjIyNjk2MTF9.P8FNBO9WBDAGlz6J4ccAZRed-x7mhAg4h8YVxOvveyuH5v84ZOBtqBmXS4vEBy9o0l53d1Rx8mqdH7IQVfWcww'
+ 'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMzU1MTA0LCJleHAiOjE3MjIzNTg3MDR9.89Yf45yjhDeU0k6CYLtjklTXJTJZ8K1aDtqgdLnpIzLkpfVMlu-JX84iwIzn47WsywraIJ-7AsJSS_5zs4TaQw'
@@ -846,7 +846,7 @@

Curl request

$ curl 'http://localhost:8080/api/members/wishlist' -i -X GET \
-    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDExLCJleHAiOjE3MjIyNjk2MTF9.P8FNBO9WBDAGlz6J4ccAZRed-x7mhAg4h8YVxOvveyuH5v84ZOBtqBmXS4vEBy9o0l53d1Rx8mqdH7IQVfWcww'
+ -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMzU1MTA0LCJleHAiOjE3MjIzNTg3MDR9.89Yf45yjhDeU0k6CYLtjklTXJTJZ8K1aDtqgdLnpIzLkpfVMlu-JX84iwIzn47WsywraIJ-7AsJSS_5zs4TaQw'
@@ -855,7 +855,7 @@

HTTP request

GET /api/members/wishlist HTTP/1.1
-Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDExLCJleHAiOjE3MjIyNjk2MTF9.P8FNBO9WBDAGlz6J4ccAZRed-x7mhAg4h8YVxOvveyuH5v84ZOBtqBmXS4vEBy9o0l53d1Rx8mqdH7IQVfWcww
+Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMzU1MTA0LCJleHAiOjE3MjIzNTg3MDR9.89Yf45yjhDeU0k6CYLtjklTXJTJZ8K1aDtqgdLnpIzLkpfVMlu-JX84iwIzn47WsywraIJ-7AsJSS_5zs4TaQw
 Host: localhost:8080
@@ -886,7 +886,7 @@

HTTPie request

$ http GET 'http://localhost:8080/api/members/wishlist' \
-    'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDExLCJleHAiOjE3MjIyNjk2MTF9.P8FNBO9WBDAGlz6J4ccAZRed-x7mhAg4h8YVxOvveyuH5v84ZOBtqBmXS4vEBy9o0l53d1Rx8mqdH7IQVfWcww'
+ 'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMzU1MTA0LCJleHAiOjE3MjIzNTg3MDR9.89Yf45yjhDeU0k6CYLtjklTXJTJZ8K1aDtqgdLnpIzLkpfVMlu-JX84iwIzn47WsywraIJ-7AsJSS_5zs4TaQw'
@@ -998,7 +998,7 @@

Curl request

$ curl 'http://localhost:8080/api/members/wishlist/1' -i -X PUT \
     -H 'Content-Type: application/json' \
-    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDExLCJleHAiOjE3MjIyNjk2MTF9.P8FNBO9WBDAGlz6J4ccAZRed-x7mhAg4h8YVxOvveyuH5v84ZOBtqBmXS4vEBy9o0l53d1Rx8mqdH7IQVfWcww' \
+    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMzU1MTA0LCJleHAiOjE3MjIzNTg3MDR9.89Yf45yjhDeU0k6CYLtjklTXJTJZ8K1aDtqgdLnpIzLkpfVMlu-JX84iwIzn47WsywraIJ-7AsJSS_5zs4TaQw' \
     -d '{
   "quantity" : 5
 }'
@@ -1011,7 +1011,7 @@

HTTP request

PUT /api/members/wishlist/1 HTTP/1.1
 Content-Type: application/json
-Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDExLCJleHAiOjE3MjIyNjk2MTF9.P8FNBO9WBDAGlz6J4ccAZRed-x7mhAg4h8YVxOvveyuH5v84ZOBtqBmXS4vEBy9o0l53d1Rx8mqdH7IQVfWcww
+Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMzU1MTA0LCJleHAiOjE3MjIzNTg3MDR9.89Yf45yjhDeU0k6CYLtjklTXJTJZ8K1aDtqgdLnpIzLkpfVMlu-JX84iwIzn47WsywraIJ-7AsJSS_5zs4TaQw
 Content-Length: 20
 Host: localhost:8080
 
@@ -1048,7 +1048,7 @@ 

HTTPie request

"quantity" : 5 }' | http PUT 'http://localhost:8080/api/members/wishlist/1' \ 'Content-Type:application/json' \ - 'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDExLCJleHAiOjE3MjIyNjk2MTF9.P8FNBO9WBDAGlz6J4ccAZRed-x7mhAg4h8YVxOvveyuH5v84ZOBtqBmXS4vEBy9o0l53d1Rx8mqdH7IQVfWcww'
+ 'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMzU1MTA0LCJleHAiOjE3MjIzNTg3MDR9.89Yf45yjhDeU0k6CYLtjklTXJTJZ8K1aDtqgdLnpIzLkpfVMlu-JX84iwIzn47WsywraIJ-7AsJSS_5zs4TaQw'
@@ -1158,7 +1158,7 @@

Curl request

$ curl 'http://localhost:8080/api/members/wishlist/1' -i -X DELETE \
-    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDExLCJleHAiOjE3MjIyNjk2MTF9.P8FNBO9WBDAGlz6J4ccAZRed-x7mhAg4h8YVxOvveyuH5v84ZOBtqBmXS4vEBy9o0l53d1Rx8mqdH7IQVfWcww'
+ -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMzU1MTA0LCJleHAiOjE3MjIzNTg3MDR9.89Yf45yjhDeU0k6CYLtjklTXJTJZ8K1aDtqgdLnpIzLkpfVMlu-JX84iwIzn47WsywraIJ-7AsJSS_5zs4TaQw'
@@ -1167,7 +1167,7 @@

HTTP request

DELETE /api/members/wishlist/1 HTTP/1.1
-Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDExLCJleHAiOjE3MjIyNjk2MTF9.P8FNBO9WBDAGlz6J4ccAZRed-x7mhAg4h8YVxOvveyuH5v84ZOBtqBmXS4vEBy9o0l53d1Rx8mqdH7IQVfWcww
+Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMzU1MTA0LCJleHAiOjE3MjIzNTg3MDR9.89Yf45yjhDeU0k6CYLtjklTXJTJZ8K1aDtqgdLnpIzLkpfVMlu-JX84iwIzn47WsywraIJ-7AsJSS_5zs4TaQw
 Host: localhost:8080
@@ -1185,7 +1185,7 @@

HTTPie request

$ http DELETE 'http://localhost:8080/api/members/wishlist/1' \
-    'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDExLCJleHAiOjE3MjIyNjk2MTF9.P8FNBO9WBDAGlz6J4ccAZRed-x7mhAg4h8YVxOvveyuH5v84ZOBtqBmXS4vEBy9o0l53d1Rx8mqdH7IQVfWcww'
+ 'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMzU1MTA0LCJleHAiOjE3MjIzNTg3MDR9.89Yf45yjhDeU0k6CYLtjklTXJTJZ8K1aDtqgdLnpIzLkpfVMlu-JX84iwIzn47WsywraIJ-7AsJSS_5zs4TaQw'
@@ -1212,7 +1212,7 @@

Response body

diff --git a/src/main/resources/static/product.html b/src/main/resources/static/product.html index a4e58ab2a..93f25fe52 100644 --- a/src/main/resources/static/product.html +++ b/src/main/resources/static/product.html @@ -450,7 +450,7 @@

Curl request

$ curl 'http://localhost:8080/api/products?page=0&size=10' -i -X GET \
-    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDExLCJleHAiOjE3MjIyNjk2MTF9.P8FNBO9WBDAGlz6J4ccAZRed-x7mhAg4h8YVxOvveyuH5v84ZOBtqBmXS4vEBy9o0l53d1Rx8mqdH7IQVfWcww'
+ -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMzU1MTA0LCJleHAiOjE3MjIzNTg3MDR9.89Yf45yjhDeU0k6CYLtjklTXJTJZ8K1aDtqgdLnpIzLkpfVMlu-JX84iwIzn47WsywraIJ-7AsJSS_5zs4TaQw'
@@ -459,7 +459,7 @@

HTTP request

GET /api/products?page=0&size=10 HTTP/1.1
-Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDExLCJleHAiOjE3MjIyNjk2MTF9.P8FNBO9WBDAGlz6J4ccAZRed-x7mhAg4h8YVxOvveyuH5v84ZOBtqBmXS4vEBy9o0l53d1Rx8mqdH7IQVfWcww
+Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMzU1MTA0LCJleHAiOjE3MjIzNTg3MDR9.89Yf45yjhDeU0k6CYLtjklTXJTJZ8K1aDtqgdLnpIzLkpfVMlu-JX84iwIzn47WsywraIJ-7AsJSS_5zs4TaQw
 Host: localhost:8080
@@ -500,7 +500,7 @@

HTTPie request

$ http GET 'http://localhost:8080/api/products?page=0&size=10' \
-    'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDExLCJleHAiOjE3MjIyNjk2MTF9.P8FNBO9WBDAGlz6J4ccAZRed-x7mhAg4h8YVxOvveyuH5v84ZOBtqBmXS4vEBy9o0l53d1Rx8mqdH7IQVfWcww'
+ 'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMzU1MTA0LCJleHAiOjE3MjIzNTg3MDR9.89Yf45yjhDeU0k6CYLtjklTXJTJZ8K1aDtqgdLnpIzLkpfVMlu-JX84iwIzn47WsywraIJ-7AsJSS_5zs4TaQw'
@@ -656,7 +656,7 @@

Curl request

$ curl 'http://localhost:8080/api/products/1' -i -X GET \
-    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDExLCJleHAiOjE3MjIyNjk2MTF9.P8FNBO9WBDAGlz6J4ccAZRed-x7mhAg4h8YVxOvveyuH5v84ZOBtqBmXS4vEBy9o0l53d1Rx8mqdH7IQVfWcww'
+ -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMzU1MTA0LCJleHAiOjE3MjIzNTg3MDR9.89Yf45yjhDeU0k6CYLtjklTXJTJZ8K1aDtqgdLnpIzLkpfVMlu-JX84iwIzn47WsywraIJ-7AsJSS_5zs4TaQw'
@@ -665,7 +665,7 @@

HTTP request

GET /api/products/1 HTTP/1.1
-Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDExLCJleHAiOjE3MjIyNjk2MTF9.P8FNBO9WBDAGlz6J4ccAZRed-x7mhAg4h8YVxOvveyuH5v84ZOBtqBmXS4vEBy9o0l53d1Rx8mqdH7IQVfWcww
+Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMzU1MTA0LCJleHAiOjE3MjIzNTg3MDR9.89Yf45yjhDeU0k6CYLtjklTXJTJZ8K1aDtqgdLnpIzLkpfVMlu-JX84iwIzn47WsywraIJ-7AsJSS_5zs4TaQw
 Host: localhost:8080
@@ -704,7 +704,7 @@

HTTPie request

$ http GET 'http://localhost:8080/api/products/1' \
-    'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDExLCJleHAiOjE3MjIyNjk2MTF9.P8FNBO9WBDAGlz6J4ccAZRed-x7mhAg4h8YVxOvveyuH5v84ZOBtqBmXS4vEBy9o0l53d1Rx8mqdH7IQVfWcww'
+ 'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMzU1MTA0LCJleHAiOjE3MjIzNTg3MDR9.89Yf45yjhDeU0k6CYLtjklTXJTJZ8K1aDtqgdLnpIzLkpfVMlu-JX84iwIzn47WsywraIJ-7AsJSS_5zs4TaQw'
@@ -834,7 +834,7 @@

Curl request

$ curl 'http://localhost:8080/api/products' -i -X POST \
     -H 'Content-Type: application/json' \
-    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDEyLCJleHAiOjE3MjIyNjk2MTJ9.51JkouAk3os1RvXpWPbMB15kEfjb6-N4hCgmFJDyx41jc0e2-BaohTOL7LFxElg8n1NNpO3n7S4d9hg_C90V7w' \
+    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMzU1MTA0LCJleHAiOjE3MjIzNTg3MDR9.89Yf45yjhDeU0k6CYLtjklTXJTJZ8K1aDtqgdLnpIzLkpfVMlu-JX84iwIzn47WsywraIJ-7AsJSS_5zs4TaQw' \
     -d '{
   "name" : "상품01",
   "price" : 1000,
@@ -854,7 +854,7 @@ 

HTTP request

POST /api/products HTTP/1.1
 Content-Type: application/json
-Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDEyLCJleHAiOjE3MjIyNjk2MTJ9.51JkouAk3os1RvXpWPbMB15kEfjb6-N4hCgmFJDyx41jc0e2-BaohTOL7LFxElg8n1NNpO3n7S4d9hg_C90V7w
+Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMzU1MTA0LCJleHAiOjE3MjIzNTg3MDR9.89Yf45yjhDeU0k6CYLtjklTXJTJZ8K1aDtqgdLnpIzLkpfVMlu-JX84iwIzn47WsywraIJ-7AsJSS_5zs4TaQw
 Content-Length: 196
 Host: localhost:8080
 
@@ -910,7 +910,7 @@ 

HTTPie request

} ] }' | http POST 'http://localhost:8080/api/products' \ 'Content-Type:application/json' \ - 'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDEyLCJleHAiOjE3MjIyNjk2MTJ9.51JkouAk3os1RvXpWPbMB15kEfjb6-N4hCgmFJDyx41jc0e2-BaohTOL7LFxElg8n1NNpO3n7S4d9hg_C90V7w'
+ 'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMzU1MTA0LCJleHAiOjE3MjIzNTg3MDR9.89Yf45yjhDeU0k6CYLtjklTXJTJZ8K1aDtqgdLnpIzLkpfVMlu-JX84iwIzn47WsywraIJ-7AsJSS_5zs4TaQw'
@@ -1077,7 +1077,7 @@

Curl request

$ curl 'http://localhost:8080/api/products/1' -i -X PUT \
     -H 'Content-Type: application/json' \
-    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDExLCJleHAiOjE3MjIyNjk2MTF9.P8FNBO9WBDAGlz6J4ccAZRed-x7mhAg4h8YVxOvveyuH5v84ZOBtqBmXS4vEBy9o0l53d1Rx8mqdH7IQVfWcww' \
+    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMzU1MTA0LCJleHAiOjE3MjIzNTg3MDR9.89Yf45yjhDeU0k6CYLtjklTXJTJZ8K1aDtqgdLnpIzLkpfVMlu-JX84iwIzn47WsywraIJ-7AsJSS_5zs4TaQw' \
     -d '{
   "name" : "상품01",
   "price" : 1000,
@@ -1092,7 +1092,7 @@ 

HTTP request

PUT /api/products/1 HTTP/1.1
 Content-Type: application/json
-Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDExLCJleHAiOjE3MjIyNjk2MTF9.P8FNBO9WBDAGlz6J4ccAZRed-x7mhAg4h8YVxOvveyuH5v84ZOBtqBmXS4vEBy9o0l53d1Rx8mqdH7IQVfWcww
+Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMzU1MTA0LCJleHAiOjE3MjIzNTg3MDR9.89Yf45yjhDeU0k6CYLtjklTXJTJZ8K1aDtqgdLnpIzLkpfVMlu-JX84iwIzn47WsywraIJ-7AsJSS_5zs4TaQw
 Content-Length: 93
 Host: localhost:8080
 
@@ -1138,7 +1138,7 @@ 

HTTPie request

"imageUrl" : "https://via.placeholder.com/150" }' | http PUT 'http://localhost:8080/api/products/1' \ 'Content-Type:application/json' \ - 'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDExLCJleHAiOjE3MjIyNjk2MTF9.P8FNBO9WBDAGlz6J4ccAZRed-x7mhAg4h8YVxOvveyuH5v84ZOBtqBmXS4vEBy9o0l53d1Rx8mqdH7IQVfWcww'
+ 'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMzU1MTA0LCJleHAiOjE3MjIzNTg3MDR9.89Yf45yjhDeU0k6CYLtjklTXJTJZ8K1aDtqgdLnpIzLkpfVMlu-JX84iwIzn47WsywraIJ-7AsJSS_5zs4TaQw'
@@ -1280,7 +1280,7 @@

Curl request

$ curl 'http://localhost:8080/api/products/1' -i -X DELETE \
-    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDExLCJleHAiOjE3MjIyNjk2MTF9.P8FNBO9WBDAGlz6J4ccAZRed-x7mhAg4h8YVxOvveyuH5v84ZOBtqBmXS4vEBy9o0l53d1Rx8mqdH7IQVfWcww'
+ -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMzU1MTA0LCJleHAiOjE3MjIzNTg3MDR9.89Yf45yjhDeU0k6CYLtjklTXJTJZ8K1aDtqgdLnpIzLkpfVMlu-JX84iwIzn47WsywraIJ-7AsJSS_5zs4TaQw'
@@ -1289,7 +1289,7 @@

HTTP request

DELETE /api/products/1 HTTP/1.1
-Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDExLCJleHAiOjE3MjIyNjk2MTF9.P8FNBO9WBDAGlz6J4ccAZRed-x7mhAg4h8YVxOvveyuH5v84ZOBtqBmXS4vEBy9o0l53d1Rx8mqdH7IQVfWcww
+Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMzU1MTA0LCJleHAiOjE3MjIzNTg3MDR9.89Yf45yjhDeU0k6CYLtjklTXJTJZ8K1aDtqgdLnpIzLkpfVMlu-JX84iwIzn47WsywraIJ-7AsJSS_5zs4TaQw
 Host: localhost:8080
@@ -1307,7 +1307,7 @@

HTTPie request

$ http DELETE 'http://localhost:8080/api/products/1' \
-    'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDExLCJleHAiOjE3MjIyNjk2MTF9.P8FNBO9WBDAGlz6J4ccAZRed-x7mhAg4h8YVxOvveyuH5v84ZOBtqBmXS4vEBy9o0l53d1Rx8mqdH7IQVfWcww'
+ 'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMzU1MTA0LCJleHAiOjE3MjIzNTg3MDR9.89Yf45yjhDeU0k6CYLtjklTXJTJZ8K1aDtqgdLnpIzLkpfVMlu-JX84iwIzn47WsywraIJ-7AsJSS_5zs4TaQw'
@@ -1336,7 +1336,7 @@

Curl request

$ curl 'http://localhost:8080/api/products/1/order' -i -X POST \
     -H 'Content-Type: application/json' \
-    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDEyLCJleHAiOjE3MjIyNjk2MTJ9.51JkouAk3os1RvXpWPbMB15kEfjb6-N4hCgmFJDyx41jc0e2-BaohTOL7LFxElg8n1NNpO3n7S4d9hg_C90V7w' \
+    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMzU1MTA0LCJleHAiOjE3MjIzNTg3MDR9.89Yf45yjhDeU0k6CYLtjklTXJTJZ8K1aDtqgdLnpIzLkpfVMlu-JX84iwIzn47WsywraIJ-7AsJSS_5zs4TaQw' \
     -d '{
   "optionId" : 1,
   "quantity" : 1,
@@ -1351,7 +1351,7 @@ 

HTTP request

POST /api/products/1/order HTTP/1.1
 Content-Type: application/json
-Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDEyLCJleHAiOjE3MjIyNjk2MTJ9.51JkouAk3os1RvXpWPbMB15kEfjb6-N4hCgmFJDyx41jc0e2-BaohTOL7LFxElg8n1NNpO3n7S4d9hg_C90V7w
+Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMzU1MTA0LCJleHAiOjE3MjIzNTg3MDR9.89Yf45yjhDeU0k6CYLtjklTXJTJZ8K1aDtqgdLnpIzLkpfVMlu-JX84iwIzn47WsywraIJ-7AsJSS_5zs4TaQw
 Content-Length: 63
 Host: localhost:8080
 
@@ -1369,14 +1369,12 @@ 

HTTP response

HTTP/1.1 200 OK
 Content-Type: application/json
-Content-Length: 134
+Content-Length: 83
 
 {
   "productId" : 1,
   "optionId" : 1,
-  "optionStock" : 10,
-  "quantity" : 1,
-  "productName" : "상품01",
+  "quantity" : 10,
   "message" : "message"
 }
@@ -1392,7 +1390,7 @@

HTTPie request

"message" : "message" }' | http POST 'http://localhost:8080/api/products/1/order' \ 'Content-Type:application/json' \ - 'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDEyLCJleHAiOjE3MjIyNjk2MTJ9.51JkouAk3os1RvXpWPbMB15kEfjb6-N4hCgmFJDyx41jc0e2-BaohTOL7LFxElg8n1NNpO3n7S4d9hg_C90V7w'
+ 'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMzU1MTA0LCJleHAiOjE3MjIzNTg3MDR9.89Yf45yjhDeU0k6CYLtjklTXJTJZ8K1aDtqgdLnpIzLkpfVMlu-JX84iwIzn47WsywraIJ-7AsJSS_5zs4TaQw'
@@ -1432,7 +1430,7 @@

Request fields

quantity

Number

-

수량

+

주문 수량

message

@@ -1449,9 +1447,7 @@

Response body

{
   "productId" : 1,
   "optionId" : 1,
-  "optionStock" : 10,
-  "quantity" : 1,
-  "productName" : "상품01",
+  "quantity" : 10,
   "message" : "message"
 }
@@ -1484,19 +1480,9 @@

Response fields

상품 옵션 ID

-

optionStock

-

Number

-

상품 옵션 재고

- -

quantity

Number

-

수량

- - -

productName

-

String

-

상품명

+

주문 수량

message

@@ -1515,7 +1501,7 @@

Curl request

$ curl 'http://localhost:8080/api/products/wish' -i -X POST \
     -H 'Content-Type: application/json' \
-    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDExLCJleHAiOjE3MjIyNjk2MTF9.P8FNBO9WBDAGlz6J4ccAZRed-x7mhAg4h8YVxOvveyuH5v84ZOBtqBmXS4vEBy9o0l53d1Rx8mqdH7IQVfWcww' \
+    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMzU1MTA0LCJleHAiOjE3MjIzNTg3MDR9.89Yf45yjhDeU0k6CYLtjklTXJTJZ8K1aDtqgdLnpIzLkpfVMlu-JX84iwIzn47WsywraIJ-7AsJSS_5zs4TaQw' \
     -d '{
   "productId" : 1,
   "quantity" : 1
@@ -1529,7 +1515,7 @@ 

HTTP request

POST /api/products/wish HTTP/1.1
 Content-Type: application/json
-Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDExLCJleHAiOjE3MjIyNjk2MTF9.P8FNBO9WBDAGlz6J4ccAZRed-x7mhAg4h8YVxOvveyuH5v84ZOBtqBmXS4vEBy9o0l53d1Rx8mqdH7IQVfWcww
+Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMzU1MTA0LCJleHAiOjE3MjIzNTg3MDR9.89Yf45yjhDeU0k6CYLtjklTXJTJZ8K1aDtqgdLnpIzLkpfVMlu-JX84iwIzn47WsywraIJ-7AsJSS_5zs4TaQw
 Content-Length: 39
 Host: localhost:8080
 
@@ -1564,7 +1550,7 @@ 

HTTPie request

"quantity" : 1 }' | http POST 'http://localhost:8080/api/products/wish' \ 'Content-Type:application/json' \ - 'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDExLCJleHAiOjE3MjIyNjk2MTF9.P8FNBO9WBDAGlz6J4ccAZRed-x7mhAg4h8YVxOvveyuH5v84ZOBtqBmXS4vEBy9o0l53d1Rx8mqdH7IQVfWcww'
+ 'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMzU1MTA0LCJleHAiOjE3MjIzNTg3MDR9.89Yf45yjhDeU0k6CYLtjklTXJTJZ8K1aDtqgdLnpIzLkpfVMlu-JX84iwIzn47WsywraIJ-7AsJSS_5zs4TaQw'
@@ -1657,7 +1643,7 @@

Curl request

$ curl 'http://localhost:8080/api/products/1/options' -i -X POST \
     -H 'Content-Type: application/json' \
-    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDEyLCJleHAiOjE3MjIyNjk2MTJ9.51JkouAk3os1RvXpWPbMB15kEfjb6-N4hCgmFJDyx41jc0e2-BaohTOL7LFxElg8n1NNpO3n7S4d9hg_C90V7w' \
+    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMzU1MTA0LCJleHAiOjE3MjIzNTg3MDR9.89Yf45yjhDeU0k6CYLtjklTXJTJZ8K1aDtqgdLnpIzLkpfVMlu-JX84iwIzn47WsywraIJ-7AsJSS_5zs4TaQw' \
     -d '{
   "name" : "상품 옵션 01",
   "stock" : 100
@@ -1671,7 +1657,7 @@ 

HTTP request

POST /api/products/1/options HTTP/1.1
 Content-Type: application/json
-Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDEyLCJleHAiOjE3MjIyNjk2MTJ9.51JkouAk3os1RvXpWPbMB15kEfjb6-N4hCgmFJDyx41jc0e2-BaohTOL7LFxElg8n1NNpO3n7S4d9hg_C90V7w
+Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMzU1MTA0LCJleHAiOjE3MjIzNTg3MDR9.89Yf45yjhDeU0k6CYLtjklTXJTJZ8K1aDtqgdLnpIzLkpfVMlu-JX84iwIzn47WsywraIJ-7AsJSS_5zs4TaQw
 Content-Length: 50
 Host: localhost:8080
 
@@ -1708,7 +1694,7 @@ 

HTTPie request

"stock" : 100 }' | http POST 'http://localhost:8080/api/products/1/options' \ 'Content-Type:application/json' \ - 'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDEyLCJleHAiOjE3MjIyNjk2MTJ9.51JkouAk3os1RvXpWPbMB15kEfjb6-N4hCgmFJDyx41jc0e2-BaohTOL7LFxElg8n1NNpO3n7S4d9hg_C90V7w'
+ 'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMzU1MTA0LCJleHAiOjE3MjIzNTg3MDR9.89Yf45yjhDeU0k6CYLtjklTXJTJZ8K1aDtqgdLnpIzLkpfVMlu-JX84iwIzn47WsywraIJ-7AsJSS_5zs4TaQw'
@@ -1777,7 +1763,7 @@

Curl request

$ curl 'http://localhost:8080/api/products/1/options' -i -X GET \
-    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDExLCJleHAiOjE3MjIyNjk2MTF9.P8FNBO9WBDAGlz6J4ccAZRed-x7mhAg4h8YVxOvveyuH5v84ZOBtqBmXS4vEBy9o0l53d1Rx8mqdH7IQVfWcww'
+ -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMzU1MTA0LCJleHAiOjE3MjIzNTg3MDR9.89Yf45yjhDeU0k6CYLtjklTXJTJZ8K1aDtqgdLnpIzLkpfVMlu-JX84iwIzn47WsywraIJ-7AsJSS_5zs4TaQw'
@@ -1786,7 +1772,7 @@

HTTP request

GET /api/products/1/options HTTP/1.1
-Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDExLCJleHAiOjE3MjIyNjk2MTF9.P8FNBO9WBDAGlz6J4ccAZRed-x7mhAg4h8YVxOvveyuH5v84ZOBtqBmXS4vEBy9o0l53d1Rx8mqdH7IQVfWcww
+Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMzU1MTA0LCJleHAiOjE3MjIzNTg3MDR9.89Yf45yjhDeU0k6CYLtjklTXJTJZ8K1aDtqgdLnpIzLkpfVMlu-JX84iwIzn47WsywraIJ-7AsJSS_5zs4TaQw
 Host: localhost:8080
@@ -1814,7 +1800,7 @@

HTTPie request

$ http GET 'http://localhost:8080/api/products/1/options' \
-    'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDExLCJleHAiOjE3MjIyNjk2MTF9.P8FNBO9WBDAGlz6J4ccAZRed-x7mhAg4h8YVxOvveyuH5v84ZOBtqBmXS4vEBy9o0l53d1Rx8mqdH7IQVfWcww'
+ 'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMzU1MTA0LCJleHAiOjE3MjIzNTg3MDR9.89Yf45yjhDeU0k6CYLtjklTXJTJZ8K1aDtqgdLnpIzLkpfVMlu-JX84iwIzn47WsywraIJ-7AsJSS_5zs4TaQw'
@@ -1883,7 +1869,7 @@

Curl request

$ curl 'http://localhost:8080/api/products/1/options/1' -i -X PUT \
     -H 'Content-Type: application/json' \
-    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDExLCJleHAiOjE3MjIyNjk2MTF9.P8FNBO9WBDAGlz6J4ccAZRed-x7mhAg4h8YVxOvveyuH5v84ZOBtqBmXS4vEBy9o0l53d1Rx8mqdH7IQVfWcww' \
+    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMzU1MTA0LCJleHAiOjE3MjIzNTg3MDR9.89Yf45yjhDeU0k6CYLtjklTXJTJZ8K1aDtqgdLnpIzLkpfVMlu-JX84iwIzn47WsywraIJ-7AsJSS_5zs4TaQw' \
     -d '{
   "name" : "상품 옵션 01",
   "stock" : 100
@@ -1897,7 +1883,7 @@ 

HTTP request

PUT /api/products/1/options/1 HTTP/1.1
 Content-Type: application/json
-Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDExLCJleHAiOjE3MjIyNjk2MTF9.P8FNBO9WBDAGlz6J4ccAZRed-x7mhAg4h8YVxOvveyuH5v84ZOBtqBmXS4vEBy9o0l53d1Rx8mqdH7IQVfWcww
+Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMzU1MTA0LCJleHAiOjE3MjIzNTg3MDR9.89Yf45yjhDeU0k6CYLtjklTXJTJZ8K1aDtqgdLnpIzLkpfVMlu-JX84iwIzn47WsywraIJ-7AsJSS_5zs4TaQw
 Content-Length: 50
 Host: localhost:8080
 
@@ -1933,7 +1919,7 @@ 

HTTPie request

"stock" : 100 }' | http PUT 'http://localhost:8080/api/products/1/options/1' \ 'Content-Type:application/json' \ - 'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDExLCJleHAiOjE3MjIyNjk2MTF9.P8FNBO9WBDAGlz6J4ccAZRed-x7mhAg4h8YVxOvveyuH5v84ZOBtqBmXS4vEBy9o0l53d1Rx8mqdH7IQVfWcww'
+ 'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMzU1MTA0LCJleHAiOjE3MjIzNTg3MDR9.89Yf45yjhDeU0k6CYLtjklTXJTJZ8K1aDtqgdLnpIzLkpfVMlu-JX84iwIzn47WsywraIJ-7AsJSS_5zs4TaQw'
@@ -2031,7 +2017,7 @@

Curl request

$ curl 'http://localhost:8080/api/products/1/options/1' -i -X DELETE \
-    -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDExLCJleHAiOjE3MjIyNjk2MTF9.P8FNBO9WBDAGlz6J4ccAZRed-x7mhAg4h8YVxOvveyuH5v84ZOBtqBmXS4vEBy9o0l53d1Rx8mqdH7IQVfWcww'
+ -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMzU1MTA0LCJleHAiOjE3MjIzNTg3MDR9.89Yf45yjhDeU0k6CYLtjklTXJTJZ8K1aDtqgdLnpIzLkpfVMlu-JX84iwIzn47WsywraIJ-7AsJSS_5zs4TaQw'
@@ -2040,7 +2026,7 @@

HTTP request

DELETE /api/products/1/options/1 HTTP/1.1
-Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDExLCJleHAiOjE3MjIyNjk2MTF9.P8FNBO9WBDAGlz6J4ccAZRed-x7mhAg4h8YVxOvveyuH5v84ZOBtqBmXS4vEBy9o0l53d1Rx8mqdH7IQVfWcww
+Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMzU1MTA0LCJleHAiOjE3MjIzNTg3MDR9.89Yf45yjhDeU0k6CYLtjklTXJTJZ8K1aDtqgdLnpIzLkpfVMlu-JX84iwIzn47WsywraIJ-7AsJSS_5zs4TaQw
 Host: localhost:8080
@@ -2058,7 +2044,7 @@

HTTPie request

$ http DELETE 'http://localhost:8080/api/products/1/options/1' \
-    'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMjY2MDExLCJleHAiOjE3MjIyNjk2MTF9.P8FNBO9WBDAGlz6J4ccAZRed-x7mhAg4h8YVxOvveyuH5v84ZOBtqBmXS4vEBy9o0l53d1Rx8mqdH7IQVfWcww'
+ 'Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzIyMzU1MTA0LCJleHAiOjE3MjIzNTg3MDR9.89Yf45yjhDeU0k6CYLtjklTXJTJZ8K1aDtqgdLnpIzLkpfVMlu-JX84iwIzn47WsywraIJ-7AsJSS_5zs4TaQw'
@@ -2085,7 +2071,7 @@

Response body