From 964a904f422ff62989872553c4561a23241de5a8 Mon Sep 17 00:00:00 2001 From: Niklas Fondberg Date: Fri, 20 Aug 2021 08:16:01 +0200 Subject: [PATCH] P5 7619 refactor products to return list (#11) --- Makefile | 9 ++++ queries.graphql | 45 ++++++++++++++++ src/__test__/all.test.ts | 4 +- src/datasources/category-api.ts | 2 +- src/datasources/order-api.ts | 27 +++++----- src/datasources/product-api.ts | 25 ++++++--- src/datasources/sql/products-sql.ts | 29 ++++++++--- src/index.ts | 2 +- src/resolvers/designers-resolver.ts | 9 ++-- src/resolvers/orders-resolver.ts | 19 +++++-- src/resolvers/products-resolver.ts | 34 ++++++++----- src/schema.ts | 79 ++++++++++++++++++----------- src/types/order-types.ts | 7 +++ src/types/product-types.ts | 16 ++++++ src/types/types.ts | 20 ++++++-- 15 files changed, 235 insertions(+), 92 deletions(-) create mode 100644 Makefile diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..1c53475 --- /dev/null +++ b/Makefile @@ -0,0 +1,9 @@ + +publish-to-staging: + @current_branch=$$(git rev-parse --abbrev-ref HEAD); \ + git branch -D staging 2>/dev/null; \ + git checkout -b staging; \ + git merge $$current_branch && \ + git push -f origin staging && \ + git checkout $$current_branch; \ + echo Pushed $$current_branch to staging diff --git a/queries.graphql b/queries.graphql index 2f8bb29..08a2b6a 100644 --- a/queries.graphql +++ b/queries.graphql @@ -339,3 +339,48 @@ priceAdjustment } } + +# for pwinty +{ + order(id: 562333) { + id + pwintyId + locale { + value + } + deliveryInformation { + address { + recipientName + companyname + address1 + address2 + city + countryCode + stateCountyOrRegion + zipcode + } + email + phone + } + billingInformation { + address { + recipientName + companyname + address1 + address2 + city + countryCode + stateCountyOrRegion + zipcode + } + email + phone + } + rows { + id + pwintySku + pwintyImageId + frameColor + } + } +} diff --git a/src/__test__/all.test.ts b/src/__test__/all.test.ts index 3b5a111..1db61d3 100644 --- a/src/__test__/all.test.ts +++ b/src/__test__/all.test.ts @@ -114,7 +114,7 @@ describe('GraphQL Apollo tests', () => { id name path - orderRows { + orderRows(pagination: {limit: 1000}) { name artNo } @@ -156,7 +156,7 @@ describe('GraphQL Apollo tests', () => { id name path - orderRows { + orderRows(pagination: {limit: 1000}) { name, artNo } diff --git a/src/datasources/category-api.ts b/src/datasources/category-api.ts index 72d6ae6..57b7d90 100644 --- a/src/datasources/category-api.ts +++ b/src/datasources/category-api.ts @@ -43,7 +43,7 @@ WHERE product_category.product_id = ? /** * TODO: should we have these? - * category keywords + * category keywords * SELECT category_keyword.category_id, keywords.value FROM category_keyword diff --git a/src/datasources/order-api.ts b/src/datasources/order-api.ts index e0b9da9..eb61602 100644 --- a/src/datasources/order-api.ts +++ b/src/datasources/order-api.ts @@ -6,7 +6,7 @@ import { Order, OrderRow, } from '../types/order-types'; -import { GeneralInput, Maybe } from '../types/types'; +import { DateFilterInput, GeneralInput, Maybe } from '../types/types'; import { convertToPossibleType } from './utils'; import { getProductGroupStringFromString } from '../types/product-types'; @@ -54,10 +54,9 @@ export class OrderAPI extends BaseSQLDataSource { } createOrderFromRow(row: any): Order { + row.__resolveType = 'Order'; row.billingInformation = this.getBillingInformation(row); row.deliveryInformation = this.getDeliveryInformation(row); - row.marketName = row.market; - row.localeName = row.locale; return row; } @@ -71,11 +70,9 @@ export class OrderAPI extends BaseSQLDataSource { } async getOrders(input: Maybe): Promise> { - let query = this.getOrdersQuery(input); + let query = this.getOrdersQuery(input).limit(input?.pagination.limit); - if (input?.limit) { - query = query.limit(input?.limit); - } + // TODO: handle offset query = query.orderBy('inserted', 'ASC'); return query .cache(MINUTE) @@ -84,12 +81,12 @@ export class OrderAPI extends BaseSQLDataSource { private getOrdersQuery(input: GeneralInput) { let query = this.knex.table('orders'); - if (input?.dates?.from) { - query = query.where('inserted', '>=', input.dates.from); + if (input?.filter?.dates?.from) { + query = query.where('inserted', '>=', input.filter.dates.from); } - if (input?.dates?.to) { - query = query.where('inserted', '<=', input.dates.to); + if (input?.filter?.dates?.to) { + query = query.where('inserted', '<=', input.filter.dates.to); } return query; @@ -190,14 +187,16 @@ export class OrderAPI extends BaseSQLDataSource { AND orderdetails.inserted >= ? AND orderdetails.inserted <= ? ORDER BY orderdetails.inserted ASC - ${input?.limit ? `LIMIT ${input.limit}` : ''} + ${input?.pagination.limit ? `LIMIT ${input.pagination.limit}` : ''} `; - const from = input?.dates?.from ? input.dates.from : new Date('2000-01-01'); + const from = input?.filter?.dates?.from + ? input.filter.dates.from + : new Date('2000-01-01'); const tomorrow = new Date(); tomorrow.setDate(tomorrow.getDate() + 1); - const to = input?.dates?.to ? input.dates.to : tomorrow; // Tomorrow + const to = input?.filter?.dates?.to ? input.filter?.dates.to : tomorrow; // Tomorrow const rows = await this.knex .raw(query, [designerId, this.getSQLDate(from), this.getSQLDate(to)]) diff --git a/src/datasources/product-api.ts b/src/datasources/product-api.ts index 76e4f70..c08d744 100644 --- a/src/datasources/product-api.ts +++ b/src/datasources/product-api.ts @@ -10,6 +10,8 @@ import { ProductWallpaperType, ProductFields, Orientation, + ProductsFilter, + ProductsFilterInput, } from '../types/product-types'; import { Maybe } from '../types/types'; import { convertToPossibleType } from './utils'; @@ -67,6 +69,9 @@ export class ProductAPI extends BaseSQLDataSource { } getProductFields(row: any): ProductFields { + if (!row.fields) { + return null; // 53460 for example + } const fields = Object.keys(row.fields).reduce((mem, key) => { mem[camelcase(key)] = convertToPossibleType(row.fields[key]); return mem; @@ -76,7 +81,7 @@ export class ProductAPI extends BaseSQLDataSource { } getOrientation(fields: ProductFields): string { - if (!fields.width || !fields.height) { + if (!fields || !fields.width || !fields.height) { return Orientation[Orientation.UNKNOWN]; } if (fields.width > fields.height) { @@ -100,13 +105,17 @@ export class ProductAPI extends BaseSQLDataSource { return row; } - async getProducts( - limit: Maybe, - visible: Maybe, - browsable: Maybe, - ): Promise> { - limit = limit ?? 100; - const query = sql.products(limit, visible, browsable); + async getProductsTotal(input: ProductsFilterInput): Promise { + const query = sql.productsTotal(input); + const res = await this.cachedRaw(query) + .cache(MINUTE * 60) + .then((data) => data.rows); + return convertToPossibleType(res[0]['count']); // Optimize later to return Promise + } + + async getProducts(input: ProductsFilterInput): Promise> { + // TODO: handle offset + const query = sql.products(input); return this.knex .raw(query) diff --git a/src/datasources/sql/products-sql.ts b/src/datasources/sql/products-sql.ts index 6533e16..7017fb0 100644 --- a/src/datasources/sql/products-sql.ts +++ b/src/datasources/sql/products-sql.ts @@ -1,3 +1,4 @@ +import { ProductsFilter, ProductsFilterInput } from '../../types/product-types'; import { Maybe } from '../../types/types'; // Get syntax highlighting with vscode by installing "Comment tagged templates2 @@ -75,19 +76,17 @@ FROM "product-products" products LEFT JOIN "product-stockproducts" stock ON stock.productid = products.productid `; -export function products( - limit: number = 100, - visible: Maybe, - browsable: Maybe, -) { +export function products(input: ProductsFilterInput) { + const limit = input.pagination.limit ?? 100; + const filter = input.filter; return ( baseQuery + /* sql */ ` ${ - visible != null + filter.visible != null ? /* sql */ ` - WHERE products.visible = ${visible ? '1' : '0'} - AND products.browsable = ${browsable ? '1' : '0'}` + WHERE products.visible = ${filter?.visible ? '1' : '0'} + AND products.browsable = ${filter?.browsable ? '1' : '0'}` : `` } LIMIT ${limit} @@ -95,6 +94,20 @@ LIMIT ${limit} ); } +export function productsTotal(input: ProductsFilterInput) { + const filter = input.filter; + return /*sql */ ` + SELECT count(*) FROM "product-products" products + ${ + filter.visible != null + ? /* sql */ ` + WHERE products.visible = ${filter?.visible ? '1' : '0'} + AND products.browsable = ${filter?.browsable ? '1' : '0'}` + : `` + } + `; +} + export function product(id: number) { return ( baseQuery + diff --git a/src/index.ts b/src/index.ts index 142efe8..95c3a68 100644 --- a/src/index.ts +++ b/src/index.ts @@ -65,7 +65,7 @@ async function main() { dataSources, context, introspection: true, - playground: false, + playground: true, }); await server.listen(); diff --git a/src/resolvers/designers-resolver.ts b/src/resolvers/designers-resolver.ts index 590e94f..0a91831 100644 --- a/src/resolvers/designers-resolver.ts +++ b/src/resolvers/designers-resolver.ts @@ -1,14 +1,11 @@ import { IResolverObject } from 'graphql-tools'; import { DesignerAPI } from '../datasources/designer-api'; import { OrderAPI } from '../datasources/order-api'; -import { FilterInput } from '../types/types'; +import { GeneralInput } from '../types/types'; const Designer: IResolverObject = { - async orderRows({ id }, args: FilterInput, { dataSources }) { - return (dataSources.orderApi).getOrderRowsByDesignerId( - id, - args.filter, - ); + async orderRows({ id }, input: GeneralInput, { dataSources }) { + return (dataSources.orderApi).getOrderRowsByDesignerId(id, input); }, }; diff --git a/src/resolvers/orders-resolver.ts b/src/resolvers/orders-resolver.ts index d0c8659..11024a8 100644 --- a/src/resolvers/orders-resolver.ts +++ b/src/resolvers/orders-resolver.ts @@ -1,7 +1,8 @@ import { IResolverObject } from 'graphql-tools'; import { MarketLocaleAPI } from '../datasources/market-locale-api'; import { OrderAPI } from '../datasources/order-api'; -import { FilterInput } from '../types/types'; +import { OrdersResult } from '../types/order-types'; +import { GeneralInput } from '../types/types'; const ContactInformation: IResolverObject = { // (parent, args, ctx, info) @@ -32,12 +33,20 @@ const OrderRow: IResolverObject = { }, }; -async function getOrdersResult(_, args: FilterInput, { dataSources }) { - const total = (dataSources.orderApi).getOrdersTotal(args.filter); - const items = (dataSources.orderApi).getOrders(args.filter); +async function getOrdersResult( + _, + input: GeneralInput, + { dataSources }, +): Promise { + const total = (dataSources.orderApi).getOrdersTotal(input); + const items = (dataSources.orderApi).getOrders(input); return { - total: total, items: items, + pagination: { + total: total, + limit: input.pagination.limit, + offset: 0, // TODO: this will be implemented later if the client app needs it + }, }; } diff --git a/src/resolvers/products-resolver.ts b/src/resolvers/products-resolver.ts index 215cf4f..d9a48f7 100644 --- a/src/resolvers/products-resolver.ts +++ b/src/resolvers/products-resolver.ts @@ -1,6 +1,10 @@ import { IResolverObject } from 'graphql-tools'; import { ProductAPI } from '../datasources/product-api'; -import { Product } from '../types/product-types'; +import { + Product, + ProductListResult, + ProductsFilterInput, +} from '../types/product-types'; import { CategoryAPI } from '../datasources/category-api'; import { Category } from '../types/category-types'; import { KeywordAPI } from '../datasources/keyword-api'; @@ -21,7 +25,7 @@ const Product: IResolverObject = { async categories({ id }, _args, { dataSources }): Promise> { return (dataSources.categoryApi).getProductCategories(id); }, - async designer({ designerId }, args, { dataSources }) { + async designer({ designerId }, _args, { dataSources }) { if (!designerId) { return null; } @@ -41,16 +45,22 @@ const PrintProduct: IResolverObject = { }, }; -async function getProducts( +async function getProductsResult( _, - { limit, visible, browsable }, + input: ProductsFilterInput, { dataSources }, -): Promise> { - return (dataSources.productApi).getProducts( - limit, - visible, - browsable, - ); +): Promise { + const total = (dataSources.productApi).getProductsTotal(input); + const items = (dataSources.productApi).getProducts(input); + + return { + pagination: { + limit: input.pagination.limit, + offset: 0, // TODO: this will be implemented later if the client app needs it + total: total, + }, + items: items, + }; } async function getProduct(_, { id }, { dataSources }): Promise { @@ -64,11 +74,11 @@ export const productTypeDefs = { }; export const productQueryTypeDefs = { - products: getProducts, + products: getProductsResult, product: getProduct, }; -// Mutations below +// Mutations below, embryo stage export const productMutationTypeDefs = { addKeyword(_, { productId, keywordId }, { dataSources }) { diff --git a/src/schema.ts b/src/schema.ts index ee81355..e9b8241 100644 --- a/src/schema.ts +++ b/src/schema.ts @@ -6,12 +6,12 @@ const typeDefs = gql` scalar JSON type Query { - orders(filter: FilterInput!): OrderResult + orders(pagination: PaginationInput!, filter: DateFilterInput!): OrdersResult order(id: ID!): Order - """ - Will be replaced with a ProductsResult type similar to OrdersResult but we need to change in the similar-images code then - """ - products(limit: Int, visible: Boolean, browsable: Boolean): [Product] + products( + pagination: PaginationInput! + filter: ProductsFilterInput! + ): ProductsResult product(id: Int!): Product categories: [Category] category(id: Int!): Category @@ -26,12 +26,42 @@ const typeDefs = gql` interiors(filter: InteriorsFilterInput): [Interior] } - type Mutation { - addKeyword(productId: Int!, keywordId: Int!): MutationResult + input PaginationInput { + limit: Int! + """ + offset is not implemented yet and result will always return as if it was 0. Implement when needed. + """ + offset: Int } - type MutationResult { - result: String! + input DateInput { + from: Date! + to: Date! + } + + input DateFilterInput { + dates: DateInput + } + + input ProductsFilterInput { + visible: Boolean + browsable: Boolean + } + + type PaginationResult { + total: Int! + offset: Int! + limit: Int! + } + + type OrdersResult { + pagination: PaginationResult! + items: [Order] + } + + type ProductsResult { + pagination: PaginationResult! + items: [Product] } """ @@ -43,26 +73,6 @@ const typeDefs = gql` orientation: Orientation! } - input DateInput { - from: Date - to: Date - } - - input FilterInput { - dates: DateInput - limit: Int! - offset: Int! - } - """ - Later replace with implements ListResult when products support it - """ - type OrderResult { - total: Int! - offset: Int - limit: Int - items: [Order] - } - type Market { id: ID! name: String! @@ -342,7 +352,7 @@ const typeDefs = gql` id: ID! name: String path: String - orderRows(filter: FilterInput): [OrderRow] + orderRows(pagination: PaginationInput!, filter: DateFilterInput): [OrderRow] } enum KeywordType { @@ -356,6 +366,15 @@ const typeDefs = gql` type: KeywordType products: [Product] } + + # Mutations below + type Mutation { + addKeyword(productId: Int!, keywordId: Int!): MutationResult + } + + type MutationResult { + result: String! + } `; export { typeDefs }; diff --git a/src/types/order-types.ts b/src/types/order-types.ts index 15e100d..22a99e8 100644 --- a/src/types/order-types.ts +++ b/src/types/order-types.ts @@ -1,3 +1,10 @@ +import { PaginationResult } from './types'; + +export interface OrdersResult { + pagination: PaginationResult; + items: Array | Promise>; +} + export interface Address { id: number; firstname: string; diff --git a/src/types/product-types.ts b/src/types/product-types.ts index 7f93f03..0756e26 100644 --- a/src/types/product-types.ts +++ b/src/types/product-types.ts @@ -1,3 +1,19 @@ +import { PaginationInput, PaginationResult } from './types'; + +export interface ProductsFilterInput { + pagination: PaginationInput; + filter?: ProductsFilter; +} +export interface ProductsFilter { + visible?: boolean; + browsable?: boolean; +} + +export interface ProductListResult { + pagination: PaginationResult; + items: Array | Promise>; +} + export enum ProductGroup { PHOTO_WALLPAPER = 1, CANVAS = 2, diff --git a/src/types/types.ts b/src/types/types.ts index 629bf0d..c662426 100644 --- a/src/types/types.ts +++ b/src/types/types.ts @@ -1,16 +1,26 @@ export type Maybe = T | undefined; +export interface PaginationInput { + limit: number; + offset?: number; +} + export interface DateInput { from: Date; to: Date; } -export interface GeneralInput { +export interface DateFilterInput { dates: DateInput; - limit: number; - offset: number; } -export interface FilterInput { - filter: GeneralInput; +export interface GeneralInput { + pagination: PaginationInput; + filter?: DateFilterInput; +} + +export interface PaginationResult { + total: Promise | number; + offset: number; + limit: number; }