feat: add book metadata editor with Google Books and OpenLibrary integration

This commit is contained in:
2025-05-17 15:49:26 -04:00
parent f4f227b24d
commit d2b88f4e59
11 changed files with 981 additions and 30 deletions

183
index.js
View File

@@ -130,6 +130,16 @@ app.get('/api/books-with-images', async (req, res) => {
},
attributes: ['isbn', 'title', 'cover_small', 'cover_medium', 'cover_large', 'authors'],
});
// CORRECTED DEBUG LOGGING ON SERVER
if (books && books.length > 0) {
// Log a specific book if its ID is known, e.g., ID 207 for Artemis Fowl
const specificBookForDebug = books.find(b => b.id === 207);
if (specificBookForDebug) {
console.log("Server-side specific book from /api/books (ID 207):", JSON.stringify(specificBookForDebug, null, 2));
} else {
console.log("Server-side book ID 207 not found in /api/books results, logging first book instead:", JSON.stringify(books[0], null, 2));
}
}
res.json(books);
} catch (error) {
console.error('Failed to fetch books with images:', error);
@@ -301,23 +311,25 @@ function delay(ms) {
// Confirm book is in the library by ISBN
app.get('/book/confirm/:isbn', async (req, res) => {
const { isbn } = req.params;
console.log(`Fetching book data for ISBN: ${isbn}`);
console.log(`Cascade: /book/confirm/:isbn - Confirming ISBN: ${isbn}`);
try {
// Check if the book is in the local database
const localBook = await fetchBookFromLocalDatabase(isbn);
if (localBook) {
console.log('Book found in the local database');
console.log('Cascade: /book/confirm/:isbn - Book found in local database.');
return res.json({ source: 'local', data: localBook });
} else {
console.log('Cascade: /book/confirm/:isbn - Book NOT found in local database.');
return res.status(404).json({ error: 'Book not found in local library' });
}
} catch (error) {
console.error(error);
return res.status(500).json({ error: 'Failed to fetch book data' });
console.error('Cascade: /book/confirm/:isbn - Error during lookup:', error);
return res.status(500).json({ error: 'Failed to confirm book in local library' });
}
});
app.get('/search-title', async (req, res) => {
const { title, internalOnly = false } = req.query;
console.log(`Searching for books by title or related fields: ${title}`);
@@ -393,9 +405,14 @@ app.delete('/book/:id', async (req, res) => {
}
});
app.get('/locations', async (req, res) => {
app.get('/api/locations', async (req, res) => {
try {
const locations = await Location.findAll();
const locations = await Location.findAll({
order: [
['name', 'ASC'],
['shelf', 'ASC']
]
});
res.json(locations);
} catch (error) {
console.error('Failed to fetch locations:', error);
@@ -657,6 +674,156 @@ function requestCheckout(isbn) {
});
}
// API endpoint to get all books
app.get('/api/books', async (req, res) => {
try {
const books = await Book.findAll({
include: [{
model: Location,
as: 'Location' // Corrected alias based on model definition
}],
order: [['title', 'ASC']]
});
res.json(books.map(book => ({
id: book.id,
title: book.title,
authors: book.authors,
isbn: book.isbn,
publishedDate: book.publishedDate,
description: book.description,
cover_small: book.cover_small,
number_of_pages: book.number_of_pages,
publishers: book.publishers,
subjects: book.subjects,
location_id: book.location_id,
Location: book.Location ? { id: book.Location.id, name: book.Location.name, room: book.Location.room, shelf: book.Location.shelf } : null
})));
} catch (error) {
console.error('Failed to fetch books:', error);
res.status(500).json({ error: 'Failed to fetch books' });
}
});
// API endpoint to add a new book
app.post('/api/books', async (req, res) => {
try {
const {
title,
authors,
publishedDate,
description,
isbn,
number_of_pages,
publishers,
subjects,
cover_small,
cover_medium,
cover_large,
location_id
} = req.body;
// Basic validation (e.g., title is required)
if (!title) {
return res.status(400).json({ error: 'Title is required' });
}
const newBookData = {
title,
authors: Array.isArray(authors) ? authors.join(', ') : authors,
publishedDate,
description,
isbn,
number_of_pages,
publishers: Array.isArray(publishers) ? publishers.join(', ') : publishers,
subjects: Array.isArray(subjects) ? subjects.join(', ') : subjects,
cover_small,
cover_medium,
cover_large,
location_id: location_id === '' ? null : location_id // Handle empty string as null
};
const book = await Book.create(newBookData);
// Fetch the newly created book with its location to send back in the response
const createdBookWithLocation = await Book.findByPk(book.id, {
include: [{
model: Location,
as: 'Location'
}]
});
res.status(201).json({ success: true, book: createdBookWithLocation });
} catch (error) {
console.error('Failed to add new book:', error);
if (error.name === 'SequelizeUniqueConstraintError') {
let errorMessage = 'Failed to add new book due to a conflict.';
// Assuming 'isbn' is a unique field in your Book model
if (error.fields && typeof error.fields === 'object' && 'isbn' in error.fields) {
errorMessage = 'A book with this ISBN already exists. Please use the edit feature if you want to update it.';
}
return res.status(409).json({ error: errorMessage });
}
res.status(500).json({ error: 'Failed to add new book' });
}
});
// API endpoint to update a book's metadata by its primary key ID
app.put('/api/books/:id', async (req, res) => {
try {
const bookId = req.params.id;
const book = await Book.findByPk(bookId);
if (!book) {
return res.status(404).json({ error: 'Book not found' });
}
const {
title,
authors,
publishedDate,
description,
isbn,
number_of_pages,
publishers,
subjects,
cover_small,
cover_medium,
cover_large,
location_id // Added to handle location updates
} = req.body;
const updateData = {};
if (title !== undefined) updateData.title = title;
if (authors !== undefined) updateData.authors = Array.isArray(authors) ? authors.join(', ') : authors;
if (publishedDate !== undefined) updateData.publishedDate = publishedDate;
if (description !== undefined) updateData.description = description;
if (isbn !== undefined) updateData.isbn = isbn === '' ? null : isbn;
if (number_of_pages !== undefined) updateData.number_of_pages = number_of_pages;
if (publishers !== undefined) updateData.publishers = Array.isArray(publishers) ? publishers.join(', ') : publishers;
if (subjects !== undefined) updateData.subjects = Array.isArray(subjects) ? subjects.join(', ') : subjects;
if (cover_small !== undefined) updateData.cover_small = cover_small;
if (cover_medium !== undefined) updateData.cover_medium = cover_medium;
if (cover_large !== undefined) updateData.cover_large = cover_large;
if (location_id !== undefined) updateData.location_id = location_id === '' ? null : location_id; // Handle empty string as null
await book.update(updateData);
console.log(`Book data for ID ${bookId} updated successfully.`);
// Fetch the updated book with its location to send back in the response
const updatedBookWithLocation = await Book.findByPk(bookId, {
include: [{
model: Location,
as: 'Location'
}]
});
res.json({ success: true, book: updatedBookWithLocation });
} catch (error) {
console.error(`Failed to update book with ID ${req.params.id}:`, error);
res.status(500).json({ error: 'Failed to update book' });
}
});
const httpsServer = https.createServer(credentials, app);
@@ -698,7 +865,7 @@ app.get('/api/books-on-loan', async (req, res) => {
}
});
app.post('/book/update/:isbn', async (req, res) => {
app.post('/book/update/:isbn', authMiddleware, async (req, res) => { // Added authMiddleware for consistency
const { isbn } = req.params;
const googleBooksApiKey = process.env.GOOGLE_BOOKS_API_KEY;