@infrab4a/connect-angular 5.0.0-beta.96 → 5.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +13 -0
- package/eslint.config.js +18 -0
- package/index.ts +1 -0
- package/karma.conf.js +32 -0
- package/ng-package.json +8 -0
- package/package.json +10 -22
- package/project.json +37 -0
- package/src/angular-connect.module.ts +256 -0
- package/src/angular-elastic-search.module.ts +23 -0
- package/src/angular-firebase-auth.module.ts +101 -0
- package/src/angular-firestore.module.ts +371 -0
- package/src/angular-hasura-graphql.module.ts +219 -0
- package/src/angular-vertex-search.module.ts +23 -0
- package/src/consts/backend-url.const.ts +1 -0
- package/src/consts/category-structure.ts +1 -0
- package/src/consts/default-shop.const.ts +1 -0
- package/src/consts/es-config.const.ts +1 -0
- package/src/consts/firebase-const.ts +5 -0
- package/src/consts/hasura-options.const.ts +1 -0
- package/src/consts/index.ts +8 -0
- package/src/consts/persistence.const.ts +1 -0
- package/src/consts/storage-base-url.const.ts +1 -0
- package/src/consts/vertex-config.const.ts +1 -0
- package/src/helpers/index.ts +1 -0
- package/src/helpers/mobile-operation-system-checker.helper.ts +10 -0
- package/src/index.ts +6 -0
- package/src/interfaces/catalog-strategies.interface.ts +36 -0
- package/src/interfaces/category-facades.interface.ts +7 -0
- package/src/interfaces/index.ts +2 -0
- package/src/persistence/cookie-data-persistence.ts +21 -0
- package/src/persistence/data-persistence.ts +7 -0
- package/src/persistence/index.ts +2 -0
- package/src/services/auth.service.ts +39 -0
- package/src/services/cart/cart-services.facade.ts +14 -0
- package/src/services/cart/index.ts +1 -0
- package/src/services/cart.service.ts +124 -0
- package/src/services/catalog/adapters/category-structure.adapter.ts +5 -0
- package/src/services/catalog/adapters/index.ts +3 -0
- package/src/services/catalog/adapters/new-category-structure.adapter.ts +42 -0
- package/src/services/catalog/adapters/old-category-structure.adapter.ts +17 -0
- package/src/services/catalog/catalog.service.ts +153 -0
- package/src/services/catalog/category.service.ts +52 -0
- package/src/services/catalog/context/catalog-search.context.ts +61 -0
- package/src/services/catalog/enums/index.ts +1 -0
- package/src/services/catalog/enums/product-sorts.enum.ts +9 -0
- package/src/services/catalog/facades/catalog-service.facade.ts +32 -0
- package/src/services/catalog/facades/catalog-strategies.facade.ts +12 -0
- package/src/services/catalog/facades/category-repository.facade.ts +10 -0
- package/src/services/catalog/facades/category-service.facade.ts +25 -0
- package/src/services/catalog/facades/index.ts +5 -0
- package/src/services/catalog/facades/product-catalog.facade.ts +13 -0
- package/src/services/catalog/helpers/brand-manager.helper.ts +63 -0
- package/src/services/catalog/helpers/catalog-filter.helper.ts +50 -0
- package/src/services/catalog/helpers/catalog-sort.helper.ts +54 -0
- package/src/services/catalog/helpers/index.ts +4 -0
- package/src/services/catalog/helpers/product-fields.helper.ts +35 -0
- package/src/services/catalog/index.ts +7 -0
- package/src/services/catalog/models/category-with-tree.model.ts +7 -0
- package/src/services/catalog/models/index.ts +1 -0
- package/src/services/catalog/services/catalog-helpers.service.ts +35 -0
- package/src/services/catalog/services/catalog-operations.facade.ts +24 -0
- package/src/services/catalog/services/catalog-repository.service.ts +20 -0
- package/src/services/catalog/services/index.ts +4 -0
- package/src/services/catalog/services/product-management.facade.ts +20 -0
- package/src/services/catalog/strategies/category-search.strategy.ts +172 -0
- package/src/services/catalog/strategies/index.ts +3 -0
- package/src/services/catalog/strategies/profile-search.strategy.ts +51 -0
- package/src/services/catalog/strategies/term-search.strategy.ts +178 -0
- package/src/services/catalog/strategies/types/strategy-params.type.ts +58 -0
- package/src/services/catalog/types/fetch-products-options.type.ts +10 -0
- package/src/services/catalog/types/fetch-products-params.type.ts +27 -0
- package/src/services/catalog/types/fetch-products-response.type.ts +11 -0
- package/src/services/catalog/types/index.ts +5 -0
- package/src/services/catalog/types/method-params.type.ts +21 -0
- package/src/services/catalog/types/product-sort.type.ts +3 -0
- package/src/services/catalog/wishlist.service.ts +368 -0
- package/src/services/checkout/checkout-dependencies.facade.ts +13 -0
- package/src/services/checkout/checkout-repositories.facade.ts +10 -0
- package/src/services/checkout/index.ts +2 -0
- package/src/services/checkout-subscription.service.ts +76 -0
- package/src/services/checkout.service.ts +196 -0
- package/src/services/coupon/coupon-repositories.facade.ts +11 -0
- package/src/services/coupon/index.ts +1 -0
- package/src/services/coupon/types/coupon-params.type.ts +15 -0
- package/src/services/coupon.service.ts +347 -0
- package/src/services/errors/group-invalid-coupon.error.ts +9 -0
- package/src/services/errors/index.ts +2 -0
- package/src/services/errors/invalid-coupon.error.ts +7 -0
- package/src/services/helpers/index.ts +1 -0
- package/src/services/helpers/util.helper.ts +17 -0
- package/src/services/home-shop/home-shop-repositories.facade.ts +11 -0
- package/src/services/home-shop/index.ts +1 -0
- package/src/services/home-shop.service.ts +196 -0
- package/src/services/index.ts +9 -0
- package/src/services/order.service.ts +23 -0
- package/src/services/shared/configuration.facade.ts +21 -0
- package/src/services/shared/index.ts +1 -0
- package/src/services/types/index.ts +2 -0
- package/src/services/types/required-checkout-data.type.ts +3 -0
- package/src/services/types/required-checkout-subscription-data.type.ts +3 -0
- package/src/types/firebase-app-config.type.ts +1 -0
- package/src/types/index.ts +1 -0
- package/tsconfig.json +17 -0
- package/tsconfig.lib.json +13 -0
- package/tsconfig.lib.prod.json +13 -0
- package/tsconfig.spec.json +17 -0
- package/angular-connect.module.d.ts +0 -32
- package/angular-elastic-search.module.d.ts +0 -9
- package/angular-firebase-auth.module.d.ts +0 -11
- package/angular-firestore.module.d.ts +0 -17
- package/angular-hasura-graphql.module.d.ts +0 -14
- package/angular-vertex-search.module.d.ts +0 -9
- package/consts/backend-url.const.d.ts +0 -1
- package/consts/category-structure.d.ts +0 -1
- package/consts/default-shop.const.d.ts +0 -1
- package/consts/es-config.const.d.ts +0 -1
- package/consts/firebase-const.d.ts +0 -4
- package/consts/hasura-options.const.d.ts +0 -1
- package/consts/index.d.ts +0 -8
- package/consts/persistence.const.d.ts +0 -1
- package/consts/storage-base-url.const.d.ts +0 -1
- package/consts/vertex-config.const.d.ts +0 -1
- package/esm2022/angular-connect.module.mjs +0 -187
- package/esm2022/angular-elastic-search.module.mjs +0 -34
- package/esm2022/angular-firebase-auth.module.mjs +0 -141
- package/esm2022/angular-firestore.module.mjs +0 -541
- package/esm2022/angular-hasura-graphql.module.mjs +0 -333
- package/esm2022/angular-vertex-search.module.mjs +0 -34
- package/esm2022/consts/backend-url.const.mjs +0 -2
- package/esm2022/consts/category-structure.mjs +0 -2
- package/esm2022/consts/default-shop.const.mjs +0 -2
- package/esm2022/consts/es-config.const.mjs +0 -2
- package/esm2022/consts/firebase-const.mjs +0 -5
- package/esm2022/consts/hasura-options.const.mjs +0 -2
- package/esm2022/consts/index.mjs +0 -9
- package/esm2022/consts/persistence.const.mjs +0 -2
- package/esm2022/consts/storage-base-url.const.mjs +0 -2
- package/esm2022/consts/vertex-config.const.mjs +0 -2
- package/esm2022/helpers/index.mjs +0 -2
- package/esm2022/helpers/mobile-operation-system-checker.helper.mjs +0 -7
- package/esm2022/index.mjs +0 -7
- package/esm2022/infrab4a-connect-angular.mjs +0 -5
- package/esm2022/persistence/cookie-data-persistence.mjs +0 -22
- package/esm2022/persistence/data-persistence.mjs +0 -2
- package/esm2022/persistence/index.mjs +0 -3
- package/esm2022/services/auth.service.mjs +0 -37
- package/esm2022/services/cart.service.mjs +0 -86
- package/esm2022/services/catalog/adapters/category-structure.adapter.mjs +0 -2
- package/esm2022/services/catalog/adapters/index.mjs +0 -4
- package/esm2022/services/catalog/adapters/new-category-structure.adapter.mjs +0 -43
- package/esm2022/services/catalog/adapters/old-category-structure.adapter.mjs +0 -23
- package/esm2022/services/catalog/catalog.service.mjs +0 -325
- package/esm2022/services/catalog/category.service.mjs +0 -51
- package/esm2022/services/catalog/enums/index.mjs +0 -2
- package/esm2022/services/catalog/enums/product-sorts.enum.mjs +0 -11
- package/esm2022/services/catalog/index.mjs +0 -8
- package/esm2022/services/catalog/models/category-with-tree.model.mjs +0 -10
- package/esm2022/services/catalog/models/index.mjs +0 -2
- package/esm2022/services/catalog/types/index.mjs +0 -2
- package/esm2022/services/catalog/types/product-sort.type.mjs +0 -2
- package/esm2022/services/catalog/wishlist.service.mjs +0 -235
- package/esm2022/services/checkout-subscription.service.mjs +0 -50
- package/esm2022/services/checkout.service.mjs +0 -122
- package/esm2022/services/coupon.service.mjs +0 -228
- package/esm2022/services/helpers/index.mjs +0 -2
- package/esm2022/services/helpers/util.helper.mjs +0 -18
- package/esm2022/services/home-shop.service.mjs +0 -125
- package/esm2022/services/index.mjs +0 -10
- package/esm2022/services/order.service.mjs +0 -30
- package/esm2022/services/types/index.mjs +0 -3
- package/esm2022/services/types/required-checkout-data.type.mjs +0 -2
- package/esm2022/services/types/required-checkout-subscription-data.type.mjs +0 -2
- package/esm2022/types/firebase-app-config.type.mjs +0 -2
- package/esm2022/types/index.mjs +0 -2
- package/fesm2022/infrab4a-connect-angular.mjs +0 -2603
- package/fesm2022/infrab4a-connect-angular.mjs.map +0 -1
- package/helpers/index.d.ts +0 -1
- package/helpers/mobile-operation-system-checker.helper.d.ts +0 -3
- package/index.d.ts +0 -6
- package/persistence/cookie-data-persistence.d.ts +0 -10
- package/persistence/data-persistence.d.ts +0 -6
- package/persistence/index.d.ts +0 -2
- package/services/auth.service.d.ts +0 -18
- package/services/cart.service.d.ts +0 -31
- package/services/catalog/adapters/category-structure.adapter.d.ts +0 -4
- package/services/catalog/adapters/index.d.ts +0 -3
- package/services/catalog/adapters/new-category-structure.adapter.d.ts +0 -12
- package/services/catalog/adapters/old-category-structure.adapter.d.ts +0 -10
- package/services/catalog/catalog.service.d.ts +0 -96
- package/services/catalog/category.service.d.ts +0 -24
- package/services/catalog/enums/index.d.ts +0 -1
- package/services/catalog/enums/product-sorts.enum.d.ts +0 -9
- package/services/catalog/index.d.ts +0 -7
- package/services/catalog/models/category-with-tree.model.d.ts +0 -4
- package/services/catalog/models/index.d.ts +0 -1
- package/services/catalog/types/index.d.ts +0 -1
- package/services/catalog/types/product-sort.type.d.ts +0 -2
- package/services/catalog/wishlist.service.d.ts +0 -50
- package/services/checkout-subscription.service.d.ts +0 -19
- package/services/checkout.service.d.ts +0 -34
- package/services/coupon.service.d.ts +0 -27
- package/services/helpers/index.d.ts +0 -1
- package/services/helpers/util.helper.d.ts +0 -3
- package/services/home-shop.service.d.ts +0 -26
- package/services/index.d.ts +0 -9
- package/services/order.service.d.ts +0 -13
- package/services/types/index.d.ts +0 -2
- package/services/types/required-checkout-data.type.d.ts +0 -2
- package/services/types/required-checkout-subscription-data.type.d.ts +0 -2
- package/types/firebase-app-config.type.d.ts +0 -1
- package/types/index.d.ts +0 -1
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Category,
|
|
3
|
+
Product,
|
|
4
|
+
ProductGender,
|
|
5
|
+
RepositoryCacheOptions,
|
|
6
|
+
RepositoryFindResult,
|
|
7
|
+
Shops,
|
|
8
|
+
} from '@infrab4a/connect'
|
|
9
|
+
|
|
10
|
+
export interface CatalogSearchParams {
|
|
11
|
+
category?: Category
|
|
12
|
+
profile?: string[]
|
|
13
|
+
term?: string
|
|
14
|
+
filters?: CatalogFilters
|
|
15
|
+
mainGender?: ProductGender
|
|
16
|
+
limits: { limit?: number; offset?: number }
|
|
17
|
+
sort?: string
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface CatalogFilters {
|
|
21
|
+
clubDiscount?: number[]
|
|
22
|
+
brands?: string[]
|
|
23
|
+
prices?: { min?: number; max?: number; subscriberPrice?: boolean }
|
|
24
|
+
gender?: ProductGender[]
|
|
25
|
+
rate?: number
|
|
26
|
+
customOptions?: string[]
|
|
27
|
+
tags?: string[]
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface CatalogSearchStrategy {
|
|
31
|
+
search(
|
|
32
|
+
params: CatalogSearchParams,
|
|
33
|
+
shop: Shops,
|
|
34
|
+
optionsCache?: { cache?: RepositoryCacheOptions },
|
|
35
|
+
): Promise<RepositoryFindResult<Product>>
|
|
36
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { CategoryRepository, ProductSearchIndex, ProductStockNotificationRepository } from '@infrab4a/connect'
|
|
2
|
+
|
|
3
|
+
export interface ProductServicesFacade {
|
|
4
|
+
productStockNotificationRepository: ProductStockNotificationRepository
|
|
5
|
+
categoryRepository: CategoryRepository
|
|
6
|
+
productSearch: ProductSearchIndex
|
|
7
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { Injectable } from '@angular/core'
|
|
2
|
+
import cookie from 'js-cookie'
|
|
3
|
+
import { Observable, from, of } from 'rxjs'
|
|
4
|
+
import { map } from 'rxjs/operators'
|
|
5
|
+
|
|
6
|
+
import { DataPersistence } from './data-persistence'
|
|
7
|
+
|
|
8
|
+
@Injectable()
|
|
9
|
+
export class CookieDataPersistence implements DataPersistence {
|
|
10
|
+
get(key: string): Observable<string> {
|
|
11
|
+
return of(cookie.get(key))
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
remove(key: string): Observable<void> {
|
|
15
|
+
return of(cookie.remove(key))
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
set(key: string, value: string): Observable<void> {
|
|
19
|
+
return from(cookie.set(key, value)).pipe(map(() => {}))
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { Inject, Injectable } from '@angular/core'
|
|
2
|
+
import { Auth, User as FirebaseUser, authState, getIdToken } from '@angular/fire/auth'
|
|
3
|
+
import { User, UserRepository } from '@infrab4a/connect'
|
|
4
|
+
import { Observable, combineLatest, from, of } from 'rxjs'
|
|
5
|
+
import { catchError, map, mergeMap } from 'rxjs/operators'
|
|
6
|
+
|
|
7
|
+
@Injectable()
|
|
8
|
+
export class AuthService {
|
|
9
|
+
constructor(
|
|
10
|
+
private readonly angularFireAuth: Auth,
|
|
11
|
+
@Inject('UserRepository') private readonly userRepository: UserRepository,
|
|
12
|
+
) {}
|
|
13
|
+
|
|
14
|
+
getAuthstate(): Observable<{ user: User; isAnonymous: boolean }> {
|
|
15
|
+
const observables: [Observable<FirebaseUser>, Observable<User>] = [this.getFireUser(), this.getUser()]
|
|
16
|
+
|
|
17
|
+
return combineLatest(observables).pipe(
|
|
18
|
+
map(([fireUser, user]) => ({
|
|
19
|
+
user,
|
|
20
|
+
isAnonymous: fireUser?.isAnonymous,
|
|
21
|
+
})),
|
|
22
|
+
)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
getUser(): Observable<User> {
|
|
26
|
+
return this.getFireUser().pipe(
|
|
27
|
+
map((user) => user?.uid),
|
|
28
|
+
mergeMap((id) => (id ? from(this.userRepository.get({ id })).pipe(catchError(() => of(null))) : of(null))),
|
|
29
|
+
)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
getTokenId(): Observable<string> {
|
|
33
|
+
return from(getIdToken(this.angularFireAuth.currentUser))
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
getFireUser(): Observable<FirebaseUser> {
|
|
37
|
+
return authState(this.angularFireAuth).pipe(catchError(() => of(null)))
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { HttpClient } from '@angular/common/http'
|
|
2
|
+
import { Injectable } from '@angular/core'
|
|
3
|
+
|
|
4
|
+
import { AuthService } from '../auth.service'
|
|
5
|
+
import { CheckoutService } from '../checkout.service'
|
|
6
|
+
|
|
7
|
+
@Injectable()
|
|
8
|
+
export class CartServicesFacade {
|
|
9
|
+
constructor(
|
|
10
|
+
public readonly authService: AuthService,
|
|
11
|
+
public readonly checkoutService: CheckoutService,
|
|
12
|
+
public readonly httpClient: HttpClient,
|
|
13
|
+
) {}
|
|
14
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { CartServicesFacade } from './cart-services.facade'
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { Injectable } from '@angular/core'
|
|
2
|
+
import { Checkout, LineItem, User } from '@infrab4a/connect'
|
|
3
|
+
import { Observable, of, Subject } from 'rxjs'
|
|
4
|
+
import { concatMap, map, tap } from 'rxjs/operators'
|
|
5
|
+
|
|
6
|
+
import { RequiredCheckoutData } from './types'
|
|
7
|
+
import { CartServicesFacade } from './cart'
|
|
8
|
+
import { ConfigurationFacade } from './shared'
|
|
9
|
+
|
|
10
|
+
export type Cart = {
|
|
11
|
+
[id: string]: LineItem
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
@Injectable()
|
|
15
|
+
export class CartService {
|
|
16
|
+
private cartSubject: Subject<Cart> = new Subject()
|
|
17
|
+
private checkoutUrl: string = null
|
|
18
|
+
|
|
19
|
+
constructor(
|
|
20
|
+
private readonly servicesFacade: CartServicesFacade,
|
|
21
|
+
private readonly configurationFacade: ConfigurationFacade,
|
|
22
|
+
) {
|
|
23
|
+
this.checkoutUrl = this.configurationFacade.getCheckoutUrl()
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
addItem(item: LineItem, quantity: number = 1): Observable<Cart> {
|
|
27
|
+
return this.servicesFacade.checkoutService.getCheckout().pipe(
|
|
28
|
+
concatMap((checkout) =>
|
|
29
|
+
this.servicesFacade.httpClient.post(`${this.checkoutUrl}/checkoutAddItemToCart`, {
|
|
30
|
+
checkoutId: checkout.id,
|
|
31
|
+
productId: item.id,
|
|
32
|
+
quantity,
|
|
33
|
+
}),
|
|
34
|
+
),
|
|
35
|
+
concatMap(() => this.servicesFacade.checkoutService.getCheckout()),
|
|
36
|
+
map((updatedCheckout) => this.generateCartObject(updatedCheckout.lineItems)),
|
|
37
|
+
tap((cart) => this.cartSubject.next(cart)),
|
|
38
|
+
)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
decreaseItem(item: LineItem): Observable<Cart> {
|
|
42
|
+
return this.servicesFacade.checkoutService.getCheckout().pipe(
|
|
43
|
+
concatMap((checkout) =>
|
|
44
|
+
this.servicesFacade.httpClient.post(`${this.checkoutUrl}/checkoutDecreaseProductFromCart`, {
|
|
45
|
+
checkoutId: checkout.id,
|
|
46
|
+
productId: item.id,
|
|
47
|
+
quantity: 1,
|
|
48
|
+
}),
|
|
49
|
+
),
|
|
50
|
+
concatMap(() => this.servicesFacade.checkoutService.getCheckout()),
|
|
51
|
+
map((updatedCheckout) => this.generateCartObject(updatedCheckout.lineItems)),
|
|
52
|
+
tap((cart) => this.cartSubject.next(cart)),
|
|
53
|
+
)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
removeItem(item: LineItem): Observable<Cart> {
|
|
57
|
+
return this.servicesFacade.checkoutService.getCheckout().pipe(
|
|
58
|
+
concatMap((checkout) =>
|
|
59
|
+
this.servicesFacade.httpClient.post(`${this.checkoutUrl}/checkoutRemoveProductFromCart`, {
|
|
60
|
+
checkoutId: checkout.id,
|
|
61
|
+
productId: item.id,
|
|
62
|
+
}),
|
|
63
|
+
),
|
|
64
|
+
concatMap(() => this.servicesFacade.checkoutService.getCheckout()),
|
|
65
|
+
map((updatedCheckout) => this.generateCartObject(updatedCheckout.lineItems)),
|
|
66
|
+
tap((cart) => this.cartSubject.next(cart)),
|
|
67
|
+
)
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
updateUserCart(user: User): Observable<Cart> {
|
|
71
|
+
return this.servicesFacade.checkoutService.getCheckout().pipe(
|
|
72
|
+
concatMap((checkout) =>
|
|
73
|
+
this.servicesFacade.checkoutService.updateCheckoutUser(Checkout.toInstance({ ...checkout.toPlain(), user })),
|
|
74
|
+
),
|
|
75
|
+
concatMap((checkout) => {
|
|
76
|
+
return this.servicesFacade.httpClient.post(`${this.checkoutUrl}/checkoutUpdateUserCart`, {
|
|
77
|
+
checkoutId: checkout.id,
|
|
78
|
+
})
|
|
79
|
+
}),
|
|
80
|
+
concatMap(() => this.servicesFacade.checkoutService.getCheckout()),
|
|
81
|
+
map((updatedCheckout) => {
|
|
82
|
+
return this.generateCartObject(updatedCheckout.lineItems)
|
|
83
|
+
}),
|
|
84
|
+
tap((cart) => this.cartSubject.next(cart)),
|
|
85
|
+
)
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
getCart(checkout?: RequiredCheckoutData): Observable<Cart> {
|
|
89
|
+
this.buildCartFromCheckout(checkout).subscribe((cart) => this.cartSubject.next(cart))
|
|
90
|
+
|
|
91
|
+
return this.cartSubject
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
clearCart(): Observable<Cart> {
|
|
95
|
+
return this.servicesFacade.checkoutService.getCheckout().pipe(
|
|
96
|
+
map((checkout) => {
|
|
97
|
+
this.servicesFacade.checkoutService.clearCheckoutFromSession()
|
|
98
|
+
|
|
99
|
+
return checkout
|
|
100
|
+
}),
|
|
101
|
+
concatMap((oldCheckout) => this.buildCartFromCheckout(oldCheckout)),
|
|
102
|
+
tap((cart) => this.cartSubject.next(cart)),
|
|
103
|
+
)
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
private buildCartFromCheckout(checkoutData?: RequiredCheckoutData): Observable<Cart> {
|
|
107
|
+
return this.servicesFacade.checkoutService.getCheckout(checkoutData).pipe(
|
|
108
|
+
map((checkout) => checkout.lineItems),
|
|
109
|
+
concatMap((lineItems) => of(this.generateCartObject(lineItems))),
|
|
110
|
+
)
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
private generateCartObject = (items: LineItem[]): Cart =>
|
|
114
|
+
items?.reduce(
|
|
115
|
+
(cart, item) => ({
|
|
116
|
+
...cart,
|
|
117
|
+
[item.id]: LineItem.toInstance({
|
|
118
|
+
...(cart[item.id] || item),
|
|
119
|
+
quantity: (cart[item.id]?.quantity || 0) + (item.quantity ? item.quantity : 1),
|
|
120
|
+
}),
|
|
121
|
+
}),
|
|
122
|
+
{} as Record<string, LineItem>,
|
|
123
|
+
) || {}
|
|
124
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { Inject, Injectable } from '@angular/core'
|
|
2
|
+
import { Category, CategoryRepository, isNil, Product, RepositoryFindFielters, Where } from '@infrab4a/connect'
|
|
3
|
+
|
|
4
|
+
import { CategoryStructureAdapter } from './category-structure.adapter'
|
|
5
|
+
|
|
6
|
+
@Injectable()
|
|
7
|
+
export class NewCategoryStructureAdapter implements CategoryStructureAdapter {
|
|
8
|
+
constructor(@Inject('CategoryRepository') private readonly categoryRepository: CategoryRepository) {}
|
|
9
|
+
|
|
10
|
+
async buildProductFilterByCategory(category: Category): Promise<RepositoryFindFielters<Product>> {
|
|
11
|
+
const loadedCategory = await this.getCategory(category)
|
|
12
|
+
|
|
13
|
+
if (loadedCategory.isCollection) return { id: { operator: Where.IN, value: loadedCategory.products } }
|
|
14
|
+
|
|
15
|
+
const categoryIds = [...(await this.getAllCategoriesIdFromCategory(category)), category.id.toString()]
|
|
16
|
+
|
|
17
|
+
return {
|
|
18
|
+
category: {
|
|
19
|
+
id: {
|
|
20
|
+
operator: Where.IN,
|
|
21
|
+
value: categoryIds,
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
private async getAllCategoriesIdFromCategory(category: Category): Promise<string[]> {
|
|
28
|
+
return this.categoryRepository
|
|
29
|
+
.getChildren(+category.id)
|
|
30
|
+
.then((categories) => categories.map((category) => category.id.toString()))
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
private async getCategory(category: Category): Promise<Category> {
|
|
34
|
+
const collectionCategory =
|
|
35
|
+
category.isCollection ||
|
|
36
|
+
(isNil(category.isCollection) && !category.products?.length) ||
|
|
37
|
+
category.isWishlist ||
|
|
38
|
+
category.brandCategory
|
|
39
|
+
|
|
40
|
+
return collectionCategory ? this.categoryRepository.get({ id: category.id }) : category
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { Inject, Injectable } from '@angular/core'
|
|
2
|
+
import { Category, CategoryRepository, Product, RepositoryFindFielters, Where } from '@infrab4a/connect'
|
|
3
|
+
|
|
4
|
+
import { CategoryStructureAdapter } from './category-structure.adapter'
|
|
5
|
+
|
|
6
|
+
@Injectable()
|
|
7
|
+
export class OldCategoryStructureAdapter implements CategoryStructureAdapter {
|
|
8
|
+
constructor(@Inject('CategoryRepository') private readonly categoryRepository: CategoryRepository) {}
|
|
9
|
+
|
|
10
|
+
async buildProductFilterByCategory(category: Category): Promise<RepositoryFindFielters<Product>> {
|
|
11
|
+
const productsIds = category.products?.length
|
|
12
|
+
? category.products
|
|
13
|
+
: await this.categoryRepository.get({ id: category.id }).then((categoryFound) => categoryFound.products)
|
|
14
|
+
|
|
15
|
+
return { id: { operator: Where.IN, value: productsIds } }
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import { Inject, Injectable } from '@angular/core'
|
|
2
|
+
import {
|
|
3
|
+
Category,
|
|
4
|
+
InvalidArgumentError,
|
|
5
|
+
RepositoryCacheOptions,
|
|
6
|
+
RoundProductPricesHelper,
|
|
7
|
+
Shops,
|
|
8
|
+
} from '@infrab4a/connect'
|
|
9
|
+
|
|
10
|
+
import { DEFAULT_SHOP } from '../../consts'
|
|
11
|
+
import { CatalogFilters, CatalogSearchParams } from '../../interfaces'
|
|
12
|
+
|
|
13
|
+
import { CatalogSearchContext } from './context/catalog-search.context'
|
|
14
|
+
import { CatalogHelpersService } from './services'
|
|
15
|
+
import {
|
|
16
|
+
BuildResponseParams,
|
|
17
|
+
FetchProductsMainParams,
|
|
18
|
+
FetchProductsOptions,
|
|
19
|
+
FetchProductsParams,
|
|
20
|
+
FetchProductsResponse,
|
|
21
|
+
StockNotificationParams,
|
|
22
|
+
} from './types'
|
|
23
|
+
|
|
24
|
+
@Injectable()
|
|
25
|
+
export class CatalogService<T extends Category = Category> {
|
|
26
|
+
constructor(
|
|
27
|
+
@Inject('CatalogSearchContext') private readonly searchContext: CatalogSearchContext,
|
|
28
|
+
@Inject('CatalogHelpersService') private readonly helpersService: CatalogHelpersService,
|
|
29
|
+
@Inject(DEFAULT_SHOP) private readonly shop: Shops,
|
|
30
|
+
) {}
|
|
31
|
+
|
|
32
|
+
async fetchProducts(
|
|
33
|
+
options: FetchProductsParams<T>,
|
|
34
|
+
optionsCache?: { cache?: RepositoryCacheOptions },
|
|
35
|
+
): Promise<FetchProductsResponse> {
|
|
36
|
+
this.validateRequest(options)
|
|
37
|
+
|
|
38
|
+
const searchParams = this.buildSearchParams(options)
|
|
39
|
+
const {
|
|
40
|
+
data,
|
|
41
|
+
count: total,
|
|
42
|
+
maximum,
|
|
43
|
+
minimal,
|
|
44
|
+
distinct,
|
|
45
|
+
} = await this.searchContext.executeSearch(searchParams, this.shop, optionsCache)
|
|
46
|
+
|
|
47
|
+
await this.helpersService.getBrandManager().setBrandsList(searchParams, distinct?.brand)
|
|
48
|
+
|
|
49
|
+
return this.buildResponse({
|
|
50
|
+
data,
|
|
51
|
+
total,
|
|
52
|
+
maximum,
|
|
53
|
+
minimal,
|
|
54
|
+
searchParams,
|
|
55
|
+
options,
|
|
56
|
+
})
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
async addCustomerToStockNotification(params: StockNotificationParams): Promise<void> {
|
|
60
|
+
const { shop, productId, name, email } = params
|
|
61
|
+
const productServices = this.helpersService.getProductServices()
|
|
62
|
+
|
|
63
|
+
return productServices.productStockNotificationRepository.addCustomerEmail({
|
|
64
|
+
shop,
|
|
65
|
+
productId,
|
|
66
|
+
name,
|
|
67
|
+
email,
|
|
68
|
+
})
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
private validateRequest(options: FetchProductsParams<T>): void {
|
|
72
|
+
if (this.hasProfile(options) && options.filters?.customOptions) {
|
|
73
|
+
throw new InvalidArgumentError(`It couldn't filled customOptions when profile is given`)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (this.hasProfile(options) && options.filters?.tags) {
|
|
77
|
+
throw new InvalidArgumentError(`It couldn't filled tags when profile is given`)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (this.hasTerm(options) && options.filters?.customOptions) {
|
|
81
|
+
throw new InvalidArgumentError(`It couldn't filled customOptions when term is given`)
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
private buildSearchParams(options: FetchProductsParams<T>): CatalogSearchParams {
|
|
86
|
+
const limits = this.buildLimitQuery(options)
|
|
87
|
+
|
|
88
|
+
return {
|
|
89
|
+
category: this.hasCategory(options) ? options.category : undefined,
|
|
90
|
+
profile: this.hasProfile(options) ? options.profile : undefined,
|
|
91
|
+
term: this.hasTerm(options) ? options.term : undefined,
|
|
92
|
+
filters: options.filters,
|
|
93
|
+
mainGender: options.mainGender,
|
|
94
|
+
sort: options.sort,
|
|
95
|
+
limits,
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
private buildResponse(params: BuildResponseParams<T>): FetchProductsResponse {
|
|
100
|
+
const { data, total, maximum, minimal, searchParams } = params
|
|
101
|
+
const limits = searchParams.limits
|
|
102
|
+
const indexKey = this.helpersService.getBrandManager().buildIndexBrands(searchParams)
|
|
103
|
+
|
|
104
|
+
return {
|
|
105
|
+
products: {
|
|
106
|
+
data: data.map((product) => RoundProductPricesHelper.roundProductPrices(product)),
|
|
107
|
+
total,
|
|
108
|
+
},
|
|
109
|
+
pages: Math.ceil(total / limits.limit),
|
|
110
|
+
prices: {
|
|
111
|
+
price: {
|
|
112
|
+
min: +(minimal?.price?.price?.toFixed(2) || 0),
|
|
113
|
+
max: +(maximum?.price?.price?.toFixed(2) || 0),
|
|
114
|
+
},
|
|
115
|
+
subscriberPrice: {
|
|
116
|
+
min: +(minimal?.price?.subscriberPrice?.toFixed(2) || 0),
|
|
117
|
+
max: +(maximum?.price?.subscriberPrice?.toFixed(2) || 0),
|
|
118
|
+
},
|
|
119
|
+
},
|
|
120
|
+
brands: this.helpersService.getBrandManager().getBrandsList(indexKey),
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
private buildLimitQuery(options: { page?: number; perPage?: number }): { limit: number; offset: number } {
|
|
125
|
+
const limit = options?.perPage || 20
|
|
126
|
+
|
|
127
|
+
return {
|
|
128
|
+
limit,
|
|
129
|
+
offset: ((options?.page || 1) - 1) * limit,
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
private hasProfile(options: Partial<FetchProductsParams<T>>): options is FetchProductsOptions & {
|
|
134
|
+
profile: FetchProductsMainParams<T>['profile']
|
|
135
|
+
filters?: CatalogFilters & { customOptions?: never; tags?: never }
|
|
136
|
+
} {
|
|
137
|
+
return 'profile' in options
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
private hasTerm(options: Partial<FetchProductsParams<T>>): options is FetchProductsOptions & {
|
|
141
|
+
term: FetchProductsMainParams<T>['term']
|
|
142
|
+
filters?: CatalogFilters & { customOptions?: never }
|
|
143
|
+
} {
|
|
144
|
+
return 'term' in options
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
private hasCategory(options: Partial<FetchProductsParams<T>>): options is FetchProductsOptions & {
|
|
148
|
+
category: FetchProductsMainParams<T>['category']
|
|
149
|
+
filters?: CatalogFilters & { customOptions?: never }
|
|
150
|
+
} {
|
|
151
|
+
return 'category' in options
|
|
152
|
+
}
|
|
153
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { Inject, Injectable } from '@angular/core'
|
|
2
|
+
import { Category, Filter, ProductGender, RepositoryCacheOptions, Shops } from '@infrab4a/connect'
|
|
3
|
+
|
|
4
|
+
import { DEFAULT_SHOP } from '../../consts'
|
|
5
|
+
|
|
6
|
+
import { CategoryRepositoryFacade, ProductCatalogFacade } from './facades'
|
|
7
|
+
|
|
8
|
+
type FetchBrandsOptions = {
|
|
9
|
+
mainGender?: ProductGender
|
|
10
|
+
shop?: Shops
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
@Injectable()
|
|
14
|
+
export class CategoryService {
|
|
15
|
+
constructor(
|
|
16
|
+
@Inject('CategoryRepositoryFacade') private readonly categoryFacade: CategoryRepositoryFacade,
|
|
17
|
+
@Inject('ProductCatalogFacade') private readonly productCatalog: ProductCatalogFacade,
|
|
18
|
+
@Inject(DEFAULT_SHOP) private readonly shop: Shops,
|
|
19
|
+
) {}
|
|
20
|
+
|
|
21
|
+
async fetchBrands(
|
|
22
|
+
category: Category,
|
|
23
|
+
options?: FetchBrandsOptions,
|
|
24
|
+
optionsCache?: { cache?: RepositoryCacheOptions },
|
|
25
|
+
): Promise<Category[]> {
|
|
26
|
+
const brands = await this.productCatalog.productRepository
|
|
27
|
+
.findCatalog(
|
|
28
|
+
{
|
|
29
|
+
filters: await this.productCatalog.categoryStructureAdapter.buildProductFilterByCategory(category),
|
|
30
|
+
fields: ['brand'],
|
|
31
|
+
},
|
|
32
|
+
options?.mainGender ? options?.mainGender : this.shop === Shops.MENSMARKET ? 'male' : 'female',
|
|
33
|
+
optionsCache,
|
|
34
|
+
)
|
|
35
|
+
.then(({ data }) =>
|
|
36
|
+
Object.keys(data.map((product) => product.brand).reduce((brands, brand) => ({ ...brands, [brand]: true }), {})),
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
return this.categoryFacade.categoryRepository
|
|
40
|
+
.find(
|
|
41
|
+
{ filters: { brandCategory: true, shop: options?.shop || this.shop }, orderBy: { name: 'asc' } },
|
|
42
|
+
optionsCache,
|
|
43
|
+
)
|
|
44
|
+
.then(({ data }) => data.filter((category) => brands.includes(category.conditions.brand)))
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async fetchFilterOptions(category: Category, optionsCache?: { cache?: RepositoryCacheOptions }): Promise<Filter[]> {
|
|
48
|
+
return await this.categoryFacade.categoryFilterRepository
|
|
49
|
+
.find({ filters: { categoryId: +category.id } }, optionsCache)
|
|
50
|
+
.then(({ data }) => data.map((categoryFilter) => categoryFilter.filter))
|
|
51
|
+
}
|
|
52
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { Injectable } from '@angular/core'
|
|
2
|
+
import { Product, RepositoryCacheOptions, RepositoryFindResult, Shops } from '@infrab4a/connect'
|
|
3
|
+
|
|
4
|
+
import { CatalogSearchParams, CatalogSearchStrategy } from '../../../interfaces'
|
|
5
|
+
|
|
6
|
+
@Injectable()
|
|
7
|
+
export class CatalogSearchContext {
|
|
8
|
+
private strategies = new Map<string, CatalogSearchStrategy>()
|
|
9
|
+
|
|
10
|
+
constructor(
|
|
11
|
+
private readonly categoryStrategy: CatalogSearchStrategy,
|
|
12
|
+
private readonly termStrategy: CatalogSearchStrategy,
|
|
13
|
+
private readonly profileStrategy: CatalogSearchStrategy,
|
|
14
|
+
) {
|
|
15
|
+
this.initializeStrategies()
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
async executeSearch(
|
|
19
|
+
params: CatalogSearchParams,
|
|
20
|
+
shop: Shops,
|
|
21
|
+
optionsCache?: { cache?: RepositoryCacheOptions },
|
|
22
|
+
): Promise<RepositoryFindResult<Product>> {
|
|
23
|
+
const strategy = this.selectStrategy(params)
|
|
24
|
+
|
|
25
|
+
return strategy.search(params, shop, optionsCache)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
private initializeStrategies(): void {
|
|
29
|
+
this.strategies.set('category', this.categoryStrategy)
|
|
30
|
+
this.strategies.set('term', this.termStrategy)
|
|
31
|
+
this.strategies.set('profile', this.profileStrategy)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
private selectStrategy(params: CatalogSearchParams): CatalogSearchStrategy {
|
|
35
|
+
if (params.category) {
|
|
36
|
+
const strategy = this.strategies.get('category')
|
|
37
|
+
|
|
38
|
+
if (!strategy) throw new Error('Category strategy not found')
|
|
39
|
+
|
|
40
|
+
return strategy
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (params.term) {
|
|
44
|
+
const strategy = this.strategies.get('term')
|
|
45
|
+
|
|
46
|
+
if (!strategy) throw new Error('Term strategy not found')
|
|
47
|
+
|
|
48
|
+
return strategy
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (params.profile?.length) {
|
|
52
|
+
const strategy = this.strategies.get('profile')
|
|
53
|
+
|
|
54
|
+
if (!strategy) throw new Error('Profile strategy not found')
|
|
55
|
+
|
|
56
|
+
return strategy
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
throw new Error('No valid search strategy found for the given parameters')
|
|
60
|
+
}
|
|
61
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './product-sorts.enum'
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { Inject, Injectable } from '@angular/core'
|
|
2
|
+
import { Shops } from '@infrab4a/connect'
|
|
3
|
+
|
|
4
|
+
import { DEFAULT_SHOP } from '../../../consts'
|
|
5
|
+
import { CatalogService } from '../catalog.service'
|
|
6
|
+
import { CatalogSearchContext } from '../context/catalog-search.context'
|
|
7
|
+
import { CatalogHelpersService } from '../services'
|
|
8
|
+
|
|
9
|
+
import { CatalogStrategiesFacade } from './catalog-strategies.facade'
|
|
10
|
+
|
|
11
|
+
@Injectable()
|
|
12
|
+
export class CatalogServiceFacade {
|
|
13
|
+
private catalogService: CatalogService
|
|
14
|
+
|
|
15
|
+
constructor(
|
|
16
|
+
private readonly helpersService: CatalogHelpersService,
|
|
17
|
+
private readonly strategiesFacade: CatalogStrategiesFacade,
|
|
18
|
+
@Inject(DEFAULT_SHOP) private readonly shop: Shops,
|
|
19
|
+
) {
|
|
20
|
+
const searchContext = new CatalogSearchContext(
|
|
21
|
+
this.strategiesFacade.categoryStrategy,
|
|
22
|
+
this.strategiesFacade.termStrategy,
|
|
23
|
+
this.strategiesFacade.profileStrategy,
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
this.catalogService = new CatalogService(searchContext, this.helpersService, this.shop)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
getCatalogService(): CatalogService {
|
|
30
|
+
return this.catalogService
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { Injectable } from '@angular/core'
|
|
2
|
+
|
|
3
|
+
import { CategorySearchStrategy, ProfileSearchStrategy, TermSearchStrategy } from '../strategies'
|
|
4
|
+
|
|
5
|
+
@Injectable()
|
|
6
|
+
export class CatalogStrategiesFacade {
|
|
7
|
+
constructor(
|
|
8
|
+
public readonly categoryStrategy: CategorySearchStrategy,
|
|
9
|
+
public readonly termStrategy: TermSearchStrategy,
|
|
10
|
+
public readonly profileStrategy: ProfileSearchStrategy,
|
|
11
|
+
) {}
|
|
12
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { Inject, Injectable } from '@angular/core'
|
|
2
|
+
import { CategoryFilterRepository, CategoryRepository } from '@infrab4a/connect'
|
|
3
|
+
|
|
4
|
+
@Injectable()
|
|
5
|
+
export class CategoryRepositoryFacade {
|
|
6
|
+
constructor(
|
|
7
|
+
@Inject('CategoryRepository') public readonly categoryRepository: CategoryRepository,
|
|
8
|
+
@Inject('CategoryFilterRepository') public readonly categoryFilterRepository: CategoryFilterRepository,
|
|
9
|
+
) {}
|
|
10
|
+
}
|