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주차 과제(2단계) #168

Open
wants to merge 35 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
577b80d
docs : 구현할 기능 목록 명세
Jun 26, 2024
526556b
feat : 상품 DTO 구현
Jun 27, 2024
6e6515e
feat : HTTP API 구현
Jun 27, 2024
41ac09c
feat : ControllerTest JUnit 구현
Jun 27, 2024
278b54d
style : README 오자 수정
Jun 27, 2024
ba85a3c
style : README 오자 수정
Jun 27, 2024
557c978
docs : [step2] README Step2 명세
Jun 27, 2024
05b42f1
feat : [step2] GET API 구현
Jun 27, 2024
b9b54e3
feat : [step2] Product Detail GET API 구현
Jun 27, 2024
5eaa188
fix : [step2] ProductController 복구
Jun 28, 2024
6cd18a3
feat : [step2] ProductService 생성
Jun 28, 2024
ce558e9
feat : [step2] GET products 비즈니스 로직 분리
Jun 28, 2024
731b7e6
feat : [step2] GET product 비즈니스 로직 분리
Jun 28, 2024
bfe7fd9
feat : [step2] Create product 비즈니스 로직 분리
Jun 28, 2024
d72b62b
feat : [step2] Update product 비즈니스 로직 분리
Jun 28, 2024
4da5702
feat : [step2] Delete product 비즈니스 로직 분리
Jun 28, 2024
824a714
refactor : [step2] ProductController에 ProductService 생성자 추가
Jun 28, 2024
0c7924e
refactor : [step2] getProducts 수정
Jun 28, 2024
a09d9e6
refactor : [step2] getProduct 수정
Jun 28, 2024
28459d6
style : [step2] 가독성 수정
Jun 28, 2024
f730d4d
refactor : [step2] createProduct 수정
Jun 28, 2024
3045378
refactor : [step2] updateProduct 수정
Jun 28, 2024
0e2855d
refactor : [step2] deleteProduct 수정
Jun 28, 2024
387afa4
feat : [step2] AdminController 생성
Jun 28, 2024
1643f6e
feat : [step2] getProducts 구현
Jun 28, 2024
dc50ce1
feat : [step2] getProductById 구현
Jun 28, 2024
34aa0a2
feat : [step2] createProduct form 구현
Jun 28, 2024
8e60069
feat : [step2] createProduct POST API 구현
Jun 28, 2024
daf36c1
feat : [step2] update delete 구현
Jun 28, 2024
7f3f239
fix : [step2] 오타 수정
Jun 28, 2024
a75b803
refactor : [step2] 불필요한 import 제거
Jun 28, 2024
98cfa59
feat : [step2] 상품 리스트를 보여주는 html 작성
Jun 28, 2024
869af5d
feat : [step2] html thymeleaf 연동
Jun 28, 2024
b70ec2f
feat : [step2] create html form 작성 및 구현
Jun 28, 2024
5991652
feat : [step2] edit html form 작성 및 구현
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
24 changes: 23 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,23 @@
# spring-gift-product
# spring-gift-product

구현할 기능 목록

## step1

상품 정보 DTO - record로 구현 (gift.web.dto.Product)

HTTP API - ProductController (gift.web.ProductController)

- POST
- GET
- PUT / PATCH
- DELETE

## step2

상품을 조회, 추가, 수정, 삭제할 수 있는 관리자 화면을 구현한다.

Thymeleaf를 사용해, HTML 폼 전송을 이용한 페이지 이동을 기반으로 구현

* ProductController로 view를 반환하도록 재작성
* index(product) html, create update html 작성
58 changes: 58 additions & 0 deletions src/main/java/gift/web/ProductController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package gift.web;

import gift.web.dto.Product;
import java.util.List;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

