Improve search return values and performance

This commit is contained in:
Robbie Antenesse 2020-02-06 17:28:30 -07:00
parent a3f6137dec
commit 326747c0ce
1 changed files with 78 additions and 64 deletions

View File

@ -1,5 +1,5 @@
const fetch = require('node-fetch');
const { Op, fn } = require('sequelize');
const { Op, fn, col } = require('sequelize');
const BooksController = require('../bookData');
const { quickSearchInventaire } = require('./Inventaire');
@ -14,7 +14,7 @@ class SearchController {
constructor(sequelizeModels, searchTerm, options = defaultSearchOptions) {
this.models = sequelizeModels;
this.searchTerm = searchTerm;
this.searchBy = options.searchBy;
this.searchBy = options.searchBy.replace('title', 'name').replace('author', 'description');
this.source = options.source;
this.lang = options.language;
}
@ -23,28 +23,40 @@ class SearchController {
return typeof this.searchTerm !== 'undefined' && this.searchTerm !== '';
}
get includeQuery() {
return [
{
model: this.models.Review,
where: { text: { [Op.not]: null } },
attributes: [[fn('COUNT', 'id'), 'total']], // Get the total number of text reviews
as: 'reviews',
},
{
model: this.models.Review,
where: { rating: { [Op.not]: null } },
attributes: [[fn('AVG', 'rating'), 'average']], // Get the average star rating
as: 'rating',
},
]
}
get orderQuery() {
return [{
model: this.models.Review,
attributes: [[fn('COUNT', 'id'), 'total']], // Get the total number of text reviews
}, 'total', 'DESC']; // Order references from most to least interaction
get bookReferenceSearchAttributes() {
return {
include: [
{
as: 'Interactions',
model: this.models.Review,
attributes: ['id'],
required: false,
},
{
as: 'Reviews',
model: this.models.Review,
attributes: ['id'],
required: false,
},
{
as: 'Ratings',
model: this.models.Review,
attributes: ['rating'],
required: false,
},
], // These are all subsets of Review model specified in BookReference associations
attributes: [
[col('BookReference.id'), 'id'],
'name',
'description',
'sources',
'covers',
[fn('COUNT', col('Interactions.id')), 'totalInteractions'],
[fn('COUNT', col('Reviews.id')), 'numReviews'],
[fn('AVG', col('Ratings.rating')), 'averageRating'],
],
order: [[col('totalInteractions'), 'DESC']],
};
}
async search() {
@ -63,19 +75,18 @@ class SearchController {
}
// Add any search results that match refs with the same URI and delete from results array
searchResults.forEach((result, i) => {
// If the result is not already in bookReferences
if (!bookReferences.some(ref => result.uri === ref.sources[this.source])) {
// Check if the URI is already in references table
const reference = await this.searchReferencesBySourceCode(this.source, result.uri);
if (reference) {
bookReferences.push(reference);
searchResults[i] = null;
}
} else { // If the result is already in references, null it out.
searchResults[i] = null;
}
});
const urisToCheck = searchResults.filter(
result => !bookReferences.some(ref => result.uri === ref.sources[this.source])
).map(result => result.uri);
if (urisToCheck.length > 0) {
const foundReferences = await this.searchReferencesBySourceCodes(this.source, urisToCheck);
return [
...bookReferences,
...foundReferences,
...searchResults.filter(result => !urisToCheck.includes(result.uri)),
];
}
return [
...bookReferences,
@ -84,24 +95,7 @@ class SearchController {
}
async searchReferences() {
const { BookReference, Review } = this.models;
// const includeQuery = [{
// model: Review,
// include: [
// {
// model: Reaction.scope('Review'),
// group: ['reactionType'],
// attributes: ['reactionType', [fn('COUNT', 'reactionType'), 'count']]
// },
// ],
// order: [{
// model: Reaction.scope('Review'),
// attributes: [[fn('COUNT', 'id'), 'total']],
// limit: 1,
// }, 'total', 'DESC'],
// limit: 5,
// }];
const { BookReference } = this.models;
const exact = await BookReference.findAll({
where: {
@ -114,14 +108,15 @@ class SearchController {
},
]
},
include: includeQuery(Review),
order: orderQuery(Review),
});
...this.bookReferenceSearchAttributes,
}).then( // Empty results give 1 empty model in an array, so filter those out
references => references.filter(ref => typeof ref.id !== 'undefined' && ref.id !== null)
);
if (exact.length > 0) {
return exact;
}
// If no exact matches are found, return any approximate ones.
return await BookReference.findAll({
where: {
@ -136,9 +131,10 @@ class SearchController {
},
]
},
include: this.includeQuery,
order: this.orderQuery,
});
...this.bookReferenceSearchAttributes,
}).then( // Empty results give 1 empty model in an array, so filter those out
references => references.filter(ref => typeof ref.id !== 'undefined' && ref.id !== null)
);
}
async searchReferencesBySourceCode(source, sourceId) {
@ -151,8 +147,26 @@ class SearchController {
},
},
},
include: this.includeQuery,
});
...this.bookReferenceSearchAttributes,
}).then( // Empty results give 1 empty model in an array, so filter those out
references => references.filter(ref => typeof ref.id !== 'undefined' && ref.id !== null)
);
}
async searchReferencesBySourceCodes(source, sourceIds) {
const sourceJSONKey = `"${source}"`; // Enable searching withing JSON column.
return await this.models.BookReference.findOne({
where: {
[Op.or]: sourceIds.map(sourceId => ({
source: {
[sourceJSONKey]: sourceId,
},
})),
},
...this.bookReferenceSearchAttributes,
}).then( // Empty results give 1 empty model in an array, so filter those out
references => references.filter(ref => typeof ref.id !== 'undefined' && ref.id !== null)
);
}
/**