order pages all done and stock pages coming
Former-commit-id: da90fc25deecf2843a6269d877387ce07fdb2728
This commit is contained in:
@@ -49,7 +49,6 @@ export async function InitAuth0() {
|
||||
if (isAuthenticated) {
|
||||
const user = await auth0.getUser();
|
||||
localStorage.setItem('user', user.given_name || user.nickname);
|
||||
NotifyNavbar('login', user);
|
||||
localStorage.setItem('loggedIn', true);
|
||||
ready = true;
|
||||
|
||||
@@ -67,6 +66,7 @@ export async function InitAuth0() {
|
||||
const res = await fetch('/api/auth/login', fetchOptions).then(res => res.json());
|
||||
|
||||
localStorage.setItem('admin', res.user.admin);
|
||||
NotifyNavbar('login', user);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
70
client/public/components/css/stock-audit.css
Normal file
70
client/public/components/css/stock-audit.css
Normal file
@@ -0,0 +1,70 @@
|
||||
.stock-editor {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.stock-header {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
flex-direction: column;
|
||||
margin-top: 20px;
|
||||
font-size: 2em;
|
||||
border-bottom: 1px solid #ccc;
|
||||
}
|
||||
|
||||
.collapsible-menu {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
margin-top: 1em;
|
||||
}
|
||||
|
||||
.menu-header {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-end;
|
||||
font-size: 1.5em;
|
||||
font-weight: bold;
|
||||
cursor: pointer;
|
||||
border-bottom: #1A1A1A solid 1px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.menu-header-arrow {
|
||||
transform: rotate(-180deg);
|
||||
margin-left: 0.5em;
|
||||
transition: transform 0.2s ease-in-out;
|
||||
}
|
||||
|
||||
/* rotate the arrow down when the details are open */
|
||||
.menu-header-arrow-down {
|
||||
margin-left: 0.5em;
|
||||
transform: rotate(-90deg);
|
||||
}
|
||||
|
||||
.menu-content {
|
||||
max-width: fit-content;
|
||||
display: none;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
min-width: 0;
|
||||
margin-top: 1em;
|
||||
}
|
||||
|
||||
.details-open {
|
||||
display: flex;
|
||||
position: static;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.product-details-content-item {
|
||||
padding-top: 0.6em;
|
||||
}
|
||||
|
||||
.menu-content-item {
|
||||
width: 100%;
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
@@ -54,8 +54,8 @@ class NavBar extends Component {
|
||||
<a class="nav-link" href="#">${localStorage.user}▾</a>
|
||||
<ul class="sub-nav" >
|
||||
<li><a class="sub-nav-link" href="/orders">My Orders</a></li>
|
||||
<li><a class="sub-nav-link" href="">Add or Remove Stock</a></li>
|
||||
<li><a class="sub-nav-link" href="">Review Open Orders</a></li>
|
||||
<li><a class="sub-nav-link" href="/staff/stock">Add or Remove Stock</a></li>
|
||||
<li><a class="sub-nav-link" href="/staff/revieworders">Review Open Orders</a></li>
|
||||
<li><a class="sub-nav-link logout-button" href="#">Log Out</a></li>
|
||||
</ul>
|
||||
`;
|
||||
|
||||
@@ -9,30 +9,48 @@ class OrderList extends Component {
|
||||
}
|
||||
|
||||
async OnMount() {
|
||||
const doStaffList = this.state.staff !== undefined;
|
||||
|
||||
const options = {
|
||||
method: 'GET',
|
||||
headers: { Authorization: `Bearer ${await Auth.GetToken()}` },
|
||||
};
|
||||
if (doStaffList) {
|
||||
const res = await fetch('/api/auth/staff/orders', options).then(res => res.json());
|
||||
|
||||
const res = await fetch('/api/auth/orders', options).then(res => res.json());
|
||||
this.setState({
|
||||
...this.getState,
|
||||
orders: res.data,
|
||||
title: 'Orders left to fufill',
|
||||
none: 'All done :)',
|
||||
}, false);
|
||||
} else {
|
||||
const res = await fetch('/api/auth/orders', options).then(res => res.json());
|
||||
|
||||
console.log(res);
|
||||
|
||||
this.setState({
|
||||
...this.getState,
|
||||
orders: res.data,
|
||||
}, false);
|
||||
console.log(this.state);
|
||||
this.setState({
|
||||
...this.getState,
|
||||
orders: res.data,
|
||||
title: 'Your Orders',
|
||||
none: 'You have no orders',
|
||||
}, false);
|
||||
}
|
||||
}
|
||||
|
||||
Render() {
|
||||
return {
|
||||
template: /* html */`
|
||||
<div class="order-header">
|
||||
<span class="order-header-title">Your Orders</span>
|
||||
<span class="order-header-title">{this.state.title}</span>
|
||||
</div>
|
||||
|
||||
<div class="orders-list-body">
|
||||
${this.state.orders.length === 0
|
||||
? /* html */`
|
||||
<div class="orders-list-item">
|
||||
<span class="order-list-item-header-title">{this.state.none}</span>
|
||||
</div>
|
||||
`
|
||||
: ''}
|
||||
${this.state.orders.map(order => /* html */`
|
||||
<div class="orders-list-item">
|
||||
<a href="/orders/order?id=${order.id}"><div class="order-list-item">
|
||||
@@ -43,6 +61,12 @@ class OrderList extends Component {
|
||||
<div class="order-list-item-body">
|
||||
<span class="order-list-item-body-item-title">Paid: £${parseFloat(order.subtotal_paid).toFixed(2)}</span>
|
||||
<span class="order-list-item-body-item-title">Shipped? ${order.shipped ? 'Yes' : 'No'}</span>
|
||||
${this.state.staff !== undefined
|
||||
? /* html */`
|
||||
<span class="order-list-item-ship">Posted? <input type="checkbox" class="order-list-item-shipped-checker" ${order.shipped ? 'checked disabled' : ''} /></span>
|
||||
<span class="order-list-item-done">Done & Recieved? <input type="checkbox" class="order-list-item-done-checker" ${order.recieved ? 'checked disabled' : ''} /></span>
|
||||
`
|
||||
: ''}
|
||||
</div>
|
||||
</div></a>
|
||||
</div>
|
||||
@@ -54,6 +78,69 @@ class OrderList extends Component {
|
||||
}
|
||||
|
||||
OnRender() {
|
||||
this.root.querySelectorAll('.order-list-item-shipped-checker').forEach(checkbox => {
|
||||
checkbox.addEventListener('click', async (event) => {
|
||||
const orderID = checkbox.parentElement.parentElement.parentElement.parentElement.parentElement.querySelector('.order-list-item-header-title').innerText.split('#')[1];
|
||||
const options = {
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `Bearer ${await Auth.GetToken()}`,
|
||||
},
|
||||
body: JSON.stringify({
|
||||
status: {
|
||||
shipped: true,
|
||||
},
|
||||
}),
|
||||
};
|
||||
const orderUpdate = await fetch(`/api/auth/staff/order/${orderID}`, options).then(res => res.json());
|
||||
|
||||
const getOptions = {
|
||||
method: 'GET',
|
||||
headers: { Authorization: `Bearer ${await Auth.GetToken()}` },
|
||||
};
|
||||
const res = await fetch('/api/auth/staff/orders', getOptions).then(res => res.json());
|
||||
|
||||
this.setState({
|
||||
...this.getState,
|
||||
orders: res.data,
|
||||
title: 'Orders left to fufill',
|
||||
none: 'All done :)',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
this.root.querySelectorAll('.order-list-item-done-checker').forEach(checkbox => {
|
||||
checkbox.addEventListener('click', async (event) => {
|
||||
const orderID = checkbox.parentElement.parentElement.parentElement.parentElement.parentElement.querySelector('.order-list-item-header-title').innerText.split('#')[1];
|
||||
const options = {
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `Bearer ${await Auth.GetToken()}`,
|
||||
},
|
||||
body: JSON.stringify({
|
||||
status: {
|
||||
completed: true,
|
||||
},
|
||||
}),
|
||||
};
|
||||
const orderUpdate = await fetch(`/api/auth/staff/order/${orderID}`, options).then(res => res.json());
|
||||
|
||||
const getOptions = {
|
||||
method: 'GET',
|
||||
headers: { Authorization: `Bearer ${await Auth.GetToken()}` },
|
||||
};
|
||||
const res = await fetch('/api/auth/staff/orders', getOptions).then(res => res.json());
|
||||
|
||||
this.setState({
|
||||
...this.getState,
|
||||
orders: res.data,
|
||||
title: 'Orders left to fufill',
|
||||
none: 'All done :)',
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ class Order extends Component {
|
||||
}
|
||||
|
||||
async OnMount() {
|
||||
// get order id from search param
|
||||
// get order id from search param
|
||||
const query = new URLSearchParams(window.location.search);
|
||||
const id = query.get('id');
|
||||
|
||||
@@ -20,7 +20,6 @@ class Order extends Component {
|
||||
...res,
|
||||
}, false);
|
||||
|
||||
console.log(this.state);
|
||||
localStorage.setItem('viewing-order', JSON.stringify({ items: res.items }));
|
||||
}
|
||||
|
||||
@@ -86,22 +85,22 @@ class Order extends Component {
|
||||
</div>
|
||||
<div class="order-track-step">
|
||||
<span class="order-track-status">
|
||||
<div class="order-track-step-icon"></div>
|
||||
<div class="order-track-step-line"></div>
|
||||
<div class="order-track-step-icon ${this.state.shipped ? 'completed' : ''}"></div>
|
||||
<div class="order-track-step-line ${this.state.shipped ? 'completed' : ''}"></div>
|
||||
</span>
|
||||
<div class="order-track-text">
|
||||
<span class="order-body-status-title">Posted</span>
|
||||
<span class="when"></span>
|
||||
<span class="when">${this.state.shipped ? new Date(this.state.date_shipped).toDateString() : ''}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="order-track-step">
|
||||
<span class="order-track-status">
|
||||
<div class="order-track-step-icon"></div>
|
||||
<div class="order-track-step-line"></div>
|
||||
<div class="order-track-step-icon ${this.state.recieved ? 'completed' : ''}"></div>
|
||||
<div class="order-track-step-line ${this.state.recieved ? 'completed' : ''}"></div>
|
||||
</span>
|
||||
<div class="order-track-text">
|
||||
<span class="order-body-status-title">Delivered</span>
|
||||
<span class="when"></span>
|
||||
<span class="when">${this.state.recieved ? new Date(this.state.date_recieved).toDateString() : ''}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -118,7 +117,6 @@ class Order extends Component {
|
||||
}
|
||||
|
||||
OnRender() {
|
||||
// todo: add order tracking, the data is already there
|
||||
}
|
||||
|
||||
OnUnMount() {
|
||||
|
||||
72
client/public/components/stock-audit.mjs
Normal file
72
client/public/components/stock-audit.mjs
Normal file
@@ -0,0 +1,72 @@
|
||||
import { RegisterComponent, Component, SideLoad } from './components.mjs';
|
||||
|
||||
class StockEditor extends Component {
|
||||
static __IDENTIFY() { return 'stock-editor'; }
|
||||
|
||||
constructor() {
|
||||
super(StockEditor);
|
||||
}
|
||||
|
||||
Render() {
|
||||
return {
|
||||
template: /* html */`
|
||||
<div class="stock-editor">
|
||||
<div class="stock-header">Stock Editor</div>
|
||||
<div class="collapsible-menu">
|
||||
<div class="menu-header">
|
||||
<span class="menu-header-text">Remove Item</span>
|
||||
<img class="menu-header-arrow" src="/res/back-arrow.svg" height="30em" alt="down-arrow">
|
||||
</div>
|
||||
<div class="menu-content">
|
||||
<div class="menu-content-item">
|
||||
<span class="menu-content-item-text">Remove Item</span>
|
||||
<input class="menu-content-item-id-input" type="text" placeholder="Item ID (1010)">
|
||||
<input class="menu-content-item-type-input" type="text" placeholder="Item Type (brick/set)">
|
||||
<button class="menu-content-item-button stock-lookup-button">Lookup</button>
|
||||
<div id="remove-preview">
|
||||
<div class="preview-text">
|
||||
<span class="preview-text-title">Preview</span>
|
||||
<super-compact-listing-component class="stock-remove-preview"></super-compact-listing-component>
|
||||
</div>
|
||||
<button class="menu-content-item-button remove-stock-button">Remove</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="collapsible-menu">
|
||||
<div class="menu-header">
|
||||
<span class="menu-header-text">Add Item</span>
|
||||
<img class="menu-header-arrow" src="/res/back-arrow.svg" height="30em" alt="down-arrow">
|
||||
</div>
|
||||
<div class="menu-content">
|
||||
</div>
|
||||
</div>
|
||||
`,
|
||||
style: SideLoad('/components/css/stock-audit.css'),
|
||||
};
|
||||
}
|
||||
|
||||
OnRender() {
|
||||
const collapseButton = this.root.querySelectorAll('.menu-header');
|
||||
|
||||
collapseButton.forEach(el => el.addEventListener('click', (e) => {
|
||||
const parent = e.path[2].querySelector('.collapsible-menu') ? e.path[1] : e.path[2];
|
||||
const collapseContent = parent.querySelector('.menu-content');
|
||||
const collapseArrow = parent.querySelector('.menu-header-arrow');
|
||||
collapseContent.classList.toggle('details-open');
|
||||
collapseArrow.classList.toggle('menu-header-arrow-down');
|
||||
}));
|
||||
|
||||
// remove
|
||||
const removeStockLookup = this.root.querySelector('.stock-lookup-button');
|
||||
removeStockLookup.addEventListener('click', () => {
|
||||
const preview = this.root.querySelector('.stock-remove-preview');
|
||||
const id = this.root.querySelector('.menu-content-item-id-input').value;
|
||||
const type = this.root.querySelector('.menu-content-item-type-input').value;
|
||||
|
||||
preview.setAttribute('id', id);
|
||||
preview.setAttribute('type', type);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
RegisterComponent(StockEditor);
|
||||
@@ -8,7 +8,7 @@ class SuperCompactProductListing extends Component {
|
||||
super(SuperCompactProductListing);
|
||||
}
|
||||
|
||||
async OnMount() {
|
||||
async Update() {
|
||||
if (!this.state.name || !this.state.price) {
|
||||
const product = (await fetch(`/api/${this.state.type}/${this.state.id}`).then(res => res.json())).data;
|
||||
const name = product.name;
|
||||
@@ -28,7 +28,12 @@ class SuperCompactProductListing extends Component {
|
||||
colours,
|
||||
quantity: product.quantity,
|
||||
}, false);
|
||||
} else if (this.state.tags) {
|
||||
}
|
||||
|
||||
if (this.state.tags) {
|
||||
if (this.state.tags.length >= 1) {
|
||||
return;
|
||||
}
|
||||
const tags = JSON.parse(this.state.tags);
|
||||
this.setState({
|
||||
...this.getState,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<html>
|
||||
<head>
|
||||
<title>LegoLog Basket</title>
|
||||
<title>LegoLog Your Orders</title>
|
||||
<meta name="viewport">
|
||||
<link rel="icon" type="image/svg+xml" href="/res/favicon.svg">
|
||||
<link rel="stylesheet" type="text/css" href="/global.css">
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<html>
|
||||
<head>
|
||||
<title>LegoLog Basket</title>
|
||||
<title>LegoLog Order</title>
|
||||
<meta name="viewport">
|
||||
<link rel="icon" type="image/svg+xml" href="/res/favicon.svg">
|
||||
<link rel="stylesheet" type="text/css" href="/global.css">
|
||||
|
||||
37
client/public/staff/revieworders/index.html
Normal file
37
client/public/staff/revieworders/index.html
Normal file
@@ -0,0 +1,37 @@
|
||||
<html>
|
||||
<head>
|
||||
<title>LegoLog Logistical Fufillment Service</title>
|
||||
<meta name="viewport">
|
||||
<link rel="icon" type="image/svg+xml" href="/res/favicon.svg">
|
||||
<link rel="stylesheet" type="text/css" href="/global.css">
|
||||
|
||||
<!-- Fonts -->
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Londrina+Solid&display=swap" rel="stylesheet">
|
||||
<link href="https://fonts.googleapis.com/css2?family=Josefin+Sans&display=swap" rel="stylesheet">
|
||||
|
||||
<!-- Auth0 - a library for authentication -->
|
||||
<script src="https://cdn.auth0.com/js/auth0-spa-js/1.13/auth0-spa-js.production.js"></script>
|
||||
|
||||
<!-- Components used on this page - they must be included to work -->
|
||||
<script type="module" src="/components/components.mjs"></script>
|
||||
<script type="module" src="/components/navbar.mjs"></script>
|
||||
<script type="module" src="/components/search.mjs"></script>
|
||||
<script type="module" src="/components/order-list.mjs"></script>
|
||||
<script type="module" src="/components/basket-popout.mjs"></script>
|
||||
<script type="module" src="/components/immutable-list.mjs"></script>
|
||||
<script type="module" src="/components/accessability-popout.mjs"></script>
|
||||
<script type="module" src="/components/notificationbar.mjs"></script>
|
||||
<script type="module" src="/components/tag.mjs"></script>
|
||||
<script type="module" src="/components/super-compact-listing.mjs"></script>
|
||||
|
||||
<script type="module" src="/index.mjs"></script>
|
||||
</head>
|
||||
<body>
|
||||
<notificationbar-component></notificationbar-component>
|
||||
<navbar-component></navbar-component>
|
||||
<limited-margin>
|
||||
<order-list-component staff="true"></order-list-component>
|
||||
</limited-margin>
|
||||
</body>
|
||||
37
client/public/staff/stock/index.html
Normal file
37
client/public/staff/stock/index.html
Normal file
@@ -0,0 +1,37 @@
|
||||
<html>
|
||||
<head>
|
||||
<title>LegoLog Stock Editor</title>
|
||||
<meta name="viewport">
|
||||
<link rel="icon" type="image/svg+xml" href="/res/favicon.svg">
|
||||
<link rel="stylesheet" type="text/css" href="/global.css">
|
||||
|
||||
<!-- Fonts -->
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Londrina+Solid&display=swap" rel="stylesheet">
|
||||
<link href="https://fonts.googleapis.com/css2?family=Josefin+Sans&display=swap" rel="stylesheet">
|
||||
|
||||
<!-- Auth0 - a library for authentication -->
|
||||
<script src="https://cdn.auth0.com/js/auth0-spa-js/1.13/auth0-spa-js.production.js"></script>
|
||||
|
||||
<!-- Components used on this page - they must be included to work -->
|
||||
<script type="module" src="/components/components.mjs"></script>
|
||||
<script type="module" src="/components/navbar.mjs"></script>
|
||||
<script type="module" src="/components/search.mjs"></script>
|
||||
<script type="module" src="/components/stock-audit.mjs"></script>
|
||||
<script type="module" src="/components/basket-popout.mjs"></script>
|
||||
<script type="module" src="/components/immutable-list.mjs"></script>
|
||||
<script type="module" src="/components/accessability-popout.mjs"></script>
|
||||
<script type="module" src="/components/notificationbar.mjs"></script>
|
||||
<script type="module" src="/components/tag.mjs"></script>
|
||||
<script type="module" src="/components/super-compact-listing.mjs"></script>
|
||||
|
||||
<script type="module" src="/index.mjs"></script>
|
||||
</head>
|
||||
<body>
|
||||
<notificationbar-component></notificationbar-component>
|
||||
<navbar-component></navbar-component>
|
||||
<limited-margin>
|
||||
<stock-editor-component></stock-editor-component>
|
||||
</limited-margin>
|
||||
</body>
|
||||
33
docs/API.md
33
docs/API.md
@@ -10,27 +10,30 @@ automatically every request
|
||||
|
||||
| Type | Route | Queries | Auth? | Notes |
|
||||
| --- | --- | --- | - | --- |
|
||||
| GET | /api/special/ | | ❌ | |
|
||||
| GET | /api/search/ | query (q), page | ❌ | Query endpoint |
|
||||
| GET | /api/sets/featured | page | ❌ | Query endpoint |
|
||||
| GET | /api/brick/:id | | ❌ | |
|
||||
| POST | /api/bulk/brick | array | ❌ | POST due to bulk nature |
|
||||
| GET | /api/set/:id | | ❌ | |
|
||||
| GET | /api/cdn/:id | | ❌ | |
|
||||
| GET | /api/basket/price/ | | ❌ | |
|
||||
| GET | /api/discount/ | offer code | ❌ | |
|
||||
| POST | /api/order/ | | ❌ | IF user is authenticated, auth/bearer will be sent and done manually without middleware |
|
||||
| GET | /api/auth/order/:id | | ❌ | Security By Obscurity |
|
||||
| GET | /api/auth/login/ | | ✔️ | |
|
||||
| GET | /api/auth/orders/ | | ✔️ | |
|
||||
| GET | /api/special/ | | ❌ | |
|
||||
| GET | /api/search/ | query (q), page | ❌ | Query endpoint |
|
||||
| GET | /api/sets/featured | page | ❌ | Query endpoint |
|
||||
| GET | /api/brick/:id | | ❌ | |
|
||||
| POST | /api/bulk/brick | array | ❌ | POST due to bulk nature |
|
||||
| GET | /api/set/:id | | ❌ | |
|
||||
| GET | /api/cdn/:id | | ❌ | |
|
||||
| GET | /api/basket/price/ | | ❌ | |
|
||||
| GET | /api/discount/ | offer code | ❌ | |
|
||||
| POST | /api/order/ | | ❌ | IF user is authenticated, auth/bearer will be sent and done manually without middleware |
|
||||
| GET | /api/auth/order/:id | | ❌ | Security By Obscurity |
|
||||
| GET | /api/auth/login/ | | ✔️ | |
|
||||
| GET | /api/auth/orders/ | | ✔️ | |
|
||||
| GET | /api/auth/staff/orders/ | | ✔️ | All unshipped orders |
|
||||
| PUT | /api/auth/staff/order/:id | | ✔️ | Update order to shipped, recieved (carrier) |
|
||||
| PUT | /api/auth/staff/stock/:type/:id | | ✔️ | Update stock on item |
|
||||
| POST | /api/auth/staff/stock/ | | ✔️ | Add item to inventory |
|
||||
| DEL | /api/auth/staff/stock/:type/:id | | ✔️ | Remove item from inventory |
|
||||
|
||||
Query endpoints do not return the full data on a brick/set, they return
|
||||
a subset for product listing pages
|
||||
|
||||
## Query structure
|
||||
|
||||
## Query parameters
|
||||
|
||||
For all endpoints that query, the following parameters are supported:
|
||||
|
||||
q: string to search for (fuzzy)
|
||||
|
||||
@@ -54,6 +54,7 @@ function LevenshteinDistance(s, t) {
|
||||
// TODO: get this working properly
|
||||
|
||||
function SanatiseQuery(query) {
|
||||
if (!query) return '';
|
||||
query = query.trim();
|
||||
query = query.replace(/[^a-zA-Z0-9,&/\s]/g, '');
|
||||
query = escape(query);
|
||||
|
||||
@@ -115,6 +115,7 @@ async function GetOrdersByUser(userId) {
|
||||
discount, date_placed, shipped
|
||||
FROM order_log
|
||||
WHERE order_log.user_id = $1
|
||||
ORDER BY date_placed DESC
|
||||
`, [userId]).catch(() => {
|
||||
return {
|
||||
error: 'Database error',
|
||||
@@ -133,14 +134,76 @@ async function GetOrdersByUser(userId) {
|
||||
return result;
|
||||
}
|
||||
|
||||
async function GetUnFinishedOrders() {
|
||||
await Database.Query('BEGIN TRANSACTION;');
|
||||
const dbres = await Database.Query(`
|
||||
SELECT order_log.id, order_log.user_id, subtotal_paid,
|
||||
date_placed, shipped, recieved
|
||||
FROM order_log
|
||||
WHERE order_log.recieved = FALSE
|
||||
ORDER BY date_placed DESC
|
||||
`).catch(() => {
|
||||
return {
|
||||
error: 'Database error',
|
||||
};
|
||||
});
|
||||
if (dbres.error) {
|
||||
Database.Query('ROLLBACK TRANSACTION;');
|
||||
Logger.Error(dbres.error);
|
||||
return {
|
||||
error: 'Database error',
|
||||
};
|
||||
}
|
||||
Database.Query('COMMIT TRANSACTION;');
|
||||
|
||||
const result = dbres.rows;
|
||||
return result;
|
||||
}
|
||||
|
||||
// U
|
||||
|
||||
async function OrderShipped(orderid) {
|
||||
|
||||
await Database.Query('BEGIN TRANSACTION;');
|
||||
const dbres = await Database.Query(`
|
||||
UPDATE order_log
|
||||
SET shipped = TRUE, date_shipped = NOW()
|
||||
WHERE id = $1
|
||||
`, [orderid]).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;
|
||||
}
|
||||
|
||||
async function OrderRecieved(orderid) {
|
||||
|
||||
await Database.Query('BEGIN TRANSACTION;');
|
||||
const dbres = await Database.Query(`
|
||||
UPDATE order_log
|
||||
SET recieved = TRUE, date_recieved = NOW()
|
||||
WHERE id = $1
|
||||
`, [orderid]).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;
|
||||
}
|
||||
|
||||
// D
|
||||
@@ -149,6 +212,7 @@ module.exports = {
|
||||
NewOrder,
|
||||
GetOrderById,
|
||||
GetOrdersByUser,
|
||||
GetUnFinishedOrders,
|
||||
OrderShipped,
|
||||
OrderRecieved,
|
||||
};
|
||||
|
||||
@@ -31,6 +31,9 @@ function Init() {
|
||||
Server.App.post('/api/auth/order/', Auth0.JWTMiddleware, Order.ProcessNew);
|
||||
Server.App.get('/api/auth/orders/', Auth0.JWTMiddleware, Order.GetOrders);
|
||||
|
||||
Server.App.get('/api/auth/staff/orders/', Auth0.JWTMiddleware, Auth0.AdminOnlyEndpoint, Order.GetUnFinishedOrders);
|
||||
Server.App.put('/api/auth/staff/order/:id', Auth0.JWTMiddleware, Auth0.AdminOnlyEndpoint, Order.UpdateOrderStatus);
|
||||
|
||||
Logger.Module('API', 'API Routes Initialized');
|
||||
}
|
||||
|
||||
|
||||
@@ -13,6 +13,10 @@ const AUTH0CONFIG = {
|
||||
audience: 'localhost:8080/api',
|
||||
};
|
||||
|
||||
// Auth0 was rate limiting me a LOT, so I'm going to use a cache to
|
||||
// prevent that from happening again
|
||||
const Auth0UserCache = [];
|
||||
|
||||
const JWTChecker = OAuth2JWTBearer.auth({
|
||||
audience: AUTH0CONFIG.audience,
|
||||
issuerBaseURL: `https://${AUTH0CONFIG.domain}`,
|
||||
@@ -33,6 +37,25 @@ function JWTMiddleware(req, res, next) {
|
||||
});
|
||||
}
|
||||
|
||||
async function AdminOnlyEndpoint(req, res, next) {
|
||||
const user = await Auth0GetUser(req);
|
||||
if (!user) {
|
||||
return res.send({
|
||||
error: 'No user found',
|
||||
});
|
||||
}
|
||||
|
||||
const localUser = await Controller.GetUserByID(user.sub.split('|')[1]);
|
||||
|
||||
if (!localUser.admin) {
|
||||
return res.send({
|
||||
error: 'Unauthorized',
|
||||
});
|
||||
}
|
||||
|
||||
next();
|
||||
}
|
||||
|
||||
async function Auth0GetUser(req) {
|
||||
if (!req.auth) {
|
||||
return null;
|
||||
@@ -40,14 +63,21 @@ async function Auth0GetUser(req) {
|
||||
|
||||
if (!req.auth || !req.auth.token) return null;
|
||||
|
||||
const token = req.auth.token;
|
||||
if (Auth0UserCache[token]) {
|
||||
return Auth0UserCache[token];
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await Axios.get(`https://${AUTH0CONFIG.domain}/userinfo`, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
authorization: `Bearer ${req.auth.token}`,
|
||||
authorization: `Bearer ${token}`,
|
||||
},
|
||||
});
|
||||
|
||||
Auth0UserCache[token] = response.data;
|
||||
|
||||
return response.data;
|
||||
} catch (err) {
|
||||
Logger.Error('error getting auth profile', req.auth, err);
|
||||
@@ -58,6 +88,11 @@ async function Auth0GetUser(req) {
|
||||
async function Login(req, res) {
|
||||
// tell the database the user is new if they don't already exist
|
||||
const user = await Auth0GetUser(req);
|
||||
if (!user) {
|
||||
return res.send({
|
||||
error: 'No user found',
|
||||
});
|
||||
}
|
||||
|
||||
const id = user.sub.split('|')[1];
|
||||
|
||||
@@ -91,6 +126,7 @@ async function Login(req, res) {
|
||||
|
||||
module.exports = {
|
||||
JWTMiddleware,
|
||||
AdminOnlyEndpoint,
|
||||
Auth0GetUser,
|
||||
Login,
|
||||
};
|
||||
|
||||
@@ -115,7 +115,7 @@ async function ProcessNew(req, res) {
|
||||
discount: 0,
|
||||
};
|
||||
if (discountCode !== null) {
|
||||
const sanatisedCode = ControllerMaster.SanatiseQuery(req.query.code);
|
||||
const sanatisedCode = ControllerMaster.SanatiseQuery(discountCode);
|
||||
|
||||
discount = await MiscController.GetDiscount(sanatisedCode);
|
||||
|
||||
@@ -188,8 +188,76 @@ async function GetOrders(req, res) {
|
||||
});
|
||||
}
|
||||
|
||||
async function GetUnFinishedOrders(req, res) {
|
||||
const orders = await OrderController.GetUnFinishedOrders();
|
||||
|
||||
if (orders.error) {
|
||||
return res.send({
|
||||
error: orders.error,
|
||||
});
|
||||
}
|
||||
|
||||
return res.send({
|
||||
data: orders,
|
||||
});
|
||||
}
|
||||
|
||||
async function UpdateOrderStatus(req, res) {
|
||||
const orderId = req.params.id;
|
||||
const status = req.body.status;
|
||||
|
||||
if (!orderId) {
|
||||
return res.send({
|
||||
error: 'No order id in request',
|
||||
});
|
||||
}
|
||||
|
||||
if (!status) {
|
||||
return res.send({
|
||||
error: 'No status in request',
|
||||
});
|
||||
}
|
||||
|
||||
const shipped = status.shipped;
|
||||
if (!shipped) {
|
||||
const completed = status.completed;
|
||||
if (!completed) {
|
||||
return res.send({
|
||||
error: 'No status in request',
|
||||
});
|
||||
}
|
||||
const orderRecieved = await OrderController.OrderRecieved(orderId);
|
||||
if (orderRecieved.error) {
|
||||
return res.send({
|
||||
error: orderRecieved.error,
|
||||
});
|
||||
}
|
||||
|
||||
return res.send({
|
||||
data: {
|
||||
success: true,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
const orderShipped = await OrderController.OrderShipped(orderId);
|
||||
if (orderShipped.error) {
|
||||
return res.send({
|
||||
error: orderShipped.error,
|
||||
});
|
||||
}
|
||||
|
||||
return res.send({
|
||||
data: {
|
||||
success: true,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
ProcessNew,
|
||||
GetOrder,
|
||||
GetOrders,
|
||||
GetUnFinishedOrders,
|
||||
UpdateOrderStatus,
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user