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주차 과제(1단계 ~ 3단계) #204

Open
wants to merge 41 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
b30bca0
docs: 작성 README.md
cocharm Jun 28, 2024
ced7912
feat: Add Product class
cocharm Jun 28, 2024
8ff227f
feat: Add MemoryProductRepository
cocharm Jun 28, 2024
46d42da
test: Add MemoryProductRepository testcode
cocharm Jun 28, 2024
0e3f39f
add: add Repository annotation
cocharm Jun 28, 2024
54c8b31
feat: Add getAllProducts, getProduct
cocharm Jun 28, 2024
9677627
move: move Product.java from /gift to /gift/model
cocharm Jun 28, 2024
248f66c
add: add ProductForm for post and put
cocharm Jun 28, 2024
1c097d0
add: add get, post, put, delete
cocharm Jun 28, 2024
3d6b6a9
refactor: refactor save와 edit의 파라미터 타입 Product->ProductForm으로 변경
cocharm Jun 28, 2024
9c3474b
refactor: refactor save와 edit의 파라미터 타입 Product->ProductForm으로 변경
cocharm Jun 28, 2024
9233af6
Test: 테스트 코드 리펙토링
cocharm Jun 28, 2024
3616f84
add: change deleteProduct delete 성공시 status NO_CONTENT to OK
cocharm Jun 28, 2024
1bee1a0
add: add status code
cocharm Jun 28, 2024
5429dc4
docs: add step2 readme
cocharm Jun 28, 2024
c29de3d
refactor: change price int to Integer
cocharm Jun 28, 2024
b8c43f9
fix: fix putProduct
cocharm Jun 28, 2024
33dce60
feat: add contoller for adminpage
cocharm Jun 28, 2024
7e19112
feat: add controller for errorpage
cocharm Jun 28, 2024
c73252b
add: add static files
cocharm Jun 28, 2024
47d94a1
feat: README.md에 구현할 기능 목록을 정리
cocharm Jun 28, 2024
d11f6c0
feat: README.md에 구현할 기능 목록을 정리
cocharm Jun 28, 2024
3bf2cba
feat: add adminpage
cocharm Jun 28, 2024
0088049
feat: add admin add page
cocharm Jun 28, 2024
0a42f99
feat: add error page
cocharm Jun 28, 2024
c7f414f
feat: add admin product detail page
cocharm Jun 28, 2024
3111479
feat: add admin product deleted page
cocharm Jun 28, 2024
8b8363c
docs: add step3 readme
cocharm Jun 28, 2024
84d0360
fix: fit editProduct test
cocharm Jun 28, 2024
558cb4d
fix: fix editProduct test
cocharm Jun 28, 2024
d3052e8
docs: ddl.sql 관리
cocharm Jun 28, 2024
19e9c76
feat: add SpringConfig.java
cocharm Jun 28, 2024
c7ef350
feat: add JdbcProductRepository.java
cocharm Jun 28, 2024
4febc53
feat: add AdminController
cocharm Jun 28, 2024
8966964
feat: di를 위해 인터페이스로 생성자 형성
cocharm Jun 28, 2024
1962d6d
feat: repository 어노테이션 삭제 및ProductRepositoryIntegrationTest 이동
cocharm Jun 28, 2024
0a86c31
feat: application.properties h2 세팅
cocharm Jun 28, 2024
b7c0b55
feat: add ProductRepositoryIntegrationTest
cocharm Jun 28, 2024
ab6871b
docs: add step3 readme
cocharm Jun 28, 2024
c0ecf75
feat: 애플리케이션이 실행될 때 테이블 새로 구축되도록 수정
cocharm Jun 28, 2024
eec33d2
Fix 전반적인 오류 수정 및 주석으로 설명추가
cocharm 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
76 changes: 75 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,75 @@
# spring-gift-product
# spring-gift-product
## 기능 요구 사항
* 상품을 조회, 추가, 수정, 삭제할 수 있는 간단한 HTTP API를 구현한다.
* HTTP 요청과 응답은 JSON 형식으로 주고받는다.
* 현재는 별도의 데이터베이스가 없으므로 적절한 자바 컬렉션 프레임워크를 사용하여 메모리에 저장한다.

**Request**
```
GET /api/products HTTP/1.1
```

**Response**
```
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"
}
]
```

**상품 데이터 관리**
```java
public class ProductController {
private final Map<Long, Product> products = new HashMap<>();
}

public class Product {
private Long id;
private String name;
private int quantity;
private double price;
// ...
}
```

