Files
graphql-api-js/src/resolvers/products-resolver.ts
T

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',
};
},
};