From e39fe52e65043657d79c2078bf86737eb67d5cac Mon Sep 17 00:00:00 2001 From: Robbie Antenesse Date: Thu, 26 Sep 2019 12:22:58 -0600 Subject: [PATCH] Add search by Open Library --- app/views/search/controller.js | 2 +- app/views/search/index.js | 4 +-- server/controllers/books.js | 39 ++++++++++++++++++++++++--- server/controllers/search.js | 49 +++++++++++++++++++++------------- server/routes/search.js | 21 +++++++++++++-- 5 files changed, 89 insertions(+), 26 deletions(-) diff --git a/app/views/search/controller.js b/app/views/search/controller.js index e899f1d..c3c2255 100644 --- a/app/views/search/controller.js +++ b/app/views/search/controller.js @@ -53,7 +53,7 @@ export class SearchController extends ViewController { const searchTerm = this.appState.query.for.trim(); - return fetch(`/api/search?for=${searchTerm}&lang=${this.appState.language}`) + return fetch(`/api/search?for=${searchTerm}&by=${this.state.searchBy}&lang=${this.appState.language}&source=${this.state.searchSource}`) .then(response => response.json()) .then(responseJSON => { this.state.results = responseJSON; diff --git a/app/views/search/index.js b/app/views/search/index.js index 3f05ec8..d22280f 100644 --- a/app/views/search/index.js +++ b/app/views/search/index.js @@ -94,10 +94,10 @@ export const searchView = (state, emit, i18n) => { - - diff --git a/server/controllers/books.js b/server/controllers/books.js index 727275b..c1137e2 100644 --- a/server/controllers/books.js +++ b/server/controllers/books.js @@ -1,8 +1,11 @@ const fetch = require('node-fetch'); class BooksController { - constructor(inventaireDomain, bookURI, language) { - this.inventaire = inventaireDomain; + constructor(bookSource, bookURI, language) { + this.source = bookSource; + this.inventaire = 'https://inventaire.io'; + this.openLibrary = 'https://openlibrary.org'; + this.bookBrainz = 'https://bookbrainz.org'; this.uri = bookURI; this.lang = language; } @@ -87,7 +90,7 @@ class BooksController { typeof entityObject.image !== 'undefined' ? entityObject.image.map(imageId => { return { - uri: imageId, + uri: imageId.toString(), url: `${this.inventaire}/img/entities/${imageId}`, } }) @@ -132,6 +135,36 @@ class BooksController { }; } + handleOpenLibraryEntity(entityObject) { + return { + name: ( + typeof entityObject.title_suggest !== 'undefined' + ? entityObject.title_suggest + : null + ), + description: ( + typeof entityObject.author_name !== 'undefined' + ? `${entityObject.type} by ${entityObject.author_name.map(name => name.trim()).join(', ')}` + : null + ), + link: ( + typeof entityObject.key !== 'undefined' + ? `${this.openLibrary}${entityObject.key}` + : null + ), + uri: ( + typeof entityObject.key !== 'undefined' + ? entityObject.key.substr(entityObject.key.lastIndexOf('/') + 1) + : null + ), + coverId: ( + typeof entityObject.cover_i !== 'undefined' + ? entityObject.cover_i.toString() + : false + ), + }; + } + async getBookDataFromInventaire() { if (this.uri) { const request = fetch(`${this.inventaire}/api/entities?action=by-uris&uris=${encodeURIComponent(this.uri)}`) diff --git a/server/controllers/search.js b/server/controllers/search.js index ff30ae0..90ee365 100644 --- a/server/controllers/search.js +++ b/server/controllers/search.js @@ -3,8 +3,7 @@ const fetch = require('node-fetch'); const BooksController = require('./books'); class SearchController { - constructor(inventaireDomain, searchTerm, language = 'en') { - this.inventaire = inventaireDomain; + constructor(searchTerm, language = 'en') { this.term = searchTerm; this.lang = language; } @@ -15,7 +14,7 @@ class SearchController { quickSearchInventaire() { if (this.hasQuery) { - const request = fetch(`${this.inventaire}/api/search?types=works&search=${encodeURIComponent(this.term)}&lang=${encodeURIComponent(this.lang)}&limit=10`) + const request = fetch(`https://inventaire.io/api/search?types=works&search=${encodeURIComponent(this.term)}&lang=${encodeURIComponent(this.lang)}&limit=10`) request.catch(exception => { console.error(exception); return { @@ -32,9 +31,11 @@ class SearchController { } }); return json.then(responseJSON => { + const booksController = new BooksController('inventaire', undefined, this.lang); + return responseJSON.results.map(work => { - const booksController = new BooksController(this.inventaire, work.uri, this.lang); const bookData = booksController.handleQuickInventaireEntity(work); + booksController.uri = bookData.uri; // Update booksController.uri for each book when fetching community data. const communityData = booksController.getCommunityData(5); return { @@ -46,9 +47,9 @@ class SearchController { } } - searchInventaire() { + searchInventaire(searchBy = 'title') { if (this.hasQuery) { - const request = fetch(`${this.inventaire}/api/entities?action=search&search=${encodeURIComponent(this.term)}&lang=${encodeURIComponent(this.lang)}`) + const request = fetch(`https://inventaire.io/api/entities?action=search&search=${encodeURIComponent(this.term)}&lang=${encodeURIComponent(this.lang)}`) request.catch(exception => { console.error(exception); return { @@ -65,8 +66,8 @@ class SearchController { } }); return json.then(responseJSON => { + const booksController = new BooksController('inventaire', undefined, this.lang); return responseJSON.works.map(work => { - const booksController = new BooksController(this.inventaire, work.uri, this.lang); const bookData = booksController.handleInventaireEntity(work); const communityData = booksController.getCommunityData(5); @@ -167,44 +168,56 @@ class SearchController { }); } - async searchOpenLibrary() { + async searchOpenLibrary(searchBy = 'title') { if (!this.hasQuery) { return []; } - return fetch('http://openlibrary.org/search.json?q=' + encodeURIComponent(this.term)) + return fetch(`http://openlibrary.org/search.json?${searchBy}=${encodeURIComponent(this.term)}`) .then(res => res.json()) .then(response => { if (!response.hasOwnProperty('docs')) { return []; } + + const booksController = new BooksController('openLibrary', undefined, this.lang); + // Format the response into usable objects const docs = response.docs.map(doc => { - return { - title: doc.title_suggest.trim(), - authors: doc.hasOwnProperty('author_name') ? doc.author_name.map(name => name.trim()) : [], - cover: doc.hasOwnProperty('cover_i') ? `//covers.openlibrary.org/b/id/${doc.cover_i}-S.jpg` : false, - }; + return booksController.handleOpenLibraryEntity(doc); }); // Filter out duplicate items with the same title and author const results = docs.filter((doc, index, allDocs) => { return typeof allDocs.find((filterResult, filterIndex) => { return index !== filterIndex && filterResult.title === doc.title - && JSON.stringify(filterResult.authors) === JSON.stringify(doc.authors); + && filterResult.description === doc.description; }) === 'undefined'; }).map(result => { // Find any duplicates in case they have different cover data const duplicates = docs.filter(doc => { - return doc.title.toLowerCase() === result.title.toLowerCase() && JSON.stringify(doc.authors) === JSON.stringify(result.authors); + return doc.name.toLowerCase() === result.name.toLowerCase() && doc.description === result.description; }); result.covers = []; duplicates.forEach(duplicate => { - if (duplicate.cover !== false) { - result.covers.push(duplicate.cover); + if (duplicate.coverId !== false) { + result.covers.push({ + uri: duplicate.coverId, + url: `//covers.openlibrary.org/b/id/${duplicate.coverId}-M.jpg`, + }); } }); + delete result.coverId; return result; + }).map(bookData => { + // Use bookController to get community data + booksController.uri = bookData.uri; // Update booksController.uri for each book when fetching community data. + const communityData = booksController.getCommunityData(5); + + return { + ...bookData, + ...communityData, + }; }); return results; diff --git a/server/routes/search.js b/server/routes/search.js index 87cd520..89297fd 100644 --- a/server/routes/search.js +++ b/server/routes/search.js @@ -3,10 +3,27 @@ const SearchController = require('../controllers/search'); async function routes(fastify, options) { fastify.get('/api/search', async (request, reply) => { const searchTerm = typeof request.query.for !== 'undefined' ? request.query.for.trim() : ''; + const searchBy = typeof request.query.by !== 'undefined' ? request.query.by.trim() : 'title'; const language = typeof request.query.lang !== 'undefined' ? request.query.lang.trim().split('-')[0] : undefined; // Get base language in cases like 'en-US' - const search = new SearchController(fastify.siteConfig.inventaireDomain, searchTerm, language); + const searchSource = typeof request.query.source !== 'undefined' ? request.query.source.trim() : undefined; // Get base language in cases like 'en-US' + const search = new SearchController(searchTerm, language); - return await search.quickSearchInventaire(); + switch (searchSource) { + case 'openLibrary': { + return await search.searchOpenLibrary(searchBy); + } + case 'bookBrainz': { + return await search.searchOpenLibrary(searchBy); + } + case 'inventaire': + default: { + if (searchBy === 'title') { + return await search.quickSearchInventaire(); + } else { + return await search.searchInventaire(searchBy); + } + } + } }); fastify.get('/api/search/cover', async (request, reply) => {