diff --git a/README.md b/README.md index 8376bdfff..432b85a8a 100644 --- a/README.md +++ b/README.md @@ -1 +1,18 @@ -# spring-gift-wishlist \ No newline at end of file +# spring-gift-wishlist + +## step1 + +- 상품을 추가하거나 수정할 때 잘못된 값이 있다면 클라이언트에게 어떤 부분이 왜 잘못됐는지 전달하기 +- 상품 이름은 공백을 포함하여 최대 15자까지 입력할 수 있다. +- 특수 문자 + - 가능: ( ), [ ], +, -, &, /, _ + - 그 외 특수 문자 사용 불가 + - "카카오"가 포함된 문구는 담당 MD와 협의한 경우에만 사용할 수 있다. +--- +## step2 + +사용자가 회원가입, 로그인, 추후 회원별 기능을 이용할 수 있도록 구현한다. + +- 회원은 이메일과 비밀번호를 입력하여 가입한다. +- 토큰을 받으려면 이메일과 비밀번호를 보내야 하며, 가입한 이메일과 비밀번호가 일치하면 토큰이 발급된다. +- 토큰을 생성하는 방법에는 여러 가지가 있다. 방법 중 하나를 선택한다. \ No newline at end of file diff --git a/build.gradle b/build.gradle index df7db9334..14b8633d8 100644 --- a/build.gradle +++ b/build.gradle @@ -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') { diff --git a/src/main/java/gift/controller/ProductController.java b/src/main/java/gift/controller/ProductController.java new file mode 100644 index 000000000..d77300138 --- /dev/null +++ b/src/main/java/gift/controller/ProductController.java @@ -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 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 deleteSelectedProducts(@RequestBody List 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); + } +} diff --git a/src/main/java/gift/controller/UserController.java b/src/main/java/gift/controller/UserController.java new file mode 100644 index 000000000..35f037b73 --- /dev/null +++ b/src/main/java/gift/controller/UserController.java @@ -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(); + } +} diff --git a/src/main/java/gift/domain/Product.java b/src/main/java/gift/domain/Product.java new file mode 100644 index 000000000..a2329182f --- /dev/null +++ b/src/main/java/gift/domain/Product.java @@ -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()); + } +} + diff --git a/src/main/java/gift/domain/User.java b/src/main/java/gift/domain/User.java new file mode 100644 index 000000000..28f3ad4de --- /dev/null +++ b/src/main/java/gift/domain/User.java @@ -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; +} diff --git a/src/main/java/gift/dto/ProductErrorResponse.java b/src/main/java/gift/dto/ProductErrorResponse.java new file mode 100644 index 000000000..0cefa5855 --- /dev/null +++ b/src/main/java/gift/dto/ProductErrorResponse.java @@ -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; +} diff --git a/src/main/java/gift/dto/ProductRequestDTO.java b/src/main/java/gift/dto/ProductRequestDTO.java new file mode 100644 index 000000000..8fd737956 --- /dev/null +++ b/src/main/java/gift/dto/ProductRequestDTO.java @@ -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; + } +} diff --git a/src/main/java/gift/dto/ProductUpdateRequestDTO.java b/src/main/java/gift/dto/ProductUpdateRequestDTO.java new file mode 100644 index 000000000..5165b2490 --- /dev/null +++ b/src/main/java/gift/dto/ProductUpdateRequestDTO.java @@ -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; + } +} diff --git a/src/main/java/gift/dto/UserLogin.java b/src/main/java/gift/dto/UserLogin.java new file mode 100644 index 000000000..03969d38e --- /dev/null +++ b/src/main/java/gift/dto/UserLogin.java @@ -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; + } +} diff --git a/src/main/java/gift/dto/UserSignUp.java b/src/main/java/gift/dto/UserSignUp.java new file mode 100644 index 000000000..c87aba3af --- /dev/null +++ b/src/main/java/gift/dto/UserSignUp.java @@ -0,0 +1,29 @@ +package gift.dto; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +public class UserSignUp { + + @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; + } +} diff --git a/src/main/java/gift/exception/GlobalExceptionHandler.java b/src/main/java/gift/exception/GlobalExceptionHandler.java new file mode 100644 index 000000000..3159c0f96 --- /dev/null +++ b/src/main/java/gift/exception/GlobalExceptionHandler.java @@ -0,0 +1,50 @@ +package gift.exception; + +import gift.dto.UserLogin; +import java.util.HashMap; +import java.util.Map; +import jdk.jshell.spi.ExecutionControl; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.ui.Model; +import org.springframework.validation.FieldError; +import org.springframework.web.bind.MethodArgumentNotValidException; +import org.springframework.web.bind.annotation.ControllerAdvice; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.ResponseStatus; +import org.springframework.web.servlet.ModelAndView; + +@ControllerAdvice +public class GlobalExceptionHandler { + + @ExceptionHandler(ProductException.class) + public ModelAndView handleProductException(ProductException e, Model model){ + model.addAttribute("errorCode", e.getProductErrorCode()); + model.addAttribute("errorMessage", e.getDetailMessage()); + return new ModelAndView("error"); + } + + @ExceptionHandler(MethodArgumentNotValidException.class) + @ResponseStatus(HttpStatus.BAD_REQUEST) + public ResponseEntity> handleValidationExceptions(MethodArgumentNotValidException e) { + Map errors = new HashMap<>(); + + e.getBindingResult().getAllErrors().forEach((error) -> { + String fieldName = ((FieldError) error).getField(); + String errorMessage = error.getDefaultMessage(); + errors.put(fieldName, errorMessage); + }); + + return new ResponseEntity<>(errors, HttpStatus.BAD_REQUEST); + } + + @ExceptionHandler(UserException.Forbidden.class) + @ResponseStatus(HttpStatus.FORBIDDEN) + @ResponseBody + public UserLogin.ForbiddenResponse handleForbiddenLoginException(UserException.Forbidden e){ + return UserLogin.ForbiddenResponse.builder() + .message(e.getDetailMessage()) + .build(); + } +} diff --git a/src/main/java/gift/exception/ProductErrorCode.java b/src/main/java/gift/exception/ProductErrorCode.java new file mode 100644 index 000000000..0646658b8 --- /dev/null +++ b/src/main/java/gift/exception/ProductErrorCode.java @@ -0,0 +1,19 @@ +package gift.exception; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public enum ProductErrorCode { + INVALID_PRODUCT_LENGTH("상품 이름은 공백을 포함하여 최대 15자까지 입력할 수 있습니다"), + CONTAINS_FORBIDDEN_SPECIAL_CHAR("( ), [ ], +, -, &, /, _ 특수문자만 사용 가능합니다"), + CONTAINS_KAKAO("상품 이름에 '카카오'가 포함된 경우 담당 MD와 협의가 필요합니다"), + ID_NOT_EXISTS("해당하는 상품이 존재하지 않습니다"), + + INTERNAL_SERVER_ERROR("서버에 오류가 발생했습니다."), + INVALID_REQUEST("잘못된 요청입니다.") + ; + + private final String message; +} diff --git a/src/main/java/gift/exception/ProductException.java b/src/main/java/gift/exception/ProductException.java new file mode 100644 index 000000000..fffc24806 --- /dev/null +++ b/src/main/java/gift/exception/ProductException.java @@ -0,0 +1,26 @@ +package gift.exception; + +public class ProductException extends RuntimeException{ + private ProductErrorCode productErrorCode; + private String detailMessage; + + public ProductException(ProductErrorCode productErrorCode) { + super(productErrorCode.getMessage()); + this.productErrorCode = productErrorCode; + this.detailMessage = productErrorCode.getMessage(); + } + + public ProductException(ProductErrorCode productErrorCode, String detailMessage) { + super(detailMessage); + this.productErrorCode = productErrorCode; + this.detailMessage = detailMessage; + } + + public ProductErrorCode getProductErrorCode() { + return productErrorCode; + } + + public String getDetailMessage() { + return detailMessage; + } +} diff --git a/src/main/java/gift/exception/UserErrorCode.java b/src/main/java/gift/exception/UserErrorCode.java new file mode 100644 index 000000000..f6c388d1c --- /dev/null +++ b/src/main/java/gift/exception/UserErrorCode.java @@ -0,0 +1,12 @@ +package gift.exception; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public enum UserErrorCode { + + WRONG_LOGIN("잘못된 로그인 정보입니다"); + private String message; +} diff --git a/src/main/java/gift/exception/UserException.java b/src/main/java/gift/exception/UserException.java new file mode 100644 index 000000000..7875df0a3 --- /dev/null +++ b/src/main/java/gift/exception/UserException.java @@ -0,0 +1,23 @@ +package gift.exception; + +import lombok.Getter; + +public class UserException { + + @Getter + public static class Forbidden extends RuntimeException{ + private UserErrorCode errorCode; + private String detailMessage; + + public Forbidden(UserErrorCode errorCode){ + super(errorCode.getMessage()); + this.errorCode = errorCode; + this.detailMessage = errorCode.getMessage(); + } + public Forbidden(UserErrorCode errorCode, String detailMessage){ + super(detailMessage); + this.errorCode = errorCode; + this.detailMessage = detailMessage; + } + } +} diff --git a/src/main/java/gift/repository/ProductJdbcRepository.java b/src/main/java/gift/repository/ProductJdbcRepository.java new file mode 100644 index 000000000..f5b8c6046 --- /dev/null +++ b/src/main/java/gift/repository/ProductJdbcRepository.java @@ -0,0 +1,65 @@ +package gift.repository; + +import gift.domain.Product; +import gift.dto.ProductUpdateRequestDTO; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.List; +import java.util.Optional; +import org.springframework.dao.EmptyResultDataAccessException; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.core.RowMapper; +import org.springframework.stereotype.Repository; + +@Repository +public class ProductJdbcRepository { + private final JdbcTemplate jdbcTemplate; + + public ProductJdbcRepository(JdbcTemplate jdbcTemplate) { + this.jdbcTemplate = jdbcTemplate; + } + + public void save(Product product) { + String sql = "INSERT INTO products_tb (name, price, imageUrl) VALUES (?, ?, ?)"; + jdbcTemplate.update(sql, product.getName(), product.getPrice(), product.getImageUrl()); + } + + public List findAll() { + String sql = "SELECT * FROM products_tb"; + return jdbcTemplate.query(sql, productRowMapper()); + } + + public Optional findById(Long id) { + String sql = "SELECT * FROM products_tb WHERE id = ?"; + try { + Product product = jdbcTemplate.queryForObject(sql, productRowMapper(), id); + return Optional.ofNullable(product); + } catch (EmptyResultDataAccessException e) { + return Optional.empty(); + } + } + + public void update(ProductUpdateRequestDTO product) { + String sql = "UPDATE products_tb SET name = ?, price = ?, imageUrl = ? WHERE id = ?"; + jdbcTemplate.update(sql, product.getName(), product.getPrice(), product.getImageUrl(), product.getId()); + } + + public void deleteById(Long id) { + String sql = "DELETE FROM products_tb WHERE id = ?"; + jdbcTemplate.update(sql, id); + } + + private RowMapper productRowMapper() { + return new RowMapper() { + @Override + public Product mapRow(ResultSet rs, int rowNum) throws SQLException { + Product product = new Product(); + product.setId(rs.getLong("id")); + product.setName(rs.getString("name")); + product.setPrice(rs.getInt("price")); + product.setImageUrl(rs.getString("imageUrl")); + return product; + } + }; + } +} diff --git a/src/main/java/gift/repository/UserRepository.java b/src/main/java/gift/repository/UserRepository.java new file mode 100644 index 000000000..dee5e4ac6 --- /dev/null +++ b/src/main/java/gift/repository/UserRepository.java @@ -0,0 +1,51 @@ +package gift.repository; + +import gift.domain.Product; +import gift.domain.User; +import gift.dto.UserLogin; +import gift.dto.UserSignUp.Request; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.Optional; +import lombok.AllArgsConstructor; +import org.springframework.dao.EmptyResultDataAccessException; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.core.RowMapper; +import org.springframework.stereotype.Repository; + +@Repository +@AllArgsConstructor +public class UserRepository { + + private final JdbcTemplate jdbcTemplate; + + public void save(Request request, String uuid) { + String sql = "INSERT INTO user_tb (email, password, accessToken) vALUES(?, ?, ?)"; + jdbcTemplate.update(sql, request.getEmail(), request.getPassword(), uuid); + } + + public Optional findByEmailAndPassword(UserLogin.Request request) { + String sql = "SELECT * FROM user_tb WHERE email=? AND password=?"; + + try { + User user = jdbcTemplate.queryForObject(sql, userRowMapper(), request.getEmail(), request.getPassword()); + return Optional.ofNullable(user.getAccessToken()); + } catch (EmptyResultDataAccessException e) { + return Optional.empty(); + } + } + + private static RowMapper userRowMapper() { + return new RowMapper() { + @Override + public User mapRow(ResultSet rs, int rowNum) throws SQLException { + return User.builder() + .id(rs.getLong("id")) + .email(rs.getString("email")) + .password(rs.getString("password")) + .accessToken(rs.getString("accessToken")) + .build(); + } + }; + } +} diff --git a/src/main/java/gift/service/ProductService.java b/src/main/java/gift/service/ProductService.java new file mode 100644 index 000000000..3be71fac7 --- /dev/null +++ b/src/main/java/gift/service/ProductService.java @@ -0,0 +1,6 @@ +package gift.service; + +public class ProductService { + + +} diff --git a/src/main/java/gift/service/UserService.java b/src/main/java/gift/service/UserService.java new file mode 100644 index 000000000..de560ed91 --- /dev/null +++ b/src/main/java/gift/service/UserService.java @@ -0,0 +1,29 @@ +package gift.service; + +import gift.dto.UserLogin; +import gift.dto.UserSignUp.Request; +import gift.exception.UserErrorCode; +import gift.exception.UserException; +import gift.repository.UserRepository; +import lombok.AllArgsConstructor; +import org.springframework.stereotype.Service; + +@Service +@AllArgsConstructor +public class UserService { + + private final UserRepository userRepository; + public String signUp(Request request) { + // todo 이미 존재하는 이메일인지 검증 추가 + + String _email = "_".concat(request.getEmail()); + userRepository.save(request, _email); + return _email; + } + + public String login(UserLogin.Request request) { + String accessToken = userRepository.findByEmailAndPassword(request) + .orElseThrow(() -> new UserException.Forbidden(UserErrorCode.WRONG_LOGIN)); + return accessToken; + } +} diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 3d16b65f4..e588d4352 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1 +1,15 @@ spring.application.name=spring-gift +# h2-console ??? ?? +spring.h2.console.enabled=true +# db url +spring.datasource.url=jdbc:h2:mem:test +spring.datasource.username=sa + +# ??? ? ??? ????? ?? ?? +spring.sql.init.schema-locations=classpath:db/schema.sql +spring.sql.init.data-locations=classpath:db/data.sql + +spring.mvc.hiddenmethod.filter.enabled=true +# +## ?? SQL ?????? ??? +#spring.sql.init.mode=always \ No newline at end of file diff --git a/src/main/resources/db/data.sql b/src/main/resources/db/data.sql new file mode 100644 index 000000000..a509760a5 --- /dev/null +++ b/src/main/resources/db/data.sql @@ -0,0 +1,3 @@ +INSERT INTO products_tb (name, price, imageUrl) VALUES ('AAA', 1000, 'aoeitwi'); +INSERT INTO products_tb (name, price, imageUrl) VALUES ('BBB', 2000, 'woitjewt'); +INSERT INTO products_tb (name, price, imageUrl) VALUES ('CCC', 3000, 'ghleag'); \ No newline at end of file diff --git a/src/main/resources/db/schema.sql b/src/main/resources/db/schema.sql new file mode 100644 index 000000000..99b7bf181 --- /dev/null +++ b/src/main/resources/db/schema.sql @@ -0,0 +1,17 @@ +drop table if exists products_tb CASCADE; +create table products_tb +( + id bigint AUTO_INCREMENT PRIMARY KEY, + name varchar(50), + price int, + imageUrl varchar(255) +); + +drop table if exists user_tb CASCADE; +create table user_tb +( + id bigint AUTO_INCREMENT PRIMARY KEY, + email varchar(50), + password varchar(20), + accessToken varchar(255) +); \ No newline at end of file diff --git a/src/main/resources/templates/error.html b/src/main/resources/templates/error.html new file mode 100644 index 000000000..cc134c050 --- /dev/null +++ b/src/main/resources/templates/error.html @@ -0,0 +1,13 @@ + + + + + 에러 페이지 + + +