@RestController //컨트롤러를 JSON을 반환하는 컨트롤러로 만들어줌
@RequestMapping("/api/products")
public class ProductController {

private final ProductService productService;

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

@GetMapping
public ResponseEntity<List<Product>> getProducts() {
return new ResponseEntity<>(productService.getProducts(), HttpStatus.OK);
}

// products/{상품번호}의 GetMapping
@GetMapping("/{id}") //Query Param과 Path Variable 사용의 차이점 알아보기
public ResponseEntity<Product> getProductById(@PathVariable Long id) {
Product product = productService.getProductById(id);
if(product != null) {
return new ResponseEntity<>(product, HttpStatus.OK);
}
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
}

@PostMapping
public ResponseEntity<?> createProduct(@RequestBody Product product) {
return new ResponseEntity<>(productService.createProduct(product), HttpStatus.CREATED);
}

// PUT은 업데이트시 존재하지 않는다면, 생성을 하게 되는데, POST와의 차이점?이 뭔 지 알아보기
// PUT 구현
@PutMapping("/{id}")
public ResponseEntity<?> updateProduct(@PathVariable Long id, @RequestBody Product product) {
boolean exists = productService.getProductById(id) != null;
productService.updateProduct(id, product);
if(exists) {
return ResponseEntity.ok(product);
}
return ResponseEntity.status(HttpStatus.CREATED).body(product);
}

@DeleteMapping("/{id}")
public ResponseEntity<?> deleteProduct(@PathVariable Long id) {
if(productService.deleteProduct(id)) {
return ResponseEntity.ok("Delete Success");
}
return ResponseEntity.status(HttpStatus.NOT_FOUND).body("Product Not Found");
}
}
39 changes: 39 additions & 0 deletions src/main/java/gift/web/ProductService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package gift.web;

import gift.web.dto.Product;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicLong;
import org.springframework.stereotype.Service;

@Service
public class ProductService {
private final Map<Long, Product> products = new HashMap<>();
private final AtomicLong incrementCounter = new AtomicLong(1); // ID를 관리할 변수

public List<Product> getProducts() {
return List.copyOf(products.values());

Choose a reason for hiding this comment

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

List.copyOf(products.values());를 이용해 컬렉션을 복사하여 반환하고 있군요!
불변을 만들기 위해 컬렉션을 복사하는 방법에는 어떤 것들이 있고, 많은 방법 중 List.copyOf를 사용하신 이유는 무엇일까요?

}

public Product getProductById(Long id) {
return products.get(id);
}

public Product createProduct(Product product) {
Long id = incrementCounter.getAndIncrement(); // 1씩 증가하는 id
Product newProduct = new Product(id, product.name(), product.price(), product.imageUrl());
products.put(id, newProduct);

return newProduct;
}

public Product updateProduct(Long id, Product product) {
products.put(id, product);
return product;
}

public boolean deleteProduct(Long id) {
return products.remove(id) != null;
}
}
68 changes: 68 additions & 0 deletions src/main/java/gift/web/dto/AdminController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package gift.web.dto;

Choose a reason for hiding this comment

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

AdminController 클래스가 dto 패키지 하위에 있네요!
여기에 위치하게 된 이유가 있을까요?


import gift.web.ProductService;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
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.RequestMapping;

