From 3c648cee29a089a6467c165f51b39054e44a55d6 Mon Sep 17 00:00:00 2001 From: Niklas Fondberg Date: Thu, 8 Apr 2021 23:11:24 +0200 Subject: [PATCH] Added designers --- package-lock.json | 15 +++ package.json | 1 + queries.graphql | 95 +++++++++----- src/datasources/BaseSQLDataSource.ts | 4 + src/datasources/designer-api.ts | 42 +++++++ src/datasources/designer-types.ts | 5 + src/datasources/order-api.ts | 182 +++++++++++++++------------ src/datasources/order-types.ts | 2 + src/datasources/product-api.ts | 8 +- src/datasources/sql/products-sql.ts | 2 +- src/datasources/types.ts | 14 +++ src/index.ts | 5 + src/resolvers/datetime-type.ts | 27 +++- src/resolvers/designers-resolver.ts | 28 +++++ src/resolvers/index.ts | 14 ++- src/resolvers/orders-resolver.ts | 16 +-- src/resolvers/products-resolver.ts | 17 ++- src/schema.ts | 40 +++--- 18 files changed, 366 insertions(+), 151 deletions(-) create mode 100644 src/datasources/designer-api.ts create mode 100644 src/datasources/designer-types.ts create mode 100644 src/resolvers/designers-resolver.ts diff --git a/package-lock.json b/package-lock.json index f9bd791..2eddace 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2684,6 +2684,21 @@ } } }, + "graphql-scalars": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/graphql-scalars/-/graphql-scalars-1.9.0.tgz", + "integrity": "sha512-31bBDnHdBapb2wknLCjNzTSjKfVEtm+0HxI7DKM7jQ4Uipk1o1aMUCYCkYunmRDdgQaI03u1MD5KutLf7yHnvw==", + "requires": { + "tslib": "~2.1.0" + }, + "dependencies": { + "tslib": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.1.0.tgz", + "integrity": "sha512-hcVC3wYEziELGGmEEXue7D75zbwIIVUMWAVbHItGPx0ziyXxrOMQx4rQEVEV45Ut/1IotuEvwqPopzIOkDMf0A==" + } + } + }, "graphql-subscriptions": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/graphql-subscriptions/-/graphql-subscriptions-1.2.1.tgz", diff --git a/package.json b/package.json index 75ede10..58f1c87 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,7 @@ "datasource-sql": "^1.4.0", "dotenv": "^8.2.0", "graphql": "^15.5.0", + "graphql-scalars": "^1.9.0", "knex-stringcase": "^1.4.5", "nodemon": "^2.0.7", "pg": "^8.5.1", diff --git a/queries.graphql b/queries.graphql index 9c51176..55d791d 100644 --- a/queries.graphql +++ b/queries.graphql @@ -20,44 +20,63 @@ } { - order(id: 588274) { + product(id: 42280) { id + inserted + updated + visible + browsable + designerId + designer { + id + name + path + } + printProducts { + group + id + updated + } + blacklisting { + group + id + marketId + updated + } + } +} + +{ + orders( + filter: { limit: 1, dates: { from: "2021-03-01", to: "2021-04-30" } } + ) { + id + marketObject { + id + name + vat + currency + priceAdjustment + } rows { id - } - billingInformation { - email - phone - address { + order { id - firstname - lastname - recipientName - companyname - address1 - address2 - countryCode - city - zipcode - stateCountyOrRegion - } - } - deliveryInformation { - email - phone - address { - id - firstname - lastname - recipientName - companyname - address1 - address2 - countryCode - city - zipcode - stateCountyOrRegion } + inserted + orderId + productId + productGroup + artNo + path + name + price + designerId + commissionAmount + status + pwintyImageId + frameColor + pwintySku } } } @@ -77,7 +96,7 @@ order { id } - insertedDate + inserted orderId productId productGroup @@ -94,3 +113,11 @@ } } } + +{ + designers(limit: 10) { + id + name + path + } +} diff --git a/src/datasources/BaseSQLDataSource.ts b/src/datasources/BaseSQLDataSource.ts index 9d3fd8e..e1b579b 100644 --- a/src/datasources/BaseSQLDataSource.ts +++ b/src/datasources/BaseSQLDataSource.ts @@ -9,6 +9,10 @@ export class BaseSQLDataSource extends SQLDataSource { this.cache = config.cache || new InMemoryLRUCache(); } + getSQLDate(date: Date): string { + return date.toISOString().split('T')[0]; + } + async cacheQuery(ttl = 5, query) { const cacheKey = crypto .createHash('sha1') diff --git a/src/datasources/designer-api.ts b/src/datasources/designer-api.ts new file mode 100644 index 0000000..53b7547 --- /dev/null +++ b/src/datasources/designer-api.ts @@ -0,0 +1,42 @@ +import { BaseSQLDataSource } from './BaseSQLDataSource'; +import { Designer } from './designer-types'; +import { Maybe } from './types'; + +const MINUTE = 60; +export class DesignerAPI extends BaseSQLDataSource { + constructor(config) { + super(config); + } + + async getDesignerById(id: number): Promise { + return await this.knex + .select('*') + .from('designers') + .where('designerid', id) + .first() + .cache(MINUTE) + .then((row) => { + return { + ...row, + id: row.designerid, + }; + }); + } + + async getDesigners(limit: Maybe): Promise> { + limit = limit ?? 100; + return await this.knex + .select('*') + .from('designers') + .limit(limit) + .cache(MINUTE) + .then((rows) => { + return rows.map((row) => { + return { + ...row, + id: row.designerid, + }; + }); + }); + } +} diff --git a/src/datasources/designer-types.ts b/src/datasources/designer-types.ts new file mode 100644 index 0000000..0b9004b --- /dev/null +++ b/src/datasources/designer-types.ts @@ -0,0 +1,5 @@ +export interface Designer { + id: number; + name: string; + path: string; +} diff --git a/src/datasources/order-api.ts b/src/datasources/order-api.ts index 8dda540..465a32d 100644 --- a/src/datasources/order-api.ts +++ b/src/datasources/order-api.ts @@ -1,3 +1,5 @@ +import { SQLDataSource } from 'datasource-sql'; + import { BaseSQLDataSource } from './BaseSQLDataSource'; import { camelcase } from 'stringcase'; import { @@ -7,11 +9,10 @@ import { Order, OrderRow, } from './order-types'; -import { Maybe } from './types'; +import { GeneralInput, Maybe } from './types'; const MINUTE = 60; export class OrderAPI extends BaseSQLDataSource { - cache: any; constructor(config) { super(config); } @@ -65,28 +66,40 @@ export class OrderAPI extends BaseSQLDataSource { getDeliveryInformation(row): ContactInformation { return { - email: 'niklas.fondberg@photowall.se', + email: 'niklas.fondberg@photowall.se', // TODO: fix hardcoded phone: '+46761386397', addressId: row.deliveryAddressId, }; } - async getOrders(): Promise> { - return await this.knex - .select('*') - .from('orders') - .where('inserted', '>=', '2021-03-01T00:00:00Z') - .cache(MINUTE) - .then((rows) => - rows.map((row) => { - row.billingInformation = this.getBillingInformation(row); - row.deliveryInformation = this.getDeliveryInformation(row); - return row; - }), - ); + async getOrders(input: Maybe): Promise> { + let query = this.knex.select('*').from('orders'); + // .where('inserted', '>=', '2021-03-01T00:00:00Z') + + if (input?.dates?.from) { + query = query.where('inserted', '>=', input.dates.from); + } + + if (input?.dates?.to) { + query = query.where('inserted', '<=', input.dates.to); + } + query = query.orderBy('inserted', 'ASC'); + + if (input?.limit) { + query = query.limit(input?.limit); + } + + // console.log(query.toString()); + return await query.cache(MINUTE).then((rows) => + rows.map((row) => { + row.billingInformation = this.getBillingInformation(row); + row.deliveryInformation = this.getDeliveryInformation(row); + return row; + }), + ); } - async getOrder(id: number): Promise { + async getOrderById(id: number): Promise { return await this.knex .select('*') .from('orders') @@ -100,90 +113,99 @@ export class OrderAPI extends BaseSQLDataSource { }); } - async getOrderRows(orderId: number): Promise> { + async getOrderRowFieldMapping(): Promise { + // const fields = await this.knex.select('*').from('order-row_fields'); const fieldsRes = await this.cacheQuery( - 120, + 60, this.knex .raw('SELECT * from "order-row_fields"') .then((data) => data.rows), ); - const fields = fieldsRes.reduce((obj, item) => { + return fieldsRes.reduce((obj, item) => { obj[item.fieldid] = item.name; return obj; }, {}); + } - const rows = await this.knex - .raw('SELECT * FROM "order-rows" WHERE orderid = ?', orderId) + /** + * Gets the row details + */ + async getOrderRowDetailsForRowId(rowId: number): Promise { + const details = await this.knex + .raw('SELECT * FROM "order-rows_details" WHERE rowid = ?', rowId) .then((data) => data.rows); + // Skip empty order rows + if (!details.length) { + return null; + } + const fields = await this.getOrderRowFieldMapping(); + + const orderRow = details.reduce((obj, item) => { + obj[camelcase(fields[item.fieldid])] = item.value; + return obj; + }, {}); + + if ('group' in orderRow) { + orderRow['productGroup'] = orderRow['group']; + } + return orderRow; + } + + /** + * Takes input from order-rows query and gets the row details + */ + async getOrderRows(rows: any): Promise> { let orderRows = []; + for (let i = 0; i < rows.length; i++) { const rowId = rows[i].rowid; - const details = await this.knex - .raw('SELECT * FROM "order-rows_details" WHERE rowid = ?', rowId) - .then((data) => data.rows); - - // Skip empty order rows - if (!details.length) { + const orderRow = await this.getOrderRowDetailsForRowId(rowId); + if (!orderRow) { continue; } - const orderRow = details.reduce((obj, item) => { - obj[camelcase(fields[item.fieldid])] = item.value; - return obj; - }, {}); - - if ('group' in orderRow) { - orderRow['productGroup'] = orderRow['group']; - } + // take some data from order-rows results orderRow['id'] = rowId; - orderRow['orderId'] = orderId; + orderRow['orderId'] = rows[i].orderid; + orderRow['inserted'] = rows[i].inserted; + orderRows.push(orderRow); } return orderRows; } + + async getOrderRowsByOrderId(orderId: number): Promise> { + const rows = await this.knex + .raw('SELECT * FROM "order-rows" WHERE orderid = ?', orderId) + .then((data) => data.rows); + + return await this.getOrderRows(rows); + } + + async getOrderRowsByDesignerId( + designerId: number, + input: Maybe, + ): Promise> { + const query = /* sql */ ` + SELECT orderdetails.*, "order-rows".orderid + FROM "order-rows_details" orderdetails + JOIN "order-rows" ON "order-rows".rowid = orderdetails.rowid + WHERE orderdetails.fieldid = 105 + AND orderdetails.value = ? + AND orderdetails.inserted >= ? + AND orderdetails.inserted <= ? + ORDER BY orderdetails.inserted ASC + ${input?.limit ? `LIMIT ${input.limit}` : ''} + `; + + const from = input?.dates?.from ? input.dates.from : new Date('2000-01-01'); + const to = input?.dates?.to ? input.dates.to : new Date(); + + const rows = await this.knex + .raw(query, [designerId, this.getSQLDate(from), this.getSQLDate(to)]) + .then((data) => data.rows); + return await this.getOrderRows(rows); + } } - -/** - * row -{ - printId: '73963', - inserted: 2021-02-04T10:11:41.093Z, - productId: '54192', - group: 'photo-wallpaper', - type: 'scaling', - artNo: 'e50075', - path: 'flora-hysterica-4', - name: 'Flora Hysterica 4', - price: '587.092480', - width: '640', - height: '260', - x: '0.000000', - y: '0.135133', - weight: '1', - designer: 'martin-bergstrom', - designerId: '201', - commission: '0', - commissionResale: '0', - status: 'print', - process: 'true', - vat: '1.210000', - commissionAmount: '0.000000', - framed: '0', - mirrored: '0', - edge: '0', - imageprocessed: 'true', - imageprocessingrejected: 'false', - resolution: '150', - imagedonotprint: 'false', - material: 'premium-wallpaper', - discountType: '%', - discountValue: '25', - measureUnit: 'cm', - displayWidth: '640', - displayHeight: '260', - designerPath: 'martin-bergstrom' - }, - - */ diff --git a/src/datasources/order-types.ts b/src/datasources/order-types.ts index 23d6fce..3499c1b 100644 --- a/src/datasources/order-types.ts +++ b/src/datasources/order-types.ts @@ -1,3 +1,5 @@ +import { DateInput } from './types'; + export interface Market { id: number; name: string; diff --git a/src/datasources/product-api.ts b/src/datasources/product-api.ts index 077f05c..ead08ca 100644 --- a/src/datasources/product-api.ts +++ b/src/datasources/product-api.ts @@ -45,11 +45,12 @@ export class ProductAPI extends SQLDataSource { } getPrintProducts(row: any): Array { - if (!row.printProducts) { + if (!row.printproducts) { return []; } - return row.printProducts.map((pp: any) => { + return row.printproducts.map((pp: any) => { pp.group = ProductGroup[pp.groupId]; + pp.id = pp.printId; return pp; }); } @@ -66,6 +67,7 @@ export class ProductAPI extends SQLDataSource { data.rows.map((row) => { row.blacklisting = this.getBlacklisting(row); row.printProducts = this.getPrintProducts(row); + row.designerId = row.designerid; return row; }), ); @@ -76,8 +78,10 @@ export class ProductAPI extends SQLDataSource { const res = await this.knex.raw(query).then((data) => data.rows.map((row) => { + console.log(row); row.blacklisting = this.getBlacklisting(row); row.printProducts = this.getPrintProducts(row); + row.designerId = row.designerid; return row; }), ); diff --git a/src/datasources/sql/products-sql.ts b/src/datasources/sql/products-sql.ts index 3fd2be6..f70c49a 100644 --- a/src/datasources/sql/products-sql.ts +++ b/src/datasources/sql/products-sql.ts @@ -26,7 +26,7 @@ SELECT products.productid as id, "product-printproducts".updated ) ) FROM "product-printproducts" WHERE "product-printproducts".productid = products.productid - ) AS printProducts, + ) AS printproducts, ( SELECT json_agg( json_build_object( 'id', diff --git a/src/datasources/types.ts b/src/datasources/types.ts index d798e4f..7a5540d 100644 --- a/src/datasources/types.ts +++ b/src/datasources/types.ts @@ -1 +1,15 @@ export type Maybe = T | undefined; + +export interface DateInput { + from: Date; + to: Date; +} + +export interface GeneralInput { + dates: DateInput; + limit: number; +} + +export interface FilterInput { + filter: GeneralInput; +} diff --git a/src/index.ts b/src/index.ts index 26ea246..47dbdb6 100644 --- a/src/index.ts +++ b/src/index.ts @@ -8,13 +8,18 @@ import { typeDefs } from './schema'; import resolvers from './resolvers'; import { OrderAPI } from './datasources/order-api'; import { ProductAPI } from './datasources/product-api'; +import { DesignerAPI } from './datasources/designer-api'; 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 dataSources = () => ({ orderApi: new OrderAPI(knexConfig), productApi: new ProductAPI(knexConfig), + designerApi: new DesignerAPI(knexConfig), }); const context = async ({ req }) => { diff --git a/src/resolvers/datetime-type.ts b/src/resolvers/datetime-type.ts index fb87b29..c56afc9 100644 --- a/src/resolvers/datetime-type.ts +++ b/src/resolvers/datetime-type.ts @@ -2,9 +2,9 @@ import { GraphQLScalarType } from 'graphql'; import { Kind } from 'graphql/language'; // possible switch to https://github.com/Urigo/graphql-scalars -export const DateTimeScalar = new GraphQLScalarType({ +const DateTimeScalar = new GraphQLScalarType({ name: 'DateTime', - description: 'Date custom scalar type', + description: 'Date time custom scalar type', parseValue(value) { return new Date(value); // value from the client }, @@ -18,3 +18,26 @@ export const DateTimeScalar = new GraphQLScalarType({ return null; }, }); + +const DateScalar = new GraphQLScalarType({ + name: 'DateTime', + description: 'Date custom scalar type', + parseValue(value) { + console.log('FROM CLIENT:', value); + return new Date(value); // value from the client + }, + serialize(value) { + return new Date(value).toISOString().split('T')[0]; // value sent to the client + }, + parseLiteral(ast) { + if (ast.kind === Kind.INT) { + return parseInt(ast.value, 10); // ast value is always in string format + } + return null; + }, +}); + +export const CustomTypes = { + DateTime: DateTimeScalar, + Date: DateScalar, +}; diff --git a/src/resolvers/designers-resolver.ts b/src/resolvers/designers-resolver.ts new file mode 100644 index 0000000..828104f --- /dev/null +++ b/src/resolvers/designers-resolver.ts @@ -0,0 +1,28 @@ +import { IResolverObject } from 'graphql-tools'; +import { DesignerAPI } from '../datasources/designer-api'; +import { OrderAPI } from '../datasources/order-api'; +import { FilterInput } from '../datasources/types'; + +const Designer: IResolverObject = { + async orderRows({ id }, args: FilterInput, { dataSources }) { + return (dataSources.orderApi).getOrderRowsByDesignerId( + id, + args.filter, + ); + }, +}; + +async function getDesigners(_, { limit }, { dataSources }) { + return (dataSources.designerApi).getDesigners(limit); +} + +async function getDesigner(_, { id }, { dataSources }) { + return (dataSources.designerApi).getDesignerById(id); +} + +export const designerTypeDefs = { Designer }; + +export const designerQueryTypeDefs = { + designers: getDesigners, + designer: getDesigner, +}; diff --git a/src/resolvers/index.ts b/src/resolvers/index.ts index 2215810..a5599a1 100644 --- a/src/resolvers/index.ts +++ b/src/resolvers/index.ts @@ -1,12 +1,18 @@ import { orderQueryTypeDefs, orderTypeDefs } from './orders-resolver'; import { productQueryTypeDefs, productTypeDefs } from './products-resolver'; -import { DateTimeScalar } from './datetime-type'; - +import { designerQueryTypeDefs, designerTypeDefs } from './designers-resolver'; +import { DateTimeResolver, DateResolver } from 'graphql-scalars'; const resolvers = { - Query: { ...orderQueryTypeDefs, ...productQueryTypeDefs }, - DateTime: DateTimeScalar, + Query: { + ...orderQueryTypeDefs, + ...productQueryTypeDefs, + ...designerQueryTypeDefs, + }, ...orderTypeDefs, ...productTypeDefs, + ...designerTypeDefs, + DateTime: DateTimeResolver, + Date: DateResolver, }; export default resolvers; diff --git a/src/resolvers/orders-resolver.ts b/src/resolvers/orders-resolver.ts index a9f8c91..897b37f 100644 --- a/src/resolvers/orders-resolver.ts +++ b/src/resolvers/orders-resolver.ts @@ -1,33 +1,35 @@ import { IResolverObject } from 'graphql-tools'; +import { OrderAPI } from '../datasources/order-api'; +import { FilterInput } from '../datasources/types'; const ContactInformation: IResolverObject = { // (parent, args, ctx, info) async address({ addressId }, args, { dataSources }) { - return dataSources.orderApi.getAddress(addressId); + return (dataSources.orderApi).getAddress(addressId); }, }; const Order: IResolverObject = { async rows({ id }, args, { dataSources }) { - return dataSources.orderApi.getOrderRows(id); + return (dataSources.orderApi).getOrderRowsByOrderId(id); }, async marketObject({ market }, args, { dataSources }) { - return dataSources.orderApi.getMarketByName(market); + return (dataSources.orderApi).getMarketByName(market); }, }; const OrderRow: IResolverObject = { async order(parent, args, { dataSources }) { - return dataSources.orderApi.getOrder(parent.orderId); + return (dataSources.orderApi).getOrderById(parent.orderId); }, }; -async function getOrders(_, __, { dataSources }) { - return dataSources.orderApi.getOrders(); +async function getOrders(_, args: FilterInput, { dataSources }) { + return (dataSources.orderApi).getOrders(args.filter); } async function getOrder(_, { id }, { dataSources }) { - return dataSources.orderApi.getOrder(id); + return (dataSources.orderApi).getOrderById(id); } export const orderTypeDefs = { diff --git a/src/resolvers/products-resolver.ts b/src/resolvers/products-resolver.ts index c4aa670..f40bd3f 100644 --- a/src/resolvers/products-resolver.ts +++ b/src/resolvers/products-resolver.ts @@ -1,24 +1,33 @@ // (parent, args, ctx, info) import { IResolverObject } from 'graphql-tools'; +import { OrderAPI } from '../datasources/order-api'; +import { ProductAPI } from '../datasources/product-api'; const ProductBlacklist: IResolverObject = { async market(parent, args, { dataSources }) { - return dataSources.orderApi.getMarketById(parent.marketId); + return (dataSources.orderApi).getMarketById(parent.marketId); }, }; const Product: IResolverObject = { async categories({ id }, args, { dataSources }) { - return dataSources.productApi.getCategories(id); + return (dataSources.productApi).getCategories(id); + }, + async designer({ designerId }, args, { dataSources }) { + return dataSources.designerApi.getDesignerById(designerId); }, }; async function getProducts(_, { limit, visible, browsable }, { dataSources }) { - return dataSources.productApi.getProducts(limit, visible, browsable); + return (dataSources.productApi).getProducts( + limit, + visible, + browsable, + ); } async function getProduct(_, { id }, { dataSources }) { - return dataSources.productApi.getProduct(id); + return (dataSources.productApi).getProduct(id); } export const productTypeDefs = { diff --git a/src/schema.ts b/src/schema.ts index d4c8f2b..1a42d46 100644 --- a/src/schema.ts +++ b/src/schema.ts @@ -2,12 +2,25 @@ import { gql } from 'apollo-server'; const typeDefs = gql` scalar DateTime + scalar Date type Query { - orders: [Order] + orders(filter: FilterInput): [Order] order(id: ID!): Order products(limit: Int, visible: Boolean, browsable: Boolean): [Product] product(id: Int!): Product + designers(limit: Int): [Designer] + designer(id: Int!): Designer + } + + input DateInput { + from: Date + to: Date + } + + input FilterInput { + dates: DateInput + limit: Int } type Market { @@ -125,6 +138,8 @@ const typeDefs = gql` printProducts: [PrintProduct] blacklisting: [ProductBlacklist] categories: [Category] + designerId: Int + designer: Designer } enum ProductGroup { @@ -154,22 +169,13 @@ const typeDefs = gql` inserted: DateTime! updated: DateTime } + + type Designer { + id: ID! + name: String + path: String + orderRows(filter: FilterInput): [OrderRow] + } `; export { typeDefs }; -/** -type Designer { - id: ID! - name: String - orderRows(fromDate: Date, toDate: Date): [OrderRow] -} - -type Product { - designer: Designer -} - -type Query { - designer(id: Int!): Designer - designers: [Designer] -} -*/