diff --git a/backup/books.db b/backup/books.db new file mode 100644 index 0000000..75ac5f7 Binary files /dev/null and b/backup/books.db differ diff --git a/books.db b/books.db index 75ac5f7..4cf3ec9 100644 Binary files a/books.db and b/books.db differ diff --git a/index.js b/index.js index 4de90ed..3a92726 100644 --- a/index.js +++ b/index.js @@ -18,7 +18,110 @@ const credentials = { key: privateKey, cert: certificate }; // Middleware to parse JSON bodies app.use(express.json()); // Use built-in body-parser for JSON -// Set up the SQLite database +const { Sequelize, DataTypes } = require('sequelize'); + +// Initialize Sequelize +const sequelize = new Sequelize({ + dialect: 'sqlite', + storage: './books.db', +}); + + +// Test the connection + +sequelize.authenticate() + .then(() => { + console.log('Connection has been established successfully.'); + }) + .catch(err => { + console.error('Unable to connect to the database:', err); + }); + // TODO: Add physical location of book to database + const Book = sequelize.define('Book', { + id: { + type: DataTypes.INTEGER, + autoIncrement: true, + primaryKey: true, + }, + isbn: { + type: DataTypes.TEXT, + allowNull: false, + unique: true, + }, + title: { + type: DataTypes.TEXT, + allowNull: false, + }, + authors: { + type: DataTypes.TEXT, + }, + publishedDate: { + type: DataTypes.TEXT, + }, + description: { + type: DataTypes.TEXT, + }, + url: { + type: DataTypes.TEXT, + }, + number_of_pages: { + type: DataTypes.INTEGER, + }, + identifiers: { + type: DataTypes.TEXT, + }, + publishers: { + type: DataTypes.TEXT, + }, + subjects: { + type: DataTypes.TEXT, + }, + notes: { + type: DataTypes.TEXT, + }, + cover_small: { + type: DataTypes.TEXT, + }, + cover_medium: { + type: DataTypes.TEXT, + }, + cover_large: { + type: DataTypes.TEXT, + }, + location_id: { + type: DataTypes.INTEGER, + references: { + model: Location, + key: 'id' + } + } + }, { + tableName: 'books', + timestamps: false, // If your table doesn't have `createdAt` and `updatedAt` + }); + +const Location = sequelize.define('Location', { + id: { + type: DataTypes.INTEGER, + autoIncrement: true, + primaryKey: true, + }, + name: { + type: DataTypes.TEXT, + allowNull: false, + }, + shelf: { + type: DataTypes.TEXT, + allowNull: false, + } +}, { + tableName: 'locations', + timestamps: false, // If your table doesn't have `createdAt` and `updatedAt` +}); + + + + // Set up the SQLite database const db = new sqlite3.Database('./books.db', (err) => { if (err) { @@ -51,14 +154,22 @@ const db = new sqlite3.Database('./books.db', (err) => { // Serve static files from the 'public' directory app.use(express.static(path.join(__dirname, 'public'))); -// Endpoint to fetch book details by ISBN // Endpoint to fetch book details by ISBN app.get('/book/:isbn', async (req, res) => { const { isbn } = req.params; console.log(`Fetching book data for ISBN: ${isbn}`); try { - // First try Google Books + // First, check if the book is in the local database + const book = await Book.findOne({ where: { isbn } }); + + if (book) { + console.log('Book found in the local database'); + res.json({ source: 'local', data: book }); + return; + } + + // If not found locally, try Google Books const apiKey = 'AIzaSyCQikthZ5TlkFTcKTG8n171dRafosK2Mg8'; const googleBooksResponse = await axios.get(`https://www.googleapis.com/books/v1/volumes?q=${isbn}&key=${apiKey}`); @@ -77,7 +188,7 @@ app.get('/book/:isbn', async (req, res) => { if (googleBooksResponse.data.items && googleBooksResponse.data.items.length > 0) { const googleBookData = googleBooksResponse.data.items[0]; console.log('Book data found in Google Books'); - res.json(formatGoogleBooksData(googleBookData)); + res.json({ source: 'external', data: formatGoogleBooksData(googleBookData) }); return; } else { console.log('Book not found in Google Books'); @@ -101,7 +212,7 @@ app.get('/book/:isbn', async (req, res) => { if (bookData) { console.log('Book data found in Open Library'); - res.json(formatOpenLibraryData(bookData)); + res.json({ source: 'external', data: formatOpenLibraryData(bookData) }); return; } else { console.log('Book not found in Open Library'); @@ -113,7 +224,7 @@ app.get('/book/:isbn', async (req, res) => { if (archiveData) { console.log('Book data found in the Internet Archive'); - res.json(formatArchiveData(archiveData)); + res.json({ source: 'external', data: formatArchiveData(archiveData) }); } else { console.log('Book not found in the Internet Archive'); res.status(404).json({ error: 'Book not found' }); @@ -126,20 +237,39 @@ app.get('/book/:isbn', async (req, res) => { - -// Endpoint to search for book by title app.get('/search-title', async (req, res) => { - const { title } = req.query; - console.log(`Searching for book by title: ${title}`); + const { title, internalOnly=false } = req.query; + console.log(`Searching for books by title or related fields: ${title}`); try { - // Search Open Library by title, limit to 5 results + // First, search in the local database across all relevant fields + const localBooks = await Book.findAll({ + where: { + [Sequelize.Op.or]: [ + { title: { [Sequelize.Op.like]: `%${title}%` } }, + { authors: { [Sequelize.Op.like]: `%${title}%` } }, + { publishers: { [Sequelize.Op.like]: `%${title}%` } }, + { description: { [Sequelize.Op.like]: `%${title}%` } }, + { subjects: { [Sequelize.Op.like]: `%${title}%` } } + ] + } + }); + + if (localBooks.length > 0) { + console.log('Books found in the local database'); + res.json({ source: 'local', results: localBooks }); + return; + } + if (internalOnly) { + res.status(404).json({ error: 'No books found with that title or related fields.' }); + return; + } + // If no results found locally, proceed to search external sources const openLibraryResponse = await axios.get(`https://openlibrary.org/search.json?q=${encodeURIComponent(title)}&limit=7`); const searchResults = openLibraryResponse.data.docs; - console.log(searchResults); if (searchResults.length > 0) { - console.log('Books found by title'); + console.log('Books found by title in external sources'); const bookData = searchResults.map(result => ({ title: result.title, authors: result.author_name || [], @@ -148,88 +278,63 @@ app.get('/search-title', async (req, res) => { publisher: result.publisher ? result.publisher[0] : '', key: result.key // Unique key to fetch more detailed data later if needed })); - console.log(bookData); - res.json({ results: bookData }); + res.json({ source: 'external', results: bookData }); } else { - res.status(404).json({ error: 'No books found with that title.' }); + res.status(404).json({ error: 'No books found with that title or related fields.' }); } } catch (error) { console.error(error); - res.status(500).json({ error: 'Failed to search for book by title' }); + res.status(500).json({ error: 'Failed to search for book by title or related fields' }); } }); // Endpoint to store book in the database -// Endpoint to store book in the database -// Endpoint to store book in the database -app.post('/store-book', (req, res) => { - const { - isbn, title, authors, publishedDate, description, url, - number_of_pages, identifiers, publishers, subjects, - notes, cover_small, cover_medium, cover_large - } = req.body; - - // Check if a book with the same ISBN already exists - const checkQuery = `SELECT * FROM books WHERE isbn = ?`; - db.get(checkQuery, [isbn], (err, row) => { - if (err) { - console.error('Error checking for existing book:', err); - return res.status(500).json({ error: 'Failed to check for existing book' }); - } - - if (row) { - // Book already exists, update the existing record - const updateQuery = ` - UPDATE books - SET title = ?, authors = ?, publishedDate = ?, description = ?, url = ?, - number_of_pages = ?, identifiers = ?, publishers = ?, subjects = ?, - notes = ?, cover_small = ?, cover_medium = ?, cover_large = ? - WHERE isbn = ? - `; - - const updateParams = [ - title, authors ? authors.join(', ') : '', publishedDate, description || '', - url, number_of_pages, identifiers, publishers, subjects, - notes, cover_small, cover_medium, cover_large, isbn - ]; - - db.run(updateQuery, updateParams, function (err) { - if (err) { - console.error('Error updating book in database:', err); - return res.status(500).json({ error: 'Failed to update book in database' }); - } else { - res.json({ success: true, message: 'Book updated successfully' }); - } - }); - } else { - // Book does not exist, insert a new record - const insertQuery = ` - INSERT INTO books ( - isbn, title, authors, publishedDate, description, url, - number_of_pages, identifiers, publishers, subjects, - notes, cover_small, cover_medium, cover_large - ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) - `; - - const insertParams = [ - isbn, title, authors ? authors.join(', ') : '', publishedDate, description || '', - url, number_of_pages, identifiers, publishers, subjects, - notes, cover_small, cover_medium, cover_large - ]; - - db.run(insertQuery, insertParams, function (err) { - if (err) { - console.error('Error storing book in database:', err); - return res.status(500).json({ error: 'Failed to store book in database' }); - } else { - res.json({ success: true, message: 'Book stored successfully', bookId: this.lastID }); - } - }); - } - }); +app.post('/store-book', async (req, res) => { + try { + const book = await Book.create(req.body); + res.json({ success: true, book }); + } catch (error) { + console.error('Failed to store book:', error); + res.status(500).json({ error: 'Failed to store book in database' }); + } }); +app.put('/book/:isbn', async (req, res) => { + try { + const { isbn } = req.params; + const book = await Book.findOne({ where: { isbn } }); + if (book) { + await book.update(req.body); + res.json({ success: true, message: 'Book updated successfully' }); + } else { + res.status(404).json({ error: 'Book not found' }); + } + } catch (error) { + console.error('Failed to update book:', error); + res.status(500).json({ error: 'Failed to update book in database' }); + } +}); + +app.delete('/book/:id', async (req, res) => { + try { + const { id } = req.params; + const book = await Book.findByPk(id); + if (book) { + await book.destroy(); + res.json({ success: true, message: 'Book deleted successfully' }); + } else { + res.status(404).json({ error: 'Book not found' }); + } + } catch (error) { + console.error('Failed to delete book:', error); + res.status(500).json({ error: 'Failed to delete book from database' }); + } +}); + +// TODO: Function to checkout a book + +// TODO: Function to return a book function formatOpenLibraryData(data) { return { @@ -294,6 +399,11 @@ function formatGoogleBooksData(data) { const httpsServer = https.createServer(credentials, app); +sequelize.sync({ alter: true }).then(() => { + console.log('Database & tables synced!'); +}); + + httpsServer.listen(PORT, () => { console.log(`HTTPS Server running on https://localhost:${PORT}`); }); diff --git a/public/library.html b/public/library.html new file mode 100644 index 0000000..59ecc39 --- /dev/null +++ b/public/library.html @@ -0,0 +1,97 @@ + + + + + + Book Library + + + +

