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_정재빈_1주차 과제_step3 #172

Open
wants to merge 56 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
56 commits
Select commit Hold shift + click to select a range
98f3a17
feat(global): create global controller
JaeBin2019 Jun 26, 2024
e114c0e
docs(global): summerize list of features
JaeBin2019 Jun 25, 2024
5b37775
feat(product): create product controller
JaeBin2019 Jun 25, 2024
2b52d04
feat(product): create product entity
JaeBin2019 Jun 25, 2024
f54b33a
fix(product): add column id into product entity
JaeBin2019 Jun 25, 2024
f376876
feat(product): add method get product by id in controller
JaeBin2019 Jun 25, 2024
0ded7d3
feat(product): create product request record
JaeBin2019 Jun 25, 2024
dc565f4
feat(product): add constructor to initialize Product from ProductRequest
JaeBin2019 Jun 25, 2024
5507db1
fix(product): change RequestMapping url error
JaeBin2019 Jun 25, 2024
fa94538
feat(product): add create product method
JaeBin2019 Jun 25, 2024
3028213
fix(product): change endpoint product to products
JaeBin2019 Jun 25, 2024
eedb420
feat(product): add update product method
JaeBin2019 Jun 25, 2024
c3c5908
feat(product): add delete product method
JaeBin2019 Jun 25, 2024
317a4ba
feat(product): create http request file
JaeBin2019 Jun 25, 2024
5d21ad1
refactor(global): combine common parts of API URLs
JaeBin2019 Jun 26, 2024
c42e117
feat(product): add get all product method
JaeBin2019 Jun 26, 2024
225aaf7
chore(product): rename global controller to view controller
JaeBin2019 Jun 26, 2024
ebc23b2
fix(global): change get products list api url
JaeBin2019 Jun 26, 2024
bf7afcd
chore(view): delete unnecssary parameter
JaeBin2019 Jun 27, 2024
ecc4ff9
feat(view): create product management UI
JaeBin2019 Jun 27, 2024
199c894
feat(product): create ServiceDto
JaeBin2019 Jun 27, 2024
a38cc6f
feat(product): add toServiceDto method to convert ProductRequest
JaeBin2019 Jun 27, 2024
4c57bf1
feat(product): change all method to use productService
JaeBin2019 Jun 27, 2024
20d14b7
feat(product): create Product Not Found Exception
JaeBin2019 Jun 27, 2024
dd3d0e5
feat(product): create Service Dto
JaeBin2019 Jun 27, 2024
fc49319
feat(product): create Product Service and methods
JaeBin2019 Jun 27, 2024
a944475
feat(product): add isNew method in product entity
JaeBin2019 Jun 28, 2024
56f5f57
chore(global): add data jdbc dependency
JaeBin2019 Jun 28, 2024
29cf8c4
feat(product): create product repository and methods
JaeBin2019 Jun 28, 2024
714baf2
feat(global): setting global exception handler
JaeBin2019 Jun 28, 2024
902f597
feat(global): setting db properties
JaeBin2019 Jun 28, 2024
71fb6ed
feat(product): create input initial data sql
JaeBin2019 Jun 28, 2024
b00ee6f
feat(product): create schema sql to make table products
JaeBin2019 Jun 28, 2024
1d4f2bb
fix(product): add default constructor and change directory entity to …
JaeBin2019 Jun 28, 2024
199a7ff
feat(global): create result code enum
JaeBin2019 Jun 28, 2024
b388095
feat(product): set result code of product api
JaeBin2019 Jun 28, 2024
8a22e26
feat(global): create result response
JaeBin2019 Jun 28, 2024
9410cc5
fix(global): change result code status type String to int
JaeBin2019 Jun 28, 2024
c3736c5
fix(global): change result response private to public
JaeBin2019 Jun 28, 2024
7bac451
chore(global): change ResultResponse name to ResultResponseDto
JaeBin2019 Jun 28, 2024
0759818
fix(http): update url for get all products
JaeBin2019 Jun 28, 2024
7e61e41
feat(global): create simple result response dto
JaeBin2019 Jun 28, 2024
3ef0273
chore(global): move global exception handler package response to handler
JaeBin2019 Jun 28, 2024
1d83a57
feat(product): change all method response to use ResponseEntity
JaeBin2019 Jun 28, 2024
c300531
fix(product): remove return value from create and update methods
JaeBin2019 Jun 28, 2024
77076be
feat(global): create response helper util
JaeBin2019 Jun 28, 2024
fd89619
refactor(product): apply ResponseHelper for response creation in Prod…
JaeBin2019 Jun 28, 2024
8fdd8ea
fix(product): change method name from isNew to checkNew to fix JSON s…
JaeBin2019 Jun 28, 2024
ebee7f4
fix(view): update index.html to use response.data for request parameter
JaeBin2019 Jun 28, 2024
d512a90
refactor(global): change resultCode variable to code in ResultCode enum
JaeBin2019 Jun 28, 2024
bedd0c2
feat(global): create ErrorCode enum
JaeBin2019 Jun 28, 2024
9d897c4
feat(global): create ErrorResponseDto record
JaeBin2019 Jun 28, 2024
39cddd4
fix(global): add private constructor to ResponseHelper class
JaeBin2019 Jun 28, 2024
18efb9c
refactor(global): update error handling to return ErrorResponseDto in…
JaeBin2019 Jun 28, 2024
025b774
refactor(product): rename ProductRequest to ProductRequestDto
JaeBin2019 Jun 28, 2024
52a232e
feat(view): add search product by productId
JaeBin2019 Jun 28, 2024
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
47 changes: 46 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,46 @@
# spring-gift-product
# spring-gift-product

