Add product data domain layer
This commit is contained in:
37
src/app/core/products/models/product-domain.model.ts
Normal file
37
src/app/core/products/models/product-domain.model.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
import { Category, Item } from '../../../models';
|
||||
|
||||
export type Product = Item;
|
||||
export type ProductCategory = Category;
|
||||
export type ProductSort = 'relevance' | 'price_asc' | 'price_desc' | 'popular' | 'rating' | 'latest';
|
||||
|
||||
export interface ProductFilters {
|
||||
categoryIDs?: number[];
|
||||
minPrice?: number;
|
||||
maxPrice?: number;
|
||||
tag?: string;
|
||||
badges?: string[];
|
||||
inStockOnly?: boolean;
|
||||
}
|
||||
|
||||
export interface ProductListQuery extends ProductFilters {
|
||||
count?: number;
|
||||
skip?: number;
|
||||
sort?: ProductSort;
|
||||
}
|
||||
|
||||
export interface ProductSearchQuery extends ProductListQuery {
|
||||
search: string;
|
||||
}
|
||||
|
||||
export interface ProductListResult {
|
||||
items: Product[];
|
||||
total: number;
|
||||
count: number;
|
||||
skip: number;
|
||||
}
|
||||
|
||||
export interface RelatedProductsQuery {
|
||||
productID: number;
|
||||
categoryID?: number;
|
||||
count?: number;
|
||||
}
|
||||
20
src/app/core/products/product-data-provider.token.ts
Normal file
20
src/app/core/products/product-data-provider.token.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import { InjectionToken, inject } from '@angular/core';
|
||||
import { RuntimeProviderStrategyService } from '../providers/runtime-provider-strategy.service';
|
||||
import { ApiProductDataProvider } from './providers/api-product-data.provider';
|
||||
import { ProductDataProvider } from './providers/product-data-provider.interface';
|
||||
|
||||
export const PRODUCT_DATA_PROVIDER = new InjectionToken<ProductDataProvider>('PRODUCT_DATA_PROVIDER', {
|
||||
providedIn: 'root',
|
||||
factory: () => {
|
||||
const strategy = inject(RuntimeProviderStrategyService);
|
||||
const apiProvider = inject(ApiProductDataProvider);
|
||||
|
||||
switch (strategy.getProductProviderMode()) {
|
||||
case 'mock':
|
||||
case 'remote-config':
|
||||
case 'api':
|
||||
default:
|
||||
return apiProvider;
|
||||
}
|
||||
}
|
||||
});
|
||||
41
src/app/core/products/product-data.service.ts
Normal file
41
src/app/core/products/product-data.service.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
import { Injectable, inject } from '@angular/core';
|
||||
import { Observable } from 'rxjs';
|
||||
import { PRODUCT_DATA_PROVIDER } from './product-data-provider.token';
|
||||
import { Product, ProductCategory, ProductListQuery, ProductListResult, ProductSearchQuery, RelatedProductsQuery } from './models/product-domain.model';
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class ProductDataService {
|
||||
private readonly provider = inject(PRODUCT_DATA_PROVIDER);
|
||||
|
||||
getProducts(query?: ProductListQuery): Observable<ProductListResult> {
|
||||
return this.provider.getProducts(query);
|
||||
}
|
||||
|
||||
getProduct(productID: number): Observable<Product> {
|
||||
return this.provider.getProduct(productID);
|
||||
}
|
||||
|
||||
getCategories(): Observable<ProductCategory[]> {
|
||||
return this.provider.getCategories();
|
||||
}
|
||||
|
||||
searchProducts(query: ProductSearchQuery): Observable<ProductListResult> {
|
||||
return this.provider.searchProducts(query);
|
||||
}
|
||||
|
||||
getFeaturedProducts(query?: ProductListQuery): Observable<ProductListResult> {
|
||||
return this.provider.getFeaturedProducts(query);
|
||||
}
|
||||
|
||||
getLatestProducts(query?: ProductListQuery): Observable<ProductListResult> {
|
||||
return this.provider.getLatestProducts(query);
|
||||
}
|
||||
|
||||
getProductsByCategory(categoryID: number, query?: ProductListQuery): Observable<ProductListResult> {
|
||||
return this.provider.getProductsByCategory(categoryID, query);
|
||||
}
|
||||
|
||||
getRelatedProducts(query: RelatedProductsQuery): Observable<ProductListResult> {
|
||||
return this.provider.getRelatedProducts(query);
|
||||
}
|
||||
}
|
||||
75
src/app/core/products/providers/api-product-data.provider.ts
Normal file
75
src/app/core/products/providers/api-product-data.provider.ts
Normal file
@@ -0,0 +1,75 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Observable, map } from 'rxjs';
|
||||
import { ApiService } from '../../../services';
|
||||
import { ProductDataProvider } from './product-data-provider.interface';
|
||||
import { Product, ProductCategory, ProductListQuery, ProductListResult, ProductSearchQuery, RelatedProductsQuery } from '../models/product-domain.model';
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class ApiProductDataProvider implements ProductDataProvider {
|
||||
constructor(private readonly apiService: ApiService) {}
|
||||
|
||||
getProducts(query: ProductListQuery = {}): Observable<ProductListResult> {
|
||||
return this.apiService.searchItems('', query.count, query.skip, this.toSearchOptions(query))
|
||||
.pipe(map(result => this.toResult(result.items, result.total, query)));
|
||||
}
|
||||
|
||||
getProduct(productID: number): Observable<Product> {
|
||||
return this.apiService.getItem(productID);
|
||||
}
|
||||
|
||||
getCategories(): Observable<ProductCategory[]> {
|
||||
return this.apiService.getCategories();
|
||||
}
|
||||
|
||||
searchProducts(query: ProductSearchQuery): Observable<ProductListResult> {
|
||||
return this.apiService.searchItems(query.search, query.count, query.skip, this.toSearchOptions(query))
|
||||
.pipe(map(result => this.toResult(result.items, result.total, query)));
|
||||
}
|
||||
|
||||
getFeaturedProducts(query: ProductListQuery = {}): Observable<ProductListResult> {
|
||||
return this.apiService.getRandomItems(query.count ?? 10)
|
||||
.pipe(map(items => this.toResult(items, items.length, query)));
|
||||
}
|
||||
|
||||
getLatestProducts(query: ProductListQuery = {}): Observable<ProductListResult> {
|
||||
return this.apiService.searchItems('', query.count, query.skip, { ...this.toSearchOptions(query), sort: 'popular' })
|
||||
.pipe(map(result => this.toResult(result.items, result.total, query)));
|
||||
}
|
||||
|
||||
getProductsByCategory(categoryID: number, query: ProductListQuery = {}): Observable<ProductListResult> {
|
||||
return this.apiService.getCategoryItems(categoryID, query.count, query.skip)
|
||||
.pipe(map(items => this.toResult(items, items.length, query)));
|
||||
}
|
||||
|
||||
getRelatedProducts(query: RelatedProductsQuery): Observable<ProductListResult> {
|
||||
if (query.categoryID == null) {
|
||||
return this.getFeaturedProducts({ count: query.count ?? 8 });
|
||||
}
|
||||
|
||||
return this.getProductsByCategory(query.categoryID, { count: query.count ?? 8 }).pipe(
|
||||
map(result => ({
|
||||
...result,
|
||||
items: result.items.filter(item => item.itemID !== query.productID)
|
||||
}))
|
||||
);
|
||||
}
|
||||
|
||||
private toSearchOptions(query: ProductListQuery) {
|
||||
return {
|
||||
categoryIDs: query.categoryIDs,
|
||||
minPrice: query.minPrice,
|
||||
maxPrice: query.maxPrice,
|
||||
tag: query.tag,
|
||||
sort: query.sort === 'latest' ? 'popular' : query.sort
|
||||
};
|
||||
}
|
||||
|
||||
private toResult(items: Product[], total: number, query: ProductListQuery): ProductListResult {
|
||||
return {
|
||||
items,
|
||||
total,
|
||||
count: query.count ?? items.length,
|
||||
skip: query.skip ?? 0
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
import { Observable } from 'rxjs';
|
||||
import { Product, ProductCategory, ProductListQuery, ProductListResult, ProductSearchQuery, RelatedProductsQuery } from '../models/product-domain.model';
|
||||
|
||||
export interface ProductDataProvider {
|
||||
getProducts(query?: ProductListQuery): Observable<ProductListResult>;
|
||||
getProduct(productID: number): Observable<Product>;
|
||||
getCategories(): Observable<ProductCategory[]>;
|
||||
searchProducts(query: ProductSearchQuery): Observable<ProductListResult>;
|
||||
getFeaturedProducts(query?: ProductListQuery): Observable<ProductListResult>;
|
||||
getLatestProducts(query?: ProductListQuery): Observable<ProductListResult>;
|
||||
getProductsByCategory(categoryID: number, query?: ProductListQuery): Observable<ProductListResult>;
|
||||
getRelatedProducts(query: RelatedProductsQuery): Observable<ProductListResult>;
|
||||
}
|
||||
@@ -20,4 +20,12 @@ export class RuntimeProviderStrategyService {
|
||||
|
||||
return 'api';
|
||||
}
|
||||
|
||||
getProductProviderMode(): RuntimeProviderMode {
|
||||
if (environment.useMockData) {
|
||||
return 'mock';
|
||||
}
|
||||
|
||||
return 'api';
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user