Book Library

+ + + + + + + + + + + + + + + +
TitleAuthorsPublisherPublished DateISBN
+ + + + diff --git a/public/script.js b/public/script.js index f42f331..5ab7f99 100644 --- a/public/script.js +++ b/public/script.js @@ -46,7 +46,7 @@ function startScanner() { alert('No camera selected or available.'); return; } - + // TODO: Limit the field of view to a smaller area for faster detection Quagga.init({ inputStream: { name: "Live", @@ -68,7 +68,11 @@ function startScanner() { } console.log("Initialization finished. Ready to start"); Quagga.start(); + + // TODO: Add a button to enable/disable torch Quagga.CameraAccess.enableTorch(); + + // TODO: Add a button to enable/disable the "locate" functionality }); Quagga.onDetected(async function (data) { @@ -79,6 +83,9 @@ function startScanner() { console.log("Detected ISBN:", isbn); Quagga.stop(); // Stop the scanner once an ISBN is detected + // TODO: Validate the ISBN before fetching book details + // Use a library like barcode-validator to validate the ISBN + // Fetch book details await fetchBookInfo(isbn); @@ -91,6 +98,10 @@ async function fetchBookInfo(isbn) { const response = await fetch(`/book/${isbn}`); const bookData = await response.json(); + // TODO: If bookData value of "source" is "local", then the book data is already in the database + // TODO: Display the book info and ask if they would like to checkout the book + + // TODO: Store scanned ISBN in a different field to check against the one we get from the API if (bookData.title) { bookData.isbn2 = isbn; // Add the ISBN to the book data promptUserWithBook(bookData); @@ -125,6 +136,9 @@ function promptUserWithBook(bookData) { } } +// TODO: Function to prompt user for physical location of the book +// Should pull from the database and allow the user to select or add a location + // Add an event listener for the search button document.getElementById('search-title').addEventListener('click', searchByTitle);