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_김상해 3주차 과제 (2단계) #354

Open
wants to merge 26 commits into
base: gobad820
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
d25c602
feat: Week2 파일 복사
gobad820 Jul 9, 2024
795e65f
feat: Week2 파일 복사
gobad820 Jul 9, 2024
b18fc19
refactor: 모델 엔티티로 변경
gobad820 Jul 9, 2024
3bc43b3
chore: jpa 의존성 추가
gobad820 Jul 9, 2024
32cfade
refactor: JpaRepository로 변경
gobad820 Jul 10, 2024
05322a8
feat: Product Optional<Product>로 변경
gobad820 Jul 10, 2024
95706f3
feat: Product Optional<Product>로 변경
gobad820 Jul 10, 2024
2dd8752
refactor: datasource url 변경
gobad820 Jul 10, 2024
8a2c99e
feat: JpaRepository Test 생성
gobad820 Jul 10, 2024
3dcce74
feat: test용 application 생성
gobad820 Jul 10, 2024
36c8f4c
chore: 파일 포매팅
gobad820 Jul 11, 2024
17623dc
feat: 연관 관계 매핑 추가
gobad820 Jul 11, 2024
c278615
feat: Repository에서 find 시 참조 사용
gobad820 Jul 11, 2024
3e530a5
feat: 매개변수 외래키에서 참조로 변경
gobad820 Jul 11, 2024
e3bf5d8
chore: import 추가 및 포매팅
gobad820 Jul 11, 2024
2dc9350
feat: 매개변수 외래 키에서 참조로 변경
gobad820 Jul 11, 2024
db2b0f7
chore: 포매팅 및 주석 추가
gobad820 Jul 11, 2024
6a490d5
feat: gitignore 생성
gobad820 Jul 11, 2024
d9757a0
fix: href 링크 업데이트
gobad820 Jul 12, 2024
73a65f3
chore: 변수명 변경 및 import 수정
gobad820 Jul 12, 2024
00231b0
feat(model): equals, hashCode 오버라이드 메소드 추가
gobad820 Jul 12, 2024
47e53bf
feat: findByName 메소드 추가
gobad820 Jul 12, 2024
1d165c1
fix: updateProduct 수정
gobad820 Jul 12, 2024
ab77779
feat: SetUp 메소드 추가
gobad820 Jul 12, 2024
8bd8807
feat: ProductController 뷰 템플릿 리턴
gobad820 Jul 12, 2024
4387a1f
feat: READEME step2 추가
gobad820 Jul 12, 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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,4 @@ out/

### Mac OS ###
.DS_Store
/src/test/java/gift/study/
8 changes: 7 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,7 @@
# spring-gift-jpa
# spring-gift-jpa

- [x] Member, Wish 연관 관계 매핑
- [x] Product, Wish 연관 관계 매핑
- [x] MemberRepositoryTest
- [x] WishRepositoryTest
- [x] ProductRepositoryTest
10 changes: 9 additions & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,17 @@ 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'
runtimeOnly 'com.h2database:h2'
implementation 'org.springframework.boot:spring-boot-starter-validation'
implementation 'io.jsonwebtoken:jjwt-api:0.12.6'
implementation 'org.mindrot:jbcrypt:0.4'
runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.12.6'
runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.12.6'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
testImplementation 'org.mockito:mockito-core:3.12.4'
testImplementation 'org.mockito:mockito-junit-jupiter:3.12.4'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
runtimeOnly 'com.h2database:h2'
}

