Skip to content

Commit

Permalink
Merge pull request #159 from WKHAllen/dev
Browse files Browse the repository at this point in the history
Release 1.2.2
  • Loading branch information
WKHAllen authored Aug 4, 2020
2 parents 0cea0db + 137172d commit 98909ba
Show file tree
Hide file tree
Showing 13 changed files with 194 additions and 27 deletions.
4 changes: 2 additions & 2 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import * as hbs from 'express-handlebars';
import * as session from 'express-session';
import * as bodyParser from 'body-parser';
import * as routes from './routes';
import { renderPage, Request, Response } from './routes/util';
import { renderPage, Request, Response, NextFunction } from './routes/util';

const debug = Boolean(Number(process.env.DEBUG));
const port = Number(process.env.PORT);
Expand All @@ -15,7 +15,7 @@ var app = express();

// Disable caching for authentication purposes
app.set('etag', false);
app.use((req, res, next) => {
app.use((req: Request, res: Response, next: NextFunction) => {
res.set('Cache-Control', 'no-store, no-cache, must-revalidate, private');
next();
});
Expand Down
16 changes: 16 additions & 0 deletions src/routes/admin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -207,3 +207,19 @@ router.get('/users', adminAuth, (req: Request, res: Response) => {
renderPage(req, res, 'admin-users', { users: users, orderBy: orderBy, orderDirection: orderDirection });
});
});

// Admin books page
router.get('/books', adminAuth, (req: Request, res: Response) => {
var orderBy = req.query.orderBy as string || 'listedTimestamp';
var orderDirection = req.query.orderDirection as string || 'ASC';
services.AdminService.getBooks(orderBy, orderDirection, (books) => {
renderPage(req, res, 'admin-books', { books: books, orderBy: orderBy, orderDirection: orderDirection });
});
});

