Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

부산대 BE_문성재 2주차 과제 (2단계) #294

Open
wants to merge 24 commits into
base: student-p
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 18 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,18 @@
# spring-gift-wishlist
# spring-gift-wishlist

## step1

- 상품을 추가하거나 수정할 때 잘못된 값이 있다면 클라이언트에게 어떤 부분이 왜 잘못됐는지 전달하기
- 상품 이름은 공백을 포함하여 최대 15자까지 입력할 수 있다.
- 특수 문자
- 가능: ( ), [ ], +, -, &, /, _
- 그 외 특수 문자 사용 불가
- "카카오"가 포함된 문구는 담당 MD와 협의한 경우에만 사용할 수 있다.
---
## step2

사용자가 회원가입, 로그인, 추후 회원별 기능을 이용할 수 있도록 구현한다.

- 회원은 이메일과 비밀번호를 입력하여 가입한다.
- 토큰을 받으려면 이메일과 비밀번호를 보내야 하며, 가입한 이메일과 비밀번호가 일치하면 토큰이 발급된다.
- 토큰을 생성하는 방법에는 여러 가지가 있다. 방법 중 하나를 선택한다.
7 changes: 7 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,16 @@ dependencies {
implementation 'org.springframework.boot:spring-boot-starter-jdbc'
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.projectlombok:lombok:1.18.28'
runtimeOnly 'com.h2database:h2'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
implementation group: 'org.springframework.boot', name: 'spring-boot-starter-validation', version: '3.3.1'
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'
compileOnly 'io.jsonwebtoken:jjwt-api:0.12.6'
runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.12.6'
runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.12.6'
}