---

## Step1

**요구 사항**<br>
아래와 같이 http 메세지를 받도록 구현한다

```http request
GET /api/products HTTP/1.1
```

```http request
HTTP/1.1 200
Content-Type: application/json

[
{
"id": 8146027,
"name": "아이스 카페 아메리카노 T",
"price": 4500,
"imageUrl": "https://st.kakaocdn.net/product/gift/product/20231010111814_9a667f9eccc943648797925498bdd8a3.jpg"
}
]
```


**필요 조건**<br>
상품 데이터 관리
현재는 별도의 데이터베이스가 없으므로 적절한 컬렉션을 이용하여 메모리에 저장한다.
```java
public class ProductController {
private final Map<Long, Product> productMap = new HashMap<>();
}
```
----
## 구현 기능
- product 조회
- product 추가
- product 수정
- product 삭제




1 change: 1 addition & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ repositories {

dependencies {
implementation 'org.springframework.boot:spring-boot-starter-jdbc'
implementation 'org.springframework.boot:spring-boot-starter-data-jdbc'
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
implementation 'org.springframework.boot:spring-boot-starter-web'
runtimeOnly 'com.h2database:h2'
Expand Down
31 changes: 31 additions & 0 deletions src/main/http/product/Product.http
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
### get product list
GET http://localhost:8080/api/products
Content-Type: application/json

### get product
GET http://localhost:8080/api/products/1
Content-Type: application/json

### create product
POST http://localhost:8080/api/products
Content-Type: application/json

{
"name": "아메리카노",
"price": "4500",
"imageUrl": "http://hello"
}

### update product
PUT http://localhost:8080/api/products/1
Content-Type: application/json

{
"name": "아메리카노2",
"price": "5000",
"imageUrl": "http://hello"
}

### delete product
DELETE http://localhost:8080/api/products/1
Content-Type: application/json
21 changes: 21 additions & 0 deletions src/main/java/gift/global/exception/BusinessException.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package gift.global.exception;

import gift.global.response.ErrorCode;

public class BusinessException extends RuntimeException {
private final ErrorCode errorCode;

public BusinessException(ErrorCode errorCode) {
super(errorCode.getMessage());
this.errorCode = errorCode;
}

public ErrorCode getErrorCode() {
return errorCode;
}

@Override
public String toString() {
return errorCode.toString() + " : " + super.toString();
}
}
16 changes: 16 additions & 0 deletions src/main/java/gift/global/handler/GlobalExceptionHandler.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package gift.global.handler;

import gift.global.exception.BusinessException;
import gift.global.response.ErrorResponseDto;
import gift.global.utils.ResponseHelper;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(BusinessException.class)
public ResponseEntity<ErrorResponseDto> handleBusinessException(BusinessException e) {
return ResponseHelper.createErrorResponse(e.getErrorCode());
}
}
28 changes: 28 additions & 0 deletions src/main/java/gift/global/response/ErrorCode.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package gift.global.response;

public enum ErrorCode {
// Product
PRODUCT_NOT_FOUND_ERROR(400, "EP001", "Product Not Found Error"),
;
private final int status;
private final String code;
private final String message;

ErrorCode(int status, String code, String message) {
this.status = status;
this.code = code;
this.message = message;
}

public int getStatus() {
return status;
}

public String getCode() {
return code;
}

public String getMessage() {
return message;
}
}
7 changes: 7 additions & 0 deletions src/main/java/gift/global/response/ErrorResponseDto.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package gift.global.response;

public record ErrorResponseDto(String code, String message) {
public ErrorResponseDto(ErrorCode errorCode) {
this(errorCode.getCode(), errorCode.getMessage());
}
}
35 changes: 35 additions & 0 deletions src/main/java/gift/global/response/ResultCode.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package gift.global.response;

public enum ResultCode {
// Product
GET_ALL_PRODUCTS_SUCCESS(200, "P001", "모든 제품 조회 성공"),
GET_PRODUCT_BY_ID_SUCCESS(200, "P002", "단일 제품 조회 성공"),
CREATE_PRODUCT_SUCCESS(200, "P003", "제품 추가 성공"),
UPDATE_PRODUCT_SUCCESS(200, "P002", "제품 수정 성공"),
DELETE_PRODUCT_SUCCESS(200, "P002", "제품 삭제 성공"),

;

// status 를 HttpStatus 로 관리하는 것이 좋을까, 아니면 int로 관리하는 것이 좋을까?
private final int status;
private final String code;
private final String message;

ResultCode(int status, String code, String message) {
this.status = status;
this.code = code;
this.message = message;
}

public int getStatus() {
return status;
}

public String getCode() {
return code;
}

public String getMessage() {
return message;
}
}
8 changes: 8 additions & 0 deletions src/main/java/gift/global/response/ResultResponseDto.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package gift.global.response;

public record ResultResponseDto<T>(String code, String message, T data) {
// 자바 record 에서 생성자를 만들 경우, 반드시 canonical(표준) 생성자를 사용해야 한다!!
public ResultResponseDto(ResultCode resultCode, T data) {
this(resultCode.getCode(), resultCode.getMessage(), data);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package gift.global.response;

public record SimpleResultResponseDto(String code, String message) {
public SimpleResultResponseDto(ResultCode resultCode) {
this(resultCode.getCode(), resultCode.getMessage());
}
}
26 changes: 26 additions & 0 deletions src/main/java/gift/global/utils/ResponseHelper.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package gift.global.utils;

import gift.global.response.*;
import org.springframework.http.ResponseEntity;

public class ResponseHelper {
private ResponseHelper() {}

public static <T> ResponseEntity<ResultResponseDto<T>> createResponse(ResultCode resultCode, T data) {
ResultResponseDto<T> resultResponseDto = new ResultResponseDto<>(resultCode, data);
return org.springframework.http.ResponseEntity.status(resultCode.getStatus())
.body(resultResponseDto);
}

public static ResponseEntity<SimpleResultResponseDto> createSimpleResponse(ResultCode resultCode) {
var resultResponseDto = new SimpleResultResponseDto(resultCode);
return ResponseEntity.status(resultCode.getStatus())
.body(resultResponseDto);
}

public static ResponseEntity<ErrorResponseDto> createErrorResponse(ErrorCode errorCode) {
ErrorResponseDto errorResponseDto = new ErrorResponseDto(errorCode);
return ResponseEntity.status(errorCode.getStatus())
.body(errorResponseDto);
}
}
53 changes: 53 additions & 0 deletions src/main/java/gift/product/controller/ProductController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package gift.product.controller;

import gift.global.response.ResultCode;
import gift.global.response.ResultResponseDto;
import gift.global.response.SimpleResultResponseDto;
import gift.global.utils.ResponseHelper;
import gift.product.dto.ProductRequestDto;
import gift.product.domain.Product;
import gift.product.service.ProductService;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@RestController
@RequestMapping("/api/products")
public class ProductController {
private final ProductService productService;

public ProductController(ProductService productService) {
this.productService = productService;
}

@GetMapping("")
public ResponseEntity<ResultResponseDto<List<Product>>> getAllProducts() {
List<Product> products = productService.getAllProducts();
return ResponseHelper.createResponse(ResultCode.GET_ALL_PRODUCTS_SUCCESS, products);
}

@GetMapping("/{id}")
public ResponseEntity<ResultResponseDto<Product>> getProductById(@PathVariable(name = "id") Long id) {
Product product = productService.getProductById(id);
return ResponseHelper.createResponse(ResultCode.GET_PRODUCT_BY_ID_SUCCESS, product);
}

@PostMapping("")
public ResponseEntity<SimpleResultResponseDto> createProduct(@RequestBody ProductRequestDto productRequestDto) {
productService.createProduct(productRequestDto.toServiceDto());
return ResponseHelper.createSimpleResponse(ResultCode.CREATE_PRODUCT_SUCCESS);
}

@PutMapping("/{id}")
public ResponseEntity<SimpleResultResponseDto> updateProduct(@PathVariable(name = "id") Long id, @RequestBody ProductRequestDto productRequestDto) {
productService.updateProduct(productRequestDto.toServiceDto(id));
return ResponseHelper.createSimpleResponse(ResultCode.UPDATE_PRODUCT_SUCCESS);
}

@DeleteMapping("/{id}")
public ResponseEntity<SimpleResultResponseDto> deleteProduct(@PathVariable(name = "id") Long id) {
productService.deleteProduct(id);
return ResponseHelper.createSimpleResponse(ResultCode.DELETE_PRODUCT_SUCCESS);
}
}
52 changes: 52 additions & 0 deletions src/main/java/gift/product/domain/Product.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package gift.product.domain;

import gift.product.dto.ProductRequestDto;
import org.springframework.data.annotation.Id;
import org.springframework.data.relational.core.mapping.Table;

@Table("products")
public class Product {
@Id
private Long id;
private String name;
private int price;
private String imageUrl;

// JDBC 에서 엔티티 클래스를 인스턴스화할 때 반드시 기본 생성자와 파라미터 생성자가 필요하다
public Product() {}

public Product(Long id, String name, int price, String imageUrl) {
this.id = id;
this.name = name;
this.price = price;
this.imageUrl = imageUrl;
}

public Product(Long id, ProductRequestDto productRequestDto) {
this.id = id;
this.name = productRequestDto.name();
this.price = productRequestDto.price();
this.imageUrl = productRequestDto.imageUrl();
}


public Long getId() {
return id;
}

public String getName() {
return name;
}

public int getPrice() {
return price;
}

public String getImageUrl() {
return imageUrl;
}

public boolean checkNew() {
return id == null;
}
}
18 changes: 18 additions & 0 deletions src/main/java/gift/product/dto/ProductRequestDto.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package gift.product.dto;

import java.util.Objects;

public record ProductRequestDto(String name, int price, String imageUrl) {
public ProductRequestDto {
Objects.requireNonNull(name);
Objects.requireNonNull(imageUrl);
}

public ServiceDto toServiceDto() {
return new ServiceDto(null, this.name, this.price, this.imageUrl);
}

public ServiceDto toServiceDto(Long id) {
return new ServiceDto(id, this.name, this.price, this.imageUrl);
}
}
9 changes: 9 additions & 0 deletions src/main/java/gift/product/dto/ServiceDto.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package gift.product.dto;

import gift.product.domain.Product;

public record ServiceDto(Long id, String name, int price, String imageUrl) {
public Product toProduct() {
return new Product(id, name, price, imageUrl);
}
}
10 changes: 10 additions & 0 deletions src/main/java/gift/product/exception/ProductNotFoundException.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package gift.product.exception;

import gift.global.exception.BusinessException;
import gift.global.response.ErrorCode;

public class ProductNotFoundException extends BusinessException {
public ProductNotFoundException() {
super(ErrorCode.PRODUCT_NOT_FOUND_ERROR);
}
}
Loading