diff --git a/.github/workflows/spring-delploy.yml b/.github/workflows/spring-delploy.yml index 950b7e7..8d5ee5e 100644 --- a/.github/workflows/spring-delploy.yml +++ b/.github/workflows/spring-delploy.yml @@ -47,7 +47,9 @@ jobs: ssh ec2-user@${{ vars.EC2_HOST }} << 'EOF' cd /home/ec2-user/starchive/scripts - git checkout develop + # Dynamically checkout the branch that triggered the merge + git checkout ${{ github.event.pull_request.base.ref }} + # 스프링 배포 스크립트 실행 ./spring-deploy.sh diff --git a/BACK/spring-app/src/main/java/com/starchive/springapp/category/controller/CategoryController.java b/BACK/spring-app/src/main/java/com/starchive/springapp/category/controller/CategoryController.java index 85e4938..ec8643b 100644 --- a/BACK/spring-app/src/main/java/com/starchive/springapp/category/controller/CategoryController.java +++ b/BACK/spring-app/src/main/java/com/starchive/springapp/category/controller/CategoryController.java @@ -1,17 +1,25 @@ package com.starchive.springapp.category.controller; +import com.starchive.springapp.category.dto.CategoryCreateRequest; import com.starchive.springapp.category.dto.CategoryDto; +import com.starchive.springapp.category.dto.CategoryUpdateRequest; +import com.starchive.springapp.category.dto.CategoryUpdateResponse; import com.starchive.springapp.category.service.CategoryService; import com.starchive.springapp.global.dto.ResponseDto; import com.starchive.springapp.hashtag.dto.HashTagDto; import com.starchive.springapp.hashtag.service.HashTagService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; +import jakarta.validation.constraints.Null; import java.util.List; import lombok.RequiredArgsConstructor; 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.RestController; @RestController @@ -36,4 +44,28 @@ public ResponseEntity>> showHashTags(@PathVariable( ResponseDto> listResponseDto = new ResponseDto<>(categories); return ResponseEntity.ok(listResponseDto); } + + @PostMapping("/categories") + @Operation(summary = "카테고리 생성") + public ResponseEntity createCategory(@Valid CategoryCreateRequest categoryCreateRequest) { + categoryService.create(categoryCreateRequest); + + return ResponseEntity.noContent().build(); + } + + @PutMapping("/categories") + @Operation(summary = "카테고리 수정") + public ResponseEntity> updateCategory( + @Valid CategoryUpdateRequest categoryUpdateRequest) { + CategoryUpdateResponse updateResponse = categoryService.update(categoryUpdateRequest); + + return ResponseEntity.ok(new ResponseDto<>(updateResponse)); + } + + @DeleteMapping("/categories/{categoryId}") + @Operation(summary = "카테고리 삭제") + public ResponseEntity deleteCategory(@PathVariable("categoryId") Long categoryId) { + categoryService.delete(categoryId); + return ResponseEntity.noContent().build(); + } } diff --git a/BACK/spring-app/src/main/java/com/starchive/springapp/category/domain/Category.java b/BACK/spring-app/src/main/java/com/starchive/springapp/category/domain/Category.java index b4c63e8..4a74d6d 100644 --- a/BACK/spring-app/src/main/java/com/starchive/springapp/category/domain/Category.java +++ b/BACK/spring-app/src/main/java/com/starchive/springapp/category/domain/Category.java @@ -2,6 +2,7 @@ import static jakarta.persistence.FetchType.LAZY; +import jakarta.persistence.CascadeType; import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.GeneratedValue; @@ -33,7 +34,7 @@ public class Category { @Column(length = 100) String name; - @OneToMany(mappedBy = "parent", fetch = LAZY) + @OneToMany(mappedBy = "parent", fetch = LAZY, cascade = CascadeType.ALL, orphanRemoval = true) private List children = new ArrayList<>(); public Category(String name, Category parent) { @@ -44,9 +45,15 @@ public Category(String name, Category parent) { } } - private void changeParent(Category parent) { + public void changeName(String name) { + this.name = name; + } + + public void changeParent(Category parent) { this.parent = parent; - parent.getChildren().add(this); + if (parent != null) { + parent.getChildren().add(this); + } } } diff --git a/BACK/spring-app/src/main/java/com/starchive/springapp/category/dto/CategoryCreateRequest.java b/BACK/spring-app/src/main/java/com/starchive/springapp/category/dto/CategoryCreateRequest.java new file mode 100644 index 0000000..2821eed --- /dev/null +++ b/BACK/spring-app/src/main/java/com/starchive/springapp/category/dto/CategoryCreateRequest.java @@ -0,0 +1,20 @@ +package com.starchive.springapp.category.dto; + +import jakarta.annotation.Nullable; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@AllArgsConstructor +@NoArgsConstructor +public class CategoryCreateRequest { + @Size(max = 100) + @NotNull + private String name; + + @Nullable + private Long parentId; +} diff --git a/BACK/spring-app/src/main/java/com/starchive/springapp/category/dto/CategoryUpdateRequest.java b/BACK/spring-app/src/main/java/com/starchive/springapp/category/dto/CategoryUpdateRequest.java new file mode 100644 index 0000000..1eab455 --- /dev/null +++ b/BACK/spring-app/src/main/java/com/starchive/springapp/category/dto/CategoryUpdateRequest.java @@ -0,0 +1,23 @@ +package com.starchive.springapp.category.dto; + +import jakarta.annotation.Nullable; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@AllArgsConstructor +@NoArgsConstructor +public class CategoryUpdateRequest { + @NotNull + private Long categoryId; + + @Size(max = 100) + @NotNull + private String name; + + @Nullable + private Long parentId; +} diff --git a/BACK/spring-app/src/main/java/com/starchive/springapp/category/dto/CategoryUpdateResponse.java b/BACK/spring-app/src/main/java/com/starchive/springapp/category/dto/CategoryUpdateResponse.java new file mode 100644 index 0000000..2227807 --- /dev/null +++ b/BACK/spring-app/src/main/java/com/starchive/springapp/category/dto/CategoryUpdateResponse.java @@ -0,0 +1,29 @@ +package com.starchive.springapp.category.dto; + +import com.starchive.springapp.category.domain.Category; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@AllArgsConstructor +@NoArgsConstructor +public class CategoryUpdateResponse { + private Long categoryId; + + private String name; + + private Long parentId; + + public static CategoryUpdateResponse from(Category category) { + CategoryUpdateResponse categoryUpdateResponse = new CategoryUpdateResponse(); + categoryUpdateResponse.categoryId = category.getId(); + categoryUpdateResponse.name = category.getName(); + if (category.getParent() == null) { + categoryUpdateResponse.parentId = null; + } else { + categoryUpdateResponse.parentId = category.getParent().getId(); + } + return categoryUpdateResponse; + } +} diff --git a/BACK/spring-app/src/main/java/com/starchive/springapp/category/exception/CategoryAlreadyExistsException.java b/BACK/spring-app/src/main/java/com/starchive/springapp/category/exception/CategoryAlreadyExistsException.java new file mode 100644 index 0000000..afb2c70 --- /dev/null +++ b/BACK/spring-app/src/main/java/com/starchive/springapp/category/exception/CategoryAlreadyExistsException.java @@ -0,0 +1,26 @@ +package com.starchive.springapp.category.exception; + +import static com.starchive.springapp.global.ErrorMessage.ALREADY_EXISTS_CATEGORY; + +public class CategoryAlreadyExistsException extends RuntimeException { + public CategoryAlreadyExistsException() { + super(ALREADY_EXISTS_CATEGORY); + } + + public CategoryAlreadyExistsException(String message) { + super(message); + } + + public CategoryAlreadyExistsException(String message, Throwable cause) { + super(message, cause); + } + + public CategoryAlreadyExistsException(Throwable cause) { + super(cause); + } + + protected CategoryAlreadyExistsException(String message, Throwable cause, boolean enableSuppression, + boolean writableStackTrace) { + super(message, cause, enableSuppression, writableStackTrace); + } +} diff --git a/BACK/spring-app/src/main/java/com/starchive/springapp/category/repository/CategoryRepository.java b/BACK/spring-app/src/main/java/com/starchive/springapp/category/repository/CategoryRepository.java index 37d0bc6..3cd4ddb 100644 --- a/BACK/spring-app/src/main/java/com/starchive/springapp/category/repository/CategoryRepository.java +++ b/BACK/spring-app/src/main/java/com/starchive/springapp/category/repository/CategoryRepository.java @@ -9,8 +9,14 @@ public interface CategoryRepository extends JpaRepository { @Query("SELECT c FROM Category c LEFT JOIN FETCH c.children left join fetch c.parent WHERE c.id = :id") - Optional findByIdWithChildren(@Param("id") Long id); + Optional findByIdWithParentAndChildren(@Param("id") Long id); @Query("SELECT DISTINCT c FROM Category c LEFT JOIN FETCH c.children WHERE c.parent IS NULL") List findRootCategoriesWithChildren(); + + Optional findByName(@Param("name") String name); + + @Query("select c from Category c left join fetch c.parent where c.id = :id") + Optional findByIdWithParent(@Param("id") Long id); + } diff --git a/BACK/spring-app/src/main/java/com/starchive/springapp/category/service/CategoryService.java b/BACK/spring-app/src/main/java/com/starchive/springapp/category/service/CategoryService.java index 77ed129..1703657 100644 --- a/BACK/spring-app/src/main/java/com/starchive/springapp/category/service/CategoryService.java +++ b/BACK/spring-app/src/main/java/com/starchive/springapp/category/service/CategoryService.java @@ -1,17 +1,26 @@ package com.starchive.springapp.category.service; import com.starchive.springapp.category.domain.Category; +import com.starchive.springapp.category.dto.CategoryCreateRequest; import com.starchive.springapp.category.dto.CategoryDto; +import com.starchive.springapp.category.dto.CategoryUpdateRequest; +import com.starchive.springapp.category.dto.CategoryUpdateResponse; +import com.starchive.springapp.category.exception.CategoryAlreadyExistsException; import com.starchive.springapp.category.exception.CategoryNotFoundException; import com.starchive.springapp.category.repository.CategoryRepository; +import com.starchive.springapp.post.repository.PostRepository; +import java.util.ArrayList; import java.util.List; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; @Service +@Transactional @RequiredArgsConstructor public class CategoryService { private final CategoryRepository categoryRepository; + private final PostRepository postRepository; public List findAll() { List rootCateGories = categoryRepository.findRootCategoriesWithChildren(); @@ -19,8 +28,99 @@ public List findAll() { } public Category findOne(Long id) { - return categoryRepository.findByIdWithChildren(id).orElseThrow(CategoryNotFoundException::new); + return categoryRepository.findByIdWithParentAndChildren(id).orElseThrow(CategoryNotFoundException::new); } + public void create(CategoryCreateRequest categoryCreateRequest) { + categoryRepository.findByName(categoryCreateRequest.getName()).ifPresent(category -> { + throw new CategoryAlreadyExistsException(); + }); + + if (categoryCreateRequest.getParentId() == null) { + Category category = new Category(categoryCreateRequest.getName(), null); + categoryRepository.save(category); + return; + } + + Category parentCategory = categoryRepository.findById(categoryCreateRequest.getParentId()) + .orElseThrow(() -> new CategoryNotFoundException("존재하지 않는 부모 카테고리입니다.")); + + Category category = new Category(categoryCreateRequest.getName(), parentCategory); + categoryRepository.save(category); + + } + + public CategoryUpdateResponse update(CategoryUpdateRequest categoryUpdateRequest) { + Category category = categoryRepository.findByIdWithParent(categoryUpdateRequest.getCategoryId()) + .orElseThrow(CategoryNotFoundException::new); + + if (!category.getName().equals(categoryUpdateRequest.getName())) { + categoryRepository.findByName(categoryUpdateRequest.getName()).ifPresent(findOne -> { + throw new CategoryAlreadyExistsException(categoryUpdateRequest.getName() + " 은 이미 존재하는 카테고리 이름입니다."); + }); + + category.changeName(categoryUpdateRequest.getName()); + } + + updateParentCategory(categoryUpdateRequest, category); + + return CategoryUpdateResponse.from(category); + } + + public void delete(Long id) { + if (id == 0) { + throw new RuntimeException("삭제할 수 없는 카테고리입니다."); + } + Category category = categoryRepository.findByIdWithParentAndChildren(id) + .orElseThrow(CategoryNotFoundException::new); + + List categoryIds = new ArrayList<>(); + categoryIds.add(category.getId()); + + if (!category.getChildren().isEmpty()) { + for (Category child : category.getChildren()) { + categoryIds.add(child.getId()); + } + } + + postRepository.bulkUpdateToNoneCategory(categoryIds); + + categoryRepository.deleteById(category.getId()); + } + + private Category updateParentCategory(CategoryUpdateRequest categoryUpdateRequest, Category category) { + Long newParentId = categoryUpdateRequest.getParentId(); + if (category.getParent() != null && newParentId == null) { + category.changeParent(null); + return category; + } + + if (category.getParent() != null && newParentId != null) { + + Category parentCategory = findParentCategoryById(newParentId); + + if (category.getParent().getId() != newParentId) { + category.changeParent(parentCategory); + } + + return category; + } + + if (category.getParent() == null && newParentId != null) { + + Category parentCategory = findParentCategoryById(newParentId); + + category.changeParent(parentCategory); + + return category; + } + + return category; + } + + private Category findParentCategoryById(Long parentId) { + return categoryRepository.findById(parentId) + .orElseThrow(() -> new CategoryNotFoundException("존재하지 않는 부모 카테고리입니다.")); + } } diff --git a/BACK/spring-app/src/main/java/com/starchive/springapp/global/ErrorMessage.java b/BACK/spring-app/src/main/java/com/starchive/springapp/global/ErrorMessage.java index a96681d..24bd0d5 100644 --- a/BACK/spring-app/src/main/java/com/starchive/springapp/global/ErrorMessage.java +++ b/BACK/spring-app/src/main/java/com/starchive/springapp/global/ErrorMessage.java @@ -7,4 +7,5 @@ public class ErrorMessage { final public static String CATEGORY_NOT_FOUND = "카테고리가 존재하지 않습니다."; final public static String HASHTAG_NOT_FOUND = "해쉬태그가 존재하지 않습니다."; final public static String POST_NOT_FOUND = "게시글이 존재하지 않습니다."; + final public static String ALREADY_EXISTS_CATEGORY = "이미 존재하는 카테고리 이름입니다."; } diff --git a/BACK/spring-app/src/main/java/com/starchive/springapp/global/dto/ErrorResult.java b/BACK/spring-app/src/main/java/com/starchive/springapp/global/dto/ErrorResult.java new file mode 100644 index 0000000..edd7604 --- /dev/null +++ b/BACK/spring-app/src/main/java/com/starchive/springapp/global/dto/ErrorResult.java @@ -0,0 +1,12 @@ +package com.starchive.springapp.global.dto; + +import lombok.AllArgsConstructor; +import lombok.Data; +import org.springframework.http.HttpStatus; + +@Data +@AllArgsConstructor +public class ErrorResult { + private HttpStatus code; + private String message; +} diff --git a/BACK/spring-app/src/main/java/com/starchive/springapp/global/exception/GlobalExceptionHandler.java b/BACK/spring-app/src/main/java/com/starchive/springapp/global/exception/GlobalExceptionHandler.java index abba3eb..c6c6142 100644 --- a/BACK/spring-app/src/main/java/com/starchive/springapp/global/exception/GlobalExceptionHandler.java +++ b/BACK/spring-app/src/main/java/com/starchive/springapp/global/exception/GlobalExceptionHandler.java @@ -1,8 +1,8 @@ package com.starchive.springapp.global.exception; -import static com.starchive.springapp.global.ErrorMessage.INVALID_FILE_SIZE; - +import com.starchive.springapp.category.exception.CategoryAlreadyExistsException; import com.starchive.springapp.category.exception.CategoryNotFoundException; +import com.starchive.springapp.global.dto.ErrorResult; import com.starchive.springapp.hashtag.exception.HashTagNotFoundException; import com.starchive.springapp.post.exception.PostNotFoundException; import java.util.HashMap; @@ -28,21 +28,23 @@ public ResponseEntity> handleValidationExceptions(MethodArgu return ResponseEntity.badRequest().body(errors); } - @ExceptionHandler(CategoryNotFoundException.class) - public ResponseEntity handleMaxSizeException(CategoryNotFoundException ex) { - return ResponseEntity.status(HttpStatus.NOT_FOUND) - .body(ex.getMessage()); + @ExceptionHandler({CategoryNotFoundException.class, PostNotFoundException.class, HashTagNotFoundException.class}) + public ResponseEntity handleNotFoundException(RuntimeException ex) { + ErrorResult errorResult = new ErrorResult(HttpStatus.BAD_REQUEST, ex.getMessage()); + return ResponseEntity.badRequest() + .body(errorResult); } - @ExceptionHandler(PostNotFoundException.class) - public ResponseEntity handlePostNotFoundException(PostNotFoundException ex) { - return ResponseEntity.status(HttpStatus.NOT_FOUND) - .body(ex.getMessage()); + @ExceptionHandler({CategoryAlreadyExistsException.class}) + public ResponseEntity handleAlreadyExistsException(RuntimeException ex) { + ErrorResult errorResult = new ErrorResult(HttpStatus.BAD_REQUEST, ex.getMessage()); + return ResponseEntity.badRequest() + .body(errorResult); } - @ExceptionHandler(HashTagNotFoundException.class) - public ResponseEntity handleHashTagNotFoundException(HashTagNotFoundException ex) { - return ResponseEntity.status(HttpStatus.PAYLOAD_TOO_LARGE) - .body(INVALID_FILE_SIZE); + @ExceptionHandler(RuntimeException.class) + public ResponseEntity handleRuntimeException(RuntimeException ex) { + ErrorResult errorResult = new ErrorResult(HttpStatus.INTERNAL_SERVER_ERROR, ex.getMessage()); + return ResponseEntity.internalServerError().body(errorResult); } } diff --git a/BACK/spring-app/src/main/java/com/starchive/springapp/post/repository/PostRepository.java b/BACK/spring-app/src/main/java/com/starchive/springapp/post/repository/PostRepository.java index 6622a6e..173a4ae 100644 --- a/BACK/spring-app/src/main/java/com/starchive/springapp/post/repository/PostRepository.java +++ b/BACK/spring-app/src/main/java/com/starchive/springapp/post/repository/PostRepository.java @@ -5,6 +5,7 @@ import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Modifying; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; @@ -16,4 +17,7 @@ Page findManyByCategoryIds(@Param("categoryIds") List categoryIds, @Param("postIds") List postIds, Pageable pageable); + @Modifying + @Query("update Post p set p.category.id = 0 where p.category.id in :categoryIds") + int bulkUpdateToNoneCategory(@Param("categoryIds") List categoryIds); } diff --git a/BACK/spring-app/src/main/resources/data.sql b/BACK/spring-app/src/main/resources/data.sql new file mode 100644 index 0000000..e171dac --- /dev/null +++ b/BACK/spring-app/src/main/resources/data.sql @@ -0,0 +1 @@ +INSERT IGNORE INTO Categories (categoryId, name) VALUES (0, '카테고리 없음'); \ No newline at end of file diff --git a/BACK/spring-app/src/test/java/com/starchive/springapp/category/dto/CategoryDtoTest.java b/BACK/spring-app/src/test/java/com/starchive/springapp/category/dto/CategoryDtoTest.java index c733d82..3ec2d1f 100644 --- a/BACK/spring-app/src/test/java/com/starchive/springapp/category/dto/CategoryDtoTest.java +++ b/BACK/spring-app/src/test/java/com/starchive/springapp/category/dto/CategoryDtoTest.java @@ -35,7 +35,7 @@ class CategoryDtoTest { entityManager.flush(); entityManager.clear(); System.out.println("---------------------쿼리3"); - Category findOne = categoryRepository.findByIdWithChildren(parent.getId()).get(); + Category findOne = categoryRepository.findByIdWithParentAndChildren(parent.getId()).get(); System.out.println("---------------------쿼리4"); //when diff --git a/BACK/spring-app/src/test/java/com/starchive/springapp/category/service/CategoryServiceTest.java b/BACK/spring-app/src/test/java/com/starchive/springapp/category/service/CategoryServiceTest.java index 492c85c..18ad713 100644 --- a/BACK/spring-app/src/test/java/com/starchive/springapp/category/service/CategoryServiceTest.java +++ b/BACK/spring-app/src/test/java/com/starchive/springapp/category/service/CategoryServiceTest.java @@ -1,15 +1,25 @@ package com.starchive.springapp.category.service; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; import com.starchive.springapp.category.domain.Category; +import com.starchive.springapp.category.dto.CategoryCreateRequest; import com.starchive.springapp.category.dto.CategoryDto; +import com.starchive.springapp.category.dto.CategoryUpdateRequest; +import com.starchive.springapp.category.dto.CategoryUpdateResponse; +import com.starchive.springapp.category.exception.CategoryAlreadyExistsException; +import com.starchive.springapp.category.exception.CategoryNotFoundException; import com.starchive.springapp.category.repository.CategoryRepository; +import com.starchive.springapp.post.domain.Post; +import com.starchive.springapp.post.repository.PostRepository; +import jakarta.persistence.EntityManager; +import java.time.LocalDateTime; import java.util.List; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.jdbc.Sql; import org.springframework.transaction.annotation.Transactional; @SpringBootTest @@ -19,9 +29,13 @@ class CategoryServiceTest { CategoryRepository categoryRepository; @Autowired CategoryService categoryService; + @Autowired + PostRepository postRepository; + @Autowired + EntityManager em; - @BeforeEach - void init() { + @Test + void 전체_목록_조회_테스트() { //given Category parent1 = new Category("알고리즘", null); Category child1 = new Category("자료구조", parent1); @@ -35,11 +49,6 @@ void init() { categoryRepository.save(parent2); categoryRepository.save(child3); categoryRepository.save(parent3); - } - - - @Test - void 전체_목록_조회_테스트() { // when List response = categoryService.findAll(); @@ -53,4 +62,199 @@ void init() { assertThat(response.get(1).getChildren().get(0).getName()).isEqualTo("요구사항"); } + @Test + public void 루트_카테고리_생성_테스트() throws Exception { + //given + CategoryCreateRequest request = new CategoryCreateRequest("알고리즘", null); + + //when + categoryService.create(request); + List categories = categoryRepository.findAll(); + Category category = categories.get(0); + + //then + assertThat(categoryRepository.findAll().size()).isEqualTo(1); + assertThat(category.getName()).isEqualTo("알고리즘"); + } + + @Test + public void 하위_카테고리_생성_테스트() throws Exception { + //given + CategoryCreateRequest request1 = new CategoryCreateRequest("알고리즘", null); + categoryService.create(request1); + + Category category = categoryRepository.findByName("알고리즘").get(); + + CategoryCreateRequest request2 = new CategoryCreateRequest("DP", category.getId()); + categoryService.create(request2); + //when + Category findOne = categoryRepository.findByName("DP").get(); + //then + assertThat(categoryRepository.findAll().size()).isEqualTo(2); + assertThat(findOne.getName()).isEqualTo("DP"); + assertThat(findOne.getParent().getName()).isEqualTo("알고리즘"); + + } + + @Test + public void 이미_존재하는_카테고리_이름_생성_테스트() throws Exception { + //given + CategoryCreateRequest request1 = new CategoryCreateRequest("알고리즘", null); + categoryService.create(request1); + CategoryCreateRequest request2 = new CategoryCreateRequest("알고리즘", null); + //when + //then + + assertThatThrownBy(() -> categoryService.create(request2)).isInstanceOf(CategoryAlreadyExistsException.class) + .hasMessage("이미 존재하는 카테고리 이름입니다."); + } + + + @Test + public void 카테고리_이름_수정_테스트() throws Exception { + //given + Category category = new Category("알고리즘", null); + categoryRepository.save(category); + + CategoryUpdateRequest updateRequest = new CategoryUpdateRequest(category.getId(), "프로젝트", null); + + //when + CategoryUpdateResponse updateResponse = categoryService.update(updateRequest); + + Category findOne = categoryRepository.findById(category.getId()).get(); + //then + assertThat(findOne.getName()).isEqualTo("프로젝트"); + } + + @Test + public void 카테고리_다른_부모로_수정_테스트() throws Exception { + //given + Category parent1 = new Category("프로젝트", null); + categoryRepository.save(parent1); + Category parent2 = new Category("알고리즘", null); + categoryRepository.save(parent2); + Category child = new Category("DP", parent1); + categoryRepository.save(child); + + CategoryUpdateRequest updateRequest = new CategoryUpdateRequest(child.getId(), "DP", parent2.getId()); + + //when + CategoryUpdateResponse updateResponse = categoryService.update(updateRequest); + + Category findOne = categoryRepository.findById(child.getId()).get(); + //then + assertThat(findOne.getName()).isEqualTo("DP"); + assertThat(findOne.getParent().getName()).isEqualTo("알고리즘"); + } + + @Test + public void 카테고리_루트카테고리로_수정_테스트() throws Exception { + //given + Category parent1 = new Category("알고리즘", null); + categoryRepository.save(parent1); + Category child = new Category("DP", parent1); + categoryRepository.save(child); + + CategoryUpdateRequest updateRequest = new CategoryUpdateRequest(child.getId(), "DP", null); + + //when + CategoryUpdateResponse updateResponse = categoryService.update(updateRequest); + + Category findOne = categoryRepository.findById(child.getId()).get(); + //then + assertThat(findOne.getName()).isEqualTo("DP"); + assertThat(findOne.getParent()).isNull(); + } + + @Test + public void 카테고리_수정_이미_존재하는_이름예외_테스트() throws Exception { + //given + Category category = new Category("알고리즘", null); + categoryRepository.save(category); + Category category1 = new Category("프로젝트", null); + categoryRepository.save(category1); + + CategoryUpdateRequest updateRequest = new CategoryUpdateRequest(category.getId(), "프로젝트", null); + + //when + assertThatThrownBy(() -> categoryService.update(updateRequest)).isInstanceOf( + CategoryAlreadyExistsException.class); + } + + @Test + public void 카테고리_수정_존재하지않는_부모카테고리로_수정_테스트() throws Exception { + //given + Category category = new Category("알고리즘", null); + categoryRepository.save(category); + + CategoryUpdateRequest updateRequest = new CategoryUpdateRequest(category.getId(), "프로젝트", -1L); + + //when + assertThatThrownBy(() -> categoryService.update(updateRequest)).isInstanceOf(CategoryNotFoundException.class); + } + + @Test + @Sql(statements = "INSERT INTO Categories (categoryId, name) VALUES (0, 'Default Category')") + public void 자식_카테고리_삭제_테스트() throws Exception { + //given + Category parent1 = new Category("알고리즘", null); + categoryRepository.save(parent1); + Category child = new Category("DP", parent1); + categoryRepository.save(child); + Category child2 = new Category("greedy", parent1); + categoryRepository.save(child2); + Post post1 = new Post(null, "title1", "content", "author1", "123", LocalDateTime.now(), child); + postRepository.save(post1); + + //when + em.flush(); + em.clear(); + categoryService.delete(child.getId()); + em.flush(); + em.clear(); + post1 = postRepository.findById(post1.getId()).orElseThrow(); + List categories = categoryRepository.findAll(); + for (Category category : categories) { + System.out.println(category.getName()); + } + + //then + assertThat(categories.size()).isEqualTo(3); + assertThat(post1.getCategory().getId()).isEqualTo(0); + } + + @Test + @Sql(statements = "INSERT INTO Categories (categoryId, name) VALUES (0, 'Default Category')") + public void 부모_카테고리_삭제_테스트() throws Exception { + //given + Category parent1 = new Category("알고리즘", null); + categoryRepository.save(parent1); + Category child = new Category("DP", parent1); + categoryRepository.save(child); + Category child2 = new Category("greedy", parent1); + categoryRepository.save(child2); + Post post1 = new Post(null, "title1", "content", "author1", "123", LocalDateTime.now(), parent1); + Post post2 = new Post(null, "title1", "content", "author1", "123", LocalDateTime.now(), child); + Post post3 = new Post(null, "title1", "content", "author1", "123", LocalDateTime.now(), child2); + postRepository.save(post1); + postRepository.save(post2); + postRepository.save(post3); + + //when + categoryService.delete(parent1.getId()); + + em.flush(); + em.clear(); + post1 = postRepository.findById(post1.getId()).orElseThrow(); + post2 = postRepository.findById(post2.getId()).orElseThrow(); + post3 = postRepository.findById(post3.getId()).orElseThrow(); + + //then + assertThat(categoryRepository.findAll().size()).isEqualTo(1); + assertThat(post1.getCategory().getId()).isEqualTo(0); + assertThat(post2.getCategory().getId()).isEqualTo(0); + assertThat(post3.getCategory().getId()).isEqualTo(0); + } + + } \ No newline at end of file diff --git a/FRONT/package-lock.json b/FRONT/package-lock.json index d393695..95149e8 100644 --- a/FRONT/package-lock.json +++ b/FRONT/package-lock.json @@ -36,7 +36,8 @@ "prismjs": "^1.29.0", "typescript": "~5.6.2", "typescript-eslint": "^8.11.0", - "vite": "^5.4.10" + "vite": "^5.4.10", + "vite-plugin-svgr": "^4.3.0" } }, "node_modules/@adobe/css-tools": { @@ -1197,6 +1198,49 @@ "node": ">=14.0.0" } }, + "node_modules/@rollup/pluginutils": { + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.1.4.tgz", + "integrity": "sha512-USm05zrsFxYLPdWWq+K3STlWiT/3ELn3RcV5hJMghpeAIhxfsUIg6mt12CBJBInWMV4VneoV7SfGv8xIwo2qNQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "estree-walker": "^2.0.2", + "picomatch": "^4.0.2" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/pluginutils/node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/pluginutils/node_modules/picomatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", + "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/@rollup/rollup-android-arm-eabi": { "version": "4.24.4", "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.24.4.tgz", @@ -1431,6 +1475,231 @@ "win32" ] }, + "node_modules/@svgr/babel-plugin-add-jsx-attribute": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-8.0.0.tgz", + "integrity": "sha512-b9MIk7yhdS1pMCZM8VeNfUlSKVRhsHZNMl5O9SfaX0l0t5wjdgu4IDzGB8bpnGBBOjGST3rRFVsaaEtI4W6f7g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/babel-plugin-remove-jsx-attribute": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-remove-jsx-attribute/-/babel-plugin-remove-jsx-attribute-8.0.0.tgz", + "integrity": "sha512-BcCkm/STipKvbCl6b7QFrMh/vx00vIP63k2eM66MfHJzPr6O2U0jYEViXkHJWqXqQYjdeA9cuCl5KWmlwjDvbA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/babel-plugin-remove-jsx-empty-expression": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-remove-jsx-empty-expression/-/babel-plugin-remove-jsx-empty-expression-8.0.0.tgz", + "integrity": "sha512-5BcGCBfBxB5+XSDSWnhTThfI9jcO5f0Ai2V24gZpG+wXF14BzwxxdDb4g6trdOux0rhibGs385BeFMSmxtS3uA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/babel-plugin-replace-jsx-attribute-value": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-replace-jsx-attribute-value/-/babel-plugin-replace-jsx-attribute-value-8.0.0.tgz", + "integrity": "sha512-KVQ+PtIjb1BuYT3ht8M5KbzWBhdAjjUPdlMtpuw/VjT8coTrItWX6Qafl9+ji831JaJcu6PJNKCV0bp01lBNzQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/babel-plugin-svg-dynamic-title": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-svg-dynamic-title/-/babel-plugin-svg-dynamic-title-8.0.0.tgz", + "integrity": "sha512-omNiKqwjNmOQJ2v6ge4SErBbkooV2aAWwaPFs2vUY7p7GhVkzRkJ00kILXQvRhA6miHnNpXv7MRnnSjdRjK8og==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/babel-plugin-svg-em-dimensions": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-svg-em-dimensions/-/babel-plugin-svg-em-dimensions-8.0.0.tgz", + "integrity": "sha512-mURHYnu6Iw3UBTbhGwE/vsngtCIbHE43xCRK7kCw4t01xyGqb2Pd+WXekRRoFOBIY29ZoOhUCTEweDMdrjfi9g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/babel-plugin-transform-react-native-svg": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-transform-react-native-svg/-/babel-plugin-transform-react-native-svg-8.1.0.tgz", + "integrity": "sha512-Tx8T58CHo+7nwJ+EhUwx3LfdNSG9R2OKfaIXXs5soiy5HtgoAEkDay9LIimLOcG8dJQH1wPZp/cnAv6S9CrR1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/babel-plugin-transform-svg-component": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-transform-svg-component/-/babel-plugin-transform-svg-component-8.0.0.tgz", + "integrity": "sha512-DFx8xa3cZXTdb/k3kfPeaixecQLgKh5NVBMwD0AQxOzcZawK4oo1Jh9LbrcACUivsCA7TLG8eeWgrDXjTMhRmw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/babel-preset": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-preset/-/babel-preset-8.1.0.tgz", + "integrity": "sha512-7EYDbHE7MxHpv4sxvnVPngw5fuR6pw79SkcrILHJ/iMpuKySNCl5W1qcwPEpU+LgyRXOaAFgH0KhwD18wwg6ug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@svgr/babel-plugin-add-jsx-attribute": "8.0.0", + "@svgr/babel-plugin-remove-jsx-attribute": "8.0.0", + "@svgr/babel-plugin-remove-jsx-empty-expression": "8.0.0", + "@svgr/babel-plugin-replace-jsx-attribute-value": "8.0.0", + "@svgr/babel-plugin-svg-dynamic-title": "8.0.0", + "@svgr/babel-plugin-svg-em-dimensions": "8.0.0", + "@svgr/babel-plugin-transform-react-native-svg": "8.1.0", + "@svgr/babel-plugin-transform-svg-component": "8.0.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/core": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@svgr/core/-/core-8.1.0.tgz", + "integrity": "sha512-8QqtOQT5ACVlmsvKOJNEaWmRPmcojMOzCz4Hs2BGG/toAp/K38LcsMRyLp349glq5AzJbCEeimEoxaX6v/fLrA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.21.3", + "@svgr/babel-preset": "8.1.0", + "camelcase": "^6.2.0", + "cosmiconfig": "^8.1.3", + "snake-case": "^3.0.4" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + } + }, + "node_modules/@svgr/hast-util-to-babel-ast": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/hast-util-to-babel-ast/-/hast-util-to-babel-ast-8.0.0.tgz", + "integrity": "sha512-EbDKwO9GpfWP4jN9sGdYwPBU0kdomaPIL2Eu4YwmgP+sJeXT+L7bMwJUBnhzfH8Q2qMBqZ4fJwpCyYsAN3mt2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.21.3", + "entities": "^4.4.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + } + }, + "node_modules/@svgr/plugin-jsx": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@svgr/plugin-jsx/-/plugin-jsx-8.1.0.tgz", + "integrity": "sha512-0xiIyBsLlr8quN+WyuxooNW9RJ0Dpr8uOnH/xrCVO8GLUcwHISwj1AG0k+LFzteTkAA0GbX0kj9q6Dk70PTiPA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.21.3", + "@svgr/babel-preset": "8.1.0", + "@svgr/hast-util-to-babel-ast": "8.0.0", + "svg-parser": "^2.0.4" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@svgr/core": "*" + } + }, "node_modules/@tanstack/query-core": { "version": "5.59.20", "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.59.20.tgz", @@ -2335,6 +2604,19 @@ "node": ">=6" } }, + "node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/camelize": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/camelize/-/camelize-1.0.1.tgz", @@ -2526,6 +2808,33 @@ "node": ">= 0.6" } }, + "node_modules/cosmiconfig": { + "version": "8.3.6", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.3.6.tgz", + "integrity": "sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==", + "dev": true, + "license": "MIT", + "dependencies": { + "import-fresh": "^3.3.0", + "js-yaml": "^4.1.0", + "parse-json": "^5.2.0", + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/d-fischer" + }, + "peerDependencies": { + "typescript": ">=4.9.5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, "node_modules/cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", @@ -2700,6 +3009,17 @@ "license": "MIT", "peer": true }, + "node_modules/dot-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/dot-case/-/dot-case-3.0.4.tgz", + "integrity": "sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==", + "dev": true, + "license": "MIT", + "dependencies": { + "no-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, "node_modules/electron-to-chromium": { "version": "1.5.52", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.52.tgz", @@ -2727,6 +3047,16 @@ "url": "https://github.com/fb55/entities?sponsor=1" } }, + "node_modules/error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, "node_modules/es-module-lexer": { "version": "1.5.4", "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.5.4.tgz", @@ -3356,6 +3686,13 @@ "integrity": "sha512-7NXolsK4CAS5+xvdj5OMMbI962hU/wvwoxk+LWR9Ek9bVtyuuYScDN6eS0rUm6TxApFpw7CX1o4uJzcd4AyD3Q==", "license": "MIT" }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true, + "license": "MIT" + }, "node_modules/is-buffer": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.5.tgz", @@ -3535,6 +3872,13 @@ "dev": true, "license": "MIT" }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true, + "license": "MIT" + }, "node_modules/json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", @@ -3595,6 +3939,13 @@ "node": ">= 0.8.0" } }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true, + "license": "MIT" + }, "node_modules/locate-path": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", @@ -3653,6 +4004,16 @@ "integrity": "sha512-23I4pFZHmAemUnz8WZXbYRSKYj801VDaNv9ETuMh7IrMc7VuVVSo+Z9iLE3ni30+U48iDWfi30d3twAXBYmnCg==", "license": "MIT" }, + "node_modules/lower-case": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz", + "integrity": "sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^2.0.3" + } + }, "node_modules/lru-cache": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", @@ -4649,6 +5010,17 @@ "dev": true, "license": "MIT" }, + "node_modules/no-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz", + "integrity": "sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==", + "dev": true, + "license": "MIT", + "dependencies": { + "lower-case": "^2.0.2", + "tslib": "^2.0.3" + } + }, "node_modules/node-releases": { "version": "2.0.18", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.18.tgz", @@ -4742,6 +5114,25 @@ "node": ">=6" } }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/parse5": { "version": "7.2.1", "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.2.1.tgz", @@ -4782,6 +5173,16 @@ "devOptional": true, "license": "MIT" }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/pathe": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz", @@ -5374,6 +5775,17 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/snake-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/snake-case/-/snake-case-3.0.4.tgz", + "integrity": "sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg==", + "dev": true, + "license": "MIT", + "dependencies": { + "dot-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, "node_modules/source-map-js": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", @@ -5560,6 +5972,13 @@ "node": ">=8" } }, + "node_modules/svg-parser": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/svg-parser/-/svg-parser-2.0.4.tgz", + "integrity": "sha512-e4hG1hRwoOdRb37cIMSgzNsxyzKfayW6VOflrwvR+/bzrkyxY/31WkbgnQpgtrNp1SdpJvpUAGTa/ZoiPNDuRQ==", + "dev": true, + "license": "MIT" + }, "node_modules/symbol-tree": { "version": "3.2.4", "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", @@ -6081,6 +6500,21 @@ "url": "https://opencollective.com/vitest" } }, + "node_modules/vite-plugin-svgr": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/vite-plugin-svgr/-/vite-plugin-svgr-4.3.0.tgz", + "integrity": "sha512-Jy9qLB2/PyWklpYy0xk0UU3TlU0t2UMpJXZvf+hWII1lAmRHrOUKi11Uw8N3rxoNk7atZNYO3pR3vI1f7oi+6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rollup/pluginutils": "^5.1.3", + "@svgr/core": "^8.1.0", + "@svgr/plugin-jsx": "^8.1.0" + }, + "peerDependencies": { + "vite": ">=2.6.0" + } + }, "node_modules/vitest": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/vitest/-/vitest-2.1.5.tgz", diff --git a/FRONT/package.json b/FRONT/package.json index 3d81dd8..44e060a 100644 --- a/FRONT/package.json +++ b/FRONT/package.json @@ -39,6 +39,7 @@ "prismjs": "^1.29.0", "typescript": "~5.6.2", "typescript-eslint": "^8.11.0", - "vite": "^5.4.10" + "vite": "^5.4.10", + "vite-plugin-svgr": "^4.3.0" } } diff --git a/FRONT/src/App.tsx b/FRONT/src/App.tsx index 5f3214b..98f8be4 100644 --- a/FRONT/src/App.tsx +++ b/FRONT/src/App.tsx @@ -5,8 +5,10 @@ import CreatePost from './pages/CreatePost/CreatePost' import Navbar from '@_components/Navbar/Navbar' import Footer from '@_components/Footer/Footer' import Aside from '@_components/Aside/Aside' -import useLoadingStore from './store/useLoadingStore'; -import LoadingModal from '@_components/LoadingModal/LoadingModal' +import useLoadingStore from './stores/useLoadingStore'; +import LoadingModal from '@_components/LoadingModal/LoadingModal'; +import Post from './pages/Post/Post'; +import Toast from './pages/CreatePost/components/Toast/Toast' function App() { const { isLoading } = useLoadingStore(); @@ -17,9 +19,11 @@ function App() { } /> } /> + } />