better basket and users table

Former-commit-id: cb7a9735df12510bc9d45b2bef984082799b0ab2
This commit is contained in:
Ben
2022-04-27 00:35:37 +01:00
parent df78a9a6e6
commit f4b7a5fb38
15 changed files with 9775 additions and 529 deletions

BIN
README.md

Binary file not shown.

View File

@@ -11,6 +11,7 @@ let auth0 = null;
async function CheckRedirect() {
const isAuthenticated = await auth0.isAuthenticated();
if (isAuthenticated) {
localStorage.loggedIn = true;
return;
}
@@ -21,6 +22,7 @@ async function CheckRedirect() {
await auth0.handleRedirectCallback();
} catch (e) {
window.alert(e.message || 'authentication error, sorry');
localStorage.loggedIn = false;
Signout();
}
@@ -30,6 +32,9 @@ async function CheckRedirect() {
}
export async function InitAuth0() {
localStorage.loggedIn = false;
localStorage.user = 'Guest';
auth0 = await window.createAuth0Client({
domain: AUTH0CONFIG.domain,
client_id: AUTH0CONFIG.clientId,
@@ -41,7 +46,9 @@ export async function InitAuth0() {
const isAuthenticated = await auth0.isAuthenticated();
if (isAuthenticated) {
const user = await auth0.getUser();
localStorage.user = user.given_name || user.nickname;
NotifyNavbar('login', user);
localStorage.loggedIn = true;
// tell the server about the logon, so that it can make the proper
// entry in the database, if there is for example an address
@@ -52,10 +59,11 @@ export async function InitAuth0() {
method: 'GET',
headers: { Authorization: `Bearer ${token}` },
};
const res = await fetch('/api/auth/login', fetchOptions);
if (!res.ok) {
throw new Error('failed to login with the server');
}
const res = await fetch('/api/auth/login', fetchOptions).then(res => res.json());
console.log(res);
// do stuff
}
}
@@ -80,6 +88,8 @@ export async function LoginSignup() {
}
export async function Signout() {
localStorage.loggedIn = false;
localStorage.user = 'Guest';
await auth0.logout({
returnTo: window.location.origin,
});

View File

@@ -19,6 +19,13 @@ let basketCallback = null;
// TODO: Does the localstorage have a problem with mutual exclusion?
// TODO: Should the basket be persisted to the server?
export function GetBasketItems() {
if (localStorage.getItem('basket') === null || !localStorage.getItem('basket')) {
return;
}
return JSON.parse(localStorage.getItem('basket')).items;
}
export function AddProductToBasket(product, type, amount, brickModifier = 'none') {
if (localStorage.getItem('basket') === null || !localStorage.getItem('basket')) {
localStorage.setItem('basket', JSON.stringify({
@@ -61,46 +68,15 @@ export function RemoveProductFromBasket(product, type, amount, brickModifier = '
product += '~' + brickModifier;
}
if (basket.items[product].quantity > amount) {
basket.items[product].quantity -= amount;
} else {
delete basket.items[product];
}
basket.total -= amount;
localStorage.setItem('basket', JSON.stringify(basket));
if (basketCallback) {
basketCallback();
}
}
export function SetProductInBasket(product, type, amount, brickModifier = 'none') {
if (localStorage.getItem('basket') === null || !localStorage.getItem('basket')) {
return;
}
const basket = JSON.parse(localStorage.getItem('basket'));
if (type === 'brick') {
product += '~' + brickModifier;
}
if (amount === 0) {
delete basket.items[product];
} else {
if (basket.items[product]) {
basket.total -= basket.items[product].quantity;
basket.items[product].quantity = amount;
if (basket.items[product]) {
if (basket.items[product].quantity > amount) {
basket.items[product].quantity -= amount;
} else {
basket.items[product] = {
quantity: amount,
type,
};
delete basket.items[product];
}
}
basket.total += amount;
basket.total -= amount;
localStorage.setItem('basket', JSON.stringify(basket));

View File

@@ -1,4 +1,4 @@
import { AddProductToBasket, SetProductInBasket, RemoveProductFromBasket, GetBasketTotal, GetBasketTotalPrice } from './basket-popout.mjs';
import { GetBasketItems, AddProductToBasket, RemoveProductFromBasket, GetBasketTotal, GetBasketTotalPrice } from './basket-popout.mjs';
import { RegisterComponent, Component } from './components.mjs';
class Basket extends Component {
@@ -39,7 +39,6 @@ class Basket extends Component {
<div class="basket-items-list">
${Object.keys(this.state.items).map((key) => {
const item = this.state.items[key];
console.log(key, item);
const modifier = key.includes('~');
return /* html */`
<div class="basket-item">
@@ -49,13 +48,15 @@ class Basket extends Component {
bigimage="true"
${modifier ? `modifier="${key.split('~')[1]}"` : ''}>
</super-compact-listing-component>
<div class="product-quantity-selector">
<button class="product-quantity-button reduce-quantity" type="button">-</button>
<input class="quantity-input" type="number" value="${item.quantity}" min="0" max="{item.stock}">
<button class="product-quantity-button increase-quantity" type="button">+</button>
<span class="product-quantity">&nbsp;<span class="stock-number">0</span> in stock</span>
</div>
<button class="product-quantity-button remove-quantity" type="button">Remove</button>
<div class="basket-item-content">
<div class="product-quantity-selector">
<button class="product-quantity-button reduce-quantity" type="button">-</button>
<input class="quantity-input" type="number" value="${item.quantity}" min="0" max="{item.stock}">
<button class="product-quantity-button increase-quantity" type="button">+</button>
<span class="product-quantity">&nbsp;<span class="stock-number">0</span> in stock</span>
</div>
<button class="product-quantity-button remove-quantity" type="button">Remove</button>
</div>
</div>
`;
}).join('')}
@@ -65,9 +66,11 @@ class Basket extends Component {
<div class="basket-footer-subtotal">
Subtotal: £<span class="basket-subtotal">0.00</span>
</div>
<button class="basket-footer-button" type="button">Checkout</button>
<button class="basket-footer-button" type="button">Checkout As Guest</button>
</div>
<!-- checkout button -->
<div class="basket-footer-checkout">
<button class="basket-footer-button" type="button">Checkout as ${localStorage.user}</button>
</div>
</div>
`,
style: `
.basket {
@@ -88,15 +91,38 @@ class Basket extends Component {
flex-direction: row;
align-items: center;
}
.basket-item-listing {
font-size: 1.3em;
flex-basis: 65%;
flex-grow: 4;
}
@media (pointer:none), (pointer:coarse), screen and (max-width: 900px) {
.basket-item {
flex-direction: column;
}
.basket-item-listing {
font-size: 1.3em;
width: 100%;
flex-basis: 100%;
flex-grow: 4;
}
}
.basket-item-content {
flex-basis: 35%;
flex-grow: 1;
width: 100%;
margin: 0 auto;
font-size: 1.3em;
display: flex;
flex-direction: row;
justify-content: space-around;
align-items: center;
}
.product-quantity-selector {
flex-basis: 40%;
display: flex;
flex-direction: row;
justify-content: center;
@@ -140,6 +166,44 @@ class Basket extends Component {
text-align: center;
font-size: 1.2em;
}
.basket-footer {
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
padding: 10px;
border-top: 1px solid #ccc;
}
.basket-footer-subtotal {
font-size: 1.5em;
}
.basket-footer-checkout {
flex-basis: 70%;
display: flex;
flex-direction: row;
justify-content: flex-end;
align-items: center;
}
.basket-footer-button {
cursor: pointer;
background-color: #222;
border: none;
color: #F5F6F6;
padding: 0.5em;
margin-left: 1em;
font-size: 1.4em;
transition: all 0.2s ease-in;
}
.basket-footer-button:hover {
background-color: #F5F6F6;
outline: 2px solid #222;
color: #222;
}
`,
};
}
@@ -203,7 +267,6 @@ class Basket extends Component {
const modifier = listing.getAttribute('modifier');
const compositeId = id + (modifier ? `~${modifier}` : '');
const item = this.state.items[compositeId];
console.log(id, modifier, item);
// update the quantity
if (event.target.classList.contains('reduce-quantity')) {
@@ -214,13 +277,12 @@ class Basket extends Component {
if (item.quantity === 0) {
RemoveProductFromBasket(id, item.type, item.quantity, modifier);
this.UpdateSubtotal();
delete this.state.items[compositeId];
return this.setState({
...this.state,
total: GetBasketTotal(),
items: {
...this.state.items,
...GetBasketItems(),
},
});
}
@@ -232,13 +294,12 @@ class Basket extends Component {
} else if (event.target.classList.contains('remove-quantity')) {
RemoveProductFromBasket(id, item.type, item.quantity, modifier);
this.UpdateSubtotal();
delete this.state.items[compositeId];
return this.setState({
...this.state,
total: GetBasketTotal(),
items: {
...this.state.items,
...GetBasketItems(),
},
});
}
@@ -286,23 +347,30 @@ class Basket extends Component {
if (event.target.value > item.stock) {
event.target.value = item.stock;
}
item.quantity = parseInt(event.target.value);
SetProductInBasket(id, item.type, item.quantity, modifier);
this.UpdateSubtotal();
if (item.quantity === 0) {
delete this.state.items[compositeId];
if (parseInt(event.target.value) === 0) {
RemoveProductFromBasket(id, item.type, item.quantity, modifier);
return this.setState({
...this.state,
total: GetBasketTotal(),
items: {
...this.state.items,
...GetBasketItems(),
},
});
}
// if has gone up in quantity then add it
if (item.quantity < event.target.value) {
AddProductToBasket(id, item.type, event.target.value - item.quantity, modifier);
item.quantity = parseInt(event.target.value);
} else if (item.quantity > event.target.value) {
RemoveProductFromBasket(id, item.type, item.quantity - event.target.value, modifier);
item.quantity = parseInt(event.target.value);
}
this.UpdateSubtotal();
// update the total
this.setState({
...this.state,

View File

@@ -7,7 +7,7 @@ import { LoginSignup, Signout } from '../auth.mjs';
let navbarCallback = null;
export function NotifyNavbar(type, user) {
if (navbarCallback && type === 'login') {
navbarCallback.OnLogin(user);
navbarCallback.OnLogin();
}
if (navbarCallback && type === 'logout') {
navbarCallback.OnLogout();
@@ -39,12 +39,12 @@ class NavBar extends Component {
});
}
OnLogin(user) {
OnLogin() {
const account = this.root.querySelector('.account-item');
// doing this with proper dom manipulation wasn't working
account.innerHTML = `
<a class="nav-link" href="#">${user.name}▾</a>
<a class="nav-link" href="#">${localStorage.user}▾</a>
<ul class="sub-nav" >
<li><a class="sub-nav-link" href="#">My Account</a></li>
<li><a class="sub-nav-link logout-button" href="#">Log Out</a></li>

View File

@@ -16535,3 +16535,7 @@ INSERT INTO lego_set_tag (set_id, tag) VALUES ('1010', '1201');
INSERT INTO lego_set_tag (set_id, tag) VALUES ('1010', '323');
INSERT INTO set_descriptor (set_id, brick_id, amount) VALUES ('1010', '95228', '1000');
INSERT INTO lego_set_inventory (set_id, stock, price, new_price, last_updated) VALUES ('1010', '5', '1000.69', '50', now());
-- admin user
INSERT INTO users (id, email, admin, nickname, date_created, date_updated) VALUES ('62686cdead060b0068fe7d38', 'systemadmin@legolog.com', true, 'systemadmin', NOW(), NOW());

View File

@@ -79,11 +79,13 @@ CREATE TABLE IF NOT EXISTS lego_set_inventory (
CREATE TABLE IF NOT EXISTS users (
id VARCHAR (50) NOT NULL PRIMARY KEY,
email text NOT NULL,
first_name text NOT NULL,
last_name text NOT NULL,
address text NOT NULL,
postcode text NOT NULL,
email TEXT NOT NULL,
admin BOOLEAN NOT NULL,
nickname TEXT NOT NULL,
first_name TEXT,
last_name TEXT,
address TEXT,
postcode TEXT,
date_created TIMESTAMP WITHOUT TIME ZONE NOT NULL,
date_updated TIMESTAMP WITHOUT TIME ZONE NOT NULL
);

9920
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -23,7 +23,7 @@ async function Search(fuzzyStrings) {
Logger.Error(dbres.error);
return dbres;
}
await Database.Query('COMMIT TRANSACTION;');
Database.Query('COMMIT TRANSACTION;');
// validate database response
if (dbres.rows.length === 0) {
@@ -89,7 +89,7 @@ async function SumPrices(bricksArr, quantityArray) {
Logger.Error(dbres.error);
return dbres;
}
await Database.Query('COMMIT TRANSACTION;');
Database.Query('COMMIT TRANSACTION;');
// validate database response
if (dbres.rows.length === 0) {
@@ -126,7 +126,7 @@ async function GetBulkBricks(bricksArr) {
Logger.Error(dbres.error);
return dbres;
}
await Database.Query('COMMIT TRANSACTION;');
Database.Query('COMMIT TRANSACTION;');
// validate database response
if (dbres.rows.length === 0) {
@@ -181,7 +181,7 @@ async function GetBrick(brickId) {
return dbres || colDbres;
}
await Database.Query('COMMIT TRANSACTION;');
Database.Query('COMMIT TRANSACTION;');
// validate database response
if (dbres.rows.length === 0) {

View File

@@ -23,7 +23,7 @@ async function Search(fuzzyStrings) {
Logger.Error(dbres.error);
return dbres;
}
await Database.Query('COMMIT TRANSACTION;');
Database.Query('COMMIT TRANSACTION;');
// validate database response
if (dbres.rows.length === 0) {
@@ -99,7 +99,7 @@ async function SumPrices(setsArray, quantityArray) {
Logger.Error(dbres.error);
return dbres;
}
await Database.Query('COMMIT TRANSACTION;');
Database.Query('COMMIT TRANSACTION;');
// validate database response
if (dbres.rows.length === 0) {
@@ -140,7 +140,7 @@ async function GetSet(setId) {
Logger.Error(dbres.error);
return dbres;
}
await Database.Query('COMMIT TRANSACTION;');
Database.Query('COMMIT TRANSACTION;');
// validate database response
if (dbres.rows.length === 0) {
@@ -191,7 +191,7 @@ async function GetSets(page, resPerPage) {
Logger.Error(dbres.error);
return dbres;
}
await Database.Query('COMMIT TRANSACTION;');
Database.Query('COMMIT TRANSACTION;');
const total = parseInt(countRes.rows[0].count);

View File

@@ -0,0 +1,89 @@
const Database = require('../database/database.js');
const Logger = require('../logger.js');
// C
async function CreateUser(id, email, admin, nickname) {
await Database.Query('BEGIN TRANSACTION;');
const dbres = await Database.Query(`
INSERT INTO users (id, email, admin, nickname, date_created, date_updated)
VALUES ($1, $2, $3, $4, NOW(), NOW())
`, [id, email, admin, nickname]).catch(() => {
return {
error: 'Database error',
};
});
if (dbres.error) {
Database.Query('ROLLBACK TRANSACTION;');
Logger.Error(dbres.error);
return {
error: 'Database error',
};
}
Database.Query('COMMIT TRANSACTION;');
return true;
}
// R
async function DoesUserExist(id) {
await Database.Query('BEGIN TRANSACTION;');
const dbres = await Database.Query(`
SELECT id
FROM users
WHERE id = $1
`, [id]).catch(() => {
return {
error: 'Database error',
};
});
if (dbres.error) {
Logger.Error(dbres.error);
return {
error: 'Database error',
};
}
Database.Query('COMMIT TRANSACTION;');
return dbres.rows.length > 0;
}
async function GetUserByID(id) {
await Database.Query('BEGIN TRANSACTION;');
const dbres = await Database.Query(`
SELECT id, email, admin, nickname
FROM users
WHERE id = $1
`, [id]).catch(() => {
return {
error: 'Database error',
};
});
if (dbres.error) {
Logger.Error(dbres.error);
return {
error: 'Database error',
};
}
Database.Query('COMMIT TRANSACTION;');
if (dbres.rows.length === 0) {
return {
error: 'User not found',
long: 'The user you are looking for does not exist',
};
}
return dbres.rows[0];
}
// U
// D
module.exports = {
CreateUser,
DoesUserExist,
GetUserByID,
};

View File

@@ -32,18 +32,6 @@ function getFormatedTimeString() {
// levelSource is the level that the source will log at ie, if levelSource is
// LEVEL_WARN, it will only log if the current level is at or above LEVEL_WARN.
function internalLog(type, message, cConsoleColour, levelSource) {
message = [message].map(x => {
if (typeof x === 'object') {
return JSON.stringify(x);
}
if (Array.isArray(x)) {
return x.join(' ');
}
return x;
}).join(' ');
if (Options.logToConsole && (Options.logLevel <= levelSource)) {
console.log(`${getFormatedTimeString()} [${cConsoleColour(type)}] ${message}`);
}
@@ -60,16 +48,15 @@ function internalLog(type, message, cConsoleColour, levelSource) {
}
}
const Info = (...messages) => internalLog('INFO', messages, clc.greenBright, LEVEL_INFO);
const Warn = (...messages) => internalLog('WARN', messages, clc.yellowBright, LEVEL_WARN);
const Error = (...messages) => internalLog('ERROR', messages, clc.redBright, LEVEL_STICK);
const Panic = (...messages) => internalLog('PANIC', messages, clc.bgRedBright, LEVEL_STICK);
const Debug = (...messages) => internalLog('DEBUG', messages, clc.cyanBright, LEVEL_DEBUG);
const Info = (...messages) => internalLog('INFO', messages.join(' '), clc.greenBright, LEVEL_INFO);
const Warn = (...messages) => internalLog('WARN', messages.join(' '), clc.yellowBright, LEVEL_WARN);
const Error = (...messages) => internalLog('ERROR', messages.join(' '), clc.redBright, LEVEL_STICK);
const Panic = (...messages) => internalLog('PANIC', messages.join(' '), clc.bgRedBright, LEVEL_STICK);
const Debug = (...messages) => internalLog('DEBUG', messages.join(' '), clc.cyanBright, LEVEL_DEBUG);
const Module = (module, ...messages) => internalLog(`MODULE [${module}]`, ` ${messages.join(' ')}`, clc.blue, LEVEL_INFO);
const Database = (...messages) => internalLog('PSQL', `[DB] ${messages.join(' ')}`, clc.blue, LEVEL_INFO);
const ExpressLogger = (req, res, next) => {
// don't spam the console with 1000000 bloody requests for thumbnails
if (req.originalUrl.startsWith('/api/cdn/')) {
if (req.originalUrl.startsWith('/api/cdn')) {
return next();
}
internalLog('HTTP', `[${req.method}] ${req.originalUrl} FROM ${req.headers['x-forwarded-for'] || req.socket.remoteAddress}`, clc.magenta, LEVEL_VERBOSE);

View File

@@ -1,5 +1,7 @@
// Loosely based on https://github.com/portsoc/auth0-example/blob/main/stages/6/server/auth0-helpers.js
// but better (obviously)
const Controller = require('../controllers/user-controller.js');
const Logger = require('../logger.js');
const Axios = require('axios');
@@ -54,11 +56,37 @@ async function Auth0GetUser(req) {
}
async function Login(req, res) {
// tell the user all is well
res.send('Authenticated user: ' + req.auth.payload.sub);
// tell the database the user is new if they don't already exist
const user = await Auth0GetUser(req);
const id = user.sub.split('|')[1];
const doesExist = await Controller.GetUserByID(id);
if (!doesExist.error) {
res.send({
user: {
id,
nickname: doesExist.nickname,
email: doesExist.email,
admin: doesExist.admin,
},
});
return;
}
const name = user.nickname;
const email = user.email;
await Controller.CreateUser(id, email, false, name);
res.send({
user: {
id,
nickname: name,
email,
admin: false,
},
});
}
module.exports = {

View File

@@ -38,15 +38,13 @@ async function CalculateBasketPrice(req, res) {
}
}
Logger.Debug(`Set list: ${setList} Brick list: ${brickList}`);
let setSubtotal = setList > 0
let setSubtotal = setList.length > 0
? await SetController.SumPrices(setList, setQuantities)
: 0;
let brickSubtotal = brickList > 0
let brickSubtotal = brickList.length > 0
? await BrickController.SumPrices(brickList, brickQuantities)
: 0;
if (setSubtotal.error) setSubtotal = 0;
if (brickSubtotal.error) brickSubtotal = 0;

View File

@@ -17,7 +17,7 @@ async function Get(req, res) {
async function Featured(req, res) {
// query all sets and return all of them
const { sets } = await Controller.GetSets(0, 10);
const { sets } = await Controller.GetSets(0, 9);
if (sets.error) {
res.send(JSON.stringify(sets));