25 Commits

Author SHA1 Message Date
318d1e331b Merge pull request #124 from KevinMidboe/feature/plex-authentication
Feature plex authentication
2019-12-23 01:39:43 +01:00
5923cbf051 Incorrect query parameter. Changed from (the not defined) plex_userid to username. 2019-12-22 13:49:16 +01:00
291bdf089c Forget to rename copied link function to unlink 2019-12-22 13:42:13 +01:00
8eacde9ccc Moved tautulli config settings to be fetched from our configuration (either env variable or conf/development.json. 2019-12-22 13:36:13 +01:00
f8847c62f2 UserRepository handles updating db settings better.
Moved the plex_userid to settings to expanded with functions for
updating and fetching settings, each with its own helper function
towards the database.
Since we had a linkPlexUserId function and we dont want plex_userid to
be updated from the updatesettings function we moved unlinking to a
separate endpoint and class function.
Also a new controller and endpoints for getting and updating settings.
2019-12-22 13:30:18 +01:00
ddb7e7379d Every instance of sqlite database should have foreign_keys constraints on 2019-12-22 13:14:12 +01:00
720fb69648 Indentation 2019-12-22 12:45:01 +01:00
fedacf498e Changed input parameter from name from user to username, cause we just get the username string. 2019-12-22 12:44:45 +01:00
9022853502 Sql schema now has requested_by as a foreign key for user_name and on delete set null clause. 2019-12-22 12:43:12 +01:00
c1b96e17ca Moved database row plex_userid from user to a new table settings. Currently includes plex_userid, emoji and darkmode with user_name as a foreign key to user.user_name. 2019-12-20 21:45:31 +01:00
a5248f0631 TODO comment for login 2019-11-25 23:38:52 +01:00
e2d85c6242 Merged into master branch 2019-11-24 19:26:56 +01:00
510c014549 Added endpoint for getting plays grouped by days of week. 2019-11-11 17:59:16 +01:00
2650497986 Tautulli will serve all the tautulli api calls. Mostly this will be used by user requests for getting stats related to the logged in user. WIP but currently watch time stats, plays by number of days, and view history has been implemented.
TODO:
 - Error handling if tautulli request fails.
 - Filter the responses and/or limit them from tautulli
 - Handle parsing variable parameters from request
2019-11-04 23:11:00 +01:00
639f0ec17a Created middleware to check if the user has a plex user linked to seasoned account. If not respond with 403 meaning you did have a authorization key, but this is forbidden; explaining in the response message that no plex account user id was found for this user and to please authenticate their plex account at authenticate endpoint. 2019-11-04 23:04:42 +01:00
977d05c6f2 Refactor. Responses should return error string in object key message not error. 2019-11-04 22:58:42 +01:00
601fc1d0de Renamed search_history controller to searchHistory 2019-11-04 22:57:59 +01:00
acc26a2f09 Renamed user history to search_history and fixed an issue where search history received the entire user object and not just the username 2019-11-04 20:32:41 +01:00
5d3a5dc8a4 Removed unused console log 2019-11-04 18:46:42 +01:00
3bb9bd84d9 Merge branch 'master' into feature/plex-authentication 2019-11-04 18:24:19 +01:00
002e663be1 Merge branch 'api/v2' into feature/plex-authentication 2019-11-04 17:55:27 +01:00
495a3b4838 Merged api/v2 into feature branch 2019-11-04 00:58:43 +01:00
9e2a0101c9 Added new formdata pacakge 2019-11-04 00:44:57 +01:00
05b001de2e Created endpoint for authenticating with plex and linking your plex user to the logged in seasoned user. User database table gets a new plex_userid column. This will be used to fetch tautulli stats for your account. 2019-11-04 00:43:42 +01:00
1f9dc067e6 Plex params are now parsed with URI encoder. 2019-04-10 22:22:52 +02:00
20 changed files with 597 additions and 81 deletions

View File

@@ -11,6 +11,11 @@
"plex": {
"ip": ""
},
"tautulli": {
"apiKey": "",
"ip": "",
"port": ""
},
"raven": {
"DSN": ""
},

View File

@@ -22,6 +22,7 @@
"body-parser": "~1.18.2",
"cross-env": "~5.1.4",
"express": "~4.16.0",
"form-data": "^2.5.1",
"jsonwebtoken": "^8.2.0",
"km-moviedb": "^0.2.12",
"node-cache": "^4.1.1",

View File

@@ -1,11 +1,19 @@
CREATE TABLE IF NOT EXISTS user (
user_name varchar(127) UNIQUE,
password varchar(127),
email varchar(127) UNIQUE,
admin boolean DEFAULT 0,
email varchar(127) UNIQUE,
primary key (user_name)
);
CREATE TABLE IF NOT EXISTS settings (
user_name varchar(127) UNIQUE,
dark_mode boolean DEFAULT 0,
plex_userid varchar(127) DEFAULT NULL,
emoji varchar(16) DEFAULT NULL,
foreign key(user_name) REFERENCES user(user_name) ON DELETE CASCADE
);
CREATE TABLE IF NOT EXISTS cache (
key varchar(255),
value blob,
@@ -28,12 +36,13 @@ CREATE TABLE IF NOT EXISTS requests(
year NUMBER,
poster_path TEXT DEFAULT NULL,
background_path TEXT DEFAULT NULL,
requested_by TEXT,
requested_by varchar(127) DEFAULT NULL,
ip TEXT,
date DATE DEFAULT CURRENT_TIMESTAMP,
status CHAR(25) DEFAULT 'requested' NOT NULL,
user_agent CHAR(255) DEFAULT NULL,
type CHAR(50) DEFAULT 'movie'
type CHAR(50) DEFAULT 'movie',
foreign key(requested_by) REFERENCES user(user_name) ON DELETE SET NULL
);
CREATE TABLE IF NOT EXISTS request(

View File

@@ -1,4 +1,5 @@
DROP TABLE IF EXISTS user;
DROP TABLE IF EXISTS settings;
DROP TABLE IF EXISTS search_history;
DROP TABLE IF EXISTS requests;
DROP TABLE IF EXISTS request;

View File

@@ -6,6 +6,7 @@ class SqliteDatabase {
constructor(host) {
this.host = host;
this.connection = new sqlite3.Database(this.host);
this.execute('pragma foreign_keys = on;');
this.schemaDirectory = path.join(__dirname, 'schemas');
}

View File

@@ -86,18 +86,18 @@ class RequestRepository {
* @param {tmdb} tmdb class of movie|show to add
* @returns {Promise}
*/
requestFromTmdb(tmdb, ip, user_agent, user) {
requestFromTmdb(tmdb, ip, user_agent, username) {
return Promise.resolve()
.then(() => this.database.get(this.queries.read, [tmdb.id, tmdb.type]))
.then(row => assert.equal(row, undefined, 'Id has already been requested'))
.then(() => this.database.run(this.queries.add, [tmdb.id, tmdb.title, tmdb.year, tmdb.poster, tmdb.backdrop, user, ip, user_agent, tmdb.type]))
.catch((error) => {
if (error.name === 'AssertionError' || error.message.endsWith('been requested')) {
throw new Error('This id is already requested', error.message);
}
console.log('Error @ request.addTmdb:', error);
throw new Error('Could not add request');
});
.then(() => this.database.get(this.queries.read, [tmdb.id, tmdb.type]))
.then(row => assert.equal(row, undefined, 'Id has already been requested'))
.then(() => this.database.run(this.queries.add, [tmdb.id, tmdb.title, tmdb.year, tmdb.poster, tmdb.backdrop, username, ip, user_agent, tmdb.type]))
.catch((error) => {
if (error.name === 'AssertionError' || error.message.endsWith('been requested')) {
throw new Error('This id is already requested', error.message);
}
console.log('Error @ request.addTmdb:', error);
throw new Error('Could not add request');
});
}
/**

View File

@@ -28,17 +28,23 @@ class SearchHistory {
/**
* Creates a new search entry in the database.
* @param {User} user a new user
* @param {String} username logged in user doing the search
* @param {String} searchQuery the query the user searched for
* @returns {Promise}
*/
create(user, searchQuery) {
return Promise.resolve()
.then(() => this.database.run(this.queries.create, [searchQuery, user]))
.catch((error) => {
create(username, searchQuery) {
return this.database.run(this.queries.create, [searchQuery, username])
.catch(error => {
if (error.message.includes('FOREIGN')) {
throw new Error('Could not create search history.');
}
throw {
success: false,
status: 500,
message: 'An unexpected error occured',
source: 'database'
}
});
}
}

View File

@@ -0,0 +1,58 @@
const fetch = require('node-fetch');
class Tautulli {
constructor(apiKey, ip, port) {
this.apiKey = apiKey;
this.ip = ip;
this.port = port;
}
buildUrlWithCmdAndUserid(cmd, user_id) {
const url = new URL('api/v2', `http://${this.ip}:${this.port}`)
url.searchParams.append('apikey', this.apiKey)
url.searchParams.append('cmd', cmd)
url.searchParams.append('user_id', user_id)
return url
}
getPlaysByDayOfWeek(plex_userid, days, y_axis) {
const url = this.buildUrlWithCmdAndUserid('get_plays_by_dayofweek', plex_userid)
url.searchParams.append('time_range', days)
url.searchParams.append('y_axis', y_axis)
return fetch(url.href)
.then(resp => resp.json())
}
getPlaysByDays(plex_userid, days, y_axis) {
const url = this.buildUrlWithCmdAndUserid('get_plays_by_date', plex_userid)
url.searchParams.append('time_range', days)
url.searchParams.append('y_axis', y_axis)
return fetch(url.href)
.then(resp => resp.json())
}
watchTimeStats(plex_userid) {
const url = this.buildUrlWithCmdAndUserid('get_user_watch_time_stats', plex_userid)
url.searchParams.append('grouping', 0)
return fetch(url.href)
.then(resp => resp.json())
}
viewHistory(plex_userid) {
const url = this.buildUrlWithCmdAndUserid('get_history', plex_userid)
url.searchParams.append('start', 0)
url.searchParams.append('length', 50)
console.log('fetching url', url.href)
return fetch(url.href)
.then(resp => resp.json())
}
}
module.exports = Tautulli;

View File

@@ -2,64 +2,218 @@ const assert = require('assert');
const establishedDatabase = require('src/database/database');
class UserRepository {
constructor(database) {
this.database = database || establishedDatabase;
this.queries = {
read: 'select * from user where lower(user_name) = lower(?)',
create: 'insert into user (user_name) values (?)',
change: 'update user set password = ? where user_name = ?',
retrieveHash: 'select * from user where user_name = ?',
getAdminStateByUser: 'select admin from user where user_name = ?'
};
}
constructor(database) {
this.database = database || establishedDatabase;
this.queries = {
read: 'select * from user where lower(user_name) = lower(?)',
create: 'insert into user (user_name) values (?)',
change: 'update user set password = ? where user_name = ?',
retrieveHash: 'select * from user where user_name = ?',
getAdminStateByUser: 'select admin from user where user_name = ?',
link: 'update settings set plex_userid = ? where user_name = ?',
unlink: 'update settings set plex_userid = null where user_name = ?',
createSettings: 'insert into settings (user_name) values (?)',
updateSettings: 'update settings set user_name = ?, dark_mode = ?, emoji = ?',
getSettings: 'select * from settings where user_name = ?'
};
}
/**
* Create a user in a database.
* @param {User} user the user you want to create
* @returns {Promise}
*/
create(user) {
return Promise.resolve()
.then(() => this.database.get(this.queries.read, user.username))
.then(() => this.database.run(this.queries.create, user.username))
.catch((error) => {
if (error.name === 'AssertionError' || error.message.endsWith('user_name')) {
throw new Error('That username is already registered');
}
throw Error(error)
});
}
/**
* Retrieve a password from a database.
* @param {User} user the user you want to retrieve the password
* @returns {Promise}
*/
retrieveHash(user) {
return Promise.resolve()
.then(() => this.database.get(this.queries.retrieveHash, user.username))
.then((row) => {
assert(row, 'The user does not exist.');
return row.password;
})
.catch((err) => { console.log(error); throw new Error('Unable to find your user.'); });
}
/**
* Change a user's password in a database.
* @param {User} user the user you want to create
* @param {String} password the new password you want to change
* @returns {Promise}
*/
changePassword(user, password) {
return Promise.resolve(this.database.run(this.queries.change, [password, user.username]));
}
checkAdmin(user) {
return this.database.get(this.queries.getAdminStateByUser, user.username).then((row) => {
return row.admin;
/**
* Create a user in a database.
* @param {User} user the user you want to create
* @returns {Promise}
*/
create(user) {
return this.database.get(this.queries.read, user.username)
.then(() => this.database.run(this.queries.create, user.username))
.catch((error) => {
if (error.name === 'AssertionError' || error.message.endsWith('user_name')) {
throw new Error('That username is already registered');
}
throw Error(error)
});
}
}
/**
* Retrieve a password from a database.
* @param {User} user the user you want to retrieve the password
* @returns {Promise}
*/
retrieveHash(user) {
return this.database.get(this.queries.retrieveHash, user.username)
.then(row => {
assert(row, 'The user does not exist.');
return row.password;
})
.catch(err => { console.log(error); throw new Error('Unable to find your user.'); })
}
/**
* Change a user's password in a database.
* @param {User} user the user you want to create
* @param {String} password the new password you want to change
* @returns {Promise}
*/
changePassword(user, password) {
return this.database.run(this.queries.change, [password, user.username])
}
/**
* Link plex userid with seasoned user
* @param {String} username the user you want to lunk plex userid with
* @param {Number} plexUserID plex unique id
* @returns {Promsie}
*/
linkPlexUserId(username, plexUserID) {
return new Promise((resolve, reject) => {
this.database.run(this.queries.link, [plexUserID, username])
.then(row => resolve(row))
.catch(error => {
// TODO log this unknown db error
console.log('db error', error)
reject({
status: 500,
message: 'An unexpected error occured while linking plex and seasoned accounts',
source: 'seasoned database'
})
})
})
}
/**
* Unlink plex userid with seasoned user
* @param {User} user the user you want to lunk plex userid with
* @returns {Promsie}
*/
unlinkPlexUserId(username) {
return new Promise((resolve, reject) => {
this.database.run(this.queries.unlink, username)
.then(row => resolve(row))
.catch(error => {
// TODO log this unknown db error
console.log('db error', error)
reject({
status: 500,
message: 'An unexpected error occured while unlinking plex and seasoned accounts',
source: 'seasoned database'
})
})
})
}
/**
* Check if the user has boolean flag set for admin in database
* @param {User} user object
* @returns {Promsie}
*/
checkAdmin(user) {
return this.database.get(this.queries.getAdminStateByUser, user.username)
.then((row) => row.admin);
}
/**
* Get settings for user matching string username
* @param {String} username
* @returns {Promsie}
*/
getSettings(username) {
return new Promise((resolve, reject) => {
this.database.get(this.queries.getSettings, username)
.then(async (row) => {
if (row == null) {
console.log(`settings do not exist for user: ${username}. Creating settings entry.`)
const userExistsWithUsername = await this.database.get('select * from user where user_name is ?', username)
if (userExistsWithUsername !== undefined) {
try {
resolve(this.dbCreateSettings(username))
} catch (error) {
reject(error)
}
} else {
reject({
status: 404,
message: 'User not found, no settings to get'
})
}
}
resolve(row)
})
.catch(error => {
console.error('Unexpected error occured while fetching settings for your account. Error:', error)
reject({
status: 500,
message: 'An unexpected error occured while fetching settings for your account',
source: 'seasoned database'
})
})
})
}
/**
* Update settings values for user matching string username
* @param {String} username
* @param {String} dark_mode
* @param {String} emoji
* @returns {Promsie}
*/
updateSettings(username, dark_mode=undefined, emoji=undefined) {
const settings = this.getSettings(username)
dark_mode = dark_mode !== undefined ? dark_mode : settings.dark_mode
emoji = emoji !== undefined ? emoji : settings.emoji
return this.dbUpdateSettings(username, dark_mode, emoji)
.catch(error => {
if (error.status && error.message) {
return error
}
return {
status: 500,
message: 'An unexpected error occured while updating settings for your account'
}
})
}
/**
* Helper function for creating settings in the database
* @param {String} username
* @returns {Promsie}
*/
dbCreateSettings(username) {
return this.database.run(this.queries.createSettings, username)
.then(() => this.database.get(this.queries.getSettings, username))
.catch(error => rejectUnexpectedDatabaseError('Unexpected error occured while creating settings', 503, error))
}
/**
* Helper function for updating settings in the database
* @param {String} username
* @returns {Promsie}
*/
dbUpdateSettings(username, dark_mode, emoji) {
return new Promise((resolve, reject) =>
this.database.run(this.queries.updateSettings, [username, dark_mode, emoji])
.then(row => resolve(row)))
}
}
const rejectUnexpectedDatabaseError = (message, status, error, reject=null) => {
console.error(error)
const body = {
status,
message,
source: 'seasoned database'
}
if (reject == null) {
return new Promise((resolve, reject) => reject(body))
}
reject(body)
}
module.exports = UserRepository;

View File

@@ -4,9 +4,13 @@ const bodyParser = require('body-parser');
const tokenToUser = require('./middleware/tokenToUser');
const mustBeAuthenticated = require('./middleware/mustBeAuthenticated');
const mustBeAdmin = require('./middleware/mustBeAdmin');
const mustHaveAccountLinkedToPlex = require('./middleware/mustHaveAccountLinkedToPlex');
const configuration = require('src/config/configuration').getInstance();
const listController = require('./controllers/list/listController');
const tautulli = require('./controllers/user/viewHistory.js');
const SettingsController = require('./controllers/user/settings');
const AuthenticatePlexAccountController = require('./controllers/user/AuthenticatePlexAccount');
// TODO: Have our raven router check if there is a value, if not don't enable raven.
Raven.config(configuration.get('raven', 'DSN')).install();
@@ -53,8 +57,17 @@ app.use(function onError(err, req, res, next) {
*/
router.post('/v1/user', require('./controllers/user/register.js'));
router.post('/v1/user/login', require('./controllers/user/login.js'));
router.get('/v1/user/history', mustBeAuthenticated, require('./controllers/user/history.js'));
router.get('/v1/user/settings', mustBeAuthenticated, SettingsController.getSettingsController);
router.put('/v1/user/settings', mustBeAuthenticated, SettingsController.updateSettingsController);
router.get('/v1/user/search_history', mustBeAuthenticated, require('./controllers/user/searchHistory.js'));
router.get('/v1/user/requests', mustBeAuthenticated, require('./controllers/user/requests.js'));
router.post('/v1/user/link_plex', mustBeAuthenticated, AuthenticatePlexAccountController.link);
router.post('/v1/user/unlink_plex', mustBeAuthenticated, AuthenticatePlexAccountController.unlink);
router.get('/v1/user/view_history', mustHaveAccountLinkedToPlex, tautulli.userViewHistoryController);
router.get('/v1/user/watch_time', mustHaveAccountLinkedToPlex, tautulli.watchTimeStatsController);
router.get('/v1/user/plays_by_day', mustHaveAccountLinkedToPlex, tautulli.getPlaysByDaysController);
router.get('/v1/user/plays_by_dayofweek', mustHaveAccountLinkedToPlex, tautulli.getPlaysByDayOfWeekController);
/**
* Seasoned

View File

@@ -24,7 +24,7 @@ function multiSearchController(req, res) {
const { query, page } = req.query;
if (user) {
searchHistory.create(user, query)
searchHistory.create(user.username, query)
}
return tmdb.multiSearch(query, page)

View File

@@ -0,0 +1,87 @@
const UserRepository = require('src/user/userRepository');
const userRepository = new UserRepository();
const fetch = require('node-fetch');
const FormData = require('form-data');
function handleError(error, res) {
let { status, message, source } = error;
if (status && message) {
if (status === 401) {
message = 'Unauthorized. Please check plex credentials.',
source = 'plex'
}
res.status(status).send({ success: false, message, source })
} else {
console.log('caught authenticate plex account controller error', error)
res.status(500).send({
message: 'An unexpected error occured while authenticating your account with plex',
source
})
}
}
function handleResponse(response) {
if (!response.ok) {
throw {
success: false,
status: response.status,
message: response.statusText
}
}
return response.json()
}
function plexAuthenticate(username, password) {
const url = 'https://plex.tv/api/v2/users/signin'
const form = new FormData()
form.append('login', username)
form.append('password', password)
form.append('rememberMe', 'false')
const headers = {
'Accept': 'application/json, text/plain, */*',
'Content-Type': form.getHeaders()['content-type'],
'X-Plex-Client-Identifier': 'seasonedRequest'
}
const options = {
method: 'POST',
headers,
body: form
}
return fetch(url, options)
.then(resp => handleResponse(resp))
}
function link(req, res) {
const user = req.loggedInUser;
const { username, password } = req.body;
return plexAuthenticate(username, password)
.then(plexUser => userRepository.linkPlexUserId(user.username, plexUser.id))
.then(response => res.send({
success: true,
message: "Successfully authenticated and linked plex account with seasoned request."
}))
.catch(error => handleError(error, res))
}
function unlink(req, res) {
const user = req.loggedInUser;
return userRepository.unlinkPlexUserId(user.username)
.then(response => res.send({
success: true,
message: "Successfully unlinked plex account from seasoned request."
}))
.catch(error => handleError(error, res))
}
module.exports = {
link,
unlink
};

View File

@@ -8,6 +8,9 @@ const secret = configuration.get('authentication', 'secret');
const userSecurity = new UserSecurity();
const userRepository = new UserRepository();
// TODO look to move some of the token generation out of the reach of the final "catch-all"
// catch including the, maybe sensitive, error message.
/**
* Controller: Log in a user provided correct credentials.
* @param {Request} req http request variable

View File

@@ -0,0 +1,42 @@
const UserRepository = require('src/user/userRepository');
const userRepository = new UserRepository();
/**
* Controller: Retrieves settings of a logged in user
* @param {Request} req http request variable
* @param {Response} res
* @returns {Callback}
*/
const getSettingsController = (req, res) => {
const user = req.loggedInUser;
const username = user === undefined ? undefined : user.username;
userRepository.getSettings(username)
.then(settings => {
res.send({ success: true, settings });
})
.catch(error => {
res.status(404).send({ success: false, message: error.message });
});
}
const updateSettingsController = (req, res) => {
const user = req.loggedInUser;
const username = user === undefined ? undefined : user.username;
const idempotencyKey = req.headers('Idempotency-Key'); // TODO implement better transactions
const { dark_mode, emoji } = req.body;
userRepository.updateSettings(username, dark_mode, emoji)
.then(settings => {
res.send({ success: true, settings });
})
.catch(error => {
res.status(404).send({ success: false, message: error.message });
});
}
module.exports = {
getSettingsController,
updateSettingsController
}

View File

@@ -0,0 +1,105 @@
const configuration = require('src/config/configuration').getInstance();
const Tautulli = require('src/tautulli/tautulli');
const apiKey = configuration.get('tautulli', 'apiKey');
const ip = configuration.get('tautulli', 'ip');
const port = configuration.get('tautulli', 'port');
const tautulli = new Tautulli(apiKey, ip, port);
function handleError(error, res) {
const { status, message } = error;
if (status && message) {
res.status(status).send({ success: false, message })
} else {
console.log('caught view history controller error', error)
res.status(500).send({ message: 'An unexpected error occured while fetching view history'})
}
}
function watchTimeStatsController(req, res) {
const user = req.loggedInUser;
tautulli.watchTimeStats(user.plex_userid)
.then(data => {
console.log('data', data, JSON.stringify(data.response.data))
return res.send({
success: true,
data: data.response.data,
message: 'watch time successfully fetched from tautulli'
})
})
}
function getPlaysByDayOfWeekController(req, res) {
const user = req.loggedInUser;
const { days, y_axis } = req.query;
tautulli.getPlaysByDayOfWeek(user.plex_userid, days, y_axis)
.then(data => res.send({
success: true,
data: data.response.data,
message: 'play by day of week successfully fetched from tautulli'
})
)
}
function getPlaysByDaysController(req, res) {
const user = req.loggedInUser;
const { days, y_axis } = req.query;
if (days === undefined) {
return res.status(422).send({
success: false,
message: "Missing parameter: days (number)"
})
}
const allowedYAxisDataType = ['plays', 'duration'];
if (!allowedYAxisDataType.includes(y_axis)) {
return res.status(422).send({
success: false,
message: `Y axis parameter must be one of values: [${ allowedYAxisDataType }]`
})
}
tautulli.getPlaysByDays(user.plex_userid, days, y_axis)
.then(data => res.send({
success: true,
data: data.response.data
}))
}
function userViewHistoryController(req, res) {
const user = req.loggedInUser;
console.log('user', user)
// TODO here we should check if we can init tau
// and then return 501 Not implemented
tautulli.viewHistory(user.plex_userid)
.then(data => {
console.log('data', data, JSON.stringify(data.response.data.data))
return res.send({
success: true,
data: data.response.data.data,
message: 'view history successfully fetched from tautulli'
})
})
.catch(error => handleError(error))
// const username = user.username;
}
module.exports = {
watchTimeStatsController,
getPlaysByDaysController,
getPlaysByDayOfWeekController,
userViewHistoryController
};

View File

@@ -6,7 +6,7 @@ const mustBeAdmin = (req, res, next) => {
if (req.loggedInUser === undefined) {
return res.status(401).send({
success: false,
error: 'You must be logged in.',
message: 'You must be logged in.',
});
} else {
database.get(`SELECT admin FROM user WHERE user_name IS ?`, req.loggedInUser.username)
@@ -15,7 +15,7 @@ const mustBeAdmin = (req, res, next) => {
if (isAdmin.admin == 0) {
return res.status(401).send({
success: false,
error: 'You must be logged in as a admin.'
message: 'You must be logged in as a admin.'
})
}
})

View File

@@ -2,7 +2,7 @@ const mustBeAuthenticated = (req, res, next) => {
if (req.loggedInUser === undefined) {
return res.status(401).send({
success: false,
error: 'You must be logged in.',
message: 'You must be logged in.',
});
}
return next();

View File

@@ -0,0 +1,31 @@
const establishedDatabase = require('src/database/database');
const mustHaveAccountLinkedToPlex = (req, res, next) => {
let database = establishedDatabase;
const loggedInUser = req.loggedInUser;
if (loggedInUser === undefined) {
return res.status(401).send({
success: false,
message: 'You must have your account linked to a plex account.',
});
} else {
database.get(`SELECT plex_userid FROM settings WHERE user_name IS ?`, loggedInUser.username)
.then(row => {
const plex_userid = row.plex_userid;
if (plex_userid === null || plex_userid === undefined) {
return res.status(403).send({
success: false,
message: 'No plex account user id found for your user. Please authenticate your plex account at /user/authenticate.'
})
} else {
req.loggedInUser.plex_userid = plex_userid;
return next();
}
})
}
};
module.exports = mustHaveAccountLinkedToPlex;

View File

@@ -2777,7 +2777,7 @@ forever-agent@~0.6.1:
resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91"
integrity sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=
form-data@^2.3.1:
form-data@^2.3.1, form-data@^2.5.1:
version "2.5.1"
resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.5.1.tgz#f2cbec57b5e59e23716e128fe44d4e5dd23895f4"
integrity sha512-m21N3WOmEEURgk6B9GLOE4RuWOFf28Lhh9qGYeNlGq4VDXUlJy2th2slBNU8Gp8EzloYZOibZJ7t5ecIrFSjVA==