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 $$
select
name,
url,
address,
address2,
city,
region,
country,
postal_code,
coordinate,
timezone,
images,
times
from
store
left outer join (
select
store_id,
json_agg(url) as images
from
store_image
--order by rank
group by
store_id
) as store_images using (store_id)
left outer join (
select
store_id,
json_agg(open) as times--combine the ranges? key value? (day : array of times)
from
store_time
group by
store_id
) as store_times using (store_id)
where
store_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 $$
select
product_id,
type,
producer,
brand,
product.display_name,
species,
strain.display_name,
terpenes,
destination,
cbd,
thc,
min,
first
from
product
left outer join strain using (strain_id)
left outer join (
select distinct on(product_id)--https://dba.stackexchange.com/a/159899
product_id,
destination
from
product_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 later
select
product_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 edibles
from
menu_item
inner join variant using (variant_id)
where
store_id = _store_id or _store_id is null
group by
product_id
) as product_agg using (product_id)
where
show = 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 $$
select
producer,
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', case
when _store_id is null then null
else 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), '[]')
from
product
where
product_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(`
select
name,
URL,
address,
address2,
city,
region,
country,
postal_code,
coordinate,
timezone,
images,
times
from
store
left outer join (
select
store,
json_agg(URL) as images
from
store_image
--order by rank
group by
store
) as store_image on store_image.store = store.id
left outer join (
select
store,
json_agg(open) as times--need to agg the range
from
store_time
group by
store
) as store_time on store_time.store = store.id
where
store.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 nothing
acc.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/27626358
data: (await pool.query(`
select
product.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,
q
from
product
left outer join strain_family on strain_family.id = product.strain_family
left outer join (
select distinct on(product)
product,
destination as image
from
product_image
--order by rank
) as product_image on product_image.product = product.id
inner join (
select
variant.product,
range_merge(cbd) as cbd,
range_merge(thc) as thc,
min(price) as min_price,
first(quantity) as q
from
menu_item
inner join variant on variant.id = menu_item.variant
${query}
group by
variant.product
) as product_agg on product_agg.product = product.id
where
show = 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(`
select
producer,
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 variants
from
product
where
product.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(`
select
menu_item.id,
variant,
store,
store.URL || path as url,
stock
from
menu_item
inner join store on menu_item.store = store.id
inner join variant on menu_item.variant = variant.id
inner join product on variant.product = product.id
where
product.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]);