From 267bc4f1c956d7f3e3fbf12dc4341ed7ea5e15ba Mon Sep 17 00:00:00 2001 From: Niklas Fondberg Date: Wed, 7 Jul 2021 16:16:58 +0200 Subject: [PATCH] Add keywords and product types --- queries.graphql | 47 +++++++++++++++++- src/datasources/keyword-api.ts | 75 +++++++++++++++++++++++++++++ src/datasources/product-api.ts | 53 ++++++++++---------- src/datasources/sql/products-sql.ts | 16 +++++- src/index.ts | 3 ++ src/resolvers/index.ts | 3 ++ src/resolvers/keywords-resolver.ts | 24 +++++++++ src/resolvers/products-resolver.ts | 5 ++ src/schema.ts | 26 ++++++++++ src/types/keyword-types.ts | 10 ++++ src/types/product-types.ts | 53 +++++++++++--------- 11 files changed, 262 insertions(+), 53 deletions(-) create mode 100644 src/datasources/keyword-api.ts create mode 100644 src/resolvers/keywords-resolver.ts create mode 100644 src/types/keyword-types.ts diff --git a/queries.graphql b/queries.graphql index e3c85e5..7656eb7 100644 --- a/queries.graphql +++ b/queries.graphql @@ -156,13 +156,17 @@ { product(id: 73803) { id + keywords { + id + value + type + } fields_json inserted path updated visible browsable - api1json categories { id name @@ -191,3 +195,44 @@ } } } + +# Categories +{ + category(id: 1653) { + id + name + path + isLeaf + depth + childCount + lft + rgt + products { + id + fields_json + } + } +} + +# Keywords +{ + keywords { + id + value + type + products { + id + } + } +} + +{ + keyword(id: 618) { + id + value + type + products { + id + } + } +} diff --git a/src/datasources/keyword-api.ts b/src/datasources/keyword-api.ts new file mode 100644 index 0000000..0942e6b --- /dev/null +++ b/src/datasources/keyword-api.ts @@ -0,0 +1,75 @@ +import { SQLDataSource } from 'datasource-sql'; +import { Keyword, KeywordType } from '../types/keyword-types'; + +const MINUTE = 60; + +export class KeywordAPI extends SQLDataSource { + constructor(config) { + super(config); + } + + getType(data: any) { + // raw queries doesn't camelCase... + const typeObject = data.type_id ?? data.typeId; + const typeId = typeObject ?? 0; + + return { + ...data, + type: KeywordType[typeId], + }; + } + + async getProductKeywords(productId: number): Promise> { + const query = /* sql */ ` +SELECT keywords.*, keyword_type.type_id FROM keywords +LEFT JOIN keyword_type ON keyword_type.keyword_id = keywords.id +JOIN product_keyword ON product_keyword.keyword_id = keywords.id +WHERE product_keyword.product_id = ? +ORDER BY keywords.value + `; + + const res = await this.knex + .raw(query, productId) + .then((data) => data.rows.map((row) => this.getType(row))); + return res; + } + + async getKeywords(): Promise> { + const query = /* sql */ ` +SELECT keywords.*, keyword_type.type_id FROM keywords +LEFT JOIN keyword_type ON keyword_type.keyword_id = keywords.id +ORDER BY keywords.value + `; + const res = await this.knex + .raw(query) + .then((data) => data.rows.map((row) => this.getType(row))); + return res; + } + + async getKeywordById(id: number): Promise { + const res = await this.knex + .select('*') + .from('keywords') + .leftJoin('keyword_type', 'keyword_type.keyword_id', 'keywords.id') + .where('id', id) + .first() + .cache(MINUTE) + .then((row) => this.getType(row)); + return res; + } + + /** + * category keywords + * +SELECT category_keyword.category_id, keywords.value +FROM category_keyword +JOIN keywords ON category_keyword.keyword_id = keywords.id; + + + FROM categorydata cd + JOIN categorydatakeys cdk ON cd.datakey_id = cdk.id + + +* + */ +} diff --git a/src/datasources/product-api.ts b/src/datasources/product-api.ts index 8bf6293..bbbd7b6 100644 --- a/src/datasources/product-api.ts +++ b/src/datasources/product-api.ts @@ -4,6 +4,7 @@ import { Product, ProductGroup, PrintProduct, + ProductType, ProductBlacklist, } from '../types/product-types'; import { Maybe } from '../types/types'; @@ -40,9 +41,25 @@ export class ProductAPI extends SQLDataSource { }); } + getProductTypes(row: any): Array { + 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]; + }); + } + createProductFromRow(row: any) { row.blacklisting = this.getBlacklisting(row); row.printProducts = this.getPrintProducts(row); + row.type = this.getProductTypes(row); row.designerId = row.designerid; row.fields_json = row.fields; return row; @@ -80,32 +97,12 @@ export class ProductAPI extends SQLDataSource { .raw(query) .then((data) => data.rows.map((row) => this.createProductFromRow(row))); } + + async getKeywordProducts(keywordId: number): Promise> { + const query = sql.keywordProducts(keywordId); + + return await this.knex + .raw(query) + .then((data) => data.rows.map((row) => this.createProductFromRow(row))); + } } - -/** - * - * SELECT product_keyword.product_id, keywords.value -FROM product_keyword -JOIN keywords ON product_keyword.keyword_id = keywords.id; - * - - - - - -keywords -id, value - - -keyword_type: -keyword_id, type_id - -keywordstypes -id, name (color) - - -keywords_i18n NOT used it seems and misses some languages - - - - */ diff --git a/src/datasources/sql/products-sql.ts b/src/datasources/sql/products-sql.ts index 904a0e4..3abc451 100644 --- a/src/datasources/sql/products-sql.ts +++ b/src/datasources/sql/products-sql.ts @@ -48,7 +48,12 @@ SELECT products.productid as id, FROM "product-products_fields" pf JOIN "product-fields" fields ON fields.fieldid = pf.fieldid WHERE pf.productid = products.productid - ) AS fields + ) AS fields, + ( SELECT json_agg(keywords.value) + FROM product_keyword pk + JOIN keywords ON keywords.id = pk.keyword_id + WHERE keywords.id IN (1103,1104)AND pk.product_id = products.productid + ) AS types FROM "product-products" products `; @@ -89,3 +94,12 @@ export function categoryProducts(categoryId: number) { ` ); } + +export function keywordProducts(keywordId: number) { + return ( + baseQuery + + /* sql */ ` + WHERE products.productid IN (SELECT product_id FROM product_keyword WHERE keyword_id = ${keywordId}); +` + ); +} diff --git a/src/index.ts b/src/index.ts index c5081a0..fbc5a82 100644 --- a/src/index.ts +++ b/src/index.ts @@ -14,6 +14,7 @@ import { Api1DataSource } from './datasources/api1-datasource'; import { verifyToken } from './cognito/cognito-client'; import { CategoryAPI } from './datasources/category-api'; +import { KeywordAPI } from './datasources/keyword-api'; console.log(`dbConfig`, dbConfig); @@ -26,6 +27,7 @@ const api2 = new Api2DataSource(); const orderApi = new OrderAPI(knexConfig); const productApi = new ProductAPI(knexConfig); const categoryApi = new CategoryAPI(knexConfig); +const keywordApi = new KeywordAPI(knexConfig); const designerApi = new DesignerAPI(knexConfig); const dataSources = () => ({ @@ -33,6 +35,7 @@ const dataSources = () => ({ api2, categoryApi, designerApi, + keywordApi, orderApi, productApi, }); diff --git a/src/resolvers/index.ts b/src/resolvers/index.ts index df48b10..f25ad6b 100644 --- a/src/resolvers/index.ts +++ b/src/resolvers/index.ts @@ -3,17 +3,20 @@ import { productQueryTypeDefs, productTypeDefs } from './products-resolver'; import { designerQueryTypeDefs, designerTypeDefs } from './designers-resolver'; import { categoryQueryTypeDefs, categoryTypeDefs } from './categories-resolver'; import { DateTimeResolver, DateResolver, JSONResolver } from 'graphql-scalars'; +import { keywordsQueryTypeDefs, keywordTypeDefs } from './keywords-resolver'; const resolvers = { Query: { ...orderQueryTypeDefs, ...productQueryTypeDefs, ...designerQueryTypeDefs, ...categoryQueryTypeDefs, + ...keywordsQueryTypeDefs, }, ...orderTypeDefs, ...productTypeDefs, ...designerTypeDefs, ...categoryTypeDefs, + ...keywordTypeDefs, DateTime: DateTimeResolver, Date: DateResolver, JSON: JSONResolver, diff --git a/src/resolvers/keywords-resolver.ts b/src/resolvers/keywords-resolver.ts new file mode 100644 index 0000000..1596d5c --- /dev/null +++ b/src/resolvers/keywords-resolver.ts @@ -0,0 +1,24 @@ +import { IResolverObject } from 'graphql-tools'; +import { KeywordAPI } from '../datasources/keyword-api'; +import { ProductAPI } from '../datasources/product-api'; + +const Keyword: IResolverObject = { + async products({ id }, _args, { dataSources }) { + return (dataSources.productApi).getKeywordProducts(id); + }, +}; + +async function getKeywords(_, _args, { dataSources }) { + return (dataSources.keywordApi).getKeywords(); +} + +async function getKeyword(_, { id }, { dataSources }) { + return (dataSources.keywordApi).getKeywordById(id); +} + +export const keywordTypeDefs = { Keyword }; + +export const keywordsQueryTypeDefs = { + keywords: getKeywords, + keyword: getKeyword, +}; diff --git a/src/resolvers/products-resolver.ts b/src/resolvers/products-resolver.ts index e7189f2..f74d3e8 100644 --- a/src/resolvers/products-resolver.ts +++ b/src/resolvers/products-resolver.ts @@ -5,6 +5,8 @@ import { ProductAPI } from '../datasources/product-api'; import { Product } from '../types/product-types'; import { CategoryAPI } from '../datasources/category-api'; import { Category } from '../types/category-types'; +import { KeywordAPI } from '../datasources/keyword-api'; +import { Keyword } from '../types/keyword-types'; const ProductBlacklist: IResolverObject = { async market(parent, _args, { dataSources }) { @@ -20,6 +22,9 @@ const Product: IResolverObject = { async designer({ designerId }, args, { dataSources }) { return dataSources.designerApi.getDesignerById(designerId); }, + async keywords({ id }, _args, { dataSources }): Promise> { + return (dataSources.keywordApi).getProductKeywords(id); + }, async api1json({ id }, _args, { dataSources }): Promise { return dataSources.api1.getProduct(id); }, diff --git a/src/schema.ts b/src/schema.ts index 3ddf788..5060631 100644 --- a/src/schema.ts +++ b/src/schema.ts @@ -12,6 +12,8 @@ const typeDefs = gql` product(id: Int!): Product categories: [Category] category(id: Int!): Category + keywords: [Keyword] + keyword(id: Int!): Keyword designers(limit: Int): [Designer] designer(id: Int!): Designer } @@ -190,6 +192,12 @@ const typeDefs = gql` categories: [Category] designerId: Int designer: Designer + keywords: [Keyword] + """ + Will be single value return in later release + """ + type: [ProductType] + # Below are for debug api1json: JSON fields_json: JSON } @@ -206,6 +214,12 @@ const typeDefs = gql` UNKNOWN } + enum ProductType { + UNKNOWN + PHOTO + ILLUSTRATION + } + type ProductBlacklist { id: ID! groupId: Int! @@ -231,6 +245,18 @@ const typeDefs = gql` path: String orderRows(filter: FilterInput): [OrderRow] } + + enum KeywordType { + NOT_SET + COLOR + } + + type Keyword { + id: Int! + value: String! + type: KeywordType + products: [Product] + } `; export { typeDefs }; diff --git a/src/types/keyword-types.ts b/src/types/keyword-types.ts new file mode 100644 index 0000000..d5303ab --- /dev/null +++ b/src/types/keyword-types.ts @@ -0,0 +1,10 @@ +export enum KeywordType { + NOT_SET = 0, + COLOR = 1, +} + +export interface Keyword { + id: number; + value: string; + type: KeywordType; +} diff --git a/src/types/product-types.ts b/src/types/product-types.ts index 1879244..4b7ac12 100644 --- a/src/types/product-types.ts +++ b/src/types/product-types.ts @@ -10,29 +10,10 @@ export enum ProductGroup { UNKNOWN = 9, } -export function getProductGroupStringFromString(group: string): string { - return ProductGroup[getProductGroupFromString(group)]; -} - -export function getProductGroupFromString(group: string): ProductGroup { - switch (group) { - case 'canvas': - return ProductGroup.CANVAS; - case 'framed-print': - return ProductGroup.FRAMED_PRINT; - case 'photo-wallpaper': - return ProductGroup.PHOTO_WALLPAPER; - case 'poster': - return ProductGroup.POSTER; - case 'wallpaper': - return ProductGroup.WALLPAPER; - case 'design-wallpaper': - return ProductGroup.DESIGNER_WALLPAPER; - case 'doityourselfframe': - return ProductGroup.DO_IT_YOURSELF_FRAME; - default: - return ProductGroup.UNKNOWN; - } +export enum ProductType { + UNKNOWN = 0, + PHOTO = 1, + ILLUSTRATION = 2, } export interface ProductBlacklist { id: number; @@ -60,6 +41,32 @@ export interface Product { updated?: Date; printProducts: [PrintProduct]; blacklisting: [ProductBlacklist]; + type: [ProductType]; api1json: JSON; fields_json: JSON; } + +export function getProductGroupStringFromString(group: string): string { + return ProductGroup[getProductGroupFromString(group)]; +} + +export function getProductGroupFromString(group: string): ProductGroup { + switch (group) { + case 'canvas': + return ProductGroup.CANVAS; + case 'framed-print': + return ProductGroup.FRAMED_PRINT; + case 'photo-wallpaper': + return ProductGroup.PHOTO_WALLPAPER; + case 'poster': + return ProductGroup.POSTER; + case 'wallpaper': + return ProductGroup.WALLPAPER; + case 'design-wallpaper': + return ProductGroup.DESIGNER_WALLPAPER; + case 'doityourselfframe': + return ProductGroup.DO_IT_YOURSELF_FRAME; + default: + return ProductGroup.UNKNOWN; + } +}