Compare commits
10
Commits
83155577ac
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8608b28300 | ||
|
|
eecc0f4f1b | ||
|
|
dcf41787d2 | ||
|
|
e82457e45a | ||
|
|
c996cf53e0 | ||
|
|
bbb750e5f4 | ||
|
|
d25a8878f0 | ||
|
|
f9e670e1c3 | ||
|
|
2f86495a88 | ||
|
|
3bef054684 |
@@ -28,6 +28,7 @@ DOCKER_IMAGE=$(AWS_ACCOUNT_ID).dkr.ecr.$(AWS_REGION).amazonaws.com/$(STACK_NAME)
|
||||
ifeq ($(STACK_ENVIRONMENT_NAME),production)
|
||||
SUBDOMAIN=$(STACK_NAME)
|
||||
ENVIRONMENT_NAME=production
|
||||
COST_ENVIRONMENT=production
|
||||
BERNARD_QUEUE_ARN=arn:aws:sqs:eu-west-1:954747537408:photowall-bernard-production
|
||||
SAMPLE_RIPPER_QUEUE_ARN=$(SAMPLE_RIPPER_QUEUE_ARN_PRODUCTION)
|
||||
GITHUB_BRANCH=master
|
||||
@@ -39,6 +40,7 @@ ifeq ($(STACK_ENVIRONMENT_NAME),production)
|
||||
else
|
||||
SUBDOMAIN=$(STACK_NAME)-$(STACK_ENVIRONMENT_NAME)
|
||||
ENVIRONMENT_NAME=$(STACK_ENVIRONMENT_NAME)
|
||||
COST_ENVIRONMENT=testing
|
||||
BERNARD_QUEUE_ARN=arn:aws:sqs:eu-west-1:954747537408:photowall-bernard-stage
|
||||
SAMPLE_RIPPER_QUEUE_ARN=$(SAMPLE_RIPPER_QUEUE_ARN_STAGING)
|
||||
GITHUB_BRANCH=$(STACK_ENVIRONMENT_NAME)
|
||||
@@ -97,7 +99,7 @@ delete-stack:
|
||||
create-stack: check-create-variables-set check-create-initial-ecr-image
|
||||
aws cloudformation create-stack --stack-name $(STACK_NAME)-$(STACK_ENVIRONMENT_NAME) --template-body file://ecs-service.yaml \
|
||||
--capabilities CAPABILITY_NAMED_IAM \
|
||||
--tags Key=EnvironmentName,Value=$(STACK_ENVIRONMENT_NAME) \
|
||||
--tags Key=EnvironmentName,Value=$(STACK_ENVIRONMENT_NAME) Key=Project,Value=GraphQLServer Key=CostEnvironment,Value=$(COST_ENVIRONMENT) \
|
||||
--parameters ParameterKey="EnvironmentName",ParameterValue="$(ENVIRONMENT_NAME)" \
|
||||
ParameterKey="BernardQueueArn",ParameterValue="${BERNARD_QUEUE_ARN}" \
|
||||
ParameterKey="SampleRipperQueueArn",ParameterValue="${SAMPLE_RIPPER_QUEUE_ARN}" \
|
||||
@@ -117,14 +119,14 @@ update-stack: set-repository-lifecycle-policy
|
||||
$(call checkdef,ENVIRONMENT_NAME)
|
||||
aws cloudformation update-stack --stack-name $(STACK_NAME)-$(STACK_ENVIRONMENT_NAME) --template-body file://ecs-service.yaml \
|
||||
--capabilities CAPABILITY_NAMED_IAM \
|
||||
--tags Key=EnvironmentName,Value=$(STACK_ENVIRONMENT_NAME) \
|
||||
--tags Key=EnvironmentName,Value=$(STACK_ENVIRONMENT_NAME) Key=Project,Value=GraphQLServer Key=CostEnvironment,Value=$(COST_ENVIRONMENT) \
|
||||
--parameters ParameterKey="EnvironmentName",UsePreviousValue=true \
|
||||
ParameterKey="BernardQueueArn",ParameterValue="${BERNARD_QUEUE_ARN}" \
|
||||
ParameterKey="SampleRipperQueueArn",ParameterValue="${SAMPLE_RIPPER_QUEUE_ARN}" \
|
||||
ParameterKey="Image",UsePreviousValue=true \
|
||||
ParameterKey="Subdomain",UsePreviousValue=true \
|
||||
ParameterKey="Certificate",UsePreviousValue=true \
|
||||
ParameterKey="DatabaseSecurityGroup",,UsePreviousValue=true \
|
||||
ParameterKey="DatabaseSecurityGroup",UsePreviousValue=true \
|
||||
ParameterKey="GitHubRepo",ParameterValue="$(GITHUB_REPO)" \
|
||||
ParameterKey="GitHubBranch",UsePreviousValue=true \
|
||||
ParameterKey="CpuSize",ParameterValue="$(CPU_SIZE)" \
|
||||
|
||||
+117
-37
@@ -3,7 +3,18 @@ import { ScopeAccess, Scopes } from '../cognito/access-control';
|
||||
import { Category, CategoryLocaleData } from '../types/category-types';
|
||||
import { DataSourceOptions } from '../types/types';
|
||||
import { BaseSQLDataSource } from './BaseSQLDataSource';
|
||||
import { createQuestionMarksFromList, MINUTE } from './utils';
|
||||
import { createQuestionMarksFromList, HOUR, MINUTE } from './utils';
|
||||
|
||||
const ROOMS_PARENT_ID = 270;
|
||||
const COLORS_PARENT_ID = 1665;
|
||||
const STYLES_PARENT_ID = 1814;
|
||||
const PATTERNS_PARENT_ID = 1414;
|
||||
const KNOWN_FACET_PARENT_IDS = [
|
||||
ROOMS_PARENT_ID,
|
||||
COLORS_PARENT_ID,
|
||||
STYLES_PARENT_ID,
|
||||
PATTERNS_PARENT_ID,
|
||||
];
|
||||
|
||||
export class CategoryAPI extends BaseSQLDataSource {
|
||||
private categoryLoader: DataLoader<number, Category>;
|
||||
@@ -65,65 +76,114 @@ WHERE product_category.product_id = ?
|
||||
return res;
|
||||
}
|
||||
|
||||
async getCategoryBasedFacets(
|
||||
productId: number,
|
||||
): Promise<{ room: string[]; color: string[] }> {
|
||||
async getProductCategoriesV2(productId: number): Promise<Array<Category>> {
|
||||
ScopeAccess.validate(this.user).all([Scopes.CATEGORIES_READ]);
|
||||
const query = /* sql */ `
|
||||
SELECT product_category.category_id as id,
|
||||
categories.*
|
||||
FROM product_category
|
||||
JOIN v_categorytree categories ON product_category.category_id = categories.id
|
||||
WHERE product_category.product_id = ?
|
||||
`;
|
||||
|
||||
const res = await this.knex.raw(query, productId).then((data) =>
|
||||
data.rows.map((row) => {
|
||||
return {
|
||||
...row,
|
||||
pathNumeric: row.path_numeric,
|
||||
};
|
||||
}),
|
||||
);
|
||||
return res;
|
||||
}
|
||||
|
||||
async getMotifRootIds(): Promise<number[]> {
|
||||
const { rows } = await this.cachedRaw(
|
||||
/* sql */ `
|
||||
SELECT id
|
||||
FROM v_categorytree
|
||||
WHERE depth = 1
|
||||
AND id NOT IN (${createQuestionMarksFromList(KNOWN_FACET_PARENT_IDS)})
|
||||
`,
|
||||
KNOWN_FACET_PARENT_IDS,
|
||||
)
|
||||
// @ts-ignore
|
||||
.cache(HOUR);
|
||||
|
||||
return rows.map((row) => row.id);
|
||||
}
|
||||
|
||||
async getCategoryBasedFacets(productId: number): Promise<{
|
||||
room: string[];
|
||||
color: string[];
|
||||
style: string[];
|
||||
pattern: string[];
|
||||
motif: string[];
|
||||
}> {
|
||||
ScopeAccess.validate(this.user).some([
|
||||
Scopes.CATEGORIES_READ,
|
||||
Scopes.CATEGORIES_PUBLIC_READ,
|
||||
]);
|
||||
|
||||
const ROOMS_PARENT_ID = 270;
|
||||
const COLORS_PARENT_ID = 1665;
|
||||
const motifRootIds = await this.getMotifRootIds();
|
||||
const allFacetParentIds = [...KNOWN_FACET_PARENT_IDS, ...motifRootIds];
|
||||
|
||||
const { rows } = await this.cachedRaw(
|
||||
`
|
||||
WITH rooms AS (
|
||||
SELECT lft, rgt FROM categories WHERE id = ?
|
||||
),
|
||||
colors AS (
|
||||
SELECT lft, rgt FROM categories WHERE id = ?
|
||||
/* sql */ `
|
||||
WITH facet_parents AS (
|
||||
SELECT id, lft, rgt, name,
|
||||
CASE
|
||||
WHEN id = ? THEN 'room'
|
||||
WHEN id = ? THEN 'color'
|
||||
WHEN id = ? THEN 'style'
|
||||
WHEN id = ? THEN 'pattern'
|
||||
ELSE 'motif'
|
||||
END AS facet
|
||||
FROM categories
|
||||
WHERE id IN (${createQuestionMarksFromList(allFacetParentIds)})
|
||||
)
|
||||
SELECT
|
||||
array_remove(
|
||||
array_agg(
|
||||
DISTINCT CASE
|
||||
WHEN c.lft > rooms.lft AND c.rgt < rooms.rgt THEN c.name
|
||||
END
|
||||
),
|
||||
NULL
|
||||
) AS room,
|
||||
array_remove(
|
||||
array_agg(
|
||||
DISTINCT CASE
|
||||
WHEN c.lft > colors.lft AND c.rgt < colors.rgt THEN c.name
|
||||
END
|
||||
),
|
||||
NULL
|
||||
) AS color
|
||||
array_agg(DISTINCT c.name) FILTER (WHERE fp.facet = 'room') AS room,
|
||||
array_agg(DISTINCT c.name) FILTER (WHERE fp.facet = 'color') AS color,
|
||||
array_agg(DISTINCT c.name) FILTER (WHERE fp.facet = 'style') AS style,
|
||||
array_agg(DISTINCT c.name) FILTER (WHERE fp.facet = 'pattern') AS pattern,
|
||||
array_agg(DISTINCT fp.name) FILTER (WHERE fp.facet = 'motif') AS motif
|
||||
FROM product_category pc
|
||||
JOIN categories c ON c.id = pc.category_id
|
||||
CROSS JOIN rooms
|
||||
CROSS JOIN colors
|
||||
JOIN facet_parents fp ON c.lft > fp.lft AND c.rgt < fp.rgt
|
||||
WHERE pc.product_id = ?
|
||||
AND (
|
||||
(c.lft > rooms.lft AND c.rgt < rooms.rgt)
|
||||
OR
|
||||
(c.lft > colors.lft AND c.rgt < colors.rgt)
|
||||
)
|
||||
`,
|
||||
[ROOMS_PARENT_ID, COLORS_PARENT_ID, productId],
|
||||
[
|
||||
ROOMS_PARENT_ID,
|
||||
COLORS_PARENT_ID,
|
||||
STYLES_PARENT_ID,
|
||||
PATTERNS_PARENT_ID,
|
||||
...allFacetParentIds,
|
||||
productId,
|
||||
],
|
||||
)
|
||||
// @ts-ignore
|
||||
.cache(MINUTE * 5);
|
||||
|
||||
const facetRow = rows[0] ?? { room: [], color: [] };
|
||||
const facetRow = rows[0] ?? {
|
||||
room: [],
|
||||
color: [],
|
||||
style: [],
|
||||
pattern: [],
|
||||
motif: [],
|
||||
};
|
||||
const roomCategories: string[] = facetRow.room ?? [];
|
||||
const colorCategories: string[] = facetRow.color ?? [];
|
||||
const styleCategories: string[] = facetRow.style ?? [];
|
||||
const patternCategories: string[] = facetRow.pattern ?? [];
|
||||
const motifCategories: string[] = facetRow.motif ?? [];
|
||||
|
||||
return {
|
||||
room: roomCategories,
|
||||
color: colorCategories,
|
||||
style: styleCategories,
|
||||
pattern: patternCategories,
|
||||
motif: motifCategories,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -153,6 +213,26 @@ WHERE product_category.product_id = ?
|
||||
);
|
||||
}
|
||||
|
||||
async getCategoriesV2(ids: number[]): Promise<Array<Category>> {
|
||||
ScopeAccess.validate(this.user).some([
|
||||
Scopes.CATEGORIES_READ,
|
||||
Scopes.CATEGORIES_PUBLIC_READ,
|
||||
]);
|
||||
|
||||
return (
|
||||
this.knex
|
||||
.select('*')
|
||||
.modify((queryBuilder) => {
|
||||
if (ids) {
|
||||
queryBuilder.whereIn('id', ids);
|
||||
}
|
||||
})
|
||||
.from('v_categorytree')
|
||||
// @ts-ignore
|
||||
.cache(MINUTE)
|
||||
);
|
||||
}
|
||||
|
||||
async searchCategories(name: string): Promise<Array<Category>> {
|
||||
ScopeAccess.validate(this.user).all([Scopes.CATEGORIES_READ]);
|
||||
return this.knex
|
||||
|
||||
@@ -851,6 +851,12 @@ export class ProductAPI extends BaseSQLDataSource {
|
||||
ProductMaterials.PREMIUM_WALLPAPER,
|
||||
]),
|
||||
);
|
||||
insertMaterials.push(
|
||||
this.knex.raw(insertMaterialQuery, [
|
||||
printid,
|
||||
ProductMaterials.MATTE_WALLPAPER,
|
||||
]),
|
||||
);
|
||||
break;
|
||||
case ProductGroup.CANVAS:
|
||||
insertMaterials.push(
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
type InputType = string | undefined | null;
|
||||
|
||||
export const MINUTE = 60;
|
||||
export const HOUR = 3600;
|
||||
|
||||
function isNumeric(n: InputType) {
|
||||
return !isNaN(parseFloat(n));
|
||||
@@ -82,6 +83,9 @@ export const createQuestionMarksFromList = (arr: unknown[]): string => {
|
||||
return '?,'.repeat(arr.length).slice(0, -1);
|
||||
};
|
||||
|
||||
export const sanitizeFacetValues = (values: string[]): string[] =>
|
||||
values.map((v) => v.toLowerCase().replace(/,/g, ''));
|
||||
|
||||
export const isRepeatingPattern = (product: Product): boolean => {
|
||||
const found = product.printProducts.find(
|
||||
(pr) => pr.groupId === ProductGroup.WALLPAPER,
|
||||
|
||||
@@ -26,6 +26,10 @@ async function getCategories(_, { ids }, { dataSources }) {
|
||||
return (<CategoryAPI>dataSources.categoryApi).getCategories(ids);
|
||||
}
|
||||
|
||||
async function getCategoriesV2(_, { ids }, { dataSources }) {
|
||||
return (<CategoryAPI>dataSources.categoryApi).getCategoriesV2(ids);
|
||||
}
|
||||
|
||||
async function getCategory(_, { id }, { dataSources }) {
|
||||
return (<CategoryAPI>dataSources.categoryApi).getCategoryById(id);
|
||||
}
|
||||
@@ -34,5 +38,6 @@ export const categoryTypeDefs = { Category };
|
||||
|
||||
export const categoryQueryTypeDefs = {
|
||||
categories: getCategories,
|
||||
categories_v2: getCategoriesV2,
|
||||
category: getCategory,
|
||||
};
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import { ProductAPI } from '../datasources/product-api';
|
||||
import {
|
||||
Orientation,
|
||||
PrintProduct,
|
||||
Product,
|
||||
ProductGroup,
|
||||
import { Orientation, ProductGroup } from '../types/product-types';
|
||||
import type {
|
||||
PrintProduct as PrintProductType,
|
||||
Product as ProductType,
|
||||
ProductListResult,
|
||||
ProductsFilterInput,
|
||||
ProductsSearchResult,
|
||||
@@ -16,16 +15,22 @@ import { Keyword } from '../types/keyword-types';
|
||||
import { MarketLocaleAPI } from '../datasources/market-locale-api';
|
||||
import { InteriorAPI } from '../datasources/interior-api';
|
||||
import { InteriorsLambdaAPI } from '../datasources/interiors-lambda-api';
|
||||
import { IdNumberResult, MarketInput } from '../types/types';
|
||||
import type { IdNumberResult } from '../types/types';
|
||||
import { moveS3File } from '../aws/s3';
|
||||
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';
|
||||
import {
|
||||
getOrientationString,
|
||||
isRepeatingPattern,
|
||||
sanitizeFacetValues,
|
||||
} from '../datasources/utils';
|
||||
import { sendMessage } from '../aws/sqs';
|
||||
import { sendProductUpdatedEvent } from '../bernard/client';
|
||||
|
||||
const EXTRA_WIDE_RATIO_LIMIT = 2.4;
|
||||
|
||||
const ProductBlacklist = {
|
||||
async market(parent, _args, { dataSources }) {
|
||||
return (<MarketLocaleAPI>dataSources.marketLocaleApi).getMarketById(
|
||||
@@ -36,7 +41,7 @@ const ProductBlacklist = {
|
||||
|
||||
const Product = {
|
||||
async printProducts(
|
||||
product: { printProducts: PrintProduct[] },
|
||||
product: { printProducts: PrintProductType[] },
|
||||
input: { groups: ProductGroup[] },
|
||||
) {
|
||||
if (!input.groups) {
|
||||
@@ -49,6 +54,13 @@ const Product = {
|
||||
async categories({ id }, _args, { dataSources }): Promise<Array<Category>> {
|
||||
return (<CategoryAPI>dataSources.categoryApi).getProductCategories(id);
|
||||
},
|
||||
async categories_v2(
|
||||
{ id },
|
||||
_args,
|
||||
{ dataSources },
|
||||
): Promise<Array<Category>> {
|
||||
return (<CategoryAPI>dataSources.categoryApi).getProductCategoriesV2(id);
|
||||
},
|
||||
async collections({ id }, _args, { dataSources }) {
|
||||
return dataSources.collectionApi.getProductCollections(id);
|
||||
},
|
||||
@@ -64,7 +76,7 @@ const Product = {
|
||||
async related({ id }, _args, { dataSources }) {
|
||||
return (<ProductAPI>dataSources.productApi).getRelatedProducts(id);
|
||||
},
|
||||
async facets(product: Product, _args, { dataSources }) {
|
||||
async facets(product: ProductType, _args, { dataSources }) {
|
||||
const keywords = await (<KeywordAPI>(
|
||||
dataSources.keywordApi
|
||||
)).getProductKeywords(product.id);
|
||||
@@ -109,17 +121,41 @@ const Product = {
|
||||
}),
|
||||
]);
|
||||
|
||||
const patterns = isRepeating ? categoryBasedFacets.pattern : [];
|
||||
const motifs = isRepeating ? [] : categoryBasedFacets.motif;
|
||||
|
||||
return [
|
||||
{
|
||||
attribute: 'color',
|
||||
values: [...colors],
|
||||
values: sanitizeFacetValues([...colors]),
|
||||
},
|
||||
{
|
||||
attribute: 'orientation',
|
||||
values: orientations.map((value) => value.toLowerCase()),
|
||||
values: orientations.map((value) => {
|
||||
if (
|
||||
product.fields.width / product.fields.height >
|
||||
EXTRA_WIDE_RATIO_LIMIT
|
||||
) {
|
||||
return 'extra-wide';
|
||||
}
|
||||
|
||||
return value.toLowerCase();
|
||||
}),
|
||||
},
|
||||
{ attribute: 'type', values: sanitizeFacetValues(types) },
|
||||
{
|
||||
attribute: 'room',
|
||||
values: sanitizeFacetValues(categoryBasedFacets.room),
|
||||
},
|
||||
{
|
||||
attribute: 'style',
|
||||
values: sanitizeFacetValues(categoryBasedFacets.style),
|
||||
},
|
||||
{ attribute: 'pattern', values: sanitizeFacetValues(patterns) },
|
||||
{
|
||||
attribute: 'motif',
|
||||
values: sanitizeFacetValues(motifs),
|
||||
},
|
||||
{ attribute: 'type', values: types },
|
||||
{ attribute: 'room', values: categoryBasedFacets.room },
|
||||
];
|
||||
},
|
||||
};
|
||||
@@ -177,7 +213,7 @@ async function getProductsSearchResult(
|
||||
]).then((d) => {
|
||||
// flatten, remove falsy (each searchBy... can return undefined above)
|
||||
// and finally remove duplicates.
|
||||
const products = {} as { [key: string]: Product };
|
||||
const products = {} as { [key: string]: ProductType };
|
||||
d.flat().forEach((product) => {
|
||||
if (product && !products[product.id]) {
|
||||
products[product.id] = product;
|
||||
@@ -208,7 +244,7 @@ async function getProductsSearchResult(
|
||||
};
|
||||
}
|
||||
|
||||
async function getProduct(_, { id }, { dataSources }): Promise<Product> {
|
||||
async function getProduct(_, { id }, { dataSources }): Promise<ProductType> {
|
||||
return (<ProductAPI>dataSources.productApi).getProduct(id);
|
||||
}
|
||||
|
||||
@@ -216,7 +252,7 @@ async function getProductByPath(
|
||||
_,
|
||||
{ path },
|
||||
{ dataSources },
|
||||
): Promise<Product> {
|
||||
): Promise<ProductType> {
|
||||
return (<ProductAPI>dataSources.productApi).getProductByPath(path);
|
||||
}
|
||||
|
||||
@@ -547,6 +583,7 @@ export const productMutationTypeDefs = {
|
||||
productId,
|
||||
categoryIds,
|
||||
);
|
||||
await sendProductUpdatedEvent([productId], ['categories']);
|
||||
return (<ProductAPI>dataSources.productApi).getProduct(productId);
|
||||
},
|
||||
|
||||
@@ -563,6 +600,7 @@ export const productMutationTypeDefs = {
|
||||
productId,
|
||||
categoryIds,
|
||||
);
|
||||
await sendProductUpdatedEvent([productId], ['categories']);
|
||||
return (<ProductAPI>dataSources.productApi).getProduct(productId);
|
||||
},
|
||||
|
||||
|
||||
@@ -28,6 +28,7 @@ type Query {
|
||||
productByPath(path: String!): Product
|
||||
productsSearch(q: String!): ProductsSearchResult
|
||||
categories(ids: [Int]): [Category]
|
||||
categories_v2(ids: [Int]): [Category]
|
||||
category(id: Int!): Category
|
||||
keywords: [Keyword]
|
||||
keyword(id: Int!): Keyword
|
||||
@@ -262,6 +263,7 @@ type Product {
|
||||
printProducts(groups: [ProductGroup]): [PrintProduct]
|
||||
blacklisting: [ProductBlacklist]
|
||||
categories: [Category]
|
||||
categories_v2: [Category]
|
||||
collections: [Collection]
|
||||
designerId: Int
|
||||
designer: Designer
|
||||
|
||||
@@ -84,6 +84,7 @@ export enum ProductMaterials {
|
||||
PREMIUM_WALLPAPER = 4,
|
||||
PREMIUM_POSTER = 5,
|
||||
PREMIUM_FRAMED_PRINT = 6,
|
||||
MATTE_WALLPAPER = 7,
|
||||
}
|
||||
|
||||
export enum ProductType {
|
||||
|
||||
Reference in New Issue
Block a user