diff --git a/codiki-application/src/main/java/org/codiki/application/category/CategoryUseCases.java b/codiki-application/src/main/java/org/codiki/application/category/CategoryUseCases.java index 0041977..6a0ac82 100644 --- a/codiki-application/src/main/java/org/codiki/application/category/CategoryUseCases.java +++ b/codiki-application/src/main/java/org/codiki/application/category/CategoryUseCases.java @@ -7,10 +7,11 @@ import java.util.Optional; import java.util.UUID; import static org.codiki.domain.category.model.builder.CategoryBuilder.aCategory; -import static org.springframework.util.CollectionUtils.isEmpty; +import org.codiki.domain.category.exception.CategoryDeletionException; import org.codiki.domain.category.exception.CategoryEditionException; import org.codiki.domain.category.exception.CategoryNotFoundException; import org.codiki.domain.category.model.Category; +import org.codiki.domain.category.model.builder.CategoryBuilder; import org.codiki.domain.category.port.CategoryPort; import org.springframework.stereotype.Service; @@ -33,7 +34,11 @@ public class CategoryUseCases { List subCategories = emptyList(); if (!isNull(subCategoryIds)) { - subCategories = categoryPort.findAllByIds(subCategoryIds); + try { + subCategories = categoryPort.findAllByIds(subCategoryIds); + } catch (CategoryNotFoundException exception) { + throw new CategoryEditionException(exception); + } } Category newCategory = aCategory() @@ -48,19 +53,44 @@ public class CategoryUseCases { } public Category updateCategory(UUID categoryId, String name, List subCategoryIds) { - if (isNull(name) && isEmpty(subCategoryIds)) { + if (isNull(name) && isNull(subCategoryIds)) { throw new CategoryEditionException("no any field is filled"); } Category categoryToUpdate = categoryPort.findById(categoryId) .orElseThrow(() -> new CategoryNotFoundException(categoryId)); - Category updatedCategory = aCategory() - .basedOn(categoryToUpdate) - .build(); + CategoryBuilder categoryBuilder = aCategory() + .basedOn(categoryToUpdate); + if (!isNull(name)) { + categoryBuilder.withName(name); + } + + if (!isNull(subCategoryIds)) { + List subCategories; + try { + subCategories = categoryPort.findAllByIds(subCategoryIds); + } catch (CategoryNotFoundException exception) { + throw new CategoryEditionException(exception); + } + categoryBuilder.withSubCategories(subCategories); + } + + Category updatedCategory = categoryBuilder.build(); categoryPort.save(updatedCategory); - return updatedCategory; } + + public void deleteCategory(UUID categoryId) { + if (!categoryPort.existsById(categoryId)) { + throw new CategoryNotFoundException(categoryId); + } + + if (categoryPort.existsAnyAssociatedPublication(categoryId)) { + throw new CategoryDeletionException(categoryId, "some publications are associated to the category"); + } + + categoryPort.deleteById(categoryId); + } } diff --git a/codiki-domain/src/main/java/org/codiki/domain/category/exception/CategoryDeletionException.java b/codiki-domain/src/main/java/org/codiki/domain/category/exception/CategoryDeletionException.java new file mode 100644 index 0000000..7209661 --- /dev/null +++ b/codiki-domain/src/main/java/org/codiki/domain/category/exception/CategoryDeletionException.java @@ -0,0 +1,11 @@ +package org.codiki.domain.category.exception; + +import java.util.UUID; + +import org.codiki.domain.exception.FunctionnalException; + +public class CategoryDeletionException extends FunctionnalException { + public CategoryDeletionException(UUID categoryId, String cause) { + super(String.format("Impossible to delete category with id %s. Cause: %s.", categoryId, cause)); + } +} diff --git a/codiki-domain/src/main/java/org/codiki/domain/category/port/CategoryPort.java b/codiki-domain/src/main/java/org/codiki/domain/category/port/CategoryPort.java index f8e3e2f..c018b7f 100644 --- a/codiki-domain/src/main/java/org/codiki/domain/category/port/CategoryPort.java +++ b/codiki-domain/src/main/java/org/codiki/domain/category/port/CategoryPort.java @@ -12,4 +12,10 @@ public interface CategoryPort { void save(Category category); List findAllByIds(List subCategoryIds); + + boolean existsAnyAssociatedPublication(UUID categoryId); + + void deleteById(UUID categoryId); + + boolean existsById(UUID categoryId); } diff --git a/codiki-exposition/src/main/java/org/codiki/exposition/category/CategoryController.java b/codiki-exposition/src/main/java/org/codiki/exposition/category/CategoryController.java index fe3e849..c612d75 100644 --- a/codiki-exposition/src/main/java/org/codiki/exposition/category/CategoryController.java +++ b/codiki-exposition/src/main/java/org/codiki/exposition/category/CategoryController.java @@ -3,10 +3,12 @@ package org.codiki.exposition.category; import java.util.UUID; import static org.springframework.http.HttpStatus.CREATED; +import static org.springframework.http.HttpStatus.NO_CONTENT; import org.codiki.application.category.CategoryUseCases; import org.codiki.domain.category.model.Category; import org.codiki.exposition.category.model.CategoryDto; import org.codiki.exposition.category.model.CategoryEditionRequest; +import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PutMapping; @@ -43,4 +45,10 @@ public class CategoryController { ); return new CategoryDto(createdCategory); } + + @DeleteMapping("/{categoryId}") + @ResponseStatus(NO_CONTENT) + public void deleteCategory(@PathVariable("categoryId") UUID categoryId) { + categoryUseCases.deleteCategory(categoryId); + } } diff --git a/codiki-exposition/src/main/java/org/codiki/exposition/configuration/GlobalControllerExceptionHandler.java b/codiki-exposition/src/main/java/org/codiki/exposition/configuration/GlobalControllerExceptionHandler.java index aaa5113..2023382 100644 --- a/codiki-exposition/src/main/java/org/codiki/exposition/configuration/GlobalControllerExceptionHandler.java +++ b/codiki-exposition/src/main/java/org/codiki/exposition/configuration/GlobalControllerExceptionHandler.java @@ -4,6 +4,7 @@ import static org.springframework.http.HttpStatus.BAD_REQUEST; import static org.springframework.http.HttpStatus.FORBIDDEN; import static org.springframework.http.HttpStatus.NOT_FOUND; import static org.springframework.http.HttpStatus.UNAUTHORIZED; +import org.codiki.domain.category.exception.CategoryDeletionException; import org.codiki.domain.category.exception.CategoryEditionException; import org.codiki.domain.category.exception.CategoryNotFoundException; import org.codiki.domain.exception.LoginFailureException; @@ -73,4 +74,10 @@ public class GlobalControllerExceptionHandler { public void handleCategoryEditionException() { // Do nothing. } + + @ResponseStatus(BAD_REQUEST) + @ExceptionHandler(CategoryDeletionException.class) + public void handleCategoryDeletionException() { + // Do nothing. + } } diff --git a/codiki-exposition/src/main/java/org/codiki/exposition/configuration/security/SecurityConfiguration.java b/codiki-exposition/src/main/java/org/codiki/exposition/configuration/security/SecurityConfiguration.java index a8a1bc6..8ae4309 100644 --- a/codiki-exposition/src/main/java/org/codiki/exposition/configuration/security/SecurityConfiguration.java +++ b/codiki-exposition/src/main/java/org/codiki/exposition/configuration/security/SecurityConfiguration.java @@ -1,8 +1,10 @@ package org.codiki.exposition.configuration.security; +import static org.springframework.http.HttpMethod.DELETE; import static org.springframework.http.HttpMethod.GET; import static org.springframework.http.HttpMethod.OPTIONS; import static org.springframework.http.HttpMethod.POST; +import static org.springframework.http.HttpMethod.PUT; import static org.springframework.security.config.http.SessionCreationPolicy.STATELESS; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -52,6 +54,14 @@ public class SecurityConfiguration { POST, "/api/categories" ).hasRole("ADMIN") + .requestMatchers( + PUT, + "/api/categories/{categoryId}" + ).hasRole("ADMIN") + .requestMatchers( + DELETE, + "/api/categories/{categoryId}" + ).hasRole("ADMIN") .requestMatchers(OPTIONS).permitAll() .anyRequest().authenticated() ); diff --git a/codiki-infrastructure/src/main/java/org/codiki/infrastructure/category/CategoryJpaAdapter.java b/codiki-infrastructure/src/main/java/org/codiki/infrastructure/category/CategoryJpaAdapter.java index b5aeb13..c875963 100644 --- a/codiki-infrastructure/src/main/java/org/codiki/infrastructure/category/CategoryJpaAdapter.java +++ b/codiki-infrastructure/src/main/java/org/codiki/infrastructure/category/CategoryJpaAdapter.java @@ -4,6 +4,7 @@ import java.util.List; import java.util.Optional; import java.util.UUID; +import org.codiki.domain.category.exception.CategoryNotFoundException; import org.codiki.domain.category.model.Category; import org.codiki.domain.category.port.CategoryPort; import org.codiki.infrastructure.category.model.CategoryEntity; @@ -31,10 +32,34 @@ public class CategoryJpaAdapter implements CategoryPort { } @Override - public List findAllByIds(final List subCategoryIds) { - return categoryRepository.findAllById(subCategoryIds) + public List findAllByIds(List categoryIds) { + final List categories = categoryRepository.findAllById(categoryIds) .stream() .map(CategoryEntity::toDomain) .toList(); + + Optional notFoundCategoryId = categories.stream() + .filter(category -> !categoryIds.contains(category.id())) + .findFirst() + .map(Category::id); + if (notFoundCategoryId.isPresent()) { + throw new CategoryNotFoundException(notFoundCategoryId.get()); + } + return categories; + } + + @Override + public boolean existsAnyAssociatedPublication(UUID categoryId) { + return categoryRepository.existsAnyAssociatedPublication(categoryId); + } + + @Override + public void deleteById(UUID categoryId) { + categoryRepository.deleteById(categoryId); + } + + @Override + public boolean existsById(UUID categoryId) { + return categoryRepository.existsById(categoryId); } } diff --git a/codiki-infrastructure/src/main/java/org/codiki/infrastructure/category/repository/CategoryRepository.java b/codiki-infrastructure/src/main/java/org/codiki/infrastructure/category/repository/CategoryRepository.java index e9bf66a..e842783 100644 --- a/codiki-infrastructure/src/main/java/org/codiki/infrastructure/category/repository/CategoryRepository.java +++ b/codiki-infrastructure/src/main/java/org/codiki/infrastructure/category/repository/CategoryRepository.java @@ -4,6 +4,16 @@ import java.util.UUID; import org.codiki.infrastructure.category.model.CategoryEntity; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; public interface CategoryRepository extends JpaRepository { + @Query(value = """ + SELECT ( + SELECT COUNT(*) + FROM publication p + WHERE p.category_id = :categoryId + ) > 0 + """, nativeQuery = true) + boolean existsAnyAssociatedPublication(@Param("categoryId") UUID categoryId); } diff --git a/rest-client-collection/Codiki/environments/localhost.bru b/rest-client-collection/Codiki/environments/localhost.bru index 3908151..66412d7 100644 --- a/rest-client-collection/Codiki/environments/localhost.bru +++ b/rest-client-collection/Codiki/environments/localhost.bru @@ -1,6 +1,6 @@ vars { url: http://localhost:8080 publicationId: fce1de27-11c6-4deb-a248-b63288c00037 - bearerToken: eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxNWExM2RjNy0wMjlkLTRlYWItYTYzZC1jMWU5NmY5MDI0MWQiLCJleHAiOjE3MTAyNTE0MzV9.0-KmVfwoyJ1JDZs-f2paEZVAljCPVkcEi33bYra4hoVSvECFsdc0CFlJKpWEeEswIv4jSsnEzs7yFW_XM9WWAA - categoryId: 25742844-f0c2-454a-a4d1-71ad3bda28df + bearerToken: eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxNWExM2RjNy0wMjlkLTRlYWItYTYzZC1jMWU5NmY5MDI0MWQiLCJleHAiOjE3MTAyNjMyOTh9.j-PRoIYVjPcpaE92u8sEUsZib1mGTrZmfd96ZgnGgskUqhnoUtCHwQbmbvHTdkr2XdypSU3Hq9dndwxU4ElmWA + categoryId: 872abbb4-a287-4519-8eeb-c43d567d89c8 }