@Controller
@RequestMapping("/admin/products")
public class AdminController {
private final ProductService productService;

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

@GetMapping
public String getProducts(Model model) {
model.addAttribute("products", productService.getProducts());
return "products";
}

@GetMapping("/{id}")
public String getProductById(@PathVariable Long id, Model model) {
Product product = productService.getProductById(id);
if (product != null) {
model.addAttribute("product", product);
}
return "products";
}

@GetMapping("/create")
public String createProductForm(Model model) {
model.addAttribute("product", new Product(1L, "name", 0L, "image.url"));
return "create";
}
Comment on lines +36 to +40

Choose a reason for hiding this comment

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

view에 기본값의 product를 생성해서 보내는 것이 맞는 지 모르겠습니다

라고 질문주신 코드 인 것 같아요. 덕분에 테스트하기 수월했습니다 👍
현재 서버에서 model에 기본값을 생성해주고 있네요!

기본값을 설정해주는 곳으로는 서버, 클라이언트가 있습니다.
서버에서 생성해주게되면 다양한 클라이언트에서 동일한 기본값을 일관되게 받을수 있구요,
클라이언트에서 생성해주게되면 클라이언트에서 상황에 따라 동적으로 기본값을 설정할 수 있습니다.
상황에 따라서 적절한 곳에 기본값을 설정해주면 되는데, 현재상황에서는 서버에서 잘 생성하신 것 같아요 💯


@PostMapping("/create")
public String createProduct(@ModelAttribute Product product) {
productService.createProduct(product);
return "redirect:/admin/products";
}

@GetMapping("/edit/{id}")
public String editProductForm(@PathVariable Long id, Model model) {
Product product = productService.getProductById(id);
if (product != null) {
model.addAttribute("product", product);
}
return "edit";
}

@PostMapping("/edit/{id}")
public String editProduct(@PathVariable Long id, @ModelAttribute Product product) {
productService.updateProduct(id, product);
return "redirect:/admin/products";
}

@PostMapping("/delete/{id}")
public String deleteProduct(@PathVariable Long id) {
productService.deleteProduct(id);
return "redirect:/admin/products";
}
}
4 changes: 4 additions & 0 deletions src/main/java/gift/web/dto/Product.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package gift.web.dto;

public record Product(Long id, String name, Long price, String imageUrl) {
}
Comment on lines +1 to +4

Choose a reason for hiding this comment

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

Product 클래스가 dto 패키지 하위에 있네요!
호성님이 생각하신 dto 패키지의 역할은 무엇일까요?

28 changes: 28 additions & 0 deletions src/main/resources/templates/create.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<!DOCTYPE html>

<html>
<head>
<title>Product Create</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
</head>
<body>
<h1>상품 생성</h1>
<form th:action="@{/admin/products/create}" th:object="${product}" method="post">
<div>
<label for="name">상품명:</label>
<input type="text" th:field="*{name}" id="name"></input>
</div>
<div>
<label for="price">가격:</label>
<input type="text" th:field="*{price}" id="price"></input>
</div>
<div>
<label for="imageUrl">사진주소:</label>
<input type="text" th:field="*{imageUrl}" id="imageUrl"></input>
</div>
<div>
<button type="submit">Create</button>
</div>
</form>
</body>
</html>
28 changes: 28 additions & 0 deletions src/main/resources/templates/edit.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<!DOCTYPE html>

<html>
<head>
<title>Product Create</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
</head>
<body>
<h1>상품 수정</h1>
<form th:action="@{/admin/products/edit/{id}(id=${product.id()})}" th:object="${product}" method="post">
<div>
<label for="name">상품명:</label>
<input type="text" th:field="*{name}" id="name"></input>
</div>
<div>
<label for="price">가격:</label>
<input type="text" th:field="*{price}" id="price"></input>
</div>
<div>
<label for="imageUrl">사진주소:</label>
<input type="text" th:field="*{imageUrl}" id="imageUrl"></input>
</div>
<div>
<button type="submit">Update</button>
</div>
</form>
</body>
</html>
32 changes: 32 additions & 0 deletions src/main/resources/templates/products.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<!DOCTYPE html>

<html>
<head>
<title>Products</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
</head>
<body>
<h1>상품 목록</h1>
<a href="/admin/products/create">새 상품 만들기</a>
<table>
<tr>
<th>ID</th>
<th>Name</th>
<th>Price</th>
<th>Actions</th>
</tr>
<tr th:each="product : ${products}">
<td th:text="${product.id()}">1</td>
<td th:text="${product.name()}">상품 이름예시</td>
<td th:text="${product.price()}">5000</td>
<td th:text="${product.imageUrl()}">https://image.jpg</td>
<td>
<a th:href="@{/admin/products/edit/{id}(id=${product.id()})}">Edit</a>
<form th:action="@{/admin/products/delete/{id}(id=${product.id()})}" method="post" style="display:inline;">
<button type="submit">Delete</button>
</form>
</td>
</tr>
</table>
</body>
</html>
Loading