63VXWIHIAKGK7J4VTNRUAG2V32N2QUSWFELB6GD34S54FGRWAPCQC NPLF22U6DFRBKB5LINVRLZTSSY3MMHCLYSLR5WMDSKGNCHPNAEIQC A3FTXJ6OBPF426MSYBNR7IPLUOZFWQ5PMKOIGXM7TV4SX6GJ7T2QC R3NNNQ3C6ITL5FMPMEUNRXQIZX2K2LBHWB762N6TBYQVZQAUFR5QC 6QCQLOKDENPPQQ4ZIWHSZSBUCO7TDYPMUE4JPIVF7AYQXGL5QRTQC ZBUC3VBT42SDAG42OCUX7MTT6GNPX2GEGWU2QIUYAY4NMX33IGLAC 5RSETDN4QCQLU7Z55EFXSW27CBEW6HD2MVD2MKB6WDBXIKXFHKNQC EGNRBQUMOZHAF5B7KCMTAPMB4H3USK4MK33Q6T5V26D6HOJNW6LQC ZI2RJOZ2HXBHX7L54BTSDKESY6NG5KBJLVHCNY7K7ZLGIUOIHQUQC E4HFJ4L4PAGV7R7EXVG2B2CWDGGW7XWU4D37VURZ66HZ3ILHCFUAC 5XHBBUBSPTNNPEZZVELCS5AEAKQVHEMLAI2URKBMMBUGWTPARFVQC NDMM44EV2XD5RP7J4Q25ZX52LUP7WOMTKO3C2PDXW7IWFEVWUW6QC NJ233BBUD2WBHKNEWPO5JWQTECTGR5F43NYTSNOZ5J3K22U5C47QC NBGKNI25T2AKNWDH5Z6U6JDUHLQPNRL6GQFF5VS37OOHONSP4CKQC BUCBGGRUTH7HNAADSU24U4BPHPUY7QYB3KRZPIIDUERZTL3YNTZQC YUWSM4FCXGL5DRWUGCN26ADHV27THP74V6HFUR4CDUICVPIGX3QAC I4MTA4E4GTMWJ4NC42QRWV55JRZ2K2W7SDSH5GAW2N4MIAQWS3DQC SE6MCCXTIXSGMAH5EL7EPDXIQKV6THCCY2H4OQJJQOHCUQAWE5VQC ER4O22CFHMFRSJRCDYNJVT3U7Y3LMTP3SY5TEASJI5H6WLSV6K6QC PK7ZUXD7ELS4V6SNYYFWP45JKWKJOVWBGFDRSFY3LP47IU2M55BAC CHOON7UFJCWBEFPYPXHT2PPBSF6MPSZPWELPH2HRCJ6ZKQBDWKYAC WYTMZJFYVKHR4QH7AV5JUNWXT6NAC5NNQNPZCQSDI6LGI7DVXFYAC MM3HQWNEUZVOGQ3WWBTECBY6F47SN4U43I5UNDOAFWO3R7U5XPBAC 2FPZGFF5PCGNV45HOB6TROMJFPURHPXM7YSPWNXCHFJS7EICVKQAC WYEWZOEIGDF3SIG6LHQVOKHI6MTDEYMRWZFGDFCEH27EDL2H4BVQC 2ZHTBPOJQI4FS3OQJXUDBD62HVFPGSKI633IXXKAGPGQLURMKVPAC Q3A33UTWKXZATZKX3LQ42MUAWFTWDZDMNVMLKWGC3QF6L6HBLUQAC BPHFBV52V4OA6WLKXGFK7Q4IM5EUS2SKAW5ZGSA6K3NJFJJVATIAC Y2A2HVX75TL34PXDCUDRCYBAFD6ZI75WB64VYSCTX3KQWQRDBKBQC ONTV36PSUYFGQS4OJVRW4JO3SA6XMTUN3G4HIO6YVCOLUV5DXXPAC CFWRFX3QZSXT5VAUZQ6R344R4OAZBPL6VTQACDNOYMHJICP4QALAC 54TMOAIBD5UBRSWPGW45YZO4VMVKSP7YYKIQ7H4LWSVCN2E3QA4QC 5FCMFAPUTCCO2JILRWLC4PHOSHYFCQNH3D7PKLA267MHVZADOX2AC P7PFZMZQ5S7GZRJVYE6ACAIDKVTD6YHOOGBHZ52MBSGAIDDGJTBQC KRP35NGEHSKKLQP522S7LNZN37U7Z23Q7CDTWQMRKS2NHSA6CKXQC UCY5PVFCT2QIOGR47KFS4KBF2ZPHRBOAIOH6PJEVWCHLMBRBLOMQC WZKS2NSIL7VST55EQ7QEF2YG2X3L2Y6W54AIG463ZRH462QR2NWQC CPNWTEF4ZZHBAB565BALMX5QO2CR3Q32YUHE3FMWJO3O25DZNU5AC VIGS24FLOPPTBLIBZIB2LFMYCDZ3AO4FBXYADAKQMJFNPM4JHAGQC strain strain not null,strain_family_id integer references strain_family on delete set null,
species species not null,strain_id integer references strain on delete set null,
create or replace function ios_product_id(_producer text, _brand text, _display_name text, _joined_name citext, _type product_type, _strain strain, _strain_family citext, _description text, _show boolean, _terpenes citext[], _dosage decimal, out _product_id integer) as $$
create or replace function ios_product_id(_producer text, _brand text, _display_name text, _joined_name citext, _type product_type, _species species, _strain citext, _description text, _show boolean, _terpenes citext[], _dosage decimal, out _product_id integer) as $$
create or replace function get_store(_store_id integer) returns table(name text, url text, address citext, address2 citext, city citext, region citext, country citext, postal_code citext, coordinate point, timezone text, images json, times json) as $$selectname,url,address,address2,city,region,country,postal_code,coordinate,timezone,images,timesfromstoreleft outer join (selectstore_id,json_agg(url) as imagesfromstore_image--order by rankgroup bystore_id) as store_images using (store_id)left outer join (selectstore_id,json_agg(open) as times--combine the ranges? key value? (day : array of times)fromstore_timegroup bystore_id) as store_times using (store_id)wherestore_id = _store_id$$ language sql;create or replace function get_products(_store_id integer = null,_user_id integer = null) returns table(id integer,type product_type,producer text,brand text,name text,species species,strain text,terpenes citext[],image text,cbd numrange,thc numrange,min_price decimal(5, 2),quantity decimal) as $$selectproduct_id,type,producer,brand,product.display_name,species,strain.display_name,terpenes,destination,cbd,thc,min,firstfromproductleft outer join strain using (strain_id)left outer join (select distinct on(product_id)--https://dba.stackexchange.com/a/159899product_id,destinationfromproduct_image--order by rank) as product_image using (product_id)inner join (--aggregate first, then join https://stackoverflow.com/a/27626358 I think this counts as "retrieve all or most"? obviously more filters maybe not, but let's optimize laterselectproduct_id,range_merge(cbd) as cbd,range_merge(thc) as thc,min(price),first(quantity)--this is used to convert mg / g to mg / piece for ediblesfrommenu_iteminner join variant using (variant_id)wherestore_id = _store_id or _store_id is nullgroup byproduct_id) as product_agg using (product_id)whereshow = true--brand ilike $1--order by cbd, thc, min_price, etc. idk if ranges are sortable... looks like they are$$ language sql;create or replace function get_product_static(_product_id integer,_store_id integer = null) returns table(producer text,brand text,name text,type product_type,species species,terpenes citext[],strain text,images json,variants json) as $$selectproducer,brand,product.display_name,type,species,terpenes,--aggregate first, then join https://stackoverflow.com/a/27626358 use subquery since small selection(select strain.display_name from strain where strain.strain_id = product.strain_id),coalesce((select json_agg(destination) from product_image where product_image.product_id = product.product_id), '[]'),coalesce((select json_strip_nulls(json_agg(json_build_object('id', variant_id,'portions', portions,'quantity', quantity,'disabled', casewhen _store_id is null then nullelse not exists(select 1 from menu_item where menu_item.variant_id = variant.variant_id and menu_item.store_id = _store_id)end))) from variant where variant.product_id = product.product_id), '[]')fromproductwhereproduct_id = $1$$ language sql;
.replace(/\bgrand\s+daddy\b/, 'granddaddy')//todo: better space detection. lots of strain families can be spelt with or without spaces... which will throw off the sort
.replace(/\bgrand\s+daddy\b/, 'granddaddy')//todo: better space detection. lots of strains can be spelt with or without spaces... which will throw off the sort
let newUser = await pool.query('insert into user_account (email, passphrase_hash) values ($1, $2) returning id, email, type', [parameters.email, await argon2.hash(parameters.passphrase)]);//technically, we only need the id??
let newUser = await pool.query('insert into usr (email, passphrase_hash) values ($1, $2) returning user_id, email, type', [parameters.email, await argon2.hash(parameters.passphrase)]);//technically, we only need the id??
await pool.query('update user_account set token_hash = $1 where id = $2', [crypto.createHash('BLAKE2b512').update(token).digest(), user.id]);//update to blake3 once it's available in openSSL
await pool.query('update usr set token_hash = $1 where user_id = $2', [crypto.createHash('BLAKE2b512').update(token).digest(), user.user_id]);//update to blake3 once it's available in openSSL
let user = (await pool.query('select * from user_account where token_hash = $1 and now() < token_expiry', [crypto.createHash('BLAKE2b512').update(Buffer.from(parameters.token, 'base64')).digest()])).rows[0];//possible timing attack?
let user = (await pool.query('select * from usr where token_hash = $1 and now() < token_expiry', [crypto.createHash('BLAKE2b512').update(Buffer.from(parameters.token, 'base64')).digest()])).rows[0];//possible timing attack?
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(', ')}
insert into store_tax (store_id, name, rate)values ${parameters.taxes.map(tax => `((select store_id from insert1), $${params.push(tax.name)}, $${params.push(tax.rate)})`).join(', ')}
insert into store_image (store, URL)values ${parameters.images.map(image => `((select id from insert1), $${params.push(image)})`).join(', ')}
insert into store_image (store_id, URL)values ${parameters.images.map(image => `((select store_id from insert1), $${params.push(image)})`).join(', ')}
let stores = (await pool.query('select distinct on(store.id) store.id, store.name, store_image.URL from store left outer join store_image on store.id = store_image.store')).rows;
let stores = (await pool.query('select distinct on(store_id) store_id as id, name, store_image.url from store left outer join store_image using (store_id)')).rows;
let response = (await pool.query(`selectname,URL,address,address2,city,region,country,postal_code,coordinate,timezone,images,timesfromstoreleft outer join (selectstore,json_agg(URL) as imagesfromstore_image--order by rankgroup bystore) as store_image on store_image.store = store.idleft outer join (selectstore,json_agg(open) as times--need to agg the rangefromstore_timegroup bystore) as store_time on store_time.store = store.idwherestore.id = $1`, [s])).rows;
let response = (await pool.query(`select * from get_store($1)`, [s])).rows;
const [strain_families, strains, product_types] = await Promise.all([pool.query('select id, display_name as name from strain_family').then(x => x.rows),pool.query('select array_to_json(enum_range(null::strain)) as wow').then(x => x.rows[0].wow),pool.query('select array_to_json(enum_range(null::product_type)) as wow').then(x => x.rows[0].wow)
const [strains, pgenum] = await Promise.all([pool.query('select strain_id as id, display_name as name from strain').then(x => x.rows),pool.query('select array_to_json(enum_range(null::species)) as species, array_to_json(enum_range(null::product_type)) as product_types').then(x => x.rows[0])
let j = normalize(parameters.brand, parameters.name, parameters.type);let product = (await pool.query('insert into product values (default, $1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, true, $12) returning id', [...j,parameters.producer,parameters.brand,parameters.name,parameters.strain,parameters.strain_family === "" ? undefined : Number(parameters.strain_family),parameters.description,parameters.images])).rows[0].id;
let q = 1;let params = [...normalize(parameters.brand, parameters.name, parameters.type), parameters.type, parameters.species, parameters.strain === "" ? undefined : Number(parameters.strain), parameters.description];let query = `with insert1 as (insert into product (producer, brand, display_name, joined_name, type, species, strain_id, description) values ($1, $2, $3, $4, $5, $6, $7, $8) on conflict do nothing returning product_id)`;//todo: terpenes
let query = 'insert into variant values ';let params = [];for(let i = 0; i < parameters.variants.length; ++i) {if(parameters.variants[i].quantity !== undefined) {query += `(default, $${i * 3 + 1}, $${i * 3 + 2}, $${i * 3 + 3}),`;params.push(product,parameters.variants[i].portions === undefined ? 0 : Number(parameters.variants[i].portions),Number(parameters.variants[i].quantity)//Number(undefined) -> NaN);
let values = parameters.variants.reduce((acc, v) => {if(v.quantity && v.gram_equivalency) {//todo: "SELECT instead of VALUES makes sure nothing is inserted in subsidiary tables if no row is returned from a previous INSERT" since on conflict do nothingacc.push(`((select product_id from insert1), ${v.portions ? '$' + params.push(v.portions) : 'default'}, $${params.push(v.quantity)}, $${params.push(v.gram_equivalency)})`);
//console.log(query);//console.log(params);await pool.query(query.slice(0, -1), params);
}if(Array.isArray(parameters.images) && parameters.images.length) {query += `, insert${++q} as (insert into product_image (product_id, destination) values ${parameters.images.map(i => `((select product_id from insert1), $${params.push(i)})`).join(', ')} on conflict do nothing)`;
response_ID: request_ID,//https://dba.stackexchange.com/a/159899 and https://stackoverflow.com/a/27626358data: (await pool.query(`selectproduct.id as id,type,producer,brand,product.display_name as name,strain,strain_family.display_name as strain_family,terpenes,image,cbd,thc,min_price,qfromproductleft outer join strain_family on strain_family.id = product.strain_familyleft outer join (select distinct on(product)product,destination as imagefromproduct_image--order by rank) as product_image on product_image.product = product.idinner join (selectvariant.product,range_merge(cbd) as cbd,range_merge(thc) as thc,min(price) as min_price,first(quantity) as qfrommenu_iteminner join variant on variant.id = menu_item.variant${query}group byvariant.product) as product_agg on product_agg.product = product.idwhereshow = true--brand ilike $1--order by cbd, thc, min_price, etc. idk if ranges are sortable... looks like you can`, params)).rows
response_ID: request_ID,data: (await pool.query(`select * from get_products(${Number.isInteger(s) ? '$' + params.push(s) : ''})`, params)).rows
let query = '';if(Number.isInteger(s)) {query += `, 'disabled', (select bool_and(store != $${params.push(s)}) from menu_item where menu_item.variant = variant.id)`;}let response = (await pool.query(`selectproducer,brand,product.display_name as name,type,strain,(select strain_family.display_name from strain_family where strain_family.id = product.strain_family) as strain_family,coalesce((select json_agg(destination) from product_image where product_image.product = product.id), '[]') as images,coalesce((select json_agg(json_build_object('id', id,'portions', portions,'quantity', quantity${query})) from variant where variant.product = product.id), '[]') as variantsfromproductwhereproduct.id = $1`,params)).rows;
let response = (await pool.query(`select * from get_product_static($1${Number.isInteger(s) ? ', $' + params.push(s) : ''})`, params)).rows;
case 'product_select'://search for menu_item(s) given variant and / or store and return.let params = [parameters.product];let query = '';if(parameters.variant !== undefined) {query += ' and menu_item.variant = $' + params.push(parameters.variant);}if(parameters.store !== undefined) {query += ' and menu_item.store = $' + params.push(parameters.store);}ws.send(JSON.stringify({response_ID: request_ID,data: (await pool.query(`selectmenu_item.id,variant,store,store.URL || path as url,stockfrommenu_iteminner join store on menu_item.store = store.idinner join variant on menu_item.variant = variant.idinner join product on variant.product = product.idwhereproduct.id = $1${query}`, params)).rows}));break;
//whenever store_owner lands: update menu_item set where store = (select store from user_account where id = $1) and id = $2 or keep store in ws?
//whenever store_owner lands: update menu_item set where store = (select store from usr where id = $1) and id = $2 or keep store in ws?
let cart = (await pool.query(`insert into cart (user_account, store) values ($1, (select store from menu_item where id = $2)) on conflict (user_account, store) where status is null or status = 'placed' or status = 'ready' do nothing returning id`, [ws.user_ID, parameters.menu_item])).rows;
let cart = (await pool.query(`insert into cart (user_id, store_id) values ($1, (select store from menu_item where id = $2)) on conflict (user_id, store_id) where status <= 'ready' do nothing returning id`, [ws.user_ID, parameters.menu_item])).rows;
let kart = (await pool.query(`select id from cart where user_account = $1 and store = (select store from menu_item where id = $2) and status is null`, [ws.user_ID, parameters.menu_item])).rows;
let kart = (await pool.query(`select id from cart where user_id = $1 and store = (select store from menu_item where id = $2) and status is null`, [ws.user_ID, parameters.menu_item])).rows;
await pool.query('delete from line_item using cart where line_item.cart = cart.id and cart.user_account = $1 and line_item.id = $2', [ws.user_ID, parameters.line_item]);
await pool.query('delete from line_item using cart where line_item.cart = cart.id and cart.user_id = $1 and line_item.id = $2', [ws.user_ID, parameters.line_item]);