Add search by Open Library

This commit is contained in:
Robbie Antenesse 2019-09-26 12:22:58 -06:00
parent abc1e10b3f
commit e39fe52e65
5 changed files with 89 additions and 26 deletions

View File

@ -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;

View File

@ -94,10 +94,10 @@ export const searchView = (state, emit, i18n) => {
<option value="inventaire" ${controller.state.searchSource === 'inventaire' ? 'selected' : null}>
Inventaire
</option>
<option value="openlibrary" ${controller.state.searchSource === 'openlibrary' ? 'selected' : null}>
<option value="openLibrary" ${controller.state.searchSource === 'openLibrary' ? 'selected' : null}>
Open Library
</option>
<option value="bookbrainz" ${controller.state.searchSource === 'bookbrainz' ? 'selected' : null}>
<option value="bookBrainz" ${controller.state.searchSource === 'bookBrainz' ? 'selected' : null}>
BookBrainz
</option>
</select>

View File

@ -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)}`)

View File

@ -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;

View File

@ -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) => {