Compare commits

...

3 Commits

4 changed files with 130 additions and 46 deletions

View File

@ -9,6 +9,10 @@ class Inventaire {
return 'https://inventaire.io';
}
static getLink(uri) {
return `${Inventaire.url}/entity/${uri}`;
}
static handleQuickEntity(entityObject) {
return {
name: (
@ -21,9 +25,10 @@ class Inventaire {
? entityObject.description
: null
),
source: 'inventaire',
link: (
typeof entityObject.uri !== 'undefined'
? `${this.url}/entity/${entityObject.uri}`
? `${Inventaire.url}/entity/${entityObject.uri}`
: null
),
uri: (
@ -36,7 +41,7 @@ class Inventaire {
? entityObject.image.map(imageId => {
return {
uri: imageId.toString(),
url: `${this.url}/img/entities/${imageId}`,
url: `${Inventaire.url}/img/entities/${imageId}`,
}
})
: []
@ -67,9 +72,10 @@ class Inventaire {
: null
)
),
source: 'inventaire',
link: (
typeof entityObject.uri !== 'undefined'
? `${this.url}/entity/${entityObject.uri}`
? `${Inventaire.url}/entity/${entityObject.uri}`
: null
),
uri: (
@ -82,7 +88,7 @@ class Inventaire {
async getBookData(uri) {
if (uri) {
const request = fetch(`${this.url}/api/entities?action=by-uris&uris=${encodeURIComponent(uri)}`)
const request = fetch(`${Inventaire.url}/api/entities?action=by-uris&uris=${encodeURIComponent(uri)}`)
request.catch(exception => {
console.error(exception);
return {
@ -103,7 +109,7 @@ class Inventaire {
if (typeof bookData.entities !== 'undefined' && typeof bookData.entities[uri] !== 'undefined') {
const bookData = Inventaire.handleEntity(bookData.entities[uri], this.lang);
bookData['covers'] = await this.getCovers();
bookData['covers'] = await this.getCovers(bookData.uri);
return bookData;
}
@ -120,7 +126,7 @@ class Inventaire {
}
// Note: property `wdt:P629` is a given entity (uri)'s list of editions (ISBNs).
const editionsRequest = fetch(`${this.url}/api/entities?action=reverse-claims&uri=${encodeURIComponent(uri)}&property=wdt:P629`)
const editionsRequest = fetch(`${Inventaire.url}/api/entities?action=reverse-claims&uri=${encodeURIComponent(uri)}&property=wdt:P629`)
editionsRequest.catch(exception => {
console.error(exception);
return {
@ -145,7 +151,7 @@ class Inventaire {
return Promise.resolve([]);
}
const isbnsRequest = fetch(`${this.url}/api/entities?action=by-uris&uris=${encodeURIComponent(editionURIs)}`);
const isbnsRequest = fetch(`${Inventaire.url}/api/entities?action=by-uris&uris=${encodeURIComponent(editionURIs)}`);
isbnsRequest.catch(exception => {
console.error(exception);
return {
@ -175,7 +181,7 @@ class Inventaire {
const entity = responseJSON.entities[key];
return {
uri: entity.uri,
url: typeof entity.claims['invp:P2'] !== 'undefined' ? `${this.url}/img/entities/${entity.claims['invp:P2'][0]}` : null,
url: typeof entity.claims['invp:P2'] !== 'undefined' ? `${Inventaire.url}/img/entities/${entity.claims['invp:P2'][0]}` : null,
publishDate: typeof entity.claims['wdt:P577'] !== 'undefined' ? entity.claims['wdt:P577'][0] : null,
}
});

View File

@ -0,0 +1,80 @@
const Inventaire = require('./bookData/Inventaire');
const SearchController = require('./search');
class BookReferenceController {
constructor(sequelizeModels, language) {
this.models = sequelizeModels;
this.lang = language;
}
async createOrUpdateReference(source, sourceId) {
const searchController = new SearchController(this.models);
const existingReference = searchController.searchReferencesBySourceCode(source, sourceId);
if (existingReference.id !== null) {
return existingReference;
}
let dataClass;
switch (source) {
case 'openlibrary': {
// break;
}
case 'inventaire':
default: {
dataClass = new Inventaire(this.lang);
break;
}
}
// Get formatted book data from source
const bookData = dataClass.getBookData(sourceId);
if (typeof bookData.uri !== 'undefined') {
// Check for references by exact name and author from source
const matchingReference = await searchController.searchReferencesForExactMatch(bookData.name, bookData.description);
if (matchingReference.id !== null) {
// If a match is found, update the sources of reference in the database and return it.
return await this.addSourceToReference(matchingReference, source, sourceId);
}
return await this.createReference(bookData, source, sourceId);
}
return {
error: true,
}
}
async createReference(bookData, source, sourceId) {
const newReference = await this.models.BookReference.create({
name: bookData.name,
description: bookData.description,
sources: {
[source]: sourceId,
},
covers: bookData.covers,
locale: this.lang,
});
newReference.totalInteractions = 0;
newReference.numReviews = 0;
newReference.averageRating = null;
newReference.Interactions = [];
newReference.Reviews = [];
newReference.Ratings = [];
return newReference;
}
async addSourceToReference(reference, source, sourceId) {
const updatedSources = Object.assign({ [source]: sourceId }, reference.sources);
return await reference.update({
sources: updatedSources,
}).then(() => {
reference.sources = updatedSources;
return reference;
});
}
}
module.exports = BookReferenceController;

View File

@ -11,16 +11,8 @@ const defaultSearchOptions = {
}
class SearchController {
constructor(sequelizeModels, searchTerm, options = defaultSearchOptions) {
constructor(sequelizeModels) {
this.models = sequelizeModels;
this.searchTerm = searchTerm;
this.searchBy = options.searchBy.replace('title', 'name').replace('author', 'description');
this.source = options.source;
this.lang = options.language;
}
get hasQuery() {
return typeof this.searchTerm !== 'undefined' && this.searchTerm !== '';
}
get bookReferenceSearchAttributes() {
@ -59,52 +51,58 @@ class SearchController {
};
}
async search() {
const bookReferences = await this.searchReferences();
async search(searchTerm, options = defaultSearchOptions) {
const searchBy = options.searchBy.replace('title', 'name').replace('author', 'description');
const { source, language } = options;
const bookReferences = await this.searchReferences(searchTerm, options);
let searchResults;
switch (this.source) {
switch (source) {
case 'openlibrary': {
searchResults = await this.searchOpenLibrary(this.searchBy);
searchResults = await this.searchOpenLibrary(searchBy);
break;
}
case 'inventaire':
default: {
searchResults = await quickSearchInventaire(this.searchTerm, this.lang);
searchResults = await quickSearchInventaire(searchTerm, language);
break;
}
}
// Add any search results that match refs with the same URI and delete from results array
const urisToCheck = searchResults.filter(
result => !bookReferences.some(ref => result.uri === ref.sources[this.source])
result => !bookReferences.some(ref => result.uri === ref.sources[source])
).map(result => result.uri);
let extraReferences = [];
if (urisToCheck.length > 0) {
const foundReferences = await this.searchReferencesBySourceCodes(this.source, urisToCheck);
return [
...bookReferences,
...foundReferences,
...searchResults.filter(result => !urisToCheck.includes(result.uri)),
];
extraReferences = await this.searchReferencesBySourceCodes(source, urisToCheck);
}
return [
...bookReferences,
...searchResults.filter(result => result !== null),
...extraReferences,
...searchResults.filter( // Only show the rest of the search results
result => !extraReferences.some(
ref => result.uri === ref.sources[source]
)
),
];
}
async searchReferences() {
async searchReferences(searchTerm, options = defaultSearchOptions) {
const searchBy = options.searchBy.replace('title', 'name').replace('author', 'description');
const { language } = options;
const { BookReference } = this.models;
const exact = await BookReference.findAll({
where: {
[Op.and]: [ // All of the contained cases are true
{
[this.searchBy]: this.searchTerm, // searchBy is exactly searchTerm
[searchBy]: searchTerm, // searchBy is exactly searchTerm
},
{
locale: this.lang,
locale: language,
},
]
},
@ -122,12 +120,12 @@ class SearchController {
where: {
[Op.and]: [ // All of the contained cases are true
{
[this.searchBy]: { // `name` or `description`
[Op.substring]: this.searchTerm, // LIKE '%searchTerm%'
[searchBy]: { // `name` or `description`
[Op.substring]: searchTerm, // LIKE '%searchTerm%'
},
},
{
locale: this.lang,
locale: language,
},
]
},
@ -155,7 +153,7 @@ class SearchController {
async searchReferencesBySourceCodes(source, sourceIds) {
const sourceJSONKey = `"${source}"`; // Enable searching withing JSON column.
return await this.models.BookReference.findOne({
return await this.models.BookReference.findAll({
where: {
[Op.or]: sourceIds.map(sourceId => ({
source: {
@ -218,14 +216,14 @@ class SearchController {
}
async searchWikiBooks(term) {
if (!this.hasQuery) {
if (!term) {
return [];
}
const query = this.mediaWikiQuery('https://en.wikibooks.org/w/api.php', {
action: 'query',
list: 'search',
srsearch: this.searchTerm,
srsearch: term,
srprop: '',
});
query(response => {
@ -257,19 +255,19 @@ class SearchController {
});
}
async searchOpenLibrary(searchBy = 'title') {
if (!this.hasQuery) {
async searchOpenLibrary(searchTerm, searchBy = 'title') {
if (!searchTerm) {
return [];
}
return fetch(`https://openlibrary.org/search.json?${searchBy}=${encodeURIComponent(this.searchTerm)}`)
return fetch(`https://openlibrary.org/search.json?${searchBy}=${encodeURIComponent(searchTerm)}`)
.then(res => res.json())
.then(response => {
if (!response.hasOwnProperty('docs')) {
return [];
}
const booksController = new BooksController('openLibrary', undefined, this.lang);
const booksController = new BooksController('openLibrary');
// Format the response into usable objects
const docs = response.docs.map(doc => {

View File

@ -6,9 +6,9 @@ async function routes(fastify, options) {
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 source = typeof request.query.source !== 'undefined' ? request.query.source.trim() : undefined; // Get base language in cases like 'en-US'
const controller = new SearchController(fastify.models, searchTerm, { searchBy, source, language });
const controller = new SearchController(fastify.models);
return await controller.search();
return await controller.search(searchTerm, { searchBy, source, language });
});
fastify.get('/api/search/cover', async (request, reply) => {