diff --git a/README.md b/README.md index 75f82d8fa..eb631bc8c 100644 --- a/README.md +++ b/README.md @@ -1 +1,97 @@ -# spring-gift-enhancement \ No newline at end of file +# spring-gift-product + +## 과제 진행 요구 사항 + +- 기능을 구현하기 전 README.md에 구현할 기능 목록을 정리해 추가한다. + +- Git의 커밋 단위는 앞 단계에서 README.md에 정리한 기능 목록 단위로 추가한다. + +- AngularJS Git Commit Message Conventions을 참고해 커밋 메시지를 작성한다. + + + +## 기능 요구 사항 +상품 정보에 카테고리를 추가한다. 상품과 카테고리 모델 간의 관계를 고려하여 설계하고 구현한다. + +- 상품에는 항상 하나의 카테고리가 있어야 한다. +- 상품 카테고리는 수정할 수 있다. +- 관리자 화면에서 상품을 추가할 때 카테고리를 지정할 수 있다. +- 카테고리는 1차 카테고리만 있으며 2차 카테고리는 고려하지 않는다. +- 카테고리의 예시는 아래와 같다. +- 교환권, 상품권, 뷰티, 패션, 식품, 리빙/도서, 레저/스포츠, 아티스트/캐릭터, 유아동/반려, 디지털/가전, 카카오프렌즈, 트렌드 선물, 백화점, ... +아래 예시와 같이 HTTP 메시지를 주고받도록 구현한다. + +``` +Request +GET /api/categories HTTP/1.1 +Response +HTTP/1.1 200 +Content-Type: application/json +``` + +``` +[ +{ +"id": 91, +"name": "교환권", +"color": "#6c95d1", +"imageUrl": "https://gift-s.kakaocdn.net/dn/gift/images/m640/dimm_theme.png", +"description": "" +} +] +``` + +## 프로그래밍 요구 사항 +- 구현한 기능에 대해 적절한 테스트 전략을 생각하고 작성한다. + +- 힌트 +아래의 DDL을 보고 유추한다. + +``` +create table category +( +color varchar(7) not null, +id bigint not null auto_increment, +description varchar(255), +image_url varchar(255) not null, +name varchar(255) not null, +primary key (id) +) engine=InnoDB +``` + +``` +create table product +( +price integer not null, +category_id bigint not null, +id bigint not null auto_increment, +name varchar(15) not null, +image_url varchar(255) not null, +primary key (id) +) engine=InnoDB +``` + +``` +alter table category +add constraint uk_category unique (name) + +alter table product +add constraint fk_product_category_id_ref_category_id +foreign key (category_id) +references category (id) +``` + +## 프로그래밍 요구 사항 + +- 자바 코드 컨벤션을 지키면서 프로그래밍한다. + - 기본적으로 [Google Java Style Guide](https://google.github.io/styleguide/javaguide.html)를 원칙으로 한다. + - 단, 들여쓰기는 '2 spaces'가 아닌 '4 spaces'로 한다. +- indent(인덴트, 들여쓰기) depth를 3이 넘지 않도록 구현한다. 2까지만 허용한다. + - 예를 들어 while문 안에 if문이 있으면 들여쓰기는 2이다. + - 힌트: indent(인덴트, 들여쓰기) depth를 줄이는 좋은 방법은 함수(또는 메서드)를 분리하면 된다. +- 3항 연산자를 쓰지 않는다. +- 함수(또는 메서드)의 길이가 15라인을 넘어가지 않도록 구현한다. + - 함수(또는 메서드)가 한 가지 일만 잘 하도록 구현한다. +- else 예약어를 쓰지 않는다. + - else를 쓰지 말라고 하니 switch/case로 구현하는 경우가 있는데 switch/case도 허용하지 않는다. + - 힌트: if 조건절에서 값을 return하는 방식으로 구현하면 else를 사용하지 않아도 된다. diff --git a/build.gradle b/build.gradle index df7db9334..2eab4467f 100644 --- a/build.gradle +++ b/build.gradle @@ -18,9 +18,16 @@ repositories { } dependencies { - implementation 'org.springframework.boot:spring-boot-starter-jdbc' + implementation 'org.springframework.boot:spring-boot-starter-data-jpa' implementation 'org.springframework.boot:spring-boot-starter-thymeleaf' implementation 'org.springframework.boot:spring-boot-starter-web' + implementation 'org.springframework.boot:spring-boot-starter-validation' + implementation 'org.springframework.boot:spring-boot-starter-data-rest' + + implementation 'io.jsonwebtoken:jjwt-api:0.11.5' + runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.11.5' + runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.11.5' + runtimeOnly 'com.h2database:h2' testImplementation 'org.springframework.boot:spring-boot-starter-test' testRuntimeOnly 'org.junit.platform:junit-platform-launcher' diff --git a/src/main/java/gift/admin/AdminController.java b/src/main/java/gift/admin/AdminController.java index 6f36760a8..ee47bac2b 100644 --- a/src/main/java/gift/admin/AdminController.java +++ b/src/main/java/gift/admin/AdminController.java @@ -1,10 +1,13 @@ package gift.admin; +import gift.controller.category.CategoryRequest; +import gift.controller.category.CategoryResponse; import gift.controller.member.MemberRequest; import gift.controller.member.MemberResponse; import gift.controller.member.SignUpRequest; import gift.controller.product.ProductRequest; import gift.controller.product.ProductResponse; +import gift.service.CategoryService; import gift.service.MemberService; import gift.service.ProductService; import jakarta.validation.Valid; @@ -27,10 +30,52 @@ public class AdminController { private final ProductService productService; private final MemberService memberService; + private final CategoryService categoryService; - public AdminController(ProductService productService, MemberService memberService) { + public AdminController(ProductService productService, MemberService memberService, + CategoryService categoryService) { this.productService = productService; this.memberService = memberService; + this.categoryService = categoryService; + } + + @GetMapping("/categories") + public String listCategories(Model model, @RequestParam(defaultValue = "0") int page, + @RequestParam(defaultValue = "5") int size) { + Pageable pageable = PageRequest.of(page, size); + Page categories = categoryService.findAll(pageable); + model.addAttribute("categories", categories); + return "categories"; + } + + @GetMapping("/categories/add") + public String categoryAddForm(Model model) { + model.addAttribute("category", new CategoryRequest("", "", "", "")); + return "category-add-form"; + } + + @PostMapping("/categories/add") + public String addCategory(@ModelAttribute CategoryRequest category) { + categoryService.save(category); + return "redirect:/admin/categories"; + } + + @GetMapping("/categories/edit/{id}") + public String categoryEditForm(@PathVariable UUID id, Model model) { + model.addAttribute("category", categoryService.find(id)); + return "category-edit-form"; + } + + @PostMapping("/categories/edit/{id}") + public String editCategory(@PathVariable UUID id, @ModelAttribute CategoryRequest category) { + categoryService.update(id, category); + return "redirect:/admin/categories"; + } + + @GetMapping("/categories/delete/{id}") + public String deleteCategory(@PathVariable UUID id) { + categoryService.delete(id); + return "redirect:/admin/categories"; } @GetMapping("/members") @@ -66,9 +111,8 @@ public String editMember(@PathVariable UUID id, @ModelAttribute MemberRequest me return "redirect:/admin/members"; } - @GetMapping("/members/delete/{email}") - public String deleteMember(@PathVariable String email) { - UUID id = memberService.findByEmail(email).id(); + @GetMapping("/members/delete/{id}") + public String deleteMember(@PathVariable UUID id) { memberService.delete(id); return "redirect:/admin/members"; } @@ -88,7 +132,7 @@ public String listProducts(Model model, @RequestParam(defaultValue = "0") int pa @GetMapping("/products/add") public String productAddForm(Model model) { - model.addAttribute("product", new ProductRequest("", 0L, "")); + model.addAttribute("product", new ProductRequest("", 0L, "", "")); return "product-add-form"; } diff --git a/src/main/java/gift/controller/auth/LoginResponse.java b/src/main/java/gift/controller/auth/LoginResponse.java index 752b03d06..863065229 100644 --- a/src/main/java/gift/controller/auth/LoginResponse.java +++ b/src/main/java/gift/controller/auth/LoginResponse.java @@ -1,10 +1,11 @@ package gift.controller.auth; +import gift.domain.Grade; import java.util.UUID; -public record LoginResponse(UUID id, String email, String nickname, String grade) { +public record LoginResponse(UUID id, String email, String nickname, Grade grade) { public boolean isAdmin() { - return grade != null && grade.equals("admin"); + return grade != null && grade == Grade.ADMIN; } } \ No newline at end of file diff --git a/src/main/java/gift/controller/category/CategoryController.java b/src/main/java/gift/controller/category/CategoryController.java new file mode 100644 index 000000000..347dde0ad --- /dev/null +++ b/src/main/java/gift/controller/category/CategoryController.java @@ -0,0 +1,61 @@ +package gift.controller.category; + +import gift.config.LoginMember; +import gift.controller.auth.AuthController; +import gift.controller.auth.LoginResponse; +import gift.controller.product.ProductRequest; +import gift.controller.product.ProductResponse; +import gift.service.CategoryService; +import java.util.UUID; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +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.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/api/categories") +public class CategoryController { + CategoryService categoryService; + + public CategoryController(CategoryService categoryService) { + this.categoryService = categoryService; + } + + @GetMapping + public ResponseEntity> getAllCategories( + @RequestParam(defaultValue = "0") int page, @RequestParam(defaultValue = "5") int size) { + Pageable pageable = PageRequest.of(page, size); + return ResponseEntity.status(HttpStatus.OK).body(categoryService.findAll(pageable)); + } + + @PostMapping + public ResponseEntity createCategory(@LoginMember LoginResponse loginMember, + @RequestBody CategoryRequest product) { + return ResponseEntity.status(HttpStatus.CREATED).body(categoryService.save(product)); + } + + @PutMapping("/{categoryId}") + public ResponseEntity updateCategory(@LoginMember LoginResponse loginMember, + @PathVariable UUID categoryId, @RequestBody CategoryRequest category) { + AuthController.validateAdmin(loginMember); + return ResponseEntity.status(HttpStatus.OK).body(categoryService.update(categoryId, category)); + } + + @DeleteMapping("/{categoryId}") + public ResponseEntity deleteCategory(@LoginMember LoginResponse loginMember, + @PathVariable UUID categoryId) { + AuthController.validateAdmin(loginMember); + categoryService.delete(categoryId); + return ResponseEntity.status(HttpStatus.NO_CONTENT).body(null); + } +} diff --git a/src/main/java/gift/controller/category/CategoryMapper.java b/src/main/java/gift/controller/category/CategoryMapper.java new file mode 100644 index 000000000..c3d2c3fc2 --- /dev/null +++ b/src/main/java/gift/controller/category/CategoryMapper.java @@ -0,0 +1,16 @@ +package gift.controller.category; + +import gift.domain.Category; + +public class CategoryMapper { + + public static CategoryResponse toCategoryResponse(Category category) { + return new CategoryResponse(category.getId(), category.getName(), category.getColor(), + category.getDescription(), category.getImage_url()); + } + + public static Category from(CategoryRequest categoryRequest) { + return new Category(categoryRequest.name(), categoryRequest.color(), + categoryRequest.description(), categoryRequest.imageUrl()); + } +} diff --git a/src/main/java/gift/controller/category/CategoryRequest.java b/src/main/java/gift/controller/category/CategoryRequest.java new file mode 100644 index 000000000..a6717db01 --- /dev/null +++ b/src/main/java/gift/controller/category/CategoryRequest.java @@ -0,0 +1,5 @@ +package gift.controller.category; + +public record CategoryRequest (String name, String color, String description, String imageUrl){ + +} diff --git a/src/main/java/gift/controller/category/CategoryResponse.java b/src/main/java/gift/controller/category/CategoryResponse.java new file mode 100644 index 000000000..20db2fbe5 --- /dev/null +++ b/src/main/java/gift/controller/category/CategoryResponse.java @@ -0,0 +1,7 @@ +package gift.controller.category; + +import java.util.UUID; + +public record CategoryResponse(UUID id, String name, String color, String description, String imageUrl) { + +} \ No newline at end of file diff --git a/src/main/java/gift/controller/member/MemberRequest.java b/src/main/java/gift/controller/member/MemberRequest.java index f509d0253..077041d2e 100644 --- a/src/main/java/gift/controller/member/MemberRequest.java +++ b/src/main/java/gift/controller/member/MemberRequest.java @@ -1,5 +1,7 @@ package gift.controller.member; -public record MemberRequest(String email, String password, String grade) { +import gift.domain.Grade; + +public record MemberRequest(String email, String password, Grade grade) { } \ No newline at end of file diff --git a/src/main/java/gift/controller/member/MemberResponse.java b/src/main/java/gift/controller/member/MemberResponse.java index 42344ed19..1688c90f6 100644 --- a/src/main/java/gift/controller/member/MemberResponse.java +++ b/src/main/java/gift/controller/member/MemberResponse.java @@ -1,8 +1,9 @@ package gift.controller.member; +import gift.domain.Grade; import java.util.UUID; public record MemberResponse(UUID id, String email, String password, String nickName, - String grade) { + Grade grade) { } \ No newline at end of file diff --git a/src/main/java/gift/controller/product/ProductRequest.java b/src/main/java/gift/controller/product/ProductRequest.java index 36a0c4e26..9719a4e76 100644 --- a/src/main/java/gift/controller/product/ProductRequest.java +++ b/src/main/java/gift/controller/product/ProductRequest.java @@ -10,6 +10,8 @@ public record ProductRequest( @PositiveOrZero Long price, - String imageUrl) { + String imageUrl, + + String categoryName) { } \ No newline at end of file diff --git a/src/main/java/gift/domain/Category.java b/src/main/java/gift/domain/Category.java new file mode 100644 index 000000000..2e48e1d3b --- /dev/null +++ b/src/main/java/gift/domain/Category.java @@ -0,0 +1,75 @@ +package gift.domain; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.OneToMany; +import jakarta.persistence.PreRemove; +import java.util.LinkedList; +import java.util.List; +import java.util.UUID; + +@Entity +public class Category { + + public static Category defaultCategory; + @OneToMany(mappedBy = "category") + private final List products = new LinkedList<>(); + @Id + @GeneratedValue(strategy = GenerationType.UUID) + private UUID id; + @Column(nullable = false) + private String name; + @Column + private String color; + @Column + private String description; + @Column + private String image_url; + + public Category() { + } + + public Category(String name, String color, String description, String image_url) { + this.name = name; + this.color = color; + this.description = description; + this.image_url = image_url; + } + + @PreRemove + private void preRemove() { + for (Product product : products) { + product.setCategory(defaultCategory); + } + } + + public UUID getId() { + return id; + } + + public String getName() { + return name; + } + + public String getColor() { + return color; + } + + public String getDescription() { + return description; + } + + public String getImage_url() { + return image_url; + } + + public void updateDetails(String name, String color, String description, String image_url) { + this.name = name; + this.color = color; + this.description = description; + this.image_url = image_url; + } +} diff --git a/src/main/java/gift/domain/Grade.java b/src/main/java/gift/domain/Grade.java new file mode 100644 index 000000000..b828d8f90 --- /dev/null +++ b/src/main/java/gift/domain/Grade.java @@ -0,0 +1,7 @@ +package gift.domain; + +public enum Grade { + ADMIN, + USER, + GUEST +} \ No newline at end of file diff --git a/src/main/java/gift/domain/Member.java b/src/main/java/gift/domain/Member.java index 048a543fa..74834d0d9 100644 --- a/src/main/java/gift/domain/Member.java +++ b/src/main/java/gift/domain/Member.java @@ -4,6 +4,8 @@ import jakarta.persistence.CascadeType; import jakarta.persistence.Column; import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; import jakarta.persistence.Id; @@ -28,7 +30,8 @@ public class Member { @Column(nullable = false) private String nickName; @Column - private String grade; + @Enumerated(EnumType.STRING) + private Grade grade; public Member() { } @@ -42,7 +45,7 @@ public Member(String email, String password, String nickName) { @PrePersist public void prePersist() { if (grade == null) { - grade = "normal"; + grade = Grade.USER; } } @@ -62,7 +65,7 @@ public String getPassword() { return password; } - public String getGrade() { + public Grade getGrade() { return grade; } diff --git a/src/main/java/gift/domain/Product.java b/src/main/java/gift/domain/Product.java index fadf299cf..71fccb20f 100644 --- a/src/main/java/gift/domain/Product.java +++ b/src/main/java/gift/domain/Product.java @@ -6,6 +6,8 @@ import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; import jakarta.persistence.OneToMany; import java.util.LinkedList; import java.util.List; @@ -24,6 +26,9 @@ public class Product { @Column(nullable = false) private Long price; private String imageUrl; + @ManyToOne + @JoinColumn(name = "catetoryId", nullable = false) + private Category category; public Product() { } @@ -67,4 +72,8 @@ public void updateDetails(String name, Long price, String imageUrl) { this.price = price; this.imageUrl = imageUrl; } -} + + public void setCategory(Category category) { + this.category = category; + } +} \ No newline at end of file diff --git a/src/main/java/gift/exception/CategoryAlreadyExistsException.java b/src/main/java/gift/exception/CategoryAlreadyExistsException.java new file mode 100644 index 000000000..459411627 --- /dev/null +++ b/src/main/java/gift/exception/CategoryAlreadyExistsException.java @@ -0,0 +1,8 @@ +package gift.exception; + +public class CategoryAlreadyExistsException extends ConflictException{ + + public CategoryAlreadyExistsException() { + super("Category already exists"); + } +} diff --git a/src/main/java/gift/exception/CategoryNotExistsException.java b/src/main/java/gift/exception/CategoryNotExistsException.java new file mode 100644 index 000000000..c527c2857 --- /dev/null +++ b/src/main/java/gift/exception/CategoryNotExistsException.java @@ -0,0 +1,8 @@ +package gift.exception; + +public class CategoryNotExistsException extends NotFoundException{ + + public CategoryNotExistsException() { + super("Category not exists"); + } +} \ No newline at end of file diff --git a/src/main/java/gift/repository/CategoryRepository.java b/src/main/java/gift/repository/CategoryRepository.java new file mode 100644 index 000000000..afab3400e --- /dev/null +++ b/src/main/java/gift/repository/CategoryRepository.java @@ -0,0 +1,10 @@ +package gift.repository; + +import gift.domain.Category; +import java.util.Optional; +import java.util.UUID; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface CategoryRepository extends JpaRepository { + public Optional findByName(String name); +} diff --git a/src/main/java/gift/repository/ProductRepository.java b/src/main/java/gift/repository/ProductRepository.java index 418249957..3092eab36 100644 --- a/src/main/java/gift/repository/ProductRepository.java +++ b/src/main/java/gift/repository/ProductRepository.java @@ -14,4 +14,4 @@ public interface ProductRepository extends JpaRepository { Optional findByNameAndPriceAndImageUrl(String name, Long price, String imageUrl); Page findAll(Pageable pageable); -} +} \ No newline at end of file diff --git a/src/main/java/gift/repository/WishRepository.java b/src/main/java/gift/repository/WishRepository.java index 3bcc5b19c..c395c1e58 100644 --- a/src/main/java/gift/repository/WishRepository.java +++ b/src/main/java/gift/repository/WishRepository.java @@ -6,6 +6,8 @@ import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.lang.NonNull; +import org.springframework.lang.NonNullApi; import org.springframework.stereotype.Repository; @Repository @@ -15,7 +17,8 @@ public interface WishRepository extends JpaRepository { void deleteByMemberIdAndProductId(UUID memberId, UUID productId); - Page findPageable(Pageable pageable); + @NonNull + Page findAll(@NonNull Pageable pageable); Page findAllByMemberId(UUID memberId, Pageable pageable); } \ No newline at end of file diff --git a/src/main/java/gift/service/CategoryService.java b/src/main/java/gift/service/CategoryService.java new file mode 100644 index 000000000..829d2028b --- /dev/null +++ b/src/main/java/gift/service/CategoryService.java @@ -0,0 +1,67 @@ +package gift.service; + +import static gift.controller.category.CategoryMapper.from; +import static gift.controller.category.CategoryMapper.toCategoryResponse; + +import gift.controller.category.CategoryMapper; +import gift.controller.category.CategoryRequest; +import gift.controller.category.CategoryResponse; +import gift.domain.Category; +import gift.exception.CategoryAlreadyExistsException; +import gift.exception.CategoryNotExistsException; +import gift.repository.CategoryRepository; +import java.util.List; +import java.util.UUID; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +public class CategoryService { + + private final CategoryRepository categoryRepository; + + public CategoryService(CategoryRepository categoryRepository) { + this.categoryRepository = categoryRepository; + } + + @Transactional(readOnly = true) + public Page findAll(Pageable pageable) { + Page categoryPage = categoryRepository.findAll(pageable); + List categoryResponses = categoryPage.stream() + .map(CategoryMapper::toCategoryResponse).toList(); + return new PageImpl<>(categoryResponses, pageable, categoryPage.getTotalElements()); + } + + @Transactional(readOnly = true) + public CategoryResponse find(UUID id) { + Category target = categoryRepository.findById(id) + .orElseThrow(CategoryNotExistsException::new); + return toCategoryResponse(target); + } + + @Transactional + public CategoryResponse save(CategoryRequest category) { + categoryRepository.findByName(category.name()).ifPresent(p -> { + throw new CategoryAlreadyExistsException(); + }); + return toCategoryResponse(categoryRepository.save(from(category))); + } + + @Transactional + public CategoryResponse update(UUID categoryId, CategoryRequest category) { + Category target = categoryRepository.findById(categoryId) + .orElseThrow(CategoryNotExistsException::new); + target.updateDetails(category.name(), category.color(), category.description(), + category.imageUrl()); + return toCategoryResponse(target); + } + + @Transactional + public void delete(UUID id) { + categoryRepository.findById(id).orElseThrow(CategoryNotExistsException::new); + categoryRepository.deleteById(id); + } +} \ No newline at end of file diff --git a/src/main/java/gift/service/MemberService.java b/src/main/java/gift/service/MemberService.java index 289fe7f65..beabd4f58 100644 --- a/src/main/java/gift/service/MemberService.java +++ b/src/main/java/gift/service/MemberService.java @@ -18,6 +18,7 @@ import org.springframework.data.domain.PageImpl; import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; @Service public class MemberService { @@ -28,6 +29,7 @@ public MemberService(MemberRepository memberRepository) { this.memberRepository = memberRepository; } + @Transactional(readOnly = true) public Page findAll(Pageable pageable) { Page memberPage = memberRepository.findAll(pageable); List memberResponses = memberPage.stream() @@ -35,17 +37,20 @@ public Page findAll(Pageable pageable) { return new PageImpl<>(memberResponses, pageable, memberPage.getTotalElements()); } + @Transactional(readOnly = true) public MemberResponse findById(UUID id) { Member member = memberRepository.findById(id).orElseThrow(MemberNotExistsException::new); return toMemberResponse(member); } + @Transactional(readOnly = true) public MemberResponse findByEmail(String email) { Member member = memberRepository.findByEmail(email) .orElseThrow(MemberNotExistsException::new); return toMemberResponse(member); } + @Transactional public MemberResponse save(SignUpRequest member) { memberRepository.findByEmail(member.getEmail()).ifPresent(p -> { throw new MemberAlreadyExistsException(); @@ -59,14 +64,16 @@ public MemberResponse save(SignUpRequest member) { return toMemberResponse(memberRepository.save(MemberMapper.from(member))); } + @Transactional public MemberResponse update(UUID id, MemberRequest member) { Member target = memberRepository.findById(id).orElseThrow(MemberNotExistsException::new); target.setMember(member); return toMemberResponse(target); } + @Transactional public void delete(UUID id) { memberRepository.findById(id).orElseThrow(MemberNotExistsException::new); memberRepository.deleteById(id); } -} +} \ No newline at end of file diff --git a/src/main/java/gift/service/ProductService.java b/src/main/java/gift/service/ProductService.java index 9f98f9abb..5346c82b5 100644 --- a/src/main/java/gift/service/ProductService.java +++ b/src/main/java/gift/service/ProductService.java @@ -16,6 +16,7 @@ import org.springframework.data.domain.PageImpl; import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; @Service public class ProductService { @@ -26,6 +27,7 @@ public ProductService(ProductRepository productRepository) { this.productRepository = productRepository; } + @Transactional(readOnly = true) public Page findAll(Pageable pageable) { Page productPage = productRepository.findAll(pageable); List productResponses = productPage.stream() @@ -33,11 +35,13 @@ public Page findAll(Pageable pageable) { return new PageImpl<>(productResponses, pageable, productPage.getTotalElements()); } + @Transactional(readOnly = true) public ProductResponse find(UUID id) { Product target = productRepository.findById(id).orElseThrow(ProductNotExistsException::new); return toProductResponse(target); } + @Transactional public ProductResponse save(ProductRequest product) { productRepository.findByNameAndPriceAndImageUrl(product.name(), product.price(), product.imageUrl()).ifPresent(p -> { @@ -46,12 +50,14 @@ public ProductResponse save(ProductRequest product) { return toProductResponse(productRepository.save(from(product))); } + @Transactional public ProductResponse update(UUID id, ProductRequest product) { Product target = productRepository.findById(id).orElseThrow(ProductNotExistsException::new); target.updateDetails(product.name(), product.price(), product.imageUrl()); return toProductResponse(target); } + @Transactional public void delete(UUID id) { productRepository.findById(id).orElseThrow(ProductNotExistsException::new); productRepository.deleteById(id); diff --git a/src/main/java/gift/service/WishService.java b/src/main/java/gift/service/WishService.java index 383bab3a7..769e43b13 100644 --- a/src/main/java/gift/service/WishService.java +++ b/src/main/java/gift/service/WishService.java @@ -22,6 +22,7 @@ import org.springframework.data.domain.PageImpl; import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; @Service public class WishService { @@ -37,13 +38,15 @@ public WishService(ProductRepository productRepository, MemberRepository memberR this.wishRepository = wishRepository; } + @Transactional(readOnly = true) public Page findAll(Pageable pageable) { - Page wishPage = wishRepository.findPageable(pageable); + Page wishPage = wishRepository.findAll(pageable); List wishResponses = wishPage.stream().map(WishMapper::toWishResponse) .toList(); return new PageImpl<>(wishResponses, pageable, wishPage.getTotalElements()); } + @Transactional(readOnly = true) public Page findAllByMemberId(UUID memberId, Pageable pageable) { Page wishPage = wishRepository.findAllByMemberId(memberId, pageable); List wishResponses = wishPage.stream().map(WishMapper::toWishResponse) @@ -51,6 +54,7 @@ public Page findAllByMemberId(UUID memberId, Pageable pageable) { return new PageImpl<>(wishResponses, pageable, wishPage.getTotalElements()); } + @Transactional public WishResponse save(UUID memberId, WishCreateRequest wish) { wishRepository.findByMemberIdAndProductId(memberId, wish.productId()).ifPresent(p -> { throw new MemberAlreadyExistsException(); @@ -63,6 +67,7 @@ public WishResponse save(UUID memberId, WishCreateRequest wish) { return toWishResponse(wishRepository.save(new Wish(member, product, wish.count()))); } + @Transactional public WishResponse update(UUID memberId, UUID productId, WishUpdateRequest wish) { Wish target = wishRepository.findByMemberIdAndProductId(memberId, productId) .orElseThrow(WishNotExistsException::new); @@ -70,7 +75,7 @@ public WishResponse update(UUID memberId, UUID productId, WishUpdateRequest wish return toWishResponse(wishRepository.save(target)); } - // @Transactional + @Transactional public void delete(UUID memberId, UUID productId) { wishRepository.findByMemberIdAndProductId(memberId, productId) .orElseThrow(WishNotExistsException::new); diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index d7ebc56a6..6fbd655d4 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1,5 +1,5 @@ spring.application.name=spring-gift -server.port=8080 +server.port=8082 spring.h2.console.enabled=true logging.level.root=INFO spring.jpa.hibernate.ddl-auto=create diff --git a/src/main/resources/templates/categories.html b/src/main/resources/templates/categories.html new file mode 100644 index 000000000..ccd435532 --- /dev/null +++ b/src/main/resources/templates/categories.html @@ -0,0 +1,51 @@ + + + + + + members + + + +
+ + + + + + + + + + + + + + + + + + + +
NameColorDescriptionImageUrl
+
+
+ UPDATE +
+
+ DELETE +
+
+
+
+ + diff --git a/src/main/resources/templates/category-add-form.html b/src/main/resources/templates/category-add-form.html new file mode 100644 index 000000000..8c40c42d8 --- /dev/null +++ b/src/main/resources/templates/category-add-form.html @@ -0,0 +1,31 @@ + + + + + + Category Add + + + +
+
+ +
+ back +
+
+

