Merge pull request #51 from KevinMidboe/api_cache

Api cache
This commit is contained in:
2017-10-22 18:19:36 +02:00
committed by GitHub
18 changed files with 193 additions and 82 deletions

View File

@@ -10,6 +10,7 @@ import URI from 'urijs';
import InfiniteScroll from 'react-infinite-scroller';
import { fetchJSON } from './http.jsx';
import { getCookie } from './Cookie.jsx';
var MediaQuery = require('react-responsive');
@@ -105,11 +106,18 @@ class SearchRequest extends React.Component {
// Test this by calling missing endpoint or 404 query and see what code
// and filter the error message based on the code.
// Calls a uri and returns the response as json
callURI(uri) {
return fetch(uri)
callURI(uri, method, data={}) {
return fetch(uri, {
method: method,
headers: new Headers({
'Content-Type': 'application/json',
'authorization': getCookie('token'),
'loggedinuser': getCookie('loggedInUser'),
})
})
.then(response => { return response })
.catch(error => {
throw Error('Something went wrong while fetching URI.');
.catch((error) => {
throw Error(error);
});
}
@@ -128,7 +136,7 @@ class SearchRequest extends React.Component {
// this.writeLoading();
Promise.resolve()
.then(() => this.callURI(uri))
.then(() => this.callURI(uri, 'GET'))
.then(response => {
// If we get a error code for the request
if (!response.ok) {
@@ -174,8 +182,8 @@ class SearchRequest extends React.Component {
console.log('CallSearchFillMovieList: ', error)
})
})
.catch(() => {
throw Error('Something went wrong when fetching query.')
.catch((error) => {
console.log('Something went wrong when fetching query.', error)
})
}
@@ -184,7 +192,7 @@ class SearchRequest extends React.Component {
// this.writeLoading();
Promise.resolve()
.then(() => this.callURI(uri))
.then(() => this.callURI(uri, 'GET', undefined))
.then(response => {
// If we get a error code for the request
if (!response.ok) {
@@ -217,8 +225,8 @@ class SearchRequest extends React.Component {
}
})
})
.catch(() => {
throw Error('Something went wrong when fetching query.')
.catch((error) => {
console.log('Something went wrong when fetching query.', error)
})
}

View File

@@ -44,7 +44,7 @@ function setLoginError(loginError) {
}
function callLoginApi(email, password, callback) {
function callLoginApi(username, password, callback) {
Promise.resolve()
fetch('https://apollo.kevinmidboe.com/api/v1/user/login', {
@@ -53,7 +53,7 @@ function callLoginApi(email, password, callback) {
'Content-type': 'application/json'
},
body: JSON.stringify({
username: email,
username: username,
password: password,
})
})
@@ -66,6 +66,7 @@ function callLoginApi(email, password, callback) {
let token = data.token;
setCookie('token', token, 10);
setCookie('logged_in', true, 10);
setCookie('loggedInUser', username, 10);
window.location.reload();
}
@@ -77,7 +78,7 @@ function callLoginApi(email, password, callback) {
}
})
.catch(error => {
return callback(new Error('Invalid email and password'));
return callback(new Error('Invalid username and password'));
});
}

View File

@@ -31,7 +31,7 @@ class PlexRepository {
nowPlaying() {
var options = {
uri: 'http://10.0.0.41:32400/status/sessions',
uri: 'http://10.0.0.42:32400/status/sessions',
headers: {
'Accept': 'application/json'
},

View File

@@ -2,8 +2,10 @@ const assert = require('assert');
const PlexRepository = require('src/plex/plexRepository');
const plexRepository = new PlexRepository();
const configuration = require('src/config/configuration').getInstance();
const Cache = require('src/tmdb/cache');
const TMDB = require('src/tmdb/tmdb');
const tmdb = new TMDB(configuration.get('tmdb', 'apiKey'));
const cache = new Cache();
const tmdb = new TMDB(cache, configuration.get('tmdb', 'apiKey'));
var Promise = require('bluebird');
var rp = require('request-promise');
@@ -17,7 +19,7 @@ const nodemailer = require('nodemailer');
class RequestRepository {
constructor(database) {
constructor(cache, database) {
this.database = database || establishedDatabase;
this.queries = {
'insertRequest': "INSERT INTO requests VALUES (?, ?, ?, ?, ?, ?, CURRENT_DATE, 'requested', ?, ?)",
@@ -26,13 +28,12 @@ class RequestRepository {
}
}
searchRequest(query, page, type) {
// TODO get from cache
searchRequest(text, page, type) {
// STRIP METADATA THAT IS NOT ALLOWED
// Do a search in the tmdb api and return the results of the object
let getTmdbResults = function() {
return tmdb.search(query, page, type)
return tmdb.search(text, page, type)
.then((tmdbSearch) => {
return tmdbSearch.results;
})
@@ -52,35 +53,34 @@ class RequestRepository {
}
return Promise.resolve()
.then(() => plexRepository.searchMedia(query)
// Get the list of plexItems matching the query passed.
.then((plexItem) => {
let tmdbSearchResult = getTmdbResults();
.then(() => plexRepository.searchMedia(text))
// Get the list of plexItems matching the query passed.
.then((plexItem) => {
let tmdbSearchResult = getTmdbResults();
// When we get the result from tmdbSearchResult we pass it along and iterate over each
// element, and updates the matchedInPlex status of a item.
return tmdbSearchResult.then((tmdbResult) => {
for (var i = 0; i < tmdbResult.length; i++) {
let foundMatchInPlex = checkIfMatchesPlexObjects(tmdbResult[i].title, tmdbResult[i].year, plexItem);
tmdbResult[i].matchedInPlex = foundMatchInPlex;
}
return { 'results': tmdbResult, 'page': 1 };
})
// TODO log error
.catch((error) => {
console.log(error);
throw new Error('Search query did not give any results.');
})
// When we get the result from tmdbSearchResult we pass it along and iterate over each
// element, and updates the matchedInPlex status of a item.
return tmdbSearchResult.then((tmdbResult) => {
for (var i = 0; i < tmdbResult.length; i++) {
let foundMatchInPlex = checkIfMatchesPlexObjects(tmdbResult[i].title, tmdbResult[i].year, plexItem);
tmdbResult[i].matchedInPlex = foundMatchInPlex;
}
return { 'results': tmdbResult, 'page': 1 };
})
.catch(() => {
let tmdbSearchResult = getTmdbResults();
// TODO log error
.catch((error) => {
console.log(error);
throw new Error('Search query did not give any results.');
})
})
.catch(() => {
let tmdbSearchResult = getTmdbResults();
// Catch if empty, then 404
return tmdbSearchResult.then((tmdbResult) => {
return {'results': tmdbResult, 'page': 1 };
})
// Catch if empty, then 404
return tmdbSearchResult.then((tmdbResult) => {
return {'results': tmdbResult, 'page': 1 };
})
)
})
}
lookup(identifier, type = 'movie') {

View File

@@ -16,7 +16,7 @@ class SearchHistory {
* @returns {Promise}
*/
read(user) {
return this.database.all(this.queries.read, user.username)
return this.database.all(this.queries.read, user)
.then(rows => rows.map(row => row.search_query));
}
@@ -27,7 +27,7 @@ class SearchHistory {
* @returns {Promise}
*/
create(user, searchQuery) {
return this.database.run(this.queries.create, [searchQuery, user.username]).catch((error) => {
return this.database.run(this.queries.create, [searchQuery, user]).catch((error) => {
if (error.message.includes('FOREIGN')) {
throw new Error('Could not create search history.');
}

View File

@@ -0,0 +1,44 @@
const assert = require('assert');
const establishedDatabase = require('src/database/database');
class Cache {
constructor(database) {
this.database = database || establishedDatabase
this.queries = {
'read': 'SELECT value, time_to_live, created_at, DATETIME("now", "localtime") as now, ' +
'DATETIME(created_at, "+" || time_to_live || " seconds") as expires ' +
'FROM cache WHERE key = ? AND now < expires',
'create': 'INSERT OR REPLACE INTO cache (key, value, time_to_live) VALUES (?, ?, ?)',
};
}
/**
* Retrieve an unexpired cache entry by key.
* @param {String} key of the cache entry
* @returns {Object}
*/
get(key) {
return Promise.resolve()
.then(() => this.database.get(this.queries.read, [key]))
.then((row) => {
assert(row, 'Could not find cache enrty with that key.');
return JSON.parse(row.value);
})
}
/**
* Insert cache entry with key and value.
* @param {String} key of the cache entry
* @param {String} value of the cache entry
* @param {Number} timeToLive the number of seconds before entry expires
* @returns {Object}
*/
set(key, value, timeToLive = 86400) {
const json = JSON.stringify(value);
return Promise.resolve()
.then(() => this.database.run(this.queries.create, [key, json, timeToLive]))
.then(() => value);
}
}
module.exports = Cache;

View File

@@ -7,8 +7,18 @@ var methodTypes = { 'movie': 'searchMovie', 'show': 'searchTv', 'multi': 'search
'showSimilar': 'tvSimilar' };
class TMDB {
constructor(apiKey, tmdbLibrary) {
constructor(cache, apiKey, tmdbLibrary) {
this.cache = cache
this.tmdbLibrary = tmdbLibrary || moviedb(apiKey);
this.cacheTags = {
'search': 'se',
'discover': 'd',
'popular': 'p',
'nowplaying': 'n',
'upcoming': 'u',
'similar': 'si',
'lookup': 'l'
}
}
/**
@@ -16,22 +26,21 @@ class TMDB {
* @param {queryText, page, type} the page number to specify in the request for discover,
* @returns {Promise} dict with query results, current page and total_pages
*/
search(queryText, page = 1, type = 'multi') {
// Setup query object for tmdb api search
const query = { 'query': queryText, 'page': page };
search(text, page = 1, type = 'multi') {
const query = { 'query': text, 'page': page };
const cacheKey = `${this.cacheTags.search}:${page}:${type}:${text}`;
return Promise.resolve()
.then(() => this.tmdb(type, query)) // Search the tmdb api
.catch(() => { throw new Error('Could not search for movies.'); }) // If any error at all when fetching
.then(() => this.cache.get(cacheKey))
.catch(() => this.tmdb(type, query))
.catch(() => { throw new Error('Could not search for movies/shows at tmdb.'); })
.then((response) => this.cache.set(cacheKey, response))
.then((response) => {
try {
// We want to filter because there are movies really low rated that are not interesting to us.
let filteredTmdbItems = response.results.filter(function(tmdbResultItem) {
return ((tmdbResultItem.vote_count >= 40 || tmdbResultItem.popularity > 8) && (tmdbResultItem.release_date !== undefined || tmdbResultItem.first_air_date !== undefined))
})
// Here we convert the filtered result from the tmdb api to seaonsed objects
let seasonedItems = filteredTmdbItems.map((tmdbItem) => {
if (type === 'movie')
return convertTmdbToSeasoned(tmdbItem, 'movie');
else if (type === 'show')
@@ -45,7 +54,6 @@ class TMDB {
'page': 1, 'total_pages': 1 };
} catch (parseError) {
console.log(parseError)
throw new Error('Could not parse result.');
}
});
@@ -75,9 +83,12 @@ class TMDB {
// Build a query for tmdb with pagenumber
const query = { 'page': page }
const cacheKey = `${this.cacheTags.discover}:${page}:${type}`;
return Promise.resolve()
.then(() => this.tmdb(tmdbType, query))
.then(() => this.cache.get(cacheKey))
.catch(() => this.tmdb(tmdbType, query))
.catch(() => { throw new Error('Could not fetch discover.'); })
.then((response) => this.cache.set(cacheKey, response))
.then((response) => {
try {
// Return a object that has the results and a variable for page, total_pages
@@ -119,9 +130,12 @@ class TMDB {
// Build a query for tmdb with pagenumber
const query = { 'page': page }
const cacheKey = `${this.cacheTags.popular}:${page}:${type}`;
return Promise.resolve()
.then(() => this.tmdb(tmdbType, query))
.then(() => this.cache.get(cacheKey))
.catch(() => this.tmdb(tmdbType, query))
.catch(() => { throw new Error('Could not fetch popular.'); })
.then((response) => this.cache.set(cacheKey, response))
.then((response) => {
try {
var seasonedResponse = response.results.map((result) => {
@@ -164,9 +178,12 @@ class TMDB {
// Build a query for tmdb with pagenumber
const query = { 'page': page }
const cacheKey = `${this.cacheTags.nowplaying}:${page}:${type}`;
return Promise.resolve()
.then(() => this.tmdb(tmdbType, query))
.then(() => this.cache.get(cacheKey))
.catch(() => this.tmdb(tmdbType, query))
.catch(() => { throw new Error('Could not fetch popular.'); })
.then((response) => this.cache.set(cacheKey, response))
.then((response) => {
try {
var seasonedResponse = response.results.map((result) => {
@@ -191,9 +208,12 @@ class TMDB {
// TODO add filter for language
upcoming(page) {
const query = { 'page': page }
const cacheKey = `${this.cacheTags.upcoming}:${page}`;
return Promise.resolve()
.then(() => this.tmdb('upcomingMovies', query))
.then(() => this.cache.get(cacheKey))
.catch(() => this.tmdb('upcomingMovies', query))
.catch(() => { throw new Error('Could not fetch upcoming movies.'); })
.then((response) => this.cache.set(cacheKey, response))
.then((response) => {
try {
var seasonedResponse = response.results.map((result) => {
@@ -231,9 +251,12 @@ class TMDB {
}
const query = { id: identifier }
const cacheKey = `${this.cacheTags.similar}:${type}:${identifier}`;
return Promise.resolve()
.then(() => this.tmdb(tmdbType, query))
.then(() => this.cache.get(cacheKey))
.catch(() => this.tmdb(tmdbType, query))
.catch(() => { throw new Error('Could not fetch upcoming movies.'); })
.then((response) => this.cache.set(cacheKey, response))
.then((response) => {
try {
var seasonedResponse = response.results.map((result) => {
@@ -266,9 +289,12 @@ class TMDB {
})
}
const query = { id: identifier };
const cacheKey = `${this.cacheTags.lookup}:${type}:${identifier}`;
return Promise.resolve()
.then(() => this.tmdb(type, query))
.then(() => this.cache.get(cacheKey))
.catch(() => this.tmdb(type, query))
.catch(() => { throw new Error('Could not find a movie with that id.'); })
.then((response) => this.cache.set(cacheKey, response))
.then((response) => {
try {
var car = convertTmdbToSeasoned(response, queryType);

View File

@@ -33,7 +33,7 @@ router.use(function(req, res, next) {
console.log('allowed');
res.setHeader('Access-Control-Allow-Origin', origin);
}
res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization');
res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization, loggedinuser');
res.header('Access-Control-Allow-Methods', 'POST, GET, PUT');
next();

View File

@@ -1,13 +1,23 @@
const PlexRepository = require('src/plex/plexRepository');
const plexRepository = new PlexRepository();
/**
* Controller: Search for media and check existence
* in plex by query and page
* @param {Request} req http request variable
* @param {Response} res
* @returns {Callback}
*/
function searchMediaController(req, res) {
const { query, page } = req.query;
console.log(query);
plexRepository.searchMedia(query)
.then((movies) => {
res.send(movies);
.then((media) => {
if (media !== undefined || media.length > 0) {
res.send(media);
} else {
res.status(404).send({ success: false, error: 'Search query did not return any results.'})
}
})
.catch((error) => {
res.status(500).send({success: false, error: error.message });

View File

@@ -1,17 +1,27 @@
const SearchHistory = require('src/searchHistory/searchHistory');
const Cache = require('src/tmdb/cache');
const RequestRepository = require('src/plex/requestRepository.js');
const requestRepository = new RequestRepository();
const cache = new Cache();
const requestRepository = new RequestRepository(cache);
const searchHistory = new SearchHistory();
function searchRequestController(req, res) {
const user = req.headers.loggedinuser;
const { query, page, type } = req.query;
console.log('searchReq: ' + query, page, type);
requestRepository.searchRequest(query, page, type)
Promise.resolve()
.then(() => {
if (user !== 'false') {
searchHistory.create(user, query);
}
})
.then(() => requestRepository.searchRequest(query, page, type))
.then((searchResult) => {
// Verify that respond has content, if so send the content back
if (searchResult.results.length > 0) {
res.send(searchResult);
}
// If no content was found, send 404 status and error message
else {
res.status(404).send({success: false, error: 'Search query did not return any results.'})
}

View File

@@ -1,6 +1,8 @@
const configuration = require('src/config/configuration').getInstance();
const Cache = require('src/tmdb/cache');
const TMDB = require('src/tmdb/tmdb');
const tmdb = new TMDB(configuration.get('tmdb', 'apiKey'));
const cache = new Cache();
const tmdb = new TMDB(cache, configuration.get('tmdb', 'apiKey'));
/**
* Controller: Retrieve a list of movies or shows in discover section in TMDB

View File

@@ -1,6 +1,8 @@
const configuration = require('src/config/configuration').getInstance();
const Cache = require('src/tmdb/cache');
const TMDB = require('src/tmdb/tmdb');
const tmdb = new TMDB(configuration.get('tmdb', 'apiKey'));
const cache = new Cache();
const tmdb = new TMDB(cache, configuration.get('tmdb', 'apiKey'));
/**
* Controller: Retrieve upcoming movies

View File

@@ -1,6 +1,8 @@
const configuration = require('src/config/configuration').getInstance();
const Cache = require('src/tmdb/cache');
const TMDB = require('src/tmdb/tmdb');
const tmdb = new TMDB(configuration.get('tmdb', 'apiKey'));
const cache = new Cache();
const tmdb = new TMDB(cache, configuration.get('tmdb', 'apiKey'));
/**
* Controller: Retrieve nowplaying movies / now airing shows

View File

@@ -1,6 +1,8 @@
const configuration = require('src/config/configuration').getInstance();
const Cache = require('src/tmdb/cache');
const TMDB = require('src/tmdb/tmdb');
const tmdb = new TMDB(configuration.get('tmdb', 'apiKey'));
const cache = new Cache();
const tmdb = new TMDB(cache, configuration.get('tmdb', 'apiKey'));
/**
* Controller: Retrieve information for a movie

View File

@@ -1,6 +1,8 @@
const configuration = require('src/config/configuration').getInstance();
const Cache = require('src/tmdb/cache');
const TMDB = require('src/tmdb/tmdb');
const tmdb = new TMDB(configuration.get('tmdb', 'apiKey'));
const cache = new Cache();
const tmdb = new TMDB(cache, configuration.get('tmdb', 'apiKey'));
/**
* Controller: Retrieve information for a movie

View File

@@ -1,14 +1,16 @@
const configuration = require('src/config/configuration').getInstance();
const Cache = require('src/tmdb/cache');
const TMDB = require('src/tmdb/tmdb');
const tmdb = new TMDB(configuration.get('tmdb', 'apiKey'));
const cache = new Cache();
const tmdb = new TMDB(cache, configuration.get('tmdb', 'apiKey'));
/**
* Controller: Search for movies by query, page and optionally adult
* Controller: Search for movies by query, page and optional type
* @param {Request} req http request variable
* @param {Response} res
* @returns {Callback}
*/
function searchMoviesController(req, res) {
function searchMediaController(req, res) {
const { query, page, type } = req.query;
Promise.resolve()
@@ -19,11 +21,10 @@ function searchMoviesController(req, res) {
} else {
res.status(404).send({ success: false, error: 'Search query did not return any results.'})
}
res.send(movies);
})
.catch((error) => {
res.status(500).send({ success: false, error: error.message });
});
}
module.exports = searchMoviesController;
module.exports = searchMediaController;

View File

@@ -1,6 +1,8 @@
const configuration = require('src/config/configuration').getInstance();
const Cache = require('src/tmdb/cache');
const TMDB = require('src/tmdb/tmdb');
const tmdb = new TMDB(configuration.get('tmdb', 'apiKey'));
const cache = new Cache();
const tmdb = new TMDB(cache, configuration.get('tmdb', 'apiKey'));
/**
* Controller: Retrieve similar movies or shows

View File

@@ -14,7 +14,6 @@ const userSecurity = new UserSecurity();
function loginController(req, res) {
const user = new User(req.body.username);
const password = req.body.password;
// console.log('login: ', req.body)
userSecurity.login(user, password)
.then(() => {