585 lines
16 KiB
TypeScript
585 lines
16 KiB
TypeScript
import { ProductAPI } from '../datasources/product-api';
|
|
import {
|
|
Orientation,
|
|
PrintProduct,
|
|
Product,
|
|
ProductGroup,
|
|
ProductListResult,
|
|
ProductsFilterInput,
|
|
ProductsSearchResult,
|
|
} from '../types/product-types';
|
|
import * as CONFIG from '../config';
|
|
import { CategoryAPI } from '../datasources/category-api';
|
|
import { Category } from '../types/category-types';
|
|
import { KeywordAPI } from '../datasources/keyword-api';
|
|
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 { 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 { sendMessage } from '../aws/sqs';
|
|
|
|
const ProductBlacklist = {
|
|
async market(parent, _args, { dataSources }) {
|
|
return (<MarketLocaleAPI>dataSources.marketLocaleApi).getMarketById(
|
|
parent.marketId,
|
|
);
|
|
},
|
|
};
|
|
|
|
const Product = {
|
|
async printProducts(
|
|
product: { printProducts: PrintProduct[] },
|
|
input: { groups: ProductGroup[] },
|
|
) {
|
|
if (!input.groups) {
|
|
return product.printProducts;
|
|
}
|
|
return product.printProducts.filter((printProduct) => {
|
|
return input.groups.includes(printProduct.group);
|
|
});
|
|
},
|
|
async categories({ id }, _args, { dataSources }): Promise<Array<Category>> {
|
|
return (<CategoryAPI>dataSources.categoryApi).getProductCategories(id);
|
|
},
|
|
async designer({ designerId }, _args, { dataSources }) {
|
|
if (!designerId) {
|
|
return null;
|
|
}
|
|
return dataSources.designerApi.getDesignerById(designerId);
|
|
},
|
|
async keywords({ id }, _args, { dataSources }): Promise<Array<Keyword>> {
|
|
return (<KeywordAPI>dataSources.keywordApi).getProductKeywords(id);
|
|
},
|
|
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.map((value) => value.toLowerCase()),
|
|
},
|
|
{ attribute: 'type', values: types },
|
|
];
|
|
},
|
|
};
|
|
|
|
const PrintProduct = {
|
|
async interiors({ id }, _args, { dataSources }) {
|
|
return (<InteriorAPI>dataSources.interiorApi).getPrintProductInteriors(id);
|
|
},
|
|
async defaults({ id }, _args, { dataSources }) {
|
|
return (<PrintProductDefaultsAPI>(
|
|
dataSources.printProductDefaultsApi
|
|
)).getDefaults(id);
|
|
},
|
|
};
|
|
|
|
async function getProductsResult(
|
|
_,
|
|
input: ProductsFilterInput,
|
|
{ dataSources },
|
|
): Promise<ProductListResult> {
|
|
if (input.pagination.limit > 5000) {
|
|
throw new GraphQLError('Pagination limit cannot exceed 5000', {
|
|
extensions: { code: 'BAD_USER_INPUT' },
|
|
});
|
|
}
|
|
const totalPromise = (<ProductAPI>dataSources.productApi).getProductsTotal(
|
|
input,
|
|
);
|
|
const itemsPromise = (<ProductAPI>dataSources.productApi).getProducts(input);
|
|
const [total, items] = await Promise.all([totalPromise, itemsPromise]);
|
|
const offset = input.pagination?.offset ?? 0;
|
|
return {
|
|
pagination: {
|
|
limit: input.pagination.limit,
|
|
offset: offset,
|
|
total: total,
|
|
},
|
|
items: items,
|
|
};
|
|
}
|
|
|
|
async function getProductsSearchResult(
|
|
_,
|
|
{ q },
|
|
{ dataSources },
|
|
): Promise<ProductsSearchResult> {
|
|
const prodApi = <ProductAPI>dataSources.productApi;
|
|
const catApi = <CategoryAPI>dataSources.categoryApi;
|
|
const keywApi = <KeywordAPI>dataSources.keywordApi;
|
|
const designerApi = <DesignerAPI>dataSources.designerApi;
|
|
const products = await Promise.all([
|
|
prodApi.searchByName(q),
|
|
prodApi.searchByArtNo(q),
|
|
prodApi.searchByCompleteProductId(q),
|
|
]).then((d) => {
|
|
// flatten, remove falsy (each searchBy... can return undefined above)
|
|
// and finally remove duplicates.
|
|
const products = {} as { [key: string]: Product };
|
|
d.flat().forEach((product) => {
|
|
if (product && !products[product.id]) {
|
|
products[product.id] = product;
|
|
}
|
|
});
|
|
return Object.values(products);
|
|
});
|
|
|
|
const [batches, keywords, categories, designers, copyrights, references] =
|
|
await Promise.all([
|
|
prodApi.searchByBatch(q),
|
|
keywApi.searchKeywords(q),
|
|
catApi.searchCategories(q),
|
|
designerApi.searchDesigners(q),
|
|
prodApi.searchByCopyright(q),
|
|
prodApi.searchByReferences(q),
|
|
]);
|
|
|
|
return {
|
|
q: q,
|
|
products: products,
|
|
batches: batches,
|
|
keywords: keywords,
|
|
categories: categories,
|
|
designers: designers,
|
|
copyrights: copyrights,
|
|
references: references,
|
|
};
|
|
}
|
|
|
|
async function getProduct(_, { id }, { dataSources }): Promise<Product> {
|
|
return (<ProductAPI>dataSources.productApi).getProduct(id);
|
|
}
|
|
|
|
async function getProductByPath(
|
|
_,
|
|
{ path },
|
|
{ dataSources },
|
|
): Promise<Product> {
|
|
return (<ProductAPI>dataSources.productApi).getProductByPath(path);
|
|
}
|
|
|
|
export const productTypeDefs = {
|
|
ProductBlacklist,
|
|
Product,
|
|
PrintProduct,
|
|
};
|
|
|
|
export const productQueryTypeDefs = {
|
|
products: getProductsResult,
|
|
product: getProduct,
|
|
productByPath: getProductByPath,
|
|
productsSearch: getProductsSearchResult,
|
|
};
|
|
|
|
// ---------------------------------------------------------
|
|
// MUTATIONS
|
|
// ---------------------------------------------------------
|
|
|
|
export const productMutationTypeDefs = {
|
|
/**
|
|
* @function addProduct
|
|
* @description Will add a product
|
|
*/
|
|
async addProduct(_, { name, batch, designerId }, { dataSources }) {
|
|
const id = await (<ProductAPI>dataSources.productApi).addProduct(
|
|
name,
|
|
batch,
|
|
designerId,
|
|
);
|
|
|
|
const product = await (<ProductAPI>dataSources.productApi).getProduct(id);
|
|
|
|
// Send sqs message to trigger sample ripper
|
|
await sendMessage(
|
|
JSON.stringify({ productId: id, artNo: product.fields.artNo }),
|
|
process.env.SAMPLE_SQS_QUEUE_URL,
|
|
60 * 10,
|
|
);
|
|
|
|
return product;
|
|
},
|
|
|
|
/**
|
|
* @function cleanupInteriors
|
|
* @description Will remove all unused interior images for s3
|
|
*/
|
|
async cleanupInteriors(_, { productId }, { dataSources }) {
|
|
const product = await (<ProductAPI>dataSources.productApi).getProduct(
|
|
productId,
|
|
);
|
|
const interiors = [];
|
|
for (const printProduct of product.printProducts) {
|
|
const i = await (<InteriorAPI>(
|
|
dataSources.interiorApi
|
|
)).getPrintProductInteriors(printProduct.id);
|
|
interiors.push(...i);
|
|
}
|
|
const response = await (<ProductAPI>(
|
|
dataSources.productApi
|
|
)).cleanupInteriors(productId, interiors);
|
|
|
|
return response;
|
|
},
|
|
|
|
/**
|
|
* @function productInfo
|
|
* @description Mutate base information about a product
|
|
*/
|
|
async productInfo(_, { productId, info }, { dataSources }) {
|
|
await (<ProductAPI>dataSources.productApi).updateProductInfo(
|
|
productId,
|
|
info,
|
|
);
|
|
return (<ProductAPI>dataSources.productApi).getProduct(productId);
|
|
},
|
|
|
|
/**
|
|
* @function productFocusPoint
|
|
* @description Mutate the focuspoint for a product
|
|
*/
|
|
async productFocusPoint(
|
|
_,
|
|
{ productId, focusXpoint2, focusYpoint2 },
|
|
{ dataSources },
|
|
) {
|
|
await (<ProductAPI>dataSources.productApi).updateFocusPoint(
|
|
productId,
|
|
focusXpoint2,
|
|
focusYpoint2,
|
|
);
|
|
|
|
// generate new interiors
|
|
const product = await (<ProductAPI>dataSources.productApi).getProduct(
|
|
productId,
|
|
);
|
|
await (<InteriorsLambdaAPI>(
|
|
dataSources.interiorsLambdaApi
|
|
)).generateNewInteriors(product);
|
|
return (<ProductAPI>dataSources.productApi).getProduct(productId);
|
|
},
|
|
|
|
/**
|
|
* @function productBlacklisting
|
|
* @description Mutate the product blacklist
|
|
*/
|
|
async productBlacklisting(_, { productId, markets }, { dataSources }) {
|
|
await (<ProductAPI>dataSources.productApi).updateBlacklisting(
|
|
productId,
|
|
markets,
|
|
);
|
|
return (<ProductAPI>dataSources.productApi).getProduct(productId);
|
|
},
|
|
|
|
/**
|
|
* @function productGroup
|
|
* @description Mutate the product groups, when changing group interiors lambda is triggered.
|
|
*/
|
|
async productGroup(_, { productId, groupIds }, { dataSources }) {
|
|
await (<ProductAPI>dataSources.productApi).updateGroups(
|
|
productId,
|
|
groupIds,
|
|
);
|
|
const product = await (<ProductAPI>dataSources.productApi).getProduct(
|
|
productId,
|
|
);
|
|
await (<InteriorsLambdaAPI>(
|
|
dataSources.interiorsLambdaApi
|
|
)).generateNewInteriors(product);
|
|
return product;
|
|
},
|
|
|
|
/**
|
|
* @function generateInteriors
|
|
* @description Will generate one interior for a specific product
|
|
*/
|
|
async generateInteriors(_, { productId, roomIds }, { dataSources }) {
|
|
const product = await (<ProductAPI>dataSources.productApi).getProduct(
|
|
productId,
|
|
);
|
|
await (<InteriorsLambdaAPI>(
|
|
dataSources.interiorsLambdaApi
|
|
)).generateInteriors(product, roomIds);
|
|
return {
|
|
value: 'OK',
|
|
};
|
|
},
|
|
|
|
/**
|
|
* @function productGroupInteriors
|
|
* @description Mutate the list of interiors on a product and its groups.
|
|
*/
|
|
async productGroupInteriors(
|
|
_,
|
|
{ productId, groupId, uris },
|
|
{ dataSources },
|
|
) {
|
|
await (<ProductAPI>dataSources.productApi).addInteriorsToProductGroup(
|
|
productId,
|
|
groupId,
|
|
uris,
|
|
);
|
|
return (<ProductAPI>dataSources.productApi).getProduct(productId);
|
|
},
|
|
|
|
/**
|
|
* @function productKeywords
|
|
* @description Mutate the keywords for a product
|
|
*/
|
|
async productKeywords(_, { productId, keywordIds }, { dataSources }) {
|
|
await (<KeywordAPI>dataSources.keywordApi).setProductKeywords(
|
|
productId,
|
|
keywordIds,
|
|
);
|
|
return (<ProductAPI>dataSources.productApi).getProduct(productId);
|
|
},
|
|
|
|
/**
|
|
* @function productProportionsWarning
|
|
* @description Mutate the values for proportion warnings on a product
|
|
*/
|
|
async productProportionsWarning(
|
|
_,
|
|
{ productId, proportions },
|
|
{ dataSources },
|
|
) {
|
|
await (<ProductAPI>dataSources.productApi).setProportionsWarning(
|
|
productId,
|
|
proportions,
|
|
);
|
|
return (<ProductAPI>dataSources.productApi).getProduct(productId);
|
|
},
|
|
|
|
/**
|
|
* @function addOwnInteriorToPrintProduct
|
|
* @description Add a uploaded interior image to a product and its groups.
|
|
*/
|
|
async addOwnInteriorToPrintProduct(
|
|
_,
|
|
{ printId, uploadedS3Key },
|
|
{ dataSources },
|
|
): Promise<IdNumberResult> {
|
|
return (<InteriorAPI>dataSources.interiorApi).addOwnUploadToPrintId(
|
|
printId,
|
|
uploadedS3Key,
|
|
);
|
|
},
|
|
|
|
/**
|
|
* @function printProductDefaults
|
|
* @description Add default values to a printproduct, this is used through admin gui.
|
|
*/
|
|
async printProductDefaults(
|
|
_,
|
|
{ printId, widthMm, heightMm, cropX, cropY, border },
|
|
{ dataSources },
|
|
): Promise<PrintProductDefaults> {
|
|
return (<PrintProductDefaultsAPI>(
|
|
dataSources.printProductDefaultsApi
|
|
)).updateDefaults(printId, widthMm, heightMm, cropX, cropY, border);
|
|
},
|
|
|
|
/**
|
|
* @function productWallpaperType
|
|
* @description Set the wallpapertype for a wallpaper product
|
|
*/
|
|
async productWallpaperType(
|
|
_,
|
|
{ productId, wallpaperTypes },
|
|
{ dataSources },
|
|
) {
|
|
await (<ProductAPI>dataSources.productApi).setWallpaperTypes(
|
|
productId,
|
|
wallpaperTypes,
|
|
);
|
|
return (<ProductAPI>dataSources.productApi).getProduct(productId);
|
|
},
|
|
|
|
/**
|
|
* @function updateProductImage
|
|
* @description Set a new image for the product, image is first uploaded to inquiries
|
|
* bucket then moved to photowall-images. When new image is uploaded interiors are
|
|
* generated.
|
|
*/
|
|
async updateProductImage(
|
|
_,
|
|
{ productId, uploadedS3Key, width, height },
|
|
{ dataSources },
|
|
) {
|
|
await moveS3File(
|
|
CONFIG.uploadBucket,
|
|
uploadedS3Key,
|
|
CONFIG.imageBucket,
|
|
`products/${productId}.jpg`,
|
|
);
|
|
await (<ProductAPI>dataSources.productApi).updateProductField(
|
|
productId,
|
|
'width',
|
|
width.toString(),
|
|
);
|
|
await (<ProductAPI>dataSources.productApi).updateProductField(
|
|
productId,
|
|
'height',
|
|
height.toString(),
|
|
);
|
|
|
|
const product = await (<ProductAPI>dataSources.productApi).getProduct(
|
|
productId,
|
|
);
|
|
await (<InteriorsLambdaAPI>(
|
|
dataSources.interiorsLambdaApi
|
|
)).generateNewInteriors(product);
|
|
return 'success';
|
|
},
|
|
|
|
/**
|
|
* @function addRelatedProducts
|
|
* @description Adds a related product to a product, when doing this the related product
|
|
* gets the same relationship back. So products are always related to eachother.
|
|
*/
|
|
async addRelatedProducts(_, { productId, articleNumbers }, { dataSources }) {
|
|
await (<ProductAPI>dataSources.productApi).addRelatedProducts(
|
|
productId,
|
|
articleNumbers,
|
|
);
|
|
return (<ProductAPI>dataSources.productApi).getProduct(productId);
|
|
},
|
|
|
|
/**
|
|
* @function removeRelatedProducts
|
|
* @description remove relation between products
|
|
*/
|
|
async removeRelatedProducts(
|
|
_,
|
|
{ productId, relatedProductIds },
|
|
{ dataSources },
|
|
) {
|
|
await (<ProductAPI>dataSources.productApi).removeRelatedProducts(
|
|
productId,
|
|
relatedProductIds,
|
|
);
|
|
return (<ProductAPI>dataSources.productApi).getProduct(productId);
|
|
},
|
|
|
|
/**
|
|
* @function addCategoriesToProduct
|
|
* @description Add categories to a product
|
|
*/
|
|
async addCategoriesToProduct(_, { productId, categoryIds }, { dataSources }) {
|
|
await (<ProductAPI>dataSources.productApi).addCategoriesToProduct(
|
|
productId,
|
|
categoryIds,
|
|
);
|
|
return (<ProductAPI>dataSources.productApi).getProduct(productId);
|
|
},
|
|
|
|
/**
|
|
* @function removeCategoriesFromProduct
|
|
* @description Remove categories from a product
|
|
*/
|
|
async removeCategoriesFromProduct(
|
|
_,
|
|
{ productId, categoryIds },
|
|
{ dataSources },
|
|
) {
|
|
await (<ProductAPI>dataSources.productApi).removeCategoriesFromProduct(
|
|
productId,
|
|
categoryIds,
|
|
);
|
|
return (<ProductAPI>dataSources.productApi).getProduct(productId);
|
|
},
|
|
|
|
/**
|
|
* @function updateProductComments
|
|
* @description Mutate product comments
|
|
*/
|
|
async updateProductComments(_, { productId, comments }, { dataSources }) {
|
|
await (<ProductAPI>dataSources.productApi).updateComments(
|
|
productId,
|
|
comments,
|
|
);
|
|
return (<ProductAPI>dataSources.productApi).getProduct(productId);
|
|
},
|
|
/**
|
|
* @function massUpdatePublishing
|
|
* @description Mutate the publishing for products
|
|
*/
|
|
async massUpdatePublishing(
|
|
_,
|
|
{ productIds, visible, browsable },
|
|
{ dataSources },
|
|
) {
|
|
await (<ProductAPI>dataSources.productApi).massUpdatePublishing(
|
|
productIds,
|
|
visible,
|
|
browsable,
|
|
);
|
|
return {
|
|
value: 'OK',
|
|
};
|
|
},
|
|
/**
|
|
* @function massUpdateBlacklisting
|
|
* @description Mutate the blacklisting for products
|
|
*/
|
|
async massUpdateBlacklisting(_, { productIds, markets }, { dataSources }) {
|
|
await (<ProductAPI>dataSources.productApi).massUpdateBlacklisting(
|
|
productIds,
|
|
markets,
|
|
);
|
|
return {
|
|
value: 'OK',
|
|
};
|
|
},
|
|
/**
|
|
* @function massUpdatePublishing
|
|
* @description Mutate the Publishing for products
|
|
*/
|
|
async massUpdatePublishingDate(_, { productIds, date }, { dataSources }) {
|
|
await (<ProductAPI>dataSources.productApi).massUpdatePublishingDate(
|
|
productIds,
|
|
date,
|
|
);
|
|
return {
|
|
value: 'OK',
|
|
};
|
|
},
|
|
};
|