diff --git a/bookHelpers.js b/bookHelpers.js index 24dca16..3c0304f 100644 --- a/bookHelpers.js +++ b/bookHelpers.js @@ -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 } }); diff --git a/books.db b/books.db index 31d02b2..2ce00de 100644 Binary files a/books.db and b/books.db differ diff --git a/index.js b/index.js index d81cde3..668e6a2 100644 --- a/index.js +++ b/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' }); + } +}); diff --git a/libraryManager.py b/libraryManager.py index e4d52c2..d9cd80d 100644 --- a/libraryManager.py +++ b/libraryManager.py @@ -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 diff --git a/public/index.html b/public/index.html index 4b4e20b..7bf4fe8 100644 --- a/public/index.html +++ b/public/index.html @@ -1,11 +1,13 @@ + Ramsey Library +

Ramsey Library

@@ -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,60 +76,60 @@ }); } -// 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') || ''; - let name = getCookie('lastName') || ''; - - // Prompt the user for email and name, pre-filling with existing values if available - email = prompt('Please enter your email:', email); - name = prompt('Please enter your name:', 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.'); + // 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(); } - }) - .catch(error => { - console.error('Error:', error); - alert('An error occurred while sending the checkout request.'); - }); -} - + + // 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') || ''; + let name = getCookie('lastName') || ''; + + // Prompt the user for email and name, pre-filling with existing values if available + email = prompt('Please enter your email:', email); + name = prompt('Please enter your name:', 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'); @@ -171,4 +173,5 @@ function requestCheckout(isbn) { }); - + + \ No newline at end of file diff --git a/public/script.js b/public/script.js index c37b2fa..62b302d 100644 --- a/public/script.js +++ b/public/script.js @@ -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()) diff --git a/public/styles.css b/public/styles.css index 673de36..27a1d5a 100644 --- a/public/styles.css +++ b/public/styles.css @@ -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; +}