@@ -3,6 +3,7 @@ import { Category } from '../entities';
3
3
import type { BaseItems } from '../interfaces/BaseItems' ;
4
4
import type { PaginatedOptions } from '../interfaces/PaginationOptions' ;
5
5
import { paginate } from '../utils/paginate' ;
6
+ import type { CategoryFilters } from '../interfaces/CategoryFilters' ;
6
7
7
8
export class CategoryGateway {
8
9
private repository : EntityRepository < Category > ;
@@ -11,36 +12,66 @@ export class CategoryGateway {
11
12
this . repository = orm . em . getRepository ( Category ) ;
12
13
}
13
14
14
- public async findAllRoot ( options : PaginatedOptions ) : Promise < BaseItems < Category > > {
15
+ public async findAll ( filters : CategoryFilters , options : PaginatedOptions ) : Promise < BaseItems < Category > > {
15
16
const pagination = paginate ( options ) ;
16
- const [ categories , totalResults ] = await Promise . all ( [
17
- this . repository
18
- . createQueryBuilder ( 'category' )
19
- . where ( 'category.parent_id IS NULL' )
20
- . leftJoinAndSelect ( 'category.image' , 'image' )
21
- . limit ( pagination . limit )
22
- . offset ( pagination . offset )
23
- . getResult ( ) ,
24
- this . repository . createQueryBuilder ( 'category' ) . where ( 'category.parent_id IS NULL' ) . count ( )
25
- ] ) ;
26
- return {
27
- items : categories ,
28
- totalItems : totalResults ,
29
- totalPages : Math . ceil ( totalResults / pagination . limit ) ,
30
- page : Math . ceil ( pagination . offset / pagination . limit ) + 1 ,
31
- pageSize : categories . length
32
- } ;
33
- }
34
17
35
- public async findAllChildrenOfCategory ( categoryId : number , options : PaginatedOptions ) : Promise < BaseItems < Category > > {
36
- const pagination = paginate ( options ) ;
37
- const [ categories , totalResults ] = await Promise . all ( [
38
- this . repository . find (
39
- { parent : categoryId } ,
40
- { fields : [ 'name' , 'parent.name' , 'image' ] , limit : pagination . limit , offset : pagination . offset }
41
- ) ,
42
- this . repository . count ( { parent : categoryId } )
43
- ] ) ;
18
+ const parentFilterValues = [ ] ;
19
+ if ( filters . parentId ) parentFilterValues . push ( filters . parentId ) ;
20
+ const parentWhere = `category.parent_id ${ parentFilterValues . length ? '= ?' : 'IS NULL' } ` ;
21
+ const qb = this . repository . createQueryBuilder ( 'category' ) . where ( parentWhere , parentFilterValues ) ;
22
+
23
+ if ( filters . productMaxPrice || filters . productMinPrice || filters . productSearch ) {
24
+ // Number() to prevent SQL injection
25
+ const signal = [ '>' , '<' ] ; // used for mapping
26
+ const priceQuery = [ Number ( filters . productMinPrice ) , Number ( filters . productMaxPrice ) ]
27
+ . filter ( ( v ) => ! Number . isNaN ( v ) )
28
+ . map ( ( v , idx ) => `producer_product.current_price ${ signal [ idx ] } = ${ v } ` )
29
+ . join ( ' AND ' ) ;
30
+
31
+ const productSearchFilterValues = [ ] ;
32
+ if ( filters . productSearch ) productSearchFilterValues . push ( `%${ filters . productSearch } %` ) ;
33
+
34
+ // We need to check if the current level (children, filhos) meets the criteria
35
+ // or if any of the children's children (netos) meet the criteria.
36
+ // We need to show every leaf of the tree, even if the actual leaf does not meet the criteria, for navigation:
37
+ // we need the *parents to show a child that meets it.
38
+ void qb . andWhere (
39
+ // We check if the current category meets the criteria OR has a child that meets the criteria
40
+ `category.id IN (SELECT product_spec_category.category_id
41
+ FROM product_spec_category,
42
+ producer_product,
43
+ product_spec
44
+ WHERE product_spec_category.product_spec_id = product_spec.id
45
+ AND producer_product.product_spec_id = product_spec.id
46
+ AND (${ priceQuery . length ? `(${ priceQuery } )` : '' }
47
+ ${ productSearchFilterValues . length ? `${ priceQuery . length ? 'OR' : '' } product_spec.name LIKE ?` : '' }
48
+ OR category.id IN (select category_inner.parent_id
49
+ from (select *
50
+ from category category_inner2
51
+ order by parent_id, id) category_inner,
52
+ (select @pv := category.id) initialisation
53
+ where find_in_set(parent_id, @pv) > 0
54
+ and @pv :=
55
+ concat(@pv, ',', id)
56
+ AND category_inner.id IN
57
+ (SELECT product_spec_category_inner.category_id
58
+ FROM product_spec_category product_spec_category_inner,
59
+ producer_product producer_product_inner,
60
+ product_spec product_spec_inner
61
+ WHERE product_spec_category_inner.product_spec_id = product_spec.id
62
+ AND producer_product_inner.product_spec_id = product_spec.id
63
+ AND (${ priceQuery . length ? `(${ priceQuery } )` : '' }
64
+ ${ productSearchFilterValues . length ? `${ priceQuery . length ? 'OR' : '' } product_spec.name LIKE ?` : '' } )))))` ,
65
+ // Needs to be duplicated because we use it in two places (children and grandchildren)
66
+ [ ...productSearchFilterValues , ...productSearchFilterValues ]
67
+ ) ;
68
+ }
69
+
70
+ const totalResultsQb = qb . clone ( ) ;
71
+
72
+ void qb . leftJoinAndSelect ( 'category.image' , 'image' ) . limit ( pagination . limit ) . offset ( pagination . offset ) ;
73
+
74
+ const [ categories , totalResults ] = await Promise . all ( [ qb . getResult ( ) , totalResultsQb . count ( ) ] ) ;
44
75
return {
45
76
items : categories ,
46
77
totalItems : totalResults ,
0 commit comments