diff --git a/seasoned_api/src/cache/redis.js b/seasoned_api/src/cache/redis.js index df206bb..df1cd34 100644 --- a/seasoned_api/src/cache/redis.js +++ b/seasoned_api/src/cache/redis.js @@ -11,15 +11,15 @@ class Cache { return new Promise((resolve, reject) => { client.get(key, (error, reply) => { if (reply == null) { - return reject() + return reject(); } - resolve(JSON.parse(reply)) - }) - }) + resolve(JSON.parse(reply)); + }); + }); } - /** + /** * Insert cache entry with key and value. * @param {String} key of the cache entry * @param {String} value of the cache entry @@ -27,22 +27,25 @@ class Cache { * @returns {Object} */ set(key, value, timeToLive = 10800) { - if (value == null || key == null) - return null + if (value == null || key == null) return null; const json = JSON.stringify(value); client.set(key, json, (error, reply) => { - if (reply == 'OK') { - + if (reply == "OK") { // successfully set value with key, now set TTL for key - client.expire(key, timeToLive, (e) => { - if (e) - console.error('Unexpected error while setting expiration for key:', key, '. Error:', error) - }) + client.expire(key, timeToLive, e => { + if (e) + console.error( + "Unexpected error while setting expiration for key:", + key, + ". Error:", + error + ); + }); } - }) + }); - return value + return value; } } diff --git a/seasoned_api/src/tautulli/tautulli.js b/seasoned_api/src/tautulli/tautulli.js index 42ebafc..3ac47bc 100644 --- a/seasoned_api/src/tautulli/tautulli.js +++ b/seasoned_api/src/tautulli/tautulli.js @@ -1,4 +1,4 @@ -const fetch = require('node-fetch'); +const fetch = require("node-fetch"); class Tautulli { constructor(apiKey, ip, port) { @@ -8,50 +8,66 @@ class Tautulli { } 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) + 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 + return url; + } + + logTautulliError(error) { + console.error("error fetching from tautulli"); + + throw error; } 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) + 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()) + .catch(error => this.logTautulliError(error)); } 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) + 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()) + .catch(error => this.logTautulliError(error)); } watchTimeStats(plex_userid) { - const url = this.buildUrlWithCmdAndUserid('get_user_watch_time_stats', plex_userid) - url.searchParams.append('grouping', 0) + const url = this.buildUrlWithCmdAndUserid( + "get_user_watch_time_stats", + plex_userid + ); + url.searchParams.append("grouping", 0); return fetch(url.href) .then(resp => resp.json()) -} + .catch(error => this.logTautulliError(error)); + } viewHistory(plex_userid) { - const url = this.buildUrlWithCmdAndUserid('get_history', plex_userid) - - url.searchParams.append('start', 0) - url.searchParams.append('length', 50) + const url = this.buildUrlWithCmdAndUserid("get_history", plex_userid); - console.log('fetching url', url.href) + url.searchParams.append("start", 0); + url.searchParams.append("length", 50); + + console.log("fetching url", url.href); return fetch(url.href) .then(resp => resp.json()) + .catch(error => this.logTautulliError(error)); } } diff --git a/seasoned_api/src/user/userRepository.js b/seasoned_api/src/user/userRepository.js index 8c0221c..745fce7 100644 --- a/seasoned_api/src/user/userRepository.js +++ b/seasoned_api/src/user/userRepository.js @@ -1,219 +1,256 @@ -const assert = require('assert'); -const establishedDatabase = require('src/database/database'); +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 = ?', - 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 = ?' + 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 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) + 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'); + .catch(error => { + if ( + error.name === "AssertionError" || + error.message.endsWith("user_name") + ) { + throw new Error("That username is already registered"); } - throw Error(error) + throw Error(error); }); } /** - * Retrieve a password from a database. - * @param {User} user the user you want to retrieve the password - * @returns {Promise} - */ + * 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) + return this.database + .get(this.queries.retrieveHash, user.username) .then(row => { - assert(row, 'The user does not exist.'); + assert(row, "The user does not exist."); return row.password; }) - .catch(err => { console.log(error); throw new Error('Unable to find your user.'); }) + .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} - */ + * 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]) + 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} - */ + * 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]) + 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) + console.error("db error", error); reject({ status: 500, - message: 'An unexpected error occured while linking plex and seasoned accounts', - source: 'seasoned database' - }) - }) - }) + 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} - */ + * 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) + this.database + .run(this.queries.unlink, username) .then(row => resolve(row)) .catch(error => { // TODO log this unknown db error - console.log('db error', error) + console.log("db error", error); reject({ status: 500, - message: 'An unexpected error occured while unlinking plex and seasoned accounts', - source: 'seasoned database' - }) - }) - }) + 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} - */ + /** + * 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); + 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} - */ + /** + * 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) => { + 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.`) + console.debug( + `settings do not exist for user: ${username}. Creating settings entry.` + ); - const userExistsWithUsername = await this.database.get('select * from user where user_name is ?', username) + const userExistsWithUsername = await this.database.get( + "select * from user where user_name is ?", + username + ); if (userExistsWithUsername !== undefined) { try { - resolve(this.dbCreateSettings(username)) + resolve(this.dbCreateSettings(username)); } catch (error) { - reject(error) + reject(error); } } else { reject({ status: 404, - message: 'User not found, no settings to get' - }) + message: "User not found, no settings to get" + }); } } - resolve(row) + resolve(row); }) .catch(error => { - console.error('Unexpected error occured while fetching settings for your account. Error:', 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' - }) - }) - }) + 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 + /** + * 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 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' - } - }) + 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} - */ + /** + * Helper function for creating settings in the database + * @param {String} username + * @returns {Promsie} + */ dbCreateSettings(username) { - return this.database.run(this.queries.createSettings, 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)) + .catch(error => + rejectUnexpectedDatabaseError( + "Unexpected error occured while creating settings", + 503, + error + ) + ); } - /** - * Helper function for updating settings in the database - * @param {String} username - * @returns {Promsie} - */ + /** + * 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))) + 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 rejectUnexpectedDatabaseError = ( + message, + status, + error, + reject = null +) => { + console.error(error); const body = { status, message, - source: 'seasoned database' - } + source: "seasoned database" + }; if (reject == null) { - return new Promise((resolve, reject) => reject(body)) + return new Promise((resolve, reject) => reject(body)); } - reject(body) -} + reject(body); +}; module.exports = UserRepository; diff --git a/seasoned_api/src/user/userSecurity.js b/seasoned_api/src/user/userSecurity.js index d878664..4c5556a 100644 --- a/seasoned_api/src/user/userSecurity.js +++ b/seasoned_api/src/user/userSecurity.js @@ -1,10 +1,10 @@ -const bcrypt = require('bcrypt'); -const UserRepository = require('src/user/userRepository'); +const bcrypt = require("bcrypt"); +const UserRepository = require("src/user/userRepository"); class UserSecurity { constructor(database) { - this.userRepository = new UserRepository(database); -} + this.userRepository = new UserRepository(database); + } /** * Create a new user in PlanFlix. @@ -13,15 +13,15 @@ class UserSecurity { * @returns {Promise} */ createNewUser(user, clearPassword) { - if (user.username.trim() === '') { - throw new Error('The username is empty.'); - } else if (clearPassword.trim() === '') { - throw new Error('The password is empty.'); + if (user.username.trim() === "") { + throw new Error("The username is empty."); + } else if (clearPassword.trim() === "") { + throw new Error("The password is empty."); } else { return Promise.resolve() .then(() => this.userRepository.create(user)) .then(() => UserSecurity.hashPassword(clearPassword)) - .then(hash => this.userRepository.changePassword(user, hash)) + .then(hash => this.userRepository.changePassword(user, hash)); } } @@ -35,21 +35,22 @@ class UserSecurity { return Promise.resolve() .then(() => this.userRepository.retrieveHash(user)) .then(hash => UserSecurity.compareHashes(hash, clearPassword)) - .catch(() => { throw new Error('Incorrect username or password.'); }); + .catch(() => { + throw new Error("Incorrect username or password."); + }); } - /** - * Compare between a password and a hash password from database. - * @param {String} hash the hash password from database - * @param {String} clearPassword the user's password - * @returns {Promise} - */ + /** + * Compare between a password and a hash password from database. + * @param {String} hash the hash password from database + * @param {String} clearPassword the user's password + * @returns {Promise} + */ static compareHashes(hash, clearPassword) { return new Promise((resolve, reject) => { bcrypt.compare(clearPassword, hash, (error, match) => { - if (match) - resolve() - reject() + if (match) resolve(true); + reject(false); }); }); } @@ -60,7 +61,7 @@ class UserSecurity { * @returns {Promise} */ static hashPassword(clearPassword) { - return new Promise((resolve) => { + return new Promise(resolve => { const saltRounds = 10; bcrypt.hash(clearPassword, saltRounds, (error, hash) => { resolve(hash); diff --git a/seasoned_api/src/webserver/controllers/user/viewHistory.js b/seasoned_api/src/webserver/controllers/user/viewHistory.js index 1b7b4a4..9f49fc9 100644 --- a/seasoned_api/src/webserver/controllers/user/viewHistory.js +++ b/seasoned_api/src/webserver/controllers/user/viewHistory.js @@ -1,47 +1,52 @@ -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 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 }) + return 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'}) + console.log("caught view history controller error", error); + return 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) + return 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' - }) + message: "watch time successfully fetched from tautulli" + }); }) + .catch(error => handleError(error, res)); } 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' - }) + return 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" + }) ) + .catch(error => handleError(error, res)); } function getPlaysByDaysController(req, res) { @@ -52,49 +57,46 @@ function getPlaysByDaysController(req, res) { return res.status(422).send({ success: false, message: "Missing parameter: days (number)" - }) + }); } - const allowedYAxisDataType = ['plays', 'duration']; + 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 }]` - }) + message: `Y axis parameter must be one of values: [${allowedYAxisDataType}]` + }); } - tautulli.getPlaysByDays(user.plex_userid, days, y_axis) - .then(data => res.send({ + return tautulli + .getPlaysByDays(user.plex_userid, days, y_axis) + .then(data => + res.send({ success: true, data: data.response.data - })) + }) + ) + .catch(error => handleError(error, res)); } - function userViewHistoryController(req, res) { - const user = req.loggedInUser; + const user = req.loggedInUser; - console.log('user', user) + // TODO here we should check if we can init tau + // and then return 501 Not implemented + return tautulli + .viewHistory(user.plex_userid) + .then(data => { + return res.send({ + success: true, + data: data.response.data.data, + message: "view history successfully fetched from tautulli" + }); + }) + .catch(error => handleError(error, res)); - // 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; + // const username = user.username; } module.exports = { diff --git a/seasoned_api/src/webserver/middleware/mustBeAuthenticated.js b/seasoned_api/src/webserver/middleware/mustBeAuthenticated.js index e1153ce..6af925b 100644 --- a/seasoned_api/src/webserver/middleware/mustBeAuthenticated.js +++ b/seasoned_api/src/webserver/middleware/mustBeAuthenticated.js @@ -1,11 +1,11 @@ const mustBeAuthenticated = (req, res, next) => { - if (req.loggedInUser === undefined) { - return res.status(401).send({ - success: false, - message: 'You must be logged in.', - }); - } - return next(); + if (req.loggedInUser === undefined) { + return res.status(401).send({ + success: false, + message: "You must be logged in." + }); + } + return next(); }; module.exports = mustBeAuthenticated;