Add basic authentication to non-GET requests and update library management features
- Integrated express-basic-auth middleware in index.js to secure non-GET routes with basic authentication. - Updated libraryManager.py to use HTTPBasicAuth for API requests, enhancing security for book management operations. - Modified public/index.html to improve the user interface with a new search feature and dynamic book table. - Removed obsolete public/library.html file to streamline the project structure. - Updated package.json and package-lock.json to include express-basic-auth as a new dependency.
This commit is contained in:
@@ -3,34 +3,145 @@
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>ISBN Scanner and Title Lookup</title>
|
||||
<title>Ramsey Library</title>
|
||||
<link rel="stylesheet" href="styles.css">
|
||||
</head>
|
||||
<body>
|
||||
<h1>Scan a Book ISBN or Search by Title</h1>
|
||||
<div>
|
||||
<label for="camera-select">Select Camera:</label>
|
||||
<select id="camera-select"></select>
|
||||
<!-- Confirm mode toggle -->
|
||||
<input type="checkbox" id="confirm-mode">
|
||||
<label for="confirm-mode">Confirm mode</label>s
|
||||
</div>
|
||||
<div id="interactive" class="viewport"></div>
|
||||
<div>
|
||||
<h2>Or search for a book by title</h2>
|
||||
<input type="text" id="title-input" placeholder="Enter book title">
|
||||
<button id="search-title">Search by Title</button>
|
||||
</div>
|
||||
<div id="book-info"></div>
|
||||
<div id="prompt">
|
||||
<p id="prompt-message"></p>
|
||||
<p id="book-title"></p>
|
||||
<p id="book-author"></p>
|
||||
<p id="book-desc"></p>
|
||||
<button id="confirm">Add to Database</button>
|
||||
<button id="edit-title">No, I'll add a title</button>
|
||||
</div>
|
||||
<script src="https://cdn.jsdelivr.net/npm/@ericblade/quagga2/dist/quagga.js"></script>
|
||||
<script src="script.js"></script>
|
||||
<h1>Ramsey Library</h1>
|
||||
<input type="text" id="search-bar" placeholder="Search by title, author, publisher, etc.">
|
||||
|
||||
<table id="book-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th onclick="sortTable(0)">Title</th>
|
||||
<th onclick="sortTable(1)">Authors</th>
|
||||
<th onclick="sortTable(2)">Publisher</th>
|
||||
<th onclick="sortTable(3)">Published Date</th>
|
||||
<th onclick="sortTable(4)">ISBN</th>
|
||||
<th onclick="sortTable(5)">Status</th>
|
||||
<th>Checkout</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="book-table-body">
|
||||
<!-- Book rows will be inserted here -->
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<script>
|
||||
// Fetch all books when the page loads
|
||||
window.onload = fetchBooks('');
|
||||
|
||||
document.getElementById('search-bar').addEventListener('input', function() {
|
||||
fetchBooks(this.value);
|
||||
});
|
||||
|
||||
function fetchBooks(query = '') {
|
||||
let url = '/search-title';
|
||||
if (query) {
|
||||
url += `?title=${encodeURIComponent(query)}&internalOnly=true`;
|
||||
}
|
||||
fetch(url)
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.results) {
|
||||
displayBooks(data.results);
|
||||
} else {
|
||||
document.getElementById('book-table-body').innerHTML = '<tr><td colspan="5">No books found</td></tr>';
|
||||
}
|
||||
})
|
||||
.catch(error => console.error('Error fetching books:', error));
|
||||
}
|
||||
|
||||
function displayBooks(books) {
|
||||
console.debug('Books:', books);
|
||||
const tbody = document.getElementById('book-table-body');
|
||||
tbody.innerHTML = '';
|
||||
|
||||
books.forEach(book => {
|
||||
const row = document.createElement('tr');
|
||||
const isDisabled = book.status === 'Checked Out' ? 'disabled' : '';
|
||||
row.innerHTML = `
|
||||
<td><a href="/book/${book.isbn}" target="_blank">${book.title || ''}</a></td>
|
||||
<td>${book.authors ? book.authors : ''}</td>
|
||||
<td>${book.publishers || ''}</td>
|
||||
<td>${book.publishedDate || ''}</td>
|
||||
<td>${book.isbn || ''}</td>
|
||||
<td>${book.status || ''}</td>
|
||||
<td><button id="checkout-btn-${book.isbn}" onclick="requestCheckout('${book.isbn}')" ${isDisabled}>Checkout</button></td>
|
||||
`;
|
||||
tbody.appendChild(row);
|
||||
});
|
||||
}
|
||||
|
||||
// Utility function to get a cookie by name
|
||||
function getCookie(name) {
|
||||
const value = `; ${document.cookie}`;
|
||||
const parts = value.split(`; ${name}=`);
|
||||
if (parts.length === 2) return parts.pop().split(';').shift();
|
||||
}
|
||||
|
||||
// Utility function to set a cookie
|
||||
function setCookie(name, value, days) {
|
||||
const date = new Date();
|
||||
date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));
|
||||
const expires = `expires=${date.toUTCString()}`;
|
||||
document.cookie = `${name}=${value}; ${expires}; path=/`;
|
||||
}
|
||||
|
||||
// Client-side function to request checkout
|
||||
function requestCheckout(isbn) {
|
||||
let email = getCookie('lastEmail') || prompt('Please enter your email:');
|
||||
let name = getCookie('lastName') || prompt('Please enter your name:');
|
||||
|
||||
if (!email || !name) {
|
||||
alert('Email and name are required to proceed with checkout.');
|
||||
return;
|
||||
}
|
||||
|
||||
// Store the email and name in cookies for future use
|
||||
setCookie('lastEmail', email, 7); // Store for 7 days
|
||||
setCookie('lastName', name, 7); // Store for 7 days
|
||||
|
||||
fetch(`/api/checkout/${isbn}`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({ email, name })
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
alert('Checkout request sent successfully.');
|
||||
} else {
|
||||
alert(data.error || 'Failed to send checkout request.');
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error:', error);
|
||||
alert('An error occurred while sending the checkout request.');
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function sortTable(columnIndex) {
|
||||
const table = document.getElementById('book-table');
|
||||
const rows = Array.from(table.rows).slice(1);
|
||||
const isAscending = table.getAttribute('data-sort-order') === 'asc';
|
||||
const direction = isAscending ? 1 : -1;
|
||||
|
||||
rows.sort((a, b) => {
|
||||
const aText = a.cells[columnIndex].innerText.toLowerCase();
|
||||
const bText = b.cells[columnIndex].innerText.toLowerCase();
|
||||
|
||||
if (aText < bText) return -1 * direction;
|
||||
if (aText > bText) return 1 * direction;
|
||||
return 0;
|
||||
});
|
||||
|
||||
rows.forEach(row => table.tBodies[0].appendChild(row));
|
||||
table.setAttribute('data-sort-order', isAscending ? 'desc' : 'asc');
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1,147 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Ramsey Library</title>
|
||||
<link rel="stylesheet" href="styles.css">
|
||||
</head>
|
||||
<body>
|
||||
<h1>Ramsey Library</h1>
|
||||
<input type="text" id="search-bar" placeholder="Search by title, author, publisher, etc.">
|
||||
|
||||
<table id="book-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th onclick="sortTable(0)">Title</th>
|
||||
<th onclick="sortTable(1)">Authors</th>
|
||||
<th onclick="sortTable(2)">Publisher</th>
|
||||
<th onclick="sortTable(3)">Published Date</th>
|
||||
<th onclick="sortTable(4)">ISBN</th>
|
||||
<th onclick="sortTable(5)">Status</th>
|
||||
<th>Checkout</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="book-table-body">
|
||||
<!-- Book rows will be inserted here -->
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<script>
|
||||
// Fetch all books when the page loads
|
||||
window.onload = fetchBooks('');
|
||||
|
||||
document.getElementById('search-bar').addEventListener('input', function() {
|
||||
fetchBooks(this.value);
|
||||
});
|
||||
|
||||
function fetchBooks(query = '') {
|
||||
let url = '/search-title';
|
||||
if (query) {
|
||||
url += `?title=${encodeURIComponent(query)}&internalOnly=true`;
|
||||
}
|
||||
fetch(url)
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.results) {
|
||||
displayBooks(data.results);
|
||||
} else {
|
||||
document.getElementById('book-table-body').innerHTML = '<tr><td colspan="5">No books found</td></tr>';
|
||||
}
|
||||
})
|
||||
.catch(error => console.error('Error fetching books:', error));
|
||||
}
|
||||
|
||||
function displayBooks(books) {
|
||||
console.debug('Books:', books);
|
||||
const tbody = document.getElementById('book-table-body');
|
||||
tbody.innerHTML = '';
|
||||
|
||||
books.forEach(book => {
|
||||
const row = document.createElement('tr');
|
||||
const isDisabled = book.status === 'Checked Out' ? 'disabled' : '';
|
||||
row.innerHTML = `
|
||||
<td><a href="/book/${book.isbn}" target="_blank">${book.title || ''}</a></td>
|
||||
<td>${book.authors ? book.authors : ''}</td>
|
||||
<td>${book.publishers || ''}</td>
|
||||
<td>${book.publishedDate || ''}</td>
|
||||
<td>${book.isbn || ''}</td>
|
||||
<td>${book.status || ''}</td>
|
||||
<td><button id="checkout-btn-${book.isbn}" onclick="requestCheckout('${book.isbn}')" ${isDisabled}>Checkout</button></td>
|
||||
`;
|
||||
tbody.appendChild(row);
|
||||
});
|
||||
}
|
||||
|
||||
// Utility function to get a cookie by name
|
||||
function getCookie(name) {
|
||||
const value = `; ${document.cookie}`;
|
||||
const parts = value.split(`; ${name}=`);
|
||||
if (parts.length === 2) return parts.pop().split(';').shift();
|
||||
}
|
||||
|
||||
// Utility function to set a cookie
|
||||
function setCookie(name, value, days) {
|
||||
const date = new Date();
|
||||
date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));
|
||||
const expires = `expires=${date.toUTCString()}`;
|
||||
document.cookie = `${name}=${value}; ${expires}; path=/`;
|
||||
}
|
||||
|
||||
// Client-side function to request checkout
|
||||
function requestCheckout(isbn) {
|
||||
let email = getCookie('lastEmail') || prompt('Please enter your email:');
|
||||
let name = getCookie('lastName') || prompt('Please enter your name:');
|
||||
|
||||
if (!email || !name) {
|
||||
alert('Email and name are required to proceed with checkout.');
|
||||
return;
|
||||
}
|
||||
|
||||
// Store the email and name in cookies for future use
|
||||
setCookie('lastEmail', email, 7); // Store for 7 days
|
||||
setCookie('lastName', name, 7); // Store for 7 days
|
||||
|
||||
fetch(`/api/checkout/${isbn}`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({ email, name })
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
alert('Checkout request sent successfully.');
|
||||
} else {
|
||||
alert(data.error || 'Failed to send checkout request.');
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error:', error);
|
||||
alert('An error occurred while sending the checkout request.');
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function sortTable(columnIndex) {
|
||||
const table = document.getElementById('book-table');
|
||||
const rows = Array.from(table.rows).slice(1);
|
||||
const isAscending = table.getAttribute('data-sort-order') === 'asc';
|
||||
const direction = isAscending ? 1 : -1;
|
||||
|
||||
rows.sort((a, b) => {
|
||||
const aText = a.cells[columnIndex].innerText.toLowerCase();
|
||||
const bText = b.cells[columnIndex].innerText.toLowerCase();
|
||||
|
||||
if (aText < bText) return -1 * direction;
|
||||
if (aText > bText) return 1 * direction;
|
||||
return 0;
|
||||
});
|
||||
|
||||
rows.forEach(row => table.tBodies[0].appendChild(row));
|
||||
table.setAttribute('data-sort-order', isAscending ? 'desc' : 'asc');
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
36
public/scanner.html
Normal file
36
public/scanner.html
Normal file
@@ -0,0 +1,36 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>ISBN Scanner and Title Lookup</title>
|
||||
<link rel="stylesheet" href="styles.css">
|
||||
</head>
|
||||
<body>
|
||||
<h1>Scan a Book ISBN or Search by Title</h1>
|
||||
<div>
|
||||
<label for="camera-select">Select Camera:</label>
|
||||
<select id="camera-select"></select>
|
||||
<!-- Confirm mode toggle -->
|
||||
<input type="checkbox" id="confirm-mode">
|
||||
<label for="confirm-mode">Confirm mode</label>s
|
||||
</div>
|
||||
<div id="interactive" class="viewport"></div>
|
||||
<div>
|
||||
<h2>Or search for a book by title</h2>
|
||||
<input type="text" id="title-input" placeholder="Enter book title">
|
||||
<button id="search-title">Search by Title</button>
|
||||
</div>
|
||||
<div id="book-info"></div>
|
||||
<div id="prompt">
|
||||
<p id="prompt-message"></p>
|
||||
<p id="book-title"></p>
|
||||
<p id="book-author"></p>
|
||||
<p id="book-desc"></p>
|
||||
<button id="confirm">Add to Database</button>
|
||||
<button id="edit-title">No, I'll add a title</button>
|
||||
</div>
|
||||
<script src="https://cdn.jsdelivr.net/npm/@ericblade/quagga2/dist/quagga.js"></script>
|
||||
<script src="script.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user