const { normalize, Big, util, request, pg } = require('./scrape/common.js');
const qs = require('querystring');
const argon2 = require('argon2');
const { hash: blake3 } = require('blake3/dist/node');const b = require('fs').readFileSync('./16byteSecretKey');
const cryptids = new (require('crypt-ids').Cryptids)(new Uint8Array(b.buffer, b.byteOffset, b.byteLength));
const sgMail = require('@sendgrid/mail');
sgMail.setApiKey(process.env.SENDGRID);
const pool = new pg.Pool();
const randomBytes = util.promisify(require('crypto').randomBytes);
let clients = 0;
const authenticated = new Map();
setInterval(() => pool.query('select invalidate_stale_carts()'), 1000);
async function is(ws, store, owner) { return (await pool.query(`select god or (owner is not null and owner >= $1) as t from usr left join user_store on usr.user_id = user_store.user_id and store_id = $2 where usr.user_id = $3`, [Boolean(owner), store, ws.user_ID])).rows?.[0]?.t;
}
function notmissing(parameters, fields, ws, response_ID) {
let sad;
if(parameters) {
sad = fields.filter(field => parameters[field] === "" || parameters[field] === null || parameters[field] === undefined || parameters[field] === NaN).join(', ');
} else {
sad = fields;
}
if(sad.length) {
ws.send(JSON.stringify({
response_ID,
data: 'missing ' + sad
}));
return false;
} else {
return true;
}
}
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_id
from cart
where cart_id = any($2) and user_id is null and status = 'active'
),
associate_user2cart as (
update cart
set user_id = $1
where
cart_id in (select cart_id from distinct_cart_per_store) and
not 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
)
select
t.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_qty
from
(select * from distinct_cart_per_store except all select * from associate_user2cart) as t inner join
cart on (t.store_id = cart.store_id and status = 'active' and user_id = $1) inner join
line_item on (t.cart_id = line_item.cart_id)
group by
t.cart_id,
cart.cart_id
`, [ws.user_ID, decryptedCarts])).rows;
loop1: 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();
}
}
}
const origins = process.env.WS_ACCEPTED_ORIGINS.split(',');
const ssl = Boolean(process.env.SSLKEY && process.env.SSLCERT);
const port = Number(ssl ? process.env.SSLPORT : process.env.PORT);
const app = require('uWebSockets.js')[(ssl ? 'SSL' : '') + 'App']({key_file_name: process.env.SSLKEY, cert_file_name: process.env.SSLCERT})
.ws('/*', {
compression: 0,
maxPayloadLength: 16 * 1024 * 1024,
idleTimeout: 10000,
upgrade: (res, req, context) => {
if(origins.includes(req.getHeader('origin')) || origins.includes('*')) {
res.upgrade(
{q: qs.parse(req.getQuery())},
req.getHeader('sec-websocket-key'),
req.getHeader('sec-websocket-protocol'),
req.getHeader('sec-websocket-extensions'),
context
);
} else {
res.close();
}
},
open: async ws => {
console.log('WebSocket opened');
pool.query(`select * from get_stores()`).then(r => {
for(let i = 0; i < r.rows.length; ++i) {
for(const prop in r.rows[i]) {
if(r.rows[i][prop] === null) {
delete r.rows[i][prop];
}
}
}
ws.subscribe('store');
ws.send(JSON.stringify({
what: 'store',
how: 'replace',
data: r.rows
}));
});
let fuck;
let hack;
if(ws.q.token) {
let user = (await pool.query('select user_id, email, god, json_object_agg(store_id, owner) filter (where store_id is not null) as acl from usr left join user_store using (user_id) where token_hash = $1 and now() < token_expiry group by user_id', [blake3(Buffer.from(ws.q.token, 'hex'), {length: 16})])).rows[0];
if(user === undefined) {
fuck = JSON.stringify({
what: 'user',
how: 'replace',
data: null,
why: 'invalid token'
});
} else {
ws.user_ID = user.user_id;
delete user.user_id;
hack = user;
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'); } else {
ws.publish('user/authenticated', JSON.stringify({
what: 'user/authenticated',
how: 'add',
data: ws.user_ID
}));
}
}
}
if(ws.q.otp && (ws.q.token && fuck || !ws.q.token)) {
let token = await randomBytes(16);
let user = (await pool.query(
`with u as (
update usr
set
otp_hash = null,
token_hash = $1
where
otp_hash = $2 and
now() < otp_expiry
returning
user_id,
email,
god
)
select u.*, json_object_agg(store_id, owner) filter (where store_id is not null) as acl
from u left join user_store using (user_id)
group by user_id, email, god`,
[
blake3(token, {length: 16}),
blake3(Buffer.from(ws.q.otp, 'hex'), {length: 16})
]
)).rows[0];
if(user === undefined) {
ws.send(JSON.stringify({
what: 'user',
how: 'replace',
data: null,
why: 'invalid otp'
}));
if(fuck) {
ws.send(fuck);
}
} else {
ws.user_ID = user.user_id;
delete user.user_id;
user.token = token.toString('hex');
hack = user;
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'); } else {
ws.publish('user/authenticated', JSON.stringify({
what: 'user/authenticated',
how: 'add',
data: ws.user_ID
}));
}
}
} else if(fuck) {
ws.send(fuck);
}
++clients;
ws.publish('user/count', JSON.stringify({
what: 'user/count',
how: 'add'
}));
if(hack) {
await mergeCart(null, ws.q.carts, ws);
ws.send(JSON.stringify({
what: 'user',
how: 'replace',
data: hack
}));
}
delete ws.q;
},
message: async (ws, message, isBinary) => {
let {what, parameters = {}, request_ID} = JSON.parse(String.fromCharCode.apply(null, new Uint8Array(message)));
switch(what) {
case 'request_otp':
if(ws.user_ID) {
ws.send(JSON.stringify({
response_ID: request_ID,
data: "already logged in"
}));
} else {
let token = await randomBytes(16);
if((await pool.query(`update usr set otp_expiry = now() + interval '5 minute', otp_hash = $2 where email = $1`, [parameters.email, blake3(token, {length: 16})])).rowCount === 1) {
token = token.toString('hex');
sgMail.send({
to: parameters.email,
from: 'lobojane <support@lobojane.com>',
subject: `Click this link to finish logging in`,
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.`
}).then(() => ws.send(JSON.stringify({
response_ID: request_ID
})), error => {
console.error(error);
ws.send(JSON.stringify({
response_ID: request_ID,
data: 'failed to send email'
}));
});
} else {
ws.send(JSON.stringify({
response_ID: request_ID,
data: 'user does not exist'
}))
}
}
break;
case 'login_otp': if(ws.user_ID) {
ws.send(JSON.stringify({
response_ID: request_ID,
data: "already logged in"
}));
} else {
let token = await randomBytes(16);
let user = (await pool.query(
`with u as (
update usr
set
otp_hash = null,
token_hash = $1
where
otp_hash = $2 and
now() < otp_expiry
returning
user_id,
email,
god
)
select u.*, json_object_agg(store_id, owner) filter (where store_id is not null) as acl
from u left join user_store using (user_id)
group by user_id, email, god`,
[
blake3(token, {length: 16}),
blake3(Buffer.from(parameters.otp, 'hex'), {length: 16})
]
)).rows[0];
if(user === undefined) {
ws.send(JSON.stringify({
response_ID: request_ID,
data: "invalid otp"
}));
} else {
ws.user_ID = user.user_id;
await mergeCart(null, parameters.carts, ws);
delete user.user_id;
user.token = token.toString('hex');
ws.send(JSON.stringify({
response_ID: request_ID,
data: user
}));
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'); } else {
ws.publish('user/authenticated', JSON.stringify({
what: 'user/authenticated',
how: 'add',
data: ws.user_ID
}));
}
}
}
break;
case 'register':
if(ws.user_ID) {
ws.send(JSON.stringify({
response_ID: request_ID,
data: "already logged in"
}));
} else {
try {
let token = await randomBytes(16);
let decryptedCarts = [];
if(Array.isArray(parameters.carts)) {
for(let i = 0; i < parameters.carts.length; ++i) {
try {
decryptedCarts.push(cryptids.s2i(parameters.carts[i]));
} catch(e) {}
}
}
let user = (await pool.query(`
with i as (
insert into usr
(email, passphrase_hash, token_hash)
values ($1, $2, $3)
returning user_id
), distinct_cart_per_store as (
select distinct on (store_id) cart_id
from cart
where cart_id = any($4) and user_id is null and status = 'active'
), u as (
update cart
set user_id = (select user_id from i)
where cart_id in (select cart_id from distinct_cart_per_store)
)
select user_id from i`,
[
parameters.email,
await argon2.hash(parameters.passphrase),
blake3(token, {length: 16}),
decryptedCarts
])).rows[0];
ws.user_ID = user.user_id;
ws.send(JSON.stringify({
response_ID: request_ID,
data: {token: token.toString('hex')}
}));
user.email = parameters.email;
user.god = false;
user.acl = null;
ws.publish('user/s', JSON.stringify({
what: "users",
how: 'add',
data: user
}));
authenticated.set(ws.user_ID, ws);
ws.publish('user/authenticated', JSON.stringify({
what: 'user/authenticated',
how: 'add',
data: {user_id: ws.user_ID, email: parameters.email, god: false, logged_in: true},
why: 'register'
}));
} catch(e) {
let error = 'user already exists';
if(e.constraint !== 'usr_email_key') {
console.error(e);
error = 'error';
}
ws.send(JSON.stringify({
response_ID: request_ID,
data: error
}));
}
}
break;
case 'unregister':
if(ws.user_ID) {
await pool.query(`delete from usr where user_id = $1`, [ws.user_ID]);
ws.send(JSON.stringify({
response_ID: request_ID
}));
authenticated.delete(ws.user_ID);
ws.publish('user/authenticated', JSON.stringify({
what: 'user/authenticated',
how: 'remove',
data: ws.user_ID,
why: 'unregister'
}));
ws.end(1000, 'jendo'); } else {
ws.send(JSON.stringify({
response_ID: request_ID,
data: 'unauthenticated'
}));
}
break;
case 'login':
if(ws.user_ID) {
ws.send(JSON.stringify({
response_ID: request_ID,
data: "already logged in"
}));
} else {
let user = (await pool.query('select user_id, passphrase_hash, god, json_object_agg(store_id, owner) filter (where store_id is not null) as acl from usr left join user_store using (user_id) where email = $1 group by user_id', [parameters.email])).rows[0];
if(user === undefined) {
ws.send(JSON.stringify({
response_ID: request_ID,
data: "user does not exist"
}));
} else {
try {
if(user.passphrase_hash !== null && user.passphrase_hash !== undefined && await argon2.verify(user.passphrase_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 where user_id = $2`, [blake3(token, {length: 16}), ws.user_ID]);
await mergeCart(client, parameters.carts, ws);
await client.query('commit');
client.release();
ws.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'); } else {
ws.publish('user/authenticated', JSON.stringify({
what: 'user/authenticated',
how: 'add',
data: ws.user_ID
}));
}
} else { ws.send(JSON.stringify({
response_ID: request_ID,
data: "wrong passphrase"
}));
}
} catch(e) {
console.error(e);
ws.send(JSON.stringify({
response_ID: request_ID,
data: 'error'
}));
}
}
}
break;
case 'logout':
if(ws.user_ID) {
await pool.query(`update usr set token_expiry = null, token_hash = null where user_id = $1`, [ws.user_ID]);
ws.send(JSON.stringify({
response_ID: request_ID
}));
authenticated.delete(ws.user_ID);
ws.publish('user/authenticated', JSON.stringify({
what: 'user/authenticated',
how: 'remove',
data: ws.user_ID
}));
ws.end(1000, 'see ya l8er alligator'); } else {
ws.send(JSON.stringify({
response_ID: request_ID,
data: 'unauthenticated'
}));
}
break;
case 'list_users': {
const auth = await is(ws);
if(auth) {
ws.subscribe('user/#'); let users = (await pool.query('select user_id, email, god from usr')).rows;
for(let i = 0; i < users.length; ++i) {
if(authenticated.has(users[i].user_id)) {
users[i].logged_in = true;
} else {
users[i].logged_in = false;
}
}
ws.send(JSON.stringify({
response_ID: request_ID,
data: {
users,
count: clients
}
}));
} else if(auth === undefined) {
if(ws.user_ID) {
authenticated.delete(ws.user_ID);
ws.publish('user/authenticated', JSON.stringify({
what: 'user/authenticated',
how: 'remove',
data: ws.user_ID
}));
delete ws.user_ID;
}
ws.send(JSON.stringify({
response_ID: request_ID,
data: 'unauthenticated'
})); } else {
ws.send(JSON.stringify({
response_ID: request_ID,
data: 'unauthorized'
})); }
break;}
case 'update_profile':
if(ws.user_ID) {
let dawg = [];
let poop = [ws.user_ID];
if(typeof parameters.email === 'string') {
dawg.push(`email = $${poop.push(parameters.email)}`);
}
if(typeof parameters.passphrase === 'string') {
dawg.push(`passphrase_hash = $${poop.push(await argon2.hash(parameters.passphrase))}`);
}
if(dawg.length) {
try {
if((await pool.query(`update usr set ${dawg.join(',')} where user_id = $1`, poop)).rowCount) {
ws.send(JSON.stringify({
response_ID: request_ID
}));
} else {
authenticated.delete(ws.user_ID);
ws.publish('user/authenticated', JSON.stringify({
what: 'user/authenticated',
how: 'remove',
data: ws.user_ID
}));
delete ws.user_ID;
ws.send(JSON.stringify({
response_ID: request_ID,
data: 'unauthenticated'
}));
}
} catch(e) {
let error = 'this email address is in use';
if(e.constraint !== 'usr_email_key') {
console.error(e);
error = 'error';
}
ws.send(JSON.stringify({
response_ID: request_ID,
data: error
}));
}
} else {
ws.send(JSON.stringify({
response_ID: request_ID,
data: 'WHAT DO U WANT ME 2 DO???'
}));
}
} else {
ws.send(JSON.stringify({
response_ID: request_ID,
data: 'unauthenticated'
}));
}
break;
case 'create_store': {
const auth = await is(ws);
if(auth) {
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);
}
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]);
} else {
fuck.push([ts, te]);
}
} catch(e) {}
}
}
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 => {
console.log(e);
ws.send(JSON.stringify({
response_ID: request_ID,
data: 'db fuckup'
}));
});
} else if(auth === undefined) {
if(ws.user_ID) {
authenticated.delete(ws.user_ID);
ws.publish('user/authenticated', JSON.stringify({
what: 'user/authenticated',
how: 'remove',
data: ws.user_ID
}));
delete ws.user_ID;
}
ws.send(JSON.stringify({
response_ID: request_ID,
data: 'unauthenticated'
})); } else {
ws.send(JSON.stringify({
response_ID: request_ID,
data: 'unauthorized'
})); }
break;}
case 'store_distance': {
let stores = (await pool.query('select store_id, longitude, latitude, osrm_hint_foot, osrm_hint_car from store where longitude is not null and latitude is not null')).rows;
let longlats = stores.map(x => `${x.longitude},${x.latitude}`).join(';');
let [foot, car] = await Promise.all([
request(`http://127.0.0.1:5000/table/v1/fuck/${parameters.longitude},${parameters.latitude};${longlats}?annotations=duration,distance&skip_waypoints=true&sources=0&hints=;${stores.map(x => x.osrm_hint_foot).join(';')}`).then(JSON.parse),
request(`http://127.0.0.1:5001/table/v1/fuck/${parameters.longitude},${parameters.latitude};${longlats}?annotations=duration,distance&skip_waypoints=true&sources=0&hints=;${stores.map(x => x.osrm_hint_car).join(';')}`).then(JSON.parse)
]);
if(foot.code === 'Ok' && car.code === 'Ok') {
for(let i = stores.length - 1; i >= 0; --i) {
delete stores[i].longitude;
delete stores[i].latitude;
delete stores[i].osrm_hint_foot;
delete stores[i].osrm_hint_car;
if(foot.durations[0][i + 1] === null && car.durations[0][i + 1] === null) {
stores.splice(i, 1);
} else if(foot.durations[0][i + 1] === null) {
stores[i].distance = car.distances[0][i + 1];
} else if(car.durations[0][i + 1] === null || foot.durations[0][i + 1] <= car.durations[0][i + 1] + 300) { stores[i].distance = foot.distances[0][i + 1];
} else {
stores[i].distance = car.distances[0][i + 1];
}
}
ws.send(JSON.stringify({
response_ID: request_ID,
data: stores
}));
} else {
console.log(foot, car);
ws.send(JSON.stringify({
response_ID: request_ID,
data: 'could not compute routes'
}));
}
break;}
case 'cloudinary_preset': {
const auth = (await pool.query(`select god or owner as t from usr left join user_store using (user_id) where user_id = $1 order by owner desc nulls last limit 1`, [ws.user_ID])).rows?.[0]?.t;
if(auth) {
ws.send(JSON.stringify({
response_ID: request_ID,
data: process.env.CLOUDINARY
}));
} else if(auth === undefined) {
if(ws.user_ID) {
authenticated.delete(ws.user_ID);
ws.publish('user/authenticated', JSON.stringify({
what: 'user/authenticated',
how: 'remove',
data: ws.user_ID
}));
delete ws.user_ID;
}
ws.send(JSON.stringify({
response_ID: request_ID,
data: 'unauthenticated'
})); } else {
ws.send(JSON.stringify({
response_ID: request_ID,
data: 'unauthorized'
})); let r = (await pool.query({text: 'select store_id, owner from user_store where user_id = $1', values: [ws.user_ID], rowMode: 'array'})).rows;
ws.send(JSON.stringify({
what: 'user',
how: 'update',
data: {acl: r.length ? Object.fromEntries(r) : null}
}));
}
break;}
case 'product_form': {
const auth = (await pool.query(`select god or owner as t from usr left join user_store using (user_id) where user_id = $1 order by owner desc nulls last limit 1`, [ws.user_ID])).rows?.[0]?.t;
if(auth) {
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])
]);
ws.send(JSON.stringify({
response_ID: request_ID,
data: {strains, ...pgenum}
}));
} else if(auth === undefined) {
if(ws.user_ID) {
authenticated.delete(ws.user_ID);
ws.publish('user/authenticated', JSON.stringify({
what: 'user/authenticated',
how: 'remove',
data: ws.user_ID
}));
delete ws.user_ID;
}
ws.send(JSON.stringify({
response_ID: request_ID,
data: 'unauthenticated'
})); } else {
ws.send(JSON.stringify({
response_ID: request_ID,
data: 'unauthorized'
})); let r = (await pool.query({text: 'select store_id, owner from user_store where user_id = $1', values: [ws.user_ID], rowMode: 'array'})).rows;
ws.send(JSON.stringify({
what: 'user',
how: 'update',
data: {acl: r.length ? Object.fromEntries(r) : null}
}));
}
break;}
case 'create_product': {
const auth = (await pool.query(`select god, owner from usr left join user_store using (user_id) where user_id = $1 order by owner desc nulls last limit 1`, [ws.user_ID])).rows?.[0];
if(auth === undefined) {
if(ws.user_ID) {
authenticated.delete(ws.user_ID);
ws.publish('user/authenticated', JSON.stringify({
what: 'user/authenticated',
how: 'remove',
data: ws.user_ID
}));
delete ws.user_ID;
}
ws.send(JSON.stringify({
response_ID: request_ID,
data: 'unauthenticated'
})); } else if(auth.god || auth.owner) {
try {
let q = 1;
let [producer, brand, joined_name, display_name] = normalize(parameters.brand, parameters.name, parameters.type);
let params = [
producer,
brand,
joined_name,
display_name,
parameters.type,
parameters.species,
parameters.strain,
parameters.description,
parameters.terpenes,
auth.god
];
let query = `with insert1 as (insert into product (producer, brand, display_name, joined_name, type, species, strain_id, description, terpenes, show) values ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10) on conflict do nothing returning product_id)`;
if(Array.isArray(parameters.variants) && parameters.variants.length) {
if(parameters.type === 'flower') {
let values = parameters.variants.reduce((acc, v) => {
if(isFinite(v.quantity)) {
acc.push(`($${params.push(v.quantity)})`);
}
return acc;
}, []);
if(values.length) {
query += `, insert${++q} as (insert into variant (product_id, quantity) select product_id, x::numeric from insert1 cross join (values ${values.join(',')}) as t(x) on conflict do nothing)`;
}
} else if(parameters.type === 'preroll' || parameters.type === 'tea' || parameters.type === 'gummy' || parameters.type === 'mint' || parameters.type === 'chocolate' || parameters.type === 'baked') {
let values = parameters.variants.reduce((acc, v) => {
if(isFinite(v.quantity) && Number.isInteger(Number(v.portions))) {
acc.push(`($${params.push(v.portions)}, $${params.push(v.quantity)})`);
}
return acc;
}, []);
if(values.length) {
query += `, insert${++q} as (insert into variant (product_id, portions, quantity) select product_id, x::smallint, y::numeric from insert1 cross join (values ${values.join(',')}) as t(x, y) on conflict do nothing)`;
}
} else if(parameters.type === 'cartridge' || parameters.type === 'disposable') {
let values = parameters.variants.reduce((acc, v) => {
if(isFinite(v.quantity)) {
acc.push(`($${params.push(v.quantity)}, ${v.flavor ? '$' + params.push(v.flavor) : 'null'})`);
}
return acc;
}, []);
if(values.length) {
query += `, insert${++q} as (insert into variant (product_id, quantity, flavor) select product_id, x::numeric, y from insert1 cross join (values ${values.join(',')}) as t(x, y) on conflict do nothing)`;
}
} else if (parameters.type === 'oil' || parameters.type === 'spray') {
let values = parameters.variants.reduce((acc, v) => {
if(isFinite(v.gram_equivalency) && Number.isInteger(Number(v.quantity))) {
acc.push(`($${params.push(v.quantity)}, $${params.push(v.gram_equivalency)})`);
}
return acc;
}, []);
if(values.length) {
query += `, insert${++q} as (insert into variant (product_id, quantity, gram_equivalency) select product_id, y::numeric, z::numeric from insert1 cross join (values ${values.join(',')}) as t(y, z) on conflict do nothing)`;
}
} else if(parameters.type === 'capsule') {
let values = parameters.variants.reduce((acc, v) => {
if(isFinite(v.gram_equivalency) && Number.isInteger(Number(v.portions))) {
acc.push(`($${params.push(v.portions)}, ${isFinite(v.quantity) ? '$' + params.push(v.quantity) : 'null'}, $${params.push(v.gram_equivalency)})`);
}
return acc;
}, []);
if(values.length) {
query += `, insert${++q} as (insert into variant (product_id, portions, quantity, gram_equivalency) select product_id, x::smallint, y::numeric, z::numeric from insert1 cross join (values ${values.join(',')}) as t(x, y, z) on conflict do nothing)`;
}
}
}
if(Array.isArray(parameters.images) && parameters.images.length) {
query += `, insert${++q} as (insert into product_image (product_id, destination) select product_id, x from insert1 cross join (values ${parameters.images.map(i => `(${params.push(i)})`).join(',')}) as t(x) on conflict do nothing)`;
}
let product = (await pool.query(query + ' select product_id from insert1', params)).rows;
ws.send(JSON.stringify({
response_ID: request_ID,
data: product.length === 1 && Number.isInteger(Number(product[0].product_id)) ? undefined : 'product already exists'
}));
} catch(e) {
console.error(e);
ws.send(JSON.stringify({
response_ID: request_ID,
data: 'check log'
}));
}
} else {
ws.send(JSON.stringify({
response_ID: request_ID,
data: 'unauthorized'
})); let r = (await pool.query({text: 'select store_id, owner from user_store where user_id = $1', values: [ws.user_ID], rowMode: 'array'})).rows;
ws.send(JSON.stringify({
what: 'user',
how: 'update',
data: {acl: r.length ? Object.fromEntries(r) : null}
}));
}
break; }
case 'productsq':
const [strains, idk, pgenum] = await Promise.all([
pool.query('select strain_id as id, display_name as name from strain').then(x => x.rows),
pool.query('select producer, array_agg(distinct brand) as brands from product group by producer').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])
]);
ws.send(JSON.stringify({
response_ID: request_ID,
data: {strains, idk, ...pgenum}
}));
break;
case 'products': {
let params = [];
let qs = '';
let wack = '';
if(parameters) {
if(Array.isArray(parameters.stores) && (parameters.stores = parameters.stores.map(Number)).every(Number.isInteger)) {
qs += `_store_ids => $${params.push(parameters.stores)},`;
}
if(Array.isArray(parameters.strain_ids) && (parameters.strain_ids = parameters.strain_ids.map(Number)).every(Number.isInteger)) {
qs += `_strain_ids => $${params.push(parameters.strain_ids)},`;
}
if(Array.isArray(parameters.types) && parameters.types.every(s => typeof s === 'string')) {
qs += `_types => $${params.push(parameters.types)},`;
}
if(Array.isArray(parameters.species) && parameters.species.every(s => typeof s === 'string')) {
qs += `_species => $${params.push(parameters.species)},`;
}
if(typeof parameters.search === 'string') {
qs += `search => $${params.push(parameters.search)},`;
}
if(typeof parameters.in_stock === 'boolean') {
qs += `in_stock => $${params.push(parameters.in_stock)},`;
}
if(Array.isArray(parameters.thc2tc) && parameters.thc2tc.every(isFinite) && parameters.thc2tc.length === 2) {
qs += `min_thc2tc => $${params.push(parameters.thc2tc[0])},max_thc2tc => $${params.push(parameters.thc2tc[1])},`;
}
if(parameters.min_quantity) {
qs += `min_quantity => $${params.push(parameters.min_quantity)},`;
}
if(parameters.max_quantity) {
qs += `max_quantity => $${params.push(parameters.max_quantity)},`;
}
if(Array.isArray(parameters.producers) && parameters.producers.every(p => typeof p === 'object' && (typeof p.producer === 'string' || p.producer === null) && (Array.isArray(p.brands) || p.brands === undefined))) {
wack += ` where ${parameters.producers.map(p => `producer ${typeof p.producer === 'string' ? `= $${params.push(p.producer)}` : 'is null'}${Array.isArray(p.brands) ? ` and brand = any($${params.push(p.brands)})`: ''}`).join(' or ')}`;
}
if(typeof parameters.price === 'boolean') {
wack += ` order by min_price ${parameters.price ? 'a' : 'de'}sc`;
} else if(typeof parameters.THC === 'boolean') {
wack += ` order by avg_range(thc) ${parameters.THC ? 'a' : 'de'}sc, case when type in ('tea', 'gummy', 'mint', 'chocolate', 'baked') then avg_range(thc) / (min_portions + max_portions) else null end ${parameters.THC ? 'a' : 'de'}sc`; } else if(typeof parameters.CBD === 'boolean') {
wack += ` order by avg_range(cbd) ${parameters.CBD ? 'a' : 'de'}sc, case when type in ('tea', 'gummy', 'mint', 'chocolate', 'baked') then avg_range(cbd) / (min_portions + max_portions) else null end ${parameters.CBD ? 'a' : 'de'}sc`;
} else if(typeof parameters.producer === 'boolean') { wack += ` order by lower(producer) ${parameters.producer ? 'a' : 'de'}sc nulls last, case when brand = '' then lower(producer) else lower(brand) end ${parameters.producer ? 'a' : 'de'}sc, lower(name) ${parameters.producer ? 'a' : 'de'}sc`;
} else if(typeof parameters.brand === 'boolean') {
wack += ` order by case when brand = '' then lower(producer) else lower(brand) end ${parameters.brand ? 'a' : 'de'}sc, lower(name) ${parameters.brand ? 'a' : 'de'}sc`;
} else if(typeof parameters.name === 'boolean') {
wack += ` order by lower(name) ${parameters.name ? 'a' : 'de'}sc, lower(producer) ${parameters.name ? 'a' : 'de'}sc nulls last, case when brand = '' then lower(producer) else lower(brand) end ${parameters.name ? 'a' : 'de'}sc`;
} else if(typeof parameters.price_g === 'boolean') {
wack += ` order by min_price_g ${parameters.price_g ? 'a' : 'de'}sc`;
}
}
ws.send(JSON.stringify({
response_ID: request_ID,
data: (await pool.query(`select * from get_products(${qs.slice(0, -1)})${wack}`, params)).rows
}));
break;}
case 'similar_products': {
let params = [];
let qs = '';
if(parameters) {
if(Array.isArray(parameters.stores) && (parameters.stores = parameters.stores.map(Number)).every(Number.isInteger)) {
qs += `_store_ids => $${params.push(parameters.stores)},`;
}
if(typeof parameters.in_stock === 'boolean') {
qs += `in_stock => $${params.push(parameters.in_stock)},`;
}
let v = Number(parameters.variant_id);
if(Number.isInteger(v)) {
qs += `_variant_id => $${params.push(v)},`;
ws.send(JSON.stringify({
response_ID: request_ID,
data: (await pool.query(`select * from similar_products(${qs.slice(0, -1)})`, params)).rows
}));
break;
}
}
break;
}
case 'product_static':
let p = Number(parameters.id);
let data = 'invalid id';
if(Number.isInteger(p)) {
let params = [p];
let s = Number(parameters.store);
let response = (await pool.query(`select * from get_product_static($1${Number.isInteger(s) ? ', $' + params.push(s) : ''})`, params)).rows;
if(response.length === 0) {
data = 'product not found';
} else {
data = response[0];
}
}
ws.send(JSON.stringify({
response_ID: request_ID,
data: data
}));
break;
case 'product_dynamic': {
let v = Number(parameters.variant);
let data = 'invalid id';
if(Number.isInteger(v)) {
let params = [v];
let s = Number(parameters.store);
let response;
if(Number.isInteger(s)) {
response = (await pool.query(`select * from get_menu_item($1, $${params.push(s)})`, params)).rows;
} else {
response = (await pool.query(`select * from get_product_dynamic($1)`, params)).rows;
}
if(response.length === 0) {
data = 'product not found';
} else {
data = response[0];
}
}
ws.send(JSON.stringify({
response_ID: request_ID,
data: data
}));
break;}
case 'menu_item_loader': {
const auth = await is(ws, parameters.store);
if(auth) {
ws.send(JSON.stringify({
response_ID: request_ID,
data: (await pool.query(`select * from menu_item_loader($1)`, [parameters.store])).rows
}));
} else if(auth === undefined) {
if(ws.user_ID) {
authenticated.delete(ws.user_ID);
ws.publish('user/authenticated', JSON.stringify({
what: 'user/authenticated',
how: 'remove',
data: ws.user_ID
}));
delete ws.user_ID;
}
ws.send(JSON.stringify({
response_ID: request_ID,
data: 'unauthenticated'
})); } else {
ws.send(JSON.stringify({
response_ID: request_ID,
data: 'unauthorized'
})); let r = (await pool.query({text: 'select store_id, owner from user_store where user_id = $1', values: [ws.user_ID], rowMode: 'array'})).rows;
ws.send(JSON.stringify({
what: 'user',
how: 'update',
data: {acl: r.length ? Object.fromEntries(r) : null}
}));
}
break;}
case 'menu_items_builder': {
const auth = await is(ws, parameters.store);
if(auth) {
let shit;
if(Array.isArray(parameters.fresh) && parameters.fresh.length) {
let query = 'insert into menu_item values ';
let params = [];
for(let i = 0; i < parameters.fresh.length; ++i) {
query += `(default, $${i * 7 + 1}, $${i * 7 + 2}, $${i * 7 + 3}, $${i * 7 + 4}, $${i * 7 + 5}, $${i * 7 + 6}, $${i * 7 + 7}),`;
params.push(
parameters.fresh[i].variant,
parameters.store,
`[${parameters.fresh[i].min_cbd},${parameters.fresh[i].max_cbd}]`,
`[${parameters.fresh[i].min_thc},${parameters.fresh[i].max_thc}]`,
parameters.fresh[i].price,
parameters.fresh[i].stock,
Boolean(parameters.fresh[i].featured)
);
}
shit = (await pool.query({text: query.slice(0, -1) + ` on conflict (variant_id, store_id) do update set
cbd = excluded.cbd,
thc = excluded.thc,
price = excluded.price,
stock = excluded.stock,
featured = excluded.featured
where (menu_item.cbd, menu_item.thc, menu_item.price, menu_item.stock, menu_item.featured) is distinct from
(excluded.cbd, excluded.thc, excluded.price, excluded.stock, excluded.featured) returning menu_item_id
`,
values: params,
rowMode: 'array'
})).rows.flat(); }
if(Array.isArray(parameters.updated)) {
for(let i = 0; i < parameters.updated.length; ++i) {
if(Number.isInteger(Number(parameters.updated[i].menu_item))) {
let params = [parameters.store, parameters.updated[i].menu_item];
let what = [];
if(isFinite(parameters.updated[i].price)) {
what.push(`price = $${params.push(parameters.updated[i].price)}`);
}
if(Number.isInteger(Number(parameters.updated[i].stock))) {
what.push(`stock = $${params.push(parameters.updated[i].stock)}`);
}
if(typeof parameters.updated[i].featured === 'boolean') {
what.push(`featured = $${params.push(parameters.updated[i].featured)}`);
}
if(isFinite(parameters.updated[i].min_cbd) && isFinite(parameters.updated[i].max_cbd)) {
what.push(`cbd = $${params.push(`[${parameters.updated[i].min_cbd},${parameters.updated[i].max_cbd}]`)}`);
} else if(isFinite(parameters.updated[i].min_cbd)) {
what.push(`cbd = numrange($${params.push(parameters.updated[i].min_cbd)}, upper(cbd), '[]')`);
} else if(isFinite(parameters.updated[i].max_cbd)) {
what.push(`cbd = numrange(lower(cbd), $${params.push(parameters.updated[i].max_cbd)}, '[]')`);
}
if(isFinite(parameters.updated[i].min_thc) && isFinite(parameters.updated[i].max_thc)) {
what.push(`thc = $${params.push(`[${parameters.updated[i].min_thc},${parameters.updated[i].max_thc}]`)}`);
} else if(isFinite(parameters.updated[i].min_thc)) {
what.push(`thc = numrange($${params.push(parameters.updated[i].min_thc)}, upper(thc), '[]')`);
} else if(isFinite(parameters.updated[i].max_thc)) {
what.push(`thc = numrange(lower(thc), $${params.push(parameters.updated[i].max_thc)}, '[]')`);
}
if(what.length > 0) {
await pool.query(`update menu_item set ${what.join(', ')} where store_id = $1 and menu_item_id = $2`, params);
}
}
}
}
if(Array.isArray(parameters.deleted) && parameters.deleted.length) {
await pool.query('delete from menu_item where menu_item_id = any($1) and store_id = $2', [parameters.deleted, parameters.store]);
}
ws.send(JSON.stringify({
response_ID: request_ID,
data: shit
}));
} else if(auth === undefined) {
if(ws.user_ID) {
authenticated.delete(ws.user_ID);
ws.publish('user/authenticated', JSON.stringify({
what: 'user/authenticated',
how: 'remove',
data: ws.user_ID
}));
delete ws.user_ID;
}
ws.send(JSON.stringify({
response_ID: request_ID,
data: 'unauthenticated'
})); } else {
ws.send(JSON.stringify({
response_ID: request_ID,
data: 'unauthorized'
})); let r = (await pool.query({text: 'select store_id, owner from user_store where user_id = $1', values: [ws.user_ID], rowMode: 'array'})).rows;
ws.send(JSON.stringify({
what: 'user',
how: 'update',
data: {acl: r.length ? Object.fromEntries(r) : null}
}));
}
break;}
case 'modifycart':
if(Number.isInteger(Number(parameters.menu_item))) {
let params = [];
let qs = [`_menu_item_id => $${params.push(parameters.menu_item)}`];
if(Number.isInteger(Number(parameters.qty))) {
qs.push(`qty => $${params.push(parameters.qty)}`);
}
if(ws.user_ID) {
qs.push(`_user_id => $${params.push(ws.user_ID)}`);
} 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)}`);
}
}
let data;
try {
data = (await pool.query(`select modifycart(${qs.join(',')})`,
params
)).rows[0].modifycart;
if(data === null) {
data = undefined;
} else {
data = cryptids.i2s(data);
}
} catch(e) {
data = e.message;
}
ws.send(JSON.stringify({
response_ID: request_ID,
data
}));
}
break;
case 'cart': {
let data = 'invalid cart';
if(ws.user_ID) {
data = (await pool.query(`select * from authenticated_carts($1)`, [ws.user_ID])).rows
} 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) {
data = (await pool.query(`select * from anonymous_carts($1)`, [decryptedCarts])).rows;
}
}
if(Array.isArray(data)) {
for(let i = 0; i < data.length; ++i) {
data[i].cart_id = cryptids.i2s(data[i].cart_id);
}
}
ws.send(JSON.stringify({
response_ID: request_ID,
data
}));
break;}
case 'reservation': {
let kartik;
try {
kartik = cryptids.s2i(parameters.cart);
} catch(e) {
ws.send(JSON.stringify({
response_ID: request_ID,
data: 'invalid cart'
}));
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.subscribe('reservations/' + parameters.cart);
ws.send(JSON.stringify({
response_ID: request_ID,
data: data.rows[0]
}));
} else {
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',
how: 'remove',
data: ws.user_ID
}));
delete ws.user_ID;
}
ws.send(JSON.stringify({
response_ID: request_ID,
data: 'unauthenticated'
})); } else if(!auth && n) {
ws.send(JSON.stringify({
response_ID: request_ID,
data: 'unauthorized'
})); } else {
const id = auth && n ? parameters.user_ID : ws.user_ID;
let r = (await pool.query(`
update cart
set user_id = $1
where
cart_id = $2 and
user_id is null and
status > 'active'
`, [id, kartik]
));
if(r.rowCount === 1) {
ws.send(JSON.stringify({
response_ID: request_ID
})); ws.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'
}));
}
}
break;}
case 'user_reservations': {
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',
how: 'remove',
data: ws.user_ID
}));
delete ws.user_ID;
}
ws.send(JSON.stringify({
response_ID: request_ID,
data: 'unauthenticated'
})); } else if(!auth && n) {
ws.send(JSON.stringify({
response_ID: request_ID,
data: 'unauthorized'
})); } else {
const id = auth && n ? parameters.user_ID : ws.user_ID;
ws.subscribe('user/reservations/' + id);
let data = (await pool.query(`select * from user_reservations($1)`, [id])).rows;
for(let i = 0; i < data.length; ++i) {
data[i].cart_id = cryptids.i2s(data[i].cart_id);
let out;
for(let j = 0; j < data[i].statuses.length; ++j) {
if(j === data[i].statuses.length - 1 && data[i].statuses[j].end !== 'infinity' && (data[i].statuses[j].status === 'placed' || data[i].statuses[j].status === 'ready')) {
out = {status: 'store_cancelled', as_of: data[i].statuses[j].end};
}
delete data[i].statuses[j].end;
}
if(out) {
data[i].statuses.push(out);
}
}
ws.send(JSON.stringify({
response_ID: request_ID,
data
}));
}
break;}
case 'user_update_reservation': {
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',
how: 'remove',
data: ws.user_ID
}));
delete ws.user_ID;
}
}
if(!auth && n) {
ws.send(JSON.stringify({
response_ID: request_ID,
data: 'unauthorized'
})); } else {
const id = auth && n ? parameters.user_ID : ws.user_ID;
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) {
let r = await pool.query(`
update cart as x
set
status = $3,
user_id = case
when x.user_id is null then $1
else x.user_id
end
from (select cart_id, user_id from cart where cart_id = $2 for update) as y
where
x.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) { ws.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 { let 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 image
from
line_item
inner 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) { ws.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
}
}));
}
}
}
}
ws.send(JSON.stringify({
response_ID: request_ID,
data
}));
}
break;}
case 'store_reservations': {
if(Number.isInteger(Number(parameters.store))) {
const auth = await is(ws, parameters.store);
if(auth) {
ws.subscribe('store/reservations/' + parameters.store);
let data = (await pool.query(`select * from store_reservations($1)`, [parameters.store])).rows;
for(let i = 0; i < data.length; ++i) {
data[i].cart_id = cryptids.i2s(data[i].cart_id);
let out;
for(let j = data[i].statuses.length - 1; j >= 0; --j) {
if(j === data[i].statuses.length - 1 && data[i].statuses[j].end !== 'infinity' && (data[i].statuses[j].status === 'placed' || data[i].statuses[j].status === 'ready')) {
out = {status: 'user_cancelled', as_of: data[i].statuses[j].end};
}
delete data[i].statuses[j].end;
if(j > 0 && data[i].statuses[j].status === data[i].statuses[j - 1].status) {
data[i].statuses.splice(j, 1);
}
}
if(out) {
data[i].statuses.push(out);
}
}
ws.send(JSON.stringify({
response_ID: request_ID,
data
}));
} else if(auth === undefined) {
if(ws.user_ID) {
authenticated.delete(ws.user_ID);
ws.publish('user/authenticated', JSON.stringify({
what: 'user/authenticated',
how: 'remove',
data: ws.user_ID
}));
delete ws.user_ID;
}
ws.send(JSON.stringify({
response_ID: request_ID,
data: 'unauthenticated'
})); } else {
ws.send(JSON.stringify({
response_ID: request_ID,
data: 'unauthorized'
})); let r = (await pool.query({text: 'select store_id, owner from user_store where user_id = $1', values: [ws.user_ID], rowMode: 'array'})).rows;
ws.send(JSON.stringify({
what: 'user',
how: 'update',
data: {acl: r.length ? Object.fromEntries(r) : null}
}));
}
} else {
ws.send(JSON.stringify({
response_ID: request_ID,
data: 'missing store'
}));
}
break;}
case 'store_update_reservation': {
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 {
const auth = await is(ws, parameters.store);
if(auth) {
let params = [parameters.store, kartik];
switch(parameters.update) { case 'ready':
params.push('ready', 'placed');
break;
case 'paid':
params.push('paid', 'ready');
break;
case 'cancel':
params.push('store_cancelled', 'placed');
break;
case 'noshow':
params.push('no_show', 'ready');
break;
}
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
}
}));
}
}
}
ws.send(JSON.stringify({
response_ID: request_ID,
data
}));
} else if(auth === undefined) {
if(ws.user_ID) {
authenticated.delete(ws.user_ID);
ws.publish('user/authenticated', JSON.stringify({
what: 'user/authenticated',
how: 'remove',
data: ws.user_ID
}));
delete ws.user_ID;
}
ws.send(JSON.stringify({
response_ID: request_ID,
data: 'unauthenticated'
})); } else {
ws.send(JSON.stringify({
response_ID: request_ID,
data: 'unauthorized'
})); let r = (await pool.query({text: 'select store_id, owner from user_store where user_id = $1', values: [ws.user_ID], rowMode: 'array'})).rows;
ws.send(JSON.stringify({
what: 'user',
how: 'update',
data: {acl: r.length ? Object.fromEntries(r) : null}
}));
}
}
break;}
case 'unsubscribe':
if(parameters && typeof parameters.what === 'string' && parameters.what.length) {
ws.send(JSON.stringify({
response_ID: request_ID,
data: parameters.what === 'all' ? ws.unsubscribeAll() : ws.unsubscribe(parameters.what)
}));
}
break;
default:
console.log('unknown operation');
}
},
drain: (ws) => {
console.log('WebSocket backpressure: ' + ws.getBufferedAmount());
},
close: async (ws, code, message) => {
if(ws.user_ID) {
await pool.query(`update usr set token_expiry = now() + interval '14 day' where user_id = $1`, [ws.user_ID]);
authenticated.delete(ws.user_ID);
app.publish('user/authenticated', JSON.stringify({
what: 'user/authenticated',
how: 'remove',
data: ws.user_ID
}));
}
--clients;
app.publish('user/count', JSON.stringify({
what: 'user/count',
how: 'remove'
}));
console.log('WebSocket closed');
}
}).any('/*', (res, req) => {
res.end('<div>Nothing to see here!</div><script>const socket = new WebSocket("ws' + (ssl ? 's' : '') + '://" + location.hostname);</script>');
}).listen(port, μSocket => {
if(μSocket) {
console.log('Listening to port ' + port);
} else {
console.log('Failed to listen to port ' + port);
}
});