Source: controllers/BookController.js

/** @module controllers */

const BaseDatabaseController = require("./BaseDatabaseController");

/** Class representing a book controller. */
class BookController extends BaseDatabaseController {
    /** Create a book controller. */
    constructor() {
        super();
    }

    /**
     * Get a book by id.
     * 
     * @param {number} id
     * @returns {Promise<object>}
     */
    async byId(id) {
        const bookResponse = await this._query('SELECT * FROM books WHERE id = ?', id);
        if (bookResponse.length !== 1) {
            return null;
        }

        return bookResponse[0];
    }

    /**
     * Get a book by title.
     * 
     * @param {string} title
     * @returns {Promise<object>}
     */
    async byTitle(title) {
        const bookResponse = await this._query('SELECT * FROM books WHERE title = ?', title);
        if (bookResponse.length !== 1) {
            return null;
        }

        return bookResponse[0];
    }

    /**
     * Search books by title.
     * 
     * @param {string} title
     * @param {number} amount
     * @returns {Promise<object[]>}
     */
    async searchByTitle(title, amount) {
        const books = await this._query('SELECT * FROM books WHERE MATCH(title) AGAINST(? IN NATURAL LANGUAGE MODE) AND title LIKE ? LIMIT ?', title, `%${title}%`, amount);
        
        return books;
    }

    /**
     * Search books by title using natural language mode.
     * 
     * @param {string} title
     * @param {number} amount
     * @returns {Promise<object[]>}
     */
    async searchByTitleNatural(title, amount) {
        const books = await this._query('SELECT *, MATCH(title) AGAINST(? IN NATURAL LANGUAGE MODE) AS score FROM books WHERE MATCH(title) AGAINST(? IN NATURAL LANGUAGE MODE) LIMIT ?', title, title, amount);
        
        return books;
    }

    /**
     * Search books using OCR elements.
     * 
     * @param {string[]} ocrElements
     * @returns {Promise<object[]>}
     */
    async searchByOCRElements(ocrElements) {
        const books = await this._query('CALL getPossibleBooks(?)', ocrElements.join(','));
        
        return books[0];
    }

    /**
     * Search books by title and author name.
     * 
     * @param {string} title
     * @param {string} authorName
     * @returns {Promise<object[]>}
     */
    async searchByTitleAndAuthorName(title, authorName) {
        const books = await this._query(
            `WITH CandidateBooks AS (
                SELECT id, MATCH(title) AGAINST(? IN NATURAL LANGUAGE MODE) AS title_score
                FROM books
                WHERE MATCH(title) AGAINST(? IN NATURAL LANGUAGE MODE)
                ORDER BY title_score DESC
                LIMIT 250
            ),
            CandidateAuthors AS (
                SELECT id, name, MATCH(name) AGAINST(? IN NATURAL LANGUAGE MODE) AS author_score
                FROM authors
                WHERE MATCH(name) AGAINST(? IN NATURAL LANGUAGE MODE)
                ORDER BY author_score DESC
                LIMIT 250
            )

            SELECT b.*, ca.name AS author_name
            FROM CandidateBooks cb
            JOIN books_authors ba ON ba.books_id = cb.id
            JOIN CandidateAuthors ca ON ba.authors_id = ca.id
            JOIN books b ON b.id = cb.id
            ORDER BY (cb.title_score + ca.author_score) DESC
            LIMIT 10;`,
            title,
            title,
            authorName,
            authorName
        );
        
        return books;
    }

    /**
     * Get the cover URL of a book.
     * 
     * @param {number} id
     * @returns {Promise<string | null>}
     */
    async getCoverURL(id) {
        const bookResponse = await this._query('SELECT * FROM books WHERE id = ?', id);
        if (bookResponse.length !== 1) {
            return null;
        }

        const workResponse = await fetch(`https://openlibrary.org${bookResponse[0].openlibrary_id}.json`, {
            method: 'GET',
            headers: {
                'Content-Type': 'application/json'
            }
        });

        if (!workResponse.ok) {
            return null;
        }

        const workData = await workResponse.json();

        if (!workData.covers) {
            return null;
        }
        if (workData.covers.length === 0) {
            return null;
        }

        const coverId = workData.covers[0];
        
        return `https://covers.openlibrary.org/b/id/${coverId}-M.jpg`;
    }

    /**
     * Get books by author.
     * 
     * @param {number} authorId
     * @returns {Promise<object[]>}
     */
    async byAuthor(authorId) {
        const booksResponse = await this._query('SELECT * FROM books AS b JOIN books_authors AS ba WHERE b.id = ba.books_id AND ba.authors_id = ?', authorId);
        
        return booksResponse;
    }

    /**
     * Get books by term.
     * 
     * @param {number} termId
     * @returns {Promise<object[]>}
     */
    async byTerm(termId) {
        const booksResponse = await this._query('SELECT * FROM books AS b JOIN books_terms AS bt WHERE b.id = bt.books_id AND bt.terms_id = ?', termId);
        
        return booksResponse;
    }

    /**
     * Insert a book.
     * 
     * @param {string} title
     * @param {string} subtitle
     * @param {date} publicationDate
     * @returns {Promise<object>}
     */
    async insert(title, subtitle, publicationDate) {
        const bookInsertion = await this._query('INSERT INTO books (title, subtitle, publication_date) VALUES (?, ?, ?)', title, subtitle, publicationDate);

        return await this.byId(bookInsertion.insertId);
    }
}

module.exports = BookController;