XBHLXAKNBJNWSXZMHVKDMDVI7CUF5MN22FFS3XQIOLFDJHDCLTVAC NHVZFKZDPQGCZ6UOVQU5U3Q3SOAKGOGZNFIE7C6IB6YP3ZS4VVDAC RAN5QU4U2OVNLDGZVVTJNCXDN5GNAVPXGKHADZO2ML3Y5KLU4QPQC QFATAUXF3SMSTZULKPCK4W3AZ4NEO3VFZE7SWIBDBXGC7JU5L6TAC QQAJ5O5EW6ASWLXQ627Y2F6YGMYKCM4WS37LTZYOMNPPFDPMNBUAC ZI2RJOZ2HXBHX7L54BTSDKESY6NG5KBJLVHCNY7K7ZLGIUOIHQUQC E4HFJ4L4PAGV7R7EXVG2B2CWDGGW7XWU4D37VURZ66HZ3ILHCFUAC PMEQGKI5R3WTMDKH4B2PN67XDRMYZFULPKPSDTOGF5BPDGYNO2KQC JP2LJ6B34S4IS6MPKF5LMMYUWIYMGU3JCJRRR4PTZ4IWM653JCXAC NDMM44EV2XD5RP7J4Q25ZX52LUP7WOMTKO3C2PDXW7IWFEVWUW6QC XQA3IQXSGAO2FST3F64WLVZ7JNLMQYKT3LO4SHHKYR2D765OPGHQC Y4W4MACPKH3IVO6JIQYGN4V5LR6EM6RPZEJ4RHH6ZBZUDA63AGRQC VO5G3FF3NDOKCIF2OU7G257R3XIPTFDCAMDTOVKHKOB5Z3I55KDQC SE6MCCXTIXSGMAH5EL7EPDXIQKV6THCCY2H4OQJJQOHCUQAWE5VQC Y6DVC22CQACFO75XTUE4BZQXCVJAYOECY2F7M2IYV7AWFGOF5G3QC 7XVW32MMBBIGWQZ3QRD365TBIJU4DYN6T4VO2E46WFDCFHGO2BMAC WYTMZJFYVKHR4QH7AV5JUNWXT6NAC5NNQNPZCQSDI6LGI7DVXFYAC 2ZHTBPOJQI4FS3OQJXUDBD62HVFPGSKI633IXXKAGPGQLURMKVPAC 63VXWIHIAKGK7J4VTNRUAG2V32N2QUSWFELB6GD34S54FGRWAPCQC CMOE5TZ3HY6BG6KB56HMIJ6BU5J6D3NE7VFK2NSAX2XEXAPEKJQQC 4WREYORZT3SWUXADWHSS6B4PQSP3QSLH77CGNKRH6IRKUMX4TARAC const postgres = new (require('pg').Client)();
const pg = require('pg');const postgres = new pg.Client();const googleMapsClient = new (require("@googlemaps/google-maps-services-js").Client)({});const gay = /^Friendly Stranger/i;const gay2 = /^stash\s+(and|&)\s+co/i;const gay8 = /^fire\s+(and|&)\s+flower/i;const gay3 = /^tokyo\s+smoke/i;const gay4 = /^spiritleaf/i;const gay5 = /^CANNACO/i;const gay6 = /^one\s+plant/i;const gay7 = /^sessions/i;
sanitize_name: function (n) {if(n === 'Cannabis Boutique Inc.') {n = 'Cannabis Boutique';} else if(n === 'MARY JANE RIGS & CANNABIS') {n = 'Mary Jane Rigs & Cannabis';} else if(n === 'HELLO CANNABIS SSM') {n = 'HELLO CANNABIS'} else if(gay2.test(n)) {n = 'Stash & Co.';} else if(gay3.test(n)) {n = 'Tokyo Smoke';} else if(gay4.test(n)) {n = 'SPIRITLEAF';} else if(gay5.test(n)) {n = 'CANNACO';} else if(gay6.test(n)) {n = 'One Plant';} else if(gay7.test(n)) {n = 'Sessions';} else if(gay8.test(n)) {n = 'Fire & Flower';} else if(n.toLowerCase().trim() === 'rainbow thunder bay cannabis accessories') {n = 'Rainbow';} else if(n !== 'Made In Cannabis' && n !== 'Mary Jane on Penny Lane' && n !== 'This Is Cannabis' && n !== 'BURNSIDE BUDS.CA' && n !== 'The Cannabis Guys') {n = n.replace(/\./g, '');let first = n.split(/\s+/);//if(first.length > 2) {[first, ...n] = first;n = n.join(' ');//todo: replace if not prefixed by "of", "the", etc. what are those words called?n = n.split('-')[0].split(/\bat\b/i)[0].split(/\bon\b/i)[0].replace(/\b(sale(s?)|shop|retail|boutique|market|premium|cannabis|store(s?)|corp(orat(ed|ion))?|inc(orporat(ed|ion))?|co(mpany)?|supply|retailer(s?)|limited|ltd|calgary|bridgeland|downtown|kensington|barrhaven|recreational|dispensary)\b|\(.+\)/gi,'').trim();n = (first + ' ' + n).trim().replace(/\s+/g, ' ');if(n === '420') {//console.log('dumbass');n = 'FOUR20';} else if(n === 'HOBO') {n = 'Hobo';} else if(n === 'NUMO') {n = 'Numo';} else if(gay.test(n)) {n = "Friendly Stranger";}//}}if(n.toUpperCase() === n) {//todo: check how many are title cased, all caps, all lower.//console.log(`don't yell at me :( ${n}`);}return n;},g: async function (address) {let addr = {address: '', address2: ''};let location = (await googleMapsClient.geocode({params: {address, key: process.env.GAPI}})).data;if(location.status === 'OK') {let wow = 0;for(let j = 0; j < location.results[0].address_components.length; ++j) {if(location.results[0].address_components[j].types.includes('administrative_area_level_1')) {addr.region = location.results[0].address_components[j].long_name;++wow;} else if(location.results[0].address_components[j].types.includes('country')) {addr.country = location.results[0].address_components[j].long_name;++wow;} else if(location.results[0].address_components[j].types.includes('locality')) {addr.city = location.results[0].address_components[j].long_name;++wow;} else if(location.results[0].address_components[j].types.includes('postal_code')) {addr.postal_code = location.results[0].address_components[j].long_name.replace(/\s+/, '');++wow;} else if(location.results[0].address_components[j].types.includes('route')) {addr.address += ' ' + location.results[0].address_components[j].long_name;++wow;} else if(location.results[0].address_components[j].types.includes('street_number')) {addr.address += location.results[0].address_components[j].long_name;++wow;} else if(location.results[0].address_components[j].types.includes('subpremise')) {addr.address2 = location.results[0].address_components[j].long_name;}}if(wow < 6) {console.log('hardcode me', address, location);throw('unparseable address');}location = location.results[0].geometry.location;let [tz, foot, car] = await Promise.all([googleMapsClient.timezone({params: {location, timestamp: 0, key: process.env.GAPI}}),request(`http://127.0.0.1:5000/nearest/v1/fuck/${location.lng},${location.lat}`).then(x => JSON.parse(x).waypoints[0].hint),request(`http://127.0.0.1:5001/nearest/v1/fuck/${location.lng},${location.lat}`).then(x => JSON.parse(x).waypoints[0].hint)]);if(tz.data.status === 'OK') {return [addr, location, tz.data.timeZoneId, foot, car];} else {throw tz;}} else {throw location;}},
const request = require('request-promise-native');require('dotenv').config({path: '../.env'});const { Client } = require('pg');let postgres = new Client();
Object.entries(require('./common.js')).forEach(([name, exported]) => global[name] = exported);
let hints = Promise.all([request('http://127.0.0.1:5000/nearest/v1/fuck/-120.2209884,55.7501513').then(x => JSON.parse(x).waypoints[0].hint),request('http://127.0.0.1:5001/nearest/v1/fuck/-120.2209884,55.7501513').then(x => JSON.parse(x).waypoints[0].hint),'https://images.squarespace-cdn.com/content/5d2516d4eafe0d00016d4f52/1563851764355-Z79A7Y9YMODUHQ06GDR2']);
.then(async () => postgres.query(`with storeid as (select ios_store_id('Dawson Creek Cannabis Co.','british columbia','canada','11000 8th Street','Unit 19',
.then(async () => {let r = await g("UNIT 19 - 11000 8 St, Dawson Creek, V1G 4K6, british columbia, canada");return postgres.query(`select ios_store_id(
)),ins as (insert into store_image (store_id, url)select ios_store_id, $3 from storeidon conflict do nothing)select ios_store_id from storeid`,await hints));
)`,[r[0].region, r[0].country, r[0].address, r[0].address2, r[0].city, r[0].postal_code, r[1].lng, r[1].lat, r[3], r[4], r[2]]);});
//todo: other official government online stores, prescedence to be deterministic, canada only?
//todo: other official government online stores//default concentrations from online stores with fixed prescedence. OCS seems to have the highest quality data
name = 'Ontario Cannabis Store' and region = 'ontario' and country = 'canada' orname = 'BC Cannabis Stores' and region = 'british columbia' and country = 'canada'
joined_name = 'ocs' and region = 'ontario' orjoined_name = 'bc' and region = 'british columbia' orjoined_name = 'alberta' and region = 'alberta'
function sanitize_name(n) {if(n === 'Cannabis Boutique Inc.') {n = 'Cannabis Boutique';} else if(n === 'MARY JANE RIGS & CANNABIS') {n = 'Mary Jane Rigs & Cannabis';} else if(n === 'HELLO CANNABIS SSM') {n = 'HELLO CANNABIS'} else if(gay2.test(n)) {n = 'Stash & Co.';} else if(gay3.test(n)) {n = 'Tokyo Smoke';} else if(gay4.test(n)) {n = 'SPIRITLEAF';} else if(gay5.test(n)) {n = 'CANNACO';} else if(gay6.test(n)) {n = 'One Plant';} else if(gay7.test(n)) {n = 'Sessions';} else if(gay8.test(n)) {n = 'Fire & Flower';} else if(n.toLowerCase().trim() === 'rainbow thunder bay cannabis accessories') {n = 'Rainbow';} else if(n !== 'Made In Cannabis' && n !== 'Mary Jane on Penny Lane' && n !== 'This Is Cannabis' && n !== 'BURNSIDE BUDS.CA' && n !== 'The Cannabis Guys') {n = n.replace(/\./g, '');let first = n.split(/\s+/);//if(first.length > 2) {[first, ...n] = first;n = n.join(' ');//todo: replace if not prefixed by "of", "the", etc. what are those words called?n = n.split('-')[0].split(/\bat\b/i)[0].split(/\bon\b/i)[0].replace(/\b(sale(s?)|shop|retail|boutique|market|premium|cannabis|store(s?)|corp(orat(ed|ion))?|inc(orporat(ed|ion))?|co(mpany)?|supply|retailer(s?)|limited|ltd|calgary|bridgeland|downtown|kensington|barrhaven|recreational|dispensary)\b|\(.+\)/gi,'').trim();n = (first + ' ' + n).trim().replace(/\s+/g, ' ');if(n === '420') {//console.log('dumbass');n = 'FOUR20';} else if(n === 'HOBO') {n = 'Hobo';} else if(n === 'NUMO') {n = 'Numo';} else if(gay.test(n)) {n = "Friendly Stranger";}//}}if(n.toUpperCase() === n) {//todo: check how many are title cased, all caps, all lower.//console.log(`don't yell at me :( ${n}`);}return n;}async function g(address) {let addr = {address: '', address2: ''};let location = (await googleMapsClient.geocode({params: {address, key: process.env.GAPI}})).data;if(location.status === 'OK') {let wow = 0;for(let j = 0; j < location.results[0].address_components.length; ++j) {if(location.results[0].address_components[j].types.includes('administrative_area_level_1')) {addr.region = location.results[0].address_components[j].long_name;++wow;} else if(location.results[0].address_components[j].types.includes('country')) {addr.country = location.results[0].address_components[j].long_name;++wow;} else if(location.results[0].address_components[j].types.includes('locality')) {addr.city = location.results[0].address_components[j].long_name;++wow;} else if(location.results[0].address_components[j].types.includes('postal_code')) {addr.postal_code = location.results[0].address_components[j].long_name.replace(/\s+/, '');++wow;} else if(location.results[0].address_components[j].types.includes('route')) {addr.address += ' ' + location.results[0].address_components[j].long_name;++wow;} else if(location.results[0].address_components[j].types.includes('street_number')) {addr.address += location.results[0].address_components[j].long_name;++wow;} else if(location.results[0].address_components[j].types.includes('subpremise')) {addr.address2 = location.results[0].address_components[j].long_name;}}if(wow < 6) {console.log('hardcode me', address, location);throw('unparseable address');}location = location.results[0].geometry.location;let [tz, foot, car] = await Promise.all([googleMapsClient.timezone({params: {location, timestamp: 0, key: process.env.GAPI}}),request(`http://127.0.0.1:5000/nearest/v1/fuck/${location.lng},${location.lat}`).then(x => JSON.parse(x).waypoints[0].hint),request(`http://127.0.0.1:5001/nearest/v1/fuck/${location.lng},${location.lat}`).then(x => JSON.parse(x).waypoints[0].hint)]);if(tz.data.status === 'OK') {return [addr, location, tz.data.timeZoneId, foot, car];} else {throw tz;}} else {throw location;}}
if(notmissing(parameters, ['name', '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,Boolean(parameters.delivery),Boolean(parameters.pickup),Boolean(parameters.prepayment),parameters.phone ? parameters.phone : null];//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_id, name, rate, types)values ${parameters.taxes.map(tax => `((select store_id from insert1), $${params.push(tax.name)}, $${params.push(tax.rate)}, $${params.push(tax.types)})`).join(', ')})`;
let addr, location, tz, foot, car;try {[addr, location, tz, foot, car] = await g(`${parameters.address}, ${parameters.address2}, ${parameters.city}, ${parameters.region}, ${parameters.country}, ${parameters.postal_code}`);} catch(e) {if(e.response) {console.error(e.response.data);} else {console.error(e);
if(Array.isArray(parameters.times)) {let fuck = [];for(let i = 0; i < parameters.times.length; ++i) {try {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]);if(te <= ts) {fuck.push(`(int4range($${params.push(ts)}, 10080)),(int4range(0, $${params.push(te)}))`);//oops possibly 0,0 empty range. nvm good job range_agg} else {fuck.push(`(int4range($${params.push(ts)}, $${params.push(te)}))`);}} catch(e) {}}if(fuck.length) {query += `, insert${++q} as (insert into store_time (store_id, open)select store_id, unnest from insert1 cross join (select unnest(range_agg(column1, true, true)) from (values ${fuck.join(',')}) as ex) as idc)`;}
ws.send(JSON.stringify({response_ID: request_ID,data: 'geocode failure'}));break;}let fuck = [];if(Array.isArray(parameters.times)) {for(let i = 0; i < parameters.times.length; ++i) {try {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]);if(ts < 0 || ts >= 10080 || te < 0 || te >= 10080) {continue;}if(te <= ts) {fuck.push([ts, 10080], [0, te]);//oops possibly 0,0 empty range. nvm good job range_agg} else {fuck.push([ts, te]);}} catch(e) {}
if(Array.isArray(parameters.images) && parameters.images.length) {query += `, insert${++q} as (insert into store_image (store_id, URL)values ${parameters.images.map(image => `((select store_id from insert1), $${params.push(image)})`).join(', ')})`;}/*two google maps api error handlers because their api is inconsistent as fuckmissing api key will return 'REQUEST_DENIED', but 200, so (2)wrong parameters will return 'INVALID_REQUEST', so 400 (1) for geocodebut 200 (2) for timezone!! 400 throws automatically, but 200 need to be thrown manually
}let jn = sanitize_name(parameters.name)let params = [jn,jn,parameters.URL,addr.address,addr.address2,addr.city,addr.region,addr.country,addr.postal_code,location.lng,location.lat,foot,car,tz,parameters.phone ? parameters.phone : null,parameters.phone && parameters.extension ? parameters.extension : null,Boolean(parameters.delivery),Boolean(parameters.pickup),Boolean(parameters.prepayment),JSON.stringify(fuck)];let q = 1;let query = '';if(Array.isArray(parameters.taxes) && parameters.taxes.length) {query += `, insert${++q} as (insert into store_tax (store_id, name, rate, types)values ${parameters.taxes.map(tax => `((select store_id from insert1), $${params.push(tax.name)}, $${params.push(tax.rate)}, $${params.push(tax.types)})`).join(', ')})`;}if(Array.isArray(parameters.images) && parameters.images.length) {query += `, insert${++q} as (insert into store_image (store_id, URL)values ${parameters.images.map(image => `((select store_id from insert1), $${params.push(image)})`).join(', ')})`;}pool.query(`with insert1 as (insert into store (joined_name,name,url,address,address2,city,region,country,postal_code,longitude,latitude,osrm_hint_foot,osrm_hint_car,timezone,phone,extension,delivery,pickup,prepayment,open) select$1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15,$16,$17,$18,$19,(select range_agg(int4range((x->>0)::int, (x->>1)::int), true, true) from json_array_elements($20) as t(x))returning store_id)${query}select * from get_stores((select store_id from insert1))`,params).then(r => {ws.publish('store', JSON.stringify({what: 'store',how: 'add',data: r.rows[0]}));ws.send(JSON.stringify({response_ID: request_ID}));}).catch(e => {/* todoClass 23 https://www.postgresql.org/docs/current/errcodes-appendix.html with e.codee.tablehttps://stackoverflow.com/a/4108266use implicit names in initialize.sql. then, can use e.constraint, remove e.table + _ from beginning, remove suffix to see which columns are affectede.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...usefulcode: '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 < 12 > 0 and 2 < 1true and falsefalse
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 location;}} 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;}let hints = Promise.all([request(`http://127.0.0.1:5000/nearest/v1/fuck/${location.lng},${location.lat}`).then(x => JSON.parse(x).waypoints[0].hint),request(`http://127.0.0.1:5001/nearest/v1/fuck/${location.lng},${location.lat}`).then(x => JSON.parse(x).waypoints[0].hint)]);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;}hints = await hints;//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,delivery,pickup,prepayment,phone,longitude,latitude,osrm_hint_foot,osrm_hint_car,timezone) values ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$${params.push(location.lng)},$${params.push(location.lat)},$${params.push(hints[0])},$${params.push(hints[1])},$${params.push(tz)})returning store_id)${query}select * from get_stores((select store_id from insert1))`,params).then(r => {ws.publish('store', JSON.stringify({what: 'store',how: 'add',data: r.rows[0]}));ws.send(JSON.stringify({response_ID: request_ID}));}).catch(e => {/* todoClass 23 https://www.postgresql.org/docs/current/errcodes-appendix.html with e.codee.tablehttps://stackoverflow.com/a/4108266use implicit names in initialize.sql. then, can use e.constraint, remove e.table + _ from beginning, remove suffix to see which columns are affectede.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...usefulcode: '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 < 12 > 0 and 2 < 1true and falsefalse*/console.log(e);ws.send(JSON.stringify({response_ID: request_ID,data: 'db fuckup'}));});}
console.log(e);ws.send(JSON.stringify({response_ID: request_ID,data: 'db fuckup'}));});