happy with the search feature

Former-commit-id: 9d9e3a7a7b330392f9d6d1c65439ea86e45ed03c
This commit is contained in:
Ben
2022-04-15 23:35:05 +01:00
parent d3fd17cc8a
commit 9f508767fd
18 changed files with 496 additions and 164 deletions

View File

@@ -0,0 +1,42 @@
<html>
<head>
<title>LegoLog: About</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 type="module" 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.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>
<script type="module" src="index.mjs"></script>
</head>
<body>
<notificationbar-component></notificationbar-component>
<navbar-component></navbar-component>
<limited-margin>
</limited-margin>
</body>

View File

@@ -7,21 +7,31 @@ class ProductList extends Component {
super(ProductList); super(ProductList);
} }
async OnMount() { async FetchListings(from) {
const route = this.state.getroute; const products = await fetch(from).then(response => response.json());
const products = await fetch(route).then(response => response.json()); const productsList = this.state.products || [];
// concat the new products to the existing products
const newProducts = productsList.concat(products.data);
this.setState({ this.setState({
...this.getState, ...this.getState,
products, products: newProducts,
current_page: products.page.current_page, page: products.page.page,
last_page: products.page.last_page, per_page: products.page.per_page,
total: products.page.total, total: products.page.total,
}); });
} }
Render() { OnMount() {
this.loading = false;
this.keepLoading = false; this.keepLoading = false;
if (this.state.current_page >= this.state.last_page) {
const route = this.state.getroute;
this.FetchListings(route);
this.state.products = [];
}
Render() {
if (this.state.page * this.state.per_page >= this.state.total) {
this.keepLoading = false; this.keepLoading = false;
this.loadingBar = ''; this.loadingBar = '';
} else { } else {
@@ -39,7 +49,7 @@ class ProductList extends Component {
template: /* html */` template: /* html */`
<h2>{this.state.title}</h2> <h2>{this.state.title}</h2>
<div class="product-list"> <div class="product-list">
${this.state.products.data.map(product => { ${this.state.products.map(product => {
return `<compact-listing-component name="${product.name}" return `<compact-listing-component name="${product.name}"
id="${product.id}" id="${product.id}"
listing="${product.listing}" listing="${product.listing}"
@@ -151,13 +161,31 @@ class ProductList extends Component {
}; };
} }
OnRender() { OnRender() {
this.loading = false;
// scroll to bottom event listener // scroll to bottom event listener
if (this.keepLoading) { if (this.keepLoading) {
window.addEventListener('scroll', () => { window.addEventListener('scroll', async () => {
if (window.innerHeight + window.scrollY >= document.body.offsetHeight) { // start loading 200px before the bottom of the page
console.log('scrolled to bottom'); if (window.innerHeight + window.scrollY >= document.body.offsetHeight - 300) {
if (this.loading) return;
if (this.state.page * this.state.per_page >= this.state.total) {
this.keepLoading = false;
this.loadingBar = '';
return;
}
this.loading = true;
// parse the getRoute as a query string
const getRoute = this.state.getroute;
// split into query and location
const [locationStr, queryStr] = getRoute.split('?');
const query = new URLSearchParams(queryStr);
query.append('page', parseInt(this.state.page) + 1);
await this.FetchListings(`${locationStr}?${query.toString()}`);
} }
}); });
} }

View File

@@ -63,8 +63,16 @@ class Search extends Component {
background-color: #AB8FFF; background-color: #AB8FFF;
flex-wrap: wrap; flex-wrap: wrap;
font-size: 0.6em; font-size: 0.6em;
position: fixed; position: absolute;
width: 65%; width: 100%;
}
@media (pointer:none), (pointer:coarse), screen and (max-width: 900px) {
.search-results {
position: fixed;
left: 0;
width: 100%;
}
} }
#search-bar:focus + .search-results { #search-bar:focus + .search-results {
@@ -104,15 +112,28 @@ class Search extends Component {
} }
} }
OnRender() { GetSearch(value) {
const searchBar = this.root.querySelector('#search-bar'); if (value === '') {
searchBar.value = localStorage.getItem('search-bar'); this.AppendSearchResults([], true);
const route = `/api/search?q=${searchBar.value}&per_page=10`; return;
}
const route = `/api/search?q=${value}&per_page=10`;
fetch(route).then((response) => { fetch(route).then((response) => {
return response.json(); return response.json();
}).then((data) => { }).then((data) => {
if (data.error) {
this.AppendSearchResults([], true);
return;
}
this.AppendSearchResults(data.data); this.AppendSearchResults(data.data);
}); });
}
OnRender() {
const searchBar = this.root.querySelector('#search-bar');
searchBar.value = localStorage.getItem('search-bar');
this.GetSearch(searchBar.value);
searchBar.addEventListener('keyup', (e) => { searchBar.addEventListener('keyup', (e) => {
localStorage.setItem('search-bar', e.target.value); localStorage.setItem('search-bar', e.target.value);
@@ -123,15 +144,10 @@ class Search extends Component {
} }
// we want this to happen async // we want this to happen async
const route = `/api/search?q=${e.target.value}&per_page=10`; this.GetSearch(e.target.value);
fetch(route).then((response) => {
return response.json();
}).then((data) => {
this.AppendSearchResults(data.data);
});
if (e.keyCode === 13) { if (e.keyCode === 13) {
const searchTerm = searchBar.value; const searchTerm = e.target.value;
if (searchTerm.length > 0) { if (searchTerm.length > 0) {
window.location.href = `/search?q=${searchTerm}`; window.location.href = `/search?q=${searchTerm}`;
} }

View File

@@ -15,21 +15,21 @@ class StoreFront extends Component {
<img class="carousel-image" src="/res/lego-image1.jpg" alt=""> <img class="carousel-image" src="/res/lego-image1.jpg" alt="">
<div class="carousel-caption"> <div class="carousel-caption">
<h1>Welcome to LegoLog!</h1> <h1>Welcome to LegoLog!</h1>
<button>Shop LEGO® Now</button> <a href="/featured/"><button>Shop LEGO® Now</button></a>
</div> </div>
</div> </div>
<div class="carousel-cell"> <div class="carousel-cell">
<img class="carousel-image" src="res/warehouse.png" alt=""> <img class="carousel-image" src="res/warehouse.png" alt="">
<div class="carousel-caption"> <div class="carousel-caption">
<h1>Our state of the art warehouse ensures your speedy delivery</h1> <h1>Our state of the art warehouse ensures your speedy delivery</h1>
<button>Find Out More</button> <a href="/about/"><button>Find Out More</button></a>
</div> </div>
</div> </div>
<div class="carousel-cell"> <div class="carousel-cell">
<img class="carousel-image" src="res/space.png" alt=""> <img class="carousel-image" src="res/space.png" alt="">
<div class="carousel-caption"> <div class="carousel-caption">
<h1>NASA's Shuttle Discovery</h1> <h1>NASA's Shuttle Discovery</h1>
<button>Shop Space Now</button> <a href="/search/?q=nasa"><button>Shop Space Now</button></a>
</div> </div>
</div> </div>
<div class="carousel-cell"> <div class="carousel-cell">

View File

@@ -0,0 +1,46 @@
<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 type="module" 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.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>
<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="Featured Lego Sets"
getroute="/api/sets/featured"
type="set">
</product-list-component>
</limited-margin>
</body>

View File

@@ -38,20 +38,5 @@
<navbar-component></navbar-component> <navbar-component></navbar-component>
<limited-margin> <limited-margin>
<storefront-component id="store"></storefront-component> <storefront-component id="store"></storefront-component>
<!-- <canvas id="webglviewer" width="300" height="300"></canvas> -->
<!-- <canvas id="webglviewer" width="300" height="300"></canvas>
<canvas id="webglviewer" width="300" height="300"></canvas>
<canvas id="webglviewer" width="300" height="300"></canvas>
<canvas id="webglviewer" width="300" height="300"></canvas>
<canvas id="webglviewer" width="300" height="300"></canvas>
<canvas id="webglviewer" width="300" height="300"></canvas>
<canvas id="webglviewer" width="300" height="300"></canvas>
<canvas id="webglviewer" width="300" height="300"></canvas>
<canvas id="webglviewer" width="300" height="300"></canvas>
<canvas id="webglviewer" width="300" height="300"></canvas>
<canvas id="webglviewer" width="300" height="300"></canvas> -->
</limited-margin> </limited-margin>
</body> </body>

View File

@@ -1,9 +1,9 @@
<html> <html>
<head> <head>
<title>LegoLog Home!</title> <title>LegoLog!</title>
<meta name="viewport"> <meta name="viewport">
<link rel="icon" type="image/svg+xml" href="/res/favicon.svg"> <link rel="icon" type="image/svg+xml" href="/res/favicon.svg">
<link rel="stylesheet" type="text/css" href="/global.css"> <link rel="stylesheet" type="text/css" href="../global.css">
<!-- Fonts --> <!-- Fonts -->
<link rel="preconnect" href="https://fonts.googleapis.com"> <link rel="preconnect" href="https://fonts.googleapis.com">
@@ -34,19 +34,5 @@
<navbar-component></navbar-component> <navbar-component></navbar-component>
<limited-margin> <limited-margin>
<product-listing-component></product-listing-component> <product-listing-component></product-listing-component>
<!-- <canvas id="webglviewer" width="300" height="300"></canvas> -->
<!-- <canvas id="webglviewer" width="300" height="300"></canvas>
<canvas id="webglviewer" width="300" height="300"></canvas>
<canvas id="webglviewer" width="300" height="300"></canvas>
<canvas id="webglviewer" width="300" height="300"></canvas>
<canvas id="webglviewer" width="300" height="300"></canvas>
<canvas id="webglviewer" width="300" height="300"></canvas>
<canvas id="webglviewer" width="300" height="300"></canvas>
<canvas id="webglviewer" width="300" height="300"></canvas>
<canvas id="webglviewer" width="300" height="300"></canvas>
<canvas id="webglviewer" width="300" height="300"></canvas>
<canvas id="webglviewer" width="300" height="300"></canvas> -->
</limited-margin> </limited-margin>
</body> </body>

View File

@@ -0,0 +1,54 @@
<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 type="module" 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.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>
<script type="module" src="index.mjs"></script>
</head>
<body>
<notificationbar-component></notificationbar-component>
<navbar-component></navbar-component>
<limited-margin>
<script>
console.log(`/api/search/${window.location.search}`)
// parse out the search query using the query object
const query = new URLSearchParams(window.location.search)
// get the search query
const searchQuery = query.get('q')
document.write(/* html */`
<product-list-component id="results"
title="Search Results for '${searchQuery}'"
getroute="/api/search/${window.location.search}">
</product-list-component>
`);
</script>
</limited-margin>
</body>

View File

@@ -40,17 +40,17 @@ For all endpoints that query, the following parameters are supported:
tags: tags to include in search tags: tags to include in search
total: total results (not pageified)
per_page: results to include per page per_page: results to include per page
page: starting page page: page requested
pages: pages to return starting from page
q: string to search for (fuzzy) q: string to search for (fuzzy)
brick: brick to search for (absolute) brick: brick to search for (absolute type, fuzzy string)
set: brick to search for (absolute) set: brick to search for (absolute, fuzzy string)
### /api/special/ ### /api/special/

View File

@@ -1,6 +1,67 @@
const ControllerMaster = require('./controller-master.js');
const Database = require('../database/database.js'); const Database = require('../database/database.js');
const PgFormat = require('pg-format'); const PgFormat = require('pg-format');
async function Search(fuzzyString) {
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"
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 ~* $1 OR lego_brick.name ~* $1 OR tag.name ~* $1
`, [fuzzyString]);
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',
};
}
// order by levenshtine distance
const bricks = dbres.rows;
bricks.sort((a, b) => {
const aName = a.name.toLowerCase();
const bName = b.name.toLowerCase();
const aTag = a.tag.toLowerCase();
const bTag = b.tag.toLowerCase();
const aFuzzy = fuzzyString.toLowerCase();
const bFuzzy = fuzzyString.toLowerCase();
const aDist = ControllerMaster.LevenshteinDistance(aName, aFuzzy);
const bDist = ControllerMaster.LevenshteinDistance(bName, bFuzzy);
const aTagDist = ControllerMaster.LevenshteinDistance(aTag, aFuzzy);
const bTagDist = ControllerMaster.LevenshteinDistance(bTag, bFuzzy);
if (aDist < bDist) {
return -1;
} else if (aDist > bDist) {
return 1;
} else {
if (aTagDist < bTagDist) {
return -1;
} else if (aTagDist > bTagDist) {
return 1;
} else {
return 0;
}
}
});
// combine tags into a single array
for (const brick of bricks) {
brick.type = 'brick';
brick.tags = brick.tag.split(',');
}
return bricks;
}
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(`
@@ -24,6 +85,7 @@ async function GetBulkBricks(bricksArr) {
const bricks = dbres.rows; const bricks = dbres.rows;
// combine tags into a single array // combine tags into a single array
for (const brick of bricks) { for (const brick of bricks) {
brick.type = 'brick';
brick.tags = brick.tag.split(','); brick.tags = brick.tag.split(',');
} }
@@ -78,6 +140,7 @@ async function GetBrick(brickId) {
} }
module.exports = { module.exports = {
Search,
GetBulkBricks, GetBulkBricks,
GetBrick, GetBrick,
}; };

View File

@@ -1,3 +1,56 @@
// http://stackoverflow.com/questions/11919065/sort-an-array-by-the-levenshtein-distance-with-best-performance-in-javascript
function LevenshteinDistance(s, t) {
const d = []; // 2d matrix
// Step 1
const n = s.length;
const m = t.length;
if (n === 0) return m;
if (m === 0) return n;
// Create an array of arrays in javascript (a descending loop is quicker)
for (let i = n; i >= 0; i--) d[i] = [];
// Step 2
for (let i = n; i >= 0; i--) d[i][0] = i;
for (let j = m; j >= 0; j--) d[0][j] = j;
// Step 3
for (let i = 1; i <= n; i++) {
const si = s.charAt(i - 1);
// Step 4
for (let j = 1; j <= m; j++) {
// Check the jagged ld total so far
if (i === j && d[i][j] > 4) return n;
const tj = t.charAt(j - 1);
const cost = (si === tj) ? 0 : 1; // Step 5
// Calculate the minimum
let mi = d[i - 1][j] + 1;
const b = d[i][j - 1] + 1;
const c = d[i - 1][j - 1] + cost;
if (b < mi) mi = b;
if (c < mi) mi = c;
d[i][j] = mi; // Step 6
// Damerau transposition
if (i > 1 && j > 1 && si === t.charAt(j - 2) && s.charAt(i - 2) === tj) {
d[i][j] = Math.min(d[i][j], d[i - 2][j - 2] + cost);
}
}
}
// Step 7
return d[n][m];
}
module.exports = { module.exports = {
LevenshteinDistance,
ResultsPerPage: 16, ResultsPerPage: 16,
}; };

View File

@@ -1,6 +1,65 @@
const ControllerMaster = require('./controller-master.js'); const ControllerMaster = require('./controller-master.js');
const Database = require('../database/database.js'); const Database = require('../database/database.js');
async function Search(fuzzyString) {
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"
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 lego_set.id ~* $1 OR lego_set.name ~* $1 OR tag.name ~* $1
`, [fuzzyString]);
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',
};
}
// order by levenshtine distance
const sets = dbres.rows;
sets.sort((a, b) => {
const aName = a.name.toLowerCase();
const bName = b.name.toLowerCase();
const aTag = a.tag.toLowerCase();
const bTag = b.tag.toLowerCase();
const aFuzzy = fuzzyString.toLowerCase();
const bFuzzy = fuzzyString.toLowerCase();
const aDist = ControllerMaster.LevenshteinDistance(aName, aFuzzy);
const bDist = ControllerMaster.LevenshteinDistance(bName, bFuzzy);
const aTagDist = ControllerMaster.LevenshteinDistance(aTag, aFuzzy);
const bTagDist = ControllerMaster.LevenshteinDistance(bTag, bFuzzy);
if (aDist < bDist) {
return -1;
} else if (aDist > bDist) {
return 1;
} else {
if (aTagDist < bTagDist) {
return -1;
} else if (aTagDist > bTagDist) {
return 1;
} else {
return 0;
}
}
});
// combine tags into a single array
for (const set of sets) {
set.type = 'set';
set.tags = set.tag.split(',');
}
return sets;
}
async function GetSet(setId) { async function GetSet(setId) {
await Database.Query('BEGIN TRANSACTION;'); await Database.Query('BEGIN TRANSACTION;');
const dbres = await Database.Query(` const dbres = await Database.Query(`
@@ -78,6 +137,7 @@ async function GetSets(page, resPerPage) {
} }
module.exports = { module.exports = {
Search,
GetSet, GetSet,
GetSets, GetSets,
}; };

View File

@@ -1,17 +0,0 @@
const Models = require('./model-manager.js');
const { DataTypes, DataConstraints } = require('../database/database.js');
const { ORM } = Models.Database;
function Init() {
ORM.addModel('catagory', {
id: {
type: DataTypes.INTEGER,
constraints: [DataConstraints.PRIMARY_KEY, DataConstraints.NOT_NULL],
},
name: DataTypes.VARCHAR(100),
});
}
module.exports = {
Init,
};

View File

@@ -1,24 +0,0 @@
const Models = require('./model-manager.js');
const { DataTypes, DataConstraints } = require('../database/database.js');
const { ORM } = Models.Database;
function Init() {
ORM.addModel('lego_brick', {
id: {
type: DataTypes.VARCHAR(50),
constraints: [DataConstraints.PRIMARY_KEY, DataConstraints.NOT_NULL],
},
catagory: {
type: DataTypes.INTEGER,
constraints: [DataConstraints.FOREIGN_KEY_REF(ORM.model('catagory').property('id'))],
},
date_released: DataTypes.TIMESTAMP,
dimenions_x: DataTypes.DECIMAL,
dimenions_y: DataTypes.DECIMAL,
dimenions_z: DataTypes.DECIMAL,
});
}
module.exports = {
Init,
};

View File

@@ -1,19 +0,0 @@
const fs = require('fs');
function Init(databaseInstance) {
module.exports.Database = databaseInstance;
module.exports.Models = {};
const files = fs.readdirSync(__dirname).reverse();
files.forEach(file => {
if (file !== 'model-manager.js') {
const model = require(`./${file}`);
module.exports.Models[file.split('.')[0]] = model;
model.Init();
}
});
}
module.exports = {
Init,
};

View File

@@ -1,4 +1,4 @@
// 15 days from now // AppEng Deadline
const EndDate = new Date(1651269600 * 1000); const EndDate = new Date(1651269600 * 1000);
function Special(req, res, next) { function Special(req, res, next) {

View File

@@ -1,42 +1,102 @@
const ControllerMaster = require('../controllers/controller-master.js');
const BrickController = require('../controllers/brick-controller.js');
const SetController = require('../controllers/set-controller.js');
async function Search(req, res) { async function Search(req, res) {
const q = req.query.q; const q = req.query.q;
console.log(q);
const pageRequested = req.query.page || 1;
const perPage = req.query.per_page || 16;
// TODO: it is tricky to do a database offset / limit here
// due to the fact that we have to combine the results of
// the two queries, look into me (maybe merging the queries)
const brickResults = await BrickController.Search(q);
const setResults = await SetController.Search(q);
if (brickResults.error && setResults.error) {
return res.send(JSON.stringify({
error: 'Not found',
long: 'What you are looking for do not exist',
}));
}
let count = 0;
if (brickResults) count += brickResults.length;
if (setResults) count += setResults.length;
if (brickResults.error) {
// remove after the requested page
setResults.splice(perPage * pageRequested);
// remove before the requested page
setResults.splice(0, perPage * (pageRequested - 1));
return res.send(JSON.stringify({
data: setResults,
page: {
total: count,
per_page: perPage,
page: pageRequested,
},
}));
}
if (setResults.error) {
// remove after the requested page
brickResults.splice(perPage * pageRequested);
// remove before the requested page
brickResults.splice(0, perPage * (pageRequested - 1));
return res.send(JSON.stringify({
data: brickResults,
page: {
total: count,
per_page: perPage,
page: pageRequested,
},
}));
}
// organise into the most relevant 10 results
const results = [...brickResults, ...setResults];
results.sort((a, b) => {
const aName = a.name.toLowerCase();
const bName = b.name.toLowerCase();
const aTag = a.tag.toLowerCase();
const bTag = b.tag.toLowerCase();
const aFuzzy = q.toLowerCase();
const bFuzzy = q.toLowerCase();
const aDist = ControllerMaster.LevenshteinDistance(aName, aFuzzy);
const bDist = ControllerMaster.LevenshteinDistance(bName, bFuzzy);
const aTagDist = ControllerMaster.LevenshteinDistance(aTag, aFuzzy);
const bTagDist = ControllerMaster.LevenshteinDistance(bTag, bFuzzy);
if (aDist < bDist) {
return -1;
} else if (aDist > bDist) {
return 1;
} else {
if (aTagDist < bTagDist) {
return -1;
} else if (aTagDist > bTagDist) {
return 1;
} else {
return 0;
}
}
});
// remove after the requested page
results.splice(perPage * pageRequested);
// remove before the requested page
results.splice(0, perPage * (pageRequested - 1));
res.send(JSON.stringify({ res.send(JSON.stringify({
data: [ data: results,
{ page: {
id: '1010', total: count,
type: 'set', per_page: perPage,
name: q, page: pageRequested,
price: '1', },
discount: '1',
tag: '1',
},
{
id: '1',
type: 'brick',
name: q,
price: '1',
discount: '1',
tag: '1',
},
{
id: '1',
type: 'brick',
name: q,
price: '1',
discount: '1',
tag: '1',
},
{
id: '1',
type: 'brick',
name: q,
price: '1',
discount: '1',
tag: '1',
},
],
})); }));
} }

View File

@@ -28,10 +28,9 @@ async function Featured(req, res) {
res.send(JSON.stringify({ res.send(JSON.stringify({
data: [...sets], data: [...sets],
page: { page: {
total_sent: sets.length, total: sets.length,
per_page: 8, per_page: 8,
current_page: 1, page: 1,
last_page: 1,
}, },
})); }));
} }