From 9221b7f9b50625993b108d179ad624a0ab6fe714 Mon Sep 17 00:00:00 2001 From: Bart Akeley Date: Sun, 26 Nov 2017 19:52:16 -0600 Subject: [PATCH] quantities round trip api and stream make POST requests to /quantity to update the quantity of an existing food item - then mixin the new quantity with the existing food items observable --- js/apis/FoodItemsApi.js | 4 +--- js/apis/QuantityApi.js | 27 +++++++++++++++++++++++++++ js/constants/AppConstants.js | 4 ++++ js/constants/QuantityConstants.js | 12 +++++++++++- js/enhancers/quantityEnhancers.js | 13 +++++++++++++ js/helpers/ImmutableHelpers.js | 4 ++++ js/pages/FoodItemDetail.js | 21 ++++++++++++--------- js/streams/FoodItemsStream.js | 11 ++++++++--- js/streams/QuantityStream.js | 24 ++++++++++++++++++++++++ sample-query.sql | 4 +--- 10 files changed, 105 insertions(+), 19 deletions(-) create mode 100644 js/apis/QuantityApi.js create mode 100644 js/constants/AppConstants.js create mode 100644 js/enhancers/quantityEnhancers.js create mode 100644 js/streams/QuantityStream.js diff --git a/js/apis/FoodItemsApi.js b/js/apis/FoodItemsApi.js index 386ef37..92f13c6 100644 --- a/js/apis/FoodItemsApi.js +++ b/js/apis/FoodItemsApi.js @@ -1,9 +1,7 @@ // @flow import { memoize } from 'ramda'; import FilterRecord from '../records/FilterRecord'; - -const BASE_URL = 'aretherecookies.herokuapp.com'; -// const BASE_URL = '192.168.1.6:3000'; +import { BASE_URL } from '../constants/AppConstants'; export type FoodItemsFilter = { radius?: number, diff --git a/js/apis/QuantityApi.js b/js/apis/QuantityApi.js new file mode 100644 index 0000000..1b7446d --- /dev/null +++ b/js/apis/QuantityApi.js @@ -0,0 +1,27 @@ +// @flow +import type { Quantity } from '../constants/QuantityConstants'; +import { BASE_URL } from '../constants/AppConstants'; +import type { QuantityResponse } from '../constants/QuantityConstants'; + +export const setQuantity = ({ + foodItemId, + quantity, +}: { + foodItemId: string, + quantity: Quantity, +}): ?Promise => { + try { + return fetch(`http://${BASE_URL}/quantity`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + foodItemId, + quantity, + }), + }).then(res => res.json()); + } catch (error) { + console.log(error); //eslint-disable-line + } +}; diff --git a/js/constants/AppConstants.js b/js/constants/AppConstants.js new file mode 100644 index 0000000..5d3135d --- /dev/null +++ b/js/constants/AppConstants.js @@ -0,0 +1,4 @@ +// @flow + +export const BASE_URL = 'aretherecookies.herokuapp.com'; +// export const BASE_URL = '192.168.1.6:3000'; diff --git a/js/constants/QuantityConstants.js b/js/constants/QuantityConstants.js index 99ee0bd..d864d30 100644 --- a/js/constants/QuantityConstants.js +++ b/js/constants/QuantityConstants.js @@ -3,5 +3,15 @@ export const QUANTITY_NONE: 'none' = 'none'; export const QUANTITY_FEW: 'few' = 'few'; export const QUANTITY_MANY: 'many' = 'many'; export const QUANTITY_LOTS: 'lots' = 'lots'; -export type Quantity = typeof QUANTITY_NONE | typeof QUANTITY_FEW | typeof QUANTITY_MANY | typeof QUANTITY_LOTS; + export const QUANTITIES = [QUANTITY_NONE, QUANTITY_FEW, QUANTITY_LOTS, QUANTITY_MANY]; + +export type Quantity = typeof QUANTITY_NONE | typeof QUANTITY_FEW | typeof QUANTITY_MANY | typeof QUANTITY_LOTS; + +export type QuantityResponse = { + food_item_id: string, + quantity: Quantity, + date: number, +}; + +export type QuantityFragment = { quantity: Quantity, lastupdated: number }; diff --git a/js/enhancers/quantityEnhancers.js b/js/enhancers/quantityEnhancers.js new file mode 100644 index 0000000..84f187d --- /dev/null +++ b/js/enhancers/quantityEnhancers.js @@ -0,0 +1,13 @@ +//@flow +import { withProps } from 'recompose'; +import { setQuantity } from '../apis/QuantityApi'; +import { emit } from '../streams/QuantityStream'; +import type { Quantity, QuantityResponse } from '../constants/QuantityConstants'; +import { nth } from 'ramda'; + +export const withUpdateQuantity = withProps({ + updateQuantity: async ({ foodItemId, quantity }: { foodItemId: string, quantity: Quantity }) => { + const newQuantity: QuantityResponse = nth(0, await setQuantity({ foodItemId, quantity })); + emit(newQuantity); + }, +}); diff --git a/js/helpers/ImmutableHelpers.js b/js/helpers/ImmutableHelpers.js index ca87f20..f54e850 100644 --- a/js/helpers/ImmutableHelpers.js +++ b/js/helpers/ImmutableHelpers.js @@ -3,6 +3,10 @@ import { type List, type Map } from 'immutable'; export const pushInto = (list: List, item: T): List => list.push(item); +export function merge(a: Map, b: Map) { + return a.merge(b); +} + export const setById = (map: Map, item: ?{ id?: string }): Map => { if (!item || !item.id) { return map; diff --git a/js/pages/FoodItemDetail.js b/js/pages/FoodItemDetail.js index 0e448d1..a931940 100644 --- a/js/pages/FoodItemDetail.js +++ b/js/pages/FoodItemDetail.js @@ -3,8 +3,8 @@ import React, { Component } from 'react'; import { Image, View } from 'react-native'; import theme from '../ui-theme'; import { StrongText, SubText } from '../components/ItemTile'; -import { type FoodItem } from '../records/FoodItemRecord'; -import { type Quantity, QUANTITY_MANY } from '../constants/QuantityConstants'; +import typeof FoodItemRecord from '../records/FoodItemRecord'; +import { type Quantity } from '../constants/QuantityConstants'; import typeof PlaceRecord from '../records/PlaceRecord'; import { compose, pure } from 'recompose'; import IconButton from '../components/IconButton'; @@ -16,6 +16,7 @@ import QuantityPicker from '../components/QuantityPicker'; import { routeWithTitle } from '../helpers/RouteHelpers'; import { Link } from 'react-router-native'; import moment from 'moment'; +import { withUpdateQuantity } from '../enhancers/quantityEnhancers'; const { foodItemDetails: style } = theme; @@ -31,19 +32,18 @@ export class FoodItemDetail extends Component { static displayName = 'FoodItemDetail'; props: { - foodItem: FoodItem, + foodItem: FoodItemRecord, place: PlaceRecord, + updateQuantity: ({ foodItemId: string, quantity: Quantity }) => void, }; state: { isFavorite: boolean, - quantity: Quantity, currentImage: number, }; state = { currentImage: 0, - quantity: QUANTITY_MANY, }; // TODO placeholder implementation until we get a backend @@ -52,7 +52,10 @@ export class FoodItemDetail extends Component { isFavorite: !prevState.isFavorite, })); - updateAmount = (quantity: Quantity) => this.setState({ quantity }); + updateAmount = (quantity: Quantity) => { + const { updateQuantity, foodItem: { id: foodItemId } } = this.props; + updateQuantity({ foodItemId, quantity }); + }; // TODO addPhoto = () => {}; @@ -102,11 +105,11 @@ export class FoodItemDetail extends Component { }} > - Last updated at {moment(foodItem.lastupdated).format('H:mm A on MMM D, YYYY')} + Last updated at {moment(foodItem.lastupdated).format('h:mm A on MMM D, YYYY')} getFoodItems({ loc, filter })) .map(({ fooditems = [] }: FoodItemsForLocation) => { return fooditems.map(createFoodItem).reduce(setById, new Map()); + }) + .combineLatest(Quantity$, (foodItems: Map, quantities: Map) => { + return foodItems.mergeDeepWith((foodItem, quantity) => foodItem.merge(quantity), quantities); }); diff --git a/js/streams/QuantityStream.js b/js/streams/QuantityStream.js new file mode 100644 index 0000000..1657d09 --- /dev/null +++ b/js/streams/QuantityStream.js @@ -0,0 +1,24 @@ +//@flow +import { ReplaySubject } from 'rxjs'; +import type { QuantityResponse, QuantityFragment } from '../constants/QuantityConstants'; +import { Map } from 'immutable'; + +const observable: ReplaySubject = new ReplaySubject(); + +export function emit(val: ?QuantityResponse) { + observable.next(val); +} + +// force our observable to emit an initial empty map so that food items will load +emit(null); + +export default observable.scan((quantitiesByFoodItemId: Map, quantity?: QuantityResponse) => { + if (!quantity) { + return quantitiesByFoodItemId; + } + + return quantitiesByFoodItemId.set(quantity.food_item_id, { + quantity: quantity.quantity, + lastupdated: quantity.date, + }); +}, new Map()); diff --git a/sample-query.sql b/sample-query.sql index cabcda4..d1b7a93 100644 --- a/sample-query.sql +++ b/sample-query.sql @@ -10,9 +10,7 @@ SELECT q.quantity AS quantity, q.date AS lastUpdated FROM food_items f -LEFT OUTER JOIN ( - SELECT food_item_id, quantity, MAX(date) AS date FROM quantities GROUP BY food_item_id, quantity -) q +LEFT OUTER JOIN latest_quantities q ON f.id = q.food_item_id WHERE ST_DWithin(loc, ST_SetSRID(ST_Point(-97.7286718, 30.3033267),4326)::geography, 20 * 1609) AND f.category IN ('desserts', 'beverages', 'entrees', 'other')