Add product search (#64)

This commit is contained in:
Niklas Fondberg
2021-10-11 11:20:48 +02:00
committed by GitHub
parent 8b03ab8246
commit 08149c4a87
10 changed files with 168 additions and 4 deletions
+1
View File
@@ -19,6 +19,7 @@ services:
- AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY}
- AWS_ACCESS_KEY_ID=${AWS_ACCESS_KEY_ID}
- AWS_DEFAULT_REGION=${AWS_DEFAULT_REGION}
- NODE_ENV=development
networks:
default:
+7
View File
@@ -32,6 +32,13 @@ WHERE product_category.product_id = ?
return this.knex.select('*').from('v_categorytree').cache(MINUTE);
}
async searchCategories(name: string): Promise<Array<Category>> {
return this.knex
.select('*')
.from('v_categorytree')
.whereRaw(`LOWER(name) LIKE ?`, [`${name}`]);
}
async getCategoryById(id: number): Promise<Category> {
return this.knex
.select('*')
+15
View File
@@ -23,6 +23,21 @@ export class DesignerAPI extends BaseSQLDataSource {
});
}
async searchDesigners(name: string): Promise<Array<Designer>> {
return this.knex
.select('*')
.from('designers')
.whereRaw(`LOWER(name) LIKE ?`, [`${name}`])
.then((rows) => {
return rows.map((row) => {
return {
...row,
id: row.designerid,
};
});
});
}
async getDesigners(limit: Maybe<number>): Promise<Array<Designer>> {
limit = limit ?? 5000;
return this.knex
+8
View File
@@ -55,6 +55,14 @@ ORDER BY keywords.value
.then((row) => this.getType(row));
}
async searchKeywords(name: string): Promise<Array<Keyword>> {
return this.knex
.select('*')
.from('keywords')
.leftJoin('keyword_type', 'keyword_type.keyword_id', 'keywords.id')
.whereRaw(`LOWER(value) LIKE ?`, [`${name}`]);
}
async setProductKeywords(
productId: number,
keywordIds: Array<number>,
+48
View File
@@ -17,6 +17,7 @@ import {
ProductMaterials,
ProductSizeTypes,
ProductProportionsInput,
Batch,
} from '../types/product-types';
import { Maybe } from '../types/types';
import { booleanStringField, nullOrNumber, roundOrDefaultTo } from './utils';
@@ -179,6 +180,14 @@ export class ProductAPI extends BaseSQLDataSource {
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);
@@ -195,6 +204,45 @@ export class ProductAPI extends BaseSQLDataSource {
.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 searchByBatch(name: string): Promise<Array<Batch>> {
const query = sql.searchByFieldValue(36); // batch
const batchRes = await this.knex
.raw(query, name)
.then((data) => data.rows.map((row) => this.createProductFromRow(row)));
const batches = {};
batchRes.forEach((p) => {
if (!batches[p.fields.batch]) {
batches[p.fields.batch] = [];
}
batches[p.fields.batch].push(p);
});
return Object.keys(batches).map((key) => {
return <Batch>{
name: key,
products: batches[key],
};
});
}
async getRelatedProducts(id: number): Promise<Array<Product>> {
const ids = await this.knex
.raw(
+22 -3
View File
@@ -77,17 +77,18 @@ FROM "product-products" products
export function products(input: ProductsFilterInput) {
const limit = input.pagination.limit ?? 100;
const filter = input.filter;
const filter = input.filter ?? null;
return (
baseQuery +
/* sql */ `
${
filter.visible != null
filter?.visible != null
? /* sql */ `
WHERE products.visible = ${filter?.visible ? '1' : '0'}
AND products.browsable = ${filter?.browsable ? '1' : '0'}`
: ``
}
ORDER BY products.inserted DESC
LIMIT ${limit}
`
);
@@ -98,7 +99,7 @@ export function productsTotal(input: ProductsFilterInput) {
return /*sql */ `
SELECT count(*) FROM "product-products" products
${
filter.visible != null
filter?.visible != null
? /* sql */ `
WHERE products.visible = ${filter?.visible ? '1' : '0'}
AND products.browsable = ${filter?.browsable ? '1' : '0'}`
@@ -125,6 +126,15 @@ export function categoryProducts(categoryId: number) {
);
}
export function designerProducts() {
return (
baseQuery +
/* sql */ `
WHERE products.designerid = ?;
`
);
}
export function keywordProducts(keywordId: number) {
return (
baseQuery +
@@ -133,3 +143,12 @@ export function keywordProducts(keywordId: number) {
`
);
}
export function searchByFieldValue(fieldid: number) {
return (
baseQuery +
/* sql */ `
WHERE products.productid IN (SELECT productid FROM "product-products_fields" WHERE fieldid = ${fieldid} AND LOWER(value) LIKE ? LIMIT 5000); -- limit to 5000 results
`
);
}
+4
View File
@@ -1,11 +1,15 @@
import { DesignerAPI } from '../datasources/designer-api';
import { OrderAPI } from '../datasources/order-api';
import { ProductAPI } from '../datasources/product-api';
import { GeneralInput } from '../types/types';
const Designer = {
async orderRows({ id }, input: GeneralInput, { dataSources }) {
return (<OrderAPI>dataSources.orderApi).getOrderRowsByDesignerId(id, input);
},
async products({ id }, _args, { dataSources }) {
return (<ProductAPI>dataSources.productApi).getDesignerProducts(id);
},
};
async function getDesigners(_, { limit }, { dataSources }) {
+27
View File
@@ -3,6 +3,7 @@ import {
Product,
ProductListResult,
ProductsFilterInput,
ProductsSearchResult,
} from '../types/product-types';
import * as CONFIG from '../config';
import { CategoryAPI } from '../datasources/category-api';
@@ -14,6 +15,7 @@ import { InteriorAPI } from '../datasources/interior-api';
import { InteriorsLambdaAPI } from '../datasources/interiors-lambda-api';
import { IdNumberResult } from '../types/types';
import { moveS3File } from '../s3';
import { DesignerAPI } from '../datasources/designer-api';
const ProductBlacklist = {
async market(parent, _args, { dataSources }) {
@@ -66,6 +68,30 @@ async function getProductsResult(
};
}
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 prods = Promise.all([
prodApi.searchByName(q),
prodApi.searchByArtNo(q),
]).then((d) => d.flat());
return {
q: q,
products: prods,
batches: prodApi.searchByBatch(q),
keywords: keywApi.searchKeywords(q),
categories: catApi.searchCategories(q),
designers: designerApi.searchDesigners(q),
};
}
async function getProduct(_, { id }, { dataSources }): Promise<Product> {
return (<ProductAPI>dataSources.productApi).getProduct(id);
}
@@ -79,6 +105,7 @@ export const productTypeDefs = {
export const productQueryTypeDefs = {
products: getProductsResult,
product: getProduct,
productsSearch: getProductsSearchResult,
};
///////////////////
+20 -1
View File
@@ -21,9 +21,10 @@ type Query {
order(id: ID!): Order
products(
pagination: PaginationInput!
filter: ProductsFilterInput!
filter: ProductsFilterInput
): ProductsResult
product(id: Int!): Product
productsSearch(q: String!): ProductsSearchResult
categories: [Category]
category(id: Int!): Category
keywords: [Keyword]
@@ -82,6 +83,18 @@ type ProductsResult {
items: [Product]
}
type ProductsSearchResult {
q: String!
"""
products contains matches for `name` and `artNo`
"""
products: [Product]
batches: [Batch]
keywords: [Keyword]
categories: [Category]
designers: [Designer]
}
input InteriorsFilterInput {
productId: Int!
}
@@ -245,6 +258,11 @@ type Category {
products: [Product]
}
type Batch {
name: String!
products: [Product]
}
type Product {
id: ID!
stockid: Int
@@ -378,6 +396,7 @@ type Designer {
name: String
path: String
orderRows(pagination: PaginationInput!, filter: DateFilterInput): [OrderRow]
products: [Product]
}
enum KeywordType {
+16
View File
@@ -1,3 +1,6 @@
import { Category } from './category-types';
import { Designer } from './designer-types';
import { Keyword } from './keyword-types';
import { Maybe, PaginationInput, PaginationResult } from './types';
// Mutation inputs
@@ -45,6 +48,14 @@ export interface ProductsFilter {
browsable?: boolean;
}
export interface ProductsSearchResult {
q: string;
products: Promise<Array<Product>>;
batches: Promise<Array<Batch>>;
keywords: Promise<Array<Keyword>>;
categories: Promise<Array<Category>>;
designers: Promise<Array<Designer>>;
}
export interface ProductListResult {
pagination: PaginationResult;
items: Array<Product> | Promise<Array<Product>>;
@@ -112,6 +123,11 @@ export interface PrintProduct {
updated?: Date;
}
export interface Batch {
name: string;
products: Promise<Array<Product>>;
}
export interface Product {
id: number;
stockid: number;