From 3bef0546844b4035e86a8130228b3e6e4f966289 Mon Sep 17 00:00:00 2001 From: Anders Gustafsson <34234789+anders-photowall@users.noreply.github.com> Date: Thu, 5 Feb 2026 14:53:14 +0100 Subject: [PATCH] 7960 - Add categories_v2 field on Product (#180) --- src/datasources/category-api.ts | 41 ++++++++++++++++++++++++++++ src/resolvers/categories-resolver.ts | 5 ++++ src/resolvers/products-resolver.ts | 28 +++++++++++-------- src/schema.graphql | 2 ++ 4 files changed, 65 insertions(+), 11 deletions(-) diff --git a/src/datasources/category-api.ts b/src/datasources/category-api.ts index e5a0b03..9f6e2c3 100644 --- a/src/datasources/category-api.ts +++ b/src/datasources/category-api.ts @@ -65,6 +65,27 @@ WHERE product_category.product_id = ? return res; } + async getProductCategoriesV2(productId: number): Promise> { + ScopeAccess.validate(this.user).all([Scopes.CATEGORIES_READ]); + const query = /* sql */ ` +SELECT product_category.category_id as id, + categories.* +FROM product_category + JOIN v_categorytree_v2 categories ON product_category.category_id = categories.id +WHERE product_category.product_id = ? + `; + + const res = await this.knex.raw(query, productId).then((data) => + data.rows.map((row) => { + return { + ...row, + pathNumeric: row.path_numeric, + }; + }), + ); + return res; + } + async getCategoryBasedFacets( productId: number, ): Promise<{ room: string[]; color: string[] }> { @@ -153,6 +174,26 @@ WHERE product_category.product_id = ? ); } + async getCategoriesV2(ids: number[]): Promise> { + ScopeAccess.validate(this.user).some([ + Scopes.CATEGORIES_READ, + Scopes.CATEGORIES_PUBLIC_READ, + ]); + + return ( + this.knex + .select('*') + .modify((queryBuilder) => { + if (ids) { + queryBuilder.whereIn('id', ids); + } + }) + .from('v_categorytree_v2') + // @ts-ignore + .cache(MINUTE) + ); + } + async searchCategories(name: string): Promise> { ScopeAccess.validate(this.user).all([Scopes.CATEGORIES_READ]); return this.knex diff --git a/src/resolvers/categories-resolver.ts b/src/resolvers/categories-resolver.ts index 322b5b5..1e8ddb6 100644 --- a/src/resolvers/categories-resolver.ts +++ b/src/resolvers/categories-resolver.ts @@ -26,6 +26,10 @@ async function getCategories(_, { ids }, { dataSources }) { return (dataSources.categoryApi).getCategories(ids); } +async function getCategoriesV2(_, { ids }, { dataSources }) { + return (dataSources.categoryApi).getCategoriesV2(ids); +} + async function getCategory(_, { id }, { dataSources }) { return (dataSources.categoryApi).getCategoryById(id); } @@ -34,5 +38,6 @@ export const categoryTypeDefs = { Category }; export const categoryQueryTypeDefs = { categories: getCategories, + categories_v2: getCategoriesV2, category: getCategory, }; diff --git a/src/resolvers/products-resolver.ts b/src/resolvers/products-resolver.ts index 71accbc..ad3edbc 100644 --- a/src/resolvers/products-resolver.ts +++ b/src/resolvers/products-resolver.ts @@ -1,9 +1,8 @@ import { ProductAPI } from '../datasources/product-api'; -import { - Orientation, - PrintProduct, - Product, - ProductGroup, +import { Orientation, ProductGroup } from '../types/product-types'; +import type { + PrintProduct as PrintProductType, + Product as ProductType, ProductListResult, ProductsFilterInput, ProductsSearchResult, @@ -16,7 +15,7 @@ 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 type { IdNumberResult } from '../types/types'; import { moveS3File } from '../aws/s3'; import { DesignerAPI } from '../datasources/designer-api'; import { PrintProductDefaultsAPI } from '../datasources/printproduct-defaults-api'; @@ -36,7 +35,7 @@ const ProductBlacklist = { const Product = { async printProducts( - product: { printProducts: PrintProduct[] }, + product: { printProducts: PrintProductType[] }, input: { groups: ProductGroup[] }, ) { if (!input.groups) { @@ -49,6 +48,13 @@ const Product = { async categories({ id }, _args, { dataSources }): Promise> { return (dataSources.categoryApi).getProductCategories(id); }, + async categories_v2( + { id }, + _args, + { dataSources }, + ): Promise> { + return (dataSources.categoryApi).getProductCategoriesV2(id); + }, async collections({ id }, _args, { dataSources }) { return dataSources.collectionApi.getProductCollections(id); }, @@ -64,7 +70,7 @@ const Product = { async related({ id }, _args, { dataSources }) { return (dataSources.productApi).getRelatedProducts(id); }, - async facets(product: Product, _args, { dataSources }) { + async facets(product: ProductType, _args, { dataSources }) { const keywords = await (( dataSources.keywordApi )).getProductKeywords(product.id); @@ -177,7 +183,7 @@ async function getProductsSearchResult( ]).then((d) => { // flatten, remove falsy (each searchBy... can return undefined above) // and finally remove duplicates. - const products = {} as { [key: string]: Product }; + const products = {} as { [key: string]: ProductType }; d.flat().forEach((product) => { if (product && !products[product.id]) { products[product.id] = product; @@ -208,7 +214,7 @@ async function getProductsSearchResult( }; } -async function getProduct(_, { id }, { dataSources }): Promise { +async function getProduct(_, { id }, { dataSources }): Promise { return (dataSources.productApi).getProduct(id); } @@ -216,7 +222,7 @@ async function getProductByPath( _, { path }, { dataSources }, -): Promise { +): Promise { return (dataSources.productApi).getProductByPath(path); } diff --git a/src/schema.graphql b/src/schema.graphql index 7acda56..b9a8387 100644 --- a/src/schema.graphql +++ b/src/schema.graphql @@ -28,6 +28,7 @@ type Query { productByPath(path: String!): Product productsSearch(q: String!): ProductsSearchResult categories(ids: [Int]): [Category] + categories_v2(ids: [Int]): [Category] category(id: Int!): Category keywords: [Keyword] keyword(id: Int!): Keyword @@ -262,6 +263,7 @@ type Product { printProducts(groups: [ProductGroup]): [PrintProduct] blacklisting: [ProductBlacklist] categories: [Category] + categories_v2: [Category] collections: [Collection] designerId: Int designer: Designer