diff --git a/backend/codiki-domain/src/main/java/org/codiki/domain/publication/exception/BadPublicationSearchCriterionException.java b/backend/codiki-domain/src/main/java/org/codiki/domain/publication/exception/BadPublicationSearchCriterionException.java new file mode 100644 index 0000000..07e144d --- /dev/null +++ b/backend/codiki-domain/src/main/java/org/codiki/domain/publication/exception/BadPublicationSearchCriterionException.java @@ -0,0 +1,9 @@ +package org.codiki.domain.publication.exception; + +import org.codiki.domain.exception.FunctionnalException; + +public class BadPublicationSearchCriterionException extends FunctionnalException { + public BadPublicationSearchCriterionException(String message) { + super(message); + } +} diff --git a/backend/codiki-exposition/src/main/java/org/codiki/exposition/configuration/GlobalControllerExceptionHandler.java b/backend/codiki-exposition/src/main/java/org/codiki/exposition/configuration/GlobalControllerExceptionHandler.java index 5612152..7481453 100644 --- a/backend/codiki-exposition/src/main/java/org/codiki/exposition/configuration/GlobalControllerExceptionHandler.java +++ b/backend/codiki-exposition/src/main/java/org/codiki/exposition/configuration/GlobalControllerExceptionHandler.java @@ -13,10 +13,7 @@ import org.codiki.domain.exception.RefreshTokenExpiredException; import org.codiki.domain.exception.UserDoesNotExistException; import org.codiki.domain.picture.exception.PictureNotFoundException; import org.codiki.domain.picture.exception.PictureUploadException; -import org.codiki.domain.publication.exception.NoPublicationSearchResultException; -import org.codiki.domain.publication.exception.PublicationEditionException; -import org.codiki.domain.publication.exception.PublicationNotFoundException; -import org.codiki.domain.publication.exception.PublicationUpdateForbiddenException; +import org.codiki.domain.publication.exception.*; import org.codiki.domain.user.exception.UserAlreadyExistsException; import org.codiki.domain.user.exception.UserCreationException; import org.springframework.http.HttpStatus; @@ -28,6 +25,7 @@ import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExcep @RestControllerAdvice public class GlobalControllerExceptionHandler extends ResponseEntityExceptionHandler { @ExceptionHandler({ + BadPublicationSearchCriterionException.class, CategoryDeletionException.class, CategoryEditionException.class, CategoryNotFoundException.class, diff --git a/backend/codiki-infrastructure/src/main/java/org/codiki/infrastructure/publication/PublicationJpaAdapter.java b/backend/codiki-infrastructure/src/main/java/org/codiki/infrastructure/publication/PublicationJpaAdapter.java index b50eca2..35e1b7c 100644 --- a/backend/codiki-infrastructure/src/main/java/org/codiki/infrastructure/publication/PublicationJpaAdapter.java +++ b/backend/codiki-infrastructure/src/main/java/org/codiki/infrastructure/publication/PublicationJpaAdapter.java @@ -4,6 +4,7 @@ import org.codiki.domain.publication.model.Publication; import org.codiki.domain.publication.model.search.PublicationSearchCriterion; import org.codiki.domain.publication.port.PublicationPort; import org.codiki.infrastructure.publication.model.PublicationEntity; +import org.codiki.infrastructure.publication.model.PublicationSearchJpaCriterion; import org.codiki.infrastructure.publication.model.PublicationSearchResult; import org.codiki.infrastructure.publication.repository.PublicationRepository; import org.springframework.data.domain.Limit; @@ -48,7 +49,7 @@ public class PublicationJpaAdapter implements PublicationPort { @Override public List search(List criteria) { - List adaptedCriteria = publicationSearchCriteriaJpaAdapter.adaptCriteriaForJpa(criteria); + List adaptedCriteria = publicationSearchCriteriaJpaAdapter.adaptCriteriaForJpa(criteria); return repository.search(adaptedCriteria) .stream() .map(PublicationEntity::toDomain) diff --git a/backend/codiki-infrastructure/src/main/java/org/codiki/infrastructure/publication/PublicationSearchCriteriaJpaAdapter.java b/backend/codiki-infrastructure/src/main/java/org/codiki/infrastructure/publication/PublicationSearchCriteriaJpaAdapter.java index f0e2bd1..90108e2 100644 --- a/backend/codiki-infrastructure/src/main/java/org/codiki/infrastructure/publication/PublicationSearchCriteriaJpaAdapter.java +++ b/backend/codiki-infrastructure/src/main/java/org/codiki/infrastructure/publication/PublicationSearchCriteriaJpaAdapter.java @@ -3,12 +3,15 @@ package org.codiki.infrastructure.publication; import java.util.LinkedList; import java.util.List; +import org.codiki.domain.publication.exception.BadPublicationSearchCriterionException; import org.codiki.domain.publication.model.search.PublicationSearchCriterion; +import org.codiki.infrastructure.publication.model.PublicationSearchJpaCriterion; +import org.codiki.infrastructure.publication.model.PublicationSearchJpaField; import org.springframework.stereotype.Component; @Component public class PublicationSearchCriteriaJpaAdapter { - public List adaptCriteriaForJpa(List initialCriteria) { + public List adaptCriteriaForJpa(List initialCriteria) { List result = new LinkedList<>(); for (PublicationSearchCriterion criterion : initialCriteria) { @@ -29,6 +32,19 @@ public class PublicationSearchCriteriaJpaAdapter { } } - return result; + return result.stream() + .map(this::mapToJpaCriterion) + .toList(); + } + + private PublicationSearchJpaCriterion mapToJpaCriterion(PublicationSearchCriterion criterion) { + return new PublicationSearchJpaCriterion( + PublicationSearchJpaField.fromDomain(criterion.searchField()) + .orElseThrow(() -> new BadPublicationSearchCriterionException( + String.format("Unknown field research criterion: %s", criterion.searchField())) + ), + criterion.searchType(), + criterion.value() + ); } } diff --git a/backend/codiki-infrastructure/src/main/java/org/codiki/infrastructure/publication/model/PublicationSearchJpaCriterion.java b/backend/codiki-infrastructure/src/main/java/org/codiki/infrastructure/publication/model/PublicationSearchJpaCriterion.java new file mode 100644 index 0000000..c2b04e3 --- /dev/null +++ b/backend/codiki-infrastructure/src/main/java/org/codiki/infrastructure/publication/model/PublicationSearchJpaCriterion.java @@ -0,0 +1,9 @@ +package org.codiki.infrastructure.publication.model; + +import org.codiki.domain.publication.model.search.ComparisonType; + +public record PublicationSearchJpaCriterion( + PublicationSearchJpaField searchField, + ComparisonType searchType, + Object value +) { } \ No newline at end of file diff --git a/backend/codiki-infrastructure/src/main/java/org/codiki/infrastructure/publication/model/PublicationSearchJpaField.java b/backend/codiki-infrastructure/src/main/java/org/codiki/infrastructure/publication/model/PublicationSearchJpaField.java new file mode 100644 index 0000000..5c268ed --- /dev/null +++ b/backend/codiki-infrastructure/src/main/java/org/codiki/infrastructure/publication/model/PublicationSearchJpaField.java @@ -0,0 +1,36 @@ +package org.codiki.infrastructure.publication.model; + +import lombok.Getter; +import org.codiki.domain.publication.model.search.PublicationSearchField; + +import java.util.Arrays; +import java.util.Optional; + +@Getter +public enum PublicationSearchJpaField { + ID, + KEY, + TITLE, + TEXT, + DESCRIPTION, + CREATION_DATE("creationDate"), + CATEGORY_ID("categoryId"), + AUTHOR_ID("author.id"), + AUTHOR_PSEUDO("author.pseudo"); + + private final String fieldName; + + PublicationSearchJpaField() { + this.fieldName = name().toLowerCase(); + } + + PublicationSearchJpaField(String fieldName) { + this.fieldName = fieldName; + } + + public static Optional fromDomain(PublicationSearchField publicationSearchField) { + return Arrays.stream(values()) + .filter(field -> field.name().equals(publicationSearchField.name())) + .findFirst(); + } +} diff --git a/backend/codiki-infrastructure/src/main/java/org/codiki/infrastructure/publication/repository/CustomPublicationRepository.java b/backend/codiki-infrastructure/src/main/java/org/codiki/infrastructure/publication/repository/CustomPublicationRepository.java index a26790c..0cc0393 100644 --- a/backend/codiki-infrastructure/src/main/java/org/codiki/infrastructure/publication/repository/CustomPublicationRepository.java +++ b/backend/codiki-infrastructure/src/main/java/org/codiki/infrastructure/publication/repository/CustomPublicationRepository.java @@ -1,10 +1,10 @@ package org.codiki.infrastructure.publication.repository; +import org.codiki.infrastructure.publication.model.PublicationEntity; +import org.codiki.infrastructure.publication.model.PublicationSearchJpaCriterion; + import java.util.List; -import org.codiki.domain.publication.model.search.PublicationSearchCriterion; -import org.codiki.infrastructure.publication.model.PublicationEntity; - public interface CustomPublicationRepository { - List search(List criteria); + List search(List criteria); } diff --git a/backend/codiki-infrastructure/src/main/java/org/codiki/infrastructure/publication/repository/CustomPublicationRepositoryImpl.java b/backend/codiki-infrastructure/src/main/java/org/codiki/infrastructure/publication/repository/CustomPublicationRepositoryImpl.java index 3afd2b9..b255872 100644 --- a/backend/codiki-infrastructure/src/main/java/org/codiki/infrastructure/publication/repository/CustomPublicationRepositoryImpl.java +++ b/backend/codiki-infrastructure/src/main/java/org/codiki/infrastructure/publication/repository/CustomPublicationRepositoryImpl.java @@ -1,16 +1,15 @@ package org.codiki.infrastructure.publication.repository; -import java.util.List; - -import org.codiki.domain.publication.model.search.PublicationSearchCriterion; -import org.codiki.infrastructure.publication.model.PublicationEntity; -import org.springframework.stereotype.Repository; - import jakarta.persistence.EntityManager; import jakarta.persistence.criteria.CriteriaBuilder; import jakarta.persistence.criteria.CriteriaQuery; import jakarta.persistence.criteria.Predicate; import jakarta.persistence.criteria.Root; +import org.codiki.infrastructure.publication.model.PublicationEntity; +import org.codiki.infrastructure.publication.model.PublicationSearchJpaCriterion; +import org.springframework.stereotype.Repository; + +import java.util.List; @Repository public class CustomPublicationRepositoryImpl implements CustomPublicationRepository { @@ -23,7 +22,7 @@ public class CustomPublicationRepositoryImpl implements CustomPublicationReposit } @Override - public List search(final List criteria) { + public List search(final List criteria) { CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder(); CriteriaQuery query = criteriaBuilder.createQuery(PublicationEntity.class); diff --git a/backend/codiki-infrastructure/src/main/java/org/codiki/infrastructure/publication/repository/PublicationPredicateMapper.java b/backend/codiki-infrastructure/src/main/java/org/codiki/infrastructure/publication/repository/PublicationPredicateMapper.java index 8d649ef..cecda20 100644 --- a/backend/codiki-infrastructure/src/main/java/org/codiki/infrastructure/publication/repository/PublicationPredicateMapper.java +++ b/backend/codiki-infrastructure/src/main/java/org/codiki/infrastructure/publication/repository/PublicationPredicateMapper.java @@ -1,14 +1,16 @@ package org.codiki.infrastructure.publication.repository; import java.util.List; +import java.util.UUID; -import static org.codiki.domain.publication.model.search.PublicationSearchField.AUTHOR_PSEUDO; -import org.codiki.domain.publication.model.search.PublicationSearchCriterion; -import org.codiki.domain.publication.model.search.PublicationSearchField; import org.codiki.infrastructure.publication.model.PublicationEntity; +import org.codiki.infrastructure.publication.model.PublicationSearchJpaCriterion; +import org.codiki.infrastructure.publication.model.PublicationSearchJpaField; import org.springframework.stereotype.Component; import static jakarta.persistence.criteria.JoinType.LEFT; +import static org.codiki.infrastructure.publication.model.PublicationSearchJpaField.AUTHOR_PSEUDO; + import jakarta.persistence.criteria.CriteriaBuilder; import jakarta.persistence.criteria.From; import jakarta.persistence.criteria.Predicate; @@ -17,54 +19,63 @@ import jakarta.persistence.criteria.Root; @Component public class PublicationPredicateMapper { public Predicate map( - List criteria, - CriteriaBuilder criteriaBuilder, - Root fromPublication + List criteria, + CriteriaBuilder criteriaBuilder, + Root fromPublication ) { List criteriaPredicates = criteria.stream() - .map(criterion -> map(criterion, criteriaBuilder, fromPublication)) - .toList(); + .map(criterion -> map(criterion, criteriaBuilder, fromPublication)) + .toList(); return criteriaBuilder.or(criteriaPredicates.toArray(new Predicate[]{})); } private Predicate map( - PublicationSearchCriterion criterion, - CriteriaBuilder criteriaBuilder, - Root fromPublication + PublicationSearchJpaCriterion criterion, + CriteriaBuilder criteriaBuilder, + Root fromPublication ) { return switch (criterion.searchType()) { - case EQUALS -> mapEqualsPredicate(criteriaBuilder, fromPublication, criterion.searchField(), criterion.value()); - case CONTAINS -> mapContainsPredicate(criteriaBuilder, fromPublication, criterion.searchField(), criterion.value()); + case EQUALS -> + mapEqualsPredicate(criteriaBuilder, fromPublication, criterion.searchField(), criterion.value()); + case CONTAINS -> + mapContainsPredicate(criteriaBuilder, fromPublication, criterion.searchField(), criterion.value()); default -> null; }; } private Predicate mapEqualsPredicate( - CriteriaBuilder criteriaBuilder, - Root fromPublication, - PublicationSearchField searchField, - Object value + CriteriaBuilder criteriaBuilder, + Root fromPublication, + PublicationSearchJpaField searchField, + Object value ) { + Predicate result; + From from = fromPublication; - String attributeName = searchField.name().toLowerCase(); + String attributeName = searchField.getFieldName(); if (searchField == AUTHOR_PSEUDO) { from = fromPublication.join("author", LEFT); attributeName = "pseudo"; } - return criteriaBuilder.equal( - criteriaBuilder.lower( - from.get(attributeName) - ), - value - ); + if (value instanceof UUID) { + result = criteriaBuilder.equal(from.get(attributeName), value); + } else { + result = criteriaBuilder.equal( + criteriaBuilder.lower( + from.get(attributeName) + ), + value + ); + } + return result; } private Predicate mapContainsPredicate( - CriteriaBuilder criteriaBuilder, - Root fromPublication, - PublicationSearchField searchField, - Object value + CriteriaBuilder criteriaBuilder, + Root fromPublication, + PublicationSearchJpaField searchField, + Object value ) { From from = fromPublication; String attributeName = searchField.name().toLowerCase(); @@ -74,10 +85,10 @@ public class PublicationPredicateMapper { } return criteriaBuilder.like( - criteriaBuilder.lower( - from.get(attributeName) - ), - String.format("%%%s%%", value) + criteriaBuilder.lower( + from.get(attributeName) + ), + String.format("%%%s%%", value) ); } } diff --git a/backend/codiki-infrastructure/src/test/java/org/codiki/infrastructure/publication/PublicationSearchCriteriaJpaAdapterTest.java b/backend/codiki-infrastructure/src/test/java/org/codiki/infrastructure/publication/PublicationSearchCriteriaJpaAdapterTest.java index d1833ba..0bafdfd 100644 --- a/backend/codiki-infrastructure/src/test/java/org/codiki/infrastructure/publication/PublicationSearchCriteriaJpaAdapterTest.java +++ b/backend/codiki-infrastructure/src/test/java/org/codiki/infrastructure/publication/PublicationSearchCriteriaJpaAdapterTest.java @@ -1,14 +1,17 @@ package org.codiki.infrastructure.publication; +import org.codiki.domain.publication.model.search.PublicationSearchCriterion; +import org.codiki.domain.publication.model.search.PublicationSearchField; +import org.codiki.infrastructure.publication.model.PublicationSearchJpaCriterion; +import org.codiki.infrastructure.publication.model.PublicationSearchJpaField; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + import java.util.List; import static org.assertj.core.api.Assertions.assertThat; import static org.codiki.domain.publication.model.search.ComparisonType.CONTAINS; -import static org.codiki.domain.publication.model.search.PublicationSearchField.KEY; -import org.codiki.domain.publication.model.search.PublicationSearchCriterion; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Nested; -import org.junit.jupiter.api.Test; class PublicationSearchCriteriaJpaAdapterTest { private PublicationSearchCriteriaJpaAdapter adapter; @@ -24,15 +27,15 @@ class PublicationSearchCriteriaJpaAdapterTest { void should_adapt_criteria_for_jpa() { // given List initialCriteria = List.of( - new PublicationSearchCriterion(KEY, CONTAINS, "critère") + new PublicationSearchCriterion(PublicationSearchField.KEY, CONTAINS, "critère") ); // when - List result = adapter.adaptCriteriaForJpa(initialCriteria); + List result = adapter.adaptCriteriaForJpa(initialCriteria); // then - List expectedResult = List.of( - new PublicationSearchCriterion(KEY, CONTAINS, "crit_re") + List expectedResult = List.of( + new PublicationSearchJpaCriterion(PublicationSearchJpaField.KEY, CONTAINS, "crit_re") ); assertThat(result).isEqualTo(expectedResult); } diff --git a/frontend/src/app/core/rest-services/publications/publication.rest-service.ts b/frontend/src/app/core/rest-services/publications/publication.rest-service.ts index 155332e..20925f3 100644 --- a/frontend/src/app/core/rest-services/publications/publication.rest-service.ts +++ b/frontend/src/app/core/rest-services/publications/publication.rest-service.ts @@ -1,4 +1,4 @@ -import { HttpClient } from '@angular/common/http'; +import { HttpClient, HttpParams } from '@angular/common/http'; import { Injectable, inject } from '@angular/core'; import { lastValueFrom } from 'rxjs'; import { Publication } from './model/publication'; @@ -20,4 +20,10 @@ export class PublicationRestService { update(publication: Publication): Promise { return lastValueFrom(this.httpClient.put(`/api/publications/${publication.id}`, publication)); } + + search(searchCriteria: string): Promise { + let params = new HttpParams(); + params = params.set('query', searchCriteria); + return lastValueFrom(this.httpClient.get('/api/publications', { params })); + } } diff --git a/frontend/src/app/pages/home/home.component.html b/frontend/src/app/pages/home/home.component.html index 4d8c092..9da3126 100644 --- a/frontend/src/app/pages/home/home.component.html +++ b/frontend/src/app/pages/home/home.component.html @@ -1,17 +1,2 @@

Last publications

- \ No newline at end of file + \ No newline at end of file diff --git a/frontend/src/app/pages/home/home.component.ts b/frontend/src/app/pages/home/home.component.ts index e66c801..799b659 100644 --- a/frontend/src/app/pages/home/home.component.ts +++ b/frontend/src/app/pages/home/home.component.ts @@ -5,11 +5,12 @@ import { Publication } from '../../core/rest-services/publications/model/publica import { RouterModule } from '@angular/router'; import { MatTooltipModule } from '@angular/material/tooltip'; import { CommonModule } from '@angular/common'; +import { PublicationListComponent } from '../../components/publication-list/publication-list.component'; @Component({ selector: 'app-home', standalone: true, - imports: [CommonModule, RouterModule, MatTooltipModule], + imports: [PublicationListComponent], templateUrl: './home.component.html', styleUrl: './home.component.scss', providers: [HomeService]