tasks.named('test') {
Expand Down
113 changes: 113 additions & 0 deletions src/main/java/gift/controller/ProductController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
package gift.controller;

import gift.domain.Product;
import gift.dto.ProductRequestDTO;
import gift.dto.ProductUpdateRequestDTO;
import gift.exception.ProductErrorCode;
import gift.exception.ProductException;
import gift.repository.ProductJdbcRepository;
import jakarta.validation.Valid;
import java.util.List;
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.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
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;

@Controller
public class ProductController {

private final ProductJdbcRepository productjdbcRepository;

@Autowired
public ProductController(ProductJdbcRepository productRepository) {
this.productjdbcRepository = productRepository;
}


// 상품 모두 조회
@GetMapping("/api/products")
public String responseAllProducts(Model model){
List<Product> productsList = productjdbcRepository.findAll();
model.addAttribute("products", productsList);
return "index";
}


// 상품 추가 폼
@GetMapping("/api/products/new-form")
public String newProductForm(Model model) {
model.addAttribute("productRequestDTO", new ProductRequestDTO());
return "new-product-form";
}

// 상품 추가
@PostMapping("/api/products")
public String addOneProduct(@Valid @ModelAttribute ProductRequestDTO requestDTO, BindingResult bindingResult, Model model) {
if (requestDTO.getName() != null && requestDTO.getName().contains("카카오")) {
bindingResult.rejectValue("name", "error.name", "카카오가 포함된 문구는 담당 MD와 협의한 경우에만 사용할 수 있습니다.");
}
if (bindingResult.hasErrors()) {
model.addAttribute("productRequestDTO", requestDTO);
model.addAttribute("org.springframework.validation.BindingResult.productRequestDTO", bindingResult);
return "new-product-form";
}
productjdbcRepository.save(Product.fromEntity(requestDTO));
return "redirect:/api/products";
}

// 상품 수정 폼
@GetMapping("/api/products/edit/{id}")
public String editProductForm(@PathVariable("id") long id, Model model) {
Product product = productjdbcRepository.findById(id)
.orElseThrow(() -> new ProductException(ProductErrorCode.ID_NOT_EXISTS));

if (product != null) {
model.addAttribute("product", product);
return "modify-product-form";
}
return "redirect:/api/products";
}

// 상품 수정
@PutMapping("/api/products/modify/{id}")
public String modifyOneProduct(@PathVariable("id") long id, @Valid @ModelAttribute ProductUpdateRequestDTO requestDTO, BindingResult bindingResult, Model model) {
if (bindingResult.hasErrors()) {
model.addAttribute("product", requestDTO);
model.addAttribute("org.springframework.validation.BindingResult.product", bindingResult);
return "modify-product-form";
}
productjdbcRepository.update(requestDTO);
return "redirect:/api/products";
}

// 상품 삭제
@DeleteMapping("/api/products/{id}")
public String deleteOneProduct(@PathVariable("id") long id) {
productjdbcRepository.findById(id)
.orElseThrow(() -> new ProductException(ProductErrorCode.ID_NOT_EXISTS));

productjdbcRepository.deleteById(id);
return "index";
}

// 선택된 상품 삭제
@DeleteMapping("/api/products/delete-selected")
public ResponseEntity<String> deleteSelectedProducts(@RequestBody List<Long> ids) {
for (Long id : ids) {
productjdbcRepository.findById(id)
.orElseThrow(() -> new ProductException(ProductErrorCode.ID_NOT_EXISTS));

productjdbcRepository.deleteById(id);
}
return new ResponseEntity<>("Selected products deleted successfully.", HttpStatus.OK);
}
}
36 changes: 36 additions & 0 deletions src/main/java/gift/controller/UserController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package gift.controller;

import gift.dto.UserLogin;
import gift.dto.UserSignUp;
import gift.service.UserService;
import lombok.AllArgsConstructor;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
@AllArgsConstructor
public class UserController {

private final UserService userService;
@ResponseBody
@PostMapping("/api/member/signUp")
public UserSignUp.Response signUp(@RequestBody UserSignUp.Request request){
String accessToken = userService.signUp(request);

return UserSignUp.Response.builder()
.accessToken(accessToken)
.build();
}

@ResponseBody
@PostMapping("/api/member/login")
public UserLogin.Response login(@RequestBody UserLogin.Request request){
String accessToken = userService.login(request);

return UserLogin.Response.builder()
.accessToken(accessToken)
.build();
}
}
41 changes: 41 additions & 0 deletions src/main/java/gift/domain/Product.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package gift.domain;


import gift.dto.ProductRequestDTO;
import lombok.Getter;
import lombok.Setter;

@Setter
@Getter
public class Product {
private long id;
private String name;
private int price;
private String imageUrl;

public Product() {
this.id = -1;
this.name = "";
this.price = 0;
this.imageUrl = "";
}

public Product(long id, String name, int price, String imageUrl) {
this.id = id;
this.name = name;
this.price = Math.max(price,0);
this.imageUrl = imageUrl;
}

public Product(String name, int price, String imageUrl) {
this.id = -1;
this.name = name;
this.price = Math.max(price, 0);
this.imageUrl = imageUrl;
}

public static Product fromEntity(ProductRequestDTO requestDTO){
return new Product(requestDTO.getName(), requestDTO.getPrice(), requestDTO.getImageUrl());
}
}

20 changes: 20 additions & 0 deletions src/main/java/gift/domain/User.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package gift.domain;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class User {

private long id;
private String email;
private String password;
private String accessToken;
}
18 changes: 18 additions & 0 deletions src/main/java/gift/dto/ProductErrorResponse.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package gift.dto;

import gift.exception.ProductErrorCode;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class ProductErrorResponse {
private ProductErrorCode errorCode;
private String errorMessage;
}
43 changes: 43 additions & 0 deletions src/main/java/gift/dto/ProductRequestDTO.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package gift.dto;

import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Pattern;
import jakarta.validation.constraints.Size;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.ToString;

@ToString
@AllArgsConstructor
@NoArgsConstructor
@Getter
@Setter
public class ProductRequestDTO {

@NotNull(message = "비울 수 없습니다")
@Size(min = 0, max = 15, message = "길이는 최대 15자 입니다")
@Pattern(regexp = "^[ㄱ-ㅎ가-힣a-zA-Z0-9()\\[\\]+\\-&/_]*$", message = "허용되지 않는 특수 문자가 포함되어 있습니다.")
private String name;

@NotNull(message = "비울 수 없습니다")
@Min(value = 0, message = "0 이상 이여야 합니다")
private int price;

@NotNull(message = "비울 수 없습니다")
private String imageUrl;

public String getName() {
return name;
}

public int getPrice() {
return price;
}

public String getImageUrl() {
return imageUrl;
}
}
42 changes: 42 additions & 0 deletions src/main/java/gift/dto/ProductUpdateRequestDTO.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package gift.dto;

import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Pattern;
import jakarta.validation.constraints.Size;
import lombok.Getter;
import lombok.Setter;

@Getter
@Setter
public class ProductUpdateRequestDTO {

private long id;
@NotNull(message = "비울 수 없습니다")
@Size(min = 0, max = 15, message = "길이는 최대 15자 입니다")
@Pattern(regexp = "^[ㄱ-ㅎ가-힣a-zA-Z0-9()\\[\\]+\\-&/_]*$", message = "허용되지 않는 특수 문자가 포함되어 있습니다.")
private String name;

@NotNull(message = "비울 수 없스니다")
@Min(value = 0, message = "0 이상이여야 합니다")
private int price;

@NotNull(message = "비울 수 없습니다")
private String imageUrl;

public long getId() {
return id;
}

public String getName() {
return name;
}

public int getPrice() {
return price;
}

public String getImageUrl() {
return imageUrl;
}
}
38 changes: 38 additions & 0 deletions src/main/java/gift/dto/UserLogin.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package gift.dto;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

public class UserLogin {

@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Builder
public static class Request{
private String email;
private String password;
}

@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Builder
public static class Response{
private String accessToken;
}

@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Builder
public static class ForbiddenResponse{
private String message;
}
}
Loading