diff --git a/queries.graphql b/queries.graphql index 5111b44..e3c85e5 100644 --- a/queries.graphql +++ b/queries.graphql @@ -151,3 +151,43 @@ 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 + } + } +} diff --git a/src/__test__/containerSetup.ts b/src/__test__/containerSetup.ts index f923a0e..0b48a12 100644 --- a/src/__test__/containerSetup.ts +++ b/src/__test__/containerSetup.ts @@ -122,6 +122,12 @@ export const createAllTestContainers = ) .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, { version: Math.round(Math.random() * 999999), dbContainer, diff --git a/src/cognito/cognito-client.ts b/src/cognito/cognito-client.ts index 22c053d..f360676 100644 --- a/src/cognito/cognito-client.ts +++ b/src/cognito/cognito-client.ts @@ -1,7 +1,7 @@ import { promisify } from 'util'; import * as Axios from 'axios'; import * as jsonwebtoken from 'jsonwebtoken'; -import { Maybe } from '../datasources/types'; +import { Maybe } from '../types/types'; const jwkToPem = require('jwk-to-pem'); export interface ClaimVerifyRequest { diff --git a/src/datasources/api1-datasource.ts b/src/datasources/api1-datasource.ts new file mode 100644 index 0000000..26b4e2e --- /dev/null +++ b/src/datasources/api1-datasource.ts @@ -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 }, + }); + } +} diff --git a/src/datasources/category-api.ts b/src/datasources/category-api.ts new file mode 100644 index 0000000..fff8d7a --- /dev/null +++ b/src/datasources/category-api.ts @@ -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> { + 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> { + 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 { + return await this.knex + .select('*') + .from('v_categorytree') + .where('id', id) + .first() + .cache(MINUTE) + .then((row) => { + return { + ...row, + path: row.path.replace(/^root/, ''), + }; + }); + } +} diff --git a/src/datasources/designer-api.ts b/src/datasources/designer-api.ts index 53b7547..6d4a30e 100644 --- a/src/datasources/designer-api.ts +++ b/src/datasources/designer-api.ts @@ -1,6 +1,6 @@ import { BaseSQLDataSource } from './BaseSQLDataSource'; -import { Designer } from './designer-types'; -import { Maybe } from './types'; +import { Designer } from '../types/designer-types'; +import { Maybe } from '../types/types'; const MINUTE = 60; export class DesignerAPI extends BaseSQLDataSource { diff --git a/src/datasources/order-api.ts b/src/datasources/order-api.ts index 0f60556..3de0728 100644 --- a/src/datasources/order-api.ts +++ b/src/datasources/order-api.ts @@ -6,10 +6,10 @@ import { Market, Order, OrderRow, -} from './order-types'; -import { GeneralInput, Maybe } from './types'; +} from '../types/order-types'; +import { GeneralInput, Maybe } from '../types/types'; import { convertToPossibleType } from './utils'; -import { getProductGroupStringFromString } from './product-types'; +import { getProductGroupStringFromString } from '../types/product-types'; const MINUTE = 60; export class OrderAPI extends BaseSQLDataSource { @@ -80,9 +80,8 @@ export class OrderAPI extends BaseSQLDataSource { return row; } - async getOrders(input: Maybe): Promise> { - let query = this.knex.select('*').from('orders'); - // .where('inserted', '>=', '2021-03-01T00:00:00Z') + async getOrdersTotal(input: Maybe): Promise { + let query = this.knex.table('orders'); if (input?.dates?.from) { query = query.where('inserted', '>=', input.dates.from); @@ -91,17 +90,37 @@ export class OrderAPI extends BaseSQLDataSource { if (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): Promise> { + let query = this.getOrdersQuery(input); if (input?.limit) { query = query.limit(input?.limit); } - + query = query.orderBy('inserted', 'ASC'); return await query .cache(MINUTE) .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 { return await this.knex .select('*') @@ -115,7 +134,7 @@ export class OrderAPI extends BaseSQLDataSource { async getOrderRowFieldMapping(): Promise { // const fields = await this.knex.select('*').from('order-row_fields'); const fieldsRes = await this.cacheQuery( - 60, + MINUTE * 10, this.knex .raw('SELECT * from "order-row_fields"') .then((data) => data.rows), diff --git a/src/datasources/product-api.ts b/src/datasources/product-api.ts index 50071ae..132b637 100644 --- a/src/datasources/product-api.ts +++ b/src/datasources/product-api.ts @@ -5,9 +5,8 @@ import { ProductGroup, PrintProduct, ProductBlacklist, - Category, -} from './product-types'; -import { Maybe } from './types'; +} from '../types/product-types'; +import { Maybe } from '../types/types'; const MINUTE = 60; @@ -16,35 +15,10 @@ export class ProductAPI extends SQLDataSource { super(config); } - async getCategories(productId: number): Promise> { - 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 { // SELECT materialid, material, (price / 100::float) as price FROM "product-materials" } - // TODO: get product details - async getProductDetails(id: number): Promise { - // SELECT * FROM "product-fields"; - // SELECT * FROM "product-products_fields" WHERE productid = 58941; - return null; - } - getBlacklisting(row: any): Array { if (!row.blacklisting) { return []; @@ -70,6 +44,7 @@ export class ProductAPI extends SQLDataSource { row.blacklisting = this.getBlacklisting(row); row.printProducts = this.getPrintProducts(row); row.designerId = row.designerid; + row.fields_json = row.fields; return row; } @@ -89,9 +64,20 @@ export class ProductAPI extends SQLDataSource { async getProduct(id: number): Promise> { const query = sql.product(id); - const res = await this.knex - .raw(query) - .then((data) => data.rows.map((row) => this.createProductFromRow(row))); + const res = await this.knex.raw(query).then((data) => + data.rows.map((row) => { + const prod = this.createProductFromRow(row); + return prod; + }), + ); return res.find(Boolean); } + + async getCategoryProducts(categoryId: number): Promise> { + const query = sql.categoryProducts(categoryId); + + return await this.knex + .raw(query) + .then((data) => data.rows.map((row) => this.createProductFromRow(row))); + } } diff --git a/src/datasources/sql/products-sql.ts b/src/datasources/sql/products-sql.ts index f70c49a..904a0e4 100644 --- a/src/datasources/sql/products-sql.ts +++ b/src/datasources/sql/products-sql.ts @@ -1,4 +1,4 @@ -import { Maybe } from '../types'; +import { Maybe } from '../../types/types'; // Get syntax highlighting with vscode by installing "Comment tagged templates2 @@ -43,7 +43,12 @@ SELECT products.productid as id, product_blacklist.updated_at ) ) 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 `; @@ -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}); +` + ); +} diff --git a/src/index.ts b/src/index.ts index f4a6040..c5081a0 100644 --- a/src/index.ts +++ b/src/index.ts @@ -10,21 +10,31 @@ import { OrderAPI } from './datasources/order-api'; import { ProductAPI } from './datasources/product-api'; import { DesignerAPI } from './datasources/designer-api'; import { Api2DataSource } from './datasources/api2-datasource'; +import { Api1DataSource } from './datasources/api1-datasource'; + import { verifyToken } from './cognito/cognito-client'; +import { CategoryAPI } from './datasources/category-api'; console.log(`dbConfig`, dbConfig); +// Should we convert columns? 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 +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 = () => ({ - orderApi: new OrderAPI(knexConfig), - productApi: new ProductAPI(knexConfig), - designerApi: new DesignerAPI(knexConfig), - api2: new Api2DataSource(), + api1, + api2, + categoryApi, + designerApi, + orderApi, + productApi, }); const context = async ({ req }) => { diff --git a/src/resolvers/categories-resolver.ts b/src/resolvers/categories-resolver.ts new file mode 100644 index 0000000..c525f3a --- /dev/null +++ b/src/resolvers/categories-resolver.ts @@ -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 (dataSources.productApi).getCategoryProducts(id); + }, +}; + +async function getCategories(_, _args, { dataSources }) { + return (dataSources.categoryApi).getCategories(); +} + +async function getCategory(_, { id }, { dataSources }) { + return (dataSources.categoryApi).getCategoryById(id); +} + +export const categoryTypeDefs = { Category }; + +export const categoryQueryTypeDefs = { + categories: getCategories, + category: getCategory, +}; diff --git a/src/resolvers/designers-resolver.ts b/src/resolvers/designers-resolver.ts index 828104f..44a7458 100644 --- a/src/resolvers/designers-resolver.ts +++ b/src/resolvers/designers-resolver.ts @@ -1,7 +1,7 @@ import { IResolverObject } from 'graphql-tools'; import { DesignerAPI } from '../datasources/designer-api'; import { OrderAPI } from '../datasources/order-api'; -import { FilterInput } from '../datasources/types'; +import { FilterInput } from '../types/types'; const Designer: IResolverObject = { async orderRows({ id }, args: FilterInput, { dataSources }) { diff --git a/src/resolvers/index.ts b/src/resolvers/index.ts index acbc094..df48b10 100644 --- a/src/resolvers/index.ts +++ b/src/resolvers/index.ts @@ -1,16 +1,19 @@ import { orderQueryTypeDefs, orderTypeDefs } from './orders-resolver'; 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'; const resolvers = { Query: { ...orderQueryTypeDefs, ...productQueryTypeDefs, ...designerQueryTypeDefs, + ...categoryQueryTypeDefs, }, ...orderTypeDefs, ...productTypeDefs, ...designerTypeDefs, + ...categoryTypeDefs, DateTime: DateTimeResolver, Date: DateResolver, JSON: JSONResolver, diff --git a/src/resolvers/orders-resolver.ts b/src/resolvers/orders-resolver.ts index ee2f12b..b8a5979 100644 --- a/src/resolvers/orders-resolver.ts +++ b/src/resolvers/orders-resolver.ts @@ -1,31 +1,36 @@ import { IResolverObject } from 'graphql-tools'; import { OrderAPI } from '../datasources/order-api'; -import { FilterInput } from '../datasources/types'; +import { FilterInput } from '../types/types'; const ContactInformation: IResolverObject = { // (parent, args, ctx, info) - async address({ addressId }, args, { dataSources }) { + async address({ addressId }, _args, { dataSources }) { return (dataSources.orderApi).getAddress(addressId); }, }; const Order: IResolverObject = { - async rows({ id }, args, { dataSources }) { + async rows({ id }, _args, { dataSources }) { return (dataSources.orderApi).getOrderRowsByOrderId(id); }, - async market({ market }, args, { dataSources }) { + async market({ market }, _args, { dataSources }) { return (dataSources.orderApi).getMarketByName(market); }, }; const OrderRow: IResolverObject = { - async order(parent, args, { dataSources }) { + async order(parent, _args, { dataSources }) { return (dataSources.orderApi).getOrderById(parent.orderId); }, }; -async function getOrders(_, args: FilterInput, { dataSources }) { - return (dataSources.orderApi).getOrders(args.filter); +async function getOrdersResult(_, args: FilterInput, { dataSources }) { + const total = (dataSources.orderApi).getOrdersTotal(args.filter); + const items = (dataSources.orderApi).getOrders(args.filter); + return { + total: total, + items: items, + }; } async function getOrder(_, { id }, { dataSources }) { @@ -39,6 +44,6 @@ export const orderTypeDefs = { }; export const orderQueryTypeDefs = { - orders: getOrders, + orders: getOrdersResult, order: getOrder, }; diff --git a/src/resolvers/products-resolver.ts b/src/resolvers/products-resolver.ts index 7db4853..e7189f2 100644 --- a/src/resolvers/products-resolver.ts +++ b/src/resolvers/products-resolver.ts @@ -1,22 +1,28 @@ -// (parent, args, ctx, info) import { IResolverObject } from 'graphql-tools'; import { Api2DataSource } from '../datasources/api2-datasource'; import { OrderAPI } from '../datasources/order-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 = { - async market(parent, args, { dataSources }) { + async market(parent, _args, { dataSources }) { return (dataSources.orderApi).getMarketById(parent.marketId); }, }; const Product: IResolverObject = { - async categories({ id }, args, { dataSources }) { - return (dataSources.productApi).getCategories(id); + // (parent, args, ctx, info) + async categories({ id }, _args, { dataSources }): Promise> { + return (dataSources.categoryApi).getProductCategories(id); }, async designer({ designerId }, args, { dataSources }) { return dataSources.designerApi.getDesignerById(designerId); }, + async api1json({ id }, _args, { dataSources }): Promise { + return dataSources.api1.getProduct(id); + }, }; 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> { return (dataSources.productApi).getProducts( limit, visible, @@ -38,7 +48,7 @@ async function getProducts(_, { limit, visible, browsable }, { dataSources }) { ); } -async function getProduct(_, { id }, { dataSources }) { +async function getProduct(_, { id }, { dataSources }): Promise { return (dataSources.productApi).getProduct(id); } diff --git a/src/schema.ts b/src/schema.ts index f942e63..3ddf788 100644 --- a/src/schema.ts +++ b/src/schema.ts @@ -6,10 +6,12 @@ const typeDefs = gql` scalar JSON type Query { - orders(filter: FilterInput): [Order] + orders(filter: FilterInput!): OrderResult order(id: ID!): Order products(limit: Int, visible: Boolean, browsable: Boolean): [Product] product(id: Int!): Product + categories: [Category] + category(id: Int!): Category designers(limit: Int): [Designer] designer(id: Int!): Designer } @@ -21,7 +23,17 @@ const typeDefs = gql` input FilterInput { dates: DateInput + limit: Int! + offset: Int! + } + """ + Later replace with implements ListResult + """ + type OrderResult { + total: Int! + offset: Int limit: Int + items: [Order] } type Market { @@ -157,6 +169,13 @@ const typeDefs = gql` type Category { id: ID! name: String + path: String + isLeaf: Int + depth: Int + childCount: Int + lft: Int + rgt: Int + products: [Product] } type Product { @@ -171,6 +190,8 @@ const typeDefs = gql` categories: [Category] designerId: Int designer: Designer + api1json: JSON + fields_json: JSON } enum ProductGroup { diff --git a/src/types/category-types.ts b/src/types/category-types.ts new file mode 100644 index 0000000..69f3653 --- /dev/null +++ b/src/types/category-types.ts @@ -0,0 +1,10 @@ +export interface Category { + id: number; + name: string; + path: string; + isLeaf: number; + depth: number; + childCount: number; + lft: number; + rgt: number; +} diff --git a/src/datasources/designer-types.ts b/src/types/designer-types.ts similarity index 100% rename from src/datasources/designer-types.ts rename to src/types/designer-types.ts diff --git a/src/datasources/order-types.ts b/src/types/order-types.ts similarity index 100% rename from src/datasources/order-types.ts rename to src/types/order-types.ts diff --git a/src/datasources/product-types.ts b/src/types/product-types.ts similarity index 95% rename from src/datasources/product-types.ts rename to src/types/product-types.ts index 7d9add1..1879244 100644 --- a/src/datasources/product-types.ts +++ b/src/types/product-types.ts @@ -34,11 +34,6 @@ export function getProductGroupFromString(group: string): ProductGroup { return ProductGroup.UNKNOWN; } } - -export interface Category { - id: number; - name: string; -} export interface ProductBlacklist { id: number; groupId: number; @@ -65,4 +60,6 @@ export interface Product { updated?: Date; printProducts: [PrintProduct]; blacklisting: [ProductBlacklist]; + api1json: JSON; + fields_json: JSON; } diff --git a/src/datasources/types.ts b/src/types/types.ts similarity index 92% rename from src/datasources/types.ts rename to src/types/types.ts index 7a5540d..629bf0d 100644 --- a/src/datasources/types.ts +++ b/src/types/types.ts @@ -8,6 +8,7 @@ export interface DateInput { export interface GeneralInput { dates: DateInput; limit: number; + offset: number; } export interface FilterInput {