Skip to content

Commit

Permalink
T-12 Add product filtering ability
Browse files Browse the repository at this point in the history
  • Loading branch information
mayer1a committed Sep 7, 2024
1 parent 18e312a commit a80a9c9
Show file tree
Hide file tree
Showing 2 changed files with 86 additions and 21 deletions.
5 changes: 3 additions & 2 deletions Sources/App/Controllers/Products/ProductsController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,15 @@ struct ProductsController: RouteCollection {

@Sendable func getProducts(_ request: Request) async throws -> PaginationResponse<ProductDTO> {
let page = try request.query.decode(PageRequest.self)
let filters = try? request.query.decode(FilterQueryRequest.self)

if let categoryID: UUID = request.query[categoryQuery] {

return try await productsRepository.getProducts(categoryID: categoryID, with: page)
return try await productsRepository.getProducts(categoryID: categoryID, with: page, filters: filters)

} else if let saleID: UUID = request.query[saleQuery] {

return try await productsRepository.getProducts(saleID: saleID, with: page)
return try await productsRepository.getProducts(saleID: saleID, with: page, filters: filters)
}

throw ErrorFactory.badRequest(.categoryIDRequired)
Expand Down
102 changes: 83 additions & 19 deletions Sources/App/Controllers/Products/ProductsRepository.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,18 @@

import Vapor
import Fluent
import FluentSQL

protocol ProductsRepositoryProtocol: Sendable {

func getProducts(categoryID: UUID, with page: PageRequest) async throws -> PaginationResponse<ProductDTO>
func getProducts(saleID: UUID, with page: PageRequest) async throws -> PaginationResponse<ProductDTO>
func getProducts(categoryID: UUID,
with page: PageRequest,
filters: FilterQueryRequest?) async throws -> PaginationResponse<ProductDTO>

func getProducts(saleID: UUID,
with page: PageRequest,
filters: FilterQueryRequest?) async throws -> PaginationResponse<ProductDTO>

func get(for productID: UUID) async throws -> Product
func getDTO(for productID: UUID) async throws -> ProductDTO
func getFilters(for categoryID: UUID) async throws -> [FilterDTO]
Expand All @@ -26,15 +33,21 @@ final class ProductsRepository: ProductsRepositoryProtocol {
self.database = database
}

func getProducts(categoryID: UUID, with page: PageRequest) async throws -> PaginationResponse<ProductDTO> {
let paginationPoducts = try await eagerLoadRelations(categoryID: categoryID, page)
func getProducts(categoryID: UUID,
with page: PageRequest,
filters: FilterQueryRequest?) async throws -> PaginationResponse<ProductDTO> {

let paginationPoducts = try await eagerLoadRelations(categoryID: categoryID, page, filters: filters)
let productsDTOs = try DTOFactory.makeProducts(from: paginationPoducts.results) ?? []

return PaginationResponse(results: productsDTOs, paginationInfo: paginationPoducts.paginationInfo)
}

func getProducts(saleID: UUID, with page: PageRequest) async throws -> PaginationResponse<ProductDTO> {
let paginationPoducts = try await eagerLoadRelations(saleID: saleID, page)
func getProducts(saleID: UUID,
with page: PageRequest,
filters: FilterQueryRequest?) async throws -> PaginationResponse<ProductDTO> {

let paginationPoducts = try await eagerLoadRelations(saleID: saleID, page, filters: filters)
let productsDTOs = try DTOFactory.makeProducts(from: paginationPoducts.results) ?? []

return PaginationResponse(results: productsDTOs, paginationInfo: paginationPoducts.paginationInfo)
Expand Down Expand Up @@ -83,48 +96,57 @@ final class ProductsRepository: ProductsRepositoryProtocol {
}

private func eagerLoadRelations(categoryID: UUID,
_ page: PageRequest) async throws -> PaginationResponse<Product> {
_ page: PageRequest,
filters: FilterQueryRequest?) async throws -> PaginationResponse<Product> {

guard
let category = try await Category.find(categoryID, on: database),
let categoriesIDs = try? await getCategoriesIDs(for: category),
let paginationPoducts = try await eagerLoadProducts(saleID: nil, categoriesIDs: categoriesIDs, with: page)
let products = try await eagerLoadProducts(saleID: nil,
categoriesIDs: categoriesIDs,
with: page,
filters: filters)
else {
throw ErrorFactory.internalError(.fetchProductsForCategoryError, failures: [.ID(categoryID)])
}

return paginationPoducts
return products
}

private func eagerLoadRelations(saleID: UUID, _ page: PageRequest) async throws -> PaginationResponse<Product> {
private func eagerLoadRelations(saleID: UUID,
_ page: PageRequest,
filters: FilterQueryRequest?) async throws -> PaginationResponse<Product> {
guard
let saleID = try await Sale.find(saleID, on: database)?.requireID(),
let paginationPoducts = try await eagerLoadProducts(saleID: saleID, categoriesIDs: nil, with: page)
let products = try await eagerLoadProducts(saleID: saleID,
categoriesIDs: nil,
with: page,
filters: filters)
else {
throw ErrorFactory.internalError(.fetchProductsForSaleError, failures: [.ID(saleID)])
}

return paginationPoducts
return products
}

private func eagerLoadProducts(saleID: UUID?,
categoriesIDs: [UUID]?,
with page: PageRequest) async throws -> PaginationResponse<Product>? {

with page: PageRequest,
filters: FilterQueryRequest?) async throws -> PaginationResponse<Product>? {

var productsQuery = Product.query(on: database)
let productsQuery = Product.query(on: database)

if let saleID {
productsQuery = productsQuery.filter(\.$sale.$id == saleID)
productsQuery.filter(\.$sale.$id == saleID)
} else if let categoriesIDs {
productsQuery = productsQuery
productsQuery
.join(siblings: \.$categories)
.filter(Category.self, \.$id ~~ categoriesIDs)
} else {
return nil
}

productsQuery = productsQuery
addFilters(filters, to: productsQuery)
.with(\.$images)
.with(\.$variants) { variant in
variant
Expand All @@ -136,6 +158,48 @@ final class ProductsRepository: ProductsRepositoryProtocol {
return try await productsQuery.paginate(with: page)
}

private func addFilters(_ filters: FilterQueryRequest?, to query: QueryBuilder<Product>) -> QueryBuilder<Product> {
query
.join(ProductVariant.self, on: \Product.$id == \ProductVariant.$product.$id, method: .left)
.join(ProductVariantsPropertyValues.self,
on: \ProductVariant.$id == \ProductVariantsPropertyValues.$productVariant.$id,
method: .left)
.join(PropertyValue.self,
on: \ProductVariantsPropertyValues.$propertyValue.$id == \PropertyValue.$id,
method: .left)
.join(ProductProperty.self, on: \PropertyValue.$productProperty.$id == \ProductProperty.$id, method: .left)

filters?.filters?.forEach { key, values in
query
.filter(ProductProperty.self, \.$code == key)
.filter(PropertyValue.self, \.$value ~~ values)
}

query.unique()

switch filters?.sort {
case "price": query
.join(Price.self, on: \ProductVariant.$id == \Price.$productVariant.$id)
.sort(Price.self, \.$price)

case "-price": query
.join(Price.self, on: \ProductVariant.$id == \Price.$productVariant.$id)
.sort(Price.self, \.$price, .descending)

case "name":
query.sort(\Product.$name)

case "-name":
query.sort(\Product.$name, .descending)

default:
break

}

return query
}

private func getCategoriesIDs(for category: Category) async throws -> [UUID] {
guard category.hasChildren else { return [try category.requireID()] }

Expand Down

0 comments on commit a80a9c9

Please sign in to comment.