diff --git a/build.gradle b/build.gradle index 2e506ad67..75c09cde1 100644 --- a/build.gradle +++ b/build.gradle @@ -2,11 +2,14 @@ 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 "3.3.2" } group = 'camp.nextstep.edu' version = '0.0.1-SNAPSHOT' - +configurations { + asciidoctorExt // (2) +} java { toolchain { languageVersion = JavaLanguageVersion.of(21) @@ -18,6 +21,9 @@ repositories { } dependencies { + asciidoctorExt 'org.springframework.restdocs:spring-restdocs-asciidoctor' + compileOnly 'org.projectlombok:lombok' + implementation 'com.google.code.gson:gson:2.8.9' implementation 'io.jsonwebtoken:jjwt-api:0.12.3' implementation 'io.jsonwebtoken:jjwt-impl:0.12.3' implementation 'io.jsonwebtoken:jjwt-jackson:0.12.3' @@ -28,11 +34,33 @@ dependencies { implementation 'org.springframework.boot:spring-boot-starter-thymeleaf' implementation 'org.springframework.boot:spring-boot-starter-web' implementation 'org.springframework.boot:spring-boot-starter-data-jpa' + annotationProcessor 'org.projectlombok:lombok' + 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' } +ext { // (5) + snippetsDir = file('build/generated-snippets') +} + tasks.named('test') { + outputs.dir snippetsDir useJUnitPlatform() } + + + +tasks.named('asciidoctor') { + inputs.dir snippetsDir // (8) + dependsOn test // (10) +} + +bootJar{ + dependsOn asciidoctor + copy { + from "${asciidoctor.outputDir}" + into 'src/main/resources/static/docs' + } +} diff --git a/src/main/java/gift/Application.java b/src/main/java/gift/Application.java index e75835cb1..30dfac413 100644 --- a/src/main/java/gift/Application.java +++ b/src/main/java/gift/Application.java @@ -5,8 +5,10 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.context.properties.ConfigurationPropertiesScan; +import org.springframework.data.jpa.repository.config.EnableJpaAuditing; import org.springframework.jdbc.core.JdbcTemplate; +@EnableJpaAuditing @ConfigurationPropertiesScan @SpringBootApplication public class Application { diff --git a/src/main/java/gift/Util/JWTUtil.java b/src/main/java/gift/Util/JWTUtil.java index d97525529..fca7bb5f6 100644 --- a/src/main/java/gift/Util/JWTUtil.java +++ b/src/main/java/gift/Util/JWTUtil.java @@ -8,7 +8,10 @@ import org.springframework.stereotype.Component; import javax.crypto.SecretKey; +import java.time.*; import java.util.Date; +import java.util.HashMap; +import java.util.Map; @Component @@ -23,13 +26,16 @@ public JWTUtil(@Value("${jwt.secretKey}") String jwtSecret, this.expirationMs = jwtExpirationMs; } - public String generateToken(User user) { - Date date = new Date(); - Date expire = new Date(date.getTime() + expirationMs); + public String generateToken(User user, String kakaoToken) { + LocalDateTime now = LocalDateTime.now(); + Instant nowInstant = now.atZone(ZoneId.systemDefault()).toInstant(); + Instant expireInstant = nowInstant.plus(Duration.ofMillis(expirationMs)); + return Jwts.builder() .subject(Integer.toString(user.getId())) - .issuedAt(date) - .expiration(expire) + .claim("kakaoToken", kakaoToken) + .issuedAt(Date.from(nowInstant)) + .expiration(Date.from(expireInstant)) .signWith(key) .compact(); } @@ -51,4 +57,14 @@ public Integer getUserIdFromToken(String token) { .getPayload() .getSubject()); } + + public String getKakaoTokenFromToken(String token) { + Claims claims = Jwts.parser() + .verifyWith(key) + .build() + .parseSignedClaims(token) + .getPayload(); + + return (String) claims.get("kakaoToken"); + } } diff --git a/src/main/java/gift/controller/CategoryController.java b/src/main/java/gift/controller/CategoryController.java index 713158ef5..6dc059e39 100644 --- a/src/main/java/gift/controller/CategoryController.java +++ b/src/main/java/gift/controller/CategoryController.java @@ -4,6 +4,7 @@ import gift.dto.category.CategoryDTO; import gift.entity.Category; import gift.service.CategoryService; +import lombok.RequiredArgsConstructor; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; @@ -14,9 +15,9 @@ import org.springframework.web.bind.annotation.*; @Controller +@RequiredArgsConstructor public class CategoryController { - @Autowired - CategoryService categoryService; + private final CategoryService categoryService; @GetMapping("/api/category") @ResponseBody @@ -28,18 +29,18 @@ public Page getCategory(@RequestParam(value = "page", defaultValue @PostMapping("/api/category") @ResponseStatus(HttpStatus.CREATED) public void addCategory(@RequestBody Category category) { - categoryService.save(category); + categoryService.saveCategory(category); } @PutMapping("/api/category") @ResponseStatus(HttpStatus.OK) public void updateCategory(@RequestBody CategoryDTO categoryDTO) { - categoryService.update(categoryDTO); + categoryService.updateCategory(categoryDTO); } @DeleteMapping("/api/category/{id}") public String deleteCategory(@PathVariable("id") int id) { - categoryService.delete(id); + categoryService.deleteCategory(id); return "redirect:/api/category"; } diff --git a/src/main/java/gift/controller/OptionController.java b/src/main/java/gift/controller/OptionController.java index 658a62fd7..a42720532 100644 --- a/src/main/java/gift/controller/OptionController.java +++ b/src/main/java/gift/controller/OptionController.java @@ -1,46 +1,50 @@ package gift.controller; +import com.fasterxml.jackson.core.JsonProcessingException; import gift.dto.option.OptionQuantityDTO; +import gift.dto.option.OrderResponseDTO; import gift.dto.option.SaveOptionDTO; import gift.dto.option.UpdateOptionDTO; import gift.service.OptionService; +import lombok.RequiredArgsConstructor; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.*; @Controller +@RequiredArgsConstructor public class OptionController { - @Autowired - OptionService optionService; + private final OptionService optionService; @PostMapping("/api/option") @ResponseStatus(HttpStatus.CREATED) public void addOption(@RequestBody SaveOptionDTO saveOptionDTO) { - optionService.add(saveOptionDTO); + optionService.saveOption(saveOptionDTO); } @DeleteMapping("/api/option/{id}") @ResponseStatus(HttpStatus.CREATED) public void deleteOption(@PathVariable int id) { - optionService.delete(id); + optionService.deleteOption(id); } @PutMapping("/api/option") @ResponseStatus(HttpStatus.OK) public void updateOption(@RequestBody UpdateOptionDTO updateOptionDTO) { - optionService.update(updateOptionDTO); + optionService.updateOption(updateOptionDTO); } @PostMapping("/api/option/refill") @ResponseStatus(HttpStatus.OK) public void refill(@RequestBody OptionQuantityDTO optionQuantityDTO) { - optionService.refill(optionQuantityDTO); + optionService.refillQuantity(optionQuantityDTO); } @PostMapping("/api/option/order") @ResponseStatus(HttpStatus.OK) - public void order(@RequestBody OptionQuantityDTO optionQuantityDTO) { - optionService.order(optionQuantityDTO); + @ResponseBody + public OrderResponseDTO order(@RequestHeader("Authorization") String token, @RequestBody OptionQuantityDTO optionQuantityDTO) { + return optionService.order(optionQuantityDTO, token); } } diff --git a/src/main/java/gift/controller/ProductAdminController.java b/src/main/java/gift/controller/ProductAdminController.java index f2a00984c..9f392630e 100644 --- a/src/main/java/gift/controller/ProductAdminController.java +++ b/src/main/java/gift/controller/ProductAdminController.java @@ -4,6 +4,7 @@ import gift.dto.product.ProductWithOptionDTO; import gift.service.ProductService; import jakarta.servlet.http.HttpServletRequest; +import lombok.RequiredArgsConstructor; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; @@ -18,9 +19,9 @@ @Controller +@RequiredArgsConstructor public class ProductAdminController { - @Autowired - private ProductService productService; + private final ProductService productService; @GetMapping("/admin/products") public String adminProducts(HttpServletRequest request, @RequestParam(value = "page", defaultValue = "0") int pageNum) { diff --git a/src/main/java/gift/controller/ProductController.java b/src/main/java/gift/controller/ProductController.java index 92b3a479a..7b638d0bb 100644 --- a/src/main/java/gift/controller/ProductController.java +++ b/src/main/java/gift/controller/ProductController.java @@ -6,6 +6,7 @@ import gift.dto.product.ShowProductDTO; import gift.entity.Product; import gift.service.ProductService; +import lombok.RequiredArgsConstructor; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; @@ -19,10 +20,9 @@ @Controller +@RequiredArgsConstructor public class ProductController { - @Autowired - private ProductService productService; - + private final ProductService productService; @ResponseStatus(HttpStatus.OK) @ResponseBody @@ -49,8 +49,8 @@ public String deleteProduct(@PathVariable int Id) { @ResponseStatus(HttpStatus.OK) @PutMapping("/api/products") - public String modifyProduct(@RequestBody ModifyProductDTO product) { - productService.modifyProduct(product); + public String updateProduct(@RequestBody ModifyProductDTO product) { + productService.updateProduct(product); return "redirect:api/products"; } diff --git a/src/main/java/gift/controller/UserController.java b/src/main/java/gift/controller/UserController.java index 1af8fa1ce..cd40c1441 100644 --- a/src/main/java/gift/controller/UserController.java +++ b/src/main/java/gift/controller/UserController.java @@ -8,22 +8,18 @@ import gift.dto.user.SignUpDTO; import gift.dto.user.Token; -import gift.service.KakaoService; import gift.service.UserService; +import lombok.RequiredArgsConstructor; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.*; @Controller +@RequiredArgsConstructor public class UserController { - @Autowired - UserService userService; - @Autowired - KakaoService kakaoService; - @Autowired - ObjectMapper objectMapper; + private final UserService userService; + private final ObjectMapper objectMapper; @ResponseStatus(HttpStatus.OK) @GetMapping("/signup") @@ -53,10 +49,10 @@ public String signIn(@RequestBody LoginDTO loginDTO) throws JsonProcessingExcept return objectMapper.writeValueAsString(token); } - @GetMapping("/api/kakao/token") + @PostMapping("/api/kakao/login") @ResponseBody - public String getToken(){ - return kakaoService.getToken(); + public Token kakakLogin() { + return userService.kakaoLogin(); } } diff --git a/src/main/java/gift/controller/WishListController.java b/src/main/java/gift/controller/WishListController.java index c229f6cf5..3b3c4ba42 100644 --- a/src/main/java/gift/controller/WishListController.java +++ b/src/main/java/gift/controller/WishListController.java @@ -4,6 +4,7 @@ import gift.dto.product.ShowProductDTO; import gift.service.WishListService; +import lombok.RequiredArgsConstructor; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; @@ -16,14 +17,14 @@ @Controller +@RequiredArgsConstructor public class WishListController { - @Autowired - WishListService wishListService; + private final WishListService wishListService; @ResponseStatus(HttpStatus.CREATED) @PostMapping("/api/wishlist/{product_id}") public String addWishList(@RequestHeader("Authorization") String token, @PathVariable int product_id) { - wishListService.add(token, product_id); + wishListService.saveWishList(token, product_id); return "redirect:/wishlist"; } diff --git a/src/main/java/gift/dto/option/OptionQuantityDTO.java b/src/main/java/gift/dto/option/OptionQuantityDTO.java index de5ad9572..161d14b6e 100644 --- a/src/main/java/gift/dto/option/OptionQuantityDTO.java +++ b/src/main/java/gift/dto/option/OptionQuantityDTO.java @@ -1,4 +1,4 @@ package gift.dto.option; -public record OptionQuantityDTO(int id, int quantity) { +public record OptionQuantityDTO(int optionId, int quantity, String message) { } diff --git a/src/main/java/gift/dto/option/OrderResponseDTO.java b/src/main/java/gift/dto/option/OrderResponseDTO.java new file mode 100644 index 000000000..e8eb49469 --- /dev/null +++ b/src/main/java/gift/dto/option/OrderResponseDTO.java @@ -0,0 +1,6 @@ +package gift.dto.option; + +import java.time.LocalDateTime; + +public record OrderResponseDTO(int optionId, int quantity, LocalDateTime orderDateTime, String message) { +} diff --git a/src/main/java/gift/dto/user/KakaoToken.java b/src/main/java/gift/dto/user/KakaoToken.java new file mode 100644 index 000000000..3bbac39d9 --- /dev/null +++ b/src/main/java/gift/dto/user/KakaoToken.java @@ -0,0 +1,6 @@ +package gift.dto.user; + +public record KakaoToken(String access_token, String token_type, String refresh_token, Integer expires_in, String scope, + Integer refresh_token_expires_in) { + +} diff --git a/src/main/java/gift/entity/KakaoUser.java b/src/main/java/gift/entity/KakaoUser.java new file mode 100644 index 000000000..4ce936b6e --- /dev/null +++ b/src/main/java/gift/entity/KakaoUser.java @@ -0,0 +1,35 @@ +package gift.entity; + +import jakarta.persistence.*; + +@Entity +public class KakaoUser { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + int id; + + Long kakaoUserId; + + @OneToOne(fetch = FetchType.LAZY) + User user; + + public KakaoUser() { + } + + public KakaoUser(Long kakaoUserId, User user) { + this.kakaoUserId = kakaoUserId; + this.user = user; + } + + public int getId() { + return id; + } + + public Long getKakaoUserId() { + return kakaoUserId; + } + + public User getUser() { + return user; + } +} diff --git a/src/main/java/gift/entity/Option.java b/src/main/java/gift/entity/Option.java index 52ee0dd62..cb472d6b2 100644 --- a/src/main/java/gift/entity/Option.java +++ b/src/main/java/gift/entity/Option.java @@ -18,7 +18,6 @@ public class Option { Product product; @Pattern(regexp = "^[a-zA-Z0-9()\\[\\]+\\-&/_]+$", message = "특수기호 안됨") - String option; @Min(0) diff --git a/src/main/java/gift/entity/Order.java b/src/main/java/gift/entity/Order.java new file mode 100644 index 000000000..b73941887 --- /dev/null +++ b/src/main/java/gift/entity/Order.java @@ -0,0 +1,63 @@ +package gift.entity; + +import gift.dto.option.OptionQuantityDTO; +import gift.dto.option.OrderResponseDTO; +import jakarta.persistence.*; +import org.springframework.data.annotation.CreatedDate; +import org.springframework.data.jpa.domain.support.AuditingEntityListener; + +import java.time.LocalDateTime; + +@Entity(name = "order_tb") +@EntityListeners(AuditingEntityListener.class) +public class Order { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + int id; + + @ManyToOne(fetch = FetchType.LAZY) + Option option; + int quantity; + String message; + + @CreatedDate + LocalDateTime orderDateTime; + + @ManyToOne(fetch = FetchType.LAZY) + User user; + + public Order() { + + } + + public int getId() { + return id; + } + + public int getQuantity() { + return quantity; + } + + public Option getOption() { + return option; + } + + public String getMessage() { + return message; + } + + public LocalDateTime getOrderDateTime() { + return orderDateTime; + } + + public Order(OptionQuantityDTO optionQuantityDTO, Option option, User user) { + this.option = option; + this.quantity = optionQuantityDTO.quantity(); + this.message = optionQuantityDTO.message(); + this.user = user; + } + + public OrderResponseDTO toResponseDTO() { + return new OrderResponseDTO(option.getId(), quantity, orderDateTime, message); + } +} diff --git a/src/main/java/gift/entity/Product.java b/src/main/java/gift/entity/Product.java index 6c8fd6fda..18d10c958 100644 --- a/src/main/java/gift/entity/Product.java +++ b/src/main/java/gift/entity/Product.java @@ -3,8 +3,11 @@ import com.fasterxml.jackson.annotation.JsonAutoDetect; import gift.dto.product.ModifyProductDTO; +import gift.exception.exception.BadRequestException; +import gift.exception.exception.UnAuthException; import jakarta.persistence.*; import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotEmpty; import jakarta.validation.constraints.Pattern; import jakarta.validation.constraints.Size; @@ -21,6 +24,7 @@ public class Product { @NotBlank(message = "이름 공백 안됨") @Size(max = 15, message = "15글자까지만 가능") @Pattern(regexp = "^[a-zA-Z0-9()\\[\\]+\\-&/_]+$", message = "특수기호 안됨") + @Pattern(regexp = "^(?!.*카카오).*", message = "카카오는 md와 상담") @Column(nullable = false) String name; @Column(nullable = false) diff --git a/src/main/java/gift/entity/User.java b/src/main/java/gift/entity/User.java index d0564f42f..bd32f5195 100644 --- a/src/main/java/gift/entity/User.java +++ b/src/main/java/gift/entity/User.java @@ -12,14 +12,15 @@ public class User { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private int id; - @Column(unique = true, nullable = false) + @Column(unique = true) private String email; - @Column(nullable = false) private String password; @OneToMany(mappedBy = "user") List wishlist = new ArrayList<>(); + @OneToMany(mappedBy = "user") + List orders = new ArrayList<>(); public User() { } @@ -33,6 +34,10 @@ public int getId() { return id; } + public String getEmail() { + return email; + } + public String getPassword() { return password; } @@ -44,5 +49,13 @@ public void addWishlist(WishList wishlist) { public void deleteWishlist(WishList wishList) { this.wishlist.remove(wishList); } + + public void addOrder(Order order) { + this.orders.add(order); + } + + public void deleteOrder(Order order) { + this.orders.remove(order); + } } diff --git a/src/main/java/gift/exception/MyExceptionHandler.java b/src/main/java/gift/exception/MyExceptionHandler.java index e0c0efed6..029739e78 100644 --- a/src/main/java/gift/exception/MyExceptionHandler.java +++ b/src/main/java/gift/exception/MyExceptionHandler.java @@ -24,42 +24,42 @@ public static String back(String msg) { @ResponseStatus(HttpStatus.NOT_FOUND) @ResponseBody @ExceptionHandler(NotFoundException.class) - public String exception(NotFoundException e) { + public String NotFound(NotFoundException e) { return e.getMessage(); } @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) @ResponseBody @ExceptionHandler(ServerInternalException.class) - public String exception(ServerInternalException e) { - return "서버 오류"; + public String ServerInternal(ServerInternalException e) { + return e.getMessage(); } @ResponseStatus(HttpStatus.UNAUTHORIZED) @ResponseBody @ExceptionHandler(UnAuthException.class) - public String exception(UnAuthException e) { + public String UnAuthorized(UnAuthException e) { return e.getMessage(); } @ResponseStatus(HttpStatus.BAD_REQUEST) @ResponseBody @ExceptionHandler(BadRequestException.class) - public String exception(BadRequestException e) { + public String BadRequest(BadRequestException e) { return e.getMessage(); } @ResponseStatus(HttpStatus.BAD_REQUEST) @ResponseBody @ExceptionHandler(ConstraintViolationException.class) - public String exception(ConstraintViolationException e) { + public String ConstaintViolation(ConstraintViolationException e) { return "제약사항 요구 어긋남"; } @ResponseStatus(HttpStatus.UNAUTHORIZED) @ResponseBody @ExceptionHandler(ExpiredJwtException.class) - public String exception(ExpiredJwtException e) { + public String ExpiredJwt(ExpiredJwtException e) { return "인증이 잘못됨"; } } diff --git a/src/main/java/gift/repository/KakaoUserRepository.java b/src/main/java/gift/repository/KakaoUserRepository.java new file mode 100644 index 000000000..5c3474c38 --- /dev/null +++ b/src/main/java/gift/repository/KakaoUserRepository.java @@ -0,0 +1,15 @@ +package gift.repository; + +import gift.entity.KakaoUser; +import gift.entity.User; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.stereotype.Repository; + +import java.util.Optional; + +@Repository +public interface KakaoUserRepository extends JpaRepository { + @Query("select k.user from KakaoUser k where k.kakaoUserId=:kakaoUserId") + Optional findByKakaoUserId(Long kakaoUserId); +} diff --git a/src/main/java/gift/repository/OptionRepository.java b/src/main/java/gift/repository/OptionRepository.java index 918feb393..a3192b58c 100644 --- a/src/main/java/gift/repository/OptionRepository.java +++ b/src/main/java/gift/repository/OptionRepository.java @@ -19,4 +19,7 @@ public interface OptionRepository extends JpaRepository { @Query("select o from Option o where o.option = :option") Optional