P5 7619 refactor products to return list (#11)

This commit is contained in:
Niklas Fondberg
2021-08-20 08:16:01 +02:00
committed by GitHub
parent 234996abfd
commit 964a904f42
15 changed files with 235 additions and 92 deletions
+9
View File
@@ -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
+45
View File
@@ -339,3 +339,48 @@
priceAdjustment 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
}
}
}
+2 -2
View File
@@ -114,7 +114,7 @@ describe('GraphQL Apollo tests', () => {
id id
name name
path path
orderRows { orderRows(pagination: {limit: 1000}) {
name name
artNo artNo
} }
@@ -156,7 +156,7 @@ describe('GraphQL Apollo tests', () => {
id id
name name
path path
orderRows { orderRows(pagination: {limit: 1000}) {
name, name,
artNo artNo
} }
+13 -14
View File
@@ -6,7 +6,7 @@ import {
Order, Order,
OrderRow, OrderRow,
} from '../types/order-types'; } from '../types/order-types';
import { GeneralInput, Maybe } from '../types/types'; import { DateFilterInput, GeneralInput, Maybe } from '../types/types';
import { convertToPossibleType } from './utils'; import { convertToPossibleType } from './utils';
import { getProductGroupStringFromString } from '../types/product-types'; import { getProductGroupStringFromString } from '../types/product-types';
@@ -54,10 +54,9 @@ export class OrderAPI extends BaseSQLDataSource {
} }
createOrderFromRow(row: any): Order { createOrderFromRow(row: any): Order {
row.__resolveType = 'Order';
row.billingInformation = this.getBillingInformation(row); row.billingInformation = this.getBillingInformation(row);
row.deliveryInformation = this.getDeliveryInformation(row); row.deliveryInformation = this.getDeliveryInformation(row);
row.marketName = row.market;
row.localeName = row.locale;
return row; return row;
} }
@@ -71,11 +70,9 @@ export class OrderAPI extends BaseSQLDataSource {
} }
async getOrders(input: Maybe<GeneralInput>): Promise<Array<Order>> { async getOrders(input: Maybe<GeneralInput>): Promise<Array<Order>> {
let query = this.getOrdersQuery(input); let query = this.getOrdersQuery(input).limit(input?.pagination.limit);
if (input?.limit) { // TODO: handle offset
query = query.limit(input?.limit);
}
query = query.orderBy('inserted', 'ASC'); query = query.orderBy('inserted', 'ASC');
return query return query
.cache(MINUTE) .cache(MINUTE)
@@ -84,12 +81,12 @@ export class OrderAPI extends BaseSQLDataSource {
private getOrdersQuery(input: GeneralInput) { private getOrdersQuery(input: GeneralInput) {
let query = this.knex.table('orders'); let query = this.knex.table('orders');
if (input?.dates?.from) { if (input?.filter?.dates?.from) {
query = query.where('inserted', '>=', input.dates.from); query = query.where('inserted', '>=', input.filter.dates.from);
} }
if (input?.dates?.to) { if (input?.filter?.dates?.to) {
query = query.where('inserted', '<=', input.dates.to); query = query.where('inserted', '<=', input.filter.dates.to);
} }
return query; return query;
@@ -190,14 +187,16 @@ export class OrderAPI extends BaseSQLDataSource {
AND orderdetails.inserted >= ? AND orderdetails.inserted >= ?
AND orderdetails.inserted <= ? AND orderdetails.inserted <= ?
ORDER BY orderdetails.inserted ASC 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(); const tomorrow = new Date();
tomorrow.setDate(tomorrow.getDate() + 1); 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 const rows = await this.knex
.raw(query, [designerId, this.getSQLDate(from), this.getSQLDate(to)]) .raw(query, [designerId, this.getSQLDate(from), this.getSQLDate(to)])
+17 -8
View File
@@ -10,6 +10,8 @@ import {
ProductWallpaperType, ProductWallpaperType,
ProductFields, ProductFields,
Orientation, Orientation,
ProductsFilter,
ProductsFilterInput,
} from '../types/product-types'; } from '../types/product-types';
import { Maybe } from '../types/types'; import { Maybe } from '../types/types';
import { convertToPossibleType } from './utils'; import { convertToPossibleType } from './utils';
@@ -67,6 +69,9 @@ export class ProductAPI extends BaseSQLDataSource {
} }
getProductFields(row: any): ProductFields { getProductFields(row: any): ProductFields {
if (!row.fields) {
return null; // 53460 for example
}
const fields = Object.keys(row.fields).reduce((mem, key) => { const fields = Object.keys(row.fields).reduce((mem, key) => {
mem[camelcase(key)] = convertToPossibleType(row.fields[key]); mem[camelcase(key)] = convertToPossibleType(row.fields[key]);
return mem; return mem;
@@ -76,7 +81,7 @@ export class ProductAPI extends BaseSQLDataSource {
} }
getOrientation(fields: ProductFields): string { getOrientation(fields: ProductFields): string {
if (!fields.width || !fields.height) { if (!fields || !fields.width || !fields.height) {
return Orientation[Orientation.UNKNOWN]; return Orientation[Orientation.UNKNOWN];
} }
if (fields.width > fields.height) { if (fields.width > fields.height) {
@@ -100,13 +105,17 @@ export class ProductAPI extends BaseSQLDataSource {
return row; return row;
} }
async getProducts( async getProductsTotal(input: ProductsFilterInput): Promise<number> {
limit: Maybe<number>, const query = sql.productsTotal(input);
visible: Maybe<boolean>, const res = await this.cachedRaw(query)
browsable: Maybe<boolean>, .cache(MINUTE * 60)
): Promise<Array<Product>> { .then((data) => data.rows);
limit = limit ?? 100; return convertToPossibleType(res[0]['count']); // Optimize later to return Promise
const query = sql.products(limit, visible, browsable); }
async getProducts(input: ProductsFilterInput): Promise<Array<Product>> {
// TODO: handle offset
const query = sql.products(input);
return this.knex return this.knex
.raw(query) .raw(query)
+21 -8
View File
@@ -1,3 +1,4 @@
import { ProductsFilter, ProductsFilterInput } from '../../types/product-types';
import { Maybe } from '../../types/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
@@ -75,19 +76,17 @@ FROM "product-products" products
LEFT JOIN "product-stockproducts" stock ON stock.productid = products.productid LEFT JOIN "product-stockproducts" stock ON stock.productid = products.productid
`; `;
export function products( export function products(input: ProductsFilterInput) {
limit: number = 100, const limit = input.pagination.limit ?? 100;
visible: Maybe<boolean>, const filter = input.filter;
browsable: Maybe<boolean>,
) {
return ( return (
baseQuery + baseQuery +
/* sql */ ` /* sql */ `
${ ${
visible != null filter.visible != null
? /* sql */ ` ? /* sql */ `
WHERE products.visible = ${visible ? '1' : '0'} WHERE products.visible = ${filter?.visible ? '1' : '0'}
AND products.browsable = ${browsable ? '1' : '0'}` AND products.browsable = ${filter?.browsable ? '1' : '0'}`
: `` : ``
} }
LIMIT ${limit} 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) { export function product(id: number) {
return ( return (
baseQuery + baseQuery +
+1 -1
View File
@@ -65,7 +65,7 @@ async function main() {
dataSources, dataSources,
context, context,
introspection: true, introspection: true,
playground: false, playground: true,
}); });
await server.listen(); await server.listen();
+3 -6
View File
@@ -1,14 +1,11 @@
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 '../types/types'; import { GeneralInput } from '../types/types';
const Designer: IResolverObject = { const Designer: IResolverObject = {
async orderRows({ id }, args: FilterInput, { dataSources }) { async orderRows({ id }, input: GeneralInput, { dataSources }) {
return (<OrderAPI>dataSources.orderApi).getOrderRowsByDesignerId( return (<OrderAPI>dataSources.orderApi).getOrderRowsByDesignerId(id, input);
id,
args.filter,
);
}, },
}; };
+14 -5
View File
@@ -1,7 +1,8 @@
import { IResolverObject } from 'graphql-tools'; import { IResolverObject } from 'graphql-tools';
import { MarketLocaleAPI } from '../datasources/market-locale-api'; import { MarketLocaleAPI } from '../datasources/market-locale-api';
import { OrderAPI } from '../datasources/order-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 = { const ContactInformation: IResolverObject = {
// (parent, args, ctx, info) // (parent, args, ctx, info)
@@ -32,12 +33,20 @@ const OrderRow: IResolverObject = {
}, },
}; };
async function getOrdersResult(_, args: FilterInput, { dataSources }) { async function getOrdersResult(
const total = (<OrderAPI>dataSources.orderApi).getOrdersTotal(args.filter); _,
const items = (<OrderAPI>dataSources.orderApi).getOrders(args.filter); input: GeneralInput,
{ dataSources },
): Promise<OrdersResult> {
const total = (<OrderAPI>dataSources.orderApi).getOrdersTotal(input);
const items = (<OrderAPI>dataSources.orderApi).getOrders(input);
return { return {
total: total,
items: items, items: items,
pagination: {
total: total,
limit: input.pagination.limit,
offset: 0, // TODO: this will be implemented later if the client app needs it
},
}; };
} }
+22 -12
View File
@@ -1,6 +1,10 @@
import { IResolverObject } from 'graphql-tools'; import { IResolverObject } from 'graphql-tools';
import { ProductAPI } from '../datasources/product-api'; 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 { CategoryAPI } from '../datasources/category-api';
import { Category } from '../types/category-types'; import { Category } from '../types/category-types';
import { KeywordAPI } from '../datasources/keyword-api'; import { KeywordAPI } from '../datasources/keyword-api';
@@ -21,7 +25,7 @@ const Product: IResolverObject = {
async categories({ id }, _args, { dataSources }): Promise<Array<Category>> { async categories({ id }, _args, { dataSources }): Promise<Array<Category>> {
return (<CategoryAPI>dataSources.categoryApi).getProductCategories(id); return (<CategoryAPI>dataSources.categoryApi).getProductCategories(id);
}, },
async designer({ designerId }, args, { dataSources }) { async designer({ designerId }, _args, { dataSources }) {
if (!designerId) { if (!designerId) {
return null; return null;
} }
@@ -41,16 +45,22 @@ const PrintProduct: IResolverObject = {
}, },
}; };
async function getProducts( async function getProductsResult(
_, _,
{ limit, visible, browsable }, input: ProductsFilterInput,
{ dataSources }, { dataSources },
): Promise<Array<Product>> { ): Promise<ProductListResult> {
return (<ProductAPI>dataSources.productApi).getProducts( const total = (<ProductAPI>dataSources.productApi).getProductsTotal(input);
limit, const items = (<ProductAPI>dataSources.productApi).getProducts(input);
visible,
browsable, 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<Product> { async function getProduct(_, { id }, { dataSources }): Promise<Product> {
@@ -64,11 +74,11 @@ export const productTypeDefs = {
}; };
export const productQueryTypeDefs = { export const productQueryTypeDefs = {
products: getProducts, products: getProductsResult,
product: getProduct, product: getProduct,
}; };
// Mutations below // Mutations below, embryo stage
export const productMutationTypeDefs = { export const productMutationTypeDefs = {
addKeyword(_, { productId, keywordId }, { dataSources }) { addKeyword(_, { productId, keywordId }, { dataSources }) {
+49 -30
View File
@@ -6,12 +6,12 @@ const typeDefs = gql`
scalar JSON scalar JSON
type Query { type Query {
orders(filter: FilterInput!): OrderResult orders(pagination: PaginationInput!, filter: DateFilterInput!): OrdersResult
order(id: ID!): Order order(id: ID!): Order
""" products(
Will be replaced with a ProductsResult type similar to OrdersResult but we need to change in the similar-images code then pagination: PaginationInput!
""" filter: ProductsFilterInput!
products(limit: Int, visible: Boolean, browsable: Boolean): [Product] ): ProductsResult
product(id: Int!): Product product(id: Int!): Product
categories: [Category] categories: [Category]
category(id: Int!): Category category(id: Int!): Category
@@ -26,12 +26,42 @@ const typeDefs = gql`
interiors(filter: InteriorsFilterInput): [Interior] interiors(filter: InteriorsFilterInput): [Interior]
} }
type Mutation { input PaginationInput {
addKeyword(productId: Int!, keywordId: Int!): MutationResult limit: Int!
"""
offset is not implemented yet and result will always return as if it was 0. Implement when needed.
"""
offset: Int
} }
type MutationResult { input DateInput {
result: String! 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! 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 { type Market {
id: ID! id: ID!
name: String! name: String!
@@ -342,7 +352,7 @@ const typeDefs = gql`
id: ID! id: ID!
name: String name: String
path: String path: String
orderRows(filter: FilterInput): [OrderRow] orderRows(pagination: PaginationInput!, filter: DateFilterInput): [OrderRow]
} }
enum KeywordType { enum KeywordType {
@@ -356,6 +366,15 @@ const typeDefs = gql`
type: KeywordType type: KeywordType
products: [Product] products: [Product]
} }
# Mutations below
type Mutation {
addKeyword(productId: Int!, keywordId: Int!): MutationResult
}
type MutationResult {
result: String!
}
`; `;
export { typeDefs }; export { typeDefs };
+7
View File
@@ -1,3 +1,10 @@
import { PaginationResult } from './types';
export interface OrdersResult {
pagination: PaginationResult;
items: Array<Order> | Promise<Array<Order>>;
}
export interface Address { export interface Address {
id: number; id: number;
firstname: string; firstname: string;
+16
View File
@@ -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<Product> | Promise<Array<Product>>;
}
export enum ProductGroup { export enum ProductGroup {
PHOTO_WALLPAPER = 1, PHOTO_WALLPAPER = 1,
CANVAS = 2, CANVAS = 2,
+15 -5
View File
@@ -1,16 +1,26 @@
export type Maybe<T> = T | undefined; export type Maybe<T> = T | undefined;
export interface PaginationInput {
limit: number;
offset?: number;
}
export interface DateInput { export interface DateInput {
from: Date; from: Date;
to: Date; to: Date;
} }
export interface GeneralInput { export interface DateFilterInput {
dates: DateInput; dates: DateInput;
limit: number;
offset: number;
} }
export interface FilterInput { export interface GeneralInput {
filter: GeneralInput; pagination: PaginationInput;
filter?: DateFilterInput;
}
export interface PaginationResult {
total: Promise<number> | number;
offset: number;
limit: number;
} }