OfferController

This commit is contained in:
Patrick Haßel 2024-07-29 13:27:01 +02:00
parent d7a635649a
commit fed7cd420a
6 changed files with 202 additions and 12 deletions

View File

@ -0,0 +1,75 @@
package de.ph87.kleinanzeigen.crud;
import jakarta.persistence.criteria.CriteriaBuilder;
import jakarta.persistence.criteria.CriteriaQuery;
import jakarta.persistence.criteria.Predicate;
import jakarta.persistence.criteria.Root;
import lombok.Data;
import lombok.Getter;
import lombok.NonNull;
import lombok.ToString;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.domain.Specification;
import java.util.Collections;
import java.util.List;
@Getter
@ToString
public class CrudFilter<T> {
protected final int page;
protected final int size;
@NonNull
protected final List<Order> orders;
public CrudFilter(final int page, final int size, @NonNull final List<Order> orders) {
this.page = page;
this.size = size;
this.orders = orders;
}
@NonNull
public Pageable getPageable() {
return PageRequest.of(page, size, getSort());
}
@NonNull
private Sort getSort() {
return Sort.by(orders.stream().map(Order::toOrder).toList());
}
@NonNull
public Specification<T> getSpecification() {
return (root, query, criteriaBuilder) -> {
final List<Predicate> predicates = getPredicates(root, query, criteriaBuilder);
return criteriaBuilder.and(predicates.toArray(Predicate[]::new));
};
}
@NonNull
protected List<Predicate> getPredicates(@NonNull final Root<T> root, final CriteriaQuery<?> query, @NonNull final CriteriaBuilder criteriaBuilder) {
return Collections.emptyList();
}
@Data
public static class Order {
@NonNull
private final String property;
@NonNull
private final Sort.Direction direction;
@NonNull
public Sort.Order toOrder() {
return new Sort.Order(direction, property);
}
}
}

View File

@ -0,0 +1,57 @@
package de.ph87.kleinanzeigen.crud;
import jakarta.persistence.criteria.*;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.NonNull;
import lombok.ToString;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Locale;
@Getter
@ToString(callSuper = true)
@EqualsAndHashCode(callSuper = true)
public abstract class CrudFilterSearch<T> extends CrudFilter<T> {
@NonNull
protected final String search;
protected CrudFilterSearch(final int page, final int size, @NonNull final List<Order> orders, @NonNull final String search) {
super(page, size, orders);
this.search = search;
}
@NonNull
protected List<Predicate> getPredicates(@NonNull final Root<T> root, final CriteriaQuery<?> query, @NonNull final CriteriaBuilder criteriaBuilder) {
return new ArrayList<>(search(root, criteriaBuilder));
}
@NonNull
public List<Predicate> search(@NonNull final Root<T> root, @NonNull final CriteriaBuilder criteriaBuilder) {
final String[] words = search.replaceAll("([0-9])([a-zA-Z])", "$1 $2")
.replaceAll("([a-zA-Z])([0-9])", "$1 $2")
.replaceAll("([a-z])([A-Z])", "$1 $2")
.replaceAll("^\\W+|\\W+$", "")
.toLowerCase(Locale.ROOT)
.split("\\W+");
final List<Path<String>> fields = getFields(root);
return predicateAllWordsEachInAnyField(criteriaBuilder, words, fields);
}
@NonNull
private List<Predicate> predicateAllWordsEachInAnyField(@NonNull final CriteriaBuilder criteriaBuilder, @NonNull final String[] words, @NonNull final List<Path<String>> fields) {
return Arrays.stream(words).map(word -> predicateWordInAnyField(criteriaBuilder, word, fields)).toList();
}
@NonNull
private Predicate predicateWordInAnyField(@NonNull final CriteriaBuilder criteriaBuilder, @NonNull final String word, @NonNull final List<Path<String>> fields) {
return fields.stream().map(field -> criteriaBuilder.like(field, "%" + word + "%")).reduce(criteriaBuilder::or).orElseThrow();
}
@NonNull
protected abstract List<Path<String>> getFields(@NonNull final Root<T> root);
}

View File

