Merge branch 'master' of https://gitlab.com/Alamantus/Readlebee
This commit is contained in:
commit
7ea08aaf6a
|
@ -9,3 +9,4 @@ config.json
|
||||||
*.sqlite*
|
*.sqlite*
|
||||||
*.db
|
*.db
|
||||||
.dbversion
|
.dbversion
|
||||||
|
.DS_Store
|
|
@ -10,7 +10,7 @@ An attempt at a viable alternative to Goodreads
|
||||||
- Features we feel are essential to the project. Anything beyond the scope should be discussed for later and not prioritized.
|
- Features we feel are essential to the project. Anything beyond the scope should be discussed for later and not prioritized.
|
||||||
- [Dependencies Stack](https://gitlab.com/Alamantus/Readlebee/wikis/Dependencies-Stack)
|
- [Dependencies Stack](https://gitlab.com/Alamantus/Readlebee/wikis/Dependencies-Stack)
|
||||||
- A list of dependencies used in the project and a short explanation of what each of them are for.
|
- A list of dependencies used in the project and a short explanation of what each of them are for.
|
||||||
- [Contrubution Guidelines](./CONTRIBUTING.md)
|
- [Contribution Guidelines](./CONTRIBUTING.md)
|
||||||
- Subject to change but important to follow. Includes a basic code of conduct.
|
- Subject to change but important to follow. Includes a basic code of conduct.
|
||||||
- [Project chat via Gitter](https://gitter.io/Readlebee)
|
- [Project chat via Gitter](https://gitter.io/Readlebee)
|
||||||
- Real-time discussion about the project.
|
- Real-time discussion about the project.
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import { ViewController } from '../controller';
|
import { ViewController } from '../controller';
|
||||||
|
import { ShelvesController } from '../shelves/controller';
|
||||||
|
|
||||||
export class SearchController extends ViewController {
|
export class SearchController extends ViewController {
|
||||||
constructor(state, emit, i18n) {
|
constructor(state, emit, i18n) {
|
||||||
|
@ -13,6 +14,7 @@ export class SearchController extends ViewController {
|
||||||
done: true,
|
done: true,
|
||||||
results: [],
|
results: [],
|
||||||
openModal: null,
|
openModal: null,
|
||||||
|
showShelves: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
this.emit = emit;
|
this.emit = emit;
|
||||||
|
@ -44,6 +46,19 @@ export class SearchController extends ViewController {
|
||||||
return this.state.openModal;
|
return this.state.openModal;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get hasFetchedShelves() {
|
||||||
|
return typeof this.appState.viewStates.shelves !== 'undefined'
|
||||||
|
&& typeof this.appState.viewStates.shelves.myShelves !== 'undefined'
|
||||||
|
&& this.appState.viewStates.shelves.myShelves.length > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
get shelves() {
|
||||||
|
if (this.hasFetchedShelves) {
|
||||||
|
return this.appState.viewStates.shelves.myShelves;
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
set openModal(modalId) {
|
set openModal(modalId) {
|
||||||
this.state.openModal = modalId;
|
this.state.openModal = modalId;
|
||||||
}
|
}
|
||||||
|
@ -85,4 +100,29 @@ export class SearchController extends ViewController {
|
||||||
|
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
showShelves () {
|
||||||
|
const shelfController = new ShelvesController(this.appState, this.i18n);
|
||||||
|
let shelvesPromise;
|
||||||
|
if (shelfController.state.myShelves.length < 1) {
|
||||||
|
console.log('getting');
|
||||||
|
shelvesPromise = shelfController.getUserShelves();
|
||||||
|
} else {
|
||||||
|
shelvesPromise = Promise.resolve();
|
||||||
|
}
|
||||||
|
shelvesPromise.then(() => {
|
||||||
|
console.log(shelfController.state.myShelves);
|
||||||
|
this.showShelves = true;
|
||||||
|
this.emit('render');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
addToShelf(bookData, shelfId) {
|
||||||
|
const shelfController = new ShelvesController(this.appState, this.i18n);
|
||||||
|
shelfController.addItemToShelf(bookData, shelfId).then(result => {
|
||||||
|
console.log(result);
|
||||||
|
this.showShelves = false;
|
||||||
|
this.emit('render');
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -16,7 +16,6 @@ export const searchView = (state, emit, i18n) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returning an array in a view allows non-shared parent HTML elements.
|
// Returning an array in a view allows non-shared parent HTML elements.
|
||||||
// This one doesn't have the problem right now, but it's good to remember.
|
|
||||||
return [
|
return [
|
||||||
html`<h1 class="title">${__('search.header')}</h1>`,
|
html`<h1 class="title">${__('search.header')}</h1>`,
|
||||||
|
|
||||||
|
@ -45,7 +44,7 @@ export const searchView = (state, emit, i18n) => {
|
||||||
|
|
||||||
// Search Options Section
|
// Search Options Section
|
||||||
html`<section class="flex one two-700">
|
html`<section class="flex one two-700">
|
||||||
<div>
|
${/*<div>
|
||||||
${modal('searchSourceInfo', controller, [
|
${modal('searchSourceInfo', controller, [
|
||||||
html`<p>
|
html`<p>
|
||||||
${__('search.search_source.help.text')}
|
${__('search.search_source.help.text')}
|
||||||
|
@ -91,7 +90,8 @@ export const searchView = (state, emit, i18n) => {
|
||||||
</option>
|
</option>
|
||||||
</select>
|
</select>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>*/'' // Temporarily comment out the source chooser so I can focus on just Inventaire
|
||||||
|
}
|
||||||
<div>
|
<div>
|
||||||
${__('search.search_by.label')}<br>
|
${__('search.search_by.label')}<br>
|
||||||
|
|
||||||
|
|
|
@ -8,21 +8,28 @@ export const resultDetails = (searchController, result, emit = () => {}) => {
|
||||||
const { __ } = searchController.i18n;
|
const { __ } = searchController.i18n;
|
||||||
const modalId = `result_${result.uri}`;
|
const modalId = `result_${result.uri}`;
|
||||||
|
|
||||||
|
const hasReviews = typeof result.averageRating !== 'undefined' && typeof result.numberOfReviews !== 'undefined';
|
||||||
|
|
||||||
const buttonHTML = html`<label for=${modalId} class="pseudo button">
|
const buttonHTML = html`<label for=${modalId} class="pseudo button">
|
||||||
<span data-tooltip="${__('interaction.average_rating')}: ${result.averageRating}">
|
${!hasReviews
|
||||||
${starRating(result.averageRating)}
|
? __('search.no_reviews')
|
||||||
</span>
|
: html`<span data-tooltip="${__('interaction.average_rating')}: ${result.averageRating}">
|
||||||
<span style="margin-left:10px;" data-tooltip=${__('interaction.reviews_written')}>
|
${starRating(result.averageRating)}
|
||||||
<span style="margin-right:8px;"><i class="icon-chat"></i></span>
|
</span>
|
||||||
<span>${result.numberOfReviews}</span>
|
<span style="margin-left:10px;" data-tooltip=${__('interaction.reviews_written')}>
|
||||||
</span>
|
<span style="margin-right:8px;"><i class="icon-chat"></i></span>
|
||||||
|
<span>${result.numberOfReviews}</span>
|
||||||
|
</span>`
|
||||||
|
}
|
||||||
|
<br />
|
||||||
|
<small>${__('search.click_for_details')}</small>
|
||||||
</label>`;
|
</label>`;
|
||||||
|
|
||||||
const tabNames = ['one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine', 'ten', 'eleven', 'twelve', 'thirteen', 'fourteen', 'fifteen', 'sixteen', 'seventeen', 'eighteen', 'nineteen', 'twenty'];
|
const tabNames = ['one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine', 'ten', 'eleven', 'twelve', 'thirteen', 'fourteen', 'fifteen', 'sixteen', 'seventeen', 'eighteen', 'nineteen', 'twenty'];
|
||||||
|
|
||||||
const modalContent = html`<article class="flex">
|
const modalContent = html`<article class="flex">
|
||||||
<div class="sixth-700" style="text-align:center;">
|
<div class="sixth-700" style="text-align:center;">
|
||||||
<h4>Covers</h4>
|
<h4>${__('search.covers')}</h4>
|
||||||
${typeof result.covers === 'undefined'
|
${typeof result.covers === 'undefined'
|
||||||
? html`<span style="font-size:3em;"><i class="icon-loading animate-spin"></i></span>`
|
? html`<span style="font-size:3em;"><i class="icon-loading animate-spin"></i></span>`
|
||||||
: html`<div class="tabs ${typeof tabNames[result.covers.length - 1] !== 'undefined' ? tabNames[result.covers.length - 1] : null}">
|
: html`<div class="tabs ${typeof tabNames[result.covers.length - 1] !== 'undefined' ? tabNames[result.covers.length - 1] : null}">
|
||||||
|
@ -55,33 +62,41 @@ export const resultDetails = (searchController, result, emit = () => {}) => {
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
<div class="two-third-700">
|
<div class="two-third-700">
|
||||||
<h4>${__('interaction.average_rating')}</h4>
|
${!hasReviews
|
||||||
<span data-tooltip="${result.averageRating}">${starRating(result.averageRating)}</span>
|
? html`<h4>${__('search.no_reviews')}</h4>`
|
||||||
|
: html`<h4>${__('interaction.average_rating')}</h4>
|
||||||
|
<span data-tooltip="${result.averageRating}">${starRating(result.averageRating)}</span>
|
||||||
|
|
||||||
<div class="flex">
|
<div class="flex">
|
||||||
<div>
|
<div>
|
||||||
<h4>Top Reviews</h4>
|
<h4>Top Reviews</h4>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<a href="/book/${result.uri}" class="small button">
|
||||||
|
<span style="margin-right:8px;"><i class="icon-chat"></i></span>
|
||||||
|
<span>${result.numberOfReviews}</span>
|
||||||
|
<span>${__('search.see_interaction_details')}</span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
${(typeof result.reviews !== 'undefined' && Array.isArray(result.reviews) ? result.reviews : []).map(review => {
|
||||||
<a href="/book/${result.uri}" class="small button">
|
return reviewCard(searchController, review);
|
||||||
<span style="margin-right:8px;"><i class="icon-chat"></i></span>
|
})}`
|
||||||
<span>${result.numberOfReviews}</span>
|
}
|
||||||
<span>${__('search.see_interaction_details')}</span>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
${result.reviews.map(review => {
|
|
||||||
return reviewCard(searchController, review);
|
|
||||||
})}
|
|
||||||
</div>
|
</div>
|
||||||
<div class="sixth-700">
|
<div class="sixth-700">
|
||||||
<p>
|
<p>
|
||||||
<span data-tooltip=${__('interaction.add')}>
|
<button class="success" onclick=${() => searchController.showShelves()}>
|
||||||
<button class="success">
|
<i class="icon-plus"></i> <span>${__('interaction.add')}</span>
|
||||||
<i class="icon-plus"></i>
|
</button>
|
||||||
</button>
|
|
||||||
</span>
|
|
||||||
</p>
|
</p>
|
||||||
|
${!searchController.showShelves ? null : html`<ul>${searchController.shelves.map(shelf => {
|
||||||
|
return html`<li>
|
||||||
|
<button class="pseudo" onclick=${() => searchController.addToShelf({source: 'inventaire', uri: result.uri}, shelf.id)}>
|
||||||
|
${shelf.name}
|
||||||
|
</button>
|
||||||
|
</li>`;
|
||||||
|
})}</ul>`}
|
||||||
<p>
|
<p>
|
||||||
<a class="small button" href=${result.link} target="_blank">
|
<a class="small button" href=${result.link} target="_blank">
|
||||||
${__('search.see_book_details')}
|
${__('search.see_book_details')}
|
||||||
|
|
|
@ -42,4 +42,37 @@ export class ShelvesController extends ViewController {
|
||||||
this.state.loadedShelves[this.targetShelf] = shelf;
|
this.state.loadedShelves[this.targetShelf] = shelf;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async addItemToShelf (book, shelfId) {
|
||||||
|
let bookId;
|
||||||
|
if (typeof book.source !== 'undefined' && typeof book.uri !== 'undefined') {
|
||||||
|
const bookSearchResult = await fetch('/api/books/getId', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify(book),
|
||||||
|
}).then(response => response.json());
|
||||||
|
|
||||||
|
if (typeof bookSearchResult.error !== 'undefined') {
|
||||||
|
console.error(bookSearchResult);
|
||||||
|
return bookSearchResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
bookId = bookSearchResult;
|
||||||
|
} else {
|
||||||
|
bookId = book.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
return fetch('/api/shelf/addItem', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
shelfId,
|
||||||
|
bookId,
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -71,7 +71,7 @@ export const shelfView = (shelvesController, emit) => {
|
||||||
${__('shelves.owned_by')}
|
${__('shelves.owned_by')}
|
||||||
${shelf.user === null
|
${shelf.user === null
|
||||||
? __('shelves.you')
|
? __('shelves.you')
|
||||||
: `<a href="/profile?user=${shelf.user.handle}" title=${shelf.user.handle}>${shelf.user.name}</a>`}
|
: html`<a href="/profile?user=${shelf.user.handle}" title="${shelf.user.handle}">${shelf.user.name}</a>`}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="third sixth-700">
|
<div class="third sixth-700">
|
||||||
|
@ -87,9 +87,7 @@ export const shelfView = (shelvesController, emit) => {
|
||||||
return html`<article class="card">
|
return html`<article class="card">
|
||||||
<footer>
|
<footer>
|
||||||
<div class="flex one twelve-700">
|
<div class="flex one twelve-700">
|
||||||
<div class="full sixth-700">
|
<img class="full sixth-700" src=${shelfItem.coverURL} alt="cover ${shelfItem.coverEdition}" />
|
||||||
<img src=${shelfItem.coverURL} alt="cover ${shelfItem.coverEdition}" />
|
|
||||||
</div>
|
|
||||||
<div class="full half-700">
|
<div class="full half-700">
|
||||||
<h3>${shelfItem.title}</h3>
|
<h3>${shelfItem.title}</h3>
|
||||||
<span>${shelfItem.author}</span>
|
<span>${shelfItem.author}</span>
|
||||||
|
|
40
package.json
40
package.json
|
@ -7,9 +7,10 @@
|
||||||
"author": "Robbie Antenesse <dev@alamantus.com>",
|
"author": "Robbie Antenesse <dev@alamantus.com>",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "concurrently --kill-others \"npm run watch-js\" \"npm run serve\"",
|
"dev": "concurrently --kill-others \"npm run watch-app\" \"npm run watch-server\"",
|
||||||
"start": "npm run build && cross-env NODE_ENV=production npm run serve",
|
"start": "npm run build && cross-env NODE_ENV=production npm run serve",
|
||||||
"watch-js": "parcel watch app/index.html --out-dir public --no-hmr --no-cache",
|
"watch-app": "parcel watch app/index.html --out-dir public --no-hmr --no-cache",
|
||||||
|
"watch-server": "onchange -i -k \"server/**/*.js*\" -- npm run serve",
|
||||||
"serve": "node server/index.js",
|
"serve": "node server/index.js",
|
||||||
"build": "npm run process-images && npm run bundle",
|
"build": "npm run process-images && npm run bundle",
|
||||||
"bundle": "parcel build app/index.html --out-dir public --no-source-maps --no-cache",
|
"bundle": "parcel build app/index.html --out-dir public --no-source-maps --no-cache",
|
||||||
|
@ -21,35 +22,36 @@
|
||||||
"choo-devtools": "^3.0.3",
|
"choo-devtools": "^3.0.3",
|
||||||
"concurrently": "^5.1.0",
|
"concurrently": "^5.1.0",
|
||||||
"faker": "^4.1.0",
|
"faker": "^4.1.0",
|
||||||
|
"onchange": "^7.0.2",
|
||||||
"rimraf": "^3.0.1",
|
"rimraf": "^3.0.1",
|
||||||
"sequelize-erd": "https://github.com/Alamantus/sequelize-erd.git"
|
"sequelize-erd": "https://github.com/Alamantus/sequelize-erd.git"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"autoprefixer": "^9.7.4",
|
"autoprefixer": "^9.8.5",
|
||||||
"babel-polyfill": "^6.26.0",
|
"babel-polyfill": "^6.26.0",
|
||||||
"choo": "^7.1.0",
|
"choo": "^7.1.0",
|
||||||
"cross-env": "^7.0.0",
|
"cross-env": "^7.0.2",
|
||||||
"fastify": "^2.11.0",
|
"fastify": "^3.1.1",
|
||||||
"fastify-caching": "^5.0.0",
|
"fastify-caching": "^6.0.1",
|
||||||
"fastify-compress": "^2.0.0",
|
"fastify-compress": "^3.2.2",
|
||||||
"fastify-cookie": "^3.5.0",
|
"fastify-cookie": "^4.0.2",
|
||||||
"fastify-helmet": "^3.0.2",
|
"fastify-helmet": "^4.0.2",
|
||||||
"fastify-jwt": "^1.2.1",
|
"fastify-jwt": "^2.1.2",
|
||||||
"fastify-plugin": "^1.6.0",
|
"fastify-plugin": "^2.0.3",
|
||||||
"fastify-static": "^2.6.0",
|
"fastify-static": "^3.2.0",
|
||||||
"make-promises-safe": "^5.1.0",
|
"make-promises-safe": "^5.1.0",
|
||||||
"marked": "^0.8.0",
|
"marked": "^1.1.1",
|
||||||
"mysql2": "^2.1.0",
|
"mysql2": "^2.1.0",
|
||||||
"node-fetch": "^2.6.0",
|
"node-fetch": "^2.6.0",
|
||||||
"nodemailer": "^6.4.2",
|
"nodemailer": "^6.4.10",
|
||||||
"parcel-bundler": "^1.12.4",
|
"parcel-bundler": "^1.12.4",
|
||||||
"parcel-plugin-goodie-bag": "^2.0.0",
|
"parcel-plugin-goodie-bag": "^2.0.0",
|
||||||
"pg": "^7.18.1",
|
"pg": "^8.3.0",
|
||||||
"pg-hstore": "^2.3.3",
|
"pg-hstore": "^2.3.3",
|
||||||
"picnic": "^6.5.2",
|
"picnic": "^6.5.2",
|
||||||
"sass": "^1.25.0",
|
"sass": "^1.26.10",
|
||||||
"sequelize": "^5.21.3",
|
"sequelize": "^6.3.3",
|
||||||
"sharp": "^0.24.0",
|
"sharp": "^0.25.4",
|
||||||
"sqlite": "^3.0.3"
|
"sqlite3": "^5.0.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -93,6 +93,27 @@ class BooksController {
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async createBookReference (bookReferencesModel, source, uri) {
|
||||||
|
const inventaire = new Inventaire(this.language);
|
||||||
|
const bookData = await inventaire.getBookData(uri);
|
||||||
|
return await bookReferencesModel.create({
|
||||||
|
values: {
|
||||||
|
name: bookData.name,
|
||||||
|
description: bookData.description,
|
||||||
|
sources: {
|
||||||
|
[source]: uri,
|
||||||
|
},
|
||||||
|
covers: bookData.covers.map(cover => {
|
||||||
|
return {
|
||||||
|
sourceId: uri,
|
||||||
|
url: cover.url,
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
locale: this.language,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = BooksController;
|
module.exports = BooksController;
|
|
@ -76,6 +76,7 @@ class SearchController {
|
||||||
|
|
||||||
let extraReferences = [];
|
let extraReferences = [];
|
||||||
if (urisToCheck.length > 0) {
|
if (urisToCheck.length > 0) {
|
||||||
|
// Need to figure this out
|
||||||
extraReferences = await this.searchReferencesBySourceCodes(source, urisToCheck);
|
extraReferences = await this.searchReferencesBySourceCodes(source, urisToCheck);
|
||||||
}
|
}
|
||||||
return [
|
return [
|
||||||
|
@ -139,7 +140,7 @@ class SearchController {
|
||||||
const sourceJSONKey = `"${source}"`; // Enable searching withing JSON column.
|
const sourceJSONKey = `"${source}"`; // Enable searching withing JSON column.
|
||||||
return await this.models.BookReference.findOne({
|
return await this.models.BookReference.findOne({
|
||||||
where: {
|
where: {
|
||||||
source: {
|
sources: {
|
||||||
[sourceJSONKey]: { // Where the object key is the source
|
[sourceJSONKey]: { // Where the object key is the source
|
||||||
[Op.eq]: sourceId,
|
[Op.eq]: sourceId,
|
||||||
},
|
},
|
||||||
|
@ -156,7 +157,7 @@ class SearchController {
|
||||||
return await this.models.BookReference.findAll({
|
return await this.models.BookReference.findAll({
|
||||||
where: {
|
where: {
|
||||||
[Op.or]: sourceIds.map(sourceId => ({
|
[Op.or]: sourceIds.map(sourceId => ({
|
||||||
source: {
|
sources: {
|
||||||
[sourceJSONKey]: sourceId,
|
[sourceJSONKey]: sourceId,
|
||||||
},
|
},
|
||||||
})),
|
})),
|
||||||
|
|
|
@ -26,11 +26,13 @@ const fastifyNodemailer = (fastify, options, next) => {
|
||||||
try {
|
try {
|
||||||
transporter = createTransport(options);
|
transporter = createTransport(options);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
fastify.decorate('canEmail', false);
|
||||||
return next(err);
|
return next(err);
|
||||||
}
|
}
|
||||||
|
|
||||||
fastify
|
fastify
|
||||||
.decorate('nodemailer', transporter)
|
.decorate('nodemailer', transporter)
|
||||||
|
.decorate('canEmail', true)
|
||||||
.addHook('onClose', close);
|
.addHook('onClose', close);
|
||||||
|
|
||||||
next();
|
next();
|
||||||
|
|
|
@ -21,13 +21,14 @@ const fp = require('fastify-plugin');
|
||||||
const Sequelize = require('sequelize');
|
const Sequelize = require('sequelize');
|
||||||
|
|
||||||
function plugin (fastify, options) {
|
function plugin (fastify, options) {
|
||||||
const instance = options.instance || 'sequelize';
|
const { config, registerModels } = options;
|
||||||
const autoConnect = options.autoConnect || true;
|
const instance = config.instance || 'sequelize';
|
||||||
|
const autoConnect = config.autoConnect || true;
|
||||||
|
|
||||||
delete options.instance;
|
delete config.instance;
|
||||||
delete options.autoConnect;
|
delete config.autoConnect;
|
||||||
|
|
||||||
const sequelize = new Sequelize(options);
|
const sequelize = new Sequelize(config);
|
||||||
|
|
||||||
if (autoConnect) {
|
if (autoConnect) {
|
||||||
return sequelize.authenticate().then(decorate);
|
return sequelize.authenticate().then(decorate);
|
||||||
|
@ -39,6 +40,7 @@ function plugin (fastify, options) {
|
||||||
|
|
||||||
function decorate () {
|
function decorate () {
|
||||||
fastify.decorate(instance, sequelize);
|
fastify.decorate(instance, sequelize);
|
||||||
|
fastify.decorate('models', registerModels(sequelize));
|
||||||
fastify.addHook('onClose', (fastifyInstance, done) => {
|
fastify.addHook('onClose', (fastifyInstance, done) => {
|
||||||
sequelize.close()
|
sequelize.close()
|
||||||
.then(done)
|
.then(done)
|
||||||
|
|
|
@ -7,9 +7,11 @@
|
||||||
"menu_login": "Log In / Create Account",
|
"menu_login": "Log In / Create Account",
|
||||||
"menu_account": "My Profile",
|
"menu_account": "My Profile",
|
||||||
"menu_logout": "Log Out",
|
"menu_logout": "Log Out",
|
||||||
|
"menu_shelves": "My Shelves",
|
||||||
"footer_repo": "Repo",
|
"footer_repo": "Repo",
|
||||||
"footer_chat": "Chat",
|
"footer_chat": "Chat",
|
||||||
"change_language": "Language"
|
"change_language": "Language",
|
||||||
|
"error": "Oops, something went wrong!"
|
||||||
},
|
},
|
||||||
"home": {
|
"home": {
|
||||||
"logged_out": {
|
"logged_out": {
|
||||||
|
@ -72,11 +74,14 @@
|
||||||
"author": "Author"
|
"author": "Author"
|
||||||
},
|
},
|
||||||
"loading": "Loading...",
|
"loading": "Loading...",
|
||||||
|
"no_reviews": "No Reviews yet",
|
||||||
|
"click_for_details": "Show Book Details",
|
||||||
"no_results": "None Found",
|
"no_results": "None Found",
|
||||||
"no_results_suggestion": "If you're expecting book data, go and help fill out the Inventaire database!",
|
"no_results_suggestion": "If you're expecting book data, go and help fill out the Inventaire database!",
|
||||||
"people_header": "People",
|
"people_header": "People",
|
||||||
"series_header": "Series",
|
"series_header": "Series",
|
||||||
"books_header": "Books",
|
"books_header": "Books",
|
||||||
|
"covers": "Covers",
|
||||||
"see_interaction_details": "See All Interactions",
|
"see_interaction_details": "See All Interactions",
|
||||||
"see_book_details": "See Book Details"
|
"see_book_details": "See Book Details"
|
||||||
},
|
},
|
||||||
|
@ -88,6 +93,17 @@
|
||||||
"average_rating": "Average Rating",
|
"average_rating": "Average Rating",
|
||||||
"reviews_written": "Total Reviews Written"
|
"reviews_written": "Total Reviews Written"
|
||||||
},
|
},
|
||||||
|
"shelves": {
|
||||||
|
"title": "My Shelves",
|
||||||
|
"loading": "Loading Shelf...",
|
||||||
|
"you": "You",
|
||||||
|
"owned_by": "Owned By",
|
||||||
|
"no_shelf_selected": "No shelf selected.",
|
||||||
|
"not_logged_in": "You're not logged in."
|
||||||
|
},
|
||||||
|
"review": {
|
||||||
|
"review_of": "Review of"
|
||||||
|
},
|
||||||
"api": {
|
"api": {
|
||||||
"not_logged_in": "You are not logged in.",
|
"not_logged_in": "You are not logged in.",
|
||||||
"already_logged_in": "You are already logged in! You cannot create an account or log in again.",
|
"already_logged_in": "You are already logged in! You cannot create an account or log in again.",
|
||||||
|
@ -124,6 +140,11 @@
|
||||||
"invalid_token": "User not logged in: The stored token is not a valid token.",
|
"invalid_token": "User not logged in: The stored token is not a valid token.",
|
||||||
"renewed_token": "User logged in, and the token has been renewed."
|
"renewed_token": "User logged in, and the token has been renewed."
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"shelf": {
|
||||||
|
"get": {
|
||||||
|
"invalid_id": "Invalid ID specified"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -45,10 +45,14 @@ switch (fastify.siteConfig.db_engine) {
|
||||||
sequelizeConfig.password = fastify.siteConfig.db_password;
|
sequelizeConfig.password = fastify.siteConfig.db_password;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fastify.register(require('./fastify-plugins/fastify-sequelize'), sequelizeConfig);
|
fastify.register(require('./fastify-plugins/fastify-sequelize'), {
|
||||||
|
config: sequelizeConfig,
|
||||||
|
registerModels: require('./sequelize/models'),
|
||||||
|
});
|
||||||
|
|
||||||
if (!fastify.siteConfig.email_host || !fastify.siteConfig.email_username) {
|
if (!fastify.siteConfig.email_host || !fastify.siteConfig.email_username) {
|
||||||
console.warn('###\nNo email server set up. You will not be able to send emails without entering your email configuration.\n###');
|
console.warn('###\nNo email server set up. You will not be able to send emails without entering your email configuration.\n###');
|
||||||
|
fastify.decorate('canEmail', false);
|
||||||
} else {
|
} else {
|
||||||
fastify.register(require('./fastify-plugins/fastify-nodemailer'), {
|
fastify.register(require('./fastify-plugins/fastify-nodemailer'), {
|
||||||
pool: true,
|
pool: true,
|
||||||
|
@ -103,7 +107,4 @@ fastify.listen(fastify.siteConfig.port, function (err, address) {
|
||||||
fastify.log.error(err);
|
fastify.log.error(err);
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
fastify.decorate('canEmail', typeof fastify.nodemailer !== 'undefined');
|
|
||||||
fastify.decorate('models', require('./sequelize/models')(fastify.sequelize));
|
|
||||||
});
|
});
|
|
@ -1,4 +1,5 @@
|
||||||
const BooksController = require('../controllers/bookData');
|
const BooksController = require('../controllers/bookData');
|
||||||
|
const SearchController = require('../controllers/search');
|
||||||
|
|
||||||
async function routes(fastify, options) {
|
async function routes(fastify, options) {
|
||||||
fastify.get('/api/books', async (request, reply) => {
|
fastify.get('/api/books', async (request, reply) => {
|
||||||
|
@ -16,6 +17,33 @@ async function routes(fastify, options) {
|
||||||
|
|
||||||
return await books.getInventaireCovers();
|
return await books.getInventaireCovers();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
fastify.post('/api/books/getId', async (request, reply) => {
|
||||||
|
if (typeof request.body.source === 'undefined') {
|
||||||
|
return reply.code(400).send({
|
||||||
|
error: true,
|
||||||
|
message: 'api.shelf.getId.missing_source',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof request.body.uri === 'undefined') {
|
||||||
|
return reply.code(400).send({
|
||||||
|
error: true,
|
||||||
|
message: 'api.shelf.getId.missing_uri',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const search = new SearchController(fastify.models);
|
||||||
|
const existingBookReferences = await search.searchReferencesBySourceCodes(request.body.source, [request.body.uri]);
|
||||||
|
if (existingBookReferences.length > 0) {
|
||||||
|
return existingBookReferences[0].id;
|
||||||
|
}
|
||||||
|
|
||||||
|
const books = new BooksController(request.body.source, request.body.uri, request.language);
|
||||||
|
const newBookReference = await books.createBookReference(request.body.source, request.body.uri);
|
||||||
|
console.log('created new bookreference', newBookReference);
|
||||||
|
return newBookReference.id;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = routes
|
module.exports = routes
|
Loading…
Reference in New Issue