i think i'm done?
Former-commit-id: 25c3bd71ee177a9926d2aee8b02deadebccc4286
This commit is contained in:
@@ -25,6 +25,23 @@ export function ClearBasket() {
|
|||||||
localStorage.removeItem('basket');
|
localStorage.removeItem('basket');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function GetItemAmountBasket(product) {
|
||||||
|
if (localStorage.getItem('basket') === null || !localStorage.getItem('basket')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const basket = JSON.parse(localStorage.getItem('basket'));
|
||||||
|
|
||||||
|
let accumilator = 0;
|
||||||
|
|
||||||
|
for (const [item, value] of Object.entries(basket.items)) {
|
||||||
|
if (item.includes(product)) {
|
||||||
|
accumilator += value.quantity;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return accumilator;
|
||||||
|
}
|
||||||
|
|
||||||
export function AddProductToBasket(product, type, amount, brickModifier = 'none') {
|
export function AddProductToBasket(product, type, amount, brickModifier = 'none') {
|
||||||
if (localStorage.getItem('basket') === null || !localStorage.getItem('basket')) {
|
if (localStorage.getItem('basket') === null || !localStorage.getItem('basket')) {
|
||||||
localStorage.setItem('basket', JSON.stringify({
|
localStorage.setItem('basket', JSON.stringify({
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { RegisterComponent, Component } from './components.mjs';
|
import { RegisterComponent, Component } from './components.mjs';
|
||||||
|
import * as Basket from '../basket.mjs';
|
||||||
|
|
||||||
class CompactProductListing extends Component {
|
class CompactProductListing extends Component {
|
||||||
static __IDENTIFY() { return 'compact-listing'; }
|
static __IDENTIFY() { return 'compact-listing'; }
|
||||||
@@ -32,13 +33,16 @@ class CompactProductListing extends Component {
|
|||||||
</a>
|
</a>
|
||||||
<span class="product-listing-tags">
|
<span class="product-listing-tags">
|
||||||
${this.state.tags
|
${this.state.tags
|
||||||
? this.state.tags.map(tag => `<tag-component name="${tag}"></tag-component>`).join('')
|
? this.state.tags.map(tag => `<tag-component name="${tag}"></tag-component>`).join('')
|
||||||
: ''}
|
: ''}
|
||||||
</span>
|
</span>
|
||||||
|
<div class="add-to-basket">
|
||||||
|
<button class="add-to-basket-button">Add to basket</button>
|
||||||
|
</div>
|
||||||
${this.state.discount
|
${this.state.discount
|
||||||
? `<span class="product-listing-price-full">£${parseFloat(this.state.price).toFixed(2)}</span><span class="product-listing-price-new">£${parseFloat(this.state.discount).toFixed(2)}</span>`
|
? `<span class="product-listing-price-full">£${parseFloat(this.state.price).toFixed(2)}</span><span class="product-listing-price-new">£${parseFloat(this.state.discount).toFixed(2)}</span>`
|
||||||
: `<span class="product-listing-price">£${parseFloat(this.state.price).toFixed(2)}</span>`}
|
: `<span class="product-listing-price">£${parseFloat(this.state.price).toFixed(2)}</span>`}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
`,
|
`,
|
||||||
style: `
|
style: `
|
||||||
@@ -118,6 +122,35 @@ class CompactProductListing extends Component {
|
|||||||
max-height: 100%;
|
max-height: 100%;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.add-to-basket {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.add-to-basket-button {
|
||||||
|
width: 100%;
|
||||||
|
background-color: #F5F6F6;
|
||||||
|
outline: 2px solid #222;
|
||||||
|
color: #222;
|
||||||
|
border: none;
|
||||||
|
padding: 10px;
|
||||||
|
fomt-size: 1.2em;
|
||||||
|
font-weight: bold;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 250ms ease-in-out;
|
||||||
|
margin-top: 5px;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.add-to-basket-button:hover {
|
||||||
|
color: #fff;
|
||||||
|
background-color: #222;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button-refresh {
|
||||||
|
color: #fff;
|
||||||
|
background-color: #222;
|
||||||
|
}
|
||||||
`,
|
`,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -135,6 +168,28 @@ class CompactProductListing extends Component {
|
|||||||
name.addEventListener('click', () => {
|
name.addEventListener('click', () => {
|
||||||
this.OpenProductListing(Object.bind(this));
|
this.OpenProductListing(Object.bind(this));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const addToBasketButton = this.root.querySelector('.add-to-basket-button');
|
||||||
|
|
||||||
|
if (parseInt(this.state.stock) - Basket.GetItemAmountBasket(this.state.id) < 1 || parseInt(this.state.stock) === 0) {
|
||||||
|
addToBasketButton.disabled = true;
|
||||||
|
addToBasketButton.style.backgroundColor = '#888';
|
||||||
|
addToBasketButton.style.color = '#222';
|
||||||
|
addToBasketButton.innerText = 'Out of stock';
|
||||||
|
}
|
||||||
|
|
||||||
|
addToBasketButton.addEventListener('click', () => {
|
||||||
|
Basket.AddProductToBasket(this.state.id, this.state.type, 1, 0);
|
||||||
|
|
||||||
|
addToBasketButton.disabled = true;
|
||||||
|
addToBasketButton.classList.add('button-refresh');
|
||||||
|
addToBasketButton.innerText = 'Added to basket';
|
||||||
|
setTimeout(() => {
|
||||||
|
this.setState({
|
||||||
|
...this.getState,
|
||||||
|
});
|
||||||
|
}, 1000);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -86,6 +86,7 @@ class ProductList extends Component {
|
|||||||
id="${product.id}"
|
id="${product.id}"
|
||||||
listing="${product.listing}"
|
listing="${product.listing}"
|
||||||
price="${product.price}"
|
price="${product.price}"
|
||||||
|
stock="${product.stock}"
|
||||||
type="${product.type}"
|
type="${product.type}"
|
||||||
tags="${JSON.stringify(product.tags).replace(/"/g, '"')}"
|
tags="${JSON.stringify(product.tags).replace(/"/g, '"')}"
|
||||||
discount="${product.discount || ''}"></compact-listing-component>
|
discount="${product.discount || ''}"></compact-listing-component>
|
||||||
|
|||||||
@@ -11,19 +11,32 @@
|
|||||||
</button>
|
</button>
|
||||||
|
|
||||||
<ul class="primary-menu nav-menu">
|
<ul class="primary-menu nav-menu">
|
||||||
<li class="menu-item current-menu-item"><a class="nav-link" href="#">New</a></li>
|
<li class="menu-item current-menu-item"><a class="nav-link" href="/featured/new/">New</a></li>
|
||||||
<li class="menu-item dropdown"><a class="nav-link" href="#">Sets▾</a>
|
<li class="menu-item dropdown"><a class="nav-link" href="#">Sets▾</a>
|
||||||
<!-- TODO: Going to need to dynamically generate this -->
|
<!-- TODO: Going to need to dynamically generate this -->
|
||||||
<ul class = "sub-nav">
|
<ul class = "sub-nav">
|
||||||
<li><a class="sub-nav-link" href="#">Trending now</a></li>
|
<li><a class="sub-nav-link" href="/featured/">Trending now</a></li>
|
||||||
<li><a class="sub-nav-link" href="#">Sets by theme</a></li>
|
<li><a class="sub-nav-link" href="/search?type=set&tag=star%20wars">Star Wars Sets</a></li>
|
||||||
<li><a class="sub-nav-link" href="#">Sets by price</a></li>
|
<li><a class="sub-nav-link" href="/search?type=set&tag=harry%20potter">Harry Potter Sets</a></li>
|
||||||
|
<li><a class="sub-nav-link" href="/search?type=set&tag=friends">Friends Sets</a></li>
|
||||||
|
<li><a class="sub-nav-link" href="/search?type=set&tag=creator">Creator Sets</a></li>
|
||||||
|
<li><a class="sub-nav-link" href="/search?type=set&tag=minecraft">Minecraft Sets</a></li>
|
||||||
|
<li><a class="sub-nav-link" href="/search?type=set&tag=disney">Disney Sets</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
<li class="menu-item dropdown"><a class="nav-link" href="#">Bricks▾</a>
|
<li class="menu-item dropdown"><a class="nav-link" href="#">Bricks▾</a>
|
||||||
<ul class="sub-nav" >
|
<ul class="sub-nav" >
|
||||||
<li><a class="sub-nav-link" href="#">Bricks by catagory</a></li>
|
<li><a class="sub-nav-link" href="/search?type=brick&tag=technic">Technic Bricks</a></li>
|
||||||
<li><a class="sub-nav-link" href="#">Bricks by price</a></li>
|
<li><a class="sub-nav-link" href="/search?type=brick&tag=minifigure">Minifigure Bricks</a></li>
|
||||||
|
<li><a class="sub-nav-link" href="/search?type=brick&tag=animal">Animal Bricks</a></li>
|
||||||
|
<li><a class="sub-nav-link" href="/search?type=brick&tag=food">Food Bricks</a></li>
|
||||||
|
<li><a class="sub-nav-link" href="/search?type=brick&tag=brick">Basic Bricks</a></li>
|
||||||
|
<li><a class="sub-nav-link" href="/search?type=brick&tag=tile">Tile Bricks</a></li>
|
||||||
|
<li><a class="sub-nav-link" href="/search?type=brick&tag=slope">Slope Bricks</a></li>
|
||||||
|
<li><a class="sub-nav-link" href="/search?type=brick&tag=wedge">Wedge Bricks</a></li>
|
||||||
|
<li><a class="sub-nav-link" href="/search?type=brick&tag=bracket">Bracket Bricks</a></li>
|
||||||
|
<li><a class="sub-nav-link" href="/search?type=brick&tag=arch">Arch Bricks</a></li>
|
||||||
|
<li><a class="sub-nav-link" href="/search?type=brick&tag=window">Window Bricks</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
<li class="account-item menu-item"><a class="account-button nav-link" href="#">My Account</a>
|
<li class="account-item menu-item"><a class="account-button nav-link" href="#">My Account</a>
|
||||||
|
|||||||
47
client/public/featured/new/index.html
Normal file
47
client/public/featured/new/index.html
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>LegoLog Featured Sets</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>
|
||||||
|
|
||||||
|
<!-- Flickity - a library to make carousells easier to implement -->
|
||||||
|
<link rel="stylesheet" href="https://unpkg.com/flickity@2/dist/flickity.min.css">
|
||||||
|
<script src="https://unpkg.com/flickity@2/dist/flickity.pkgd.min.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/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/product-list.mjs"></script>
|
||||||
|
<script type="module" src="/components/super-compact-listing.mjs"></script>
|
||||||
|
<script type="module" src="/components/compact-listing.mjs"></script>
|
||||||
|
<script type="module" src="/components/product-listing.mjs"></script>
|
||||||
|
|
||||||
|
<script type="module" src="index.mjs"></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<notificationbar-component></notificationbar-component>
|
||||||
|
<navbar-component></navbar-component>
|
||||||
|
<limited-margin>
|
||||||
|
<product-list-component id="featured"
|
||||||
|
title="New Lego Sets"
|
||||||
|
getroute="/api/sets/new"
|
||||||
|
type="set">
|
||||||
|
</product-list-component>
|
||||||
|
</limited-margin>
|
||||||
|
</body>
|
||||||
@@ -46,9 +46,11 @@
|
|||||||
const query = new URLSearchParams(window.location.search)
|
const query = new URLSearchParams(window.location.search)
|
||||||
// get the search query from the query object
|
// get the search query from the query object
|
||||||
const searchQuery = query.get('q');
|
const searchQuery = query.get('q');
|
||||||
|
const tagQuery = query.get('tag');
|
||||||
|
const typeQuery = query.get('type');
|
||||||
document.write(/* html */`
|
document.write(/* html */`
|
||||||
<product-list-component id="results"
|
<product-list-component id="results"
|
||||||
title="Search Results for '${searchQuery}'"
|
title="Search Results for ${typeQuery || ''} '${searchQuery || tagQuery || 'By Catagory'}'"
|
||||||
getroute="/api/search/${window.location.search}"
|
getroute="/api/search/${window.location.search}"
|
||||||
augmentable="true">
|
augmentable="true">
|
||||||
</product-list-component>
|
</product-list-component>
|
||||||
|
|||||||
@@ -16532,7 +16532,6 @@ INSERT INTO lego_set_inventory (set_id, stock, price, new_price, last_updated) V
|
|||||||
|
|
||||||
INSERT INTO lego_set (id, name, description, date_released, weight, dimensions_x, dimensions_y, dimensions_z) VALUES ('1010', 'Lego Bonsai Tree', 'Lego Bonsai Tree by Matthew Dennis, Rich Boakes and Jacek Kopecky', '2022', '100', '100', '100', '100');
|
INSERT INTO lego_set (id, name, description, date_released, weight, dimensions_x, dimensions_y, dimensions_z) VALUES ('1010', 'Lego Bonsai Tree', 'Lego Bonsai Tree by Matthew Dennis, Rich Boakes and Jacek Kopecky', '2022', '100', '100', '100', '100');
|
||||||
INSERT INTO lego_set_tag (set_id, tag) VALUES ('1010', '1201');
|
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 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());
|
INSERT INTO lego_set_inventory (set_id, stock, price, new_price, last_updated) VALUES ('1010', '5', '1000.69', '50', now());
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,8 @@ automatically every request
|
|||||||
| --- | --- | --- | - | --- |
|
| --- | --- | --- | - | --- |
|
||||||
| GET | /api/special/ | | ❌ | |
|
| GET | /api/special/ | | ❌ | |
|
||||||
| GET | /api/search/ | query (q), page | ❌ | Query endpoint |
|
| GET | /api/search/ | query (q), page | ❌ | Query endpoint |
|
||||||
| GET | /api/sets/featured | page | ❌ | Query endpoint |
|
| GET | /api/sets/featured | page | ❌ | |
|
||||||
|
| GET | /api/sets/new | page | ❌ | |
|
||||||
| GET | /api/brick/:id | | ❌ | |
|
| GET | /api/brick/:id | | ❌ | |
|
||||||
| POST | /api/bulk/brick | array | ❌ | POST due to bulk nature |
|
| POST | /api/bulk/brick | array | ❌ | POST due to bulk nature |
|
||||||
| GET | /api/set/:id | | ❌ | |
|
| GET | /api/set/:id | | ❌ | |
|
||||||
@@ -38,7 +39,7 @@ For all endpoints that query, the following parameters are supported:
|
|||||||
|
|
||||||
q: string to search for (fuzzy)
|
q: string to search for (fuzzy)
|
||||||
|
|
||||||
tags: tags to include in search
|
tag: tag to include in search (one at a time)
|
||||||
|
|
||||||
type: type of entity to return (set / brick)
|
type: type of entity to return (set / brick)
|
||||||
|
|
||||||
|
|||||||
@@ -8,10 +8,49 @@ const PgFormat = require('pg-format');
|
|||||||
|
|
||||||
// R
|
// R
|
||||||
|
|
||||||
|
async function SearchByTag(fuzzyTag) {
|
||||||
|
await Database.Query('BEGIN TRANSACTION;');
|
||||||
|
const dbres = await Database.Query(`
|
||||||
|
SELECT lego_brick.id, lego_brick.name, tag.name AS "tag", inv.price, inv.new_price AS "discount", inv.stock
|
||||||
|
FROM lego_brick
|
||||||
|
LEFT JOIN lego_brick_tag AS tags ON tags.brick_id = lego_brick.id
|
||||||
|
LEFT JOIN tag AS tag ON tags.tag = tag.id
|
||||||
|
LEFT JOIN lego_brick_inventory AS inv ON inv.brick_id = lego_brick.id
|
||||||
|
WHERE tag.name ~* $1
|
||||||
|
`, [fuzzyTag]).catch(() => {
|
||||||
|
return {
|
||||||
|
error: 'Database error',
|
||||||
|
};
|
||||||
|
});
|
||||||
|
if (dbres.error) {
|
||||||
|
Database.Query('ROLLBACK TRANSACTION;');
|
||||||
|
Logger.Error(dbres.error);
|
||||||
|
return dbres;
|
||||||
|
}
|
||||||
|
Database.Query('COMMIT TRANSACTION;');
|
||||||
|
|
||||||
|
// validate database response
|
||||||
|
if (dbres.rows.length === 0) {
|
||||||
|
return {
|
||||||
|
error: 'Bricks not found',
|
||||||
|
long: 'The bricks you are looking for do not exist',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
const bricks = dbres.rows;
|
||||||
|
|
||||||
|
// combine tags into a single array
|
||||||
|
for (const brick of bricks) {
|
||||||
|
brick.type = 'brick';
|
||||||
|
brick.tags = brick.tag.split(',');
|
||||||
|
}
|
||||||
|
|
||||||
|
return bricks;
|
||||||
|
}
|
||||||
|
|
||||||
async function Search(fuzzyStrings) {
|
async function Search(fuzzyStrings) {
|
||||||
await Database.Query('BEGIN TRANSACTION;');
|
await Database.Query('BEGIN TRANSACTION;');
|
||||||
const dbres = await Database.Query(PgFormat(`
|
const dbres = await Database.Query(PgFormat(`
|
||||||
SELECT lego_brick.id, lego_brick.name, tag.name AS "tag", inv.price, inv.new_price AS "discount"
|
SELECT lego_brick.id, lego_brick.name, tag.name AS "tag", inv.price, inv.new_price AS "discount", inv.stock
|
||||||
FROM lego_brick
|
FROM lego_brick
|
||||||
LEFT JOIN lego_brick_tag AS tags ON tags.brick_id = lego_brick.id
|
LEFT JOIN lego_brick_tag AS tags ON tags.brick_id = lego_brick.id
|
||||||
LEFT JOIN tag AS tag ON tags.tag = tag.id
|
LEFT JOIN tag AS tag ON tags.tag = tag.id
|
||||||
@@ -114,7 +153,7 @@ async function SumPrices(bricksArr, quantityArray) {
|
|||||||
async function GetBulkBricks(bricksArr) {
|
async function GetBulkBricks(bricksArr) {
|
||||||
await Database.Query('BEGIN TRANSACTION;');
|
await Database.Query('BEGIN TRANSACTION;');
|
||||||
const dbres = await Database.Query(PgFormat(`
|
const dbres = await Database.Query(PgFormat(`
|
||||||
SELECT lego_brick.id, lego_brick.name, tag.name AS "tag", inv.price, inv.new_price AS "discount"
|
SELECT lego_brick.id, lego_brick.name, tag.name AS "tag", inv.price, inv.new_price AS "discount", inv.stock
|
||||||
FROM lego_brick
|
FROM lego_brick
|
||||||
LEFT JOIN lego_brick_tag AS tags ON tags.brick_id = lego_brick.id
|
LEFT JOIN lego_brick_tag AS tags ON tags.brick_id = lego_brick.id
|
||||||
LEFT JOIN tag AS tag ON tags.tag = tag.id
|
LEFT JOIN tag AS tag ON tags.tag = tag.id
|
||||||
@@ -260,6 +299,7 @@ async function UpdateStock(brickId, newStock) {
|
|||||||
// D
|
// D
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
|
SearchByTag,
|
||||||
Search,
|
Search,
|
||||||
SumPrices,
|
SumPrices,
|
||||||
GetBulkBricks,
|
GetBulkBricks,
|
||||||
|
|||||||
@@ -7,10 +7,60 @@ const PgFormat = require('pg-format');
|
|||||||
// C
|
// C
|
||||||
|
|
||||||
// R
|
// R
|
||||||
|
|
||||||
|
async function SearchByTag(fuzzyTag) {
|
||||||
|
await Database.Query('BEGIN TRANSACTION;');
|
||||||
|
const dbres = await Database.Query(`
|
||||||
|
SELECT lego_set.id, lego_set.name, tag.name AS "tag", inv.price, inv.new_price AS "discount", inv.stock
|
||||||
|
FROM lego_set
|
||||||
|
LEFT JOIN lego_set_tag AS tags ON tags.set_id = lego_set.id
|
||||||
|
LEFT JOIN tag AS tag ON tags.tag = tag.id
|
||||||
|
LEFT JOIN lego_set_inventory AS inv ON inv.set_id = lego_set.id
|
||||||
|
WHERE tag.name ~* $1
|
||||||
|
`, [fuzzyTag]).catch(() => {
|
||||||
|
return {
|
||||||
|
error: 'Database error',
|
||||||
|
};
|
||||||
|
});
|
||||||
|
if (dbres.error) {
|
||||||
|
Database.Query('ROLLBACK TRANSACTION;');
|
||||||
|
Logger.Error(dbres.error);
|
||||||
|
return dbres;
|
||||||
|
}
|
||||||
|
Database.Query('COMMIT TRANSACTION;');
|
||||||
|
|
||||||
|
// validate database response
|
||||||
|
if (dbres.rows.length === 0) {
|
||||||
|
return {
|
||||||
|
error: 'Sets not found',
|
||||||
|
long: 'The sets you are looking for do not exist',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
let sets = dbres.rows;
|
||||||
|
// combine tags into a single array
|
||||||
|
for (const set of sets) {
|
||||||
|
set.type = 'set';
|
||||||
|
set.tags = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
// combine (joined) rows into a single array
|
||||||
|
sets = sets.reduce((arr, current) => {
|
||||||
|
if (!arr.some(item => item.id === current.id)) {
|
||||||
|
arr.push(current);
|
||||||
|
}
|
||||||
|
|
||||||
|
arr.find(item => item.id === current.id).tags.push(current.tag);
|
||||||
|
return arr;
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return sets;
|
||||||
|
}
|
||||||
|
|
||||||
async function Search(fuzzyStrings) {
|
async function Search(fuzzyStrings) {
|
||||||
await Database.Query('BEGIN TRANSACTION;');
|
await Database.Query('BEGIN TRANSACTION;');
|
||||||
const dbres = await Database.Query(PgFormat(`
|
const dbres = await Database.Query(PgFormat(`
|
||||||
SELECT lego_set.id, lego_set.name, tag.name AS "tag", inv.price, inv.new_price AS "discount"
|
SELECT lego_set.id, lego_set.name, tag.name AS "tag", inv.price, inv.new_price AS "discount", inv.stock
|
||||||
FROM lego_set
|
FROM lego_set
|
||||||
LEFT JOIN lego_set_tag AS tags ON tags.set_id = lego_set.id
|
LEFT JOIN lego_set_tag AS tags ON tags.set_id = lego_set.id
|
||||||
LEFT JOIN tag AS tag ON tags.tag = tag.id
|
LEFT JOIN tag AS tag ON tags.tag = tag.id
|
||||||
@@ -31,8 +81,8 @@ async function Search(fuzzyStrings) {
|
|||||||
// validate database response
|
// validate database response
|
||||||
if (dbres.rows.length === 0) {
|
if (dbres.rows.length === 0) {
|
||||||
return {
|
return {
|
||||||
error: 'Bricks not found',
|
error: 'Set not found',
|
||||||
long: 'The bricks you are looking for do not exist',
|
long: 'The sets you are looking for do not exist',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -176,7 +226,7 @@ async function GetSets(page, resPerPage) {
|
|||||||
const countRes = await Database.Query('SELECT COUNT (*) FROM lego_set;');
|
const countRes = await Database.Query('SELECT COUNT (*) FROM lego_set;');
|
||||||
const dbres = await Database.Query(`
|
const dbres = await Database.Query(`
|
||||||
SELECT
|
SELECT
|
||||||
lego_set.id, lego_set.name, price, new_price AS "discount", tag.name AS "tag"
|
lego_set.id, lego_set.name, price, new_price AS "discount", tag.name AS "tag", inv.stock
|
||||||
FROM lego_set
|
FROM lego_set
|
||||||
LEFT JOIN lego_set_inventory as inv ON lego_set.id = inv.set_id
|
LEFT JOIN lego_set_inventory as inv ON lego_set.id = inv.set_id
|
||||||
LEFT JOIN lego_set_tag AS tags ON tags.set_id = lego_set.id
|
LEFT JOIN lego_set_tag AS tags ON tags.set_id = lego_set.id
|
||||||
@@ -227,6 +277,61 @@ async function GetSets(page, resPerPage) {
|
|||||||
return { total, sets };
|
return { total, sets };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function GetSetsByDate(page, resPerPage) {
|
||||||
|
await Database.Query('BEGIN TRANSACTION;');
|
||||||
|
const countRes = await Database.Query('SELECT COUNT (*) FROM lego_set;');
|
||||||
|
const dbres = await Database.Query(`
|
||||||
|
SELECT
|
||||||
|
lego_set.id, lego_set.name, price, new_price AS "discount", tag.name AS "tag", date_released, inv.stock
|
||||||
|
FROM lego_set
|
||||||
|
LEFT JOIN lego_set_inventory as inv ON lego_set.id = inv.set_id
|
||||||
|
LEFT JOIN lego_set_tag AS tags ON tags.set_id = lego_set.id
|
||||||
|
LEFT JOIN tag AS tag ON tags.tag = tag.id
|
||||||
|
ORDER BY date_released::int DESC
|
||||||
|
LIMIT $1
|
||||||
|
OFFSET $2;`,
|
||||||
|
[resPerPage, page * resPerPage]).catch(() => {
|
||||||
|
return {
|
||||||
|
error: 'Database error',
|
||||||
|
};
|
||||||
|
});
|
||||||
|
if (dbres.error) {
|
||||||
|
Database.Query('ROLLBACK TRANSACTION;');
|
||||||
|
Logger.Error(dbres.error);
|
||||||
|
return dbres;
|
||||||
|
}
|
||||||
|
Database.Query('COMMIT TRANSACTION;');
|
||||||
|
|
||||||
|
const total = parseInt(countRes.rows[0].count);
|
||||||
|
|
||||||
|
// validate database response
|
||||||
|
if (dbres.rows.length === 0) {
|
||||||
|
return {
|
||||||
|
error: 'No sets found',
|
||||||
|
long: 'There are no sets to display',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
let sets = dbres.rows;
|
||||||
|
|
||||||
|
for (const set of sets) {
|
||||||
|
set.type = 'set';
|
||||||
|
set.tags = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
// combine (joined) rows into a single array
|
||||||
|
sets = sets.reduce((arr, current) => {
|
||||||
|
if (!arr.some(item => item.id === current.id)) {
|
||||||
|
arr.push(current);
|
||||||
|
}
|
||||||
|
|
||||||
|
arr.find(item => item.id === current.id).tags.push(current.tag);
|
||||||
|
return arr;
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return { total, sets };
|
||||||
|
}
|
||||||
|
|
||||||
// U
|
// U
|
||||||
|
|
||||||
async function RemoveSetStock(setIdList, quantityList) {
|
async function RemoveSetStock(setIdList, quantityList) {
|
||||||
@@ -274,10 +379,12 @@ async function UpdateStock(setId, newStock) {
|
|||||||
// D
|
// D
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
|
SearchByTag,
|
||||||
Search,
|
Search,
|
||||||
SumPrices,
|
SumPrices,
|
||||||
GetSet,
|
GetSet,
|
||||||
GetSets,
|
GetSets,
|
||||||
|
GetSetsByDate,
|
||||||
RemoveSetStock,
|
RemoveSetStock,
|
||||||
UpdateStock,
|
UpdateStock,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -17,6 +17,8 @@ function Init() {
|
|||||||
Server.App.get('/api/search/', Query.Search);
|
Server.App.get('/api/search/', Query.Search);
|
||||||
|
|
||||||
Server.App.get('/api/sets/featured/', Sets.Featured);
|
Server.App.get('/api/sets/featured/', Sets.Featured);
|
||||||
|
Server.App.get('/api/sets/new/', Sets.NewSets);
|
||||||
|
|
||||||
Server.App.get('/api/brick/:id', Bricks.Get);
|
Server.App.get('/api/brick/:id', Bricks.Get);
|
||||||
Server.App.post('/api/bulk/brick', Bricks.GetMultiple);
|
Server.App.post('/api/bulk/brick', Bricks.GetMultiple);
|
||||||
Server.App.get('/api/set/:id', Sets.Get);
|
Server.App.get('/api/set/:id', Sets.Get);
|
||||||
|
|||||||
@@ -3,8 +3,95 @@ const BrickController = require('../controllers/brick-controller.js');
|
|||||||
const SetController = require('../controllers/set-controller.js');
|
const SetController = require('../controllers/set-controller.js');
|
||||||
const SpellController = require('../controllers/spellchecker.js');
|
const SpellController = require('../controllers/spellchecker.js');
|
||||||
|
|
||||||
|
async function SearchByTag(req, res, tag, pageRequested, perPage) {
|
||||||
|
const setResults = await SetController.SearchByTag(tag);
|
||||||
|
const brickResults = await BrickController.SearchByTag(tag);
|
||||||
|
|
||||||
|
if (setResults.error && brickResults.error) {
|
||||||
|
return res.send(JSON.stringify({
|
||||||
|
error: 'Not found',
|
||||||
|
long: 'What you are looking for do not exist',
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
const results = setResults.concat(brickResults);
|
||||||
|
const count = results.length;
|
||||||
|
|
||||||
|
// remove after the requested page
|
||||||
|
results.splice(perPage * pageRequested);
|
||||||
|
// remove before the requested page
|
||||||
|
results.splice(0, perPage * (pageRequested - 1));
|
||||||
|
|
||||||
|
res.send(JSON.stringify({
|
||||||
|
data: results,
|
||||||
|
page: {
|
||||||
|
total: count,
|
||||||
|
per_page: perPage,
|
||||||
|
page: pageRequested,
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
async function SearchByTypeAndTag(req, res, type, tag, pageRequested, perPage) {
|
||||||
|
let results = [];
|
||||||
|
let count = 0;
|
||||||
|
|
||||||
|
if (type === 'brick') {
|
||||||
|
results = await BrickController.SearchByTag(tag);
|
||||||
|
|
||||||
|
if (results.error) {
|
||||||
|
return res.send(JSON.stringify({
|
||||||
|
error: 'Not found',
|
||||||
|
long: 'What you are looking for do not exist',
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
count = results.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type === 'set') {
|
||||||
|
results = await SetController.SearchByTag(tag);
|
||||||
|
|
||||||
|
if (results.error) {
|
||||||
|
return res.send(JSON.stringify({
|
||||||
|
error: 'Not found',
|
||||||
|
long: 'What you are looking for do not exist',
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
count = results.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
// remove after the requested page
|
||||||
|
results.splice(perPage * pageRequested);
|
||||||
|
// remove before the requested page
|
||||||
|
results.splice(0, perPage * (pageRequested - 1));
|
||||||
|
|
||||||
|
res.send(JSON.stringify({
|
||||||
|
data: results,
|
||||||
|
page: {
|
||||||
|
total: count,
|
||||||
|
per_page: perPage,
|
||||||
|
page: pageRequested,
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
async function Search(req, res) {
|
async function Search(req, res) {
|
||||||
const q = req.query.q;
|
const q = req.query.q || '';
|
||||||
|
const tag = req.query.tag || '';
|
||||||
|
const type = req.query.type || '';
|
||||||
|
const pageRequested = req.query.page || 1;
|
||||||
|
const perPage = req.query.per_page || 16;
|
||||||
|
|
||||||
|
if (q === '' && tag !== '' && type === '') {
|
||||||
|
return SearchByTag(req, res);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (q === '' && tag !== '' && type !== '') {
|
||||||
|
return SearchByTypeAndTag(req, res, type, tag, pageRequested, perPage);
|
||||||
|
}
|
||||||
|
|
||||||
// sanatise query
|
// sanatise query
|
||||||
const sanatisedQuery = ControllerMaster.SanatiseQuery(q);
|
const sanatisedQuery = ControllerMaster.SanatiseQuery(q);
|
||||||
@@ -18,9 +105,6 @@ async function Search(req, res) {
|
|||||||
|
|
||||||
const alternateQueries = SpellController.MostProbableAlternateQueries(sanatisedQuery);
|
const alternateQueries = SpellController.MostProbableAlternateQueries(sanatisedQuery);
|
||||||
|
|
||||||
const pageRequested = req.query.page || 1;
|
|
||||||
const perPage = req.query.per_page || 16;
|
|
||||||
|
|
||||||
// TODO: it is tricky to do a database offset / limit here
|
// TODO: it is tricky to do a database offset / limit here
|
||||||
// due to the fact that we have to combine the results of
|
// due to the fact that we have to combine the results of
|
||||||
// the two queries, look into me (maybe merging the queries)
|
// the two queries, look into me (maybe merging the queries)
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ async function Get(req, res) {
|
|||||||
|
|
||||||
async function Featured(req, res) {
|
async function Featured(req, res) {
|
||||||
// query all sets and return all of them
|
// query all sets and return all of them
|
||||||
const { sets } = await Controller.GetSets(0, 9);
|
const { sets } = await Controller.GetSets(0, 8);
|
||||||
|
|
||||||
if (sets.error) {
|
if (sets.error) {
|
||||||
res.send(JSON.stringify(sets));
|
res.send(JSON.stringify(sets));
|
||||||
@@ -34,7 +34,33 @@ async function Featured(req, res) {
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function NewSets(req, res) {
|
||||||
|
// query all sets and return all of them
|
||||||
|
const pageRequested = req.query.page || 1;
|
||||||
|
const perPage = req.query.per_page || 16;
|
||||||
|
|
||||||
|
const { total, sets } = await Controller.GetSetsByDate(pageRequested, perPage);
|
||||||
|
|
||||||
|
if (!sets) {
|
||||||
|
res.send(JSON.stringify({
|
||||||
|
error: 'Not found',
|
||||||
|
long: 'What you are looking for do not exist',
|
||||||
|
}));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
res.send(JSON.stringify({
|
||||||
|
data: [...sets],
|
||||||
|
page: {
|
||||||
|
total,
|
||||||
|
per_page: perPage,
|
||||||
|
page: pageRequested,
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
Get,
|
Get,
|
||||||
Featured,
|
Featured,
|
||||||
|
NewSets,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -21,12 +21,6 @@ async function Update(req, res) {
|
|||||||
|
|
||||||
const data = req.body;
|
const data = req.body;
|
||||||
|
|
||||||
if (!data || !data.new_stock_level) {
|
|
||||||
return res.send({
|
|
||||||
error: 'No data in request',
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (type === 'brick') {
|
if (type === 'brick') {
|
||||||
const stock = await BrickController.UpdateStock(id, data.new_stock_level);
|
const stock = await BrickController.UpdateStock(id, data.new_stock_level);
|
||||||
if (stock.error) {
|
if (stock.error) {
|
||||||
|
|||||||
Reference in New Issue
Block a user