set page
Former-commit-id: e9cbcd159172587181e412bf22ead2260976aca9
This commit is contained in:
@@ -103,7 +103,7 @@ class Basket extends Component {
|
||||
|
||||
Render() {
|
||||
return {
|
||||
template: `
|
||||
template: /* html */`
|
||||
<span id="basket-wrapper">
|
||||
<div class="basket">
|
||||
<img id="basket-icon" class="menu-item" src="https://www.svgrepo.com/show/343743/cart.svg" width="50px" stroke="#222" stroke-width="2px" alt="">
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { RegisterComponent, Component } from './components.mjs';
|
||||
import * as Helpers from '../helpers.mjs';
|
||||
|
||||
class CompactProductListing extends Component {
|
||||
static __IDENTIFY() { return 'compact-listing'; }
|
||||
@@ -10,13 +9,13 @@ class CompactProductListing extends Component {
|
||||
|
||||
Render() {
|
||||
return {
|
||||
template: `
|
||||
template: /* html */`
|
||||
<div class="product-listing">
|
||||
<div class="product-listing-image">
|
||||
<img class="product-image"
|
||||
title="Image of {this.state.name}"
|
||||
alt="Image of {this.state.name}"
|
||||
src="{this.state.image}">
|
||||
src="/api/cdn/${this.state.id}.png">
|
||||
</div>
|
||||
<div class="product-listing-info">
|
||||
<div class="product-listing-name">{this.state.name} {this.state.id}</div>
|
||||
|
||||
@@ -54,11 +54,8 @@ export class Component extends HTMLElement {
|
||||
|
||||
this.Update(Object.bind(this));
|
||||
|
||||
this.setState(this.state);
|
||||
|
||||
if (this.attributes.length === 0) {
|
||||
this.__INVOKE_RENDER(Object.bind(this));
|
||||
}
|
||||
this.setState(this.state, false);
|
||||
this.__INVOKE_RENDER(Object.bind(this));
|
||||
}
|
||||
|
||||
disconnectedCallback() {
|
||||
@@ -88,8 +85,9 @@ export class Component extends HTMLElement {
|
||||
return this.state;
|
||||
}
|
||||
|
||||
setState(newState) {
|
||||
setState(newState, doRender = true) {
|
||||
this.state = newState;
|
||||
if (!doRender) return;
|
||||
this.__INVOKE_RENDER(Object.bind(this));
|
||||
}
|
||||
|
||||
|
||||
@@ -25,8 +25,9 @@
|
||||
}
|
||||
|
||||
.product-image-container {
|
||||
aspect-ratio: 1;
|
||||
flex-grow: 1;
|
||||
aspect-ratio: 1;
|
||||
max-width: 600px;
|
||||
}
|
||||
|
||||
.active-image {
|
||||
@@ -34,6 +35,15 @@
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.product-info {
|
||||
flex-grow: 2;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: flex-start;
|
||||
margin-left: 1em;
|
||||
}
|
||||
|
||||
@media (pointer:none), (pointer:coarse), screen and (max-width: 900px) {
|
||||
.product-page, .product-display {
|
||||
flex-direction: column;
|
||||
@@ -55,15 +65,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
.product-info {
|
||||
flex-grow: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: flex-start;
|
||||
margin-left: 1em;
|
||||
}
|
||||
|
||||
.product-name {
|
||||
font-size: 2em;
|
||||
font-weight: bold;
|
||||
@@ -80,16 +81,6 @@
|
||||
margin-block-start: 0.83em;
|
||||
}
|
||||
|
||||
.tag {
|
||||
padding: 0.3em 1em;
|
||||
margin-right: 1em;
|
||||
line-height: 1.5em;
|
||||
font-size: 0.8em;
|
||||
font-weight: bold;
|
||||
background-color: #F2CA52;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.product-listing-price {
|
||||
font-size: 1.5em;
|
||||
}
|
||||
@@ -181,16 +172,16 @@ input[type=number] {
|
||||
transform: translateY(4px);
|
||||
}
|
||||
|
||||
.product-details-collapsible {
|
||||
.collapsible-menu {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
margin-block-end: 3em;
|
||||
margin-block-end: 2em;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.product-details-header {
|
||||
.menu-header {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
@@ -199,37 +190,67 @@ input[type=number] {
|
||||
font-weight: bold;
|
||||
cursor: pointer;
|
||||
border-bottom: #1A1A1A solid 1px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.product-details-header-arrow {
|
||||
.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 */
|
||||
.product-details-header-arrow-down {
|
||||
.menu-header-arrow-down {
|
||||
margin-left: 0.5em;
|
||||
transform: rotate(-90deg);
|
||||
}
|
||||
|
||||
.product-details-content {
|
||||
visibility: hidden;
|
||||
width: 100%;
|
||||
.menu-content {
|
||||
max-width: fit-content;
|
||||
display: none;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
align-items: flex-start;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.details-open {
|
||||
visibility: visible;
|
||||
display: flex;
|
||||
position: static;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.product-details-content-item {
|
||||
padding-top: 0.6em;
|
||||
}
|
||||
|
||||
.scrollable-container {
|
||||
width: auto;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
align-items: flex-start;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
height: 400px;
|
||||
}
|
||||
|
||||
.set-piece-container {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
margin-block-start: 0.83em;
|
||||
margin-block-end: 0.83em;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.sc-listing {
|
||||
width: 100%;
|
||||
flex-grow: 5;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.set-piece-amount {
|
||||
flex-grow: 1;
|
||||
font-size: 2.3em;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
@@ -36,14 +36,13 @@ class ProductList extends Component {
|
||||
}
|
||||
|
||||
return {
|
||||
template: `
|
||||
template: /* html */`
|
||||
<h2>{this.state.title}</h2>
|
||||
<div class="product-list">
|
||||
${this.state.products.data.map(product => {
|
||||
return `<compact-listing-component name="${product.name}"
|
||||
id="${product.id}"
|
||||
listing="${product.listing}"
|
||||
image="${product.image}"
|
||||
price="${product.price}"
|
||||
type="${product.type}"
|
||||
discount="${product.discount || ''}"></compact-listing-component>
|
||||
|
||||
@@ -13,18 +13,64 @@ class ProductListing extends Component {
|
||||
const type = urlParams.get('type');
|
||||
const id = urlParams.get('id');
|
||||
|
||||
const getURL = new URL(`/api/${type}/${id}`, document.baseURI);
|
||||
const data = await fetch(getURL).then(response => response.json());
|
||||
console.log(data);
|
||||
const getProductURL = new URL(`/api/${type}/${id}`, document.baseURI);
|
||||
const productData = await fetch(getProductURL).then(response => response.json());
|
||||
|
||||
let setContents = [];
|
||||
if (productData.data.type === 'set') {
|
||||
const allPieces = [];
|
||||
Object.keys(productData.data.includedPieces).forEach(key => {
|
||||
allPieces.push(key);
|
||||
});
|
||||
|
||||
const bulkSets = await fetch('/api/bulk/brick', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
ids: allPieces,
|
||||
}),
|
||||
}).then(response => response.json());
|
||||
setContents = bulkSets.data;
|
||||
}
|
||||
|
||||
this.setState({
|
||||
...this.getState,
|
||||
...data.data,
|
||||
});
|
||||
...productData.data,
|
||||
setContents,
|
||||
}, false);
|
||||
}
|
||||
|
||||
Render() {
|
||||
let setContents = '';
|
||||
console.log(this.state)
|
||||
if (this.state.type === 'set') {
|
||||
setContents = /* html */`
|
||||
<div class="collapsible-menu">
|
||||
<div class="menu-header">
|
||||
<span class="menu-header-text">Set Contents</span>
|
||||
<img class="menu-header-arrow" src="/res/back-arrow.svg" height="30em" alt="down-arrow">
|
||||
</div>
|
||||
<div class="menu-content scrollable-container">
|
||||
${this.state.setContents.map(piece => /* html */`
|
||||
<div class="set-piece-container">
|
||||
<span class="set-piece-amount">x${this.state.includedPieces[piece.id]}</span>
|
||||
<super-compact-listing-component class="sc-listing" id="${piece.id}"
|
||||
name="${piece.name}"
|
||||
tag="${piece.tag}"
|
||||
type="piece"
|
||||
price="${piece.price || piece.discount}">
|
||||
</super-compact-listing-component>
|
||||
</div>
|
||||
`).join('')}
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
return {
|
||||
template: `
|
||||
template: /* html */`
|
||||
<div class="product-page">
|
||||
<div class="back-button">
|
||||
<img class="back-button-svg" src="/res/back-arrow.svg" height="60em" alt="back-arrow">
|
||||
@@ -39,11 +85,11 @@ class ProductListing extends Component {
|
||||
<div class="product-info">
|
||||
<div class="product-tags">
|
||||
${this.state.tags.map(tag => {
|
||||
return `<span class="tag">${tag}</span>`;
|
||||
return `<tag-component name="${tag}"></tag-component>`;
|
||||
}).join('')}
|
||||
</div>
|
||||
|
||||
<div class="product-name">{this.state.name} [{this.state.date_released}]</div>
|
||||
<div class="product-name">{this.state.name} {this.state.id}</div>
|
||||
${this.state.discount
|
||||
? '<span class="product-listing-price-full">£{this.state.price}</span><span class="product-listing-price-new">£{this.state.discount}</span>'
|
||||
: '<span class="product-listing-price">£{this.state.price}</span>'}
|
||||
@@ -61,34 +107,21 @@ class ProductListing extends Component {
|
||||
<img class="add-to-favorites-button" src="https://www.svgrepo.com/show/25921/heart.svg" width="45px" stroke="#222" stroke-width="2px" alt="Add to Favorites" title="Add to Favorites">
|
||||
</div>
|
||||
|
||||
<div class="product-details-collapsible">
|
||||
<div class="product-details-header">
|
||||
<span class="product-details-header-text">Product Details</span>
|
||||
<img class="product-details-header-arrow" src="/res/back-arrow.svg" height="30em" alt="down-arrow">
|
||||
<div class="collapsible-menu">
|
||||
<div class="menu-header">
|
||||
<span class="menu-header-text">Product Details</span>
|
||||
<img class="menu-header-arrow" src="/res/back-arrow.svg" height="30em" alt="down-arrow">
|
||||
</div>
|
||||
<div class="product-details-content">
|
||||
<div class="product-details-content-item">
|
||||
<span class="product-details-date">Released in {this.state.date_released}</span>
|
||||
</div>
|
||||
<div class="product-details-content-item">
|
||||
<span class="product-details-dimensions">Dimensions: </span>
|
||||
<span class="product-details-dimensions-value">
|
||||
{this.state.dimensions_x} x {this.state.dimensions_y} x {this.state.dimensions_z}
|
||||
</span>
|
||||
</div>
|
||||
<div class="product-details-content-item">
|
||||
<span class="product-details-weight">Weight: </span>
|
||||
<span class="product-details-weight-value">{this.state.weight}</span>
|
||||
<span class="product-details-weight-unit">g</span>
|
||||
</div>
|
||||
<div class="product-details-content-item">
|
||||
Not suitable for children under the age of 3 years old, small parts are a choking hazard.
|
||||
</div>
|
||||
<div class="product-details-content-item">
|
||||
Not for individual resale.
|
||||
</div>
|
||||
<div class="menu-content">
|
||||
<div class="product-details-content-item">Released in {this.state.date_released}</div>
|
||||
<div class="product-details-content-item">Dimensions: {this.state.dimensions_x} x {this.state.dimensions_y} x {this.state.dimensions_z}</div>
|
||||
<div class="product-details-content-item">Weight: {this.state.weight}g</div>
|
||||
<div class="product-details-content-item">Not suitable for children under the age of 3 years old, small parts are a choking hazard.</div>
|
||||
<div class="product-details-content-item">Not for individual resale.</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
${setContents}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
@@ -136,14 +169,15 @@ class ProductListing extends Component {
|
||||
});
|
||||
|
||||
// product details, collapsable
|
||||
const collapseButton = this.root.querySelector('.product-details-header');
|
||||
const collapseContent = this.root.querySelector('.product-details-content');
|
||||
const collapseArrow = this.root.querySelector('.product-details-header-arrow');
|
||||
const collapseButton = this.root.querySelectorAll('.menu-header');
|
||||
|
||||
collapseButton.addEventListener('click', () => {
|
||||
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('product-details-header-arrow-down');
|
||||
});
|
||||
collapseArrow.classList.toggle('menu-header-arrow-down');
|
||||
}));
|
||||
|
||||
// add quantity to basket and then update the basket count
|
||||
const addToBasket = this.root.querySelector('.add-to-basket-button');
|
||||
|
||||
@@ -9,7 +9,7 @@ class Search extends Component {
|
||||
|
||||
Render() {
|
||||
return {
|
||||
template: `
|
||||
template: /* html */`
|
||||
<input id="search-bar" class="menu-item" type="text" placeholder="search..."/>
|
||||
`,
|
||||
style: `
|
||||
|
||||
@@ -9,7 +9,7 @@ class StoreFront extends Component {
|
||||
|
||||
Render() {
|
||||
return {
|
||||
template: `
|
||||
template: /* html */`
|
||||
<div class="main-carousel">
|
||||
<div class="carousel-cell">
|
||||
<img class="carousel-image" src="/res/lego-image1.jpg" alt="">
|
||||
|
||||
87
client/public/components/super-compact-listing.mjs
Normal file
87
client/public/components/super-compact-listing.mjs
Normal file
@@ -0,0 +1,87 @@
|
||||
import { RegisterComponent, Component } from './components.mjs';
|
||||
|
||||
class SuperCompactProductListing extends Component {
|
||||
static __IDENTIFY() { return 'super-compact-listing'; }
|
||||
|
||||
constructor() {
|
||||
super(SuperCompactProductListing);
|
||||
}
|
||||
|
||||
Render() {
|
||||
return {
|
||||
template: /* html */`
|
||||
<span class="product-listing">
|
||||
<span class="product-listing-image">
|
||||
<img class="product-image"
|
||||
title="Image of {this.state.name}"
|
||||
alt="Image of {this.state.name}"
|
||||
src="/api/cdn/${this.state.id}-thumb.png">
|
||||
</span>
|
||||
<span class="product-listing-info">
|
||||
<span class="product-listing-name">{this.state.name}</span>
|
||||
<tag-component name="{this.state.tag}"></tag-component>
|
||||
</span>
|
||||
<span class="product-pricing">
|
||||
£{this.state.price}
|
||||
</span>
|
||||
</span>
|
||||
`,
|
||||
style: `
|
||||
.product-listing {
|
||||
width: 95%;
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
margin: 7px;
|
||||
z-index: 0;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.product-listing-image {
|
||||
display: block;
|
||||
margin: 0 auto;
|
||||
margin-bottom: 7px;
|
||||
max-width: 100%;
|
||||
flex-grow: 1
|
||||
}
|
||||
|
||||
.product-image {
|
||||
object-fit: scale-down;
|
||||
object-position: center;
|
||||
}
|
||||
|
||||
.product-image:hover {
|
||||
cursor: hand;
|
||||
}
|
||||
|
||||
.product-listing-info {
|
||||
border-left: 1px solid #ccc;
|
||||
border-right: 1px solid #ccc;
|
||||
padding-left: 7px;
|
||||
padding-right: 7px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
flex-grow: 50
|
||||
}
|
||||
|
||||
.product-pricing {
|
||||
flex-grow: 1;
|
||||
text-align: right;
|
||||
font-size: 0.8em;
|
||||
font-weight: bold;
|
||||
color: #E55744;
|
||||
}
|
||||
`,
|
||||
};
|
||||
}
|
||||
|
||||
OnRender() {
|
||||
this.root.addEventListener('click', () => {
|
||||
window.location.href = `/product/?type=${this.state.type}&id=${this.state.id}&name=${encodeURIComponent(this.state.name)}`;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
RegisterComponent(SuperCompactProductListing);
|
||||
38
client/public/components/tag.mjs
Normal file
38
client/public/components/tag.mjs
Normal file
@@ -0,0 +1,38 @@
|
||||
import { RegisterComponent, Component } from './components.mjs';
|
||||
|
||||
class Tag extends Component {
|
||||
static __IDENTIFY() { return 'tag'; }
|
||||
|
||||
constructor() {
|
||||
super(Tag);
|
||||
}
|
||||
|
||||
Render() {
|
||||
return {
|
||||
template: /* html */`
|
||||
<span class="tag">{this.state.name}</span>
|
||||
`,
|
||||
style: `
|
||||
.tag {
|
||||
padding: 0.3em 1em;
|
||||
margin-right: 0.3em;
|
||||
margin-top: 0.3em;
|
||||
margin-bottom: 0.3em;
|
||||
line-height: 1.5em;
|
||||
font-size: 0.8em;
|
||||
font-weight: bold;
|
||||
background-color: #F2CA52;
|
||||
cursor: pointer;
|
||||
}
|
||||
`,
|
||||
};
|
||||
}
|
||||
|
||||
OnRender() {
|
||||
this.root.addEventListener('click', () => {
|
||||
this.root.classList.toggle('tag-selected');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
RegisterComponent(Tag);
|
||||
@@ -25,7 +25,9 @@
|
||||
<script type="module" src="/components/basket.mjs"></script>
|
||||
<script type="module" src="/components/notificationbar.mjs"></script>
|
||||
<script type="module" src="/components/storefront.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>
|
||||
|
||||
|
||||
@@ -21,7 +21,9 @@
|
||||
<script type="module" src="/components/basket.mjs"></script>
|
||||
<script type="module" src="/components/notificationbar.mjs"></script>
|
||||
<script type="module" src="/components/storefront.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>
|
||||
|
||||
|
||||
@@ -16,9 +16,10 @@ automatically every request
|
||||
| GET | /api/bricks/ | query, page | no | Query endpoint |
|
||||
| GET | /api/sets/ | query, page | no | Query endpoint |
|
||||
| GET | /api/sets/featured | page | no | Query endpoint |
|
||||
| GET | /api/brick/:id/ | | no | |
|
||||
| GET | /api/set/:id/ | | no | |
|
||||
| GET | /api/cdn/:id/ | | no | |
|
||||
| GET | /api/brick/:id | | no | |
|
||||
| POST | /api/bulk/brick | array | no | POST due to bulk nature |
|
||||
| GET | /api/set/:id | | no | |
|
||||
| GET | /api/cdn/:id | | no | |
|
||||
| PUT | /api/auth/login/ | | yes | |
|
||||
| POST | /api/auth/signup/ | | yes | |
|
||||
| GET | /api/auth/orders/ | | yes | |
|
||||
|
||||
@@ -44,7 +44,7 @@ class MyComponent extends Component {
|
||||
|
||||
Render() {
|
||||
return {
|
||||
template: `<div>{this.state.name}</div>`,
|
||||
template: /* html */`<div>{this.state.name}</div>`,
|
||||
style: `div { text-color: red }`,
|
||||
};
|
||||
}
|
||||
|
||||
1264
package-lock.json
generated
1264
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -27,6 +27,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"axios": "^0.25.0",
|
||||
"body-parser": "^1.20.0",
|
||||
"cli-color": "^2.0.1",
|
||||
"decompress": "^4.2.1",
|
||||
"decompress-targz": "^4.1.1",
|
||||
@@ -39,7 +40,9 @@
|
||||
"moment": "^2.29.1",
|
||||
"npm": "^8.6.0",
|
||||
"pg": "^8.7.3",
|
||||
"pg-native": "^3.0.0"
|
||||
"pg-format": "^1.0.4",
|
||||
"pg-native": "^3.0.0",
|
||||
"sharp": "^0.30.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"eslint": "^8.9.0",
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
const Database = require('../database/database.js');
|
||||
const PgFormat = require('pg-format');
|
||||
|
||||
async function GetBulkBricks(bricksArr) {
|
||||
await Database.Query('BEGIN TRANSACTION;');
|
||||
const dbres = await Database.Query(PgFormat(`
|
||||
SELECT lego_brick.id, lego_brick.name, tag.name AS "tag", inv.price, inv.new_price AS "discount"
|
||||
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 lego_brick.id IN (%L);
|
||||
`, bricksArr), []);
|
||||
await Database.Query('END 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.tags = brick.tag.split(',');
|
||||
}
|
||||
|
||||
return bricks;
|
||||
}
|
||||
|
||||
async function GetBrick(brickId) {
|
||||
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
GetBulkBricks,
|
||||
GetBrick,
|
||||
};
|
||||
|
||||
@@ -16,13 +16,15 @@ function ValidateQuery(query) {
|
||||
async function GetSet(setId) {
|
||||
await Database.Query('BEGIN TRANSACTION;');
|
||||
const dbres = await Database.Query(`
|
||||
SELECT lego_set.id, lego_set.name, description, tag.name AS "tag", inv.price,
|
||||
SELECT lego_set.id, lego_set.name, description, tag.name AS "tag",
|
||||
set_contents.brick_id, set_contents.amount, inv.price,
|
||||
date_released, weight, dimensions_x, dimensions_y, dimensions_z,
|
||||
new_price AS "discount", inv.stock, inv.last_updated AS "last_stock_update"
|
||||
FROM lego_set
|
||||
LEFT JOIN lego_set_inventory AS inv ON inv.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 set_descriptor AS set_contents ON set_contents.set_id = lego_set.id
|
||||
WHERE lego_set.id = $1;
|
||||
`, [setId]);
|
||||
await Database.Query('END TRANSACTION;');
|
||||
@@ -36,18 +38,22 @@ async function GetSet(setId) {
|
||||
}
|
||||
|
||||
const tags = dbres.rows.reduce((acc, cur) => {
|
||||
acc.push(cur.tag);
|
||||
acc.add(cur.tag);
|
||||
return acc;
|
||||
}, []);
|
||||
}, new Set());
|
||||
|
||||
const pieces = dbres.rows.reduce((acc, cur) => {
|
||||
acc[cur.brick_id] = cur.amount;
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
const set = dbres.rows[0];
|
||||
delete set.tag;
|
||||
set.tags = tags;
|
||||
set.includedPieces = pieces;
|
||||
set.tags = Array.from(tags);
|
||||
set.image = `/api/cdn/${set.id}.png`;
|
||||
set.type = 'set';
|
||||
|
||||
console.log(set)
|
||||
|
||||
return set;
|
||||
}
|
||||
|
||||
@@ -78,7 +84,6 @@ async function GetSets(page, resPerPage) {
|
||||
const sets = dbres.rows;
|
||||
|
||||
for (const set of sets) {
|
||||
set.image = `/api/cdn/${set.id}.png`;
|
||||
set.type = 'set';
|
||||
}
|
||||
|
||||
|
||||
@@ -49,7 +49,7 @@ async function Query(query, params, callback) {
|
||||
}
|
||||
|
||||
// debug moment
|
||||
Logger.Database(`PSQL Query: ${query.substring(0, 100)}...`);
|
||||
Logger.Database(`PSQL Query: ${query.substring(0, 500).trim()}...`);
|
||||
const result = await connection.query(query, params, callback);
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ function Init() {
|
||||
Server.App.get('/api/sets/');
|
||||
Server.App.get('/api/sets/featured/', Sets.Featured);
|
||||
Server.App.get('/api/brick/:id', Bricks.Get);
|
||||
Server.App.post('/api/bulk/brick', Bricks.GetMultiple);
|
||||
Server.App.get('/api/set/:id', Sets.Get);
|
||||
|
||||
Server.App.get('/api/cdn/:id', CDN.Get);
|
||||
|
||||
@@ -6,35 +6,33 @@ function Get(req, res) {
|
||||
}));
|
||||
}
|
||||
|
||||
function Query(req, res, next) {
|
||||
const query = req.query;
|
||||
|
||||
// Validation
|
||||
const validation = Controller.ValidateQuery(query);
|
||||
if (!validation.isValid) {
|
||||
return res.status(400).json({
|
||||
error: {
|
||||
short: validation.error,
|
||||
long: validation.longError,
|
||||
},
|
||||
});
|
||||
async function GetMultiple(req, res) {
|
||||
if (req.body.ids.length === 0) {
|
||||
res.send(JSON.stringify({
|
||||
error: 'No ids provided',
|
||||
long: 'No ids provided',
|
||||
}));
|
||||
return;
|
||||
}
|
||||
|
||||
// Query
|
||||
Controller.Query(query, (err, data) => {
|
||||
if (err) {
|
||||
return res.status(500).json({
|
||||
error: err,
|
||||
});
|
||||
}
|
||||
const bricks = await Controller.GetBulkBricks(req.body.ids);
|
||||
|
||||
res.json(data);
|
||||
});
|
||||
if (bricks.error) {
|
||||
res.send(JSON.stringify(bricks));
|
||||
return;
|
||||
}
|
||||
|
||||
res.send(JSON.stringify({
|
||||
data: bricks,
|
||||
}));
|
||||
}
|
||||
|
||||
function Query(req, res, next) {
|
||||
next();
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
Get,
|
||||
GetMultiple,
|
||||
Query,
|
||||
};
|
||||
|
||||
@@ -1,19 +1,61 @@
|
||||
const Logger = require('../logger.js');
|
||||
|
||||
const md5 = require('md5');
|
||||
const fs = require('fs');
|
||||
|
||||
// fast thumbnail generation
|
||||
const sharp = require('sharp');
|
||||
|
||||
function Get(req, res) {
|
||||
// get id from url
|
||||
const id = req.params.id;
|
||||
let id = req.params.id;
|
||||
|
||||
let thumbnail = false;
|
||||
if (id.includes('-thumb')) {
|
||||
thumbnail = true;
|
||||
id = id.replace('-thumb', '');
|
||||
}
|
||||
|
||||
// work out hash from id
|
||||
const hash = md5(id.split('.png')[0]);
|
||||
const bucket = hash.substring(0, 4);
|
||||
const file = `${process.cwd()}\\db\\img\\${bucket[0]}\\${bucket[1]}\\${bucket[2]}\\${bucket[3]}\\${id}`;
|
||||
|
||||
if (fs.existsSync(file)) {
|
||||
res.sendFile(file);
|
||||
} else {
|
||||
res.sendFile(`${process.cwd()}\\db\\img\\default.png`);
|
||||
// this very randomly fails sometimes
|
||||
try {
|
||||
if (fs.existsSync(file)) {
|
||||
if (thumbnail) {
|
||||
// generate thumbnail
|
||||
sharp(file)
|
||||
.resize({
|
||||
height: 50,
|
||||
}) // keep aspect ratio
|
||||
.toBuffer()
|
||||
.then(data => {
|
||||
res.set('Content-Type', 'image/png');
|
||||
res.send(data);
|
||||
});
|
||||
return;
|
||||
}
|
||||
res.sendFile(file);
|
||||
} else {
|
||||
if (thumbnail) {
|
||||
sharp(`${process.cwd()}\\res\\default.png`)
|
||||
.resize({
|
||||
height: 50,
|
||||
}) // keep aspect ratio
|
||||
.toBuffer()
|
||||
.then(data => {
|
||||
res.set('Content-Type', 'image/png');
|
||||
res.send(data);
|
||||
});
|
||||
return;
|
||||
}
|
||||
res.sendFile(`${process.cwd()}\\db\\img\\default.png`);
|
||||
}
|
||||
} catch (err) {
|
||||
Logger.Error(err);
|
||||
res.sendStatus(404);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
const Logger = require('../logger.js');
|
||||
|
||||
const express = require('express');
|
||||
const bodyParser = require('body-parser');
|
||||
const app = express();
|
||||
|
||||
function listen(port) {
|
||||
@@ -10,6 +11,9 @@ function listen(port) {
|
||||
Logger.Info('Setting up basic middleware...');
|
||||
|
||||
app.use(Logger.ExpressLogger);
|
||||
app.use(bodyParser.urlencoded({ extended: true }));
|
||||
app.use(bodyParser.json());
|
||||
|
||||
app.use(express.static('client/public/'));
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user