Compare commits
16 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 451b67630a | |||
| 096bbdf085 | |||
| e914e4ab45 | |||
| c1461e1f41 | |||
| 91bf2c1e2a | |||
| da3df383ed | |||
| 9816b978d3 | |||
| 8e22b0f6ea | |||
| 18359f442c | |||
| 42b8b5ea0e | |||
| 996295b1fe | |||
| 1b08c8d3d1 | |||
| 9e145f7068 | |||
| 7051edb212 | |||
| 0581813ee3 | |||
| edf1de223e |
2
.gitmodules
vendored
2
.gitmodules
vendored
@@ -1,3 +1,3 @@
|
|||||||
[submodule "torrent_search"]
|
[submodule "torrent_search"]
|
||||||
path = torrent_search
|
path = torrent_search
|
||||||
url = git@github.com:KevinMidboe/torrent_search.git
|
url = https://github.com/KevinMidboe/torrent_search.git
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
language: node_js
|
language: node_js
|
||||||
node_js: '8.7.0'
|
node_js: '8.7.0'
|
||||||
git:
|
git:
|
||||||
submodules: false
|
submodules: true
|
||||||
script:
|
script:
|
||||||
yarn test
|
yarn test
|
||||||
before_install:
|
before_install:
|
||||||
|
|||||||
@@ -3,7 +3,8 @@
|
|||||||
[](https://snyk.io/test/github/KevinMidboe/seasonedShows?targetFile=seasoned_api/package.json)
|
[](https://snyk.io/test/github/KevinMidboe/seasonedShows?targetFile=seasoned_api/package.json)
|
||||||
[]()
|
[]()
|
||||||
|
|
||||||
Your customly *seasoned* movie and show requester, downloader and organizer. Demo page can be viewed [here](https://kevinmidboe.com/request)
|
Your customly *seasoned* movie and show requester, downloader and organizer.
|
||||||
|
📺 [Demo](https://kevinmidboe.com/request)
|
||||||
|
|
||||||
## About
|
## About
|
||||||
The goal of this project is to create a full custom stack that can to everything surround downloading, organizing and notifiyng of new media. From the top down we have a website using [tmdb](https://www.themoviedb.com) api to search for from over 350k movies and 70k tv shows. Using [hjone72](https://github.com/hjone72/PlexAuth) great PHP reverse proxy we can have a secure way of allowing users to login with their plex credentials which limits request capabilites to only users that are authenticated to use your plex library.
|
The goal of this project is to create a full custom stack that can to everything surround downloading, organizing and notifiyng of new media. From the top down we have a website using [tmdb](https://www.themoviedb.com) api to search for from over 350k movies and 70k tv shows. Using [hjone72](https://github.com/hjone72/PlexAuth) great PHP reverse proxy we can have a secure way of allowing users to login with their plex credentials which limits request capabilites to only users that are authenticated to use your plex library.
|
||||||
|
|||||||
17
seasoned_api/conf/test.json
Normal file
17
seasoned_api/conf/test.json
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
"database": {
|
||||||
|
"host": ":memory:"
|
||||||
|
},
|
||||||
|
"webserver": {
|
||||||
|
"port": 31400
|
||||||
|
},
|
||||||
|
"tmdb": {
|
||||||
|
"apiKey": "bogus-api-key"
|
||||||
|
},
|
||||||
|
"raven": {
|
||||||
|
"DSN": ""
|
||||||
|
},
|
||||||
|
"authentication": {
|
||||||
|
"secret": "secret"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,16 +3,19 @@
|
|||||||
"main": "webserver/server.js",
|
"main": "webserver/server.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "cross-env SEASONED_CONFIG=conf/development.json NODE_PATH=. node src/webserver/server.js",
|
"start": "cross-env SEASONED_CONFIG=conf/development.json NODE_PATH=. node src/webserver/server.js",
|
||||||
"test": "cross-env SEASONED_CONFIG=conf/development.json TESTING=true NODE_PATH=. mocha --recursive test/system",
|
"test": "cross-env SEASONED_CONFIG=conf/test.json NODE_PATH=. mocha --recursive test",
|
||||||
"coverage": "cross-env SEASONED_CONFIG=conf/test.json NODE_PATH=. istanbul cover -x script/autogenerate-documentation.js --include-all-sources --dir test/.coverage node_modules/mocha/bin/_mocha --recursive test/**/* -- --report lcovonly && cat test/.coverage/lcov.info | coveralls && rm -rf test/.coverage",
|
"coverage": "cross-env SEASONED_CONFIG=conf/test.json NODE_PATH=. istanbul cover -x script/autogenerate-documentation.js --include-all-sources --dir test/.coverage node_modules/mocha/bin/_mocha --recursive test/**/* -- --report lcovonly && cat test/.coverage/lcov.info | coveralls && rm -rf test/.coverage",
|
||||||
"lint": "./node_modules/.bin/eslint src/"
|
"lint": "./node_modules/.bin/eslint src/"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"bcrypt-nodejs": "^0.0.3",
|
"bcrypt-nodejs": "^0.0.3",
|
||||||
|
"blanket": "^1.2.3",
|
||||||
"body-parser": "~1.0.1",
|
"body-parser": "~1.0.1",
|
||||||
|
"codecov": "^3.0.0",
|
||||||
"cross-env": "^3.1.3",
|
"cross-env": "^3.1.3",
|
||||||
"express": "~4.11.0",
|
"express": "~4.11.0",
|
||||||
"jsonwebtoken": "^8.0.1",
|
"jsonwebtoken": "^8.0.1",
|
||||||
|
"mocha-lcov-reporter": "^1.3.0",
|
||||||
"mongoose": "^3.6.13",
|
"mongoose": "^3.6.13",
|
||||||
"moviedb": "^0.2.10",
|
"moviedb": "^0.2.10",
|
||||||
"node-cache": "^4.1.1",
|
"node-cache": "^4.1.1",
|
||||||
@@ -28,7 +31,7 @@
|
|||||||
"eslint-config-airbnb-base": "^12.1.0",
|
"eslint-config-airbnb-base": "^12.1.0",
|
||||||
"eslint-plugin-import": "^2.8.0",
|
"eslint-plugin-import": "^2.8.0",
|
||||||
"istanbul": "^0.4.5",
|
"istanbul": "^0.4.5",
|
||||||
"mocha": "^3.1.0",
|
"mocha": "^5.0.4",
|
||||||
"supertest": "^2.0.1",
|
"supertest": "^2.0.1",
|
||||||
"supertest-as-promised": "^4.0.1"
|
"supertest-as-promised": "^4.0.1"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ class Config {
|
|||||||
const field = new Field(this.fields[section][option]);
|
const field = new Field(this.fields[section][option]);
|
||||||
|
|
||||||
if (field.value === '') {
|
if (field.value === '') {
|
||||||
const envField = process.env[[section.toUpperCase(), option.toUpperCase()].join('_')];
|
const envField = process.env[['SEASONED', section.toUpperCase(), option.toUpperCase()].join('_')];
|
||||||
if (envField !== undefined && envField.length !== 0) { return envField; }
|
if (envField !== undefined && envField.length !== 0) { return envField; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
const configuration = require('src/config/configuration').getInstance();
|
const configuration = require('src/config/configuration').getInstance();
|
||||||
const SqliteDatabase = require('src/database/sqliteDatabase');
|
const SqliteDatabase = require('src/database/sqliteDatabase');
|
||||||
|
|
||||||
const host = process.env.TESTING ? ':memory:' : configuration.get('database', 'host');
|
const database = new SqliteDatabase(configuration.get('database', 'host'));
|
||||||
const database = new SqliteDatabase(host);
|
|
||||||
/**
|
/**
|
||||||
* This module establishes a connection to the database
|
* This module establishes a connection to the database
|
||||||
* specified in the confgiuration file. It tries to setup
|
* specified in the confgiuration file. It tries to setup
|
||||||
|
|||||||
@@ -8,21 +8,17 @@ const plexRepository = new PlexRepository();
|
|||||||
const cache = new Cache();
|
const cache = new Cache();
|
||||||
const tmdb = new TMDB(cache, configuration.get('tmdb', 'apiKey'));
|
const tmdb = new TMDB(cache, configuration.get('tmdb', 'apiKey'));
|
||||||
|
|
||||||
const MailTemplate = require('src/plex/mailTemplate');
|
|
||||||
const nodemailer = require('nodemailer');
|
|
||||||
|
|
||||||
|
|
||||||
class RequestRepository {
|
class RequestRepository {
|
||||||
constructor(cache, database) {
|
constructor(database) {
|
||||||
this.database = database || establishedDatabase;
|
this.database = database || establishedDatabase;
|
||||||
this.queries = {
|
this.queries = {
|
||||||
insertRequest: `INSERT INTO requests(id,title,year,poster_path,background_path,requested_by,ip,user_agent,type)
|
insertRequest: `INSERT INTO requests(id,title,year,poster_path,background_path,requested_by,ip,user_agent,type)
|
||||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
||||||
fetchRequestedItems: 'SELECT * FROM requests ORDER BY date DESC',
|
fetchRequestedItems: 'SELECT * FROM requests ORDER BY date DESC LIMIT 25 OFFSET ?*25-25',
|
||||||
fetchRequestedItemsByStatus: 'SELECT * FROM requests WHERE status IS ? AND type LIKE ?',
|
fetchRequestedItemsByStatus: 'SELECT * FROM requests WHERE status IS ? AND type LIKE ? ORDER BY date DESC LIMIT 25 OFFSET ?*25-25',
|
||||||
updateRequestedById: 'UPDATE requests SET status = ? WHERE id is ? AND type is ?',
|
updateRequestedById: 'UPDATE requests SET status = ? WHERE id is ? AND type is ?',
|
||||||
checkIfIdRequested: 'SELECT * FROM requests WHERE id IS ? AND type IS ?',
|
checkIfIdRequested: 'SELECT * FROM requests WHERE id IS ? AND type IS ?',
|
||||||
userRequests: 'SELECT * FROM requests WHERE requested_by IS ?'
|
userRequests: 'SELECT * FROM requests WHERE requested_by IS ?',
|
||||||
};
|
};
|
||||||
this.cacheTags = {
|
this.cacheTags = {
|
||||||
search: 'se',
|
search: 'se',
|
||||||
@@ -51,10 +47,7 @@ class RequestRepository {
|
|||||||
.then(() => this.database.get(this.queries.checkIfIdRequested, [tmdbMovie.id, tmdbMovie.type]))
|
.then(() => this.database.get(this.queries.checkIfIdRequested, [tmdbMovie.id, tmdbMovie.type]))
|
||||||
.then((result, error) => {
|
.then((result, error) => {
|
||||||
if (error) { throw new Error(error); }
|
if (error) { throw new Error(error); }
|
||||||
let already_requested = false;
|
tmdbMovie.requested = result ? true : false;
|
||||||
if (result) { already_requested = true; }
|
|
||||||
|
|
||||||
tmdbMovie.requested = already_requested;
|
|
||||||
return tmdbMovie;
|
return tmdbMovie;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -68,19 +61,19 @@ class RequestRepository {
|
|||||||
return Promise.resolve()
|
return Promise.resolve()
|
||||||
.then(() => tmdb.lookup(identifier, type))
|
.then(() => tmdb.lookup(identifier, type))
|
||||||
.then((movie) => {
|
.then((movie) => {
|
||||||
const username = user == undefined ? undefined : user.username;
|
const username = user === undefined ? undefined : user.username;
|
||||||
// Add request to database
|
// Add request to database
|
||||||
return this.database.run(this.queries.insertRequest, [movie.id, movie.title, movie.year, movie.poster_path, movie.background_path, username, ip, user_agent, movie.type]);
|
return this.database.run(this.queries.insertRequest, [movie.id, movie.title, movie.year, movie.poster_path, movie.background_path, username, ip, user_agent, movie.type]);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fetchRequested(status, type = '%') {
|
fetchRequested(status, page = '1', type = '%') {
|
||||||
return Promise.resolve()
|
return Promise.resolve()
|
||||||
.then(() => {
|
.then(() => {
|
||||||
if (status === 'requested' || status === 'downloading' || status === 'downloaded')
|
if (status === 'requested' || status === 'downloading' || status === 'downloaded')
|
||||||
return this.database.all(this.queries.fetchRequestedItemsByStatus, [status, type]);
|
return this.database.all(this.queries.fetchRequestedItemsByStatus, [status, type, page]);
|
||||||
else
|
else
|
||||||
return this.database.all(this.queries.fetchRequestedItems);
|
return this.database.all(this.queries.fetchRequestedItems, page);
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -88,10 +81,12 @@ class RequestRepository {
|
|||||||
return Promise.resolve()
|
return Promise.resolve()
|
||||||
.then(() => this.database.all(this.queries.userRequests, user.username))
|
.then(() => this.database.all(this.queries.userRequests, user.username))
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
if (String(error).includes('no such column')) { throw new Error('Username not found'); }
|
if (String(error).includes('no such column')) {
|
||||||
else { throw new Error('Unable to fetch your requests')}
|
throw new Error('Username not found');
|
||||||
|
}
|
||||||
|
throw new Error('Unable to fetch your requests');
|
||||||
})
|
})
|
||||||
.then((result) => { return result })
|
.then((result) => { return result; });
|
||||||
}
|
}
|
||||||
|
|
||||||
updateRequestedById(id, type, status) {
|
updateRequestedById(id, type, status) {
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ class TMDB {
|
|||||||
/**
|
/**
|
||||||
* Retrieve a specific movie by id from TMDB.
|
* Retrieve a specific movie by id from TMDB.
|
||||||
* @param {Number} identifier of the movie you want to retrieve
|
* @param {Number} identifier of the movie you want to retrieve
|
||||||
|
* @param {String} type filter results by type (default movie).
|
||||||
* @returns {Promise} succeeds if movie was found
|
* @returns {Promise} succeeds if movie was found
|
||||||
*/
|
*/
|
||||||
lookup(identifier, type = 'movie') {
|
lookup(identifier, type = 'movie') {
|
||||||
@@ -36,7 +37,7 @@ class TMDB {
|
|||||||
const cacheKey = `${this.cacheTags.info}:${type}:${identifier}`;
|
const cacheKey = `${this.cacheTags.info}:${type}:${identifier}`;
|
||||||
return Promise.resolve()
|
return Promise.resolve()
|
||||||
.then(() => this.cache.get(cacheKey))
|
.then(() => this.cache.get(cacheKey))
|
||||||
.catch(() => this.tmdb(this.tmdbMethod('info', type), query))
|
.catch(() => this.tmdb(TMDB_METHODS['info'][type], query))
|
||||||
.catch(() => { throw new Error('Could not find a movie with that id.'); })
|
.catch(() => { throw new Error('Could not find a movie with that id.'); })
|
||||||
.then(response => this.cache.set(cacheKey, response))
|
.then(response => this.cache.set(cacheKey, response))
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
@@ -50,70 +51,66 @@ class TMDB {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrive list of of items from TMDB matching the query and/or type given.
|
* Retrive search results from TMDB.
|
||||||
* @param {queryText, page, type} the page number to specify in the request for discover,
|
* @param {String} text query you want to search for
|
||||||
|
* @param {Number} page representing pagination of results
|
||||||
|
* @param {String} type filter results by type (default multi)
|
||||||
* @returns {Promise} dict with query results, current page and total_pages
|
* @returns {Promise} dict with query results, current page and total_pages
|
||||||
*/
|
*/
|
||||||
search(text, page = 1, type = 'multi') {
|
search(text, page = 1, type = 'multi') {
|
||||||
const query = { query: text, page };
|
const query = { query: text, page: page };
|
||||||
const cacheKey = `${this.cacheTags.search}:${page}:${type}:${text}`;
|
const cacheKey = `${this.cacheTags.search}:${page}:${type}:${text}`;
|
||||||
return Promise.resolve()
|
return Promise.resolve()
|
||||||
.then(() => this.cache.get(cacheKey))
|
.then(() => this.cache.get(cacheKey))
|
||||||
.catch(() => this.tmdb(this.tmdbMethod('search', type), query))
|
.catch(() => this.tmdb(TMDB_METHODS['search'][type], query))
|
||||||
.catch(() => { throw new Error('Could not search for movies/shows at tmdb.'); })
|
.catch(() => { throw new Error('Could not search for movies/shows at tmdb.'); })
|
||||||
.then(response => this.cache.set(cacheKey, response))
|
.then(response => this.cache.set(cacheKey, response))
|
||||||
.then(response => this.mapResults(response))
|
.then(response => this.mapResults(response))
|
||||||
.catch((error) => { throw new Error(error); })
|
|
||||||
.then(([mappedResults, pagenumber, totalpages, total_results]) => ({
|
|
||||||
results: mappedResults, page: pagenumber, total_results, total_pages: totalpages,
|
|
||||||
}));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetches a given list from tmdb.
|
* Fetches a given list from tmdb.
|
||||||
* @param {listName} List we want to fetch.
|
* @param {String} listName Name of list
|
||||||
* @param {type} The to specify in the request for discover (default 'movie').
|
* @param {String} type filter results by type (default movie)
|
||||||
* @param {id} When finding similar a id can be added to query
|
* @param {Number} page representing pagination of results
|
||||||
* @param {page} Page number we want to fetch.
|
|
||||||
* @returns {Promise} dict with query results, current page and total_pages
|
* @returns {Promise} dict with query results, current page and total_pages
|
||||||
*/
|
*/
|
||||||
listSearch(listName, type = 'movie', id, page = '1') {
|
listSearch(listName, type = 'movie', page = '1') {
|
||||||
const params = { id, page };
|
const query = { page: page }
|
||||||
const cacheKey = `${this.cacheTags[listName]}:${type}:${id}:${page}`;
|
console.log(query)
|
||||||
|
const cacheKey = `${this.cacheTags[listName]}:${type}:${page}`;
|
||||||
return Promise.resolve()
|
return Promise.resolve()
|
||||||
.then(() => this.cache.get(cacheKey))
|
.then(() => this.cache.get(cacheKey))
|
||||||
.catch(() => this.tmdb(this.tmdbMethod(listName, type), params))
|
.catch(() => this.tmdb(TMDB_METHODS[listName][type], query))
|
||||||
|
.catch(() => { throw new Error('Error fetching list from tmdb.')})
|
||||||
.then(response => this.cache.set(cacheKey, response))
|
.then(response => this.cache.set(cacheKey, response))
|
||||||
.then(response => this.mapResults(response, type))
|
.then(response => this.mapResults(response, type))
|
||||||
.catch((error) => { throw new Error(error); })
|
|
||||||
.then(([mappedResults, pagenumber, totalpages, total_results]) => ({
|
|
||||||
results: mappedResults, page: pagenumber, total_pages: totalpages, total_results,
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
tmdbMethod(apiMethod, type) {
|
|
||||||
const method = TMDB_METHODS[apiMethod][type];
|
|
||||||
if (method !== undefined) return method;
|
|
||||||
throw new Error('Could not find tmdb api method.');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Maps our response from tmdb api to a movie/show object.
|
* Maps our response from tmdb api to a movie/show object.
|
||||||
* @param {response} JSON response from tmdb.
|
* @param {String} response from tmdb.
|
||||||
* @param {type} The type declared in listSearch.
|
* @param {String} The type declared in listSearch.
|
||||||
* @returns {Promise} dict with tmdb results, mapped as movie/show objects.
|
* @returns {Promise} dict with tmdb results, mapped as movie/show objects.
|
||||||
*/
|
*/
|
||||||
mapResults(response, type) {
|
mapResults(response, type) {
|
||||||
|
console.log(response.page)
|
||||||
return Promise.resolve()
|
return Promise.resolve()
|
||||||
.then(() => {
|
.then(() => {
|
||||||
const mappedResults = response.results.filter((element) => {
|
const mappedResults = response.results.filter((element) => {
|
||||||
return (element.media_type === 'movie' || element.media_type === 'tv' || element.media_type === undefined);
|
return (element.media_type === 'movie' || element.media_type === 'tv' || element.media_type === undefined);
|
||||||
}).map((element) => convertTmdbToSeasoned(element, type));
|
}).map((element) => convertTmdbToSeasoned(element, type));
|
||||||
return [mappedResults, response.page, response.total_pages, response.total_results];
|
return {results: mappedResults, page: response.page, total_pages: response.total_pages, total_results: response.total_results}
|
||||||
})
|
})
|
||||||
.catch((error) => { throw new Error(error); });
|
.catch((error) => { throw new Error(error); });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wraps moviedb library to support Promises.
|
||||||
|
* @param {String} method function name in the library
|
||||||
|
* @param {Object} argument argument to function being called
|
||||||
|
* @returns {Promise} succeeds if callback succeeds
|
||||||
|
*/
|
||||||
tmdb(method, argument) {
|
tmdb(method, argument) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const callback = (error, reponse) => {
|
const callback = (error, reponse) => {
|
||||||
|
|||||||
@@ -10,9 +10,9 @@ const requestRepository = new RequestRepository();
|
|||||||
*/
|
*/
|
||||||
function fetchRequestedController(req, res) {
|
function fetchRequestedController(req, res) {
|
||||||
// const user = req.loggedInUser;
|
// const user = req.loggedInUser;
|
||||||
const { status } = req.query;
|
const { status, page } = req.query;
|
||||||
|
|
||||||
requestRepository.fetchRequested(status)
|
requestRepository.fetchRequested(status, page)
|
||||||
.then((requestedItems) => {
|
.then((requestedItems) => {
|
||||||
res.send({ success: true, results: requestedItems, total_results: requestedItems.length });
|
res.send({ success: true, results: requestedItems, total_results: requestedItems.length });
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -13,8 +13,8 @@ const tmdb = new TMDB(cache, configuration.get('tmdb', 'apiKey'));
|
|||||||
*/
|
*/
|
||||||
function listSearchController(req, res) {
|
function listSearchController(req, res) {
|
||||||
const listname = req.params.listname;
|
const listname = req.params.listname;
|
||||||
const { type, id, page } = req.query;
|
const { type, page } = req.query;
|
||||||
tmdb.listSearch(listname, type, id, page)
|
tmdb.listSearch(listname, type, page)
|
||||||
.then((results) => {
|
.then((results) => {
|
||||||
res.send(results);
|
res.send(results);
|
||||||
}).catch((error) => {
|
}).catch((error) => {
|
||||||
|
|||||||
13
seasoned_api/test/fixtures/arrival-info-success-response.json
vendored
Normal file
13
seasoned_api/test/fixtures/arrival-info-success-response.json
vendored
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"background_path": "/yIZ1xendyqKvY3FGeeUYUd5X9Mm.jpg",
|
||||||
|
"id": 329865,
|
||||||
|
"popularity": 26.978601,
|
||||||
|
"poster_path": "/hLudzvGfpi6JlwUnsNhXwKKg4j.jpg",
|
||||||
|
"release_status": "Released",
|
||||||
|
"score": 7.3,
|
||||||
|
"summary": "Taking place after alien crafts land around the world, an expert linguist is recruited by the military to determine whether they come in peace or are a threat.",
|
||||||
|
"tagline": "Why are they here?",
|
||||||
|
"title": "Arrival",
|
||||||
|
"type": "movie",
|
||||||
|
"year": 2016
|
||||||
|
}
|
||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -7,7 +7,7 @@ const popularMoviesSuccess = require('test/fixtures/popular-movies-success-respo
|
|||||||
|
|
||||||
describe('As a user I want to get popular movies', () => {
|
describe('As a user I want to get popular movies', () => {
|
||||||
before(() => resetDatabase());
|
before(() => resetDatabase());
|
||||||
before(() => createCacheEntry('p:movie::1', popularMoviesSuccess));
|
before(() => createCacheEntry('p:movie:1', popularMoviesSuccess));
|
||||||
|
|
||||||
it('should return 200 with the information', () =>
|
it('should return 200 with the information', () =>
|
||||||
request(app)
|
request(app)
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ const popularShowsSuccess = require('test/fixtures/popular-show-success-response
|
|||||||
|
|
||||||
describe('As a user I want to get popular shows', () => {
|
describe('As a user I want to get popular shows', () => {
|
||||||
before(() => resetDatabase());
|
before(() => resetDatabase());
|
||||||
before(() => createCacheEntry('p:show::1', popularShowsSuccess));
|
before(() => createCacheEntry('p:show:1', popularShowsSuccess));
|
||||||
|
|
||||||
it('should return 200 with the information', () =>
|
it('should return 200 with the information', () =>
|
||||||
request(app)
|
request(app)
|
||||||
|
|||||||
@@ -1,14 +1,17 @@
|
|||||||
const resetDatabase = require('test/helpers/resetDatabase');
|
const resetDatabase = require('test/helpers/resetDatabase');
|
||||||
|
const createCacheEntry = require('test/helpers/createCacheEntry');
|
||||||
const app = require('src/webserver/app');
|
const app = require('src/webserver/app');
|
||||||
const request = require('supertest-as-promised');
|
const request = require('supertest-as-promised');
|
||||||
const createUser = require('test/helpers/createUser');
|
const createUser = require('test/helpers/createUser');
|
||||||
const createToken = require('test/helpers/createToken');
|
const createToken = require('test/helpers/createToken');
|
||||||
|
const infoMovieSuccess = require('test/fixtures/arrival-info-success-response.json');
|
||||||
|
|
||||||
describe('As a user I want to request a movie', () => {
|
describe('As a user I want to request a movie', () => {
|
||||||
before(() => {
|
before(() => {
|
||||||
return resetDatabase()
|
return resetDatabase()
|
||||||
.then(() => createUser('test_user', 'test@gmail.com', 'password'));
|
.then(() => createUser('test_user', 'test@gmail.com', 'password'));
|
||||||
})
|
})
|
||||||
|
before(() => createCacheEntry('i:movie:329865', infoMovieSuccess));
|
||||||
|
|
||||||
it('should return 200 when item is requested', () =>
|
it('should return 200 when item is requested', () =>
|
||||||
request(app)
|
request(app)
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ const interstellarQuerySuccess = require('test/fixtures/interstellar-query-succe
|
|||||||
|
|
||||||
describe('As an anonymous user I want to search for a movie', () => {
|
describe('As an anonymous user I want to search for a movie', () => {
|
||||||
before(() => resetDatabase());
|
before(() => resetDatabase());
|
||||||
before(() => createCacheEntry('s:1:movie:interstellar', interstellarQuerySuccess));
|
before(() => createCacheEntry('se:1:multi:interstellar', interstellarQuerySuccess));
|
||||||
|
|
||||||
it('should return 200 with the search results even if user is not logged in', () =>
|
it('should return 200 with the search results even if user is not logged in', () =>
|
||||||
request(app)
|
request(app)
|
||||||
|
|||||||
63
seasoned_api/test/unit/config/testConfig.js
Normal file
63
seasoned_api/test/unit/config/testConfig.js
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
const assert = require('assert');
|
||||||
|
const Config = require('src/config/configuration.js');
|
||||||
|
|
||||||
|
describe('Config', () => {
|
||||||
|
before(() => {
|
||||||
|
this.backedUpEnvironmentVariables = Object.assign({}, process.env);
|
||||||
|
this.backedUpConfigFields = Object.assign({}, Config.getInstance().fields);
|
||||||
|
});
|
||||||
|
|
||||||
|
after(() => {
|
||||||
|
process.env = this.backedUpEnvironmentVariables;
|
||||||
|
Config.getInstance().fields = this.backedUpConfigFields;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should retrieve section and option from config file', () => {
|
||||||
|
Config.getInstance().fields = { 'webserver': { 'port': 1337 } };
|
||||||
|
assert.equal(Config.getInstance().get('webserver', 'port'), 1337);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should resolve to environment variables if option is filtered with env', () => {
|
||||||
|
Config.getInstance().fields = { 'webserver': { 'port': 'env|SEASONED_WEBSERVER_PORT' } };
|
||||||
|
process.env.SEASONED_WEBSERVER_PORT = '1338';
|
||||||
|
assert.equal(Config.getInstance().get('webserver', 'port'), 1338);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('raises an exception if the environment variable does not exist', () => {
|
||||||
|
Config.getInstance().fields = { 'webserver': { 'port': 'env|DOES_NOT_EXIST' } };
|
||||||
|
process.env.SEASONED_WEBSERVER_PORT = '1338';
|
||||||
|
assert.throws(() => Config.getInstance().get('webserver', 'port'), /empty/);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('raises an exception if the environment variable is empty', () => {
|
||||||
|
Config.getInstance().fields = { 'webserver': { 'port': 'env|SEASONED_WEBSERVER_PORT' } };
|
||||||
|
process.env.SEASONED_WEBSERVER_PORT = '';
|
||||||
|
assert.throws(() => Config.getInstance().get('webserver', 'port'), /empty/);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('raises an exception if the section does not exist in the file', () => {
|
||||||
|
Config.getInstance().fields = { 'webserver': { 'port': '1338' } };
|
||||||
|
assert.throws(() => Config.getInstance().get('woops', 'port'), /does not exist/);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('raises an exception if the option does not exist in the file', () => {
|
||||||
|
Config.getInstance().fields = { 'webserver': { 'port': '1338' } };
|
||||||
|
assert.throws(() => Config.getInstance().get('webserver', 'woops'), /does not exist/);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns an array if field is an array', () => {
|
||||||
|
Config.getInstance().fields = { 'bouncer': { 'whitelist': [1, 2, 3] } };
|
||||||
|
assert.deepEqual(Config.getInstance().get('bouncer', 'whitelist'), [1, 2, 3]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('decodes field as base64 if base64| is before the variable', () => {
|
||||||
|
Config.getInstance().fields = { 'webserver': { 'port': 'base64|MTMzOA==' } };
|
||||||
|
assert.equal(Config.getInstance().get('webserver', 'port'), 1338);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('decodes environment variable as base64 if BASE64= is before the variable', () => {
|
||||||
|
Config.getInstance().fields = { 'webserver': { 'port': 'env|base64|SEASONED_WEBSERVER_PORT' } };
|
||||||
|
process.env.SEASONED_WEBSERVER_PORT = 'MTMzOA==';
|
||||||
|
assert.equal(Config.getInstance().get('webserver', 'port'), 1338);
|
||||||
|
});
|
||||||
|
});
|
||||||
72
seasoned_api/test/unit/config/testField.js
Normal file
72
seasoned_api/test/unit/config/testField.js
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
const assert = require('assert');
|
||||||
|
const Field = require('src/config/field.js');
|
||||||
|
|
||||||
|
describe('Field', () => {
|
||||||
|
it('should return an array if it is an array', () => {
|
||||||
|
const field = new Field([1, 2, 3]);
|
||||||
|
assert.deepEqual(field.value, [1, 2, 3]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return the plain value if it is an ordinary field', () => {
|
||||||
|
const field = new Field('plain value');
|
||||||
|
assert.equal(field.value, 'plain value');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return false if boolean false is field', () => {
|
||||||
|
const field = new Field(false);
|
||||||
|
assert.equal(field.value, false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not include any invalid filters', () => {
|
||||||
|
const field = new Field('invalid-filter|plain value');
|
||||||
|
assert.equal(field.value, 'plain value');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return the decoded value if it is filtered through base64', () => {
|
||||||
|
const field = new Field('base64|ZW5jb2RlZCB2YWx1ZQ==');
|
||||||
|
assert.equal(field.value, 'encoded value');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not decode the value if it missing the filter', () => {
|
||||||
|
const field = new Field('ZW5jb2RlZCB2YWx1ZQ==');
|
||||||
|
assert.equal(field.value, 'ZW5jb2RlZCB2YWx1ZQ==');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should retrieve the environment variable if env filter is used', () => {
|
||||||
|
const environmentVariables = { REDIS_URL: 'redis://127.0.0.1:1234' };
|
||||||
|
const field = new Field('env|REDIS_URL', environmentVariables);
|
||||||
|
assert.equal(field.value, 'redis://127.0.0.1:1234');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return undefined if the environment variable does not exist', () => {
|
||||||
|
const environmentVariables = { HTTP_PORT: 8080 };
|
||||||
|
const field = new Field('env|REDIS_URL', environmentVariables);
|
||||||
|
assert.equal(field.value, undefined);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return undefined if the environment variable is an empty string', () => {
|
||||||
|
const environmentVariables = { REDIS_URL: '' };
|
||||||
|
const field = new Field('env|REDIS_URL', environmentVariables);
|
||||||
|
assert.deepEqual(field.value, undefined);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Multiple filters', () => {
|
||||||
|
it('should decode the environment variable if base64 and env filter are used', () => {
|
||||||
|
const environmentVariables = { REDIS_URL: 'cmVkaXM6Ly9kYWdibGFkZXQubm8vMTIzNA==' };
|
||||||
|
const field = new Field('env|base64|REDIS_URL', environmentVariables);
|
||||||
|
assert.equal(field.value, 'redis://dagbladet.no/1234');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should disregard the order of filters when env and base64 are used', () => {
|
||||||
|
const environmentVariables = { REDIS_URL: 'cmVkaXM6Ly9kYWdibGFkZXQubm8vMTIzNA==' };
|
||||||
|
const field = new Field('base64|env|REDIS_URL', environmentVariables);
|
||||||
|
assert.equal(field.value, 'redis://dagbladet.no/1234');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return undefined if both filters are used and env var does not exist', () => {
|
||||||
|
const environmentVariables = { REDIS_URL: 'cmVkaXM6Ly9kYWdibGFkZXQubm8vMTIzNA==' };
|
||||||
|
const field = new Field('base64|env|REDIS_LOL', environmentVariables);
|
||||||
|
assert.equal(field.value, undefined);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
34
seasoned_api/test/unit/config/testFilters.js
Normal file
34
seasoned_api/test/unit/config/testFilters.js
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
const assert = require('assert');
|
||||||
|
const Filters = require('src/config/filters.js');
|
||||||
|
|
||||||
|
describe('Filters', () => {
|
||||||
|
it('should extract base64 as filter if it is at start of string followed by pipe', () => {
|
||||||
|
const filters = new Filters('base64|');
|
||||||
|
assert.deepEqual(filters.filters, ['base64']);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should extract base64 and env as filters if both are separated by pipe', () => {
|
||||||
|
const filters = new Filters('base64|env|');
|
||||||
|
assert.deepEqual(filters.filters, ['base64', 'env']);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not extract any filters if none are present', () => {
|
||||||
|
const filters = new Filters('base64');
|
||||||
|
assert.deepEqual(filters.filters, []);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should strip env filter from the value', () => {
|
||||||
|
const filters = new Filters('env|HELLO');
|
||||||
|
assert.deepEqual(filters.removeFiltersFromValue(), 'HELLO');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should strip env and base64 filter from the value', () => {
|
||||||
|
const filters = new Filters('env|base64|HELLO');
|
||||||
|
assert.deepEqual(filters.removeFiltersFromValue(), 'HELLO');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should strip no filters from the value if there are no filters', () => {
|
||||||
|
const filters = new Filters('HELLO');
|
||||||
|
assert.deepEqual(filters.removeFiltersFromValue(), 'HELLO');
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user