Compare commits

..

5 Commits

Author SHA1 Message Date
James Ketrenos
0748bcf4b8 removed a log message
Signed-off-by: James Ketrenos <james_eikona@ketrenos.com>
2021-08-17 01:56:30 -07:00
James Ketrenos
7377c316db Use useReducer to forceUpdate the view when Whiskies deep object changes
Signed-off-by: James Ketrenos <james_eikona@ketrenos.com>
2021-08-16 18:44:00 -07:00
James Ketrenos
e6616356a2 Seach CODE or DESCRIPTION. Strip % from inbound string
Signed-off-by: James Ketrenos <james_eikona@ketrenos.com>
2021-08-16 15:59:00 -07:00
James Ketrenos
afabcd057f Show quantity correctly
Signed-off-by: James Ketrenos <james_eikona@ketrenos.com>
2021-08-16 15:48:07 -07:00
James Ketrenos
056b388520 Updated whisky query to support % syntax, and summary update totals
Signed-off-by: James Ketrenos <james_eikona@ketrenos.com>
2021-08-16 15:28:36 -07:00
4 changed files with 170 additions and 63 deletions

View File

@ -1,17 +1,47 @@
# Whisky Business
Refresh the OLCC DB: ```plantuml
@startuml
object location {
address
phone
id
}
```bash object whisky {
node whisky.js label
price
average[52] : statewide average count per week
id
location[] --> location : count
}
object client
object server
object user {
location
radius
whisky[]
}
user --> whisky : monitor
@enduml
``` ```
# Whisky Business
Launch GraphQL server: Launch GraphQL server:
```bash ```bash
npm start npm start
``` ```
Refresh the OLCC DB:
```bash
node whisky.js
```
Launch website: Launch website:
```bash ```bash

View File