tasks.named('test') {
Expand Down
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
14 changes: 14 additions & 0 deletions src/main/java/gift/annotation/LoginMember.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package gift.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface LoginMember {

}
54 changes: 54 additions & 0 deletions src/main/java/gift/controller/MemberController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package gift.controller;

import gift.model.Member;
import gift.service.MemberService;
import java.util.HashMap;
import java.util.Map;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
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.RequestMapping;

@Controller
@RequestMapping("/members")
public class MemberController {

private final MemberService memberService;

public MemberController(MemberService memberService) {
this.memberService = memberService;
}

@PostMapping("/register")

Choose a reason for hiding this comment

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

이렇게 로그인 처리해야하는 부분은 다른 개발자를 위해서 샘플로 README.md 파일에 추가해주는게 좋아요.

curl --location 'http://localhost:8080/members/register' \
--header 'Content-Type: application/json' \
--data-raw '{"email":"[email protected]","password":"test"}'

public ResponseEntity<Map<String, Object>> register(@RequestBody Member member) {
return memberService.registerMember(member)
.map(token -> { // Optional<String>을 mapping -> isPresent면 map 안 실행 // 매개변수 token으로
Map<String, Object> response = new HashMap<>();
response.put("message", "Member registered successfully");
response.put("token", token); // 생성된 토큰도 같이 보내준다.
return new ResponseEntity<>(response, HttpStatus.OK);
})
.orElseGet(() -> { // isEmpty
Map<String, Object> response = new HashMap<>();
response.put("message", "Registration failed");
return new ResponseEntity<>(response, HttpStatus.INTERNAL_SERVER_ERROR);
Comment on lines +34 to +36

Choose a reason for hiding this comment

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

오류가 났을때

console log 부분
List of constraint violations:[
	ConstraintViolationImpl{interpolatedMessage='올바른 형식의 이메일 주소여야 합니다', propertyPath=email, rootBeanClass=class gift.model.Member, messageTemplate='{jakarta.validation.constraints.Email.message}'}
]] with root cause

무조건적인 500 에러보다는 대략적인 오류코드나 메세지를 전달해주는것이 좋아요.

});
}

@PostMapping("/login")
public ResponseEntity<Map<String, Object>> login(@RequestBody Member member) {
return memberService.login(member.getEmail(), member.getPassword())
.map(token -> { // 토큰이 리턴 -> 정상 로그인 됨
Map<String, Object> response = new HashMap<>();
response.put("token", token);
return new ResponseEntity<>(response, HttpStatus.OK);
})
.orElseGet(() -> { // 토큰 리턴이 안됨 -> 로그인 안됨
Map<String, Object> response = new HashMap<>();
response.put("message", "Invalid email or password");
return new ResponseEntity<>(response, HttpStatus.FORBIDDEN);
});
}
}
144 changes: 144 additions & 0 deletions src/main/java/gift/controller/ProductController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
package gift.controller;

import gift.exception.ProductNotFoundException;
import gift.model.Product;
import gift.service.ProductService;
import jakarta.validation.Valid;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
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.PatchMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;

@Controller
public class ProductController {

private final ProductService productService;

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

@GetMapping("/")
public String getAllMyProducts(Model model) {
var productList = productService.getAllProducts();
model.addAttribute("productList", productList);
return "getproducts";
}

@GetMapping("/test")
public ResponseEntity<Map<String, Object>> getAllProducts() {
List<Product> products = productService.getAllProducts();
Map<String, Object> response = new HashMap<>();
response.put("message", "All products retrieved successfully.");
response.put("products", products);
return ResponseEntity.ok(response);
}

@GetMapping("/{id}")
public String getProductById(@PathVariable(name = "id") Long id, Model model) {
Map<String, Object> response = new HashMap<>();
try {
Product product = productService.getProductById(id);
model.addAttribute("productDto", product);
return "getproducts";
} catch (ProductNotFoundException ex) {
response.put("message", ex.getMessage());
model.addAttribute("errorMessage", response.get("message"));
return "getproducts";
}
}

@GetMapping("/product/add")
public String showAddProductForm(Model model) {
model.addAttribute("product", new Product());
return "addproductform";
}

@PostMapping("/product/add")
public String createProduct(@Valid @ModelAttribute(name = "product") Product product) {
productService.createProduct(product);
return "redirect:/";
}

@GetMapping(value = "/product/update/{id}")
public String showUPdateProductForm(@PathVariable("id") Long id, Model model) {
var product = productService.getProductById(id);
model.addAttribute("product", product);
return "updateproductform";
}

@PostMapping(value = "/product/update")
public String updateProduct(@Valid @ModelAttribute(name = "product") Product product) {
var updatedProduct = productService.updateProduct(product);
return "redirect:/";
}

@PatchMapping("/{id}")
public ResponseEntity<Map<String, Object>> patchProduct(@PathVariable Long id,
@RequestBody Map<String, Object> updates) {
Map<String, Object> response = new HashMap<>();
if (productService.patchProduct(id, updates) != null) {
Product updatedProduct = productService.getProductById(id);
response.put("message", "Product patched successfully.");
response.put("product", updatedProduct);
return ResponseEntity.ok(response);
}
response.put("message", "Failed to patch product.");
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(response);
}

@PatchMapping
public ResponseEntity<Map<String, Object>> patchProducts(
@RequestBody List<Map<String, Object>> updatesList) {
List<Optional<Product>> updatedProducts = productService.patchProducts(updatesList);
Map<String, Object> response = new HashMap<>();
int originalCount = updatesList.size();
int updateCount = updatedProducts.size();

response.put("updatedProducts", updatedProducts);

if (updateCount == originalCount) {
response.put("message", "All products patched successfully.");
return ResponseEntity.ok(response);
}

if (updateCount > 0) {
response.put("message", "Some products patched successfully.");
return ResponseEntity.status(HttpStatus.PARTIAL_CONTENT).body(response);
}

response.put("message", "No products patched.");
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(response);
}

@GetMapping("/product/delete/{id}")
public String showDeleteProductForm(@PathVariable("id") Long id, Model model) {
if (!productService.deleteProduct(id)) {
model.addAttribute("errorMessage", "잘못됨");
}
return "redirect:/";
}

@DeleteMapping("/{id}")
public ResponseEntity<Map<String, Object>> deleteProduct(@PathVariable Long id) {
boolean success = productService.deleteProduct(id);
Map<String, Object> response = new HashMap<>();
if (success) {
response.put("message", "Product deleted successfully.");
return ResponseEntity.noContent().build();
}
response.put("message", "Failed to delete product.");
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(response);
}
}
65 changes: 65 additions & 0 deletions src/main/java/gift/controller/WishController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package gift.controller;

