Add Faker.js; Create BooksController and /api/books route
This commit is contained in:
parent
8047c83ede
commit
3f3ae76f2b
|
@ -21,6 +21,7 @@
|
||||||
"concurrently": "^4.1.2",
|
"concurrently": "^4.1.2",
|
||||||
"cross-env": "^5.2.1",
|
"cross-env": "^5.2.1",
|
||||||
"cssnano": "^4.1.10",
|
"cssnano": "^4.1.10",
|
||||||
|
"faker": "^4.1.0",
|
||||||
"parcel-bundler": "^1.12.3",
|
"parcel-bundler": "^1.12.3",
|
||||||
"parcel-plugin-goodie-bag": "^2.0.0",
|
"parcel-plugin-goodie-bag": "^2.0.0",
|
||||||
"rimraf": "^3.0.0",
|
"rimraf": "^3.0.0",
|
||||||
|
|
|
@ -0,0 +1,202 @@
|
||||||
|
const fetch = require('node-fetch');
|
||||||
|
|
||||||
|
class BooksController {
|
||||||
|
constructor(inventaireDomain, bookURI, language) {
|
||||||
|
this.inventaire = inventaireDomain;
|
||||||
|
this.uri = bookURI;
|
||||||
|
this.lang = language;
|
||||||
|
}
|
||||||
|
|
||||||
|
getBookData() {
|
||||||
|
const bookData = this.getBookDataFromInventaire();
|
||||||
|
const communityData = this.getCommunityData();
|
||||||
|
|
||||||
|
return {
|
||||||
|
...bookData,
|
||||||
|
...communityData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getCommunityData(maxReviews) {
|
||||||
|
if (process.NODE_ENV !== 'production') {
|
||||||
|
return this.getFakeData(maxReviews);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
getFakeData(maxReviews) {
|
||||||
|
const faker = require('faker');
|
||||||
|
const numberOfReviews = Math.floor(Math.random() * 100);
|
||||||
|
const reviews = [];
|
||||||
|
for (let i = 0; i < numberOfReviews; i++) {
|
||||||
|
const reviewerName = Math.random() < 0.5
|
||||||
|
? faker.fake('{{name.firstName}} {{name.lastName}}')
|
||||||
|
: faker.fake('{{hacker.adjective}}{{hacker.noun}}');
|
||||||
|
reviews.push({
|
||||||
|
reviewer: {
|
||||||
|
name: reviewerName,
|
||||||
|
handle: faker.fake('@{{internet.userName}}@{{internet.domainName}}'),
|
||||||
|
},
|
||||||
|
date: faker.date.past(),
|
||||||
|
rating: parseFloat((Math.random() * 5).toFixed(1)),
|
||||||
|
review: faker.lorem.paragraph(),
|
||||||
|
hearts: Math.floor(Math.random() * 1000),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const averageRating = parseFloat((reviews.reduce((total, review) => {
|
||||||
|
return total + review.rating;
|
||||||
|
}, 0) / numberOfReviews).toFixed(1));
|
||||||
|
|
||||||
|
reviews.sort((a, b) => {
|
||||||
|
if (a.hearts === b.hearts) return 0;
|
||||||
|
return a.hearts > b.hearts ? -1 : 1;
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
averageRating,
|
||||||
|
numberOfReviews,
|
||||||
|
reviews: typeof maxReviews !== 'undefined' ? reviews.slice(0, maxReviews - 1) : reviews,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handleInventaireEntity(entityObject) {
|
||||||
|
const hasLabels = typeof entityObject.labels !== 'undefined';
|
||||||
|
const hasDescriptions = typeof entityObject.descriptions !== 'undefined';
|
||||||
|
|
||||||
|
return {
|
||||||
|
name: (
|
||||||
|
hasLabels && typeof entityObject.labels[this.lang] !== 'undefined'
|
||||||
|
? entityObject.labels[this.lang]
|
||||||
|
: (
|
||||||
|
hasLabels && Object.keys(entityObject.labels).length > 0
|
||||||
|
? entityObject.labels[Object.keys(entityObject.labels)[0]]
|
||||||
|
: null
|
||||||
|
)
|
||||||
|
),
|
||||||
|
description: (
|
||||||
|
hasDescriptions && typeof entityObject.descriptions[this.lang] !== 'undefined'
|
||||||
|
? entityObject.descriptions[this.lang]
|
||||||
|
: (
|
||||||
|
hasDescriptions && Object.keys(entityObject.descriptions).length > 0
|
||||||
|
? entityObject.descriptions[Object.keys(entityObject.descriptions)[0]]
|
||||||
|
: null
|
||||||
|
)
|
||||||
|
),
|
||||||
|
link: (
|
||||||
|
typeof entityObject.uri !== 'undefined'
|
||||||
|
? `${this.inventaire}/entity/${entityObject.uri}`
|
||||||
|
: null
|
||||||
|
),
|
||||||
|
uri: (
|
||||||
|
typeof entityObject.uri !== 'undefined'
|
||||||
|
? entityObject.uri
|
||||||
|
: null
|
||||||
|
),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async getBookDataFromInventaire() {
|
||||||
|
if (this.uri) {
|
||||||
|
const request = fetch(`${this.inventaire}/api/entities?action=by-uris&uris=${encodeURIComponent(this.uri)}`)
|
||||||
|
request.catch(exception => {
|
||||||
|
console.error(exception);
|
||||||
|
return {
|
||||||
|
error: exception,
|
||||||
|
message: 'An error occurred when trying to reach the Inventaire API.',
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const json = request.then(response => response.json());
|
||||||
|
json.catch(exception => {
|
||||||
|
console.error(exception);
|
||||||
|
return {
|
||||||
|
error: exception,
|
||||||
|
message: 'An error occurred when trying read the response from Inventaire as JSON.',
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const bookData = await json;
|
||||||
|
|
||||||
|
if (typeof bookData.entities !== 'undefined' && typeof bookData.entities[this.uri] !== 'undefined') {
|
||||||
|
const bookData = this.handleInventaireEntity(bookData.entities[this.uri], this.lang);
|
||||||
|
bookData['covers'] = await this.getInventaireCovers();
|
||||||
|
|
||||||
|
return bookData;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
name: 'No URI provided',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async getInventaireCovers() {
|
||||||
|
if (!this.uri) {
|
||||||
|
return Promise.resolve([]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Note: property `wdt:P629` is a given entity (uri)'s list of editions (ISBNs).
|
||||||
|
const editionsRequest = fetch(`${this.inventaire}/api/entities?action=reverse-claims&uri=${encodeURIComponent(this.uri)}&property=wdt:P629`)
|
||||||
|
editionsRequest.catch(exception => {
|
||||||
|
console.error(exception);
|
||||||
|
return {
|
||||||
|
error: exception,
|
||||||
|
message: `An error occurred when trying to reach the Inventaire API for URI ${this.uri}.`,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const editionsJson = editionsRequest.then(response => response.json());
|
||||||
|
editionsJson.catch(exception => {
|
||||||
|
console.error(exception);
|
||||||
|
return {
|
||||||
|
error: exception,
|
||||||
|
message: 'An error occurred when trying read the response from Inventaire as JSON.',
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const editions = await editionsJson;
|
||||||
|
const editionURIs = typeof editions.uris !== 'undefined' ? editions.uris.join('|') : false;
|
||||||
|
|
||||||
|
if (editionURIs === false) {
|
||||||
|
return Promise.resolve([]);
|
||||||
|
}
|
||||||
|
|
||||||
|
const isbnsRequest = fetch(`${this.inventaire}/api/entities?action=by-uris&uris=${encodeURIComponent(editionURIs)}`);
|
||||||
|
isbnsRequest.catch(exception => {
|
||||||
|
console.error(exception);
|
||||||
|
return {
|
||||||
|
error: exception,
|
||||||
|
message: `An error occurred when trying to reach the Inventaire API for URI ${this.uri}.`,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const isbnsJson = isbnsRequest.then(response => response.json());
|
||||||
|
isbnsJson.catch(exception => {
|
||||||
|
console.error(exception);
|
||||||
|
return {
|
||||||
|
error: exception,
|
||||||
|
message: 'An error occurred when trying read the response from Inventaire as JSON.',
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return isbnsJson.then(responseJSON => {
|
||||||
|
if (typeof responseJSON.entities === 'undefined') {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return Object.keys(responseJSON.entities).filter(key => {
|
||||||
|
const entity = responseJSON.entities[key];
|
||||||
|
return entity.originalLang === this.lang && typeof entity.claims !== undefined && typeof entity.claims['invp:P2'] !== undefined;
|
||||||
|
}).map(key => {
|
||||||
|
const entity = responseJSON.entities[key];
|
||||||
|
return {
|
||||||
|
uri: entity.uri,
|
||||||
|
url: `${this.inventaire}/img/entities/${entity.claims['invp:P2'][0]}`,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = BooksController;
|
|
@ -1,5 +1,7 @@
|
||||||
const fetch = require('node-fetch');
|
const fetch = require('node-fetch');
|
||||||
|
|
||||||
|
const BooksController = require('./books');
|
||||||
|
|
||||||
class SearchController {
|
class SearchController {
|
||||||
constructor(inventaireDomain, searchTerm, language = 'en') {
|
constructor(inventaireDomain, searchTerm, language = 'en') {
|
||||||
this.inventaire = inventaireDomain;
|
this.inventaire = inventaireDomain;
|
||||||
|
@ -97,41 +99,14 @@ class SearchController {
|
||||||
});
|
});
|
||||||
|
|
||||||
const works = responseJSON.works.map(work => {
|
const works = responseJSON.works.map(work => {
|
||||||
const hasLabels = typeof work.labels !== 'undefined';
|
const booksController = new BooksController(this.inventaire, work.uri, this.lang);
|
||||||
const hasDescriptions = typeof work.descriptions !== 'undefined';
|
const bookData = booksController.handleInventaireEntity(work);
|
||||||
|
const communityData = booksController.getCommunityData(5);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
name: (
|
...bookData,
|
||||||
hasLabels && typeof work.labels[this.lang] !== 'undefined'
|
...communityData,
|
||||||
? work.labels[this.lang]
|
}
|
||||||
: (
|
|
||||||
hasLabels && Object.keys(work.labels).length > 0
|
|
||||||
? work.labels[Object.keys(work.labels)[0]]
|
|
||||||
: null
|
|
||||||
)
|
|
||||||
),
|
|
||||||
description: (
|
|
||||||
hasDescriptions && typeof work.descriptions[this.lang] !== 'undefined'
|
|
||||||
? work.descriptions[this.lang]
|
|
||||||
: (
|
|
||||||
hasDescriptions && Object.keys(work.descriptions).length > 0
|
|
||||||
? work.descriptions[Object.keys(work.descriptions)[0]]
|
|
||||||
: null
|
|
||||||
)
|
|
||||||
),
|
|
||||||
link: (
|
|
||||||
typeof work.uri !== 'undefined'
|
|
||||||
? `${this.inventaire}/entity/${work.uri}`
|
|
||||||
: null
|
|
||||||
),
|
|
||||||
uri: (
|
|
||||||
typeof work.uri !== 'undefined'
|
|
||||||
? work.uri
|
|
||||||
: null
|
|
||||||
),
|
|
||||||
rating: (Math.random() * 5).toFixed(1),
|
|
||||||
reviewCount: Math.floor(Math.random() * 100),
|
|
||||||
};
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
@ -143,73 +118,6 @@ class SearchController {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async getInventaireCovers(inventaireURI) {
|
|
||||||
if (!inventaireURI) {
|
|
||||||
return Promise.resolve([]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Note: property `wdt:P629` is a given entity (uri)'s list of editions (ISBNs).
|
|
||||||
const editionsRequest = fetch(`${this.inventaire}/api/entities?action=reverse-claims&uri=${encodeURIComponent(inventaireURI)}&property=wdt:P629`)
|
|
||||||
editionsRequest.catch(exception => {
|
|
||||||
console.error(exception);
|
|
||||||
return {
|
|
||||||
error: exception,
|
|
||||||
message: `An error occurred when trying to reach the Inventaire API for URI ${inventaireURI}.`,
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const editionsJson = editionsRequest.then(response => response.json());
|
|
||||||
editionsJson.catch(exception => {
|
|
||||||
console.error(exception);
|
|
||||||
return {
|
|
||||||
error: exception,
|
|
||||||
message: 'An error occurred when trying read the response from Inventaire as JSON.',
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const editions = await editionsJson;
|
|
||||||
const editionURIs = typeof editions.uris !== 'undefined' ? editions.uris.join('|') : false;
|
|
||||||
|
|
||||||
if (editionURIs === false) {
|
|
||||||
return Promise.resolve([]);
|
|
||||||
}
|
|
||||||
|
|
||||||
const isbnsRequest = fetch(`${this.inventaire}/api/entities?action=by-uris&uris=${encodeURIComponent(editionURIs)}`);
|
|
||||||
isbnsRequest.catch(exception => {
|
|
||||||
console.error(exception);
|
|
||||||
return {
|
|
||||||
error: exception,
|
|
||||||
message: `An error occurred when trying to reach the Inventaire API for URI ${inventaireURI}.`,
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const isbnsJson = isbnsRequest.then(response => response.json());
|
|
||||||
isbnsJson.catch(exception => {
|
|
||||||
console.error(exception);
|
|
||||||
return {
|
|
||||||
error: exception,
|
|
||||||
message: 'An error occurred when trying read the response from Inventaire as JSON.',
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return isbnsJson.then(responseJSON => {
|
|
||||||
if (typeof responseJSON.entities === 'undefined') {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
return Object.keys(responseJSON.entities).filter(key => {
|
|
||||||
const entity = responseJSON.entities[key];
|
|
||||||
return entity.originalLang === this.lang && typeof entity.claims !== undefined && typeof entity.claims['invp:P2'] !== undefined ;
|
|
||||||
}).map(key => {
|
|
||||||
const entity = responseJSON.entities[key];
|
|
||||||
return {
|
|
||||||
uri: entity.uri,
|
|
||||||
url: `${this.inventaire}/img/entities/${entity.claims['invp:P2'][0]}`,
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Query a MediaWiki api.php instance with the given options
|
* Query a MediaWiki api.php instance with the given options
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -0,0 +1,13 @@
|
||||||
|
const BooksController = require('../controllers/books');
|
||||||
|
|
||||||
|
async function routes(fastify, options) {
|
||||||
|
fastify.get('/api/books', async (request, reply) => {
|
||||||
|
const bookURI = typeof request.query.uri !== 'undefined' ? request.query.uri.trim() : '';
|
||||||
|
const language = typeof request.query.lang !== 'undefined' ? request.query.lang.trim().split('-')[0] : undefined; // Get base language in cases like 'en-US'
|
||||||
|
const books = new BooksController(fastify.siteConfig.inventaireDomain, bookURI, language);
|
||||||
|
|
||||||
|
return books.getBookData();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = routes
|
|
@ -1,17 +0,0 @@
|
||||||
async function routes(fastify, options) {
|
|
||||||
fastify.get('/', async (request, reply) => {
|
|
||||||
const viewData = {};
|
|
||||||
if (typeof request.query.loggedOut !== 'undefined') {
|
|
||||||
viewData.message = 'You have been logged out';
|
|
||||||
} else {
|
|
||||||
viewData.message = request.isLoggedInUser ? JSON.stringify(fastify.jwt.decode(request.cookies.token)) : 'you are NOT logged in';
|
|
||||||
}
|
|
||||||
if (request.isLoggedInUser) {
|
|
||||||
viewData.loggedIn = true;
|
|
||||||
viewData.statuses = [{ title: 'books' }, { title: 'fun' }];
|
|
||||||
}
|
|
||||||
reply.view('home.hbs', viewData);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = routes
|
|
|
@ -2605,6 +2605,11 @@ extsprintf@^1.2.0:
|
||||||
resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.4.0.tgz#e2689f8f356fad62cca65a3a91c5df5f9551692f"
|
resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.4.0.tgz#e2689f8f356fad62cca65a3a91c5df5f9551692f"
|
||||||
integrity sha1-4mifjzVvrWLMplo6kcXfX5VRaS8=
|
integrity sha1-4mifjzVvrWLMplo6kcXfX5VRaS8=
|
||||||
|
|
||||||
|
faker@^4.1.0:
|
||||||
|
version "4.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/faker/-/faker-4.1.0.tgz#1e45bbbecc6774b3c195fad2835109c6d748cc3f"
|
||||||
|
integrity sha1-HkW7vsxndLPBlfrSg1EJxtdIzD8=
|
||||||
|
|
||||||
falafel@^2.1.0:
|
falafel@^2.1.0:
|
||||||
version "2.1.0"
|
version "2.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/falafel/-/falafel-2.1.0.tgz#96bb17761daba94f46d001738b3cedf3a67fe06c"
|
resolved "https://registry.yarnpkg.com/falafel/-/falafel-2.1.0.tgz#96bb17761daba94f46d001738b3cedf3a67fe06c"
|
||||||
|
|
Loading…
Reference in New Issue