Compare commits
5 Commits
baf891e5ed
...
0748bcf4b8
Author | SHA1 | Date | |
---|---|---|---|
![]() |
0748bcf4b8 | ||
![]() |
7377c316db | ||
![]() |
e6616356a2 | ||
![]() |
afabcd057f | ||
![]() |
056b388520 |
38
README.md
38
README.md
@ -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
|
||||||
|
@ -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); }
|
||||||
|
@ -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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
11
whisky.js
11
whisky.js
@ -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) => {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user