Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
authorize -> authorize
.requestMatchers(PUBLIC_URLS).permitAll()
.requestMatchers(HttpMethod.GET, PUBLIC_URLS_GET).permitAll()
.requestMatchers(HttpMethod.POST, "/api/v1/properties/filtered/").permitAll()
.requestMatchers(HttpMethod.DELETE, ADMIN_URLS).hasAuthority("ROLE_ADMIN")
.requestMatchers(HttpMethod.POST, ADMIN_URLS).hasAuthority("ROLE_ADMIN")
.requestMatchers(HttpMethod.PATCH, ADMIN_URLS).hasAuthority("ROLE_ADMIN")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import kattsyn.dev.rentplace.dtos.ImageDTO;
import kattsyn.dev.rentplace.dtos.PropertyCreateEditDTO;
import kattsyn.dev.rentplace.dtos.PropertyDTO;
import kattsyn.dev.rentplace.dtos.filters.PropertyFilterDTO;
import kattsyn.dev.rentplace.services.PropertyService;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
Expand Down Expand Up @@ -76,6 +77,20 @@ public ResponseEntity<List<PropertyDTO>> getProperties() {
return ResponseEntity.ok(properties);
}

@Operation(
summary = "Получение всех объявлений, с фильтрацией",
description = "Позволяет получить все объявления, с фильтрацией"
)
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "Успешно", content = @Content(mediaType = "application/json", schema = @Schema(implementation = PropertyDTO[].class))),
@ApiResponse(responseCode = "500", description = "Непредвиденная ошибка со стороны сервера", content = @Content)
})
@PostMapping(path = "/filtered/", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public ResponseEntity<List<PropertyDTO>> getPropertiesByFilter(@Valid @ModelAttribute PropertyFilterDTO propertyFilter) {
List<PropertyDTO> properties = propertyService.findAllByFilter(propertyFilter);
return ResponseEntity.ok(properties);
}

@Operation(
summary = "Получение объявлений пользователя",
description = "Позволяет получить все объявления пользователя по его токену. Только для авторизованных пользователей."
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package kattsyn.dev.rentplace.dtos.filters;

import jakarta.validation.constraints.Min;
import kattsyn.dev.rentplace.enums.SortType;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

import java.util.List;

@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class PropertyFilterDTO {

private SortType sortType;
private List<Long> categoryIds;
private List<Long> facilityIds;
private Boolean isLongTermRent;
@Min(0)
private Integer minPrice;
@Min(0)
private Integer maxPrice;
@Min(0)
private Integer guestsAmount;
@Min(0)
private Integer bedsAmount;
@Min(0)
private Integer bedrooms;
@Min(0)
private Integer rooms;

}
19 changes: 19 additions & 0 deletions rentplace/src/main/java/kattsyn/dev/rentplace/enums/SortType.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package kattsyn.dev.rentplace.enums;

import io.swagger.v3.oas.annotations.media.Schema;

@Schema(
description = "Тип сортировки объявлений"
)
public enum SortType {

@Schema(name = "Сначала новые")
MOST_RECENT,
@Schema(name = "Сначала старые")
MOST_OLD,
@Schema(name = "Сначала дорогие, убывание по цене")
MOST_EXPENSIVE,
@Schema(name = "Сначала дешевые, возрастание по цене")
MOST_CHEAP

}
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,14 @@

import kattsyn.dev.rentplace.entities.Property;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;

import java.util.List;
import java.util.Optional;

public interface PropertyRepository extends JpaRepository<Property, Long> {
public interface PropertyRepository extends JpaRepository<Property, Long>, JpaSpecificationExecutor<Property> {

@Query("""
SELECT DISTINCT p
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import kattsyn.dev.rentplace.dtos.ImageDTO;
import kattsyn.dev.rentplace.dtos.PropertyCreateEditDTO;
import kattsyn.dev.rentplace.dtos.PropertyDTO;
import kattsyn.dev.rentplace.dtos.filters.PropertyFilterDTO;
import kattsyn.dev.rentplace.entities.Property;
import org.springframework.web.multipart.MultipartFile;

Expand All @@ -18,6 +19,8 @@ public interface PropertyService {

List<PropertyDTO> findAllByOwnerEmail(String email);

List<PropertyDTO> findAllByFilter(PropertyFilterDTO filter);

PropertyDTO findById(long id);

Property getPropertyById(long id);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,13 @@
import kattsyn.dev.rentplace.dtos.ImageDTO;
import kattsyn.dev.rentplace.dtos.PropertyCreateEditDTO;
import kattsyn.dev.rentplace.dtos.PropertyDTO;
import kattsyn.dev.rentplace.dtos.filters.PropertyFilterDTO;
import kattsyn.dev.rentplace.entities.Image;
import kattsyn.dev.rentplace.entities.Property;
import kattsyn.dev.rentplace.entities.User;
import kattsyn.dev.rentplace.enums.ImageType;
import kattsyn.dev.rentplace.enums.Role;
import kattsyn.dev.rentplace.enums.SortType;
import kattsyn.dev.rentplace.exceptions.ForbiddenException;
import kattsyn.dev.rentplace.exceptions.NotFoundException;
import kattsyn.dev.rentplace.mappers.ImageMapper;
Expand All @@ -17,8 +19,10 @@
import kattsyn.dev.rentplace.services.ImageService;
import kattsyn.dev.rentplace.services.PropertyService;
import kattsyn.dev.rentplace.services.UserService;
import kattsyn.dev.rentplace.specifications.PropertySpecification;
import kattsyn.dev.rentplace.utils.PathResolver;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Sort;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

Expand Down Expand Up @@ -69,6 +73,24 @@ public List<PropertyDTO> findAllByOwnerEmail(String email) {
return propertyMapper.fromProperties(propertyRepository.findAllByOwnerEmail(email));
}

@Override
public List<PropertyDTO> findAllByFilter(PropertyFilterDTO filter) {
return propertyMapper.fromProperties(
propertyRepository.findAll(new PropertySpecification(filter), buildSort(filter.getSortType()))
);
}

private Sort buildSort(SortType sortType) {
if (sortType == null) return Sort.unsorted();

return switch (sortType) {
case MOST_OLD -> Sort.by(Sort.Order.asc("propertyId"));
case MOST_RECENT -> Sort.by(Sort.Order.desc("propertyId"));
case MOST_EXPENSIVE -> Sort.by(Sort.Order.desc("cost"));
case MOST_CHEAP -> Sort.by(Sort.Order.asc("cost"));
};
}

@Override
@Transactional
public Property getPropertyById(long id) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
package kattsyn.dev.rentplace.specifications;

import jakarta.persistence.criteria.*;
import kattsyn.dev.rentplace.dtos.filters.PropertyFilterDTO;
import kattsyn.dev.rentplace.entities.Category;
import kattsyn.dev.rentplace.entities.Facility;
import kattsyn.dev.rentplace.entities.Property;
import lombok.AllArgsConstructor;
import org.springframework.data.jpa.domain.Specification;

@AllArgsConstructor
public class PropertySpecification implements Specification<Property> {

private final PropertyFilterDTO filter;

@Override
public Predicate toPredicate(Root<Property> root, CriteriaQuery<?> query, CriteriaBuilder criteriaBuilder) {

Predicate predicate = criteriaBuilder.conjunction();

root.fetch("images", JoinType.LEFT);
root.fetch("facilities", JoinType.LEFT);
root.fetch("categories", JoinType.LEFT);

assert query != null;
query.distinct(true);

Join<Property, Category> categoryJoin = null;
if (filter.getCategoryIds() != null && !filter.getCategoryIds().isEmpty()) {
categoryJoin = root.join("categories", JoinType.INNER);
}

Join<Property, Facility> facilityJoin = null;
if (filter.getFacilityIds() != null && !filter.getFacilityIds().isEmpty()) {
facilityJoin = root.join("facilities", JoinType.INNER);
}

if (filter.getMinPrice() != null && filter.getMinPrice() >= 0) {
predicate = criteriaBuilder.and(predicate, criteriaBuilder.greaterThanOrEqualTo(root.get("cost"), filter.getMinPrice()));
}

if (filter.getMaxPrice() != null && filter.getMaxPrice() >= 0) {
predicate = criteriaBuilder.and(predicate, criteriaBuilder.lessThanOrEqualTo(root.get("cost"), filter.getMaxPrice()));
}

if (filter.getGuestsAmount() != null && !(filter.getGuestsAmount() == 0)) {
predicate = filter.getGuestsAmount() >= 5
? criteriaBuilder.and(predicate, criteriaBuilder.greaterThanOrEqualTo(root.get("maxGuests"), filter.getGuestsAmount()))
: criteriaBuilder.and(predicate, criteriaBuilder.equal(root.get("maxGuests"), filter.getGuestsAmount()));
}

if (filter.getBedsAmount() != null && !(filter.getBedsAmount() == 0)) {
predicate = filter.getBedsAmount() >= 5
? criteriaBuilder.and(predicate, criteriaBuilder.greaterThanOrEqualTo(root.get("sleepingPlaces"), filter.getBedsAmount()))
: criteriaBuilder.and(predicate, criteriaBuilder.equal(root.get("sleepingPlaces"), filter.getBedsAmount()));
}

if (filter.getBedrooms() != null && !(filter.getBedrooms() == 0)) {
predicate = filter.getBedrooms() >= 5
? criteriaBuilder.and(predicate, criteriaBuilder.greaterThanOrEqualTo(root.get("bedrooms"), filter.getBedrooms()))
: criteriaBuilder.and(predicate, criteriaBuilder.equal(root.get("bedrooms"), filter.getBedrooms()));
}

if (filter.getRooms() != null && !(filter.getRooms() == 0)) {
predicate = filter.getRooms() >= 5
? criteriaBuilder.and(predicate, criteriaBuilder.greaterThanOrEqualTo(root.get("rooms"), filter.getRooms()))
: criteriaBuilder.and(predicate, criteriaBuilder.equal(root.get("rooms"), filter.getRooms()));
}

if (filter.getIsLongTermRent() != null) {
predicate = criteriaBuilder.and(predicate, criteriaBuilder.equal(root.get("isLongTermRent"), filter.getIsLongTermRent()));
}

if (filter.getCategoryIds() != null && !filter.getCategoryIds().isEmpty() && categoryJoin != null) {
predicate = criteriaBuilder.and(
predicate,
categoryJoin.get("categoryId").in(filter.getCategoryIds())
);
}

// Фильтр по удобствам
if (filter.getFacilityIds() != null && !filter.getFacilityIds().isEmpty() && facilityJoin != null) {
predicate = criteriaBuilder.and(
predicate,
facilityJoin.get("facilityId").in(filter.getFacilityIds())
);
}

return predicate;
}
}
Loading