에러 발생

+

+

+홈으로 돌아가기 + + \ No newline at end of file diff --git a/src/main/resources/templates/index.html b/src/main/resources/templates/index.html new file mode 100644 index 000000000..736df8be7 --- /dev/null +++ b/src/main/resources/templates/index.html @@ -0,0 +1,94 @@ + + + + + Manage Products + + + +
+
+
+

Manage Products

+
+
+
+ + Add New Product +
+ + + + + + + + + + + + + + + + + + + + + +
IDNamePriceImage URLActions
IDNamePriceImage URL + Edit + +
+
+
+
+ + + + + + diff --git a/src/main/resources/templates/modify-product-form.html b/src/main/resources/templates/modify-product-form.html new file mode 100644 index 000000000..55e1776e0 --- /dev/null +++ b/src/main/resources/templates/modify-product-form.html @@ -0,0 +1,41 @@ + + + + + Edit Product + + + +
+
+
+

Edit Product

+
+
+
+
+ + +
+
+
+ + +
+
+
+ + +
+
+ + Cancel +
+
+
+
+ + + + + diff --git a/src/main/resources/templates/new-product-form.html b/src/main/resources/templates/new-product-form.html new file mode 100644 index 000000000..78d53c17c --- /dev/null +++ b/src/main/resources/templates/new-product-form.html @@ -0,0 +1,41 @@ + + + + + Add New Product + + + +
+
+
+

Add New Product

+
+
+
+
+ + +
+
+
+ + +
+
+
+ + +
+
+ + Cancel +
+
+
+
+ + + + +