Implement Google Books API integration for book updates and enhance library management features
- Added a new endpoint in `index.js` to update book data using the Google Books API, allowing for real-time updates based on ISBN. - Introduced a `rebuild_book_entries` function in `libraryManager.py` to facilitate user-driven updates of book entries from the Google Books API. - Enhanced error handling and user prompts for better interaction during book updates. - Updated `public/index.html` and `public/script.js` to improve the user interface and support new functionalities. - Modified styles in `public/styles.css` to enhance the layout and usability of the checkout button and location prompt.
This commit is contained in:
parent
370ad7c107
commit
f4f227b24d
@ -1,4 +1,6 @@
|
||||
const { Book, Location, Checkout, User, Sequelize, Op } = require('./models');
|
||||
const axios = require('axios');
|
||||
|
||||
// Fetch book from the local database by ISBN
|
||||
const fetchBookFromLocalDatabase = async (isbn) => {
|
||||
return await Book.findOne({ where: { isbn } });
|
||||
|
||||
53
index.js
53
index.js
@ -275,14 +275,15 @@ async function refetchBookData(startIndex = 0) {
|
||||
} catch (error) {
|
||||
if (error.response && error.response.status === 429) {
|
||||
console.error('Rate limit exceeded. Pausing requests.');
|
||||
// Implement a delay or exit the loop if necessary
|
||||
break;
|
||||
// Implement a delay
|
||||
await delay(10000); // Delay for 10 seconds
|
||||
index--; // Retry the current book
|
||||
} else {
|
||||
console.error(`Error fetching data for ISBN: ${isbn}`, error.message);
|
||||
}
|
||||
}
|
||||
|
||||
// Optional: Delay to handle rate limits
|
||||
// Delay between requests to respect rate limits
|
||||
await delay(1000); // Adjust the delay as needed
|
||||
}
|
||||
|
||||
@ -696,3 +697,49 @@ app.get('/api/books-on-loan', async (req, res) => {
|
||||
res.status(500).send('Internal Server Error');
|
||||
}
|
||||
});
|
||||
|
||||
app.post('/book/update/:isbn', async (req, res) => {
|
||||
const { isbn } = req.params;
|
||||
const googleBooksApiKey = process.env.GOOGLE_BOOKS_API_KEY;
|
||||
|
||||
try {
|
||||
// Fetch data from Google Books API
|
||||
const url = `https://www.googleapis.com/books/v1/volumes?q=isbn:${isbn}&key=${googleBooksApiKey}`;
|
||||
const response = await axios.get(url);
|
||||
|
||||
if (response.data.totalItems > 0) {
|
||||
const volumeInfo = response.data.items[0].volumeInfo;
|
||||
|
||||
// Extract data from the API response
|
||||
const updatedData = {
|
||||
title: volumeInfo.title || null,
|
||||
authors: volumeInfo.authors ? volumeInfo.authors.join(', ') : null,
|
||||
publishedDate: volumeInfo.publishedDate || null,
|
||||
description: volumeInfo.description || null,
|
||||
number_of_pages: volumeInfo.pageCount || null,
|
||||
publishers: volumeInfo.publisher || null,
|
||||
subjects: volumeInfo.categories ? volumeInfo.categories.join(', ') : null,
|
||||
cover_small: volumeInfo.imageLinks ? volumeInfo.imageLinks.smallThumbnail : null,
|
||||
cover_medium: volumeInfo.imageLinks ? volumeInfo.imageLinks.thumbnail : null,
|
||||
cover_large: volumeInfo.imageLinks ? volumeInfo.imageLinks.large : null,
|
||||
// Add more fields as needed
|
||||
};
|
||||
|
||||
// Update the book entry in the database
|
||||
const book = await Book.findOne({ where: { isbn } });
|
||||
|
||||
if (book) {
|
||||
await book.update(updatedData);
|
||||
console.log(`Book data for ISBN ${isbn} updated.`);
|
||||
res.json({ success: true, book: updatedData });
|
||||
} else {
|
||||
res.status(404).json({ error: 'Book not found in local database' });
|
||||
}
|
||||
} else {
|
||||
res.status(404).json({ error: 'No data found in Google Books API' });
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error updating book data:', error);
|
||||
res.status(500).json({ error: 'Failed to update book data' });
|
||||
}
|
||||
});
|
||||
|
||||
@ -117,6 +117,99 @@ def list_books_on_loan():
|
||||
else:
|
||||
console.print("Failed to fetch books on loan.", style="bold red")
|
||||
|
||||
def rebuild_book_entries():
|
||||
isbn = Prompt.ask("Enter ISBN of the book to rebuild")
|
||||
response = requests.get(f"{API_BASE_URL}/book/confirm/{isbn}", verify=False)
|
||||
|
||||
if response.status_code == 200:
|
||||
book_data = response.json()
|
||||
console.print("Current Book Data from Local Database:", style="bold blue")
|
||||
console.print(f"Title: {book_data['data']['title']}")
|
||||
console.print(f"Authors: {book_data['data']['authors']}")
|
||||
console.print(f"Published Date: {book_data['data'].get('publishedDate', 'N/A')}")
|
||||
console.print(f"Description: {book_data['data'].get('description', 'N/A')}")
|
||||
|
||||
# Allow the user to select fields to search with
|
||||
console.print("\nSelect fields to search the Google Books API with:")
|
||||
use_title = Prompt.ask("Use Title? (yes/no)", choices=["yes", "no"], default="yes") == "yes"
|
||||
use_authors = Prompt.ask("Use Authors? (yes/no)", choices=["yes", "no"], default="yes") == "yes"
|
||||
use_isbn = Prompt.ask("Use ISBN? (yes/no)", choices=["yes", "no"], default="yes") == "yes"
|
||||
|
||||
# Build search query based on selected fields
|
||||
query_components = []
|
||||
if use_title:
|
||||
query_components.append(f'intitle:{book_data["data"]["title"]}')
|
||||
if use_authors:
|
||||
query_components.append(f'inauthor:{book_data["data"]["authors"]}')
|
||||
if use_isbn:
|
||||
query_components.append(f'isbn:{book_data["data"]["isbn"]}')
|
||||
query = '+'.join(query_components)
|
||||
|
||||
# Query the Google Books API directly
|
||||
google_response = requests.get(f"https://www.googleapis.com/books/v1/volumes?q={query}")
|
||||
if google_response.status_code == 200:
|
||||
google_data = google_response.json()
|
||||
if google_data["totalItems"] > 0:
|
||||
# Take the first result
|
||||
volumes = google_data["items"]
|
||||
console.print("\nMultiple volumes found. Please select one:")
|
||||
for i, item in enumerate(volumes):
|
||||
volume_info = item["volumeInfo"]
|
||||
title = volume_info.get('title', 'N/A')
|
||||
authors = ', '.join(volume_info.get('authors', []))
|
||||
published_date = volume_info.get('publishedDate', 'N/A')
|
||||
|
||||
# Limit the length of the title and authors to 32 characters
|
||||
if len(title) > 32:
|
||||
title = title[:29] + '...'
|
||||
if len(authors) > 32:
|
||||
authors = authors[:29] + '...'
|
||||
|
||||
console.print(f"{i + 1}. Title: {title}, Authors: {authors}, Published Date: {published_date}")
|
||||
|
||||
selected_index = int(Prompt.ask("Enter the number of the volume you want to select")) - 1
|
||||
volume_info = volumes[selected_index]["volumeInfo"]
|
||||
console.print("\nBook Data Found from Google Books API:", style="bold green")
|
||||
console.print(f"Title: {volume_info.get('title', 'N/A')}")
|
||||
console.print(f"Authors: {', '.join(volume_info.get('authors', []))}")
|
||||
console.print(f"Published Date: {volume_info.get('publishedDate', 'N/A')}")
|
||||
console.print(f"Description: {volume_info.get('description', 'N/A')}")
|
||||
# isbn (use 13, then 10 if 13 is not present) ISBN: [{'type': 'ISBN_10', 'identifier': '0521274559'}, {'type': 'ISBN_13', 'identifier': '9780521274555'}]
|
||||
isbn = volume_info.get('industryIdentifiers', [])
|
||||
isbn = next((identifier['identifier'] for identifier in isbn if identifier['type'] == 'ISBN_13'), None)
|
||||
if not isbn:
|
||||
isbn = next((identifier['identifier'] for identifier in isbn if identifier['type'] == 'ISBN_10'), None)
|
||||
console.print(f"ISBN: {isbn}")
|
||||
|
||||
# Ask the user if they want to update the entry
|
||||
update = Prompt.ask("Do you want to update this book entry? (yes/no)", choices=["yes", "no"], default="no")
|
||||
if update == "yes":
|
||||
data = {
|
||||
"title": volume_info.get('title'),
|
||||
"authors": ', '.join(volume_info.get('authors', [])),
|
||||
"publishedDate": volume_info.get('publishedDate'),
|
||||
"description": volume_info.get('description'),
|
||||
"isbn": isbn
|
||||
}
|
||||
update_response = requests.put(
|
||||
f"{API_BASE_URL}/book/{book_data['data']['isbn']}",
|
||||
json=data,
|
||||
verify=False,
|
||||
auth=HTTPBasicAuth(USERNAME, PASSWORD)
|
||||
)
|
||||
if update_response.status_code == 200:
|
||||
console.print("Book entry updated successfully.", style="bold green")
|
||||
else:
|
||||
console.print("Failed to update book entry.", style="bold red")
|
||||
print(update_response.json())
|
||||
print(update_response.status_code)
|
||||
else:
|
||||
console.print("No data found on Google Books API.", style="bold red")
|
||||
else:
|
||||
console.print("Failed to fetch data from Google Books API.", style="bold red")
|
||||
else:
|
||||
console.print("Book not found in local database.", style="bold red")
|
||||
|
||||
def main():
|
||||
while True:
|
||||
console.print("\n[bold]Admin Console[/bold]")
|
||||
@ -126,8 +219,9 @@ def main():
|
||||
console.print("4. Change Book Status")
|
||||
console.print("5. Search Books")
|
||||
console.print("6. List Books on Loan")
|
||||
console.print("7. Exit")
|
||||
choice = Prompt.ask("Choose an option", choices=["1", "2", "3", "4", "5", "6", "7"], default="7")
|
||||
console.print("7. Rebuild Book Entries")
|
||||
console.print("8. Exit")
|
||||
choice = Prompt.ask("Choose an option", choices=["1", "2", "3", "4", "5", "6", "7", "8"], default="8")
|
||||
|
||||
if choice == "1":
|
||||
list_books()
|
||||
@ -142,6 +236,8 @@ def main():
|
||||
elif choice == "6":
|
||||
list_books_on_loan()
|
||||
elif choice == "7":
|
||||
rebuild_book_entries()
|
||||
elif choice == "8":
|
||||
console.print("Exiting...", style="bold yellow")
|
||||
break
|
||||
|
||||
|
||||
@ -1,11 +1,13 @@
|
||||
<!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>
|
||||
<button id="dark-mode-toggle">🌙</button>
|
||||
<h1>Ramsey Library</h1>
|
||||
@ -32,7 +34,7 @@
|
||||
// Fetch all books when the page loads
|
||||
window.onload = fetchBooks('');
|
||||
|
||||
document.getElementById('search-bar').addEventListener('input', function() {
|
||||
document.getElementById('search-bar').addEventListener('input', function () {
|
||||
fetchBooks(this.value);
|
||||
});
|
||||
|
||||
@ -74,23 +76,23 @@
|
||||
});
|
||||
}
|
||||
|
||||
// Utility function to get a cookie by name
|
||||
function getCookie(name) {
|
||||
// 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) {
|
||||
// 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) {
|
||||
// Client-side function to request checkout
|
||||
function requestCheckout(isbn) {
|
||||
let email = getCookie('lastEmail') || '';
|
||||
let name = getCookie('lastName') || '';
|
||||
|
||||
@ -126,7 +128,7 @@ function requestCheckout(isbn) {
|
||||
console.error('Error:', error);
|
||||
alert('An error occurred while sending the checkout request.');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function sortTable(columnIndex) {
|
||||
@ -171,4 +173,5 @@ function requestCheckout(isbn) {
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@ -259,7 +259,8 @@ function requestCheckout(isbn) {
|
||||
fetch(`/api/checkout/${isbn}`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': 'Basic ' + btoa(username + ':' + password)
|
||||
}
|
||||
})
|
||||
.then(response => response.json())
|
||||
|
||||
@ -197,4 +197,49 @@ body.dark-mode #book-table-body tr:hover {
|
||||
background-color: #444;
|
||||
}
|
||||
|
||||
/* Center the checkout button and make it fill the cell */
|
||||
#book-table td button {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-sizing: border-box; /* Ensure padding and border are included in the element's total width and height */
|
||||
}
|
||||
|
||||
/* Add more styles as needed for other elements */
|
||||
|
||||
#location-prompt {
|
||||
position: fixed;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
background-color: #fff;
|
||||
color: #000;
|
||||
padding: 20px;
|
||||
border: 2px solid #000;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
#location-prompt label {
|
||||
display: block;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
#location-prompt input {
|
||||
width: 100%;
|
||||
padding: 8px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
#location-prompt select {
|
||||
width: 100%;
|
||||
padding: 8px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
#location-prompt button {
|
||||
padding: 8px 16px;
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user