Move backend files into a sub folder.
This commit is contained in:
@@ -0,0 +1,72 @@
|
||||
package org.codiki.infrastructure.category;
|
||||
|
||||
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;
|
||||
import org.codiki.infrastructure.category.repository.CategoryRepository;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
public class CategoryJpaAdapter implements CategoryPort {
|
||||
private final CategoryRepository categoryRepository;
|
||||
|
||||
public CategoryJpaAdapter(CategoryRepository categoryRepository) {
|
||||
this.categoryRepository = categoryRepository;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Category> findById(UUID categoryId) {
|
||||
return categoryRepository.findById(categoryId)
|
||||
.map(CategoryEntity::toDomain);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void save(Category category) {
|
||||
CategoryEntity categoryEntity = new CategoryEntity(category);
|
||||
categoryRepository.save(categoryEntity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Category> findAllByIds(List<UUID> categoryIds) {
|
||||
final List<Category> categories = categoryRepository.findAllById(categoryIds)
|
||||
.stream()
|
||||
.map(CategoryEntity::toDomain)
|
||||
.toList();
|
||||
|
||||
Optional<UUID> notFoundCategoryId = categoryIds.stream()
|
||||
.filter(categoryId -> categories.stream().map(Category::id).noneMatch(categoryId::equals))
|
||||
.findFirst();
|
||||
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);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Category> findAll() {
|
||||
return categoryRepository.findAll()
|
||||
.stream()
|
||||
.map(CategoryEntity::toDomain)
|
||||
.toList();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
package org.codiki.infrastructure.category.model;
|
||||
|
||||
import static java.util.Collections.emptyList;
|
||||
import static java.util.stream.Collectors.toSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.codiki.domain.category.model.Category;
|
||||
|
||||
import static jakarta.persistence.CascadeType.ALL;
|
||||
import static jakarta.persistence.FetchType.LAZY;
|
||||
import jakarta.persistence.CascadeType;
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.FetchType;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.JoinColumn;
|
||||
import jakarta.persistence.ManyToOne;
|
||||
import jakarta.persistence.OneToMany;
|
||||
import jakarta.persistence.Table;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
|
||||
@Entity
|
||||
@Table(name = "category")
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class CategoryEntity {
|
||||
@Id
|
||||
private UUID id;
|
||||
@Column(nullable = false)
|
||||
private String name;
|
||||
@OneToMany
|
||||
@JoinColumn(name = "parent_category_id")
|
||||
private Set<CategoryEntity> subCategories;
|
||||
|
||||
public CategoryEntity(Category category) {
|
||||
this(
|
||||
category.id(),
|
||||
category.name(),
|
||||
category.subCategories()
|
||||
.stream()
|
||||
.map(CategoryEntity::new)
|
||||
.collect(toSet())
|
||||
);
|
||||
}
|
||||
|
||||
public Category toDomain() {
|
||||
return new Category(
|
||||
id,
|
||||
name,
|
||||
subCategories.stream()
|
||||
.map(CategoryEntity::toDomain)
|
||||
.toList()
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package org.codiki.infrastructure.category.repository;
|
||||
|
||||
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<CategoryEntity, UUID> {
|
||||
@Query(value = """
|
||||
SELECT (
|
||||
SELECT COUNT(*)
|
||||
FROM publication p
|
||||
WHERE p.category_id = :categoryId
|
||||
) > 0
|
||||
""", nativeQuery = true)
|
||||
boolean existsAnyAssociatedPublication(@Param("categoryId") UUID categoryId);
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package org.codiki.infrastructure.configuration;
|
||||
|
||||
import org.springframework.boot.autoconfigure.domain.EntityScan;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
|
||||
|
||||
@Configuration
|
||||
@EnableJpaRepositories("org.codiki.infrastructure")
|
||||
@EntityScan("org.codiki.infrastructure")
|
||||
public class JpaConfiguration {
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
package org.codiki.infrastructure.picture;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
import static org.codiki.domain.picture.model.builder.PictureBuilder.aPicture;
|
||||
import org.codiki.domain.picture.exception.PictureNotFoundException;
|
||||
import org.codiki.domain.picture.exception.PictureStorageErrorException;
|
||||
import org.codiki.domain.picture.model.Picture;
|
||||
import org.codiki.domain.picture.port.PicturePort;
|
||||
import org.codiki.infrastructure.picture.model.PictureEntity;
|
||||
import org.codiki.infrastructure.picture.repository.PictureRepository;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import jakarta.transaction.Transactional;
|
||||
|
||||
@Component
|
||||
public class PictureJpaAdapter implements PicturePort {
|
||||
private final PictureRepository repository;
|
||||
private final String pictureFolderPath;
|
||||
|
||||
public PictureJpaAdapter(
|
||||
PictureRepository repository,
|
||||
@Value("${application.pictures.path}") String pictureFolderPath
|
||||
) {
|
||||
this.repository = repository;
|
||||
this.pictureFolderPath = pictureFolderPath;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean existsById(UUID pictureId) {
|
||||
return repository.existsById(pictureId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Picture> findById(UUID pictureId) {
|
||||
return repository.findById(pictureId)
|
||||
.map(PictureEntity::toDomain)
|
||||
.map(picture -> {
|
||||
File pictureFile = new File(String.format("%s/%s", pictureFolderPath, pictureId));
|
||||
if (!pictureFile.exists()) {
|
||||
throw new PictureNotFoundException(pictureId);
|
||||
}
|
||||
return aPicture()
|
||||
.basedOn(picture)
|
||||
.withContentFile(pictureFile)
|
||||
.build();
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public void save(Picture picture) {
|
||||
PictureEntity pictureEntity = new PictureEntity(picture);
|
||||
repository.save(pictureEntity);
|
||||
|
||||
boolean isMoved = picture.contentFile().renameTo(new File(String.format("%s/%s", pictureFolderPath, picture.id())));
|
||||
if (!isMoved) {
|
||||
throw new PictureStorageErrorException();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteById(UUID pictureId) {
|
||||
repository.deleteById(pictureId);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package org.codiki.infrastructure.picture.model;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
import org.codiki.domain.picture.model.Picture;
|
||||
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.Table;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
|
||||
@Entity
|
||||
@Table(name = "picture")
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Getter
|
||||
@Setter
|
||||
public class PictureEntity {
|
||||
@Id
|
||||
private UUID id;
|
||||
@Column(nullable = false)
|
||||
private UUID publisherId;
|
||||
|
||||
public PictureEntity(Picture picture) {
|
||||
id = picture.id();
|
||||
publisherId = picture.publisherId();
|
||||
}
|
||||
|
||||
public Picture toDomain() {
|
||||
return new Picture(id, publisherId, null);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package org.codiki.infrastructure.picture.repository;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
import org.codiki.infrastructure.picture.model.PictureEntity;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
|
||||
public interface PictureRepository extends JpaRepository<PictureEntity, UUID> {
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
package org.codiki.infrastructure.publication;
|
||||
|
||||
import static java.util.Collections.reverseOrder;
|
||||
import static java.util.Comparator.comparingInt;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
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.PublicationSearchResult;
|
||||
import org.codiki.infrastructure.publication.repository.PublicationRepository;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
public class PublicationJpaAdapter implements PublicationPort {
|
||||
private final PublicationRepository repository;
|
||||
private final PublicationSearchCriteriaJpaAdapter publicationSearchCriteriaJpaAdapter;
|
||||
|
||||
public PublicationJpaAdapter(
|
||||
PublicationRepository repository,
|
||||
PublicationSearchCriteriaJpaAdapter publicationSearchCriteriaJpaAdapter
|
||||
) {
|
||||
this.repository = repository;
|
||||
this.publicationSearchCriteriaJpaAdapter = publicationSearchCriteriaJpaAdapter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void save(Publication publication) {
|
||||
PublicationEntity newPublicationEntity = new PublicationEntity(publication);
|
||||
repository.save(newPublicationEntity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Publication> findById(UUID publicationId) {
|
||||
return repository.findById(publicationId)
|
||||
.map(PublicationEntity::toDomain);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void delete(Publication publication) {
|
||||
repository.deleteById(publication.id());
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Publication> search(List<PublicationSearchCriterion> criteria) {
|
||||
List<PublicationSearchCriterion> adaptedCriteria = publicationSearchCriteriaJpaAdapter.adaptCriteriaForJpa(criteria);
|
||||
return repository.search(adaptedCriteria)
|
||||
.stream()
|
||||
.map(PublicationEntity::toDomain)
|
||||
.map(publication -> new PublicationSearchResult(publication, criteria))
|
||||
.sorted(reverseOrder(comparingInt(PublicationSearchResult::getSearchScore)))
|
||||
.map(PublicationSearchResult::getPublication)
|
||||
.toList();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package org.codiki.infrastructure.publication;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
import org.codiki.domain.publication.model.search.PublicationSearchCriterion;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
public class PublicationSearchCriteriaJpaAdapter {
|
||||
public List<PublicationSearchCriterion> adaptCriteriaForJpa(List<PublicationSearchCriterion> initialCriteria) {
|
||||
List<PublicationSearchCriterion> result = new LinkedList<>();
|
||||
|
||||
for (PublicationSearchCriterion criterion : initialCriteria) {
|
||||
boolean criterionAdaptationOccurred = false;
|
||||
|
||||
if (criterion.value() instanceof String criterionValue) {
|
||||
String unaccentedCriterionValue = criterionValue.replaceAll("[àáâãäåçèéêëìíîïñòóôõöùúûüýÿ]", "_");
|
||||
result.add(new PublicationSearchCriterion(
|
||||
criterion.searchField(),
|
||||
criterion.searchType(),
|
||||
unaccentedCriterionValue.toLowerCase()
|
||||
));
|
||||
criterionAdaptationOccurred = true;
|
||||
}
|
||||
|
||||
if (!criterionAdaptationOccurred) {
|
||||
result.add(criterion);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package org.codiki.infrastructure.publication.model;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
import org.codiki.domain.publication.model.Author;
|
||||
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.Table;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
|
||||
@Entity
|
||||
@Table(name = "`user`")
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class AuthorEntity {
|
||||
@Id
|
||||
private UUID id;
|
||||
@Column(nullable = false)
|
||||
private String pseudo;
|
||||
// private String illustrationId;
|
||||
|
||||
public AuthorEntity(Author author) {
|
||||
this(
|
||||
author.id(),
|
||||
author.name()
|
||||
// author.illustrationId()
|
||||
);
|
||||
}
|
||||
|
||||
public Author toDomain() {
|
||||
return new Author(id, pseudo, "image");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
package org.codiki.infrastructure.publication.model;
|
||||
|
||||
import java.time.ZonedDateTime;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.codiki.domain.publication.model.Publication;
|
||||
import org.codiki.infrastructure.category.model.CategoryEntity;
|
||||
|
||||
import static jakarta.persistence.FetchType.LAZY;
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.JoinColumn;
|
||||
import jakarta.persistence.ManyToOne;
|
||||
import jakarta.persistence.Table;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
|
||||
@Entity
|
||||
@Table(name = "publication")
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class PublicationEntity {
|
||||
@Id
|
||||
private UUID id;
|
||||
@Column(nullable = false)
|
||||
private String key;
|
||||
@Column(nullable = false)
|
||||
private String title;
|
||||
@Column(nullable = false)
|
||||
private String text;
|
||||
@Column(nullable = false)
|
||||
private String description;
|
||||
@Column(nullable = false)
|
||||
private ZonedDateTime creationDate;
|
||||
@Column(nullable = false)
|
||||
private UUID illustrationId;
|
||||
@Column(nullable = false)
|
||||
private UUID categoryId;
|
||||
@ManyToOne(fetch = LAZY)
|
||||
@JoinColumn(name = "author_id")
|
||||
private AuthorEntity author;
|
||||
|
||||
public PublicationEntity(Publication publication) {
|
||||
this(
|
||||
publication.id(),
|
||||
publication.key(),
|
||||
publication.title(),
|
||||
publication.text(),
|
||||
publication.description(),
|
||||
publication.creationDate(),
|
||||
publication.illustrationId(),
|
||||
publication.categoryId(),
|
||||
new AuthorEntity(publication.author())
|
||||
);
|
||||
}
|
||||
|
||||
public Publication toDomain() {
|
||||
return new Publication(
|
||||
id,
|
||||
key,
|
||||
title,
|
||||
text,
|
||||
description,
|
||||
creationDate,
|
||||
illustrationId,
|
||||
categoryId,
|
||||
author.toDomain()
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
package org.codiki.infrastructure.publication.model;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static org.codiki.domain.publication.model.search.ComparisonType.CONTAINS;
|
||||
import static org.codiki.domain.publication.model.search.PublicationSearchField.DESCRIPTION;
|
||||
import static org.codiki.domain.publication.model.search.PublicationSearchField.TEXT;
|
||||
import static org.codiki.domain.publication.model.search.PublicationSearchField.TITLE;
|
||||
import org.codiki.domain.publication.model.Publication;
|
||||
import org.codiki.domain.publication.model.search.PublicationSearchCriterion;
|
||||
import org.codiki.domain.publication.model.search.PublicationSearchField;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
@Getter
|
||||
public class PublicationSearchResult {
|
||||
private final Publication publication;
|
||||
private final int searchScore;
|
||||
|
||||
public PublicationSearchResult(
|
||||
Publication publication,
|
||||
List<PublicationSearchCriterion> criteria
|
||||
) {
|
||||
this.publication = publication;
|
||||
|
||||
int score = 0;
|
||||
|
||||
score += computeCriterionScoreForField(TITLE, publication, criteria, 1000, 10, 3);
|
||||
score += computeCriterionScoreForField(DESCRIPTION, publication, criteria, 100, 7, 2);
|
||||
score += computeCriterionScoreForField(TEXT, publication, criteria, 100, 4, 1);
|
||||
|
||||
searchScore = score;
|
||||
}
|
||||
|
||||
private static int computeCriterionScoreForField(
|
||||
PublicationSearchField field,
|
||||
Publication publication,
|
||||
List<PublicationSearchCriterion> criteria,
|
||||
int bountyForPerfectMatch,
|
||||
int bountyForWordMatch,
|
||||
int bountyForWordContaingMatch
|
||||
) {
|
||||
return getLowercaseFieldValue(field, publication)
|
||||
.map(fieldValue ->
|
||||
criteria.stream()
|
||||
.filter(criterion -> criterion.searchType() == CONTAINS)
|
||||
.filter(criterion -> criterion.searchField() == field)
|
||||
.map(PublicationSearchCriterion::value)
|
||||
.filter(String.class::isInstance)
|
||||
.map(String.class::cast)
|
||||
.map(String::toLowerCase)
|
||||
.map(criterionValue ->
|
||||
computeCriterionScore(
|
||||
criterionValue,
|
||||
fieldValue,
|
||||
bountyForPerfectMatch,
|
||||
bountyForWordMatch,
|
||||
bountyForWordContaingMatch
|
||||
)
|
||||
)
|
||||
.mapToInt(Integer::intValue)
|
||||
.sum()
|
||||
)
|
||||
.orElse(0);
|
||||
}
|
||||
|
||||
private static Optional<String> getLowercaseFieldValue(PublicationSearchField field, Publication publication) {
|
||||
return Optional.ofNullable(
|
||||
switch (field) {
|
||||
case TITLE -> publication.title();
|
||||
case DESCRIPTION -> publication.description();
|
||||
case TEXT -> publication.text();
|
||||
default -> null;
|
||||
}
|
||||
).map(String::toLowerCase);
|
||||
}
|
||||
|
||||
private static int computeCriterionScore(
|
||||
String criterionValue,
|
||||
String publicationTitle,
|
||||
int bountyForPerfectMatch,
|
||||
int bountyForWordMatch,
|
||||
int bountyForWordContaingMatch
|
||||
) {
|
||||
int result = 0;
|
||||
|
||||
if (publicationTitle.equals(criterionValue)) {
|
||||
result = bountyForPerfectMatch;
|
||||
} else if (publicationTitle.contains(criterionValue)) {
|
||||
result = Stream.of(publicationTitle.split(" "))
|
||||
.map(titleWord -> computeWordScore(criterionValue, titleWord, bountyForWordMatch, bountyForWordContaingMatch))
|
||||
.mapToInt(Integer::intValue)
|
||||
.sum();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static int computeWordScore(
|
||||
String criterionValue,
|
||||
String titleWord,
|
||||
int bountyForWordMatch,
|
||||
int bountyForWordContaingMatch
|
||||
) {
|
||||
int result = 0;
|
||||
|
||||
if (titleWord.equals(criterionValue)) {
|
||||
result = bountyForWordMatch;
|
||||
} else if (titleWord.contains(criterionValue)) {
|
||||
result = bountyForWordContaingMatch;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
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;
|
||||
|
||||
public interface CustomPublicationRepository {
|
||||
List<PublicationEntity> search(List<PublicationSearchCriterion> criteria);
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
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;
|
||||
|
||||
@Repository
|
||||
public class CustomPublicationRepositoryImpl implements CustomPublicationRepository {
|
||||
private final EntityManager entityManager;
|
||||
private final PublicationPredicateMapper publicationPredicateMapper;
|
||||
|
||||
public CustomPublicationRepositoryImpl(EntityManager entityManager, PublicationPredicateMapper publicationPredicateMapper) {
|
||||
this.entityManager = entityManager;
|
||||
this.publicationPredicateMapper = publicationPredicateMapper;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<PublicationEntity> search(final List<PublicationSearchCriterion> criteria) {
|
||||
CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
|
||||
|
||||
CriteriaQuery<PublicationEntity> query = criteriaBuilder.createQuery(PublicationEntity.class);
|
||||
Root<PublicationEntity> fromPublication = query.from(PublicationEntity.class);
|
||||
|
||||
Predicate predicate = publicationPredicateMapper.map(criteria, criteriaBuilder, fromPublication);
|
||||
|
||||
CriteriaQuery<PublicationEntity> criteriaQuery = query.select(fromPublication)
|
||||
.distinct(true)
|
||||
.where(predicate);
|
||||
|
||||
return entityManager.createQuery(criteriaQuery)
|
||||
.getResultList();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
package org.codiki.infrastructure.publication.repository;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
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.springframework.stereotype.Component;
|
||||
|
||||
import static jakarta.persistence.criteria.JoinType.LEFT;
|
||||
import jakarta.persistence.criteria.CriteriaBuilder;
|
||||
import jakarta.persistence.criteria.From;
|
||||
import jakarta.persistence.criteria.Predicate;
|
||||
import jakarta.persistence.criteria.Root;
|
||||
|
||||
@Component
|
||||
public class PublicationPredicateMapper {
|
||||
public Predicate map(
|
||||
List<PublicationSearchCriterion> criteria,
|
||||
CriteriaBuilder criteriaBuilder,
|
||||
Root<PublicationEntity> fromPublication
|
||||
) {
|
||||
List<Predicate> criteriaPredicates = criteria.stream()
|
||||
.map(criterion -> map(criterion, criteriaBuilder, fromPublication))
|
||||
.toList();
|
||||
return criteriaBuilder.or(criteriaPredicates.toArray(new Predicate[]{}));
|
||||
}
|
||||
|
||||
private Predicate map(
|
||||
PublicationSearchCriterion criterion,
|
||||
CriteriaBuilder criteriaBuilder,
|
||||
Root<PublicationEntity> fromPublication
|
||||
) {
|
||||
return switch (criterion.searchType()) {
|
||||
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<PublicationEntity> fromPublication,
|
||||
PublicationSearchField searchField,
|
||||
Object value
|
||||
) {
|
||||
From<?, ?> from = fromPublication;
|
||||
String attributeName = searchField.name().toLowerCase();
|
||||
if (searchField == AUTHOR_PSEUDO) {
|
||||
from = fromPublication.join("author", LEFT);
|
||||
attributeName = "pseudo";
|
||||
}
|
||||
|
||||
return criteriaBuilder.equal(
|
||||
criteriaBuilder.lower(
|
||||
from.get(attributeName)
|
||||
),
|
||||
value
|
||||
);
|
||||
}
|
||||
|
||||
private Predicate mapContainsPredicate(
|
||||
CriteriaBuilder criteriaBuilder,
|
||||
Root<PublicationEntity> fromPublication,
|
||||
PublicationSearchField searchField,
|
||||
Object value
|
||||
) {
|
||||
From<?, ?> from = fromPublication;
|
||||
String attributeName = searchField.name().toLowerCase();
|
||||
if (searchField == AUTHOR_PSEUDO) {
|
||||
from = fromPublication.join("author", LEFT);
|
||||
attributeName = "pseudo";
|
||||
}
|
||||
|
||||
return criteriaBuilder.like(
|
||||
criteriaBuilder.lower(
|
||||
from.get(attributeName)
|
||||
),
|
||||
String.format("%%%s%%", value)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package org.codiki.infrastructure.publication.repository;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.codiki.infrastructure.publication.model.PublicationEntity;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.data.jpa.repository.Query;
|
||||
import org.springframework.data.repository.query.Param;
|
||||
|
||||
public interface PublicationRepository extends JpaRepository<PublicationEntity, UUID>, CustomPublicationRepository {
|
||||
@Query("""
|
||||
SELECT p
|
||||
FROM PublicationEntity p
|
||||
JOIN FETCH p.author a
|
||||
WHERE p.id = :publicationId
|
||||
""")
|
||||
Optional<PublicationEntity> findById(@Param("publicationId") UUID publicationId);
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
package org.codiki.infrastructure.user.adapter;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.codiki.domain.user.model.RefreshToken;
|
||||
import org.codiki.domain.user.model.User;
|
||||
import org.codiki.domain.user.port.UserPort;
|
||||
import org.codiki.infrastructure.user.model.RefreshTokenEntity;
|
||||
import org.codiki.infrastructure.user.repository.RefreshTokenJpaRepository;
|
||||
import org.codiki.infrastructure.user.repository.UserJpaRepository;
|
||||
import org.codiki.infrastructure.user.model.UserEntity;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
public class UserJpaAdapter implements UserPort {
|
||||
private final RefreshTokenJpaRepository refreshTokenJpaRepository;
|
||||
private final UserJpaRepository userJpaRepository;
|
||||
|
||||
public UserJpaAdapter(
|
||||
RefreshTokenJpaRepository refreshTokenJpaRepository,
|
||||
UserJpaRepository userJpaRepository
|
||||
) {
|
||||
this.refreshTokenJpaRepository = refreshTokenJpaRepository;
|
||||
this.userJpaRepository = userJpaRepository;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<User> findById(UUID userId) {
|
||||
return userJpaRepository.findById(userId)
|
||||
.map(UserEntity::toUser);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<User> findByEmail(String userEmail) {
|
||||
return userJpaRepository.findByEmail(userEmail)
|
||||
.map(UserEntity::toUser);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<User> findAll() {
|
||||
return userJpaRepository.findAll()
|
||||
.stream()
|
||||
.map(UserEntity::toUser)
|
||||
.toList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void save(User user) {
|
||||
UserEntity userEntity = new UserEntity(user);
|
||||
userJpaRepository.save(userEntity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean existsById(UUID userId) {
|
||||
return userJpaRepository.existsById(userId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean existsByEmail(String email) {
|
||||
return userJpaRepository.existsByEmail(email);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<RefreshToken> findRefreshTokenByUserId(UUID userId) {
|
||||
return refreshTokenJpaRepository.findByUserId(userId)
|
||||
.map(RefreshTokenEntity::toRefreshToken);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<RefreshToken> findRefreshTokenById(UUID refreshTokenId) {
|
||||
return refreshTokenJpaRepository.findByValue(refreshTokenId)
|
||||
.map(RefreshTokenEntity::toRefreshToken);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void save(RefreshToken refreshToken) {
|
||||
RefreshTokenEntity refreshTokenEntity = new RefreshTokenEntity(refreshToken);
|
||||
refreshTokenJpaRepository.save(refreshTokenEntity);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package org.codiki.infrastructure.user.model;
|
||||
|
||||
import java.time.ZonedDateTime;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.codiki.domain.user.model.RefreshToken;
|
||||
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.Table;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
|
||||
@Entity
|
||||
@Table(name = "refresh_token")
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
@Getter
|
||||
@Setter
|
||||
public class RefreshTokenEntity {
|
||||
@Id
|
||||
private UUID userId;
|
||||
@Column(nullable = false)
|
||||
private UUID value;
|
||||
@Column(nullable = false)
|
||||
private ZonedDateTime expirationDate;
|
||||
|
||||
public RefreshTokenEntity(RefreshToken refreshToken) {
|
||||
userId = refreshToken.userId();
|
||||
value = refreshToken.value();
|
||||
expirationDate = refreshToken.expirationDate();
|
||||
}
|
||||
|
||||
public RefreshToken toRefreshToken() {
|
||||
return new RefreshToken(userId, value, expirationDate);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
package org.codiki.infrastructure.user.model;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.codiki.domain.user.model.User;
|
||||
import org.codiki.domain.user.model.UserRole;
|
||||
|
||||
import jakarta.persistence.CollectionTable;
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.ElementCollection;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.JoinColumn;
|
||||
import jakarta.persistence.Table;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
|
||||
@Entity
|
||||
@Table(name = "`user`")
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
@Getter
|
||||
@Setter
|
||||
public class UserEntity {
|
||||
@Id
|
||||
private UUID id;
|
||||
@Column(nullable = false)
|
||||
private String pseudo;
|
||||
@Column(nullable = false)
|
||||
private String email;
|
||||
@Column(nullable = false)
|
||||
private String password;
|
||||
@Column
|
||||
private UUID photoId;
|
||||
@ElementCollection(targetClass = UserRole.class)
|
||||
@CollectionTable(
|
||||
name = "user_role",
|
||||
joinColumns = @JoinColumn(name = "user_id")
|
||||
)
|
||||
@Column(name = "role")
|
||||
private List<UserRole> roles;
|
||||
|
||||
public UserEntity(User user) {
|
||||
id = user.id();
|
||||
pseudo = user.pseudo();
|
||||
email = user.email();
|
||||
password = user.password();
|
||||
photoId = user.photoId();
|
||||
roles = user.roles();
|
||||
}
|
||||
|
||||
public User toUser() {
|
||||
return new User(
|
||||
id,
|
||||
pseudo,
|
||||
email,
|
||||
password,
|
||||
photoId,
|
||||
roles
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package org.codiki.infrastructure.user.repository;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.codiki.infrastructure.user.model.RefreshTokenEntity;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
@Repository
|
||||
public interface RefreshTokenJpaRepository extends JpaRepository<RefreshTokenEntity, UUID> {
|
||||
Optional<RefreshTokenEntity> findByUserId(UUID userId);
|
||||
|
||||
Optional<RefreshTokenEntity> findByValue(UUID refreshTokenId);
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package org.codiki.infrastructure.user.repository;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.codiki.infrastructure.user.model.UserEntity;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.data.jpa.repository.Query;
|
||||
import org.springframework.data.repository.query.Param;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
@Repository
|
||||
public interface UserJpaRepository extends JpaRepository<UserEntity, UUID> {
|
||||
@Query("SELECT u FROM UserEntity u JOIN FETCH u.roles WHERE u.id = :userId")
|
||||
Optional<UserEntity> findById(@Param("userId") UUID userId);
|
||||
|
||||
@Query("SELECT u FROM UserEntity u JOIN FETCH u.roles WHERE u.email = :email")
|
||||
Optional<UserEntity> findByEmail(@Param("email") String userEmail);
|
||||
|
||||
@Query("SELECT u FROM UserEntity u JOIN FETCH u.roles")
|
||||
List<UserEntity> findAll();
|
||||
|
||||
boolean existsByEmail(String email);
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
insert into "user" values
|
||||
('5ad462b8-8f9e-4a26-bb86-c74fef5d11b6', 'Standard user', 'standard.user@codiki.org', '$2a$10$FVhrYRXw.Zw2V5jGUkvX/.1U.IdWlwd8J.Y/5pb5etAzyoBhJ3FHG', null),
|
||||
('15a13dc7-029d-4eab-a63d-c1e96f90241d', 'Admin user', 'admin.user@codiki.org', '$2a$10$FVhrYRXw.Zw2V5jGUkvX/.1U.IdWlwd8J.Y/5pb5etAzyoBhJ3FHG', null);
|
||||
|
||||
insert into user_role values
|
||||
('5ad462b8-8f9e-4a26-bb86-c74fef5d11b6', 0),
|
||||
('15a13dc7-029d-4eab-a63d-c1e96f90241d', 0),
|
||||
('15a13dc7-029d-4eab-a63d-c1e96f90241d', 1);
|
||||
|
||||
insert into category values
|
||||
('172fa901-3f4b-4540-92f3-1c15820e8ec9', 'Main category', null),
|
||||
('3f4b4540-a901-92f3-1c15-8ec9172f820e', 'Sub category', '172fa901-3f4b-4540-92f3-1c15820e8ec9');
|
||||
@@ -0,0 +1,12 @@
|
||||
\c codiki_db
|
||||
|
||||
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
|
||||
|
||||
CREATE USER codiki_user
|
||||
WITH PASSWORD 'password'
|
||||
NOCREATEDB;
|
||||
|
||||
GRANT SELECT, INSERT, UPDATE, DELETE
|
||||
ON ALL TABLES
|
||||
IN SCHEMA public
|
||||
TO codiki_user;
|
||||
@@ -0,0 +1,65 @@
|
||||
CREATE TABLE IF NOT EXISTS "user" (
|
||||
id UUID NOT NULL,
|
||||
pseudo VARCHAR NOT NULL,
|
||||
email VARCHAR NOT NULL,
|
||||
password VARCHAR NOT NULL,
|
||||
photo_id UUID,
|
||||
CONSTRAINT user_pk PRIMARY KEY (id)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS user_role (
|
||||
user_id UUID NOT NULL,
|
||||
role SMALLINT,
|
||||
CONSTRAINT user_role_pk PRIMARY KEY (user_id, role),
|
||||
CONSTRAINT user_role_fk_user_id FOREIGN KEY (user_id) REFERENCES "user" (id)
|
||||
);
|
||||
CREATE INDEX user_role_fk_user_id_idx ON user_role (user_id);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS refresh_token (
|
||||
user_id UUID NOT NULL,
|
||||
value UUID NOT NULL,
|
||||
expiration_date TIMESTAMP NOT NULL,
|
||||
CONSTRAINT refresh_token_pk PRIMARY KEY (user_id),
|
||||
CONSTRAINT refresh_token_fk_user_id FOREIGN KEY (user_id) REFERENCES "user" (id)
|
||||
);
|
||||
CREATE INDEX refresh_token_fk_user_id_idx ON user_role (user_id);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS category (
|
||||
id UUID NOT NULL,
|
||||
name VARCHAR NOT NULL,
|
||||
parent_category_id UUID,
|
||||
CONSTRAINT category_pk PRIMARY KEY (id),
|
||||
CONSTRAINT category_parent_category_id_fk FOREIGN KEY (parent_category_id) REFERENCES category (id)
|
||||
);
|
||||
CREATE INDEX category_parent_category_id_idx ON category (parent_category_id);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS picture (
|
||||
id UUID NOT NULL,
|
||||
publisher_id UUID NOT NULL,
|
||||
CONSTRAINT picture_pk PRIMARY KEY (id),
|
||||
CONSTRAINT picture_publisher_id_fk FOREIGN KEY (publisher_id) REFERENCES "user" (id)
|
||||
);
|
||||
CREATE INDEX picture_publisher_id_idx ON picture (publisher_id);
|
||||
|
||||
ALTER TABLE "user" ADD CONSTRAINT user_photo_id_fk FOREIGN KEY (photo_id) REFERENCES picture (id);
|
||||
CREATE INDEX user_photo_id_idx ON "user" (photo_id);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS publication (
|
||||
id UUID NOT NULL,
|
||||
key VARCHAR(14) NOT NULL,
|
||||
title VARCHAR NOT NULL,
|
||||
text VARCHAR NOT NULL,
|
||||
description VARCHAR NOT NULL,
|
||||
creation_date TIMESTAMP NOT NULL,
|
||||
illustration_id UUID NOT NULL,
|
||||
author_id UUID NOT NULL,
|
||||
category_id UUID NOT NULL,
|
||||
CONSTRAINT publication_pk PRIMARY KEY (id),
|
||||
CONSTRAINT publication_picture_id_fk FOREIGN KEY (illustration_id) REFERENCES picture (id),
|
||||
CONSTRAINT publication_author_id_fk FOREIGN KEY (author_id) REFERENCES "user" (id),
|
||||
CONSTRAINT publication_category_id_fk FOREIGN KEY (category_id) REFERENCES category (id)
|
||||
);
|
||||
CREATE INDEX publication_picture_id_idx ON publication (illustration_id);
|
||||
CREATE INDEX publication_author_id_idx ON publication (author_id);
|
||||
CREATE INDEX publication_category_id_idx ON publication (category_id);
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
package org.codiki.infrastructure.publication;
|
||||
|
||||
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;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
adapter = new PublicationSearchCriteriaJpaAdapter();
|
||||
}
|
||||
|
||||
@Nested
|
||||
public class AdaptCriteriaForJpa {
|
||||
@Test
|
||||
void should_adapt_criteria_for_jpa() {
|
||||
// given
|
||||
List<PublicationSearchCriterion> initialCriteria = List.of(
|
||||
new PublicationSearchCriterion(KEY, CONTAINS, "critère")
|
||||
);
|
||||
|
||||
// when
|
||||
List<PublicationSearchCriterion> result = adapter.adaptCriteriaForJpa(initialCriteria);
|
||||
|
||||
// then
|
||||
List<PublicationSearchCriterion> expectedResult = List.of(
|
||||
new PublicationSearchCriterion(KEY, CONTAINS, "crit_re")
|
||||
);
|
||||
assertThat(result).isEqualTo(expectedResult);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,140 @@
|
||||
package org.codiki.infrastructure.publication.model;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.codiki.domain.publication.model.builder.PublicationBuilder.aPublication;
|
||||
import static org.codiki.domain.publication.model.search.ComparisonType.CONTAINS;
|
||||
import static org.codiki.domain.publication.model.search.PublicationSearchField.DESCRIPTION;
|
||||
import static org.codiki.domain.publication.model.search.PublicationSearchField.TEXT;
|
||||
import static org.codiki.domain.publication.model.search.PublicationSearchField.TITLE;
|
||||
import org.codiki.domain.publication.model.Publication;
|
||||
import org.codiki.domain.publication.model.builder.PublicationBuilder;
|
||||
import org.codiki.domain.publication.model.search.PublicationSearchCriterion;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.Arguments;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
|
||||
class PublicationSearchResultTest {
|
||||
@ParameterizedTest
|
||||
@MethodSource("arguments_of_constructor_should_compute_score")
|
||||
void constructor_should_compute_score(
|
||||
String functionalRule,
|
||||
String fieldValue,
|
||||
List<PublicationSearchCriterion> criteria,
|
||||
int expectedPoints
|
||||
) {
|
||||
// given
|
||||
PublicationBuilder publicationBuilder = aPublication();
|
||||
|
||||
switch (criteria.getFirst().searchField()) {
|
||||
case TITLE -> publicationBuilder.withTitle(fieldValue);
|
||||
case DESCRIPTION -> publicationBuilder.withDescription(fieldValue);
|
||||
case TEXT -> publicationBuilder.withText(fieldValue);
|
||||
}
|
||||
|
||||
Publication publication = publicationBuilder.build();
|
||||
|
||||
// when
|
||||
PublicationSearchResult publicationSearchResult = new PublicationSearchResult(publication, criteria);
|
||||
|
||||
// then
|
||||
assertThat(publicationSearchResult.getSearchScore()).isEqualTo(expectedPoints);
|
||||
}
|
||||
|
||||
private static Stream<Arguments> arguments_of_constructor_should_compute_score() {
|
||||
return Stream.of(
|
||||
// TITLE RULES
|
||||
Arguments.of(
|
||||
"exact match for title gives 1000 pts",
|
||||
"Exact title",
|
||||
List.of(new PublicationSearchCriterion(TITLE, CONTAINS, "Exact title")),
|
||||
1000
|
||||
),
|
||||
Arguments.of(
|
||||
"one match in title gives 10 pts",
|
||||
"Exact title",
|
||||
List.of(new PublicationSearchCriterion(TITLE, CONTAINS, "exact")),
|
||||
10
|
||||
),
|
||||
Arguments.of(
|
||||
"multiple matches in title gives 10 pts per match",
|
||||
"One super title with lot of words",
|
||||
List.of(
|
||||
new PublicationSearchCriterion(TITLE, CONTAINS, "super"),
|
||||
new PublicationSearchCriterion(TITLE, CONTAINS, "words")
|
||||
),
|
||||
20
|
||||
),
|
||||
Arguments.of(
|
||||
"one match in title gives 1 pts per match if criterion value is a title word part",
|
||||
"One super title",
|
||||
List.of(
|
||||
new PublicationSearchCriterion(TITLE, CONTAINS, "sup")
|
||||
),
|
||||
3
|
||||
),
|
||||
// DESCRIPTION RULES
|
||||
Arguments.of(
|
||||
"exact match for description gives 100 pts",
|
||||
"Exact description",
|
||||
List.of(new PublicationSearchCriterion(DESCRIPTION, CONTAINS, "Exact description")),
|
||||
100
|
||||
),
|
||||
Arguments.of(
|
||||
"one match in description gives 7 pts",
|
||||
"Exact description",
|
||||
List.of(new PublicationSearchCriterion(DESCRIPTION, CONTAINS, "exact")),
|
||||
7
|
||||
),
|
||||
Arguments.of(
|
||||
"multiple matches in description gives 7 pts per match",
|
||||
"One super description with lot of words",
|
||||
List.of(
|
||||
new PublicationSearchCriterion(DESCRIPTION, CONTAINS, "super"),
|
||||
new PublicationSearchCriterion(DESCRIPTION, CONTAINS, "words")
|
||||
),
|
||||
14
|
||||
),
|
||||
Arguments.of(
|
||||
"one match in description gives 2 pts per match if criterion value is a description word part",
|
||||
"One super description",
|
||||
List.of(
|
||||
new PublicationSearchCriterion(DESCRIPTION, CONTAINS, "sup")
|
||||
),
|
||||
2
|
||||
),
|
||||
// TEXT RULES
|
||||
Arguments.of(
|
||||
"exact match for text gives 100 pts",
|
||||
"Exact text",
|
||||
List.of(new PublicationSearchCriterion(TEXT, CONTAINS, "Exact text")),
|
||||
100
|
||||
),
|
||||
Arguments.of(
|
||||
"one match in text gives 7 pts",
|
||||
"Exact text",
|
||||
List.of(new PublicationSearchCriterion(TEXT, CONTAINS, "exact")),
|
||||
4
|
||||
),
|
||||
Arguments.of(
|
||||
"multiple matches in text gives 7 pts per match",
|
||||
"One super text with lot of words",
|
||||
List.of(
|
||||
new PublicationSearchCriterion(TEXT, CONTAINS, "super"),
|
||||
new PublicationSearchCriterion(TEXT, CONTAINS, "words")
|
||||
),
|
||||
8
|
||||
),
|
||||
Arguments.of(
|
||||
"one match in text gives 2 pts per match if criterion value is a text word part",
|
||||
"One super text",
|
||||
List.of(
|
||||
new PublicationSearchCriterion(TEXT, CONTAINS, "sup")
|
||||
),
|
||||
1
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user