### 1. get
* 모든 product 조회
* /api/products
* id로 product 선택 조회
* /api/products/:id
* 없으면 error 발생
### 2. post
* product 추가
* /api/products
### 3. put
* product 수정
* /api/products/:id
* 없으면 error 발생
### 4. delete
* product 삭제
* /api/products/:id
* 없으면 error 발생

# step 2
### /api/products/admin
상품 관리
### /api/products/admin/add
상품 추가
### /api/products/admin/:id
상품 편집 및 삭제 가능


# step 3
상품 정보에 옵션을 추가한다. 상품과 옵션 모델 간의 관계를 고려하여 설계하고 구현한다.
* 상품에는 항상 하나 이상의 옵션이 있어야 한다.

## 프로그래밍 요구 사항
* H2 데이터베이스를 사용하도록 변경한다.
* 사용하는 테이블은 애플리케이션이 실행될 때 구축되어야 한다.

8 changes: 8 additions & 0 deletions sql/ddl.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
drop table if exists product CASCADE;
create table product
(
id bigint AUTO_INCREMENT PRIMARY KEY,
name varchar(255),
price int,
imageUrl varchar(255)
);
1 change: 1 addition & 0 deletions src/main/java/gift/Application.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

@SpringBootApplication
public class Application {

public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
Expand Down
44 changes: 44 additions & 0 deletions src/main/java/gift/SpringConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package gift;

import gift.controller.AdminController;
import gift.repository.JdbcProductRepository;
import gift.repository.MemoryProductRepository;
import gift.repository.ProductRepository;
import jakarta.annotation.PostConstruct;
import javax.sql.DataSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.jdbc.datasource.init.DatabasePopulatorUtils;
import org.springframework.jdbc.datasource.init.ResourceDatabasePopulator;

@Configuration
public class SpringConfig {

private final DataSource dataSource;

@Autowired
public SpringConfig(DataSource dataSource) {
this.dataSource = dataSource;
}

@Bean
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

빈 등록을 config에서 중복적으로 하고 있는 것으로 보입니다.

public AdminController adminController() {
return new AdminController(productRepository());
}

@Bean
public ProductRepository productRepository() {
// JDBC를 이용한 데이터베이스 접근 방식 선택
return new JdbcProductRepository(dataSource);
}

@PostConstruct
private void initializeDB() {
// 데이터베이스 초기화를 위한 SQL 스크립트 실행
ResourceDatabasePopulator populator = new ResourceDatabasePopulator();
populator.addScript(new ClassPathResource("sql/ddl.sql")); // 데이터베이스 초기화 스크립트 경로 지정
DatabasePopulatorUtils.execute(populator, dataSource); // 데이터베이스 초기화 실행
}
}
45 changes: 45 additions & 0 deletions src/main/java/gift/controller/AdminController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package gift.controller;

import gift.repository.ProductRepository;
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
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이미 해당 어노테이션을 통해 빈 등록이 되니 config에서는 지워주세요.

@RequestMapping("/api/products/admin")
public class AdminController {

private final ProductRepository repository;

public AdminController(ProductRepository repository) {
this.repository = repository;
}

// 모든 제품을 표시하는 관리자 페이지로 매핑
@GetMapping("")
public String adminPage(Model model) {
model.addAttribute("products", repository.findAll());
return "admin";
}

// 새 제품을 추가하는 페이지로 매핑
@GetMapping("/add")
public String getAdminAddPage() {
return "adminAdd";
}

// 삭제된 제품을 보여주는 페이지로 매핑
@GetMapping("/deleted")
public String getAdminDeletedPage() {
return "adminDeleted";
}

// 특정 ID의 제품 세부 정보를 보여주는 페이지로 매핑
@GetMapping("/{id}")
public String getAdminProductDetailPage(@PathVariable Long id, Model model) {
model.addAttribute("product", repository.findById(id).orElse(null)); // Null을 처리하기 위해 orElse(null) 사용
return "adminProductDetail";
}
}
14 changes: 14 additions & 0 deletions src/main/java/gift/controller/ErrorController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package gift.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;

@Controller
public class ErrorController {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

목적에 따른 클래스 분리 좋습니다.


// 오류 페이지로 매핑
@GetMapping("/error")
public String errorPage() {
return "error";
}
}
68 changes: 68 additions & 0 deletions src/main/java/gift/controller/ProductController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package gift.controller;

import gift.model.Product;
import gift.model.ProductForm;
import gift.repository.ProductRepository;
import java.util.List;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/api/products")
public class ProductController {

private final ProductRepository repository;

public ProductController(ProductRepository repository) {
this.repository = repository;
}

// 모든 제품을 가져오는 엔드포인트
@GetMapping("")
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

("") 는 지워주셔도 좋습니다.

public List<Product> getAllProducts() {
return repository.findAll();
}

// 특정 ID의 제품을 가져오는 엔드포인트
@GetMapping("/{id}")
public ResponseEntity<Product> getProduct(@PathVariable Long id) {
Product result = repository.findById(id).orElse(null); // Optional 처리
if (result != null) {
return ResponseEntity.status(HttpStatus.OK).body(result);
}
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(null);
}

// 새로운 제품을 생성하는 엔드포인트
@PostMapping(path = "", consumes = "application/json")
public ResponseEntity<Product> postProduct(@RequestBody ProductForm form) {
Product product = new Product(form); // ProductForm을 Product로 변환
Product result = repository.save(product);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

컨트롤러 단에서 바로 로직이 실행되고 있습니다.
특정 클래스를 통해 controller에서 repository를 참조하지 않는 것을 추천드립니다.

if (result != null) {
return ResponseEntity.status(HttpStatus.OK).body(result);
}
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(null); // 상태 코드를 BAD_REQUEST로 수정
}

// 특정 ID의 제품을 수정하는 엔드포인트
@PutMapping(path = "/{id}", consumes = "application/json")
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

consumes은 기본값을 확인해보고 생략해주셔도 좋습니다.

public ResponseEntity<Product> putProduct(@RequestBody ProductForm form, @PathVariable Long id) {
Product product = new Product(form); // ProductForm을 Product로 변환
Product result = repository.edit(id, product);
if (result != null) {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

에러 처리가 보일러플레이트처럼 반복되는 것으로 보입니다.
global exception handler 의 도입을 추천드립니다.

return ResponseEntity.status(HttpStatus.OK).body(result);
}
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(null);
}

// 특정 ID의 제품을 삭제하는 엔드포인트
@DeleteMapping(path = "/{id}")
public ResponseEntity<String> deleteProduct(@PathVariable Long id) {
boolean result = repository.delete(id);
if (result) {
return ResponseEntity.status(HttpStatus.OK).body("Successfully deleted");
}
return ResponseEntity.status(HttpStatus.NOT_FOUND).body("Product not found"); // 메시지를 명확히 수정
}
}
71 changes: 71 additions & 0 deletions src/main/java/gift/model/Product.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package gift.model;

public class Product {

private Long id;
private String name;
private int price;
private String imageUrl;

// 기본 생성자 추가 (JPA나 다른 프레임워크에서 필요할 수 있음)
public Product() {
}

// 생성자
public Product(String name, int price, String imageUrl) {
this.name = name;
this.price = price;
this.imageUrl = imageUrl;
}

// ProductForm을 Product로 변환하는 생성자
public Product(ProductForm form) {
this.name = form.getName();
this.price = form.getPrice();
this.imageUrl = form.getImageUrl();
}

// getter 메서드들
public Long getId() {
return id;
}

public String getName() {
return name;
}

public int getPrice() {
return price;
}

public String getImageUrl() {
return imageUrl;
}

// setter 메서드들
public void setId(Long id) {
this.id = id;
}

public void setImageUrl(String imageUrl) {
this.imageUrl = imageUrl;
}

// 주어진 Product 객체의 정보를 현재 객체에 업데이트하는 메서드
public void updateFrom(Product product) {
this.name = product.getName();
this.price = product.getPrice();
this.imageUrl = product.getImageUrl();
}

// 객체를 문자열로 표현하는 메서드
@Override
public String toString() {
return "Product{" +
"id=" + id +
", name='" + name + '\'' +
", price=" + price +
", imageUrl='" + imageUrl + '\'' +
'}';
}
}
52 changes: 52 additions & 0 deletions src/main/java/gift/model/ProductForm.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package gift.model;

public class ProductForm {

private String name;
private Integer price;
private String imageUrl;

// 생성자
public ProductForm(String name, Integer price, String imageUrl) {
this.name = name;
this.price = price;
this.imageUrl = imageUrl;
}

// getter 메서드들
public String getName() {
return name;
}

public Integer getPrice() {
return price;
}

public String getImageUrl() {
return imageUrl;
}

// setter 메서드들
public void setName(String name) {
this.name = name;
}

public void setPrice(Integer price) {
this.price = price;
}

public void setImageUrl(String imageUrl) {
this.imageUrl = imageUrl;
}

// 객체를 문자열로 표현하는 메서드
@Override
public String toString() {
return "ProductForm{" +
"name='" + name + '\'' +
", price=" + price +
", imageUrl='" + imageUrl + '\'' +
'}';
}

}
Loading