@ -53,7 +53,9 @@ const WhiskyType = new graphql.GraphQLObjectType({
lastSeen: { type: graphql.GraphQLString }, lastSeen: { type: graphql.GraphQLString },
size: { type: graphql.GraphQLInt }, size: { type: graphql.GraphQLInt },
price: { type: graphql.GraphQLFloat }, price: { type: graphql.GraphQLFloat },
inventories: { type: graphql.GraphQLList(InventoryType) } inventories: { type: graphql.GraphQLList(InventoryType) },
quantity: { type: graphql.GraphQLInt },
updated: { type: graphql.GraphQLString }
}) })
}); });
@ -91,7 +93,9 @@ const buildWhiskies = (rows) => {
price: row.price, price: row.price,
size: row.size, size: row.size,
code: row.code, code: row.code,
inventories: [] inventories: [],
quantity: 0,
updated: 0
} }
whiskies[row.code] = whisky; whiskies[row.code] = whisky;
} else { } else {
@ -110,6 +114,8 @@ const buildWhiskies = (rows) => {
quantity: row.quantity, quantity: row.quantity,
updated: row.updated updated: row.updated
}); });
whisky.updated = (new Date(row.updated) > new Date(whisky.updated)) ? row.updated : whisky.updated;
whisky.quantity += row.quantity;
} }
}); });
const results = []; const results = [];
@ -176,14 +182,20 @@ var queryType = new graphql.GraphQLObjectType({
fields: { fields: {
Whiskies: { Whiskies: {
type: graphql.GraphQLList(WhiskyType), type: graphql.GraphQLList(WhiskyType),
resolve: (root, args, context, info) => { args: {
code: { type: graphql.GraphQLString },
},
resolve: (root, {code}, context, info) => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
if (code && code.trim() != "") {
code = `${code.replace(/%/g, '')}%`;
}
database.all( database.all(
"SELECT w.*,l.code AS 'location',l.address,l.city,l.phone,l.latitude,l.longitude,i.quantity,i.updated " + "SELECT w.*,i.quantity,i.updated " +
"FROM Whiskies AS w " + "FROM Whiskies AS w " +
"LEFT JOIN Inventories AS i ON w.code=i.whisky "+ "LEFT JOIN Inventories AS i ON w.code=i.whisky " +
"LEFT JOIN Locations AS l ON l.code=i.location " + (code ? "WHERE w.code LIKE (?) OR w.description LIKE (?)" : "") +
";", function(err, rows) { ";", [code, code], function(err, rows) {
if (err) { console.error(err); return reject(null); } if (err) { console.error(err); return reject(null); }
resolve(buildWhiskies(rows)); resolve(buildWhiskies(rows));
}); });
@ -198,9 +210,9 @@ var queryType = new graphql.GraphQLObjectType({
resolve: (root, {code}, context, info) => { resolve: (root, {code}, context, info) => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
database.all( database.all(
"SELECT w.*,l.code AS 'location',l.address,l.city,l.phone,l.latitude,l.longitude,i.quantity,i.updated "+ "SELECT w.*,l.code AS 'location',l.address,l.city,l.phone,l.latitude,l.longitude,i.quantity,i.updated " +
"FROM Whiskies AS w " + "FROM Whiskies AS w " +
"LEFT JOIN Inventories AS i ON w.code=i.whisky "+ "LEFT JOIN Inventories AS i ON w.code=i.whisky " +
"LEFT JOIN Locations AS l ON l.code=i.location " + "LEFT JOIN Locations AS l ON l.code=i.location " +
"WHERE w.code = (?);", [code], function(err, rows) { "WHERE w.code = (?);", [code], function(err, rows) {
if (err) { console.error(err); return reject(null); } if (err) { console.error(err); return reject(null); }

View File

@ -1,6 +1,6 @@
import { StatusBar } from 'expo-status-bar'; import { StatusBar } from 'expo-status-bar';
import React, { useState } from 'react'; import React, { useState, useReducer } from 'react';
import { StyleSheet, Text, TouchableOpacity, View } from 'react-native'; import { StyleSheet, Text, TextInput, TouchableOpacity, View } from 'react-native';
import { request, GraphQLClient, gql } from 'graphql-request'; import { request, GraphQLClient, gql } from 'graphql-request';
import moment from 'moment'; import moment from 'moment';
import { buildExecutionContext } from 'graphql/execution/execute'; import { buildExecutionContext } from 'graphql/execution/execute';
@ -14,20 +14,22 @@ const Whisky = (props : { active?: boolean, whisky: any, style: any }) => {
const locations: any[] = []; const locations: any[] = [];
whisky.inventories.forEach((item : { location: any, quantity: number, updated: string }) => { if (whisky.inventories) {
quantity += item.quantity; whisky.inventories.forEach((item : { location: any, quantity: number, updated: string }) => {
const updated = moment(item.updated, 'YYYY-MM-DD'); quantity += item.quantity;
if (time < updated) { const updated = moment(item.updated, 'YYYY-MM-DD');
time = updated; if (time < updated) {
} time = updated;
locations.push( }
<View key={item.location.code} style={[styles.horizontal,styles.container]}> locations.push(
<Text style={whiskyStyles.address}>{item.location.address}</Text> <View key={item.location.code} style={[styles.horizontal,styles.container]}>
<Text style={whiskyStyles.phone}>{item.location.phone}</Text> <Text style={whiskyStyles.address}>{item.location.address}</Text>
<Text style={whiskyStyles.quantity}>{item.quantity}</Text> <Text style={whiskyStyles.phone}>{item.location.phone}</Text>
</View> <Text style={whiskyStyles.quantity}>{item.quantity}</Text>
) </View>
}); )
});
}
const date = (time.unix() == 0) ? "" : time.format("YYYY-MM-DD"); const date = (time.unix() == 0) ? "" : time.format("YYYY-MM-DD");
return ( return (
@ -36,7 +38,7 @@ const Whisky = (props : { active?: boolean, whisky: any, style: any }) => {
<View style={whiskyStyles.code}><Text>{whisky.code}</Text></View> <View style={whiskyStyles.code}><Text>{whisky.code}</Text></View>
<View style={whiskyStyles.description}><Text>{whisky.description}</Text></View> <View style={whiskyStyles.description}><Text>{whisky.description}</Text></View>
<View style={whiskyStyles.date}><Text>{date}</Text></View> <View style={whiskyStyles.date}><Text>{date}</Text></View>
<View style={whiskyStyles.quantity}><Text>{quantity}</Text></View> <View style={whiskyStyles.quantity}><Text>{whisky.quantity}</Text></View>
</View> </View>
{ props.active && <View style={styles.vertical}> { props.active && <View style={styles.vertical}>
{ locations } { locations }
@ -84,14 +86,63 @@ const whiskyStyles = StyleSheet.create({
export default function App() { export default function App() {
const [whiskies, setWhiskies] = useState<any>(null), const [whiskies, setWhiskies] = useState<any>(null),
[search, setSearch] = useState<string>(""),
[lastSearch, setLastSearch] = useState<string>(""),
[activeWhisky, setActiveWhisky] = useState<string>(""); [activeWhisky, setActiveWhisky] = useState<string>("");
const [, forceUpdate] = useReducer(x => x + 1, 0);
const updateWhisky = (code : string) => {
const query = gql` {
Whisky(code: "${code}") {
inventories {
location {
code
address
city
phone
longitude
latitude
}
quantity
updated
}
code
size
description
updated
quantity
}
}`;
client.request(query)
.then(data => {
for (let i = 0; i < whiskies.length; i++) {
if (whiskies[i].code == data.Whisky.code) {
whiskies[i] = data.Whisky;
break;
}
}
setWhiskies(whiskies);
forceUpdate();
})
.catch (error => {
console.error(error);
});
};
const onPress = (code : string) => { const onPress = (code : string) => {
setActiveWhisky(code == activeWhisky ? "" : code); if (code == activeWhisky) {
setActiveWhisky("");
} else {
setActiveWhisky(code);
updateWhisky(code);
}
}; };
const items = whiskies ? whiskies const items = whiskies ? whiskies
.filter((item : any) => item.size == 750 && item.inventories.length) .filter((item : any) => item.size == 750)
.sort((a : any, b : any) => a.description.localeCompare(b.description))
.map((whisky : any) => { .map((whisky : any) => {
const active = whisky.code == activeWhisky; const active = whisky.code == activeWhisky;
return ( return (
@ -104,39 +155,54 @@ export default function App() {
); );
}) : []; }) : [];
let query = gql` { const submitSearch = () => {
Whiskies { if (search.trim() == "") {
code setWhiskies([]);
description return;
price }
size
lastSeen if (lastSearch == search) {
inventories { return;
location { }
code
address const _query = gql` {
city Whiskies(code:"${search}") {
phone code
longitude description
latitude price
} size
lastSeen
quantity quantity
updated updated
} }
} }`;
}`;
setLastSearch(search);
client.request(query) client.request(_query)
.then(data => { .then(data => {
setWhiskies(data.Whiskies); setWhiskies(data.Whiskies);
}) })
.catch (error => { .catch (error => {
console.error(error); console.error(error);
}); });
};
const keyPress = (event : KeyboardEvent) => {
if (event.code == "\n") {
submitSearch();
}
};
return ( return (
<View style={[styles.container]}> <View style={[styles.container]}>
<TextInput style={[{borderWidth: 1 }]}
onBlur={submitSearch}
onKeyPress={(event : any) => keyPress(event)}
onChange={(value) => setSearch(value.target.value)}></TextInput>
<View style={[styles.container]}>
{ items } { items }
</View>
</View> </View>
); );
} }

View File

@ -215,7 +215,6 @@ const rateLimit = async () => {
} }
const delay = (lastFetch + (Math.random() * 5000 + 2500)) - Date.now(); const delay = (lastFetch + (Math.random() * 5000 + 2500)) - Date.now();
if (delay > 0) { if (delay > 0) {
// console.log(`Rate limiting for ${delay}ms`);
await wait(delay); await wait(delay);
} }
lastFetch = Date.now(); lastFetch = Date.now();
@ -349,7 +348,7 @@ fs.stat("cache")
}) })
.then((response) => { .then((response) => {
if (response.status != 200) { if (response.status != 200) {
throw "Error fetching resource from oregonliquorsearch"; throw new Error("Error fetching resource from oregonliquorsearch");
} }
}); });
}).then(async () => { }).then(async () => {
@ -382,8 +381,7 @@ fs.stat("cache")
const rows = dom.window.document.querySelectorAll('table.list tr'); const rows = dom.window.document.querySelectorAll('table.list tr');
const olccWhiskies = []; const olccWhiskies = [];
const processRow = (row) => {
rows.forEach(row => {
const th = row.querySelectorAll('table.list th a'); const th = row.querySelectorAll('table.list th a');
if (th.length) { if (th.length) {
th.forEach(header => headers.push(header.textContent.trim())); th.forEach(header => headers.push(header.textContent.trim()));
@ -407,7 +405,7 @@ fs.stat("cache")
case 'Proof': case 'Proof':
if (parseFloat(value) == value) { if (parseFloat(value) == value) {
value = parseFloat(value); value = parseFloat(value);
} }
break; break;
case 'Case Price': case 'Case Price':
@ -433,8 +431,9 @@ fs.stat("cache")
whisky[headers[index]] = value; whisky[headers[index]] = value;
}); });
olccWhiskies.push(whisky); olccWhiskies.push(whisky);
}); };
rows.forEach(processRow);
return olccWhiskies; return olccWhiskies;
}).then(async (olccWhiskies) => { }).then(async (olccWhiskies) => {
await Promise.all(olccWhiskies.map(async (olccWhisky) => { await Promise.all(olccWhiskies.map(async (olccWhisky) => {