2 Commits

21 changed files with 505 additions and 607 deletions

View File

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

View File

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

View File

@@ -1,19 +1,11 @@
CREATE TABLE IF NOT EXISTS user (
user_name varchar(127) UNIQUE,
password varchar(127),
admin boolean DEFAULT 0,
email varchar(127) UNIQUE,
admin boolean DEFAULT 0,
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,
@@ -36,13 +28,12 @@ CREATE TABLE IF NOT EXISTS requests(
year NUMBER,
poster_path TEXT DEFAULT NULL,
background_path TEXT DEFAULT NULL,
requested_by varchar(127) DEFAULT NULL,
requested_by TEXT,
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',
foreign key(requested_by) REFERENCES user(user_name) ON DELETE SET NULL
type CHAR(50) DEFAULT 'movie'
);
CREATE TABLE IF NOT EXISTS request(

View File

@@ -1,5 +1,4 @@
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,7 +6,6 @@ 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, username) {
requestFromTmdb(tmdb, ip, user_agent, user) {
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, 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');
});
.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');
});
}
/**

View File

@@ -28,23 +28,17 @@ class SearchHistory {
/**
* Creates a new search entry in the database.
* @param {String} username logged in user doing the search
* @param {User} user a new user
* @param {String} searchQuery the query the user searched for
* @returns {Promise}
*/
create(username, searchQuery) {
return this.database.run(this.queries.create, [searchQuery, username])
.catch(error => {
create(user, searchQuery) {
return Promise.resolve()
.then(() => this.database.run(this.queries.create, [searchQuery, user]))
.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

@@ -1,58 +0,0 @@
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,218 +2,64 @@ 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 = ?'
};
}
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 = ?'
};
}
/**
* 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'
})
/**
* 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)
});
}
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'
})
})
})
}
/**
* 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.'); });
}
/**
* 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
/**
* 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]));
}
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)
checkAdmin(user) {
return this.database.get(this.queries.getAdminStateByUser, user.username).then((row) => {
return row.admin;
});
}
}
module.exports = UserRepository;

View File

@@ -1,16 +1,13 @@
const express = require('express');
// const reload = require('express-reload');
const Raven = require('raven');
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();
@@ -27,7 +24,7 @@ const allowedOrigins = ['https://kevinmidboe.com', 'http://localhost:8080'];
app.use(bodyParser.urlencoded({ extended: true }));
/* Decode the Authorization header if provided */
router.use(tokenToUser);
app.use(tokenToUser);
// TODO: Should have a separate middleware/router for handling headers.
router.use((req, res, next) => {
@@ -52,22 +49,39 @@ app.use(function onError(err, req, res, next) {
res.end(res.sentry + '\n');
});
/**
* GraphQL
*/
var graphqlHTTP = require('express-graphql');
var { buildSchema } = require('graphql');
// var schema = buildSchema(`
// type Query {
// hello: String
// }`);
const schema = require('./graphql/requests.js');
const roots = { hello: () => 'Hello world!' };
app.use('/graphql', graphqlHTTP({
schema: schema.schema,
graphiql: true
}))
/**
* User
*/
router.post('/v1/user', require('./controllers/user/register.js'));
router.post('/v1/user/login', require('./controllers/user/login.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/history', mustBeAuthenticated, require('./controllers/user/history.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.username, query)
searchHistory.create(user, query)
}
return tmdb.multiSearch(query, page)

View File

@@ -1,87 +0,0 @@
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,9 +8,6 @@ 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

@@ -1,42 +0,0 @@
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

@@ -1,105 +0,0 @@
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

@@ -0,0 +1,294 @@
const graphql = require("graphql");
const establishedDatabase = require('src/database/database');
const fetch = require('node-fetch');
const TorrentType = new graphql.GraphQLObjectType({
name: "Torrent",
fields: {
magnet: { type: graphql.GraphQLString },
torrent_name: { type: graphql.GraphQLString},
tmdb_id: { type: graphql.GraphQLString }
}
});
const RequestType = new graphql.GraphQLObjectType({
name: "Request",
fields: {
id: { type: graphql.GraphQLID },
title: { type: graphql.GraphQLString },
year: { type: graphql.GraphQLInt},
poster_path: { type: graphql.GraphQLString },
background_path: { type: graphql.GraphQLString },
requested_by: { type: graphql.GraphQLString },
ip: { type: graphql.GraphQLString },
date: { type: graphql.GraphQLString },
status: { type: graphql.GraphQLString },
user_agent: { type: graphql.GraphQLString },
type: { type: graphql.GraphQLString }
}
});
const RequestsType = new graphql.GraphQLObjectType({
name: 'Requests',
type: graphql.GraphQLList(RequestType),
resolve: (root, args, context, info) => {
return establishedDatabase.all("SELECT * FROM requests;")
.catch(error => console.error("something went wrong fetching 'all' query. Error:", error))
}
})
const ProgressType = new graphql.GraphQLObjectType({
name: 'TorrentProgress',
fields: {
eta: { type: graphql.GraphQLInt },
finished: { type: graphql.GraphQLBoolean },
key: { type: graphql.GraphQLString },
name: { type: graphql.GraphQLString },
progress: { type: graphql.GraphQLFloat },
state: { type: graphql.GraphQLString },
Torrent: {
type: TorrentType,
resolve(parent) {
console.log('prante: ', parent.name)
console.log(parent.name.slice(0,10))
return establishedDatabase.get(`select magnet, torrent_name, tmdb_id from requested_torrent where torrent_name like (?);`, [parent.name.slice(0, 10) + '%'])
}
},
Requested: {
type: graphql.GraphQLList(RequestType),
// resolve: () => fetch('https://api.kevinmidboe.com/api/v2/request?page=1/').then(resp => resp.json())
// .then(data => {
// // console.log('data', data)
// return data.results
// })
resolve: (parent) => {
return establishedDatabase.all("SELECT * FROM requests;")
}
}
}
})
const TorrentsRequestedType = new graphql.GraphQLObjectType({
name: 'TorrentsRequested',
fields: {
magnet: { type: graphql.GraphQLString },
torrent_name: { type: graphql.GraphQLString },
tmdb_id: { type: graphql.GraphQLString },
date_added: { type: graphql.GraphQLString },
Request: {
type: RequestType,
// resolve: () => fetch('https://api.kevinmidboe.com/api/v2/request?page=1/').then(resp => resp.json())
// .then(data => {
// return data.results
// })
resolve(parentValue, args) {
return establishedDatabase.get('select * from requests where id = (?);', [parentValue.tmdb_id])
}
},
Progress: {
type: ProgressType,
resolve(parentValue, args) {
return fetch('http://localhost:5000/')
.then(resp => resp.json())
// .then(data => { console.log('data', data); return data.filter(download => download.name === parentValue.torrent_name) })
}
}
}
})
// create a graphql query to select all and by id
var queryType = new graphql.GraphQLObjectType({
name: 'Query',
fields: {
//first query to select all
Requests: {
type: graphql.GraphQLList(RequestType),
resolve: (root, args, context, info) => {
return establishedDatabase.all("SELECT * FROM requests;")
.catch(error => console.error("something went wrong fetching 'all' query. Error:", error))
}
},
Progress: {
type: graphql.GraphQLList(ProgressType),
resolve: (root, args, context, info) => {
console.log('user', context.loggedInUser)
return fetch('http://localhost:5000')
.then(resp => resp.json())
}
},
ProgressRequested: {
type: graphql.GraphQLList(ProgressType),
resolve: (root, args, context, info) => {
console.log('root & args', root, args)
}
},
TorrentsRequested: {
type: graphql.GraphQLList(TorrentsRequestedType),
resolve: (root, args, context, info) => {
return establishedDatabase.all("SELECT * FROM requested_torrent;")
.then(data => data.filter(request => { if (request.tmdb_id === '83666') { console.log('request', request, root);}; return request }))
.catch(error => console.error("something went wrong fetching 'all' query. Error:", error))
}
},
DownloadingRequestByName: {
type: RequestType,
args:{
name:{
type: new graphql.GraphQLNonNull(graphql.GraphQLString)
}
},
resolve: (root, { name }, context, info) => {
return establishedDatabase.all("SELECT * FROM requests where requested_by = (?);", [name])
.catch(error => console.error("something went wrong fetching 'all' query. Error:", error))
}
},
//second query to select by id
Request:{
type: RequestType,
args:{
id:{
type: new graphql.GraphQLNonNull(graphql.GraphQLID)
}
},
resolve: (root, {id}, context, info) => {
return establishedDatabase.get("SELECT * FROM requests WHERE id = (?);",[id])
.catch(error => console.error(`something went wrong fetching by id: '${ id }'. Error: ${ error }`))
}
},
Torrents: {
type: graphql.GraphQLList(TorrentType),
resolve: (root, {id}, context, info) => {
// console.log('parent', parent)
return establishedDatabase.all("SELECT * FROM requested_torrent")
.catch(error => console.error(`something went wrong fetching all torrents. Error: ${ error }`))
}
},
Torrent: {
type: TorrentType,
args: {
id: {
type: new graphql.GraphQLNonNull(graphql.GraphQLID)
}
},
resolve: (parent, {id}, context, info) => {
// console.log('searcing from parent', parent)
return establishedDatabase.get("SELECT * FROM requested_torrent WHERE tmdb_id = (?);", [id])
}
}
}
});
//mutation type is a type of object to modify data (INSERT,DELETE,UPDATE)
// var mutationType = new graphql.GraphQLObjectType({
// name: 'Mutation',
// fields: {
// //mutation for creacte
// createPost: {
// //type of object to return after create in SQLite
// type: PostType,
// //argument of mutation creactePost to get from request
// args: {
// title: {
// type: new graphql.GraphQLNonNull(graphql.GraphQLString)
// },
// description:{
// type: new graphql.GraphQLNonNull(graphql.GraphQLString)
// },
// createDate:{
// type: new graphql.GraphQLNonNull(graphql.GraphQLString)
// },
// author:{
// type: new graphql.GraphQLNonNull(graphql.GraphQLString)
// }
// },
// resolve: (root, {title, description, createDate, author}) => {
// return new Promise((resolve, reject) => {
// //raw SQLite to insert a new post in post table
// database.run('INSERT INTO Posts (title, description, createDate, author) VALUES (?,?,?,?);', [title, description, createDate, author], (err) => {
// if(err) {
// reject(null);
// }
// database.get("SELECT last_insert_rowid() as id", (err, row) => {
// resolve({
// id: row["id"],
// title: title,
// description: description,
// createDate:createDate,
// author: author
// });
// });
// });
// })
// }
// },
// //mutation for update
// updatePost: {
// //type of object to return afater update in SQLite
// type: graphql.GraphQLString,
// //argument of mutation creactePost to get from request
// args:{
// id:{
// type: new graphql.GraphQLNonNull(graphql.GraphQLID)
// },
// title: {
// type: new graphql.GraphQLNonNull(graphql.GraphQLString)
// },
// description:{
// type: new graphql.GraphQLNonNull(graphql.GraphQLString)
// },
// createDate:{
// type: new graphql.GraphQLNonNull(graphql.GraphQLString)
// },
// author:{
// type: new graphql.GraphQLNonNull(graphql.GraphQLString)
// }
// },
// resolve: (root, {id, title, description, createDate, author}) => {
// return new Promise((resolve, reject) => {
// //raw SQLite to update a post in post table
// database.run('UPDATE Posts SET title = (?), description = (?), createDate = (?), author = (?) WHERE id = (?);', [title, description, createDate, author, id], (err) => {
// if(err) {
// reject(err);
// }
// resolve(`Post #${id} updated`);
// });
// })
// }
// },
// //mutation for update
// deletePost: {
// //type of object resturn after delete in SQLite
// type: graphql.GraphQLString,
// args:{
// id:{
// type: new graphql.GraphQLNonNull(graphql.GraphQLID)
// }
// },
// resolve: (root, {id}) => {
// return new Promise((resolve, reject) => {
// //raw query to delete from post table by id
// database.run('DELETE from Posts WHERE id =(?);', [id], (err) => {
// if(err) {
// reject(err);
// }
// resolve(`Post #${id} deleted`);
// });
// })
// }
// }
// }
// });
//define schema with post object, queries, and mustation
const schema = new graphql.GraphQLSchema({
query: queryType,
// mutation: mutationType
});
//export schema to use on index.js
module.exports = {
schema
}

View File

@@ -6,7 +6,7 @@ const mustBeAdmin = (req, res, next) => {
if (req.loggedInUser === undefined) {
return res.status(401).send({
success: false,
message: 'You must be logged in.',
error: '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,
message: 'You must be logged in as a admin.'
error: '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,
message: 'You must be logged in.',
error: 'You must be logged in.',
});
}
return next();

View File

@@ -1,31 +0,0 @@
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

@@ -989,7 +989,7 @@ abbrev@1.0.x:
resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.0.9.tgz#91b4792588a7738c25f35dd6f63752a2f8776135"
integrity sha1-kbR5JYinc4wl813W9jdSovh3YTU=
accepts@~1.3.5:
accepts@^1.3.7, accepts@~1.3.5:
version "1.3.7"
resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.7.tgz#531bc726517a3b2b41f850021c6cc15eaab507cd"
integrity sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==
@@ -1086,6 +1086,14 @@ ansi-styles@^3.2.0, ansi-styles@^3.2.1:
dependencies:
color-convert "^1.9.0"
anymatch@^1.3.0:
version "1.3.2"
resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-1.3.2.tgz#553dcb8f91e3c889845dfdba34c77721b90b9d7a"
integrity sha512-0XNayC8lTHQ2OI8aljNCN3sSx6hsr/1+rlcDAotXJR7C1oZZHCNsfpbKwMjRA3Uqb5tF1Rae2oloTr4xpq+WjA==
dependencies:
micromatch "^2.1.5"
normalize-path "^2.0.0"
anymatch@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-2.0.0.tgz#bcb24b4f37934d9aa7ac17b4adaf89e7c76ef2eb"
@@ -1200,7 +1208,7 @@ assign-symbols@^1.0.0:
resolved "https://registry.yarnpkg.com/assign-symbols/-/assign-symbols-1.0.0.tgz#59667f41fadd4f20ccbc2bb96b8d4f7f78ec0367"
integrity sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=
async-each@^1.0.1:
async-each@^1.0.0, async-each@^1.0.1:
version "1.0.3"
resolved "https://registry.yarnpkg.com/async-each/-/async-each-1.0.3.tgz#b727dbf87d7651602f06f4d4ac387f47d91b0cbf"
integrity sha512-z/WhQ5FPySLdvREByI2vZiTWwCnF0moMJ1hK9YQwDTHKh6I7/uSckMetoRGb5UBZPC1z0jlw+n/XCgjeH7y1AQ==
@@ -1487,6 +1495,11 @@ bytes@3.0.0:
resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.0.0.tgz#d32815404d689699f85a4ea4fa8755dd13a96048"
integrity sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg=
bytes@3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.0.tgz#f6cf7933a360e0588fa9fde85651cdc7f805d1f6"
integrity sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==
cache-base@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/cache-base/-/cache-base-1.0.1.tgz#0a7f46416831c8b662ee36fe4e7c59d76f666ab2"
@@ -1603,6 +1616,22 @@ charenc@~0.0.1:
resolved "https://registry.yarnpkg.com/charenc/-/charenc-0.0.2.tgz#c0a1d2f3a7092e03774bfa83f14c0fc5790a8667"
integrity sha1-wKHS86cJLgN3S/qD8UwPxXkKhmc=
chokidar@^1.7.0:
version "1.7.0"
resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-1.7.0.tgz#798e689778151c8076b4b360e5edd28cda2bb468"
integrity sha1-eY5ol3gVHIB2tLNg5e3SjNortGg=
dependencies:
anymatch "^1.3.0"
async-each "^1.0.0"
glob-parent "^2.0.0"
inherits "^2.0.1"
is-binary-path "^1.0.0"
is-glob "^2.0.0"
path-is-absolute "^1.0.0"
readdirp "^2.0.0"
optionalDependencies:
fsevents "^1.0.0"
chokidar@^2.0.4:
version "2.1.8"
resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-2.1.8.tgz#804b3a7b6a99358c3c5c61e71d8728f041cff917"
@@ -1797,7 +1826,7 @@ content-disposition@0.5.2:
resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.2.tgz#0cf68bb9ddf5f2be7961c3a85178cb85dba78cb4"
integrity sha1-DPaLud318r55YcOoUXjLhdunjLQ=
content-type@~1.0.4:
content-type@^1.0.4, content-type@~1.0.4:
version "1.0.4"
resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b"
integrity sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==
@@ -2503,6 +2532,24 @@ expand-range@^1.8.1:
dependencies:
fill-range "^2.1.0"
express-graphql@^0.9.0:
version "0.9.0"
resolved "https://registry.yarnpkg.com/express-graphql/-/express-graphql-0.9.0.tgz#00fd8552f866bac5c9a4612b2c4c82076107b3c2"
integrity sha512-wccd9Lb6oeJ8yHpUs/8LcnGjFUUQYmOG9A5BNLybRdCzGw0PeUrtBxsIR8bfiur6uSW4OvPkVDoYH06z6/N9+w==
dependencies:
accepts "^1.3.7"
content-type "^1.0.4"
http-errors "^1.7.3"
raw-body "^2.4.1"
express-reload@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/express-reload/-/express-reload-1.2.0.tgz#3c59734bc7508732e71e1d4e78cf116d582b676f"
integrity sha512-WS2xq7kOtspghADAzUSFMfHrqTtooXLeIg4Nxni5w/Qw5eOa5zic+T6glkWIs4oMCnnOq9d4k99g+bcbr+Z9bw==
dependencies:
chokidar "^1.7.0"
debug "^2.6.8"
express@~4.16.0:
version "4.16.4"
resolved "https://registry.yarnpkg.com/express/-/express-4.16.4.tgz#fddef61926109e24c515ea97fd2f1bdbf62df12e"
@@ -2777,7 +2824,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.5.1:
form-data@^2.3.1:
version "2.5.1"
resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.5.1.tgz#f2cbec57b5e59e23716e128fe44d4e5dd23895f4"
integrity sha512-m21N3WOmEEURgk6B9GLOE4RuWOFf28Lhh9qGYeNlGq4VDXUlJy2th2slBNU8Gp8EzloYZOibZJ7t5ecIrFSjVA==
@@ -2837,7 +2884,7 @@ fs.realpath@^1.0.0:
resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f"
integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8=
fsevents@^1.2.7:
fsevents@^1.0.0, fsevents@^1.2.7:
version "1.2.9"
resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-1.2.9.tgz#3f5ed66583ccd6f400b5a00db6f7e861363e388f"
integrity sha512-oeyj2H3EjjonWcFjD5NvZNE9Rqe4UW+nQBU2HNeKw0koVLEFIhtyETyAakeAM3de7Z/SW5kcA+fZUait9EApnw==
@@ -3031,6 +3078,13 @@ graceful-fs@^4.0.0, graceful-fs@^4.1.11, graceful-fs@^4.1.2, graceful-fs@^4.1.6:
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.3.tgz#4a12ff1b60376ef09862c2093edd908328be8423"
integrity sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ==
graphql@^14.5.8:
version "14.5.8"
resolved "https://registry.yarnpkg.com/graphql/-/graphql-14.5.8.tgz#504f3d3114cb9a0a3f359bbbcf38d9e5bf6a6b3c"
integrity sha512-MMwmi0zlVLQKLdGiMfWkgQD7dY/TUKt4L+zgJ/aR0Howebod3aNgP5JkgvAULiR2HPVZaP2VEElqtdidHweLkg==
dependencies:
iterall "^1.2.2"
growl@1.10.5, "growl@~> 1.10.0":
version "1.10.5"
resolved "https://registry.yarnpkg.com/growl/-/growl-1.10.5.tgz#f2735dc2283674fa67478b10181059355c369e5e"
@@ -3195,6 +3249,17 @@ http-errors@1.6.3, http-errors@~1.6.2, http-errors@~1.6.3:
setprototypeof "1.1.0"
statuses ">= 1.4.0 < 2"
http-errors@1.7.3, http-errors@^1.7.3:
version "1.7.3"
resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.7.3.tgz#6c619e4f9c60308c38519498c14fbb10aacebb06"
integrity sha512-ZTTX0MWrsQ2ZAhA1cejAwDLycFsd7I7nVtnkT3Ol0aqodaKW+0CTZDQ1uBv5whptCnc8e8HeRRJxRs0kmm/Qfw==
dependencies:
depd "~1.1.2"
inherits "2.0.4"
setprototypeof "1.1.1"
statuses ">= 1.5.0 < 2"
toidentifier "1.0.0"
"http-parser-js@>=0.4.0 <0.4.11":
version "0.4.10"
resolved "https://registry.yarnpkg.com/http-parser-js/-/http-parser-js-0.4.10.tgz#92c9c1374c35085f75db359ec56cc257cbb93fa4"
@@ -3216,7 +3281,7 @@ iconv-lite@0.4.23:
dependencies:
safer-buffer ">= 2.1.2 < 3"
iconv-lite@^0.4.17, iconv-lite@^0.4.4:
iconv-lite@0.4.24, iconv-lite@^0.4.17, iconv-lite@^0.4.4:
version "0.4.24"
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b"
integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==
@@ -3248,7 +3313,7 @@ inflight@^1.0.4:
once "^1.3.0"
wrappy "1"
inherits@2, inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.1, inherits@~2.0.3:
inherits@2, inherits@2.0.4, inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.1, inherits@~2.0.3:
version "2.0.4"
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
@@ -3724,6 +3789,11 @@ istanbul@^0.4.5:
which "^1.1.1"
wordwrap "^1.0.0"
iterall@^1.2.2:
version "1.2.2"
resolved "https://registry.yarnpkg.com/iterall/-/iterall-1.2.2.tgz#92d70deb8028e0c39ff3164fdbf4d8b088130cd7"
integrity sha512-yynBb1g+RFUPY64fTrFv7nsjRrENBQJaX2UL+2Szc9REFrSNm1rpSXHGzhmAy7a9uv3vlvgBlXnf9RqmPH1/DA==
js-levenshtein@^1.1.3:
version "1.1.6"
resolved "https://registry.yarnpkg.com/js-levenshtein/-/js-levenshtein-1.1.6.tgz#c6cee58eb3550372df8deb85fad5ce66ce01d59d"
@@ -3809,7 +3879,7 @@ jsonparse@^1.2.0:
resolved "https://registry.yarnpkg.com/jsonparse/-/jsonparse-1.3.1.tgz#3f4dae4a91fac315f71062f8521cc239f1366280"
integrity sha1-P02uSpH6wxX3EGL4UhzCOfE2YoA=
jsonwebtoken@^8.0.1:
jsonwebtoken@^8.2.0:
version "8.5.1"
resolved "https://registry.yarnpkg.com/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz#00e71e0b8df54c2121a1f26137df2280673bcc0d"
integrity sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w==
@@ -4207,7 +4277,7 @@ methods@^1.1.1, methods@^1.1.2, methods@~1.1.2:
resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee"
integrity sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=
micromatch@^2.3.11:
micromatch@^2.1.5, micromatch@^2.3.11:
version "2.3.11"
resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-2.3.11.tgz#86677c97d1720b363431d04d0d15293bd38c1565"
integrity sha1-hmd8l9FyCzY0MdBNDRUpO9OMFWU=
@@ -4562,7 +4632,7 @@ normalize-package-data@^2.3.2:
semver "2 || 3 || 4 || 5"
validate-npm-package-license "^3.0.1"
normalize-path@^2.0.1, normalize-path@^2.1.1:
normalize-path@^2.0.0, normalize-path@^2.0.1, normalize-path@^2.1.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-2.1.1.tgz#1ab28b556e198363a8c1a6f7e6fa20137fe6aed9"
integrity sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=
@@ -5259,6 +5329,16 @@ raw-body@2.3.3:
iconv-lite "0.4.23"
unpipe "1.0.0"
raw-body@^2.4.1:
version "2.4.1"
resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.4.1.tgz#30ac82f98bb5ae8c152e67149dac8d55153b168c"
integrity sha512-9WmIKF6mkvA0SLmA2Knm9+qj89e+j1zqgyn8aXGd7+nAduPoqgI9lO57SAZNn/Byzo5P7JhXTyg9PzaJbH73bA==
dependencies:
bytes "3.1.0"
http-errors "1.7.3"
iconv-lite "0.4.24"
unpipe "1.0.0"
raw-body@~1.1.0:
version "1.1.7"
resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-1.1.7.tgz#1d027c2bfa116acc6623bca8f00016572a87d425"
@@ -5366,7 +5446,7 @@ readable-stream@~2.1.0:
string_decoder "~0.10.x"
util-deprecate "~1.0.1"
readdirp@^2.2.1:
readdirp@^2.0.0, readdirp@^2.2.1:
version "2.2.1"
resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-2.2.1.tgz#0e87622a3325aa33e892285caf8b4e846529a525"
integrity sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ==
@@ -5593,7 +5673,7 @@ request-promise@^4.2:
stealthy-require "^1.1.1"
tough-cookie "^2.3.3"
request@^2.85.0, request@^2.86.0, request@^2.87.0:
request@^2.86.0, request@^2.87.0:
version "2.88.0"
resolved "https://registry.yarnpkg.com/request/-/request-2.88.0.tgz#9c2fca4f7d35b592efe57c7f0a55e81052124fef"
integrity sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==
@@ -5813,6 +5893,11 @@ setprototypeof@1.1.0:
resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.0.tgz#d0bd85536887b6fe7c0d818cb962d9d91c54e656"
integrity sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==
setprototypeof@1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.1.tgz#7e95acb24aa92f5885e0abef5ba131330d4ae683"
integrity sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==
shebang-command@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea"
@@ -6017,7 +6102,7 @@ static-extend@^0.1.1:
define-property "^0.2.5"
object-copy "^0.1.0"
"statuses@>= 1.4.0 < 2":
"statuses@>= 1.4.0 < 2", "statuses@>= 1.5.0 < 2":
version "1.5.0"
resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c"
integrity sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=
@@ -6402,6 +6487,11 @@ to-through@^2.0.0:
dependencies:
through2 "^2.0.3"
toidentifier@1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.0.tgz#7e1be3470f1e77948bc43d94a3c8f4d7752ba553"
integrity sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==
tough-cookie@^2.3.3:
version "2.5.0"
resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.5.0.tgz#cd9fb2a0aa1d5a12b473bd9fb96fa3dcff65ade2"