2ZHTBPOJQI4FS3OQJXUDBD62HVFPGSKI633IXXKAGPGQLURMKVPAC
WA2FN2YR2TDCOLDWCZPYHRHPKJXE7KUB7AFERUCEB3KODVBNCUVQC
Y4W4MACPKH3IVO6JIQYGN4V5LR6EM6RPZEJ4RHH6ZBZUDA63AGRQC
RGMMX4YQP6NPCPFAGZ5SRITSPJQW4F77S6RJLYJMS3D732LUGDPQC
E4HFJ4L4PAGV7R7EXVG2B2CWDGGW7XWU4D37VURZ66HZ3ILHCFUAC
NDMM44EV2XD5RP7J4Q25ZX52LUP7WOMTKO3C2PDXW7IWFEVWUW6QC
VO5G3FF3NDOKCIF2OU7G257R3XIPTFDCAMDTOVKHKOB5Z3I55KDQC
4WREYORZT3SWUXADWHSS6B4PQSP3QSLH77CGNKRH6IRKUMX4TARAC
SE6MCCXTIXSGMAH5EL7EPDXIQKV6THCCY2H4OQJJQOHCUQAWE5VQC
WYTMZJFYVKHR4QH7AV5JUNWXT6NAC5NNQNPZCQSDI6LGI7DVXFYAC
EGNRBQUMOZHAF5B7KCMTAPMB4H3USK4MK33Q6T5V26D6HOJNW6LQC
2FPZGFF5PCGNV45HOB6TROMJFPURHPXM7YSPWNXCHFJS7EICVKQAC
Q4J4JHWWARLZQBYRRR33FRSFZK5BQMWGBGYQ22MLLVUELI7FYHRQC
DCYC55MAVFDM43TEEUMAHLPOHNQ3EJ5PEVCA7H6ZTKNQHJZK7XJAC
4ADMC3SGJNATDR6D7WXHNZ6RD5F5BUPLPRMCEUERWRWKVZQVAO3AC
TBTYGTE3JUKOX6GDBR7AMMMV4RYCUFAK5N3ATSVCLCGESBSMJUHQC
ZI2RJOZ2HXBHX7L54BTSDKESY6NG5KBJLVHCNY7K7ZLGIUOIHQUQC
"@google/maps": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/@google/maps/-/maps-1.0.2.tgz",
"integrity": "sha512-iTsvIIgVjIicC+UdLCJOMSBnOmLnmgnOXrjTBUUQr/ffBMfH9GdnlrA9R+vH/kZSFEp9AZUDw0vJ5ES6avfTPQ==",
"@googlemaps/google-maps-services-js": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/@googlemaps/google-maps-services-js/-/google-maps-services-js-2.0.2.tgz",
"integrity": "sha512-AzhUckl3X9QmopPxx/QKUwoUenvnkhTAP6811siaociclxS3vf4GHGVIMAEHVcvjt+2cWeq+603gLBVciS5TKg==",
"agent-base": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.3.0.tgz",
"integrity": "sha512-salcGninV0nPrwpGNn4VTXBb1SOuXQBiqbrNXoeizJsHrsL6ERFM2Ne3JUSBWRE6aeNJI2ROP/WEEIDUiDe3cg==",
"agentkeepalive": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.1.0.tgz",
"integrity": "sha512-CW/n1wxF8RpEuuiq6Vbn9S8m0VSYDMnZESqaJ6F2cWN9fY8rei2qaxweIaRgq+ek8TqfoFIsUjaGNKGGEHElSg==",
"es6-promisify": "^5.0.0"
"debug": "^4.1.0",
"depd": "^1.1.2",
"humanize-ms": "^1.2.1"
},
"dependencies": {
"debug": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
"integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
"requires": {
"ms": "^2.1.1"
}
}
}
},
"es6-promise": {
"version": "4.2.8",
"resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz",
"integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w=="
},
"es6-promisify": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/es6-promisify/-/es6-promisify-5.0.0.tgz",
"integrity": "sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM=",
"requires": {
"es6-promise": "^4.0.3"
"follow-redirects": {
"version": "1.5.10",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.5.10.tgz",
"integrity": "sha512-0V5l4Cizzvqt5D44aTXbFZz+FtyXV1vrDN6qrelxtfYQKW0KO0W2T/hkE8xvGa/540LkZlkaUjO4ailYTFtHVQ==",
"requires": {
"debug": "=3.1.0"
},
"dependencies": {
"debug": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
"integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==",
"requires": {
"ms": "2.0.0"
}
},
"ms": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
}
}
},
"https-proxy-agent": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-3.0.1.tgz",
"integrity": "sha512-+ML2Rbh6DAuee7d07tYGEKOEi2voWPUGan+ExdPbPW6Z3svq+JCqr0v8WmKPOkz1vOVykPCBSuobe7G8GJUtVg==",
"humanize-ms": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz",
"integrity": "sha1-xG4xWaKT9riW2ikxbYtv6Lt5u+0=",
"version": "7.17.0",
"resolved": "https://registry.npmjs.org/pg/-/pg-7.17.0.tgz",
"integrity": "sha512-70Q4ZzIdPgwMPb3zUIzAUwigNJ4v5vsWdMED6OzXMfOECeYTvTm7iSC3FpKizu/R1BHL8Do3bLs6ltGfOTAnqg==",
"version": "7.18.1",
"resolved": "https://registry.npmjs.org/pg/-/pg-7.18.1.tgz",
"integrity": "sha512-1KtKBKg/zWrjEEv//klBbVOPGucuc7HHeJf6OEMueVcUeyF3yueHf+DvhVwBjIAe9/97RAydO/lWjkcMwssuEw==",
"version": "2.0.9",
"resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-2.0.9.tgz",
"integrity": "sha512-gNiuIEKNCT3OnudQM2kvgSnXsLkSpd6mS/fRnqs6ANtrke6j8OY5l9mnAryf1kgwJMWLg0C1N1cYTZG1xmEYHQ=="
"version": "2.0.10",
"resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-2.0.10.tgz",
"integrity": "sha512-qdwzY92bHf3nwzIUcj+zJ0Qo5lpG/YxchahxIN8+ZVmXqkahKXsnl2aiJPHLYN9o5mB/leG+Xh6XKxtP7e0sjg=="
"query-string": {
"version": "github:jpoehnelt/query-string#e22cdb49ef848efaed4fe60d63d2504b496027c1",
"from": "github:jpoehnelt/query-string#e22cdb49ef848efaed4fe60d63d2504b496027c1",
"requires": {
"decode-uri-component": "^0.2.0",
"split-on-first": "^1.0.0",
"strict-uri-encode": "^2.0.0"
}
},
const inBrackets = /\s*\(.*\)\s*/;
const specialqty = /((\d+)\s*[Xx]\s*)?(\d*\.?\d+)\s*([A-WYZa-wyz]{1,2})(\b|$)/;
function normalize(brand, title, type) {
let ot = title.toLowerCase();
title = title.replace(brand, '');
brand = brand.toLowerCase().replace('.', '').replace(/&|(\b(and|health|therapeutics|co|company|inc|cannabis)\b)/g, '').trim();
if(brand.includes('sundial')) {
brand = brand.replace(/\b(calm|ease|flow|lift|spark)\b/g, '').trim();
title = title.replace(/\b(calm|ease|flow|lift|spark)\b/g, '').trim();
} else if(brand === 'alta vie') {
brand = 'altavie';
} else if(brand === 'twd') {
brand = 'tweed';
}
let ratio = title.match(/\d+:\d+/);
let q = title.match(specialqty);
title = title
.toLowerCase()
.replace(inBrackets, '')
.replace(brand, '')
.replace(/twd(\.?)|"|-|−|(\d+:\d+)|%|&|:/g, '')
.replace(/\b(liquid|and|oral|drop(s?)|((hard|soft|liquid)(-|−| )?)?(capsule|cap|gel)(s?)|pre(-|−| )?roll(s?)|pack(s?)|joint(s?)|oil|flower|spray)\b/g, '')
.replace(specialqty, '')
.trim();
if(ot.includes('fiveup') || /5\s*up/.test(ot)) {
title = '5up';
//early return?
} else if(ot.includes('tenup') || /10\s*up/.test(ot)) {
title = '10up';
//early return?
} else if(title.includes('reserve') && brand.includes('edison')) {
title = title.replace('reserve', '').trim();
brand = 'edison reserve';
} else if(title.includes('select') && brand.includes('canaca')) {
title = title.replace('select', '').trim();
brand = 'canaca select';
} else if(brand === 'tantalus labs') {
title = title.replace(/bc|sungrown/g, '').trim();
let days_of_week = ['monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday', 'sunday'];
function notmissing(parameters, fields, ws, response_ID) {
let sad = fields.filter(field => parameters[field] !== 0 && parameters[field] !== 0n && !parameters[field]).join(', ');
if(sad.length) {
ws.send(JSON.stringify({
response_ID,
data: sad
}));
return false;
} else {
return true;
if(title.includes('casa') && title.includes('blanca') && brand.includes('edison')) {
title = 'casablanca';
}
if(type === 'oil' || type === 'spray') {
title = title.replace('mist', '').trim();
}
if(title.length === 0) {
if(ratio === null) {
title = ot;
} else {
title = ratio[0];
}
}
if(type === 'capsule' && q !== null) {
title += ' ' + q[0].replace(/\s+/, '');
}
title = title.split(/\s+/).sort().join(' ');
return [brand, title, type];
try {//empty string should turn to null / undefined
let fuck = await googleMapsClient.geocode({address: `${parameters.address}, ${parameters.address2}, ${parameters.city}, ${parameters.region}, ${parameters.country}, ${parameters.postal_code}`}).asPromise().then();
if(fuck.status === 200 && fuck.json.status === 'OK') {
let fuck2 = await googleMapsClient.timezone({location: fuck.json.results[0].geometry.location}).asPromise();
if(fuck2.status === 200) {//https://stackoverflow.com/questions/20561254
let store = await pool.query('insert into store (name, URL, address, address2, city, region, country, postal_code, coordinate, timezone) values ($1, $2, $3, $4, $5, $6, $7, $8, point($9, $10), $11) returning id', [
parameters.name,
parameters.URL,
parameters.address,
parameters.address2 ? parameters.address2 : null,
parameters.city,
parameters.region,
parameters.country,
parameters.postal_code,
fuck.json.results[0].geometry.location.lat,
fuck.json.results[0].geometry.location.lng,
fuck2.json.timeZoneId
]);
if(Array.isArray(parameters.taxes) && parameters.taxes.length) {
let query = 'insert into store_tax (store, name, rate) values ';
let params = [];
for(let i = 0; i < parameters.taxes.length; ++i) {
query += `($${i * 3 + 1}, $${i * 3 + 2}, $${i * 3 + 3}),`;
params.push(store.rows[0].id);
params.push(parameters.taxes[i].name);
params.push(parameters.taxes[i].rate);
}
await pool.query(query.slice(0, -1), params);
}
if(Array.isArray(parameters.times) && parameters.times.length) {
let query = 'insert into store_time (store, open) values ';
let params = [];
for(let i = 0; i < parameters.times.length; ++i) {
query += `($${i * 2 + 1}, $${i * 2 + 2}),`;
params.push(store.rows[0].id);
let ts = parameters.times[i].time_start.split(':');
ts = Number(parameters.times[i].day_start) * 1440 + Number(ts[0]) * 60 + Number(ts[1]);
let te = parameters.times[i].time_end.split(':');
te = Number(parameters.times[i].day_end) * 1440 + Number(te[0]) * 60 + Number(te[1]);
params.push(`[${ts}, ${te})`);
}
await pool.query(query.slice(0, -1), params);
}
ws.send(JSON.stringify({
response_ID: request_ID
}));
//ws.publish(store.rows[0].id)
if(notmissing(parameters, ['name', 'URL', 'address', 'city', 'region', 'country', 'postal_code'], ws, request_ID)) {
let params = [
parameters.name,
parameters.URL,
parameters.address,
parameters.address2 ? parameters.address2 : null,
parameters.city,
parameters.region,
parameters.country,
parameters.postal_code
];
//I went through the effort of delaying gapi as long as I could and folding three inserts into one transaction, only for the unique constraint to goof me up. Likely won't happen often though, so accept the tradeoff.
let q = 1;
let query = '';
if(Array.isArray(parameters.taxes) && parameters.taxes.length) {
query += `, insert${++q} as (
insert into store_tax (store, name, rate)
values ${parameters.taxes.map(tax => `((select id from insert1), $${params.push(tax.name)}, $${params.push(tax.rate)})`).join(', ')}
)`;
}
if(Array.isArray(parameters.times) && parameters.times.length) {
let times = parameters.times.map((time, idx) => {
let ts = time.time_start.split(':');
ts = Number(time.day_start) * 1440 + Number(ts[0]) * 60 + Number(ts[1]);
let te = time.time_end.split(':');
te = Number(time.day_end) * 1440 + Number(te[0]) * 60 + Number(te[1]);
if(te <= ts) {//
return `time ${idx}: close time(${days_of_week[time.day_end]} ${time.time_end}) must be later than open time(${days_of_week[time.day_start]} ${time.time_start})`;//
}//
return `((select id from insert1), $${params.push(`[${ts}, ${te})`)})`;
});
let time_errors = times.filter(time => time.startsWith('time'));//
if(time_errors.length) {//
ws.send(JSON.stringify({//
response_ID: request_ID,//
data: time_errors//
}));//
break;//
}//to overcome postgres error shortcoming
query += `, insert${++q} as (
insert into store_time (store, open)
values ${times.join(', ')}
)`;
}
if(Array.isArray(parameters.images) && parameters.images.length) {
query += `, insert${++q} as (
insert into store_image (store, URL)
values ${parameters.images.map(image => `((select id from insert1), $${params.push(image)})`).join(', ')}
)`;
}
/*
two google maps api error handlers because their api is inconsistent as fuck
missing api key will return 'REQUEST_DENIED', but 200, so (2)
wrong parameters will return 'INVALID_REQUEST', so 400 (1) for geocode
but 200 (2) for timezone!! 400 throws automatically, but 200 need to be thrown manually
*/
let location;
try {
location = (await googleMapsClient.geocode({params: {address: `${parameters.address}, ${parameters.address2}, ${parameters.city}, ${parameters.region}, ${parameters.country}, ${parameters.postal_code}`, key: process.env.GAPI}})).data;
if(location.status === 'OK') {
location = location.results[0].geometry.location;
} else {
throw 'geocode fuckup';
} catch(e) {
if(e.response) {
console.error(e.response.data);//google maps api(1)
}
else if(e.status) {
console.error(e);//google maps api(2)
}
ws.send(JSON.stringify({
response_ID: request_ID,
data: 'google geocode failure'
}));//todo: status: 'ZERO_RESULTS' => "invalid address :("
break;
} catch(e) {
let error = 'store already exists';
if(e.constraint !== 'store_url_key') {
console.error(e);
error = 'error';
let tz;
try {
tz = (await googleMapsClient.timezone({params: {location, timestamp: 0, key: process.env.GAPI}})).data;
if(tz.status === 'OK') {
tz = tz.timeZoneId;
} else {
throw tz;
}
} catch(e) {
if(e.response) {
console.error(e.response.data);//google maps api(1)
}
else if(e.status) {
console.error(e);//google maps api(2)
}
ws.send(JSON.stringify({
response_ID: request_ID,
data: 'google timezone failure'
}));
break;
ws.send(JSON.stringify({
response_ID: request_ID,
data: error
}));
//promise then catch is fine since this is the last thing to do this function. don't need to break;
pool.query(
`with insert1 as (
insert into store (name, URL, address, address2, city, region, country, postal_code, coordinate, timezone)
values ($1, $2, $3, $4, $5, $6, $7, $8, point($${params.push(location.lat)}, $${params.push(location.lng)}), $${params.push(tz)})
returning id
)${query}
select id from insert1`,
params
).then(r => {
console.log/*ws.publish*/(r.rows[0].id)
ws.send(JSON.stringify({
response_ID: request_ID
}));
}).catch(e => {
/* todo
Class 23 https://www.postgresql.org/docs/current/errcodes-appendix.html with e.code
e.table
https://stackoverflow.com/a/4108266
use implicit names in initialize.sql. then, can use e.constraint, remove e.table + _ from beginning, remove suffix to see which columns are affected
e.detail is fine for 23505, but 23514 just complains about the row as a whole. how can we get the specific details? https://stackoverflow.com/a/47972916 for python...
useful
code: '23505',
detail: 'Key (store, name)=(21, hst) already exists.',
useless
[message]: 'new row for relation "store_tax" violates check constraint "store_tax_rate_check"',
name: 'error',
length: 266,
severity: 'ERROR',
code: '23514',
detail: 'Failing row contains (15, 28, hst, 2, 2020-02-11 20:20:54.923496-05, infinity).',
what I want: rate > 0 and rate < 1
2 > 0 and 2 < 1
true and false
false
*/
console.log(e);
ws.send(JSON.stringify({
response_ID: request_ID,
data: 'db fuckup'
}));
});