// Admin rows page
router.get('/rows', adminAuth, (req: Request, res: Response) => {
services.AdminService.getRowCount((tables) => {
renderPage(req, res, 'admin-rows', { tables: tables });
});
});
4 changes: 2 additions & 2 deletions src/routes/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -185,11 +185,11 @@ export function validBook(form: BookForm, callback: (success: boolean, error: st
callback(false, 'Please enter a description of at most 1024 characters.');
} else {
// Check ISBN10
if (ISBN10.length > 0 && !validISBN(ISBN10)) {
if (ISBN10.length > 0 && (ISBN10.length !== 10 || !validISBN(ISBN10))) {
callback(false, 'Please enter a valid ISBN-10.');
} else {
// Check ISBN13
if (ISBN13.length > 0 && !validISBN(ISBN13)) {
if (ISBN13.length > 0 && (ISBN13.length !== 13 || !validISBN(ISBN13))) {
callback(false, 'Please enter a valid ISBN-13.');
} else {
callback(true, null, {
Expand Down
21 changes: 20 additions & 1 deletion src/services/admin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,8 @@ export module AdminService {
query_to_xml(format('SELECT COUNT(*) AS cnt FROM %I.%I', table_schema, table_name), false, true, '') AS xml_count
FROM information_schema.tables
WHERE table_schema = 'public'
) t;
) t
ORDER BY rows DESC;
`;
mainDB.execute(sql, [], (rows) => {
if (callback) callback(rows);
Expand Down Expand Up @@ -98,4 +99,22 @@ export module AdminService {
});
}

// Get relevant information on all books
export function getBooks(orderBy: string, orderDirection: string, callback?: rowsCallback) {
var sql = `
SELECT
bookId, title, author,
CONCAT(NBUser.firstname, ' ', NBUser.lastname) AS listedBy,
Department.name as department,
courseNumber, price, listedTimestamp
FROM Book
JOIN NBUser ON Book.userId = NBUser.id
JOIN Department ON Book.departmentId = Department.id
ORDER BY ${orderBy} ${orderDirection};
`;
mainDB.execute(sql, [], (rows) => {
if (callback) callback(rows);
});
}

}
9 changes: 7 additions & 2 deletions src/static/css/main.css
Original file line number Diff line number Diff line change
Expand Up @@ -152,12 +152,12 @@ footer .footer-list-link {
/* Index Page */

#index .card {
margin: 0 auto;
border: none;
padding: 0px 20px;
}

#index .card-link {
#index .full-card-link {
margin: 0 auto;
text-decoration: none;
}

Expand Down Expand Up @@ -481,3 +481,8 @@ footer .footer-list-link {
margin: 0;
border-radius: 0;
}

#rows-chart-container {
max-width: min(90vw, 70vh);
margin: 24px auto;
}
52 changes: 52 additions & 0 deletions src/static/js/admin-rows.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
function selectColor(number) {
var hue = Math.round(number * 137.508) % 360; // golden angle approximation
return `hsl(${hue}, 100%, 50%)`;
}

function generateColors(numColors) {
var colors = [];
for (let i = 0; i < numColors; i++) {
colors.push(selectColor(i));
}
return colors;
}

function getRowsData() {
var tables = [];
var rows = [];

$('#rows-table').find($('tr')).find($('.rows-table-table'))
.each(function() {
tables.push($(this).text());
});

$('#rows-table').find($('tr')).find($('.rows-table-rows'))
.each(function() {
rows.push($(this).text());
});

return { tables, rows };
}

function createChart() {
var img = new Image();
img.src = 'https://www.norsebooks.com/img/favicon.png';

var rowsData = getRowsData();
var colors = generateColors(rowsData.tables.length);
var ctx = document.getElementById('rows-chart').getContext('2d');
var chart = new Chart(ctx, {
type: 'doughnut',
data: {
labels: rowsData.tables,
datasets: [{
data: rowsData.rows,
backgroundColor: colors
}]
}
});
}

$(() => {
createChart();
});
File renamed without changes.
20 changes: 17 additions & 3 deletions src/static/js/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,32 @@ function getLastBookId() {
else return bookCard.getElementsByTagName('a')[0].href.slice(-4);
}

// Remove the last book on the page
function deleteLastBook() {
var bookCard = document.getElementById('index').lastElementChild;
bookCard.parentNode.removeChild(bookCard);
}

// Transform book image URLs to load smaller images
function smallerImageURL(imageUrl) {
const imgStart = 'https://res.cloudinary.com/norsebooks/image/upload';
const imgWidth = 300;
if (imageUrl.startsWith(imgStart)) {
var imgEnd = imageUrl.slice(imgStart.length + 1);
return `${imgStart}/w_${imgWidth}/${imgEnd}`;
} else {
return imageUrl;
}
}

// Add a book to the end of the page
function addBook(book) {
var courseNumber = book.coursenumber ? ' ' + book.coursenumber : '';
// <div class="card-container col-12 col-md-6 col-lg-4 mb-4">
// <a href="/book/${book.bookid}">
var newBookLink = document.createElement('a');
newBookLink.classList.add('card-link');
newBookLink.classList.add('full-card-link');
newBookLink.href = `/book/${book.bookid}`;
// <div class="card-container col-12 col-md-6 col-lg-4 mb-4">
var newBook = document.createElement('div');
newBook.classList.add('card-container', 'col-12', 'col-md-6', 'col-lg-4', 'mb-4');
// <div class="card" style="width: 18rem;">
Expand All @@ -28,7 +42,7 @@ function addBook(book) {
newCard.appendChild(imgLink);
// <img src="${book.imageurl}" class="card-img-top thumbnail" alt="...">
var newImg = document.createElement('img');
newImg.src = book.imageurl;
newImg.src = smallerImageURL(book.imageurl);
newImg.classList.add('card-img-top', 'thumbnail', 'p-1', 'pt-3');
newImg.alt = '...';
imgLink.appendChild(newImg);
Expand Down
23 changes: 11 additions & 12 deletions src/static/js/new-book.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
$('#search-google-api').on('keyup', function(){
const apiKey = 'AIzaSyCZb0ZbkRKgq06SprrrF3DNbxCZdjp8TP0';

$('#search-google-api').on('keyup', function() {
var searchFieldValue = document.getElementById('search-google-api').value;
searchFieldValue = searchFieldValue.split(' ').join('+');

var container = document.getElementById('result-list-container');

var resultListItems = document.querySelectorAll('.search-result-link');

var url = 'https://www.googleapis.com/books/v1/volumes?key=' + 'AIzaSyCZb0ZbkRKgq06SprrrF3DNbxCZdjp8TP0' + '&q=' + searchFieldValue + '&printType=books';
var url = `https://www.googleapis.com/books/v1/volumes?key=${apiKey}&q=${searchFieldValue}&printType=books`;
var xhr = new XMLHttpRequest();
if (searchFieldValue !== '') {
xhr.open('GET', url);
Expand All @@ -19,7 +21,6 @@ $('#search-google-api').on('keyup', function(){
var resultArray = responseText.items;
for (var i = 0; i < 5; i++) {
resultListItems[i].setAttribute('onclick', `populateBookInfo('${resultArray[i].id}')`);
console.log(responseText);
var titleSpan = document.createElement('span');
titleSpan.classList.add('title');
titleSpan.innerText = resultArray[i].volumeInfo.title;
Expand All @@ -40,10 +41,10 @@ $('#search-google-api').on('keyup', function(){

function populateBookInfo(volumeId) {
document.getElementById('result-list-container').style.display = 'none';
url = `https://www.googleapis.com/books/v1/volumes/${volumeId}?key=AIzaSyCZb0ZbkRKgq06SprrrF3DNbxCZdjp8TP0`;
url = `https://www.googleapis.com/books/v1/volumes/${volumeId}?key=${apiKey}`;
var xhr = new XMLHttpRequest();
xhr.open('GET', url);
xhr.send()
xhr.send();
xhr.onreadystatechange = (e) => {
if (xhr.status === 200) {
var responseText = JSON.parse(xhr.responseText);
Expand All @@ -52,15 +53,13 @@ function populateBookInfo(volumeId) {
var isbn10Field = document.querySelector('#ISBN10');
var isbn13Field = document.querySelector('#ISBN13');
titleField.value = responseText.volumeInfo.title;
titleField.classList.add("autofill-success")
titleField.classList.add('autofill-success');
authorField.value = responseText.volumeInfo.authors[0];
authorField.classList.add("autofill-success")
authorField.classList.add('autofill-success');
isbn10Field.value = responseText.volumeInfo.industryIdentifiers[0].identifier;
isbn13Field.value = responseText.volumeInfo.industryIdentifiers[1].identifier;
isbn10Field.classList.add("autofill-success")
isbn13Field.classList.add("autofill-success")
console.log(responseText);
// MAKE FIELDS GREEN!
isbn10Field.classList.add('autofill-success');
isbn13Field.classList.add('autofill-success');
}
}
}
}
33 changes: 33 additions & 0 deletions src/views/admin-books.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<script type="text/javascript" src="/js/column-sort.js"></script>
<div class="container">
<h1 class="centered">Books</h1>
<p class="justified">This page contains relevant information on books. To sort by a column, click on the column header. <a href="/admin">Click here</a> to return to the admin page.</p>
<table class="table table-striped table-hover table-sm">
<thead>
<tr>
<th scope="col"><a href="?orderBy=title" id="title-header">Title</a></th>
<th scope="col"><a href="?orderBy=author" id="author-header">Author</a></th>
<th scope="col"><a href="?orderBy=listedBy" id="listedBy-header">Listed by</a></th>
<th scope="col"><a href="?orderBy=department" id="department-header">Department</a></th>
<th scope="col"><a href="?orderBy=courseNumber" id="courseNumber-header">Course number</a></th>
<th scope="col"><a href="?orderBy=price" id="price-header">Price</a></th>
<th scope="col"><a href="?orderBy=listedTimestamp" id="listedTimestamp-header">Listed</a></th>
</tr>
</thead>
<tbody>
{{#each books}}
<tr>
<td><a href="/book/{{this.bookid}}">{{this.title}}</a></td>
<td>{{this.author}}</td>
<td>{{this.listedby}}</td>
<td>{{this.department}}</td>
<td>{{this.coursenumber}}</td>
<td>${{this.price}}</td>
<td class="timestamp">{{this.listedtimestamp}}</td>
</tr>
{{/each}}
</tbody>
</table>
<span class="hidden" id="order-by">{{orderBy}}</span>
<span class="hidden" id="order-direction">{{orderDirection}}</span>
</div>
25 changes: 25 additions & 0 deletions src/views/admin-rows.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<script type="text/javascript" src="https://cdn.jsdelivr.net/npm/chart.js@2.9.3/dist/Chart.min.js"></script>
<script type="text/javascript" src="/js/admin-rows.js"></script>
<div class="container">
<h1 class="centered">Database Rows</h1>
<p class="justified">This page shows how the database is divided. See the chart or the table below to see how many rows each table uses. <a href="/admin">Click here</a> to return to the admin page.</p>
<div id="rows-chart-container">
<canvas id="rows-chart" width="400" height="400"></canvas>
</div>
<table class="table table-striped table-hover table-sm">
<thead>
<tr>
<th scope="col">Table</th>
<th scope="col">Rows</th>
</tr>
</thead>
<tbody id="rows-table">
{{#each tables}}
<tr>
<td class="rows-table-table">{{this.table}}</td>
<td class="rows-table-rows">{{this.rows}}</td>
</tr>
{{/each}}
</tbody>
</table>
</div>
2 changes: 1 addition & 1 deletion src/views/admin-users.html
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<script type="text/javascript" src="/js/admin-users.js"></script>
<script type="text/javascript" src="/js/column-sort.js"></script>
<div class="container">
<h1 class="centered">Users</h1>
<p class="justified">This page contains relevant information on users. To sort by a column, click on the column header. <a href="/admin">Click here</a> to return to the admin page.</p>
Expand Down
12 changes: 8 additions & 4 deletions src/views/admin.html
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,10 @@ <h2>Stats</h2>
</a>
</div>
<div class="col-4">
<div class="stats-label">Books</div>
<div class="stats-value" id="numBooks">...</div>
<a href="/admin/books">
<div class="stats-label">Books</div>
<div class="stats-value" id="numBooks">...</div>
</a>
</div>
<div class="col-4">
<div class="stats-label">Sold</div>
Expand All @@ -33,8 +35,10 @@ <h2>Stats</h2>
<div class="stats-value" id="numTables">...</div>
</div>
<div class="col-4">
<div class="stats-label">Rows</div>
<div class="stats-value" id="numRows">...</div>
<a href="/admin/rows">
<div class="stats-label">Rows</div>
<div class="stats-value" id="numRows">...</div>
</a>
</div>
<div class="col-4">
<div class="stats-label">Capacity</div>
Expand Down

0 comments on commit 98909ba

Please sign in to comment.