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:
knight 2024-12-12 10:32:20 -05:00
parent 370ad7c107
commit f4f227b24d
7 changed files with 255 additions and 61 deletions

View File

@ -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 } });

BIN
books.db

Binary file not shown.

View File

@ -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' });
}
});

View File

@ -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

View File

@ -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>
@ -171,4 +173,5 @@ function requestCheckout(isbn) {
});
</script>
</body>
</html>

View File

@ -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())

View File

@ -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;
}