Category Add

+
+

Name:

+

Color:

+

Description:

+

Image URL:

+ +
+
+ + diff --git a/src/main/resources/templates/category-edit-form.html b/src/main/resources/templates/category-edit-form.html new file mode 100644 index 000000000..56adf0de4 --- /dev/null +++ b/src/main/resources/templates/category-edit-form.html @@ -0,0 +1,32 @@ + + + + + + Member Add + + + +
+ +

Category Edit

+
+

Name:

+

Color:

+

Description:

+

Image url:

+ +
+
+ + diff --git a/src/main/resources/templates/member-edit-form.html b/src/main/resources/templates/member-edit-form.html index e51e5649f..16c720198 100644 --- a/src/main/resources/templates/member-edit-form.html +++ b/src/main/resources/templates/member-edit-form.html @@ -10,17 +10,20 @@

Member Edit

-
-

Password:

+ +

Nickname:

+

Grade:

+

Password:

diff --git a/src/main/resources/templates/members.html b/src/main/resources/templates/members.html index 692f162d7..134d8ba1b 100644 --- a/src/main/resources/templates/members.html +++ b/src/main/resources/templates/members.html @@ -22,22 +22,25 @@

Manage Members

EMAIL - PASSWORD + NICKNAME + GRADE + ACTION - + + -
-
- UPDATE -
-
- DELETE -
+
+
+ UPDATE
+
+ DELETE +
+