import gift.annotation.LoginMember;
import gift.model.Member;
import gift.model.Wish;
import gift.service.WishService;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/wishes")
public class WishController {

private final WishService wishService;

public WishController(WishService wishService) {
this.wishService = wishService;
}

@GetMapping
public ResponseEntity<Map<String, Object>> getWishes(@LoginMember Member member) {
List<Wish> wishes = wishService.getWishesByMember(member);
Map<String, Object> response = new HashMap<>();
response.put("wishes", wishes);
return new ResponseEntity<>(response, HttpStatus.OK);
}

@PostMapping
public ResponseEntity<Map<String, Object>> addWish(@RequestBody Wish wish,
@LoginMember Member member) {
wish.setMemberId(member.getId());
Wish savedWish = wishService.addWish(wish);
Map<String, Object> response = new HashMap<>();
response.put("wish", savedWish);
return new ResponseEntity<>(response, HttpStatus.OK);
}

@DeleteMapping("/{id}")
public ResponseEntity<Map<String, Object>> removeWish(@PathVariable Long id,
@LoginMember Member member) {
boolean removed = wishService.removeWish(id, member);
Map<String, Object> response = new HashMap<>();
response.put("removed", removed);
return new ResponseEntity<>(response, HttpStatus.OK);
}

@GetMapping("/{id}")
public ResponseEntity<Map<String, Object>> getWishesById(@PathVariable Long productId,
@LoginMember Member member) {
List<Wish> wishes = wishService.getWishesByMember(member);
Map<String, Object> response = new HashMap<>();
response.put("wishes", wishes);
return new ResponseEntity<>(response, HttpStatus.OK);
}
}
8 changes: 8 additions & 0 deletions src/main/java/gift/exception/ForbiddenException.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package gift.exception;

public class ForbiddenException extends RuntimeException {

public ForbiddenException(String message) {
super(message);
}
}
8 changes: 8 additions & 0 deletions src/main/java/gift/exception/ForbiddenWordException.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package gift.exception;

public class ForbiddenWordException extends RuntimeException {

public ForbiddenWordException(String message) {
super(message);
}
}
36 changes: 36 additions & 0 deletions src/main/java/gift/exception/GiftExceptionHandler.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package gift.exception;

import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.NoSuchElementException;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;

@RestControllerAdvice
public class GiftExceptionHandler extends RuntimeException {

@ResponseStatus(value = HttpStatus.NOT_FOUND)
@ExceptionHandler(NoSuchElementException.class)
public void ResourceNotFoundException(NoSuchElementException e,
HttpServletResponse response) throws IOException {
response.sendRedirect("/");
}

@ResponseStatus(value = HttpStatus.NOT_FOUND)
@ExceptionHandler(IllegalArgumentException.class)
public void handleProductNameException(IllegalArgumentException e,
HttpServletResponse response) throws IOException {
response.sendRedirect("redirect:/product/add/form");
}

@ResponseStatus(value = HttpStatus.FORBIDDEN)
@ExceptionHandler(ForbiddenException.class)
public final ResponseEntity<Object> handleForbiddenException(ForbiddenException e) {
return new ResponseEntity<>(e.getMessage(), HttpStatus.FORBIDDEN);
}


}
Loading