Added designers

This commit is contained in:
Niklas Fondberg
2021-04-08 23:11:24 +02:00
parent 2b57afbc4a
commit 3c648cee29
18 changed files with 366 additions and 151 deletions
+15
View File
@@ -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",
+1
View File
@@ -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",
+61 -34
View File
@@ -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
}
}
+4
View File
@@ -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')
+42
View File
@@ -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<Designer> {
return await this.knex
.select('*')
.from('designers')
.where('designerid', id)
.first()
.cache(MINUTE)
.then((row) => {
return {
...row,
id: row.designerid,
};
});
}
async getDesigners(limit: Maybe<number>): Promise<Array<Designer>> {
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,
};
});
});
}
}
+5
View File
@@ -0,0 +1,5 @@
export interface Designer {
id: number;
name: string;
path: string;
}
+102 -80
View File
@@ -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<Array<Order>> {
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<GeneralInput>): Promise<Array<Order>> {
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<Order> {
async getOrderById(id: number): Promise<Order> {
return await this.knex
.select('*')
.from('orders')
@@ -100,90 +113,99 @@ export class OrderAPI extends BaseSQLDataSource {
});
}
async getOrderRows(orderId: number): Promise<Array<OrderRow>> {
async getOrderRowFieldMapping(): Promise<any> {
// 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<any> {
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<Array<OrderRow>> {
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<Array<OrderRow>> {
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<GeneralInput>,
): Promise<Array<OrderRow>> {
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'
},
*/
+2
View File
@@ -1,3 +1,5 @@
import { DateInput } from './types';
export interface Market {
id: number;
name: string;
+6 -2
View File
@@ -45,11 +45,12 @@ export class ProductAPI extends SQLDataSource {
}
getPrintProducts(row: any): Array<PrintProduct> {
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;
}),
);
+1 -1
View File
@@ -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',
+14
View File
@@ -1 +1,15 @@
export type Maybe<T> = T | undefined;
export interface DateInput {
from: Date;
to: Date;
}
export interface GeneralInput {
dates: DateInput;
limit: number;
}
export interface FilterInput {
filter: GeneralInput;
}
+5
View File
@@ -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 }) => {
+25 -2
View File
@@ -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,
};
+28
View File
@@ -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 (<OrderAPI>dataSources.orderApi).getOrderRowsByDesignerId(
id,
args.filter,
);
},
};
async function getDesigners(_, { limit }, { dataSources }) {
return (<DesignerAPI>dataSources.designerApi).getDesigners(limit);
}
async function getDesigner(_, { id }, { dataSources }) {
return (<DesignerAPI>dataSources.designerApi).getDesignerById(id);
}
export const designerTypeDefs = { Designer };
export const designerQueryTypeDefs = {
designers: getDesigners,
designer: getDesigner,
};
+10 -4
View File
@@ -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;
+9 -7
View File
@@ -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 (<OrderAPI>dataSources.orderApi).getAddress(addressId);
},
};
const Order: IResolverObject = {
async rows({ id }, args, { dataSources }) {
return dataSources.orderApi.getOrderRows(id);
return (<OrderAPI>dataSources.orderApi).getOrderRowsByOrderId(id);
},
async marketObject({ market }, args, { dataSources }) {
return dataSources.orderApi.getMarketByName(market);
return (<OrderAPI>dataSources.orderApi).getMarketByName(market);
},
};
const OrderRow: IResolverObject = {
async order(parent, args, { dataSources }) {
return dataSources.orderApi.getOrder(parent.orderId);
return (<OrderAPI>dataSources.orderApi).getOrderById(parent.orderId);
},
};
async function getOrders(_, __, { dataSources }) {
return dataSources.orderApi.getOrders();
async function getOrders(_, args: FilterInput, { dataSources }) {
return (<OrderAPI>dataSources.orderApi).getOrders(args.filter);
}
async function getOrder(_, { id }, { dataSources }) {
return dataSources.orderApi.getOrder(id);
return (<OrderAPI>dataSources.orderApi).getOrderById(id);
}
export const orderTypeDefs = {
+13 -4
View File
@@ -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 (<OrderAPI>dataSources.orderApi).getMarketById(parent.marketId);
},
};
const Product: IResolverObject = {
async categories({ id }, args, { dataSources }) {
return dataSources.productApi.getCategories(id);
return (<ProductAPI>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 (<ProductAPI>dataSources.productApi).getProducts(
limit,
visible,
browsable,
);
}
async function getProduct(_, { id }, { dataSources }) {
return dataSources.productApi.getProduct(id);
return (<ProductAPI>dataSources.productApi).getProduct(id);
}
export const productTypeDefs = {
+23 -17
View File
@@ -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]
}
*/