3367 add facets subquery to product (#146)
This commit is contained in:
@@ -1,7 +1,8 @@
|
||||
import { RESTDataSource } from '@apollo/datasource-rest';
|
||||
import { ScopeAccess, Scopes } from '../cognito/access-control';
|
||||
import { Product, ProductGroup } from '../types/product-types';
|
||||
import { Product } from '../types/product-types';
|
||||
import { DataSourceOptions, User } from '../types/types';
|
||||
import { isRepeatingPattern } from './utils';
|
||||
|
||||
export class InteriorsLambdaAPI extends RESTDataSource {
|
||||
override baseURL = process.env.INTERIORS_URL;
|
||||
@@ -23,11 +24,6 @@ export class InteriorsLambdaAPI extends RESTDataSource {
|
||||
async generateNewInteriors(product: Product): Promise<any> {
|
||||
ScopeAccess.validate(this.user).all([Scopes.INTERIORS_WRITE]);
|
||||
|
||||
const found = product.printProducts.find(
|
||||
(pr) => pr.groupId === ProductGroup.WALLPAPER,
|
||||
);
|
||||
const isRepeating = found !== undefined;
|
||||
|
||||
const body = {
|
||||
token: process.env.INTERIORS_TOKEN,
|
||||
image: `products/${product.id}.jpg`,
|
||||
@@ -38,7 +34,7 @@ export class InteriorsLambdaAPI extends RESTDataSource {
|
||||
autodetect: true,
|
||||
};
|
||||
|
||||
if (isRepeating) {
|
||||
if (isRepeatingPattern(product)) {
|
||||
body['print_file_height'] = product.fields.printFileHeight;
|
||||
body['print_file_dpi'] = product.fields.printFileDpi;
|
||||
}
|
||||
|
||||
@@ -1,11 +1,16 @@
|
||||
import DataLoader from 'dataloader';
|
||||
import { SQLDataSource } from 'datasource-sql';
|
||||
import { ScopeAccess, Scopes } from '../cognito/access-control';
|
||||
import { Keyword, KeywordType } from '../types/keyword-types';
|
||||
import { DataSourceOptions, User } from '../types/types';
|
||||
import { TextsAPI } from './texts-api';
|
||||
import { MINUTE } from './utils';
|
||||
import { createQuestionMarksFromList, MINUTE } from './utils';
|
||||
|
||||
interface KeywordWithProductId extends Keyword {
|
||||
product_id: number;
|
||||
}
|
||||
export class KeywordAPI extends SQLDataSource {
|
||||
private productKeywordsLoader: DataLoader<number, Keyword[]>;
|
||||
textsApi: TextsAPI;
|
||||
user: User;
|
||||
constructor(options: DataSourceOptions, config, textsApi) {
|
||||
@@ -13,6 +18,20 @@ export class KeywordAPI extends SQLDataSource {
|
||||
this.user = options.user;
|
||||
this.initialize({ cache: options.cache, context: null });
|
||||
this.textsApi = textsApi;
|
||||
|
||||
this.productKeywordsLoader = new DataLoader(async (ids: number[]) => {
|
||||
const results = await this.getProductKeywordsBatch(ids);
|
||||
const indexed = {} as Record<string, KeywordWithProductId[]>;
|
||||
results.forEach((keywordRow) => {
|
||||
if (!indexed[keywordRow.product_id]) {
|
||||
indexed[keywordRow.product_id] = [keywordRow];
|
||||
} else {
|
||||
indexed[keywordRow.product_id].push(keywordRow);
|
||||
}
|
||||
});
|
||||
const output = ids.map((id) => indexed[id] ?? []);
|
||||
return output;
|
||||
});
|
||||
}
|
||||
|
||||
// -------------------------------
|
||||
@@ -36,18 +55,24 @@ export class KeywordAPI extends SQLDataSource {
|
||||
// Functions used in resolvers or other datasources
|
||||
// ----------------------------
|
||||
|
||||
async getProductKeywords(productId: number): Promise<Array<Keyword>> {
|
||||
async getProductKeywords(productId: number): Promise<Keyword[]> {
|
||||
return this.productKeywordsLoader.load(productId);
|
||||
}
|
||||
|
||||
async getProductKeywordsBatch(
|
||||
productIds: number[],
|
||||
): Promise<KeywordWithProductId[]> {
|
||||
ScopeAccess.validate(this.user).all([Scopes.KEYWORDS_READ]);
|
||||
const query = /* sql */ `
|
||||
SELECT keywords.*, keyword_type.type_id FROM keywords
|
||||
SELECT product_keyword.product_id,keywords.*, keyword_type.type_id FROM keywords
|
||||
LEFT JOIN keyword_type ON keyword_type.keyword_id = keywords.id
|
||||
JOIN product_keyword ON product_keyword.keyword_id = keywords.id
|
||||
WHERE product_keyword.product_id = ?
|
||||
WHERE product_keyword.product_id IN (${createQuestionMarksFromList(productIds)})
|
||||
ORDER BY keywords.value
|
||||
`;
|
||||
|
||||
return this.knex
|
||||
.raw(query, productId)
|
||||
.raw(query, productIds)
|
||||
.then((data) => data.rows.map((row) => this.getType(row)));
|
||||
}
|
||||
|
||||
|
||||
@@ -11,7 +11,6 @@ import {
|
||||
ProductBlacklist,
|
||||
ProductWallpaperType,
|
||||
ProductFields,
|
||||
Orientation,
|
||||
ProductsFilterInput,
|
||||
ProductInfoInput,
|
||||
ProductBlacklistMarketInput,
|
||||
@@ -25,6 +24,7 @@ import { DataSourceOptions, Maybe } from '../types/types';
|
||||
import {
|
||||
booleanStringField,
|
||||
createQuestionMarksFromList,
|
||||
getOrientationString,
|
||||
isInt,
|
||||
nullOrNumber,
|
||||
roundOrDefaultTo,
|
||||
@@ -153,24 +153,11 @@ export class ProductAPI extends BaseSQLDataSource {
|
||||
return <ProductFields>fields;
|
||||
}
|
||||
|
||||
getOrientation(fields: ProductFields): string {
|
||||
if (!fields || !fields.width || !fields.height) {
|
||||
return Orientation[Orientation.UNKNOWN];
|
||||
}
|
||||
if (fields.width > fields.height) {
|
||||
return Orientation[Orientation.LANDSCAPE];
|
||||
}
|
||||
if (fields.width < fields.height) {
|
||||
return Orientation[Orientation.STANDING];
|
||||
}
|
||||
return Orientation[Orientation.SQUARE];
|
||||
}
|
||||
|
||||
createProductFromRow(row: any) {
|
||||
row.type = this.getProductTypes(row);
|
||||
row.wallpaperTypes = this.getWallpaperTypes(row);
|
||||
row.designerId = row.designerid;
|
||||
row.orientation = this.getOrientation(row.fields);
|
||||
row.orientation = getOrientationString(row.fields);
|
||||
row.publishingDate = row.publishing_date;
|
||||
row.printProducts = this.getPrintProducts(row);
|
||||
row.blacklisting = this.getBlacklisting(row);
|
||||
|
||||
@@ -1,3 +1,10 @@
|
||||
import {
|
||||
Orientation,
|
||||
Product,
|
||||
ProductFields,
|
||||
ProductGroup,
|
||||
} from '../types/product-types';
|
||||
|
||||
type InputType = string | undefined | null;
|
||||
|
||||
export const MINUTE = 60;
|
||||
@@ -74,3 +81,23 @@ export function roundOrDefaultTo(
|
||||
export const createQuestionMarksFromList = (arr: unknown[]): string => {
|
||||
return '?,'.repeat(arr.length).slice(0, -1);
|
||||
};
|
||||
|
||||
export const isRepeatingPattern = (product: Product): boolean => {
|
||||
const found = product.printProducts.find(
|
||||
(pr) => pr.groupId === ProductGroup.WALLPAPER,
|
||||
);
|
||||
return found !== undefined;
|
||||
};
|
||||
|
||||
export const getOrientationString = (fields: ProductFields): string => {
|
||||
if (!fields || !fields.width || !fields.height) {
|
||||
return Orientation[Orientation.UNKNOWN];
|
||||
}
|
||||
if (fields.width > fields.height) {
|
||||
return Orientation[Orientation.LANDSCAPE];
|
||||
}
|
||||
if (fields.width < fields.height) {
|
||||
return Orientation[Orientation.STANDING];
|
||||
}
|
||||
return Orientation[Orientation.SQUARE];
|
||||
};
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { ProductAPI } from '../datasources/product-api';
|
||||
import {
|
||||
Orientation,
|
||||
PrintProduct,
|
||||
Product,
|
||||
ProductListResult,
|
||||
@@ -20,6 +21,7 @@ import { DesignerAPI } from '../datasources/designer-api';
|
||||
import { PrintProductDefaultsAPI } from '../datasources/printproduct-defaults-api';
|
||||
import { PrintProductDefaults } from '../types/printproduct-defaults-types';
|
||||
import { GraphQLError } from 'graphql';
|
||||
import { getOrientationString, isRepeatingPattern } from '../datasources/utils';
|
||||
|
||||
const ProductBlacklist = {
|
||||
async market(parent, _args, { dataSources }) {
|
||||
@@ -45,6 +47,41 @@ const Product = {
|
||||
async related({ id }, _args, { dataSources }) {
|
||||
return (<ProductAPI>dataSources.productApi).getRelatedProducts(id);
|
||||
},
|
||||
async facets(product: Product, _args, { dataSources }) {
|
||||
const keywords = await (<KeywordAPI>(
|
||||
dataSources.keywordApi
|
||||
)).getProductKeywords(product.id);
|
||||
const colors = [];
|
||||
const types = [];
|
||||
keywords.forEach((keyword) => {
|
||||
if (keyword.type === 'COLOR') {
|
||||
colors.push(keyword.value);
|
||||
} else if (keyword.value === 'typeillustration') {
|
||||
types.push('illustration');
|
||||
} else if (keyword.value === 'typephotography') {
|
||||
types.push('photograph');
|
||||
}
|
||||
});
|
||||
|
||||
const isRepeating = isRepeatingPattern(product);
|
||||
if (isRepeating) {
|
||||
types.push('repeating-pattern');
|
||||
}
|
||||
|
||||
const orientations = isRepeating
|
||||
? [
|
||||
Orientation[Orientation.LANDSCAPE],
|
||||
Orientation[Orientation.STANDING],
|
||||
Orientation[Orientation.SQUARE],
|
||||
]
|
||||
: [getOrientationString(product.fields)];
|
||||
|
||||
return [
|
||||
{ attribute: 'color', values: colors },
|
||||
{ attribute: 'orientation', values: orientations },
|
||||
{ attribute: 'type', values: types },
|
||||
];
|
||||
},
|
||||
};
|
||||
|
||||
const PrintProduct = {
|
||||
|
||||
@@ -239,6 +239,11 @@ type Copyright {
|
||||
products: [Product]
|
||||
}
|
||||
|
||||
type Facet {
|
||||
attribute: String,
|
||||
values: [String]
|
||||
}
|
||||
|
||||
type Product {
|
||||
id: ID!
|
||||
stockid: Int
|
||||
@@ -261,6 +266,7 @@ type Product {
|
||||
wallpaperTypes: [ProductWallpaperType]!
|
||||
fields: ProductFields
|
||||
orientation: Orientation
|
||||
facets: [Facet]
|
||||
"""
|
||||
Will be single values return in later release
|
||||
"""
|
||||
|
||||
@@ -6,5 +6,5 @@ export enum KeywordType {
|
||||
export interface Keyword {
|
||||
id: number;
|
||||
value: string;
|
||||
type: KeywordType;
|
||||
type: string;
|
||||
}
|
||||
|
||||
@@ -136,6 +136,11 @@ export interface Copyright {
|
||||
products: Promise<Array<Product>>;
|
||||
}
|
||||
|
||||
export interface Facet {
|
||||
attribute: string;
|
||||
value: string[];
|
||||
}
|
||||
|
||||
export interface Product {
|
||||
id: number;
|
||||
stockid: number;
|
||||
@@ -152,6 +157,7 @@ export interface Product {
|
||||
blacklisting: [ProductBlacklist];
|
||||
type: [ProductType];
|
||||
fields: ProductFields;
|
||||
facets: Facet[];
|
||||
}
|
||||
|
||||
// TODO: test stock products to see what props can be mandatory
|
||||
|
||||
Reference in New Issue
Block a user