Files
graphql-api-js/src/datasources/product-api.ts
T
b2fa2f7d8a removing batch info from the url (#116)
* removing batch info from the url

* small CR changes

Co-authored-by: Sreeraj Rajendran Nair <sreeraj.rajendran@photowall.com>
2022-09-07 11:06:07 +02:00

1032 lines
31 KiB
TypeScript

import { BaseSQLDataSource } from './BaseSQLDataSource';
import { camelcase } from 'stringcase';
import slug from 'slug';
import * as sql from './sql';
import {
Product,
ProductGroup,
PrintProduct,
ProductType,
ProductBlacklist,
ProductWallpaperType,
ProductFields,
Orientation,
ProductsFilterInput,
ProductInfoInput,
ProductBlacklistMarketInput,
ProductMaterials,
ProductSizeTypes,
ProductProportionsInput,
Batch,
Copyright,
} from '../types/product-types';
import { Maybe } from '../types/types';
import {
booleanStringField,
isInt,
nullOrNumber,
roundOrDefaultTo,
} from './utils';
import { UserInputError } from 'apollo-server';
import { sendPathChangedCmd } from '../bernard/client';
import { TextsAPI } from './texts-api';
import { PrintProductDefaultsAPI } from './printproduct-defaults-api';
const MINUTE = 60;
export class ProductAPI extends BaseSQLDataSource {
textsApi: TextsAPI;
printProductDefaultsApi: PrintProductDefaultsAPI;
constructor(config, textsApi, printProductDefaultsApi) {
super(config);
this.textsApi = textsApi;
this.printProductDefaultsApi = printProductDefaultsApi;
}
// async getMaterial(printId: number): Promise<any> {
// TODO: discuss if this is still needed. Anneli wants to remove the choice of standard, premium and self-adhesive
// SELECT materialid, material, (price / 100::float) as price FROM "product-materials"
// }
getWallpaperTypes(row: any): Array<ProductWallpaperType> {
if (!row.wallpapertypes) {
return [];
}
return row.wallpapertypes.map((t: number) => ProductWallpaperType[t]);
}
getProductTypes(row: any): Array<ProductType> {
if (!row.types) {
return [];
}
return row.types.map((type: any) => {
if (type === 'typeillustration') {
return ProductType[ProductType.ILLUSTRATION];
}
if (type === 'typephotography') {
return ProductType[ProductType.PHOTO];
}
return ProductType[ProductType.UNKNOWN];
});
}
getBlacklisting(row: any): Array<ProductBlacklist> {
if (!row.blacklisting) {
return [];
}
return row.blacklisting.map((bl: any) => {
bl.group = ProductGroup[bl.groupId];
return bl;
});
}
getPrintProducts(row: any): Array<PrintProduct> {
if (!row.printproducts) {
return [];
}
return row.printproducts.map((pp: any) => {
pp.group = ProductGroup[pp.groupId];
pp.id = pp.printId;
return pp;
});
}
getProductFields(row: any): ProductFields {
if (!row.fields) {
return null; // 53460 for example
}
// Convert to camelCase first
const fields = Object.keys(row.fields).reduce((mem, key) => {
mem[camelcase(key)] = row.fields[key];
return mem;
}, {});
fields['artNo'] = fields['artNo'] ?? ''; // some actually exists without artNo
fields['name'] = fields['name'] ?? ''; // some actually exists without name
fields['copyright'] = fields['copyright'] ?? '';
fields['comments'] = fields['comments'] ?? '';
fields['batch'] = fields['batch'] ?? '';
fields['height'] = nullOrNumber(fields['height'] ?? null);
fields['width'] = nullOrNumber(fields['width'] ?? null);
fields['printFileWidth'] = nullOrNumber(fields['printFileWidth'] ?? null);
fields['printFileHeight'] = nullOrNumber(fields['printFileHeight'] ?? null);
fields['printFileDpi'] = nullOrNumber(fields['printFileDpi'] ?? null);
fields['marginWidthMax'] = nullOrNumber(fields['marginWidthMax'] ?? null);
fields['marginWidthMin'] = nullOrNumber(fields['marginWidthMin'] ?? null);
fields['marginHeightMax'] = nullOrNumber(fields['marginHeightMax'] ?? null);
fields['marginHeightMin'] = nullOrNumber(fields['marginHeightMin'] ?? null);
fields['focusXpoint'] = nullOrNumber(fields['focusXpoint'] ?? null);
fields['focusYpoint'] = nullOrNumber(fields['focusYpoint'] ?? null);
// Focus points values are in %.
// Should be rounded to two decimals. Is sometimes missing in DB.
// If missing should be treated as 50%, 50%
fields['focusXpoint2'] = roundOrDefaultTo(fields['focusXpoint2'], 2, 50);
fields['focusYpoint2'] = roundOrDefaultTo(fields['focusYpoint2'], 2, 50);
fields['photowallResolution'] = nullOrNumber(
fields['photowallResolution'] ?? null,
);
fields['canvasResolution'] = nullOrNumber(
fields['canvasResolution'] ?? null,
);
fields['wallpaperResolution'] = nullOrNumber(
fields['wallpaperResolution'] ?? null,
);
fields['proportionsWarning'] = booleanStringField(
fields['proportionsWarning'] ?? null,
);
fields['imageResolution'] = nullOrNumber(fields['imageResolution'] ?? null);
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.publishingDate = row.publishing_date;
row.blacklisting = this.getBlacklisting(row);
row.printProducts = this.getPrintProducts(row);
row.type = this.getProductTypes(row);
row.wallpaperTypes = this.getWallpaperTypes(row);
row.fields = this.getProductFields(row);
row.designerId = row.designerid;
row.orientation = this.getOrientation(row.fields);
return row;
}
async getProductsTotal(input: ProductsFilterInput): Promise<number> {
const query = sql.productsTotal(input);
const res = await this.cachedRaw(query)
.cache(MINUTE * 5)
.then((data) => data.rows);
return res[0]['count'] as number; // Optimize later to return Promise
}
async getProducts(input: ProductsFilterInput): Promise<Array<Product>> {
const query = sql.products(input);
return this.knex
.raw(query)
.then((data) => data.rows.map((row) => this.createProductFromRow(row)));
}
async getProduct(id: number): Promise<Maybe<Product>> {
const query = sql.product(id);
const res = await this.knex.raw(query).then((data) =>
data.rows.map((row) => {
const prod = this.createProductFromRow(row);
return prod;
}),
);
return res.find(Boolean);
}
async getProductByPath(path: string): Promise<Maybe<Product>> {
const query = sql.productByPath(path);
const res = await this.knex.raw(query).then((data) =>
data.rows.map((row) => {
const prod = this.createProductFromRow(row);
return prod;
}),
);
return res.find(Boolean);
}
async getDesignerProducts(designerId: number): Promise<Array<Product>> {
const query = sql.designerProducts();
return this.knex
.raw(query, designerId)
.then((data) => data.rows.map((row) => this.createProductFromRow(row)));
}
async getCategoryProducts(categoryId: number): Promise<Array<Product>> {
const query = sql.categoryProducts(categoryId);
return this.knex
.raw(query)
.then((data) => data.rows.map((row) => this.createProductFromRow(row)));
}
async getCategoryWithSubCategoriesProducts(
categoryId: number,
): Promise<Array<Product>> {
const query = sql.categoryWithAllSubCategoriesProducts(categoryId);
return this.knex
.raw(query)
.then((data) => data.rows.map((row) => this.createProductFromRow(row)));
}
async getKeywordProducts(keywordId: number): Promise<Array<Product>> {
const query = sql.keywordProducts(keywordId);
return this.knex
.raw(query)
.then((data) => data.rows.map((row) => this.createProductFromRow(row)));
}
async searchByName(name: string): Promise<Array<Product>> {
const query = sql.searchByFieldValue(2); // name
return this.knex
.raw(query, name)
.then((data) => data.rows.map((row) => this.createProductFromRow(row)));
}
async searchByArtNo(name: string): Promise<Array<Product>> {
const query = sql.searchByFieldValue(1); // artNo
return this.knex
.raw(query, name)
.then((data) => data.rows.map((row) => this.createProductFromRow(row)));
}
async searchByCompleteProductId(q: string): Promise<Maybe<Product>> {
const queryAsNumber = Number(q.replaceAll('%', ''));
if (!queryAsNumber) {
return;
}
return this.getProduct(queryAsNumber);
}
async searchByBatch(name: string): Promise<Array<Batch>> {
return this.searchByFieldValue<Batch>(name, 36, 'batch');
}
async searchByCopyright(name: string): Promise<Array<Copyright>> {
return this.searchByFieldValue<Copyright>(name, 25, 'copyright');
}
async searchByFieldValue<T>(
name: string,
fieldNo: number,
fieldKey: string,
): Promise<Array<T>> {
const query = sql.searchByFieldValue(fieldNo);
const rows = await this.knex
.raw(query, name)
.then((data) => data.rows.map((row) => this.createProductFromRow(row)));
const result = {};
rows.forEach((p) => {
if (!result[p.fields[fieldKey]]) {
result[p.fields[fieldKey]] = [];
}
result[p.fields[fieldKey]].push(p);
});
return Object.keys(result).map((key) => {
return {
name: key,
products: result[key],
} as unknown as T;
});
}
async searchByReferences(name: string): Promise<Array<Product>> {
const query = sql.referenceProducts(); // ref1, ref2, ref3
return this.knex
.raw(query, [name, name, name])
.then((data) => data.rows.map((row) => this.createProductFromRow(row)));
}
async getRelatedProducts(id: number): Promise<Array<Product>> {
const ids = await this.knex
.raw(
`SELECT productid2 FROM "product-products_products" WHERE productid1 = ${id}`,
)
.then((data) => data.rows.map((r) => r.productid2));
return ids.map(async (id) => this.getProduct(id));
}
//
// mutations below
async updateProductField(
productId: number,
field: string,
value: string,
): Promise<void> {
return this.knex.raw(
/* sql */ `
INSERT INTO "product-products_fields" (productid, fieldid, value, inserted, updated)
VALUES (?, (SELECT fieldid FROM "product-fields" WHERE field = ?), ?, now(), now())
ON CONFLICT (productid, fieldid)
DO UPDATE SET "value" = ?`,
[productId, field, value, value],
);
}
/**
* @function getUniqueProductPath
* @description Returns a unique product path, ex if foggy-forest is take it will return foggy-forest-1
* foggy-forest-2 and so on.
*/
async getUniqueProductPath(
path: string,
productId?: number,
): Promise<string> {
// If productId is set then check if path is already
// set on that product.
if (productId) {
const productPath = await this.knex.raw(
`SELECT path FROM "product-products" WHERE productid = ?`,
[productId],
);
if (productPath.rows[0].path === path) {
return path;
}
}
// Check for unique-path
// Get paths from product-products
const basePath = slug(path, { remove: /(\d+)(?!.*\d)$/g }); // Remove last number adf-1-asdf-123 = adf-1-asdf-
const pathResponse = await this.knex.raw(
`SELECT path FROM "product-products" WHERE path LIKE ?`,
[basePath + '%'],
);
const paths = pathResponse.rows.map((item) => item.path);
// Evaluate new pathname
let newPath = '';
// If the exact match of the slug is not listed
// in the paths queried from db. Then use the
// tested slug.
if (!paths.includes(path)) {
newPath = path;
} else {
// Try to find any numbers in the pathname
const numbers = paths
.map((p) => {
const numStr = p.replace(basePath + '-', '');
return isInt(numStr) ? parseInt(numStr) : null;
})
.filter(Boolean); // Remove falsy
if (numbers.length === 0) {
// No numbers found, add 1.
newPath = path + '-1';
} else {
// Get highest number + 1
const next = Math.max(...numbers) + 1;
newPath = basePath + '-' + next;
}
}
return newPath;
}
/**
* @function addProduct
* @description Adds a new product to system, will use name to
* set a unique path, artNo is the productId with e infront.
*/
async addProduct(
name: string,
batch: string,
designerId: number,
): Promise<number> {
const fixedBatch = !!batch ? batch : '';
const productPath = slug(name);
const safePath = await this.getUniqueProductPath(productPath, null);
const idres = await this.knex
.table('product-products')
.insert({
path: slug(safePath),
browsable: 0,
visible: 0,
designerid: designerId,
})
.returning('productid');
const id = idres[0].productid;
this.updateProductField(id, 'artNo', `e${id}`);
await this.updateProductField(id, 'name', name);
await this.updateProductField(id, 'batch', fixedBatch);
return id;
}
/**
* @function updateProductInfo
* @description Update base info for a product
*/
async updateProductInfo(
productId: number,
info: ProductInfoInput,
): Promise<any[]> {
const promises = [];
// Check path and insert new unique path if taken
const uniquePath = await this.getUniqueProductPath(info.path, productId);
const oldProd = await this.getProduct(productId);
if (oldProd.path !== uniquePath) {
const promise = sendPathChangedCmd(oldProd.path, uniquePath);
promises.push(promise);
}
if (oldProd.fields.name !== info.name) {
const promise = this.textsApi.translateProductName(
info.name,
oldProd.path,
uniquePath,
);
promises.push(promise);
}
const pd = info.publishingDate;
const publishingDate = new Date(
pd.getFullYear(),
pd.getMonth(),
pd.getDate(),
0,
0,
0,
);
promises.push(
this.knex
.table('product-products')
.update({
path: uniquePath,
browsable: info.browsable ? 1 : 0,
visible: info.visible ? 1 : 0,
designerid: info.designerId ?? null,
publishingDate: publishingDate,
ref1: info.ref1,
ref2: info.ref2,
ref3: info.ref3,
})
.where({ productid: productId }),
this.updateProductField(productId, 'artNo', info.artNo),
this.updateProductField(productId, 'name', info.name),
this.updateProductField(productId, 'batch', info.batch),
this.updateProductField(productId, 'copyright', info.copyright),
this.updateProductField(
productId,
'print_file_width',
info.printFileWidth.toString(),
),
this.updateProductField(
productId,
'print_file_height',
info.printFileHeight.toString(),
),
this.updateProductField(
productId,
'print_file_dpi',
info.printFileDpi.toString(),
),
);
return Promise.all(promises);
}
/**
* @function productPublishing
* @description updates a products publishing
*/
async productPublishing(productId: number, date: Date) {
return this.knex
.table('product-products')
.update({
publishing_date: date,
})
.where('productid', productId);
}
/**
* @function massUpdatePublishingDate
* @description Mass update publishing date
*/
async massUpdatePublishingDate(productIds: Array<number>, date: Date) {
return this.knex
.table('product-products')
.update({
publishing_date: date,
})
.whereIn('productid', productIds);
}
/**
* @function massUpdatePublishing
* @description Mass update visible and browsable
*/
async massUpdatePublishing(
productIds: Array<number>,
visible: boolean,
browsable: boolean,
) {
return this.knex
.table('product-products')
.update({
browsable: browsable ? 1 : 0,
visible: visible ? 1 : 0,
})
.whereIn('productid', productIds);
}
/**
* @function massUpdateBlacklisting
* @description Mass update blacklisting
*/
async massUpdateBlacklisting(
productIds: Array<number>,
markets: Array<ProductBlacklistMarketInput>,
) {
const promises = productIds.map((pId) =>
this.updateBlacklisting(pId, markets),
);
return Promise.all(promises);
}
/**
* @function updateFocusPoint
* @description Updates the focus point on a motif
*/
async updateFocusPoint(
productId: number,
focusXpoint2: number,
focusYpoint2: number,
): Promise<Promise<void>[]> {
const promises = [];
promises.push(
this.updateProductField(
productId,
'focusXpoint2',
focusXpoint2.toString(),
),
this.updateProductField(
productId,
'focusYpoint2',
focusYpoint2.toString(),
),
);
return Promise.all(promises);
}
async updateComments(productId: number, comments: string): Promise<void> {
return this.updateProductField(productId, 'comments', comments);
}
/**
* @function updateBlacklisting
* @description Updates the blacklisted countries and sections
*/
async updateBlacklisting(
productId: number,
markets: Array<ProductBlacklistMarketInput>,
) {
const sql = /* sql */ `
INSERT INTO product_blacklist (product_id, group_id, market_id, created_at, updated_at)
VALUES (?, ?, ?, now(), now())
ON CONFLICT (product_id, group_id, market_id)
DO NOTHING`;
await this.knex.raw(
/* sql */ `
DELETE FROM product_blacklist WHERE product_id = ?
`,
[productId],
);
const insertsPromises = [];
markets.forEach((market) => {
market.groupIds.forEach((groupId) => {
const query = this.knex.raw(sql, [productId, groupId, market.marketId]);
insertsPromises.push(query);
});
});
return Promise.all(insertsPromises);
}
/**
* @function setWallpaperTypes
* @description Sets what wallpaper type a product is sold as, meaning the sections on site
* like wallpaper or design-wallpaper
*/
async setWallpaperTypes(
productId: number,
wallpaperTypes: Array<ProductWallpaperType>,
): Promise<any[]> {
await this.knex('product_wallpapertypes')
.where('product_id', productId)
.del();
const promises = wallpaperTypes.map((wpt) => {
const typeId = ProductWallpaperType[wpt];
return this.knex('product_wallpapertypes').insert({
product_id: productId,
wallpapertype_id: typeId,
});
});
return Promise.all(promises);
}
/**
* @function updateGroups
* @description Updates the groups that a product is sold in, like wallpaper, canvas or
* poster. Note that group 3 is repeating wallpapers.
*/
async updateGroups(productId: number, groupIds: number[]): Promise<any[]> {
// Fetch the current state
const allPrintProducts = await this.knex.raw(
`SELECT printid, groupid FROM "product-printproducts" WHERE productid = ?`,
[productId],
);
const currentGroups = allPrintProducts.rows.map((item) => item.groupid);
// What we should add
const shouldAdd = groupIds.filter((group) => {
return !currentGroups.includes(group);
});
// What we should delete
const shouldDelete = allPrintProducts.rows.filter((item) => {
return !groupIds.includes(item.groupid);
});
// DELETE
if (shouldDelete.length > 0) {
// Delete from product-printproducts_materials
const printProductIds = shouldDelete.map((item) => item.printid);
// Delete from materials first, must be in sequence
// since we have constraint in materials to printproducts
await this.knex
.from('product-printproducts_materials')
.whereIn('printid', printProductIds)
.del();
// Delete from printproducts_defaults, same here run
// this first because of constraint.
await this.knex
.from('printproducts_defaults')
.whereIn('printId', printProductIds)
.del();
// Delete from product-printproducts
await this.knex
.from('product-printproducts')
.whereIn('printid', printProductIds)
.del();
}
// INSERT
if (shouldAdd.length > 0) {
const insertionProducts = [];
// Insert in product-printproducts
// TODO: Depracate typeid, its not used to check repeating
const insertQuery = `INSERT INTO "product-printproducts" (productid, groupid, typeid) VALUES (?, ?, ?)`;
shouldAdd.forEach((groupId) => {
insertionProducts.push(
this.knex.raw(insertQuery, [
productId,
groupId,
groupId === ProductGroup.WALLPAPER
? ProductSizeTypes.TILING
: ProductSizeTypes.SCALING,
]),
);
});
// Run parallell
await Promise.all(insertionProducts);
// Fetch the new printids to be able to insert new materials
const newPrintIds = await this.knex
.from('product-printproducts')
.select('printid', 'groupid')
.where('productid', productId)
.whereIn('groupid', shouldAdd);
// Add printproduct-defaults for all new printIds
const insertDefaults = [];
newPrintIds.forEach(({ printid, groupid }) => {
// Possible to add for canvas and framed-print later
if (groupid === ProductGroup.POSTER) {
// Add printproduct defaults if poster
insertDefaults.push(
this.printProductDefaultsApi.insertDefaults(printid),
);
}
});
// Run parallell
await Promise.all(insertDefaults);
// Insert printIds into product-printproducts_materials
const insertMaterials = [];
const insertMaterialQuery = `INSERT INTO "product-printproducts_materials" (printid, materialid) VALUES (?, ?)`;
newPrintIds.forEach(({ printid, groupid }) => {
switch (groupid) {
case ProductGroup.PHOTO_WALLPAPER:
case ProductGroup.WALLPAPER:
insertMaterials.push(
this.knex.raw(insertMaterialQuery, [
printid,
ProductMaterials.STANDARD_WALLPAPER,
]),
);
insertMaterials.push(
this.knex.raw(insertMaterialQuery, [
printid,
ProductMaterials.SELF_ADHESIVE_WALLPAPER,
]),
);
insertMaterials.push(
this.knex.raw(insertMaterialQuery, [
printid,
ProductMaterials.PREMIUM_WALLPAPER,
]),
);
break;
case ProductGroup.CANVAS:
insertMaterials.push(
this.knex.raw(insertMaterialQuery, [
printid,
ProductMaterials.STANDARD_CANVAS,
]),
);
break;
case ProductGroup.POSTER:
insertMaterials.push(
this.knex.raw(insertMaterialQuery, [
printid,
ProductMaterials.PREMIUM_POSTER,
]),
);
break;
case ProductGroup.FRAMED_PRINT:
insertMaterials.push(
this.knex.raw(insertMaterialQuery, [
printid,
ProductMaterials.PREMIUM_FRAMED_PRINT,
]),
);
break;
}
});
return Promise.all(insertMaterials);
}
}
/**
* @func productGroupInteriors
* @description Sets interiors to a product and product group
*
* @param productId The product id for the targeted product
* @param groupId The targeted group id, if product does not have group it will be created
* @param uris The uris for the interiors, common for rooms and own uploades
*/
async addInteriorsToProductGroup(
productId: number,
groupId: number,
uris: string[],
): Promise<Promise<void>[]> {
// Create object to hold data as we go along all the db calls
// This object needs to support new own uploaded images, these should have
// some other folder prefix, like /interiors-buffer/. These will
// be inserted into DB first and then the image on s3 will be
// moved to /interior-images/ with the correct id.
const insertRegistry = uris.map((uri, index) => {
let id = null;
let roomName = null;
if (uri.indexOf('/') !== 0) {
uri = '/' + uri;
}
if (uri.indexOf('/interiors/') > -1) {
const spl = uri.split('/');
roomName = `${spl[5].split('.')[0]}_${spl[4]}_${spl[3]}`;
}
if (uri.indexOf('/interior-images/') > -1) {
id = parseInt(uri.split('/')[2].split('.')[0]);
}
return {
id: id,
uri: uri,
roomName: roomName || null,
roomId: null,
position: index,
};
});
// Get all the printproducts from the productId defined.
// This is needed for the insert and for the check if a product
// has the defined group or not.
const allPrintProducts = await this.knex.raw(
`SELECT printid, groupid FROM "product-printproducts" WHERE productid = ?`,
[productId],
);
// Predefine the print id needed for insert.
let insertPrintId;
// Map out the group ids.
const currentGroupIds = allPrintProducts.rows.map(({ groupid }) => groupid);
// This product does not have group with groupId
if (!currentGroupIds.includes(groupId)) {
await this.updateGroups(productId, currentGroupIds.concat([groupId]));
// Get the new printId
const printIdsResponse = await this.knex.raw(
`SELECT printid FROM "product-printproducts" WHERE productid = ? AND groupid = ?`,
[productId, groupId],
);
insertPrintId = printIdsResponse.rows.map(({ printid }) => printid)[0];
} else {
// This product had the targeted groupid, then we use
// the previous query for printId to save some DB calls.
insertPrintId = allPrintProducts.rows
.map(({ printid, groupid }) => {
if (groupid === groupId) {
return printid;
}
})
.filter(Boolean)[0]; // Remove falsy
}
// Delete all prior interiors
await this.knex.raw(`DELETE FROM interiors WHERE print_id = ?`, [
insertPrintId,
]);
// Insert room-ids, printids and position into "interiors" table.
const insertPromises = [];
insertRegistry.forEach((item) => {
if (item.roomName) {
insertPromises.push(
this.knex.raw(
`INSERT INTO interiors (print_id, room_id, position) VALUES (?, (SELECT id FROM rooms WHERE name = ?), ?)`,
[insertPrintId, item.roomName, item.position],
),
);
} else {
insertPromises.push(
this.knex.raw(
`INSERT INTO interiors (id, print_id, position) VALUES (?, ?, ?)`,
[item.id, insertPrintId, item.position],
),
);
}
});
return Promise.all(insertPromises);
}
/**
* @function setProportionsWarning
* @description Sets propotion warning-values to a product
*/
async setProportionsWarning(
productId: number,
proportions: ProductProportionsInput,
): Promise<Promise<void>[]> {
const promises = [];
promises.push(
this.updateProductField(
productId,
'proportions_warning',
proportions.proportionsWarning ? '1' : '0',
),
this.updateProductField(
productId,
'margin_width_max',
proportions.marginWidthMax.toString(),
),
this.updateProductField(
productId,
'margin_width_min',
proportions.marginWidthMin.toString(),
),
this.updateProductField(
productId,
'margin_height_max',
proportions.marginHeightMax.toString(),
),
this.updateProductField(
productId,
'margin_height_min',
proportions.marginHeightMin.toString(),
),
);
return Promise.all(promises);
}
/**
* @function addRelatedProducts
* @description Adds a related product to a product, when settings a related product
* the target product will get the same related connection.
*/
async addRelatedProducts(
productId: number,
articleNumbers: Array<string>,
): Promise<Promise<void>[]> {
const whereClause = articleNumbers.map((a) => `'${a}'`).join(',');
const query = /* sql */ `
SELECT pp.productid, ppf.value FROM "product-products" pp
LEFT JOIN "product-products_fields" ppf
ON ppf.productid = pp.productid AND ppf.fieldid = 1
WHERE ppf.value IN (${whereClause});
`;
const res = await this.knex.raw(query).then((data) => data.rows);
if (!res.length) {
throw new UserInputError('No articles found');
}
const fieldsToInsert = res.map((item) => ({
productid1: productId,
productid2: item.productid,
}));
// Add this productId as related to its friends
res.forEach((item) => {
fieldsToInsert.push({
productid1: item.productid,
productid2: productId,
});
});
return this.knex.raw(`? ON CONFLICT (productid1, productid2) DO NOTHING;`, [
this.knex('product-products_products').insert(fieldsToInsert),
]);
}
/**
* @function removeRelatedProducts
* @description Removes variant products from productid as well as the sent in
* productid from its variants
*/
async removeRelatedProducts(
productId,
relatedProductIds,
): Promise<Promise<void>[]> {
// remove connection between the friends id and this produdId
const promises = relatedProductIds.map((pId) => {
return this.knex('product-products_products')
.where('productid1', pId)
.where('productid2', productId)
.del();
});
// remove connection to friends for this productsid
const query = this.knex('product-products_products')
.where('productid1', productId)
.whereIn('productid2', relatedProductIds)
.del();
promises.push(query);
return Promise.all(promises);
}
/**
* @function addCategoriesToProduct
* @description Adds a list of categories to a product
*/
async addCategoriesToProduct(
productId,
categoryIds,
): Promise<Promise<void>[]> {
const promises = categoryIds.map((categoryId) =>
this.knex('product_category').insert({
product_id: productId,
category_id: categoryId,
}),
);
return Promise.all(promises);
}
/**
* @function removeCategoriesFromProduct
* @description Removes list of categories from a product
*/
async removeCategoriesFromProduct(
productId,
categoryIds,
): Promise<Promise<void>[]> {
const promises = categoryIds.map((categoryId) =>
this.knex('product_category')
.where({
product_id: productId,
category_id: categoryId,
})
.del(),
);
return Promise.all(promises);
}
}