@ -0,0 +1,24 @@
package de.ph87.kleinanzeigen.kleinanzeigen.offer;
import jakarta.annotation.Nullable;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
import org.springframework.web.bind.annotation.*;
@CrossOrigin
@RestController
@RequiredArgsConstructor
@RequestMapping("Offer")
public class OfferController {
private final OfferService offerService;
@GetMapping("filter")
public Page<OfferDto> getAllOffers(@Nullable @RequestBody(required = false) final OfferFilter filter) {
if (filter == null) {
return offerService.filter(OfferFilter.DEFAULT());
}
return offerService.filter(filter);
}
}

View File

@ -0,0 +1,30 @@
package de.ph87.kleinanzeigen.kleinanzeigen.offer;
import de.ph87.kleinanzeigen.crud.CrudFilterSearch;
import jakarta.persistence.criteria.Path;
import jakarta.persistence.criteria.Root;
import lombok.Getter;
import lombok.NonNull;
import lombok.ToString;
import org.springframework.data.domain.Sort;
import java.util.List;
@Getter
@ToString(callSuper = true)
public class OfferFilter extends CrudFilterSearch<Offer> {
public OfferFilter(final int page, final int size, @NonNull final List<Order> orders, @NonNull final String search) {
super(page, size, orders, search);
}
@Override
protected @NonNull List<Path<String>> getFields(final @NonNull Root<Offer> root) {
return List.of(root.get("title"), root.get("description"));
}
public static OfferFilter DEFAULT() {
return new OfferFilter(0, 30, List.of(new Order("articleDate", Sort.Direction.DESC)), "");
}
}

View File

@ -1,10 +1,11 @@
package de.ph87.kleinanzeigen.kleinanzeigen.offer; package de.ph87.kleinanzeigen.kleinanzeigen.offer;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.data.repository.ListCrudRepository; import org.springframework.data.repository.ListCrudRepository;
import java.util.Optional; import java.util.Optional;
public interface OfferRepository extends ListCrudRepository<Offer, Long> { public interface OfferRepository extends ListCrudRepository<Offer, Long>, JpaSpecificationExecutor<Offer> {
Optional<Offer> findByArticleId(String articleId); Optional<Offer> findByArticleId(String articleId);

View File

@ -4,6 +4,7 @@ import lombok.NonNull;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationEventPublisher; import org.springframework.context.ApplicationEventPublisher;
import org.springframework.data.domain.Page;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
@ -18,19 +19,16 @@ public class OfferService {
private final ApplicationEventPublisher publisher; private final ApplicationEventPublisher publisher;
public void updateOrCreate(@NonNull final OfferCreate create, final boolean resendOnPriceChange) { public void updateOrCreate(@NonNull final OfferCreate create, final boolean resendOnPriceChange) {
offerRepository.findByArticleId(create.getArticleId()).ifPresentOrElse( offerRepository.findByArticleId(create.getArticleId()).ifPresentOrElse(existing -> {
existing -> { if (existing.update(create, resendOnPriceChange)) {
if (existing.update(create, resendOnPriceChange)) { final OfferDto dto = toDto(existing);
final OfferDto dto = toDto(existing);
publisher.publishEvent(dto);
}
},
() -> {
final Offer offer = offerRepository.save(new Offer(create, resendOnPriceChange));
final OfferDto dto = toDto(offer);
publisher.publishEvent(dto); publisher.publishEvent(dto);
} }
); }, () -> {
final Offer offer = offerRepository.save(new Offer(create, resendOnPriceChange));
final OfferDto dto = toDto(offer);
publisher.publishEvent(dto);
});
} }
public OfferDto toDto(final Offer offer) { public OfferDto toDto(final Offer offer) {
@ -41,4 +39,9 @@ public class OfferService {
return offerRepository.findById(offerDto.getId()).orElseThrow(); return offerRepository.findById(offerDto.getId()).orElseThrow();
} }
@NonNull
public Page<OfferDto> filter(@NonNull final OfferFilter filter) {
return offerRepository.findAll(filter.getSpecification(), filter.getPageable()).map(OfferDto::new);
}
} }