From 3f3ae76f2be5473eea75f1ac0509db29c60882ea Mon Sep 17 00:00:00 2001 From: Robbie Antenesse Date: Thu, 12 Sep 2019 11:35:43 -0600 Subject: [PATCH] Add Faker.js; Create BooksController and /api/books route --- package.json | 1 + server/controllers/books.js | 202 +++++++++++++++++++++++++++++++++++ server/controllers/search.js | 110 ++----------------- server/routes/books.js | 13 +++ server/routes/home.js | 17 --- yarn.lock | 5 + 6 files changed, 230 insertions(+), 118 deletions(-) create mode 100644 server/controllers/books.js create mode 100644 server/routes/books.js delete mode 100644 server/routes/home.js diff --git a/package.json b/package.json index 117e64b..50f8ce3 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,7 @@ "concurrently": "^4.1.2", "cross-env": "^5.2.1", "cssnano": "^4.1.10", + "faker": "^4.1.0", "parcel-bundler": "^1.12.3", "parcel-plugin-goodie-bag": "^2.0.0", "rimraf": "^3.0.0", diff --git a/server/controllers/books.js b/server/controllers/books.js new file mode 100644 index 0000000..e840e81 --- /dev/null +++ b/server/controllers/books.js @@ -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; \ No newline at end of file diff --git a/server/controllers/search.js b/server/controllers/search.js index 481fef6..b677bdd 100644 --- a/server/controllers/search.js +++ b/server/controllers/search.js @@ -1,5 +1,7 @@ const fetch = require('node-fetch'); +const BooksController = require('./books'); + class SearchController { constructor(inventaireDomain, searchTerm, language = 'en') { this.inventaire = inventaireDomain; @@ -97,41 +99,14 @@ class SearchController { }); const works = responseJSON.works.map(work => { - const hasLabels = typeof work.labels !== 'undefined'; - const hasDescriptions = typeof work.descriptions !== 'undefined'; - + const booksController = new BooksController(this.inventaire, work.uri, this.lang); + const bookData = booksController.handleInventaireEntity(work); + const communityData = booksController.getCommunityData(5); + return { - name: ( - hasLabels && typeof work.labels[this.lang] !== 'undefined' - ? 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), - }; + ...bookData, + ...communityData, + } }); 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 */ diff --git a/server/routes/books.js b/server/routes/books.js new file mode 100644 index 0000000..f2f4c27 --- /dev/null +++ b/server/routes/books.js @@ -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 \ No newline at end of file diff --git a/server/routes/home.js b/server/routes/home.js deleted file mode 100644 index 3d7e0ba..0000000 --- a/server/routes/home.js +++ /dev/null @@ -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 \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index a4c6ad8..6eab5c8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2605,6 +2605,11 @@ extsprintf@^1.2.0: resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.4.0.tgz#e2689f8f356fad62cca65a3a91c5df5f9551692f" 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: version "2.1.0" resolved "https://registry.yarnpkg.com/falafel/-/falafel-2.1.0.tgz#96bb17761daba94f46d001738b3cedf3a67fe06c"