3183 get locale data for categories (#143)

This commit is contained in:
Anders Gustafsson
2023-02-24 09:24:52 +01:00
committed by GitHub
parent 0a26a3143e
commit 74f89e9934
6 changed files with 135 additions and 26 deletions
+1
View File
@@ -13,6 +13,7 @@ export enum Scopes {
PRODUCTS_PUBLIC_READ = 'products.public.read', PRODUCTS_PUBLIC_READ = 'products.public.read',
PRODUCTS_WRITE = 'products.write', PRODUCTS_WRITE = 'products.write',
CATEGORIES_READ = 'categories.read', CATEGORIES_READ = 'categories.read',
CATEGORIES_PUBLIC_READ = 'categories.public.read',
CATEGORIES_WRITE = 'categories.write', CATEGORIES_WRITE = 'categories.write',
MARKETS_READ = 'markets.read', MARKETS_READ = 'markets.read',
KEYWORDS_READ = 'keywords.read', KEYWORDS_READ = 'keywords.read',
+111 -22
View File
@@ -1,12 +1,47 @@
import DataLoader from 'dataloader';
import { ScopeAccess, Scopes } from '../cognito/access-control'; import { ScopeAccess, Scopes } from '../cognito/access-control';
import { Category } from '../types/category-types'; import { Category, CategoryLocaleData } from '../types/category-types';
import { DataSourceOptions } from '../types/types'; import { DataSourceOptions } from '../types/types';
import { BaseSQLDataSource } from './BaseSQLDataSource'; import { BaseSQLDataSource } from './BaseSQLDataSource';
import { MINUTE } from './utils'; import { createQuestionMarksFromList, MINUTE } from './utils';
export class CategoryAPI extends BaseSQLDataSource { export class CategoryAPI extends BaseSQLDataSource {
private categoryLoader: DataLoader<number, Category>;
private localeDataLoader: DataLoader<number, CategoryLocaleData[]>;
private localeNamesLoader: DataLoader<number, JSON>;
constructor(options: DataSourceOptions, config) { constructor(options: DataSourceOptions, config) {
super(options, config); super(options, config);
this.categoryLoader = new DataLoader(async (keys: number[]) => {
const categoriesResult = await this.getCategories(keys);
return keys.map(
(key) =>
categoriesResult.find(
(categoryResult) => categoryResult.id === key,
) ?? null,
);
});
this.localeDataLoader = new DataLoader(async (keys: number[]) => {
const localeDataResults = await this.getLocaleDataForIds(keys);
return keys.map(
(key) =>
localeDataResults.filter(
(localeDataResult) => localeDataResult.id === key,
) ?? null,
);
});
this.localeNamesLoader = new DataLoader(async (keys: number[]) => {
const localeNameResults = await this.getLocaleNameForIds(keys);
return keys.map((key) => {
const row =
localeNameResults.find(
(localeDataResult) => localeDataResult.id === key,
) ?? null;
return row?.locale_names;
});
});
} }
async getProductCategories(productId: number): Promise<Array<Category>> { async getProductCategories(productId: number): Promise<Array<Category>> {
@@ -30,10 +65,30 @@ WHERE product_category.product_id = ?
return res; return res;
} }
async getCategories(): Promise<Array<Category>> { async getCategoryById(id: number): Promise<Category> {
ScopeAccess.validate(this.user).all([Scopes.CATEGORIES_READ]); // Dataloaders does not need scopes validation since they
// @ts-ignore // use other functions.
return this.knex.select('*').from('v_categorytree').cache(MINUTE); return this.categoryLoader.load(id);
}
async getCategories(ids: number[]): Promise<Array<Category>> {
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')
// @ts-ignore
.cache(MINUTE)
);
} }
async searchCategories(name: string): Promise<Array<Category>> { async searchCategories(name: string): Promise<Array<Category>> {
@@ -44,29 +99,63 @@ WHERE product_category.product_id = ?
.whereRaw(`LOWER(name) LIKE ?`, [`${name}`]); .whereRaw(`LOWER(name) LIKE ?`, [`${name}`]);
} }
async getCategoryById(id: number): Promise<Category> { async getLocaleNameForId(id: number): Promise<JSON> {
ScopeAccess.validate(this.user).all([Scopes.CATEGORIES_READ]); // Dataloaders does not need scopes validation since they
return ( // use other functions.
this.knex return this.localeNamesLoader.load(id);
.select('*')
.from('v_categorytree')
.where('id', id)
.first()
// @ts-ignore
.cache(MINUTE)
);
} }
async getLocaleNameForId(id: number): Promise<JSON> { async getLocaleNameForIds(
ScopeAccess.validate(this.user).all([Scopes.LOCALES_READ]); ids: number[],
): Promise<{ id: number; locale_names: JSON }[]> {
ScopeAccess.validate(this.user).all([
Scopes.CATEGORIES_READ,
Scopes.LOCALES_READ,
]);
const res = await this.cachedRaw( const res = await this.cachedRaw(
'SELECT json_object_agg(ec.locale, ec.name) as locale_names FROM v_esales_categorytree_i18n ec WHERE ec.id = ?', `SELECT id, json_object_agg(locale, name) as locale_names FROM v_esales_categorytree_i18n WHERE id IN (${createQuestionMarksFromList(
[id], ids,
)}) GROUP BY id`,
ids,
) )
.cache(MINUTE * 5) .cache(MINUTE * 5)
.then((data) => data.rows); .then((data) => data.rows);
return res;
}
return res[0].locale_names; async getLocaleDataForId(
id: number,
locale?: string,
): Promise<CategoryLocaleData[]> {
// Dataloaders does not need scopes validation since they
// use other functions.
const res = await this.localeDataLoader.load(id);
if (locale) {
return res.filter((row) => row.locale === locale);
}
return res;
}
async getLocaleDataForIds(
ids: number[],
locale?: string,
): Promise<CategoryLocaleData[]> {
ScopeAccess.validate(this.user).some([
Scopes.LOCALES_READ,
Scopes.CATEGORIES_PUBLIC_READ,
]);
const res = await this.cachedRaw(
`SELECT ec.id, ec.locale, getSafeXmlPath(ec.path, 2) as path, ec.name FROM v_esales_categorytree_i18n ec WHERE ec.id IN (${createQuestionMarksFromList(
ids,
)})`,
ids,
)
.cache(MINUTE * 5)
.then((data) => data.rows);
if (locale) {
return res.filter((row) => row.locale === locale);
}
return res;
} }
/** /**
+8 -2
View File
@@ -14,10 +14,16 @@ const Category = {
async localeNames({ id }, _, { dataSources }) { async localeNames({ id }, _, { dataSources }) {
return (<CategoryAPI>dataSources.categoryApi).getLocaleNameForId(id); return (<CategoryAPI>dataSources.categoryApi).getLocaleNameForId(id);
}, },
async localeData({ id }, input: { locale: string }, { dataSources }) {
return (<CategoryAPI>dataSources.categoryApi).getLocaleDataForId(
id,
input.locale,
);
},
}; };
async function getCategories(_, _args, { dataSources }) { async function getCategories(_, { ids }, { dataSources }) {
return (<CategoryAPI>dataSources.categoryApi).getCategories(); return (<CategoryAPI>dataSources.categoryApi).getCategories(ids);
} }
async function getCategory(_, { id }, { dataSources }) { async function getCategory(_, { id }, { dataSources }) {
+1 -1
View File
@@ -29,7 +29,7 @@ export const getClientAccessToken = async (req) => {
data: { data: {
grant_type: 'client_credentials', grant_type: 'client_credentials',
client_id: clientId, client_id: clientId,
scope: `https://graphql.photowall.com/${Scopes.PRODUCTS_PUBLIC_READ} https://graphql.photowall.com/${Scopes.DESIGNERS_PUBLIC_READ}`, scope: `https://graphql.photowall.com/${Scopes.PRODUCTS_PUBLIC_READ} https://graphql.photowall.com/${Scopes.DESIGNERS_PUBLIC_READ} https://graphql.photowall.com/${Scopes.CATEGORIES_PUBLIC_READ}`,
}, },
}); });
+6 -1
View File
@@ -27,7 +27,7 @@ type Query {
product(id: Int!): Product product(id: Int!): Product
productByPath(path: String!): Product productByPath(path: String!): Product
productsSearch(q: String!): ProductsSearchResult productsSearch(q: String!): ProductsSearchResult
categories: [Category] categories(ids: [Int]): [Category]
category(id: Int!): Category category(id: Int!): Category
keywords: [Keyword] keywords: [Keyword]
keyword(id: Int!): Keyword keyword(id: Int!): Keyword
@@ -207,6 +207,10 @@ type OrderRowData {
pwintySku: String pwintySku: String
} }
type CategoryLocaleData {
path: String, name: String, locale: String
}
type Category { type Category {
id: ID! id: ID!
name: String name: String
@@ -222,6 +226,7 @@ type Category {
""" """
products(includeAllSubCategories: Boolean): [Product] products(includeAllSubCategories: Boolean): [Product]
localeNames: JSON localeNames: JSON
localeData(locale: String): [CategoryLocaleData]!
} }
type Batch { type Batch {
+8
View File
@@ -9,4 +9,12 @@ export interface Category {
lft: number; lft: number;
rgt: number; rgt: number;
localeNames: Array<Record<string, string>>; localeNames: Array<Record<string, string>>;
localeData: CategoryLocaleData[];
}
export interface CategoryLocaleData {
name: string;
path: string;
locale: string;
id: number;
} }