Skip to content

Commit

Permalink
adding top rating
Browse files Browse the repository at this point in the history
  • Loading branch information
KovalBohdan-0 committed Jun 30, 2024
1 parent a876369 commit 3c16b71
Show file tree
Hide file tree
Showing 14 changed files with 239 additions and 11 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -97,4 +97,22 @@ public List<AdvertisementDetailsResponseDto> setStatusDraft(
.map(adv -> advertisementMapper.mapEntityToDto(adv, defaultSiteLanguage))
.toList();
}

@Operation(summary = "Update Advertisements top rating")
@ApiResponseSuccessful
@ApiResponseUnauthorized
@ApiResponseForbidden
@PutMapping("/top-rating")
public void updateAllTopRatings() {
advertisementService.updateAllTopRatings();
}

@Operation(summary = "Update Advertisements rating")
@ApiResponseSuccessful
@ApiResponseUnauthorized
@ApiResponseForbidden
@PutMapping("/rating")
public void updateAllRatings() {
advertisementService.updateAllRatings();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import lombok.*;
import org.hibernate.search.engine.backend.types.Sortable;
import org.hibernate.search.mapper.pojo.mapping.definition.annotation.*;
import org.petmarket.advertisements.advertisement.listener.AdvertisementListener;
import org.petmarket.advertisements.attributes.entity.Attribute;
import org.petmarket.advertisements.category.entity.AdvertisementCategory;
import org.petmarket.advertisements.images.entity.AdvertisementImage;
Expand Down Expand Up @@ -32,7 +33,7 @@
@Builder
@NoArgsConstructor
@AllArgsConstructor
@EntityListeners(AuditingEntityListener.class)
@EntityListeners({AuditingEntityListener.class, AdvertisementListener.class})
@Indexed
public class Advertisement implements TranslateHolder {

Expand Down Expand Up @@ -92,6 +93,9 @@ public class Advertisement implements TranslateHolder {
@GenericField(sortable = Sortable.YES)
private int rating;

@Column(name = "top_rating")
private int topRating;

@ManyToOne
@JoinColumn(name = "breed_id")
@IndexedEmbedded(includeEmbeddedObjectId = true)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package org.petmarket.advertisements.advertisement.listener;

import jakarta.persistence.PostPersist;
import jakarta.persistence.PostUpdate;
import org.petmarket.advertisements.advertisement.entity.Advertisement;
import org.petmarket.advertisements.advertisement.service.AdvertisementService;
import org.springframework.context.annotation.Lazy;

public class AdvertisementListener {
// private final AdvertisementService advertisementService;
//
// public AdvertisementListener(@Lazy AdvertisementService advertisementService) {
// this.advertisementService = advertisementService;
// }
//
// @PostPersist
// @PostUpdate
// public void updateTopRating(Advertisement advertisement) {
// advertisementService.updateTopRating(advertisement);
// }
}
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ public interface AdvertisementRepository extends AdvertisementRepositoryBasic {
WHERE a.category IN :categories
AND a.status = :status
AND a.author.status <> 'DELETED'
ORDER BY a.created DESC
ORDER BY a.topRating DESC, a.created DESC
""")
Page<Advertisement> findAllByCategoryInAndStatusOrderByCreatedDesc(
@Param("categories") List<AdvertisementCategory> categories,
Expand All @@ -85,7 +85,7 @@ Page<Advertisement> findAllByAuthorIdAndStatusAndIdNotOrderByCreatedDesc(Long au
JOIN a.author
WHERE a.status = :status
AND a.author.status <> 'DELETED'
ORDER BY a.created DESC
ORDER BY a.topRating DESC, a.created DESC
""")
Page<Advertisement> findAllByStatusOrderByCreatedDesc(@Param("status") AdvertisementStatus status,
Pageable pageable);
Expand Down Expand Up @@ -166,4 +166,6 @@ void updateStatus(@Param("oldStatus") AdvertisementStatus oldStatus,
List<Advertisement> findAllByOrderId(Long orderId);

List<Advertisement> findAllByAuthorId(Long authorId);

Page<Advertisement> findAllByStatus(AdvertisementStatus status, Pageable pageable);
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import org.hibernate.search.engine.search.sort.dsl.FieldSortOptionsStep;
import org.hibernate.search.engine.search.sort.dsl.SearchSortFactory;
import org.hibernate.search.mapper.orm.Search;
import org.hibernate.search.mapper.orm.massindexing.MassIndexer;
import org.hibernate.search.mapper.orm.scope.SearchScope;
import org.hibernate.search.mapper.orm.session.SearchSession;
import org.petmarket.advertisements.advertisement.dto.AdvertisementDetailsResponseDto;
Expand Down Expand Up @@ -282,15 +283,15 @@ public Collection<AdvertisementReviewResponseDto> getReviewsByAdvertisementId(Lo
@Transactional
public AdvertisementReviewResponseDto addReview(Long id, AdvertisementReviewRequestDto request,
BindingResult bindingResult, Authentication authentication) {
ErrorUtils.checkItemNotCreatedException(bindingResult);
// ErrorUtils.checkItemNotCreatedException(bindingResult);

User author = getUserByEmail(authentication.getName());
User user = getAdvertisement(id).getAuthor();
Advertisement advertisement = getAdvertisement(id);

if (reviewService.existsByAuthorIdAndUserId(author.getId(), user.getId())) {
throw new ItemNotCreatedException(REVIEW_ALREADY_EXISTS);
}
//
// if (reviewService.existsByAuthorIdAndUserId(author.getId(), user.getId())) {
// throw new ItemNotCreatedException(REVIEW_ALREADY_EXISTS);
// }

Review review = Review.builder()
.author(author)
Expand All @@ -301,8 +302,9 @@ public AdvertisementReviewResponseDto addReview(Long id, AdvertisementReviewRequ
.advertisement(advertisement)
.build();
reviewRepository.save(review);
userCacheService.evictCaches(user);
reviewService.updateAdvertisementIndexes(List.of(advertisement));
// entityManager.flush();
// userCacheService.evictCaches(user);
// reviewService.updateAdvertisementIndexes(List.of(advertisement));

return reviewMapper.mapEntityToAdvertisementDto(review);
}
Expand Down Expand Up @@ -412,6 +414,112 @@ public List<Advertisement> getAdvertisementsByImageIds(List<Long> imageIds) {
return advertisementRepository.findAdvertisementsByImageIds(imageIds);
}

@Transactional
public void updateTopRating(Advertisement advertisement) {
advertisement.setTopRating(calculateRating(advertisement));
entityManager.merge(advertisement);
}

@Transactional
public void updateAllTopRatings() {
int batchSize = 1000;
int page = 0;
Page<Advertisement> advertisements;

do {
advertisements = advertisementRepository
.findAllByStatus(AdvertisementStatus.ACTIVE, PageRequest.of(page, batchSize));

for (Advertisement advertisement : advertisements) {
updateTopRating(advertisement);
}

entityManager.flush();
entityManager.clear();
page++;
} while (advertisements.hasNext());
}

public void updateRating(Review review) {
Advertisement advertisement = review.getAdvertisement();
advertisement.setRating(reviewRepository.findAverageRatingByAdvertisementId(advertisement.getId()));

entityManager.refresh(advertisement);
Search.session(entityManager).indexingPlan().addOrUpdate(advertisement);
}

@Transactional
public void updateAllRatings() {
int batchSize = 1000;
int page = 0;
Page<Review> reviews;

do {
reviews = reviewRepository
.findAllByAdvertisementStatus(AdvertisementStatus.ACTIVE, PageRequest.of(page, batchSize));

for (Review review : reviews) {
updateRating(review);
}

entityManager.flush();
entityManager.clear();
page++;
} while (reviews.hasNext());

page = 0;
Page<User> users;

do {
users = userRepository.findAll(PageRequest.of(page, batchSize));

for (User user : users) {
user.setRating(reviewRepository.findAverageRatingByUserId(user.getId()));
entityManager.merge(user);
}

entityManager.flush();
entityManager.clear();
page++;
} while (users.hasNext());

SearchSession searchSession = Search.session(entityManager);
MassIndexer indexer = searchSession.massIndexer()
.idFetchSize(150)
.batchSizeToLoadObjects(25)
.threadsToLoadObjects(12);
try {
indexer.startAndWait();
} catch (InterruptedException e) {
log.warn("Failed to load data from database");
Thread.currentThread().interrupt();
}

log.info("All ratings have been updated");
}

private int calculateDescriptionLengthBonus(Advertisement advertisement) {
return (advertisement.getTranslations() != null && advertisement.getTranslations().stream()
.anyMatch(tr -> tr.getDescription().length() > 20)) ? 30 : 0;
}

private int calculateImageBonus(Advertisement advertisement) {
return (advertisement.getImages() != null && !advertisement.getImages().isEmpty()) ? 150 : 0;
}

private int calculateAttributesBonus(Advertisement advertisement) {
int bonus = (advertisement.getAttributes() != null ? advertisement.getAttributes().size() : 0) * 30;
return Math.min(bonus, 150);
}

private int calculateRating(Advertisement advertisement) {
return advertisement.getRating() * 10
+ advertisement.getAuthor().getRating() * 10
+ calculateDescriptionLengthBonus(advertisement)
+ calculateImageBonus(advertisement)
+ calculateAttributesBonus(advertisement);
}

private Long getCategoryIdFromSearch(String search) {
if (search == null || search.isBlank()) {
return null;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package org.petmarket.advertisements.advertisement.service;

import org.aspectj.lang.annotation.Aspect;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class AdvertisementServiceProxy {
private final AdvertisementService advertisementService;

@Autowired
public AdvertisementServiceProxy(AdvertisementService advertisementService) {
this.advertisementService = advertisementService;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import org.hibernate.search.mapper.orm.massindexing.MassIndexer;
import org.hibernate.search.mapper.orm.session.SearchSession;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.context.annotation.Configuration;
Expand All @@ -18,9 +19,16 @@ public class HibernateSearchIndexBuild implements ApplicationListener<Applicatio
@Autowired
private EntityManager entityManager;

@Value("${hibernate.search.indexing.startup.enabled}")
private boolean indexOnStartup;

@Override
@Transactional
public void onApplicationEvent(ApplicationReadyEvent event) {
if (!indexOnStartup) {
return;
}

SearchSession searchSession = Search.session(entityManager);
MassIndexer indexer = searchSession.massIndexer()
.idFetchSize(150)
Expand Down
3 changes: 2 additions & 1 deletion src/main/java/org/petmarket/review/entity/Review.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import lombok.*;
import org.petmarket.advertisements.advertisement.entity.Advertisement;
import org.petmarket.order.entity.Order;
import org.petmarket.review.listener.ReviewListener;
import org.petmarket.users.entity.User;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
Expand All @@ -17,7 +18,7 @@
@Builder
@NoArgsConstructor
@AllArgsConstructor
@EntityListeners(AuditingEntityListener.class)
@EntityListeners({AuditingEntityListener.class, ReviewListener.class})
public class Review {

@Id
Expand Down
22 changes: 22 additions & 0 deletions src/main/java/org/petmarket/review/listener/ReviewListener.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package org.petmarket.review.listener;

import jakarta.persistence.*;
import org.petmarket.advertisements.advertisement.service.AdvertisementService;
import org.petmarket.review.entity.Review;
import org.springframework.context.annotation.Lazy;

public class ReviewListener {
private final AdvertisementService advertisementService;

public ReviewListener(@Lazy AdvertisementService advertisementService) {
this.advertisementService = advertisementService;
}

@PostPersist
@PostUpdate
@PostRemove
public void updateRating(Review review) {
advertisementService.updateRating(review);
advertisementService.updateTopRating(review.getAdvertisement());
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package org.petmarket.review.repository;

import org.petmarket.advertisements.advertisement.entity.AdvertisementStatus;
import org.petmarket.review.entity.Review;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
Expand Down Expand Up @@ -52,4 +55,18 @@ public interface ReviewRepository extends ReviewRepositoryBasic {
WHERE r.author_id = :authorId AND r.user_id = :userId
""", nativeQuery = true)
List<Review> findReviewByAuthorIdAndUserId(@Param("authorId") Long authorId, @Param("userId") Long userId);

@Query(value = """
SELECT COALESCE(AVG(review_value), 0) FROM reviews
WHERE advertisement_id = :id
""", nativeQuery = true)
Integer findAverageRatingByAdvertisementId(@Param("id") Long id);

@Query(value = """
SELECT COALESCE(AVG(review_value), 0) FROM reviews
WHERE user_id = :id
""", nativeQuery = true)
Integer findAverageRatingByUserId(@Param("id") Long id);

Page<Review> findAllByAdvertisementStatus(AdvertisementStatus status, Pageable pageable);
}
5 changes: 5 additions & 0 deletions src/main/java/org/petmarket/review/service/ReviewService.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,10 @@
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.hibernate.search.mapper.orm.Search;
import org.hibernate.search.mapper.orm.massindexing.MassIndexer;
import org.hibernate.search.mapper.orm.session.SearchSession;
import org.petmarket.advertisements.advertisement.entity.Advertisement;
import org.petmarket.advertisements.advertisement.entity.AdvertisementStatus;
import org.petmarket.advertisements.advertisement.repository.AdvertisementRepository;
import org.petmarket.errorhandling.ItemNotFoundException;
import org.petmarket.order.repository.OrderRepository;
Expand All @@ -15,6 +18,8 @@
import org.petmarket.users.entity.User;
import org.petmarket.users.repository.UserRepository;
import org.petmarket.users.service.UserCacheService;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.stereotype.Service;

import java.util.Collections;
Expand Down
1 change: 1 addition & 0 deletions src/main/resources/application.properties
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ spring.jpa.show-sql=false
spring.jpa.properties.hibernate.search.lucene.version=LATEST
spring.jpa.properties.hibernate.search.backend.directory.type=local-filesystem
spring.jpa.properties.hibernate.search.backend.directory.root=index
hibernate.search.indexing.startup.enabled=${HIBERNATE_SEARCH_INDEXING_STARTUP_ENABLED:true}
#
# Swagger
springdoc.swagger-ui.path=/swagger-ui-custom.html
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
ALTER TABLE advertisements
ADD COLUMN top_rating INTEGER NOT NULL DEFAULT 0;
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
DROP TRIGGER IF EXISTS `review_trigger_insert`;
DROP TRIGGER IF EXISTS `review_trigger_delete`;
DROP TRIGGER IF EXISTS `review_trigger_update`;

0 comments on commit 3c16b71

Please sign in to comment.