3183 get locale data for categories (#143)
This commit is contained in:
@@ -13,6 +13,7 @@ export enum Scopes {
|
||||
PRODUCTS_PUBLIC_READ = 'products.public.read',
|
||||
PRODUCTS_WRITE = 'products.write',
|
||||
CATEGORIES_READ = 'categories.read',
|
||||
CATEGORIES_PUBLIC_READ = 'categories.public.read',
|
||||
CATEGORIES_WRITE = 'categories.write',
|
||||
MARKETS_READ = 'markets.read',
|
||||
KEYWORDS_READ = 'keywords.read',
|
||||
|
||||
+111
-22
@@ -1,12 +1,47 @@
|
||||
import DataLoader from 'dataloader';
|
||||
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 { BaseSQLDataSource } from './BaseSQLDataSource';
|
||||
import { MINUTE } from './utils';
|
||||
import { createQuestionMarksFromList, MINUTE } from './utils';
|
||||
|
||||
export class CategoryAPI extends BaseSQLDataSource {
|
||||
private categoryLoader: DataLoader<number, Category>;
|
||||
private localeDataLoader: DataLoader<number, CategoryLocaleData[]>;
|
||||
private localeNamesLoader: DataLoader<number, JSON>;
|
||||
|
||||
constructor(options: DataSourceOptions, 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>> {
|
||||
@@ -30,10 +65,30 @@ WHERE product_category.product_id = ?
|
||||
return res;
|
||||
}
|
||||
|
||||
async getCategories(): Promise<Array<Category>> {
|
||||
ScopeAccess.validate(this.user).all([Scopes.CATEGORIES_READ]);
|
||||
// @ts-ignore
|
||||
return this.knex.select('*').from('v_categorytree').cache(MINUTE);
|
||||
async getCategoryById(id: number): Promise<Category> {
|
||||
// Dataloaders does not need scopes validation since they
|
||||
// use other functions.
|
||||
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>> {
|
||||
@@ -44,29 +99,63 @@ WHERE product_category.product_id = ?
|
||||
.whereRaw(`LOWER(name) LIKE ?`, [`${name}`]);
|
||||
}
|
||||
|
||||
async getCategoryById(id: number): Promise<Category> {
|
||||
ScopeAccess.validate(this.user).all([Scopes.CATEGORIES_READ]);
|
||||
return (
|
||||
this.knex
|
||||
.select('*')
|
||||
.from('v_categorytree')
|
||||
.where('id', id)
|
||||
.first()
|
||||
// @ts-ignore
|
||||
.cache(MINUTE)
|
||||
);
|
||||
async getLocaleNameForId(id: number): Promise<JSON> {
|
||||
// Dataloaders does not need scopes validation since they
|
||||
// use other functions.
|
||||
return this.localeNamesLoader.load(id);
|
||||
}
|
||||
|
||||
async getLocaleNameForId(id: number): Promise<JSON> {
|
||||
ScopeAccess.validate(this.user).all([Scopes.LOCALES_READ]);
|
||||
async getLocaleNameForIds(
|
||||
ids: number[],
|
||||
): Promise<{ id: number; locale_names: JSON }[]> {
|
||||
ScopeAccess.validate(this.user).all([
|
||||
Scopes.CATEGORIES_READ,
|
||||
Scopes.LOCALES_READ,
|
||||
]);
|
||||
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 = ?',
|
||||
[id],
|
||||
`SELECT id, json_object_agg(locale, name) as locale_names FROM v_esales_categorytree_i18n WHERE id IN (${createQuestionMarksFromList(
|
||||
ids,
|
||||
)}) GROUP BY id`,
|
||||
ids,
|
||||
)
|
||||
.cache(MINUTE * 5)
|
||||
.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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -14,10 +14,16 @@ const Category = {
|
||||
async localeNames({ id }, _, { dataSources }) {
|
||||
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 }) {
|
||||
return (<CategoryAPI>dataSources.categoryApi).getCategories();
|
||||
async function getCategories(_, { ids }, { dataSources }) {
|
||||
return (<CategoryAPI>dataSources.categoryApi).getCategories(ids);
|
||||
}
|
||||
|
||||
async function getCategory(_, { id }, { dataSources }) {
|
||||
|
||||
@@ -29,7 +29,7 @@ export const getClientAccessToken = async (req) => {
|
||||
data: {
|
||||
grant_type: 'client_credentials',
|
||||
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
@@ -27,7 +27,7 @@ type Query {
|
||||
product(id: Int!): Product
|
||||
productByPath(path: String!): Product
|
||||
productsSearch(q: String!): ProductsSearchResult
|
||||
categories: [Category]
|
||||
categories(ids: [Int]): [Category]
|
||||
category(id: Int!): Category
|
||||
keywords: [Keyword]
|
||||
keyword(id: Int!): Keyword
|
||||
@@ -207,6 +207,10 @@ type OrderRowData {
|
||||
pwintySku: String
|
||||
}
|
||||
|
||||
type CategoryLocaleData {
|
||||
path: String, name: String, locale: String
|
||||
}
|
||||
|
||||
type Category {
|
||||
id: ID!
|
||||
name: String
|
||||
@@ -222,6 +226,7 @@ type Category {
|
||||
"""
|
||||
products(includeAllSubCategories: Boolean): [Product]
|
||||
localeNames: JSON
|
||||
localeData(locale: String): [CategoryLocaleData]!
|
||||
}
|
||||
|
||||
type Batch {
|
||||
|
||||
@@ -9,4 +9,12 @@ export interface Category {
|
||||
lft: number;
|
||||
rgt: number;
|
||||
localeNames: Array<Record<string, string>>;
|
||||
localeData: CategoryLocaleData[];
|
||||
}
|
||||
|
||||
export interface CategoryLocaleData {
|
||||
name: string;
|
||||
path: string;
|
||||
locale: string;
|
||||
id: number;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user