AMKB7ZH5QDLTGTGJ63BXZKPVYZN7VWUDBRTOBJ2ZE4TS5QOWCGUQC PUUFKW5ZJIJBWTMNX46PW4APAYBAYOAKXHSNGBIGRXCD5Z2FBM2QC DSVZLJ3ULDNYRUVWRIHGQSPZYXQ26UGXPPNIIQGKVSKOPWTPZCLQC XBHLXAKNBJNWSXZMHVKDMDVI7CUF5MN22FFS3XQIOLFDJHDCLTVAC HVE3ANDORAJ5EX3I3GMSWT3INIKS6QRWPWGA6V6WY3NRCGVRFSRAC O6OZUOIH5U2SE3FS4ZUHM755FNYXEDXSRXREAEFZ36VNII4CPRJAC 376FAPWKGZ4NT47ODONG6VIVEISEKNRHI6AC5AFS4VRJDYQHPEFQC U7BZVX5VS4IIY7CH6HFIYXK7UQBYYUN5VYVO45LVESF2PMW7A5VAC VO5G3FF3NDOKCIF2OU7G257R3XIPTFDCAMDTOVKHKOB5Z3I55KDQC MPLKJQVEPZB7GEWAKZOOIKBKWHTNQX3LLESCQEG4PE3CUHQEYUBAC 5XHBBUBSPTNNPEZZVELCS5AEAKQVHEMLAI2URKBMMBUGWTPARFVQC LUE57OK3OFZYP7GW4FL6SBV2MCBBEKQ2OFGKZSZ77AOBI6OKGDYQC E4HFJ4L4PAGV7R7EXVG2B2CWDGGW7XWU4D37VURZ66HZ3ILHCFUAC ZI2RJOZ2HXBHX7L54BTSDKESY6NG5KBJLVHCNY7K7ZLGIUOIHQUQC 76HUJPOPXCN2NMOWLI6JOY7HR35EL4IBHMES3DBJVCFZRKKPGQSAC QQAJ5O5EW6ASWLXQ627Y2F6YGMYKCM4WS37LTZYOMNPPFDPMNBUAC 2Y4DI4H5EZOGS2ZUL7SXQHYUYFWWJOJPRAF4MI2RFI6YCLAJVFJAC JP2LJ6B34S4IS6MPKF5LMMYUWIYMGU3JCJRRR4PTZ4IWM653JCXAC RAN5QU4U2OVNLDGZVVTJNCXDN5GNAVPXGKHADZO2ML3Y5KLU4QPQC Y4W4MACPKH3IVO6JIQYGN4V5LR6EM6RPZEJ4RHH6ZBZUDA63AGRQC CLMZIURF2TXSMQ6YZYXR5CHGMRW6V64YFQLRCP3H3I3XB2KKOJ6AC SE6MCCXTIXSGMAH5EL7EPDXIQKV6THCCY2H4OQJJQOHCUQAWE5VQC 7XVW32MMBBIGWQZ3QRD365TBIJU4DYN6T4VO2E46WFDCFHGO2BMAC WYTMZJFYVKHR4QH7AV5JUNWXT6NAC5NNQNPZCQSDI6LGI7DVXFYAC UXLEHDHRPKDY6DXD2Q64ULMVTPOPMQW6VAOPHG5K5HX24YPGG3MQC BXGNP4GARIQA5LT3AN2F22HHGJHLUOL3YKBWWJ6LQQKUWLPYB7OAC 5SAL2YA2IIYRBZSNM6SBKHSD6UFP67GDOR5J6W2WXREYLJZEFJ6AC SRIPJD5O5RNXAH2EOCTVJRGFSNNC4LDPBB44AE7FPEXGTC66NYTAC Z7XOXXDJ7T6E4ZCP2AK5YSXWE6TCDCLZEXAZPZZVLOY4WCFVZPHAC 7RKEQK2KPXJ2MEHPVSY7ROMHW6FTTKCXSMZV5MEXPFZKESZQYUUAC 63VXWIHIAKGK7J4VTNRUAG2V32N2QUSWFELB6GD34S54FGRWAPCQC MM3HQWNEUZVOGQ3WWBTECBY6F47SN4U43I5UNDOAFWO3R7U5XPBAC 2FPZGFF5PCGNV45HOB6TROMJFPURHPXM7YSPWNXCHFJS7EICVKQAC HFOZWZPGUIVA7QUO7VTPZ2R5ADEDGG7EGL3E5TFUUYRNAOVMHIDQC VIGS24FLOPPTBLIBZIB2LFMYCDZ3AO4FBXYADAKQMJFNPM4JHAGQC UCY5PVFCT2QIOGR47KFS4KBF2ZPHRBOAIOH6PJEVWCHLMBRBLOMQC --values (_user_id, (select store_id from menu_item where menu_item_id = _menu_item_id)) to make early exit and throw not null exception?? format error message in backend. are there side effects other than incremented cart_id serial?select _user_id, store_id from menu_itemwhere not exists (select 1 from s) and menu_item_id = _menu_item_idon conflict (user_id, store_id) where status <= 'ready' do nothing
select _user_id, store_idfrom menu_item join store using (store_id)wherenot exists (select from s) andmenu_item_id = _menu_item_id andpartner
select * from s union select * from i), cc as (--condition is wrong! consider the case where a user adds n menu items from the same store simulatenously (n tabs press add2cart same time, or restore expired cart client side will send n modifycart, first cart gets created, but some others may not see the cart yet since parallel(can fix by having another endpoint that takes in multi, or waits for first to return and then execute others)) maybe solution is do update set status = 'active' where status = 'active'(empty update)??select case when not exists (select 1 from c) and _user_id is not null then f_raise('exception', 'failed to add to cart. you already have an order in progress with this store.') end
select * from s union all select * from i
select cart_id, _menu_item_id, qty from c full join cc on true--https://dba.stackexchange.com/a/69650 force evaluation of cte--1 * 0 => 1 row, fine since we're selecting what we want--0 * 1 => 1 row, fine since the exception will skip the insert anyways. before insert was skipped due to 0 rows, but this is equivalent? wait no... if invalid menu_item_id, insert will still run, but caught by valid_line_item_as_of_now(). I'd much rather make 0 * 0 though... don't really care about that?
select cart_id, _menu_item_id, qty from c
$$ language sql stable;create or replace function reservation(_cart_id integer) returns table (store_id integer,store_name text,statuses json,taxes json,line_items json) as $$with blah as (select *from cartwherecart_id = _cart_id anduser_id is null andstatus > 'active'), blah2 as (select * from blah union allselect * from cart_historywhereexists (select * from blah) andcart_id = _cart_id anduser_id is null andstatus > 'active')selectfirst(cart.store_id),first(store__as_of.name),first(statuses),(select json_agg(json_build_object('name', name,'rate', rate::text,'types', types)) from store_tax__as_of(first(cart.system_time_start)) where store_id = first(cart.store_id) and (types is null or types && array_agg(product__as_of.type))),json_agg(json_strip_nulls(json_build_object('product_id', product_id,'variant_id', variant_id,'menu_item', menu_item_id,'producer', producer,'brand', brand,'name', product__as_of.display_name,'type', product__as_of.type,'strain', strain__as_of.display_name,'species', coalesce(product__as_of.species, strain__as_of.species),'blend', blend,'portions', portions,'quantity', variant__as_of.quantity::text,'flavor', flavor,'gram_equivalency', gram_equivalency::text,'thc', thc,'cbd', cbd,'price', price::text,'howmany', line_item.quantity,'image', (select distinct on(product_id)--https://dba.stackexchange.com/a/159899destinationfromproduct_image__as_of(cart.system_time_start)where product_image__as_of.product_id = product__as_of.product_id--order by rank))))from(selectcart_id,first(store_id) as store_id,min(system_time_start) as system_time_start,--system_time_start of 'placed' or 'paid' since that's what the customer has agreed to. right now is just 'placed'json_agg(json_build_object('status', status,'as_of', system_time_start,'end', system_time_end) order bysystem_time_start) as statusesfrom blah2group by cart_id) as cartinner join store__as_of(cart.system_time_start) using (store_id)inner join line_item using (cart_id)inner join menu_item__as_of(cart.system_time_start) using (menu_item_id)inner join variant__as_of(cart.system_time_start) using (variant_id)inner join product__as_of(cart.system_time_start) using (product_id)left outer join strain__as_of(cart.system_time_start) using (strain_id)group by cart_id
)) from store_tax__as_of(first(cart.system_time_start)) where store_id = first(cart.store_id)),
)) from store_tax__as_of(first(cart.system_time_start)) where store_id = first(cart.store_id) and (types is null or types && array_agg(product__as_of.type))),
(selectcart_id,row_number() OVER (ORDER BY casewhen last(status order by system_time_start) = 'ready' then 2when last(status order by system_time_start) = 'placed' then 1else 0end desc,max(system_time_start) desc) AS rn,first(store_id) as store_id,min(system_time_start) as system_time_start,--system_time_start of 'placed' or 'paid' since that's what the customer has agreed to. right now is just 'placed'json_agg(json_build_object('status', status,'as_of', system_time_start,'end', system_time_end) order bysystem_time_start) as statusesfrom cart_with_historywhereuser_id = _user_id andstatus >= 'placed'group by cart_idorder bycasewhen last(status order by system_time_start) = 'ready' then 2when last(status order by system_time_start) = 'placed' then 1else 0end desc,max(system_time_start) desc) as cart
(selectcart_id,row_number() OVER (ORDER BY casewhen last(status order by system_time_start) = 'ready' then 2when last(status order by system_time_start) = 'placed' then 1else 0end desc,max(system_time_start) desc) AS rn,first(store_id) as store_id,min(system_time_start) as system_time_start,--system_time_start of 'placed' or 'paid' since that's what the customer has agreed to. right now is just 'placed'json_agg(json_build_object('status', status,'as_of', system_time_start,'end', system_time_end) order bysystem_time_start) as statusesfrom cart_with_historywhereuser_id = _user_id andstatus >= 'placed'group by cart_idorder bycasewhen last(status order by system_time_start) = 'ready' then 2when last(status order by system_time_start) = 'placed' then 1else 0end desc,max(system_time_start) desc) as cart
)) from store_tax__as_of(first(cart.system_time_start)) where store_id = _store_id),
)) from store_tax__as_of(first(cart.system_time_start)) where store_id = _store_id and (types is null or types && array_agg(product__as_of.type))),
(selectcart_id,row_number() OVER (ORDER BY casewhen last(status order by system_time_start) = 'placed' then 2when last(status order by system_time_start) = 'ready' then 1else 0end desc,max(system_time_start) desc) AS rn,first(user_id) as user_id,min(system_time_start) as system_time_start,--system_time_start of 'placed' or 'paid' since that's what the customer has agreed to. right now is just 'placed'json_agg(json_build_object('status', status,'as_of', system_time_start,'end', system_time_end) order bysystem_time_start) as statusesfrom cart_with_historywherestore_id = _store_id andstatus >= 'placed'group by cart_idorder by--maybe this is unnecessary?casewhen last(status order by system_time_start) = 'placed' then 2when last(status order by system_time_start) = 'ready' then 1else 0end desc,max(system_time_start) desc) as cartinner join usr__as_of(cart.system_time_start) using (user_id)
(selectcart_id,row_number() OVER (ORDER BY casewhen last(status order by system_time_start) = 'placed' then 2when last(status order by system_time_start) = 'ready' then 1else 0end desc,max(system_time_start) desc) AS rn,min(system_time_start) as system_time_start,--system_time_start of 'placed' or 'paid' since that's what the customer has agreed to. right now is just 'placed'json_agg(json_build_object('status', status,'as_of', system_time_start,'end', system_time_end) order bysystem_time_start) as statusesfrom cart_with_historywherestore_id = _store_id andstatus >= 'placed'group by cart_idorder by--maybe this is unnecessary?casewhen last(status order by system_time_start) = 'placed' then 2when last(status order by system_time_start) = 'ready' then 1else 0end desc,max(system_time_start) desc) as cart
async function mergeCart(client, carts, ws) {let decryptedCarts = [];if(Array.isArray(carts)) {for(let i = 0; i < carts.length; ++i) {try {decryptedCarts.push(cryptids.s2i(carts[i]));} catch(e) {}}}if(decryptedCarts.length) {let diy = false;if(!client) {diy = true;client = await pool.connect();await client.query('begin');}let carts2merge = (await client.query(`with distinct_cart_per_store as (select distinct on (store_id) cart_id, store_idfrom cartwhere cart_id = any($2) and user_id is null and status = 'active'),associate_user2cart as (update cartset user_id = $1wherecart_id in (select cart_id from distinct_cart_per_store) andnot exists(select from cart as c where c.user_id = $1 and c.store_id = cart.store_id and c.status = 'active')returning cart_id, store_id)selectt.cart_id as anon_cart_id,cart.cart_id as user_cart_id,json_agg(json_build_object('line_item_id', line_item_id,'menu_item_id', menu_item_id,'quantity', quantity)) as line_items2associate_user_or_add_qtyfrom(select * from distinct_cart_per_store except all select * from associate_user2cart) as t inner joincart on (t.store_id = cart.store_id and status = 'active' and user_id = $1) inner joinline_item on (t.cart_id = line_item.cart_id)group byt.cart_id,cart.cart_id`, [ws.user_ID, decryptedCarts])).rows;//todo: set based merge cart or cursor plpgsql function. this is uglyloop1: for(let i = 0; i < carts2merge.length; ++i) {let addQty = [];let lis = carts2merge[i].line_items2associate_user_or_add_qty;for(let j = 0; j < lis.length; ++j) {try {await client.query('savepoint wow');await client.query(`update line_item set cart_id = $1 where line_item_id = $2`, [carts2merge[i].user_cart_id, lis[j].line_item_id]);} catch(e) {await client.query('rollback to wow');if(e.constraint === 'line_item_cart_id_menu_item_id_key') {addQty.push(lis[j]);} else if(e.message === 'exceeded 30g limit') {continue loop1;} else {console.error(e);}}}console.log('sanity');await client.query('delete from cart where cart_id = $1', [carts2merge[i].anon_cart_id]);for(let j = 0; j < addQty.length; ++j) {try {await client.query('savepoint wow');await client.query('update line_item set quantity = quantity + $1 where cart_id = $2 and menu_item_id = $3', [addQty[j].quantity, carts2merge[i].user_cart_id, addQty[j].menu_item_id]);} catch(e) {await client.query('rollback to wow');if(e.message === 'exceeded 30g limit') {break;} else {console.error(e);}}}}if(diy) {await client.query('commit');client.release();}}}
if(hack && Array.isArray(ws.q.carts) && (ws.q.carts = ws.q.carts.map(Number)).every(Number.isInteger)) {const client = await pool.connect();let m = (await client.query(`with f as (select distinct on (store_id) cart_id, store_idfrom cartwhere cart_id = any($2) and user_id is null and status = 'active'),r as (update cartset user_id = $1wherecart_id in (select cart_id from f) andnot exists(select 1 from cart as c where c.user_id = $1 and c.store_id = cart.store_id and status <= 'ready')returning cart_id, store_id)select * from f except select * from r`, [ws.user_ID, ws.q.carts])).rows;//todo: set based merge cart or cursor plpgsql function. this is uglyfor(let i = 0; i < m.length; ++i) {let c = (await client.query({text: `select cart_id from cart where user_id = $1 and store_id = $2 and status = 'active'`,values: [ws.user_ID, m[i].store_id],rowMode: 'array'})).rows?.[0]?.[0];if(c) {let li = (await client.query({text: 'select line_item_id, menu_item_id, quantity from line_item where cart_id = $1',values: [m[i].cart_id],rowMode: 'array'})).rows;let later = [];for(let j = 0; j < li.length; ++j) {try {await client.query('savepoint wow');await client.query(`update line_item set cart_id = $1 where line_item_id = $2`, [c, li[j][0]]);} catch(e) {await client.query('rollback to wow');if(e.constraint === 'line_item_cart_id_menu_item_id_key') {later.push(li[j]);} else if(e.message === 'exceeded 30g limit') {later = [];break;} else {console.error(e);}}}for(let j = 0; j < later.length; ++j) {try {await client.query('savepoint wow');await client.query('update line_item set quantity = quantity + $1 where cart_id = $2 and menu_item_id = $3', [later[j][2], c, later[j][1]]);} catch(e) {await client.query('rollback to wow');if(e.message === 'exceeded 30g limit') {break;} else {console.error(e);}}}} else {console.log(`failed to merge cart. reservation in progress with store ${m[i].store_id}`);}}await client.query('delete from cart where cart_id = any($1)', [m]);await client.query('commit');client.release();}delete ws.q;
html: `<a href="https://www.lobojane.com/user?otp=${token}">idk</a><div>Alternatively, copy and paste <code>${token}</code> into the input field</div>if this wasn't you, let us know by replying to this email.`
html: `<a href="http://localhost:3000?otp=${token}">idk</a><div>Alternatively, copy and paste <code>${token}</code> into the input field</div>if this wasn't you, let us know by replying to this email.`
if(Array.isArray(parameters.carts) && (parameters.carts = parameters.carts.map(Number)).every(Number.isInteger)) {const client = await pool.connect();let m = (await client.query(`with f as (select distinct on (store_id) cart_id, store_idfrom cartwhere cart_id = any($2) and user_id is null and status = 'active'),r as (update cartset user_id = $1wherecart_id in (select cart_id from f) andnot exists(select 1 from cart as c where c.user_id = $1 and c.store_id = cart.store_id and status <= 'ready')returning cart_id, store_id)select * from f except select * from r`, [user.user_id, parameters.carts])).rows;//todo: set based merge cart or cursor plpgsql function. this is uglyfor(let i = 0; i < m.length; ++i) {let c = (await client.query({text: `select cart_id from cart where user_id = $1 and store_id = $2 and status = 'active'`,values: [user.user_id, m[i].store_id],rowMode: 'array'})).rows?.[0]?.[0];if(c) {let li = (await client.query({text: 'select line_item_id, menu_item_id, quantity from line_item where cart_id = $1',values: [m[i].cart_id],rowMode: 'array'})).rows;let later = [];for(let j = 0; j < li.length; ++j) {try {await client.query('savepoint wow');await client.query(`update line_item set cart_id = $1 where line_item_id = $2`, [c, li[j][0]]);} catch(e) {await client.query('rollback to wow');if(e.constraint === 'line_item_cart_id_menu_item_id_key') {later.push(li[j]);} else if(e.message === 'exceeded 30g limit') {later = [];break;} else {console.error(e);}}}for(let j = 0; j < later.length; ++j) {try {await client.query('savepoint wow');await client.query('update line_item set quantity = quantity + $1 where cart_id = $2 and menu_item_id = $3', [later[j][2], c, later[j][1]]);} catch(e) {await client.query('rollback to wow');if(e.message === 'exceeded 30g limit') {break;} else {console.error(e);}}}} else {console.log(`failed to merge cart. reservation in progress with store ${m[i].store_id}`);}}await client.query('delete from cart where cart_id = any($1)', [m]);await client.query('commit');client.release();}
await mergeCart(null, parameters.carts, ws);
ws.user_ID = user.user_id;let token = await randomBytes(16);const client = await pool.connect();await client.query('update usr set token_hash = $1 where user_id = $2', [blake3(token, {length: 16}), user.user_id]);if(Array.isArray(parameters.carts) && (parameters.carts = parameters.carts.map(Number)).every(Number.isInteger)) {let m = (await client.query(`with f as (select distinct on (store_id) cart_id, store_idfrom cartwhere cart_id = any($2) and user_id is null and status = 'active'),r as (update cartset user_id = $1wherecart_id in (select cart_id from f) andnot exists(select 1 from cart as c where c.user_id = $1 and c.store_id = cart.store_id and status <= 'ready')returning cart_id, store_id)select * from f except select * from r`, [user.user_id, parameters.carts])).rows;//todo: set based merge cart or cursor plpgsql function. this is uglyfor(let i = 0; i < m.length; ++i) {let c = (await client.query({text: `select cart_id from cart where user_id = $1 and store_id = $2 and status = 'active'`,values: [user.user_id, m[i].store_id],rowMode: 'array'})).rows?.[0]?.[0];if(c) {let li = (await client.query({text: 'select line_item_id, menu_item_id, quantity from line_item where cart_id = $1',values: [m[i].cart_id],rowMode: 'array'})).rows;let later = [];for(let j = 0; j < li.length; ++j) {try {await client.query('savepoint wow');await client.query(`update line_item set cart_id = $1 where line_item_id = $2`, [c, li[j][0]]);} catch(e) {await client.query('rollback to wow');if(e.constraint === 'line_item_cart_id_menu_item_id_key') {later.push(li[j]);} else if(e.message === 'exceeded 30g limit') {later = [];break;} else {console.error(e);}}}for(let j = 0; j < later.length; ++j) {try {await client.query('savepoint wow');await client.query('update line_item set quantity = quantity + $1 where cart_id = $2 and menu_item_id = $3', [later[j][2], c, later[j][1]]);} catch(e) {await client.query('rollback to wow');if(e.message === 'exceeded 30g limit') {break;} else {console.error(e);}}}} else {console.log(`failed to merge cart. reservation in progress with store ${m[i].store_id}`);}}await client.query('delete from cart where cart_id = any($1)', [m]);}await client.query('commit');client.release();//yuck, since json can't send binary, need to hex encode. fyi base64 is smaller than hex(base16), but after compressing, hex is smaller. also base64 has issues with regards to url encoding, and slower than hex//if remember me on client side, store in localstorage. else do nothing with token//on subsequent new sockets, auto_login with token. token will be invalid(undefined) if remember me was not checkedws.send(JSON.stringify({response_ID: request_ID,data: {token: token.toString('hex'), god: user.god, acl: user.acl}}));let old = authenticated.get(ws.user_ID);authenticated.set(ws.user_ID, ws);if(old) {old.send(JSON.stringify({what: 'user',how: 'replace',data: null,why: 'logged in elsewhere'}));old.end(1000, 'logged in elsewhere');//this works correctly in firefox onclose, but not in chrome. https://stackoverflow.com/a/53340067 todo: report on chromium bug tracker} else {ws.publish('user/authenticated', JSON.stringify({what: 'user/authenticated',//tbdhow: 'replace',data: authenticated.size}));}
//
ws.user_ID = user.user_id;let token = await randomBytes(16);const client = await pool.connect();await client.query('update usr set token_hash = $1, passphrase_hash = $3, webportal_hash = null where user_id = $2', [blake3(token, {length: 16}), user.user_id, await argon2.hash(parameters.passphrase)]);if(Array.isArray(parameters.carts) && (parameters.carts = parameters.carts.map(Number)).every(Number.isInteger)) {let m = (await client.query(`with f as (select distinct on (store_id) cart_id, store_idfrom cartwhere cart_id = any($2) and user_id is null and status = 'active'),r as (update cartset user_id = $1wherecart_id in (select cart_id from f) andnot exists(select 1 from cart as c where c.user_id = $1 and c.store_id = cart.store_id and status <= 'ready')returning cart_id, store_id)select * from f except select * from r`, [user.user_id, parameters.carts])).rows;//todo: set based merge cart or cursor plpgsql function. this is uglyfor(let i = 0; i < m.length; ++i) {let c = (await client.query({text: `select cart_id from cart where user_id = $1 and store_id = $2 and status = 'active'`,values: [user.user_id, m[i].store_id],rowMode: 'array'})).rows?.[0]?.[0];if(c) {let li = (await client.query({text: 'select line_item_id, menu_item_id, quantity from line_item where cart_id = $1',values: [m[i].cart_id],rowMode: 'array'})).rows;let later = [];for(let j = 0; j < li.length; ++j) {try {await client.query('savepoint wow');await client.query(`update line_item set cart_id = $1 where line_item_id = $2`, [c, li[j][0]]);} catch(e) {await client.query('rollback to wow');if(e.constraint === 'line_item_cart_id_menu_item_id_key') {later.push(li[j]);} else if(e.message === 'exceeded 30g limit') {later = [];break;} else {console.error(e);}}}for(let j = 0; j < later.length; ++j) {try {await client.query('savepoint wow');await client.query('update line_item set quantity = quantity + $1 where cart_id = $2 and menu_item_id = $3', [later[j][2], c, later[j][1]]);} catch(e) {await client.query('rollback to wow');if(e.message === 'exceeded 30g limit') {break;} else {console.error(e);}}}} else {console.log(`failed to merge cart. reservation in progress with store ${m[i].store_id}`);}}await client.query('delete from cart where cart_id = any($1)', [m]);}await client.query('commit');client.release();//yuck, since json can't send binary, need to hex encode. fyi base64 is smaller than hex(base16), but after compressing, hex is smaller. also base64 has issues with regards to url encoding, and slower than hex//if remember me on client side, store in localstorage. else do nothing with token//on subsequent new sockets, auto_login with token. token will be invalid(undefined) if remember me was not checkedws.send(JSON.stringify({response_ID: request_ID,data: {token: token.toString('hex'), god: user.god, acl: user.acl}}));let old = authenticated.get(ws.user_ID);authenticated.set(ws.user_ID, ws);if(old) {old.send(JSON.stringify({what: 'user',how: 'replace',data: null,why: 'logged in elsewhere'}));old.end(1000, 'logged in elsewhere');//this works correctly in firefox onclose, but not in chrome. https://stackoverflow.com/a/53340067 todo: report on chromium bug tracker} else {ws.publish('user/authenticated', JSON.stringify({what: 'user/authenticated',//tbdhow: 'replace',data: authenticated.size}));}
q = ', passphrase_hash = $3, webportal_hash = null';p.push(await argon2.hash(parameters.passphrase));
ws.user_ID = user.user_id;let token = await randomBytes(16);const client = await pool.connect();await client.query('begin');await client.query(`update usr set token_hash = $1${q} where user_id = $2`, [blake3(token, {length: 16}), ws.user_id].concat(p));await mergeCart(client, parameters.carts, ws);await client.query('commit');client.release();//yuck, since json can't send binary, need to hex encode. fyi base64 is smaller than hex(base16), but after compressing, hex is smaller. also base64 has issues with regards to url encoding, and slower than hex//if remember me on client side, store in localstorage. else do nothing with token//on subsequent new sockets, auto_login with token. token will be invalid(undefined) if remember me was not checkedws.send(JSON.stringify({response_ID: request_ID,data: {token: token.toString('hex'), god: user.god, acl: user.acl}}));let old = authenticated.get(ws.user_ID);authenticated.set(ws.user_ID, ws);if(old) {old.send(JSON.stringify({what: 'user',how: 'replace',data: null,why: 'logged in elsewhere'}));old.end(1000, 'logged in elsewhere');//this works correctly in firefox onclose, but not in chrome. https://stackoverflow.com/a/53340067 todo: report on chromium bug tracker} else {ws.publish('user/authenticated', JSON.stringify({what: 'user/authenticated',//tbdhow: 'replace',data: authenticated.size}));}
} else if(Array.isArray(parameters.carts) && parameters.carts.every(c => Number.isInteger(Number(c)))) {qs.push(`cart_ids => $${params.push(parameters.carts)}`);
} else if(Array.isArray(parameters.carts)) {let decryptedCarts = [];for(let i = 0; i < parameters.carts.length; ++i) {try {decryptedCarts.push(cryptids.s2i(parameters.carts[i]));} catch(e) {}}if(decryptedCarts.length) {qs.push(`cart_ids => $${params.push(decryptedCarts)}`);}
} else if(parameters && Array.isArray(parameters.carts) && parameters.carts.every(c => Number.isInteger(Number(c)))) {
} else if(Array.isArray(parameters.carts)) {let data = 'invalid cart';let decryptedCarts = [];for(let i = 0; i < parameters.carts.length; ++i) {try {decryptedCarts.push(cryptids.s2i(parameters.carts[i]));} catch(e) {}}if(decryptedCarts.length) {data = (await pool.query(`select * from anonymous_carts($1)`, [decryptedCarts])).rows;for(let i = 0; i < data.length; ++i) {data[i].cart_id = cryptids.i2s(data[i].cart_id);}}
case 'reserve':if(ws.user_ID) {let data = await pool.query(`update cart set status = 'placed' where cart.user_id = $1 and cart.status = 'active' and cart.store_id = $2 returning system_time_start, cart_id`, [ws.user_ID, parameters.store]);
case 'reservation': {let kartik;try {kartik = cryptids.s2i(parameters.cart);} catch(e) {
if(data.rowCount === 1) {let email = pool.query(`select email from usr where user_id = $1`, [ws.user_ID]);let taxes = pool.query(`select name, rate::text from store_tax where store_id = $1`, [parameters.store]);let li = pool.query(`select distinct on (product_id)product_id,variant_id,menu_item_id as menu_item,producer,brand,product.display_name as name,type,strain.display_name as strain,portions,variant.quantity::text as quantity,flavor,gram_equivalency::text,thc,cbd,price::text,line_item.quantity as howmany,destination as imagefromline_iteminner join menu_item using (menu_item_id)inner join variant using (variant_id)inner join product using (product_id)left outer join strain using (strain_id)left outer join product_image using (product_id)where cart_id = $1`, [data.rows[0].cart_id])ws.publish('store/reservations/' + parameters.store, JSON.stringify({what: 'store/reservations/' + parameters.store,how: 'add',data: {user_id: ws.user_ID,user_email: (await email).rows[0].email,statuses: [{status: 'placed', as_of: data.rows[0].system_time_start}],taxes: (await taxes).rows,line_items: (await li).rows}}));
break;}let data = await pool.query('select * from reservation($1)', [kartik]);if(data.rowCount === 1) {let out;for(let j = 0; j < data.rows[0].statuses.length; ++j) {if(j === data.rows[0].statuses.length - 1 && data.rows[0].statuses[j].end !== 'infinity' && (data.rows[0].statuses[j].status === 'placed' || data.rows[0].statuses[j].status === 'ready')) {out = {status: 'store_cancelled', as_of: data.rows[0].statuses[j].end};}delete data.rows[0].statuses[j].end;}if(out) {data.rows[0].statuses.push(out);
ws.send(JSON.stringify({response_ID: request_ID,data: 'invalid cart'}));}break;}case 'claim': {let kartik;try {kartik = cryptids.s2i(parameters.cart);} catch(e) {ws.send(JSON.stringify({response_ID: request_ID,data: 'invalid cart'}));break;}const auth = await is(ws);let n = Number.isInteger(Number(parameters.user_ID));if(auth === undefined) {if(ws.user_ID) {authenticated.delete(ws.user_ID);ws.publish('user/authenticated', JSON.stringify({what: 'user/authenticated',//tbdhow: 'replace',data: authenticated.size}));delete ws.user_ID;}
} else if(!auth && n) {ws.send(JSON.stringify({response_ID: request_ID,data: 'unauthorized'}));//client side virtual event user god false} else {const id = auth && n ? parameters.user_ID : ws.user_ID;let r = (await pool.query(`update cartset user_id = $1wherecart_id = $2 anduser_id is null andstatus > 'active'`, [id, kartik]));if(r.rowCount === 1) {ws.send(JSON.stringify({response_ID: request_ID}));//redirect to all of user's reservationsws.publish('reservations/' + parameters.cart, JSON.stringify({what: 'reservations/' + parameters.cart,how: 'replace',why: 'claimed'}));} else {ws.send(JSON.stringify({response_ID: request_ID,data: 'invalid cart'}));}
let r = (await pool.query(`update cart set status = 'user_cancelled' where user_id = $1 and store_id = $2 and (status = 'placed' or status = 'ready') returning system_time_start`, [id, parameters.store]));
let params = [id, kartik];let precondition;if(parameters.update === 'cancel') {params.push('user_cancelled');precondition = "(x.status = 'placed' or x.status = 'ready')";} else if(parameters.update === 'reserve') {params.push('placed');precondition = "x.status = 'active'";}let data = 'invalid update';if(params.length === 3) {//https://stackoverflow.com/a/7927957let r = await pool.query(`update cart as xsetstatus = $3,user_id = casewhen x.user_id is null then $1else x.user_idendfrom (select cart_id, user_id from cart where cart_id = $2 for update) as ywherex.cart_id = y.cart_id and(x.user_id is null or x.user_id = $1) and${precondition}returning x.system_time_start, x.store_id, y.user_id as old_user`,params);if(r.rowCount === 1) {data = undefined;if(parameters.update === 'cancel') {ws.publish('store/reservations/' + r.rows[0].store_id, JSON.stringify({what: 'store/reservations/' + r.rows[0].store_id,how: 'update',data: {cart_id: parameters.cart,status: 'user_cancelled',as_of: r.rows[0].system_time_start}}));if(id) {//https://github.com/uNetworking/uWebSockets.js/issues/362ws.publish('user/reservations/' + id, JSON.stringify({what: 'user/reservations/' + id,how: 'update',data: {cart_id: parameters.cart,status: 'user_cancelled',as_of: r.rows[0].system_time_start}}));if(r.rows[0].old_user === null) {ws.publish('reservations/' + parameters.cart, JSON.stringify({what: 'reservations/' + parameters.cart,how: 'replace',why: 'claimed'}));}} else {ws.publish('reservations/' + parameters.cart, JSON.stringify({what: 'reservations/' + parameters.cart,how: 'update',data: {status: 'user_cancelled',as_of: r.rows[0].system_time_start}}));}} else {//'reserve'//distinct on is because product_image may multiple number of rowslet line_items = (await pool.query(`select distinct on (product_id)product_id,variant_id,menu_item_id as menu_item,producer,brand,product.display_name as name,type,strain.display_name as strain,coalesce(product.species, strain.species) as species,blend,portions,variant.quantity::text as quantity,flavor,gram_equivalency::text,thc,cbd,price::text,line_item.quantity as howmany,destination as imagefromline_iteminner join menu_item using (menu_item_id)inner join variant using (variant_id)inner join product using (product_id)left outer join strain using (strain_id)left outer join product_image using (product_id)where cart_id = $1`, [kartik])).rows;let types = new Set();for(let i = 0; i < line_items.length; ++i) {types.add(line_items[i].type);}let taxes = (await pool.query(`select name, rate::text, types from store_tax where store_id = $1 and (types is null or types && $2)`, [r.rows[0].store_id, [...types]])).rows;ws.publish('store/reservations/' + r.rows[0].store_id, JSON.stringify({what: 'store/reservations/' + r.rows[0].store_id,how: 'add',data: {cart_id: parameters.cart,statuses: [{status: 'placed', as_of: r.rows[0].system_time_start}],taxes,line_items}}));if(id) {//https://github.com/uNetworking/uWebSockets.js/issues/362ws.publish('user/reservations/' + id, JSON.stringify({what: 'user/reservations/' + id,how: 'add',data: {cart_id: parameters.cart,statuses: [{status: 'placed', as_of: r.rows[0].system_time_start}],taxes,line_items}}));}}}}
if(Number.isInteger(Number(parameters?.store)) && Number.isInteger(Number(parameters?.user))) {
let out = [];if(!Number.isInteger(Number(parameters.store))) {out.push('store');}let kartik;try {kartik = cryptids.s2i(parameters.cart);} catch(e) {out.push('cart');}if(out.length) {ws.send(JSON.stringify({response_ID: request_ID,data: 'missing ' + out.join(' and ')}));} else {
data = (await pool.query(`update cart set status = 'ready' where store_id = $1 and user_id = $2 and status = 'placed' returning system_time_start`,[parameters.store, parameters.user]))
params.push('ready', 'placed');
case 'paid'://todo: decrement stock. maybe trigger?data = (await pool.query(`update cart set status = 'paid' where store_id = $1 and user_id = $2 and status = 'ready' returning system_time_start`,[parameters.store, parameters.user]))
case 'paid':params.push('paid', 'ready');
data = (await pool.query(`update cart set status = 'store_cancelled' where store_id = $1 and user_id = $2 and status = 'placed' returning system_time_start`,[parameters.store, parameters.user]))
params.push('store_cancelled', 'placed');
data = (await pool.query(`update cart set status = 'no_show' where store_id = $1 and user_id = $2 and status = 'ready' returning system_time_start`,[parameters.store, parameters.user]))
params.push('no_show', 'ready');break;
if(data?.rowCount === 1) {ws.send(JSON.stringify({response_ID: request_ID}));if(parameters.update === 'cancel') {parameters.update = 'store_cancelled';} else if(parameters.update === 'noshow') {parameters.update = 'no_show';}ws.publish('store/reservations/' + parameters.store, JSON.stringify({what: 'store/reservations/' + parameters.store,how: 'update',data: {user_id: id,status: parameters.update,as_of: data.rows[0].system_time_start
let data = 'invalid update';if(params.length === 4) {let query = await pool.query(`update cart set status = $3 where store_id = $1 and cart_id = $2 and status = $4 returning system_time_start, user_id`,params);if(query.rowCount === 1) {data = undefined;ws.publish('store/reservations/' + parameters.store, JSON.stringify({what: 'store/reservations/' + parameters.store,how: 'update',data: {cart_id: parameters.cart,status: params[2],as_of: query.rows[0].system_time_start}}));if(query.rows[0].user_id === null) {ws.publish('reservations/' + parameters.cart, JSON.stringify({what: 'reservations/' + parameters.cart,how: 'update',data: {status: params[2],as_of: query.rows[0].system_time_start}}));} else {ws.publish('user/reservations/' + query.rows[0].user_id, JSON.stringify({what: 'user/reservations/' + query.rows[0].user_id,how: 'update',data: {cart_id: parameters.cart,status: params[2],as_of: query.rows[0].system_time_start}}));