Major refactor

This commit is contained in:
Niklas Fondberg
2021-07-06 15:57:22 +02:00
parent 6bca11c62d
commit f66dbe33d7
21 changed files with 299 additions and 73 deletions
+40
View File
@@ -151,3 +151,43 @@
path path
} }
} }
# JULY 2021
{
product(id: 73803) {
id
fields_json
inserted
path
updated
visible
browsable
api1json
categories {
id
name
path
isLeaf
depth
childCount
lft
rgt
}
designer {
id
name
path
}
printProducts {
group
id
updated
}
blacklisting {
group
id
marketId
updated
}
}
}
+6
View File
@@ -122,6 +122,12 @@ export const createAllTestContainers =
) )
.start(); .start();
const stream = await apiContainer.logs();
stream
.on('data', (line) => console.log(line))
.on('err', (line) => console.error(line))
.on('end', () => console.log('Stream closed'));
Object.assign(allContainers, { Object.assign(allContainers, {
version: Math.round(Math.random() * 999999), version: Math.round(Math.random() * 999999),
dbContainer, dbContainer,
+1 -1
View File
@@ -1,7 +1,7 @@
import { promisify } from 'util'; import { promisify } from 'util';
import * as Axios from 'axios'; import * as Axios from 'axios';
import * as jsonwebtoken from 'jsonwebtoken'; import * as jsonwebtoken from 'jsonwebtoken';
import { Maybe } from '../datasources/types'; import { Maybe } from '../types/types';
const jwkToPem = require('jwk-to-pem'); const jwkToPem = require('jwk-to-pem');
export interface ClaimVerifyRequest { export interface ClaimVerifyRequest {
+20
View File
@@ -0,0 +1,20 @@
import { RESTDataSource } from 'apollo-datasource-rest';
export class Api1DataSource extends RESTDataSource {
authHeader: string;
constructor() {
super();
this.baseURL = 'http://docker.for.mac.localhost:8082';
this.authHeader = 'Basic ZGV2OmRldg==';
}
willSendRequest(request) {
request.headers.set('Authorization', this.authHeader);
}
async getProduct(id) {
return await this.get(`/products/${id}?` + new URLSearchParams({}), null, {
cacheOptions: { ttl: 5 },
});
}
}
+60
View File
@@ -0,0 +1,60 @@
import { SQLDataSource } from 'datasource-sql';
import { Category } from '../types/category-types';
const MINUTE = 60;
export class CategoryAPI extends SQLDataSource {
constructor(config) {
super(config);
}
async getProductCategories(productId: number): Promise<Array<Category>> {
const query = /* sql */ `
SELECT product_category.category_id as id,
categories.*
FROM product_category
JOIN v_categorytree 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,
path: row.path.replace(/^root/, ''),
};
}),
);
return res;
}
async getCategories(): Promise<Array<Category>> {
return await this.knex
.select('*')
.from('v_categorytree')
.cache(MINUTE)
.then((rows) => {
return rows.map((row) => {
return {
...row,
path: row.path.replace(/^root/, ''),
};
});
});
}
async getCategoryById(id: number): Promise<Category> {
return await this.knex
.select('*')
.from('v_categorytree')
.where('id', id)
.first()
.cache(MINUTE)
.then((row) => {
return {
...row,
path: row.path.replace(/^root/, ''),
};
});
}
}
+2 -2
View File
@@ -1,6 +1,6 @@
import { BaseSQLDataSource } from './BaseSQLDataSource'; import { BaseSQLDataSource } from './BaseSQLDataSource';
import { Designer } from './designer-types'; import { Designer } from '../types/designer-types';
import { Maybe } from './types'; import { Maybe } from '../types/types';
const MINUTE = 60; const MINUTE = 60;
export class DesignerAPI extends BaseSQLDataSource { export class DesignerAPI extends BaseSQLDataSource {
+28 -9
View File
@@ -6,10 +6,10 @@ import {
Market, Market,
Order, Order,
OrderRow, OrderRow,
} from './order-types'; } from '../types/order-types';
import { GeneralInput, Maybe } from './types'; import { GeneralInput, Maybe } from '../types/types';
import { convertToPossibleType } from './utils'; import { convertToPossibleType } from './utils';
import { getProductGroupStringFromString } from './product-types'; import { getProductGroupStringFromString } from '../types/product-types';
const MINUTE = 60; const MINUTE = 60;
export class OrderAPI extends BaseSQLDataSource { export class OrderAPI extends BaseSQLDataSource {
@@ -80,9 +80,8 @@ export class OrderAPI extends BaseSQLDataSource {
return row; return row;
} }
async getOrders(input: Maybe<GeneralInput>): Promise<Array<Order>> { async getOrdersTotal(input: Maybe<GeneralInput>): Promise<Number> {
let query = this.knex.select('*').from('orders'); let query = this.knex.table('orders');
// .where('inserted', '>=', '2021-03-01T00:00:00Z')
if (input?.dates?.from) { if (input?.dates?.from) {
query = query.where('inserted', '>=', input.dates.from); query = query.where('inserted', '>=', input.dates.from);
@@ -91,17 +90,37 @@ export class OrderAPI extends BaseSQLDataSource {
if (input?.dates?.to) { if (input?.dates?.to) {
query = query.where('inserted', '<=', input.dates.to); query = query.where('inserted', '<=', input.dates.to);
} }
query = query.orderBy('inserted', 'ASC');
const res = await query.clone().count();
console.log(res);
return 57777;
}
async getOrders(input: Maybe<GeneralInput>): Promise<Array<Order>> {
let query = this.getOrdersQuery(input);
if (input?.limit) { if (input?.limit) {
query = query.limit(input?.limit); query = query.limit(input?.limit);
} }
query = query.orderBy('inserted', 'ASC');
return await query return await query
.cache(MINUTE) .cache(MINUTE)
.then((rows) => rows.map((row) => this.createOrderFromRow(row))); .then((rows) => rows.map((row) => this.createOrderFromRow(row)));
} }
private getOrdersQuery(input: GeneralInput) {
let query = this.knex.table('orders');
if (input?.dates?.from) {
query = query.where('inserted', '>=', input.dates.from);
}
if (input?.dates?.to) {
query = query.where('inserted', '<=', input.dates.to);
}
return query;
}
async getOrderById(id: number): Promise<Order> { async getOrderById(id: number): Promise<Order> {
return await this.knex return await this.knex
.select('*') .select('*')
@@ -115,7 +134,7 @@ export class OrderAPI extends BaseSQLDataSource {
async getOrderRowFieldMapping(): Promise<any> { async getOrderRowFieldMapping(): Promise<any> {
// const fields = await this.knex.select('*').from('order-row_fields'); // const fields = await this.knex.select('*').from('order-row_fields');
const fieldsRes = await this.cacheQuery( const fieldsRes = await this.cacheQuery(
60, MINUTE * 10,
this.knex this.knex
.raw('SELECT * from "order-row_fields"') .raw('SELECT * from "order-row_fields"')
.then((data) => data.rows), .then((data) => data.rows),
+17 -31
View File
@@ -5,9 +5,8 @@ import {
ProductGroup, ProductGroup,
PrintProduct, PrintProduct,
ProductBlacklist, ProductBlacklist,
Category, } from '../types/product-types';
} from './product-types'; import { Maybe } from '../types/types';
import { Maybe } from './types';
const MINUTE = 60; const MINUTE = 60;
@@ -16,35 +15,10 @@ export class ProductAPI extends SQLDataSource {
super(config); super(config);
} }
async getCategories(productId: number): Promise<Array<Category>> {
const query = /* sql */ `
SELECT product_category.category_id, categories.name
FROM product_category JOIN 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((o) => {
return {
...o,
id: o.category_id,
};
}),
);
return res;
}
async getMaterial(printId: number): Promise<any> { async getMaterial(printId: number): Promise<any> {
// SELECT materialid, material, (price / 100::float) as price FROM "product-materials" // SELECT materialid, material, (price / 100::float) as price FROM "product-materials"
} }
// TODO: get product details
async getProductDetails(id: number): Promise<any> {
// SELECT * FROM "product-fields";
// SELECT * FROM "product-products_fields" WHERE productid = 58941;
return null;
}
getBlacklisting(row: any): Array<ProductBlacklist> { getBlacklisting(row: any): Array<ProductBlacklist> {
if (!row.blacklisting) { if (!row.blacklisting) {
return []; return [];
@@ -70,6 +44,7 @@ export class ProductAPI extends SQLDataSource {
row.blacklisting = this.getBlacklisting(row); row.blacklisting = this.getBlacklisting(row);
row.printProducts = this.getPrintProducts(row); row.printProducts = this.getPrintProducts(row);
row.designerId = row.designerid; row.designerId = row.designerid;
row.fields_json = row.fields;
return row; return row;
} }
@@ -89,9 +64,20 @@ export class ProductAPI extends SQLDataSource {
async getProduct(id: number): Promise<Maybe<Product>> { async getProduct(id: number): Promise<Maybe<Product>> {
const query = sql.product(id); const query = sql.product(id);
const res = await this.knex const res = await this.knex.raw(query).then((data) =>
.raw(query) data.rows.map((row) => {
.then((data) => data.rows.map((row) => this.createProductFromRow(row))); const prod = this.createProductFromRow(row);
return prod;
}),
);
return res.find(Boolean); return res.find(Boolean);
} }
async getCategoryProducts(categoryId: number): Promise<Array<Product>> {
const query = sql.categoryProducts(categoryId);
return await this.knex
.raw(query)
.then((data) => data.rows.map((row) => this.createProductFromRow(row)));
}
} }
+16 -2
View File
@@ -1,4 +1,4 @@
import { Maybe } from '../types'; import { Maybe } from '../../types/types';
// Get syntax highlighting with vscode by installing "Comment tagged templates2 // Get syntax highlighting with vscode by installing "Comment tagged templates2
@@ -43,7 +43,12 @@ SELECT products.productid as id,
product_blacklist.updated_at product_blacklist.updated_at
) )
) FROM product_blacklist WHERE product_blacklist.product_id = products.productid ) FROM product_blacklist WHERE product_blacklist.product_id = products.productid
) AS blacklisting ) AS blacklisting,
( SELECT json_object_agg(fields.field, pf.value)
FROM "product-products_fields" pf
JOIN "product-fields" fields ON fields.fieldid = pf.fieldid
WHERE pf.productid = products.productid
) AS fields
FROM "product-products" products FROM "product-products" products
`; `;
@@ -75,3 +80,12 @@ export function product(id: number) {
` `
); );
} }
export function categoryProducts(categoryId: number) {
return (
baseQuery +
/* sql */ `
WHERE products.productid IN (SELECT product_id FROM product_category WHERE category_id = ${categoryId});
`
);
}
+17 -7
View File
@@ -10,21 +10,31 @@ import { OrderAPI } from './datasources/order-api';
import { ProductAPI } from './datasources/product-api'; import { ProductAPI } from './datasources/product-api';
import { DesignerAPI } from './datasources/designer-api'; import { DesignerAPI } from './datasources/designer-api';
import { Api2DataSource } from './datasources/api2-datasource'; import { Api2DataSource } from './datasources/api2-datasource';
import { Api1DataSource } from './datasources/api1-datasource';
import { verifyToken } from './cognito/cognito-client'; import { verifyToken } from './cognito/cognito-client';
import { CategoryAPI } from './datasources/category-api';
console.log(`dbConfig`, dbConfig); console.log(`dbConfig`, dbConfig);
// Should we convert columns?
const knexConfig = knexStringcase(dbConfig); const knexConfig = knexStringcase(dbConfig);
// TODO: Try with this to see if - tables don't get converted
// delete knexConfig.appWrapIdentifier;
// set up any dataSources our resolvers need // set up any dataSources our resolvers need
const api1 = new Api1DataSource();
const api2 = new Api2DataSource();
const orderApi = new OrderAPI(knexConfig);
const productApi = new ProductAPI(knexConfig);
const categoryApi = new CategoryAPI(knexConfig);
const designerApi = new DesignerAPI(knexConfig);
const dataSources = () => ({ const dataSources = () => ({
orderApi: new OrderAPI(knexConfig), api1,
productApi: new ProductAPI(knexConfig), api2,
designerApi: new DesignerAPI(knexConfig), categoryApi,
api2: new Api2DataSource(), designerApi,
orderApi,
productApi,
}); });
const context = async ({ req }) => { const context = async ({ req }) => {
+24
View File
@@ -0,0 +1,24 @@
import { IResolverObject } from 'graphql-tools';
import { CategoryAPI } from '../datasources/category-api';
import { ProductAPI } from '../datasources/product-api';
const Category: IResolverObject = {
async products({ id }, _args, { dataSources }) {
return (<ProductAPI>dataSources.productApi).getCategoryProducts(id);
},
};
async function getCategories(_, _args, { dataSources }) {
return (<CategoryAPI>dataSources.categoryApi).getCategories();
}
async function getCategory(_, { id }, { dataSources }) {
return (<CategoryAPI>dataSources.categoryApi).getCategoryById(id);
}
export const categoryTypeDefs = { Category };
export const categoryQueryTypeDefs = {
categories: getCategories,
category: getCategory,
};
+1 -1
View File
@@ -1,7 +1,7 @@
import { IResolverObject } from 'graphql-tools'; import { IResolverObject } from 'graphql-tools';
import { DesignerAPI } from '../datasources/designer-api'; import { DesignerAPI } from '../datasources/designer-api';
import { OrderAPI } from '../datasources/order-api'; import { OrderAPI } from '../datasources/order-api';
import { FilterInput } from '../datasources/types'; import { FilterInput } from '../types/types';
const Designer: IResolverObject = { const Designer: IResolverObject = {
async orderRows({ id }, args: FilterInput, { dataSources }) { async orderRows({ id }, args: FilterInput, { dataSources }) {
+3
View File
@@ -1,16 +1,19 @@
import { orderQueryTypeDefs, orderTypeDefs } from './orders-resolver'; import { orderQueryTypeDefs, orderTypeDefs } from './orders-resolver';
import { productQueryTypeDefs, productTypeDefs } from './products-resolver'; import { productQueryTypeDefs, productTypeDefs } from './products-resolver';
import { designerQueryTypeDefs, designerTypeDefs } from './designers-resolver'; import { designerQueryTypeDefs, designerTypeDefs } from './designers-resolver';
import { categoryQueryTypeDefs, categoryTypeDefs } from './categories-resolver';
import { DateTimeResolver, DateResolver, JSONResolver } from 'graphql-scalars'; import { DateTimeResolver, DateResolver, JSONResolver } from 'graphql-scalars';
const resolvers = { const resolvers = {
Query: { Query: {
...orderQueryTypeDefs, ...orderQueryTypeDefs,
...productQueryTypeDefs, ...productQueryTypeDefs,
...designerQueryTypeDefs, ...designerQueryTypeDefs,
...categoryQueryTypeDefs,
}, },
...orderTypeDefs, ...orderTypeDefs,
...productTypeDefs, ...productTypeDefs,
...designerTypeDefs, ...designerTypeDefs,
...categoryTypeDefs,
DateTime: DateTimeResolver, DateTime: DateTimeResolver,
Date: DateResolver, Date: DateResolver,
JSON: JSONResolver, JSON: JSONResolver,
+13 -8
View File
@@ -1,31 +1,36 @@
import { IResolverObject } from 'graphql-tools'; import { IResolverObject } from 'graphql-tools';
import { OrderAPI } from '../datasources/order-api'; import { OrderAPI } from '../datasources/order-api';
import { FilterInput } from '../datasources/types'; import { FilterInput } from '../types/types';
const ContactInformation: IResolverObject = { const ContactInformation: IResolverObject = {
// (parent, args, ctx, info) // (parent, args, ctx, info)
async address({ addressId }, args, { dataSources }) { async address({ addressId }, _args, { dataSources }) {
return (<OrderAPI>dataSources.orderApi).getAddress(addressId); return (<OrderAPI>dataSources.orderApi).getAddress(addressId);
}, },
}; };
const Order: IResolverObject = { const Order: IResolverObject = {
async rows({ id }, args, { dataSources }) { async rows({ id }, _args, { dataSources }) {
return (<OrderAPI>dataSources.orderApi).getOrderRowsByOrderId(id); return (<OrderAPI>dataSources.orderApi).getOrderRowsByOrderId(id);
}, },
async market({ market }, args, { dataSources }) { async market({ market }, _args, { dataSources }) {
return (<OrderAPI>dataSources.orderApi).getMarketByName(market); return (<OrderAPI>dataSources.orderApi).getMarketByName(market);
}, },
}; };
const OrderRow: IResolverObject = { const OrderRow: IResolverObject = {
async order(parent, args, { dataSources }) { async order(parent, _args, { dataSources }) {
return (<OrderAPI>dataSources.orderApi).getOrderById(parent.orderId); return (<OrderAPI>dataSources.orderApi).getOrderById(parent.orderId);
}, },
}; };
async function getOrders(_, args: FilterInput, { dataSources }) { async function getOrdersResult(_, args: FilterInput, { dataSources }) {
return (<OrderAPI>dataSources.orderApi).getOrders(args.filter); const total = (<OrderAPI>dataSources.orderApi).getOrdersTotal(args.filter);
const items = (<OrderAPI>dataSources.orderApi).getOrders(args.filter);
return {
total: total,
items: items,
};
} }
async function getOrder(_, { id }, { dataSources }) { async function getOrder(_, { id }, { dataSources }) {
@@ -39,6 +44,6 @@ export const orderTypeDefs = {
}; };
export const orderQueryTypeDefs = { export const orderQueryTypeDefs = {
orders: getOrders, orders: getOrdersResult,
order: getOrder, order: getOrder,
}; };
+16 -6
View File
@@ -1,22 +1,28 @@
// (parent, args, ctx, info)
import { IResolverObject } from 'graphql-tools'; import { IResolverObject } from 'graphql-tools';
import { Api2DataSource } from '../datasources/api2-datasource'; import { Api2DataSource } from '../datasources/api2-datasource';
import { OrderAPI } from '../datasources/order-api'; import { OrderAPI } from '../datasources/order-api';
import { ProductAPI } from '../datasources/product-api'; import { ProductAPI } from '../datasources/product-api';
import { Product } from '../types/product-types';
import { CategoryAPI } from '../datasources/category-api';
import { Category } from '../types/category-types';
const ProductBlacklist: IResolverObject = { const ProductBlacklist: IResolverObject = {
async market(parent, args, { dataSources }) { async market(parent, _args, { dataSources }) {
return (<OrderAPI>dataSources.orderApi).getMarketById(parent.marketId); return (<OrderAPI>dataSources.orderApi).getMarketById(parent.marketId);
}, },
}; };
const Product: IResolverObject = { const Product: IResolverObject = {
async categories({ id }, args, { dataSources }) { // (parent, args, ctx, info)
return (<ProductAPI>dataSources.productApi).getCategories(id); async categories({ id }, _args, { dataSources }): Promise<Array<Category>> {
return (<CategoryAPI>dataSources.categoryApi).getProductCategories(id);
}, },
async designer({ designerId }, args, { dataSources }) { async designer({ designerId }, args, { dataSources }) {
return dataSources.designerApi.getDesignerById(designerId); return dataSources.designerApi.getDesignerById(designerId);
}, },
async api1json({ id }, _args, { dataSources }): Promise<JSON> {
return dataSources.api1.getProduct(id);
},
}; };
const PrintProduct: IResolverObject = { const PrintProduct: IResolverObject = {
@@ -30,7 +36,11 @@ const PrintProduct: IResolverObject = {
}, },
}; };
async function getProducts(_, { limit, visible, browsable }, { dataSources }) { async function getProducts(
_,
{ limit, visible, browsable },
{ dataSources },
): Promise<Array<Product>> {
return (<ProductAPI>dataSources.productApi).getProducts( return (<ProductAPI>dataSources.productApi).getProducts(
limit, limit,
visible, visible,
@@ -38,7 +48,7 @@ async function getProducts(_, { limit, visible, browsable }, { dataSources }) {
); );
} }
async function getProduct(_, { id }, { dataSources }) { async function getProduct(_, { id }, { dataSources }): Promise<Product> {
return (<ProductAPI>dataSources.productApi).getProduct(id); return (<ProductAPI>dataSources.productApi).getProduct(id);
} }
+22 -1
View File
@@ -6,10 +6,12 @@ const typeDefs = gql`
scalar JSON scalar JSON
type Query { type Query {
orders(filter: FilterInput): [Order] orders(filter: FilterInput!): OrderResult
order(id: ID!): Order order(id: ID!): Order
products(limit: Int, visible: Boolean, browsable: Boolean): [Product] products(limit: Int, visible: Boolean, browsable: Boolean): [Product]
product(id: Int!): Product product(id: Int!): Product
categories: [Category]
category(id: Int!): Category
designers(limit: Int): [Designer] designers(limit: Int): [Designer]
designer(id: Int!): Designer designer(id: Int!): Designer
} }
@@ -21,7 +23,17 @@ const typeDefs = gql`
input FilterInput { input FilterInput {
dates: DateInput dates: DateInput
limit: Int!
offset: Int!
}
"""
Later replace with implements ListResult
"""
type OrderResult {
total: Int!
offset: Int
limit: Int limit: Int
items: [Order]
} }
type Market { type Market {
@@ -157,6 +169,13 @@ const typeDefs = gql`
type Category { type Category {
id: ID! id: ID!
name: String name: String
path: String
isLeaf: Int
depth: Int
childCount: Int
lft: Int
rgt: Int
products: [Product]
} }
type Product { type Product {
@@ -171,6 +190,8 @@ const typeDefs = gql`
categories: [Category] categories: [Category]
designerId: Int designerId: Int
designer: Designer designer: Designer
api1json: JSON
fields_json: JSON
} }
enum ProductGroup { enum ProductGroup {
+10
View File
@@ -0,0 +1,10 @@
export interface Category {
id: number;
name: string;
path: string;
isLeaf: number;
depth: number;
childCount: number;
lft: number;
rgt: number;
}
@@ -34,11 +34,6 @@ export function getProductGroupFromString(group: string): ProductGroup {
return ProductGroup.UNKNOWN; return ProductGroup.UNKNOWN;
} }
} }
export interface Category {
id: number;
name: string;
}
export interface ProductBlacklist { export interface ProductBlacklist {
id: number; id: number;
groupId: number; groupId: number;
@@ -65,4 +60,6 @@ export interface Product {
updated?: Date; updated?: Date;
printProducts: [PrintProduct]; printProducts: [PrintProduct];
blacklisting: [ProductBlacklist]; blacklisting: [ProductBlacklist];
api1json: JSON;
fields_json: JSON;
} }
@@ -8,6 +8,7 @@ export interface DateInput {
export interface GeneralInput { export interface GeneralInput {
dates: DateInput; dates: DateInput;
limit: number; limit: number;
offset: number;
} }
export interface FilterInput { export interface FilterInput {