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