diff --git a/seasoned_api/conf/development.json.example b/seasoned_api/conf/development.json.example index 6f95f9a..cb421e7 100644 --- a/seasoned_api/conf/development.json.example +++ b/seasoned_api/conf/development.json.example @@ -11,6 +11,11 @@ "plex": { "ip": "" }, + "tautulli": { + "apiKey": "", + "ip": "", + "port": "" + }, "raven": { "DSN": "" }, diff --git a/seasoned_api/package.json b/seasoned_api/package.json index 85d0d19..f313702 100644 --- a/seasoned_api/package.json +++ b/seasoned_api/package.json @@ -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", diff --git a/seasoned_api/src/database/schemas/setup.sql b/seasoned_api/src/database/schemas/setup.sql index 91bfc3e..015d356 100644 --- a/seasoned_api/src/database/schemas/setup.sql +++ b/seasoned_api/src/database/schemas/setup.sql @@ -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( diff --git a/seasoned_api/src/database/schemas/teardown.sql b/seasoned_api/src/database/schemas/teardown.sql index cf7e6e3..31f54b2 100644 --- a/seasoned_api/src/database/schemas/teardown.sql +++ b/seasoned_api/src/database/schemas/teardown.sql @@ -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; diff --git a/seasoned_api/src/database/sqliteDatabase.js b/seasoned_api/src/database/sqliteDatabase.js index aee129a..fc5459e 100644 --- a/seasoned_api/src/database/sqliteDatabase.js +++ b/seasoned_api/src/database/sqliteDatabase.js @@ -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'); } diff --git a/seasoned_api/src/request/request.js b/seasoned_api/src/request/request.js index 380c12a..43fe112 100644 --- a/seasoned_api/src/request/request.js +++ b/seasoned_api/src/request/request.js @@ -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'); + }); } /** diff --git a/seasoned_api/src/searchHistory/searchHistory.js b/seasoned_api/src/searchHistory/searchHistory.js index 4ebb899..c2d5a67 100644 --- a/seasoned_api/src/searchHistory/searchHistory.js +++ b/seasoned_api/src/searchHistory/searchHistory.js @@ -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' + } }); } } diff --git a/seasoned_api/src/tautulli/tautulli.js b/seasoned_api/src/tautulli/tautulli.js new file mode 100644 index 0000000..42ebafc --- /dev/null +++ b/seasoned_api/src/tautulli/tautulli.js @@ -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; diff --git a/seasoned_api/src/user/userRepository.js b/seasoned_api/src/user/userRepository.js index c2cf6b2..8c0221c 100644 --- a/seasoned_api/src/user/userRepository.js +++ b/seasoned_api/src/user/userRepository.js @@ -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; diff --git a/seasoned_api/src/webserver/app.js b/seasoned_api/src/webserver/app.js index b1b625e..185fd58 100644 --- a/seasoned_api/src/webserver/app.js +++ b/seasoned_api/src/webserver/app.js @@ -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 diff --git a/seasoned_api/src/webserver/controllers/search/multiSearch.js b/seasoned_api/src/webserver/controllers/search/multiSearch.js index 0f9b05f..f0a3810 100644 --- a/seasoned_api/src/webserver/controllers/search/multiSearch.js +++ b/seasoned_api/src/webserver/controllers/search/multiSearch.js @@ -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) diff --git a/seasoned_api/src/webserver/controllers/user/authenticatePlexAccount.js b/seasoned_api/src/webserver/controllers/user/authenticatePlexAccount.js new file mode 100644 index 0000000..6e17b59 --- /dev/null +++ b/seasoned_api/src/webserver/controllers/user/authenticatePlexAccount.js @@ -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 +}; diff --git a/seasoned_api/src/webserver/controllers/user/login.js b/seasoned_api/src/webserver/controllers/user/login.js index ada2bcc..b0f091c 100644 --- a/seasoned_api/src/webserver/controllers/user/login.js +++ b/seasoned_api/src/webserver/controllers/user/login.js @@ -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 diff --git a/seasoned_api/src/webserver/controllers/user/history.js b/seasoned_api/src/webserver/controllers/user/searchHistory.js similarity index 100% rename from seasoned_api/src/webserver/controllers/user/history.js rename to seasoned_api/src/webserver/controllers/user/searchHistory.js diff --git a/seasoned_api/src/webserver/controllers/user/settings.js b/seasoned_api/src/webserver/controllers/user/settings.js new file mode 100644 index 0000000..6774146 --- /dev/null +++ b/seasoned_api/src/webserver/controllers/user/settings.js @@ -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 +} \ No newline at end of file diff --git a/seasoned_api/src/webserver/controllers/user/viewHistory.js b/seasoned_api/src/webserver/controllers/user/viewHistory.js new file mode 100644 index 0000000..1b7b4a4 --- /dev/null +++ b/seasoned_api/src/webserver/controllers/user/viewHistory.js @@ -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 +}; diff --git a/seasoned_api/src/webserver/middleware/mustBeAdmin.js b/seasoned_api/src/webserver/middleware/mustBeAdmin.js index 042bd40..3fe9c50 100644 --- a/seasoned_api/src/webserver/middleware/mustBeAdmin.js +++ b/seasoned_api/src/webserver/middleware/mustBeAdmin.js @@ -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.' }) } }) diff --git a/seasoned_api/src/webserver/middleware/mustBeAuthenticated.js b/seasoned_api/src/webserver/middleware/mustBeAuthenticated.js index 17a8973..e1153ce 100644 --- a/seasoned_api/src/webserver/middleware/mustBeAuthenticated.js +++ b/seasoned_api/src/webserver/middleware/mustBeAuthenticated.js @@ -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(); diff --git a/seasoned_api/src/webserver/middleware/mustHaveAccountLinkedToPlex.js b/seasoned_api/src/webserver/middleware/mustHaveAccountLinkedToPlex.js new file mode 100644 index 0000000..67f6944 --- /dev/null +++ b/seasoned_api/src/webserver/middleware/mustHaveAccountLinkedToPlex.js @@ -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; diff --git a/seasoned_api/yarn.lock b/seasoned_api/yarn.lock index 58e6b54..a022954 100644 --- a/seasoned_api/yarn.lock +++ b/seasoned_api/yarn.lock @@ -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==