* removing batch info from the url * small CR changes Co-authored-by: Sreeraj Rajendran Nair <sreeraj.rajendran@photowall.com>
1032 lines
31 KiB
TypeScript
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);
|
|
}
|
|
}
|