From 362e5f54d10f0776180c134ba0a94169cb7c58fb Mon Sep 17 00:00:00 2001 From: KevinMidboe Date: Tue, 28 Nov 2017 20:58:45 +0100 Subject: [PATCH 01/66] Added react-interactive package to make it easier to handle hover and focus on mobile. --- client/package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/client/package.json b/client/package.json index 7d36795..f166ff8 100644 --- a/client/package.json +++ b/client/package.json @@ -19,6 +19,7 @@ "react-burger-menu": "^2.1.6", "react-dom": "^15.5.4", "react-infinite-scroller": "^1.0.15", + "react-interactive": "^0.8.1", "react-notify-toast": "^0.3.2", "react-redux": "^5.0.6", "react-responsive": "^1.3.4", From 62b6f5c8ca6935a87dc4a595434c3114e804cda9 Mon Sep 17 00:00:00 2001 From: KevinMidboe Date: Tue, 28 Nov 2017 21:02:25 +0100 Subject: [PATCH 02/66] Using react-interactive we now have hover animations for our buttons. --- client/app/components/MovieObject.jsx | 42 ++++++++++++++++--- .../components/styles/movieObjectStyle.jsx | 15 ++++++- 2 files changed, 50 insertions(+), 7 deletions(-) diff --git a/client/app/components/MovieObject.jsx b/client/app/components/MovieObject.jsx index 64ea04c..d78747e 100644 --- a/client/app/components/MovieObject.jsx +++ b/client/app/components/MovieObject.jsx @@ -11,6 +11,9 @@ import RequestButton from './buttons/request_button.jsx'; import { fetchJSON } from './http.jsx'; +import Interactive from 'react-interactive'; + + class MovieObject { constructor(object) { this.id = object.id; @@ -42,6 +45,13 @@ class MovieObject { notify.show(this.title + ' requested!', 'success', 3000); } + invertButtonColors(event) { + const event_type = event.type; + if (event_type) + console.log('test') + console.log(event.type) + } + getElement(index) { const element_key = index + this.id; @@ -55,11 +65,26 @@ class MovieObject { var foundInPlex; if (this.matchedInPlex) { - foundInPlex = ; + foundInPlex = {this.requestExisting(this)}} + style={movieStyle.requestButton}> + + Request Anyway + ; } else { - foundInPlex = ; + foundInPlex = {this.requestMovie()}} + onMouseOver={() => {this.invertButtonColors(event)}} + style={movieStyle.requestButton}> + + + Request + ; } if (this.type === 'movie') @@ -105,7 +130,14 @@ class MovieObject {
{foundInPlex} - + + + Info +
diff --git a/client/app/components/styles/movieObjectStyle.jsx b/client/app/components/styles/movieObjectStyle.jsx index 627d7dd..c16a89f 100644 --- a/client/app/components/styles/movieObjectStyle.jsx +++ b/client/app/components/styles/movieObjectStyle.jsx @@ -62,8 +62,9 @@ export default { requestButton: { color: '#e9a131', marginRight: '10px', - background: 'white', + backgroundColor: 'white', border: '#e9a131 2px solid', + borderColor: '#e9a131', borderRadius: '4px', textAlign: 'center', padding: '10px', @@ -74,10 +75,15 @@ export default { cursor: 'pointer' }, + requestButton_hover: { + backgroundColor: '#e9a131', + color: 'white', + }, + tmdbButton: { color: '#00d17c', marginRight: '10px', - background: 'white', + backgroundColor: 'white', border: '#00d17c 2px solid', borderRadius: '4px', textAlign: 'center', @@ -89,6 +95,11 @@ export default { cursor: 'pointer' }, + tmdbButton_hover: { + backgroundColor: '#00d17c', + color: 'white', + }, + row: { width: '100%' }, From 57658f10c148c6cddc84bdd209ea4976aeff4106 Mon Sep 17 00:00:00 2001 From: KevinMidboe Date: Tue, 28 Nov 2017 22:25:06 +0100 Subject: [PATCH 03/66] Used the wrong shows.db. Now corrected. --- .gitignore | 1 + seasoned_api/.gitignore | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 53ffa91..e85cd5a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ .DS_Store env +shows.db yarn.lock */yarn.lock diff --git a/seasoned_api/.gitignore b/seasoned_api/.gitignore index 922c3ee..9f7246b 100644 --- a/seasoned_api/.gitignore +++ b/seasoned_api/.gitignore @@ -60,5 +60,4 @@ typings/ # - - - - - # My own gitignore files and folders -shows.db conf From 2b772e30170a594d81ee9c7648b1bb618fc40fc0 Mon Sep 17 00:00:00 2001 From: KevinMidboe Date: Fri, 1 Dec 2017 10:14:22 +0100 Subject: [PATCH 04/66] Long overdue, now we define our database schema so we can finally build our database, and not just move it around. --- seasoned_api/src/database/schema/setup.sql | 57 ++++++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 seasoned_api/src/database/schema/setup.sql diff --git a/seasoned_api/src/database/schema/setup.sql b/seasoned_api/src/database/schema/setup.sql new file mode 100644 index 0000000..5d9d8fd --- /dev/null +++ b/seasoned_api/src/database/schema/setup.sql @@ -0,0 +1,57 @@ +CREATE TABLE IF NOT EXISTS user ( + user_name varchar(127) UNIQUE, + password varchar(127), + email varchar(127) UNIQUE, + primary key (user_name) +); + +CREATE TABLE IF NOT EXISTS cache ( + key varchar(255), + value blob, + time_to_live INTEGER DEFAULT 60, + created_at DATE DEFAULT (datetime('now','localtime')), + primary key(key) +); + +CREATE TABLE IF NOT EXISTS search_history ( + id integer, + user_name varchar(127), + search_query varchar(255), + primary key (id), + foreign key(user_name) REFERENCES user(user_name) ON DELETE CASCADE +); + +CREATE TABLE IF NOT EXISTS requests( + id TEXT, + name TEXT, + year NUMBER, + poster_path TEXT DEFAULT NULL, + background_path TEXT DEFAULT NULL, + requested_by TEXT, + ip TEXT, + requested_date DATE DEFAULT CURRENT_DATE NOT NULL, + status CHAR(25) DEFAULT 'requested' NOT NULL, + user_agent CHAR(255) DEFAULT NULL, + type CHAR(50) DEFAULT 'movie' +); + + +CREATE TABLE IF NOT EXISTS stray_eps( + id TEXT UNIQUE, + parent TEXT, + path TEXT, + name TEXT, + season NUMBER, + episode NUMBER, + video_files TEXT, + subtitles TEXT, + trash TEXT, + verified BOOLEAN DEFAULT 0, + primary key(id) +); + +CREATE TABLE IF NOT EXISTS shows( + show_names TEXT, + date_added DATE, + date_modified DATE DEFUALT CURRENT_DATE NOT NULL +); \ No newline at end of file From ac6bbe6ac66d752fa204ba0a42a76e155dfae117 Mon Sep 17 00:00:00 2001 From: KevinMidboe Date: Fri, 1 Dec 2017 10:19:22 +0100 Subject: [PATCH 05/66] Now we can build or database by reading schema files. Also added doc strings to all the functions. This is a much better definition of a database script for our usecases.. --- seasoned_api/src/database/sqliteDatabase.js | 88 ++++++++++++++++----- 1 file changed, 69 insertions(+), 19 deletions(-) diff --git a/seasoned_api/src/database/sqliteDatabase.js b/seasoned_api/src/database/sqliteDatabase.js index 3d0c5a4..5ac1330 100644 --- a/seasoned_api/src/database/sqliteDatabase.js +++ b/seasoned_api/src/database/sqliteDatabase.js @@ -4,30 +4,80 @@ const sqlite = require('sqlite'); class SqliteDatabase { - constructor(host) { - this.host = host; - this.connection = sqlite; + constructor(host) { + this.host = host; + this.connection = sqlite; + this.schemaDirectory = path.join(__dirname, 'schemas'); + } - // this.schemaDirectory = path.join(__dirname, 'schemas'); - } + /** + * Connect to the database. + * @returns {Promise} succeeds if connection was established + */ + connect() { + return Promise.resolve() + .then(() => sqlite.open(this.host)) + .then(() => sqlite.exec('pragma foreign_keys = on;')); + } - connect() { - return Promise.resolve() - .then(() => sqlite.open(this.host)) - .then(() => sqlite.exec('pragma foreign_keys = on;')); - } + /** + * Run a SQL query against the database. + * @param {String} sql SQL query + * @param {Array} parameters in the SQL query + * @returns {Promise} + */ + run(sql, parameters) { + return this.connection.run(sql, parameters); + } - all(sql, parameters) { - return this.connection.all(sql, parameters); - } + /** + * Run a SQL query against the database and retrieve all the rows. + * @param {String} sql SQL query + * @param {Array} parameters in the SQL query + * @returns {Promise} + */ + all(sql, parameters) { + return this.connection.all(sql, parameters); + } - get(sql, parameters) { - return this.connection.get(sql, parameters); - } + /** + * Run a SQL query against the database and retrieve one row. + * @param {String} sql SQL query + * @param {Array} parameters in the SQL query + * @returns {Promise} + */ + get(sql, parameters) { + return this.connection.get(sql, parameters); + } - run(sql, parameters) { - return this.connection.run(sql, parameters); - } + /** + * Run a SQL query against the database and retrieve the status. + * @param {String} sql SQL query + * @param {Array} parameters in the SQL query + * @returns {Promise} + */ + execute(sql) { + return this.connection.exec(sql); + } + + /** + * Setup the database by running setup.sql file in schemas/. + * @returns {Promise} + */ + setUp() { + const setupSchema = this.readSqlFile('setup.sql'); + return this.execute(setupSchema); + } + + /** + * Returns the file contents of a SQL file in schemas/. + * @returns {String} + */ + readSqlFile(filename) { + const schemaPath = path.join(this.schemaDirectory, filename); + const schema = fs.readFileSync(schemaPath).toString('utf-8'); + return schema; + } } module.exports = SqliteDatabase; From b8647f982d71d2f657795db23210f6840518c8f0 Mon Sep 17 00:00:00 2001 From: KevinMidboe Date: Fri, 1 Dec 2017 10:27:46 +0100 Subject: [PATCH 06/66] This is where we use our setup function to add tables if none are present in our database file. --- seasoned_api/src/database/database.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/seasoned_api/src/database/database.js b/seasoned_api/src/database/database.js index 00c9d98..8e509df 100644 --- a/seasoned_api/src/database/database.js +++ b/seasoned_api/src/database/database.js @@ -10,6 +10,6 @@ const database = new SqliteDatabase(configuration.get('database', 'host')); */ Promise.resolve() .then(() => database.connect()) -// .then(() => database.setUp()); +.then(() => database.setUp()); module.exports = database; From db11c968a3db67782adf14f4f3d90ed917baa844 Mon Sep 17 00:00:00 2001 From: KevinMidboe Date: Fri, 1 Dec 2017 10:28:29 +0100 Subject: [PATCH 07/66] Renamed folder the plural. --- seasoned_api/src/database/{schema => schemas}/setup.sql | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename seasoned_api/src/database/{schema => schemas}/setup.sql (100%) diff --git a/seasoned_api/src/database/schema/setup.sql b/seasoned_api/src/database/schemas/setup.sql similarity index 100% rename from seasoned_api/src/database/schema/setup.sql rename to seasoned_api/src/database/schemas/setup.sql From d0597b4e1e650d606710910d0b08c91d82eee929 Mon Sep 17 00:00:00 2001 From: KevinMidboe Date: Fri, 1 Dec 2017 10:34:09 +0100 Subject: [PATCH 08/66] This is our main focus on changing, this is where we add a background parameter in our database entry. --- seasoned_api/src/plex/requestRepository.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/seasoned_api/src/plex/requestRepository.js b/seasoned_api/src/plex/requestRepository.js index 94bfdc8..ffddd12 100644 --- a/seasoned_api/src/plex/requestRepository.js +++ b/seasoned_api/src/plex/requestRepository.js @@ -22,7 +22,7 @@ class RequestRepository { constructor(cache, database) { this.database = database || establishedDatabase; this.queries = { - 'insertRequest': "INSERT INTO requests VALUES (?, ?, ?, ?, ?, ?, CURRENT_DATE, 'requested', ?, ?)", + 'insertRequest': "INSERT INTO requests VALUES (?, ?, ?, ?, ?, ?, ?, CURRENT_DATE, 'requested', ?, ?)", 'fetchRequstedItems': "SELECT * FROM requests", 'updateRequestedById': "UPDATE requests SET status = ? WHERE id is ? AND type is ?", } @@ -117,7 +117,7 @@ class RequestRepository { user = 'NULL'; console.log(user) // Add request to database - this.database.run(this.queries.insertRequest, [movie.id, movie.title, movie.year, movie.poster, user, ip, user_agent, movie.type]) + this.database.run(this.queries.insertRequest, [movie.id, movie.title, movie.year, movie.poster, movie.background, user, ip, user_agent, movie.type]) // create reusable transporter object using the default SMTP transport From 8542da34cd4d41fd144177a38d39d1c1e2d2a067 Mon Sep 17 00:00:00 2001 From: KevinMidboe Date: Sat, 2 Dec 2017 10:37:31 +0100 Subject: [PATCH 09/66] Added a separate stylesheet for our buttons. --- client/app/components/styles/buttons.jsx | 80 ++++++++++++++++++++++++ 1 file changed, 80 insertions(+) create mode 100644 client/app/components/styles/buttons.jsx diff --git a/client/app/components/styles/buttons.jsx b/client/app/components/styles/buttons.jsx new file mode 100644 index 0000000..a27fdba --- /dev/null +++ b/client/app/components/styles/buttons.jsx @@ -0,0 +1,80 @@ + +export default { + + submit: { + color: '#e9a131', + marginRight: '10px', + backgroundColor: 'white', + border: '#e9a131 2px solid', + borderColor: '#e9a131', + borderRadius: '4px', + textAlign: 'center', + padding: '10px', + minWidth: '100px', + float: 'left', + fontSize: '13px', + fontWeight: '800', + cursor: 'pointer', + }, + + submit_hover: { + backgroundColor: '#e9a131', + color: 'white', + }, + + info: { + color: '#00d17c', + marginRight: '10px', + backgroundColor: 'white', + border: '#00d17c 2px solid', + borderRadius: '4px', + textAlign: 'center', + padding: '10px', + minWidth: '100px', + float: 'left', + fontSize: '13px', + fontWeight: '800', + cursor: 'pointer', + }, + + info_hover: { + backgroundColor: '#00d17c', + color: 'white', + }, + + edit: { + color: '#4a95da', + marginRight: '10px', + backgroundColor: 'white', + border: '#4a95da 2px solid', + borderRadius: '4px', + textAlign: 'center', + padding: '10px', + minWidth: '100px', + float: 'left', + fontSize: '13px', + fontWeight: '800', + cursor: 'pointer', + }, + + edit_small: { + color: '#4a95da', + marginRight: '10px', + backgroundColor: 'white', + border: '#4a95da 2px solid', + borderRadius: '4px', + textAlign: 'center', + padding: '4px', + minWidth: '50px', + float: 'left', + fontSize: '13px', + fontWeight: '800', + cursor: 'pointer', + }, + + edit_hover: { + backgroundColor: '#4a95da', + color: 'white', + }, + +} \ No newline at end of file From e1fed24fc02b4a8f9b91987ac81d686024b82485 Mon Sep 17 00:00:00 2001 From: KevinMidboe Date: Sat, 2 Dec 2017 10:39:18 +0100 Subject: [PATCH 10/66] Added a loading image to pirateSearch. Now we get feedback when waiting for response from the api. --- client/app/components/admin/PirateSearch.jsx | 32 +++++++++++++++--- client/app/components/images/loading.jsx | 34 ++++++++++++++++++++ client/app/components/images/loading.svg | 7 ++++ 3 files changed, 69 insertions(+), 4 deletions(-) create mode 100644 client/app/components/images/loading.jsx create mode 100644 client/app/components/images/loading.svg diff --git a/client/app/components/admin/PirateSearch.jsx b/client/app/components/admin/PirateSearch.jsx index b1d5191..25917fa 100644 --- a/client/app/components/admin/PirateSearch.jsx +++ b/client/app/components/admin/PirateSearch.jsx @@ -1,18 +1,25 @@ import React, { Component } from 'react'; import { fetchJSON } from '../http.jsx'; +// Stylesheets +import btnStylesheet from '../styles/buttons.jsx'; + +// Interactive button +import Interactive from 'react-interactive'; + +import Loading from '../images/loading.jsx' + class PirateSearch extends Component { constructor() { super(); this.state = { response: [], name: '', + loading: '', } } sendToDownload(torrent) { - console.log(torrent.magnet) - let data = {magnet: torrent.magnet} fetchJSON('https://apollo.kevinmidboe.com/api/v1/pirate/add', 'POST', data) .then((response) => { @@ -24,10 +31,15 @@ class PirateSearch extends Component { const query = this.props.name; const type = this.props.type; + this.setState({ + loading: + }) + fetchJSON('https://apollo.kevinmidboe.com/api/v1/pirate/search?query='+query+'&type='+type, 'GET') .then((response) => { console.log(response.torrents) this.setState({ + loading: '', response: response.torrents.map((torrent, index) => { return (
@@ -46,8 +58,20 @@ class PirateSearch extends Component { render() { return (
- {this.props.name} - +
+ {this.searchTheBay()}} + style={btnStylesheet.submit} + focus={btnStylesheet.submit_hover} + hover={btnStylesheet.submit_hover}> + + Search for torrents + +
+ + { this.state.loading } + {this.state.response}
) diff --git a/client/app/components/images/loading.jsx b/client/app/components/images/loading.jsx new file mode 100644 index 0000000..455e39c --- /dev/null +++ b/client/app/components/images/loading.jsx @@ -0,0 +1,34 @@ +import React from 'react'; + +function Loading() { + return ( +
+ + + + + + +
+ + ) +} + +export default Loading; \ No newline at end of file diff --git a/client/app/components/images/loading.svg b/client/app/components/images/loading.svg new file mode 100644 index 0000000..c8f7e85 --- /dev/null +++ b/client/app/components/images/loading.svg @@ -0,0 +1,7 @@ + + + + + + \ No newline at end of file From 5fb1e7ba2ed6a4e78f28c0b2437a0d724d7ff3ea Mon Sep 17 00:00:00 2001 From: KevinMidboe Date: Sat, 2 Dec 2017 11:20:26 +0100 Subject: [PATCH 11/66] Sidebar is now redesigned with color indicators, filtering name by search query or filtering status by dropdown. Moved CSS to a separate stylesheet. --- client/app/components/admin/Sidebar.jsx | 248 ++++++++++++++---- client/app/components/styles/adminSidebar.jsx | 50 ++++ 2 files changed, 252 insertions(+), 46 deletions(-) create mode 100644 client/app/components/styles/adminSidebar.jsx diff --git a/client/app/components/admin/Sidebar.jsx b/client/app/components/admin/Sidebar.jsx index 5d001b0..7d48397 100644 --- a/client/app/components/admin/Sidebar.jsx +++ b/client/app/components/admin/Sidebar.jsx @@ -1,57 +1,213 @@ import React, { Component } from 'react'; import { Link } from 'react-router-dom'; +import Interactive from 'react-interactive'; + +import sidebarCSS from '../styles/adminSidebar.jsx' + class SidebarComponent extends Component { - generateListElements(index, item) { - if (index == this.props.listItemSelected) - return ( - {item.name} - ) - else - return ( - {item.name} - ) - } - - displayRequestedElementsInfo() { - if (this.props.requested_objects) { - let requestedElement = this.props.requested_objects.map((item, index) => { - return ( - - { this.generateListElements(index, item) } - {item.status} - {item.requested_date} - - ) - }) - - return ( - - - - - - - - - - {requestedElement} - -
NameStatusDate
- ) + constructor(props){ + super(props) + // Constructor with states holding the search query and the element of reponse. + this.state = { + filterValue: '', + filterQuery: '', + requestItemsToBeDisplayed: [], + listItemSelected: '', } } - render() { - console.log('sidebar: ', this.props.requested_objects) - return ( -
-

Hello from the sidebar:

- { this.displayRequestedElementsInfo() } -
- ); - } + // Where we wait for api response to be delivered from parent through props + componentWillReceiveProps(props) { + this.state.listItemSelected = props.listItemSelected; + this.displayRequestedElementsInfo(props.requested_objects); + } + + // Inputs a date and returns a text string that matches how long it was since + convertDateToDaysSince(date) { + var oneDay = 24*60*60*1000; + var firstDate = new Date(date); + var secondDate = new Date(); + + var diffDays = Math.round(Math.abs((firstDate.getTime() - secondDate.getTime()) / oneDay)); + + switch (diffDays) { + case 0: + return 'Today'; + case 1: + return '1 day ago' + default: + return diffDays + ' days ago' + } + } + + // Called from our dropdown, receives a filter string and checks it with status field + // of our request objects. + filterItems(filterValue) { + let filteredRequestElements = this.props.requested_objects.map((item, index) => { + if (item.status === filterValue || filterValue === 'all') + return this.generateListElements(index, item); + }) + + this.setState({ + requestItemsToBeDisplayed: filteredRequestElements, + filterValue: filterValue, + }) + } + + + // Updates the internal state of the query filter and updates the list to only + // display names matching the query. This is real-time filtering. + updateFilterQuery(event) { + const query = event.target.value; + + let filteredByQuery = this.props.requested_objects.map((item, index) => { + if (item.name.toLowerCase().indexOf(query.toLowerCase()) != -1) + return this.generateListElements(index, item); + }) + + this.setState({ + requestItemsToBeDisplayed: filteredByQuery, + filterQuery: query, + }); + } + + + generateFilterDropdown() { + return ( + + ) + } + + generateFilterSearchbar() { + return ( + this.updateFilterQuery(event)} + value={this.state.filterQuery}/> + ) + } + + // A colored bar indicating the status of a item by color. + generateRequestIndicator(status) { + let statusColor; + + switch (status) { + case 'requested': + // Yellow + statusColor = '#ffe14d'; + break; + case 'downloading': + // Blue + statusColor = '#3fc3f3'; + break; + case 'downloaded': + // Green + statusColor = '#6be682'; + break; + default: + statusColor = 'grey'; + } + + const indicatorCSS = { + width: '100%', + height: '4px', + marginTop: '3px', + backgroundColor: statusColor, + } + + return ( +
+ ) + } + + + generateListElements(index, item) { + if (index == this.state.listItemSelected) { + return ( +
+
+ {item.name } +
+ { this.convertDateToDaysSince(item.requested_date) } +
+
+ Status: { item.status } +
+ Matches found: 0 + { this.generateRequestIndicator(item.status) } +
+ ) + } + else + return ( + + + + {item.name } +
+ { this.convertDateToDaysSince(item.requested_date) } +
+
+ { this.generateRequestIndicator(item.status) } +
+ + ) + } + + // This is our main loader that gets called when we receive api response through props from parent + displayRequestedElementsInfo(requested_objects) { + let requestedElement = requested_objects.map((item, index) => { + if (['requested', 'downloading', 'downloaded'].indexOf(this.state.filterValue) != -1) { + if (item.status === this.state.filterValue){ + return this.generateListElements(index, item); + } + } + + else if (this.state.filterQuery !== '') { + if (item.name.toLowerCase().indexOf(this.state.filterQuery.toLowerCase()) != -1) + return this.generateListElements(index, item); + } + + else + return this.generateListElements(index, item); + }) + + this.setState({ + requestItemsToBeDisplayed: requestedElement, + }) + } + + render() { + let bodyCSS = sidebarCSS.body; + if (typeof InstallTrigger !== 'undefined') + bodyCSS.width = '-moz-min-content'; + + return ( +
+

Hello from the sidebar:

+ { this.generateFilterDropdown() } + { this.generateFilterSearchbar() } +
+ { this.state.requestItemsToBeDisplayed } +
+
+ ); + } } export default SidebarComponent; \ No newline at end of file diff --git a/client/app/components/styles/adminSidebar.jsx b/client/app/components/styles/adminSidebar.jsx new file mode 100644 index 0000000..c10fe49 --- /dev/null +++ b/client/app/components/styles/adminSidebar.jsx @@ -0,0 +1,50 @@ +export default { + body: { + backgroundColor: 'white', + width: 'min-content', + }, + + parentElement: { + display: 'inline-block', + width: '300px', + border: '1px solid black', + borderRadius: '2px', + padding: '4px', + margin: '4px', + marginLeft: '4px', + backgroundColor: 'white', + }, + + parentElement_hover: { + marginLeft: '10px', + }, + + parentElement_active: { + textDecoration: 'none', + }, + + parentElement_selected: { + display: 'inline-block', + width: '300px', + border: '1px solid black', + borderRadius: '2px', + padding: '4px', + margin: '4px 0px 4px 4px', + marginLeft: '10px', + backgroundColor: 'white', + }, + + title: { + maxWidth: '70%', + display: 'inline-flex', + }, + + link: { + color: 'black', + textDecoration: 'none', + }, + + rightContainer: { + float: 'right', + }, +} \ No newline at end of file From 3fe8f46dd49e9589daf74011e14b8bcc527dfdc7 Mon Sep 17 00:00:00 2001 From: KevinMidboe Date: Sat, 2 Dec 2017 11:31:25 +0100 Subject: [PATCH 12/66] Viewport changes to limit zoom in on mobile the typing in a search bar. --- client/app/index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/app/index.html b/client/app/index.html index 3ab7f3f..3ec9c02 100644 --- a/client/app/index.html +++ b/client/app/index.html @@ -4,7 +4,7 @@ - + seasoned Shows From 8695a553d641894d2cb82f81afef9a716825372a Mon Sep 17 00:00:00 2001 From: KevinMidboe Date: Sat, 2 Dec 2017 11:32:49 +0100 Subject: [PATCH 13/66] Admin page is our landing page for admin panel, now it passes our api response to sidebar and info panel when we get the response back from the api. Some renaming of stylesheet variable. --- client/app/components/admin/Admin.jsx | 30 +++++++------------ .../app/components/styles/adminComponent.jsx | 8 +++++ 2 files changed, 19 insertions(+), 19 deletions(-) create mode 100644 client/app/components/styles/adminComponent.jsx diff --git a/client/app/components/admin/Admin.jsx b/client/app/components/admin/Admin.jsx index 47d941a..50047b1 100644 --- a/client/app/components/admin/Admin.jsx +++ b/client/app/components/admin/Admin.jsx @@ -1,10 +1,5 @@ -/* - ./app/components/App.jsx - - -*/ + import React from 'react'; -import { HashRouter as Router, Route, Switch, IndexRoute } from 'react-router-dom'; import LoginForm from './LoginForm/LoginForm.jsx'; import { Provider } from 'react-redux'; @@ -16,6 +11,8 @@ import { fetchJSON } from '../http.jsx'; import Sidebar from './Sidebar.jsx'; import AdminRequestInfo from './AdminRequestInfo.jsx'; +import adminCSS from '../styles/adminComponent.jsx' + class AdminComponent extends React.Component { constructor(props) { @@ -25,6 +22,7 @@ class AdminComponent extends React.Component { } } + // Fetches all requested elements and updates the state with response componentWillMount() { fetchJSON('https://apollo.kevinmidboe.com/api/v1/plex/requests/all', 'GET') .then(result => { @@ -34,16 +32,9 @@ class AdminComponent extends React.Component { }) } + // Displays loginform if not logged in and passes response from + // api call to sidebar and infoPanel through props verifyLoggedIn() { - let adminComponentStyle = { - sidebar: { - float: 'left', - }, - selectedObjectPanel: { - float: 'left', - } - } - const logged_in = getCookie('logged_in'); if (!logged_in) { return @@ -53,20 +44,21 @@ class AdminComponent extends React.Component { let listItemSelected = undefined; const requestParam = this.props.match.params.request; + if (requestParam && this.state.requested_objects !== '') { selectedRequest = this.state.requested_objects[requestParam] - listItemSelected = requestParam + listItemSelected = requestParam; } return (
-
+
+ />
-
+
Date: Sat, 2 Dec 2017 11:35:10 +0100 Subject: [PATCH 14/66] Deleted unused svg image. --- client/app/components/images/loading.svg | 7 ------- 1 file changed, 7 deletions(-) delete mode 100644 client/app/components/images/loading.svg diff --git a/client/app/components/images/loading.svg b/client/app/components/images/loading.svg deleted file mode 100644 index c8f7e85..0000000 --- a/client/app/components/images/loading.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - \ No newline at end of file From 9fcc82d7cf24fa02facc8b8dd9dab74eb6affee3 Mon Sep 17 00:00:00 2001 From: KevinMidboe Date: Sat, 2 Dec 2017 11:35:39 +0100 Subject: [PATCH 15/66] Put both jsx and js varaibles on same line in config file. --- client/webpack.common.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/client/webpack.common.js b/client/webpack.common.js index 114c775..0fd9a5f 100644 --- a/client/webpack.common.js +++ b/client/webpack.common.js @@ -21,8 +21,7 @@ module.exports = { ], module: { loaders: [ - { test: /\.js$/, loader: 'babel-loader', exclude: /node_modules/ }, - { test: /\.jsx$/, loader: 'babel-loader', exclude: /node_modules/ }, + { test: /\.(js|jsx)$/, loader: 'babel-loader', exclude: /node_modules/ }, ] }, From 187ac6317e33a930855c9cc7bdb8798aef848672 Mon Sep 17 00:00:00 2001 From: KevinMidboe Date: Sat, 2 Dec 2017 12:01:49 +0100 Subject: [PATCH 16/66] Still a bit messy, but removed unsued sections and redid the naming schema for css items. --- client/app/components/MovieObject.jsx | 231 ++++++++++++-------------- 1 file changed, 106 insertions(+), 125 deletions(-) diff --git a/client/app/components/MovieObject.jsx b/client/app/components/MovieObject.jsx index d78747e..9f13a46 100644 --- a/client/app/components/MovieObject.jsx +++ b/client/app/components/MovieObject.jsx @@ -3,154 +3,135 @@ import React from 'react'; import Notifications, {notify} from 'react-notify-toast'; // StyleComponents -import movieStyle from './styles/movieObjectStyle.jsx'; +import searchResultCSS from './styles/searchResult.jsx'; +import buttonsCSS from './styles/buttons.jsx'; var MediaQuery = require('react-responsive'); -import RequestButton from './buttons/request_button.jsx'; - import { fetchJSON } from './http.jsx'; import Interactive from 'react-interactive'; class MovieObject { - constructor(object) { - this.id = object.id; - this.title = object.title; - this.year = object.year; - this.type = object.type; - // Check if object.poster != undefined - this.rating = object.rating; - this.poster = object.poster; - this.background = object.background; - this.matchedInPlex = object.matchedInPlex; - this.summary = object.summary; - } + constructor(object) { + this.id = object.id; + this.title = object.title; + this.year = object.year; + this.type = object.type; + this.rating = object.rating; + this.poster = object.poster; + this.background = object.background; + this.matchedInPlex = object.matchedInPlex; + this.summary = object.summary; + } - requestExisting(movie) { - console.log('Exists', movie); - } + requestExisting(movie) { + console.log('Exists', movie); + } - requestMovie() { - // fetch('http://localhost:31459/api/v1/plex/request/' + this.id + '?type='+this.type, { - // fetch('https://apollo.kevinmidboe.com/api/v1/plex/request/' + this.id + '?type='+this.type, { - // method: 'POST' - // }); - fetchJSON('https://apollo.kevinmidboe.com/api/v1/plex/request/' + this.id + '?type='+this.type, 'POST') - .then((response) => { - console.log(response); - }) + requestMovie() { + fetchJSON('https://apollo.kevinmidboe.com/api/v1/plex/request/' + this.id + '?type='+this.type, 'POST') + .then((response) => { + console.log(response); + notify.show(this.title + ' requested!', 'success', 3000); + }) + .catch((e) => { + console.error('Request movie fetch went wrong: '+ e); + }) - notify.show(this.title + ' requested!', 'success', 3000); - } + } - invertButtonColors(event) { - const event_type = event.type; - if (event_type) - console.log('test') - console.log(event.type) - } + getElement(index) { + const element_key = index + this.id; - getElement(index) { - const element_key = index + this.id; + if (this.poster == null || this.poster == undefined) { + var posterPath = 'https://openclipart.org/image/2400px/svg_to_png/211479/Simple-Image-Not-Found-Icon.png' + } else { + var posterPath = 'https://image.tmdb.org/t/p/w300' + this.poster; + } + var backgroundPath = 'https://image.tmdb.org/t/p/w640_and_h360_bestv2/' + this.background; - // TODO set the poster image async by updating the dom after this is returned - if (this.poster == null || this.poster == undefined) { - var posterPath = 'https://openclipart.org/image/2400px/svg_to_png/211479/Simple-Image-Not-Found-Icon.png' - } else { - var posterPath = 'https://image.tmdb.org/t/p/w300' + this.poster; - } - var backgroundPath = 'https://image.tmdb.org/t/p/w640_and_h360_bestv2/' + this.background; + var foundInPlex; + if (this.matchedInPlex) { + foundInPlex = {this.requestExisting(this)}} + style={buttonsCSS.submit} + focus={buttonsCSS.submit_hover} + hover={buttonsCSS.submit_hover}> - var foundInPlex; - if (this.matchedInPlex) { - foundInPlex = {this.requestExisting(this)}} - style={movieStyle.requestButton}> - - Request Anyway - ; - } else { - foundInPlex = {this.requestMovie()}} - onMouseOver={() => {this.invertButtonColors(event)}} - style={movieStyle.requestButton}> + Request Anyway + ; + } else { + foundInPlex = {this.requestMovie()}} + style={buttonsCSS.submit} + focus={buttonsCSS.submit_hover} + hover={buttonsCSS.submit_hover}> - + Request - ; - } + + Request + ; + } - if (this.type === 'movie') - var themoviedbLink = 'https://www.themoviedb.org/movie/' + this.id - else if (this.type === 'show') - var themoviedbLink = 'https://www.themoviedb.org/tv/' + this.id + if (this.type === 'movie') + var themoviedbLink = 'https://www.themoviedb.org/movie/' + this.id + else if (this.type === 'show') + var themoviedbLink = 'https://www.themoviedb.org/tv/' + this.id - + // TODO go away from using mediaQuery, and create custom resizer + return ( +
+ + +
+ +
+ +
+ {this.title} +

+ Released: { this.year } | Rating: {this.rating} +

+ {this.summary} +

+
- // TODO add request button class - return ( -
- -
- -
- -
-
-
- - {this.title} -

- Released: { this.year } | Rating: {this.rating} -

- {this.summary} -

-
+ + + {this.title} +

+ Released: {this.year} | Rating: {this.rating} +
+ +
+ + +
+
+ +
+
+
+
+ ) - - - - - -
- {foundInPlex} - - - - Info - - -
-
-
- -

-
-
-
-
-
) - - } + } } export default MovieObject; \ No newline at end of file From e55067025ec5fd0060b910ad4d078911665e66b0 Mon Sep 17 00:00:00 2001 From: KevinMidboe Date: Sat, 2 Dec 2017 12:05:17 +0100 Subject: [PATCH 17/66] Renamed movieObject to searchObject, same with css. --- .../{MovieObject.jsx => SearchObject.jsx} | 26 ++-- .../components/styles/movieObjectStyle.jsx | 112 ------------------ client/app/components/styles/searchObject.jsx | 58 +++++++++ 3 files changed, 71 insertions(+), 125 deletions(-) rename client/app/components/{MovieObject.jsx => SearchObject.jsx} (81%) delete mode 100644 client/app/components/styles/movieObjectStyle.jsx create mode 100644 client/app/components/styles/searchObject.jsx diff --git a/client/app/components/MovieObject.jsx b/client/app/components/SearchObject.jsx similarity index 81% rename from client/app/components/MovieObject.jsx rename to client/app/components/SearchObject.jsx index 9f13a46..b06208c 100644 --- a/client/app/components/MovieObject.jsx +++ b/client/app/components/SearchObject.jsx @@ -3,7 +3,7 @@ import React from 'react'; import Notifications, {notify} from 'react-notify-toast'; // StyleComponents -import searchResultCSS from './styles/searchResult.jsx'; +import searchObjectCSS from './styles/searchObject.jsx'; import buttonsCSS from './styles/buttons.jsx'; var MediaQuery = require('react-responsive'); @@ -85,27 +85,27 @@ class MovieObject {
-
+
-
- +
+
- {this.title} + {this.title}

- Released: { this.year } | Rating: {this.rating} + Released: { this.year } | Rating: {this.rating}

- {this.summary} + {this.summary}

- - {this.title} + + {this.title}

- Released: {this.year} | Rating: {this.rating} + Released: {this.year} | Rating: {this.rating}
-
+
{foundInPlex} @@ -125,8 +125,8 @@ class MovieObject {
-
-
+
+
) diff --git a/client/app/components/styles/movieObjectStyle.jsx b/client/app/components/styles/movieObjectStyle.jsx deleted file mode 100644 index c16a89f..0000000 --- a/client/app/components/styles/movieObjectStyle.jsx +++ /dev/null @@ -1,112 +0,0 @@ - -export default { - resultItem: { - maxWidth: '95%', - margin: '0 auto', - minHeight: '230px' - }, - - movie_content: { - marginLeft: '15px' - }, - - resultTitleLarge: { - color: 'black', - fontSize: '2em', - }, - - resultTitleSmall: { - color: 'black', - fontSize: '22px', - }, - - yearRatingLarge: { - fontSize: '0.8em' - }, - - resultPoster: { - float: 'left', - zIndex: '3', - position: 'relative', - marginRight: '30px' - }, - - background: { - width: '100%' - }, - - yearRatingSmall: { - marginTop: '5px', - fontSize: '0.8em' - }, - - resultPosterImg: { - border: '2px none', - borderRadius: '2px', - width: '150px' - }, - - cornerRibbon: { - position: 'absolute', - width: '450px', - }, - - summary: { - fontSize: '15px', - }, - - buttons: { - paddingTop: '20px' - }, - - requestButton: { - color: '#e9a131', - marginRight: '10px', - backgroundColor: 'white', - border: '#e9a131 2px solid', - borderColor: '#e9a131', - borderRadius: '4px', - textAlign: 'center', - padding: '10px', - minWidth: '100px', - float: 'left', - fontSize: '13px', - fontWeight: '800', - cursor: 'pointer' - }, - - requestButton_hover: { - backgroundColor: '#e9a131', - color: 'white', - }, - - tmdbButton: { - color: '#00d17c', - marginRight: '10px', - backgroundColor: 'white', - border: '#00d17c 2px solid', - borderRadius: '4px', - textAlign: 'center', - padding: '10px', - minWidth: '100px', - float: 'left', - fontSize: '13px', - fontWeight: '800', - cursor: 'pointer' - }, - - tmdbButton_hover: { - backgroundColor: '#00d17c', - color: 'white', - }, - - row: { - width: '100%' - }, - - itemDivider: { - width: '90%', - borderBottom: '1px solid grey', - margin: '2rem auto' - } -} \ No newline at end of file diff --git a/client/app/components/styles/searchObject.jsx b/client/app/components/styles/searchObject.jsx new file mode 100644 index 0000000..4d4ed72 --- /dev/null +++ b/client/app/components/styles/searchObject.jsx @@ -0,0 +1,58 @@ + +export default { + container: { + maxWidth: '95%', + margin: '0 auto', + minHeight: '230px' + }, + + title_large: { + color: 'black', + fontSize: '2em', + }, + + title_small: { + color: 'black', + fontSize: '22px', + }, + + stats_large: { + fontSize: '0.8em' + }, + + stats_small: { + marginTop: '5px', + fontSize: '0.8em' + }, + + posterContainer: { + float: 'left', + zIndex: '3', + position: 'relative', + marginRight: '30px' + }, + + posterImage: { + border: '2px none', + borderRadius: '2px', + width: '150px' + }, + + backgroundImage: { + width: '100%' + }, + + summary: { + fontSize: '15px', + }, + + dividerRow: { + width: '100%' + }, + + itemDivider: { + width: '90%', + borderBottom: '1px solid grey', + margin: '2rem auto' + } +} \ No newline at end of file From fd0a2c9d50cff4d38a170f360078ca265e4ed49c Mon Sep 17 00:00:00 2001 From: KevinMidboe Date: Sat, 2 Dec 2017 12:08:47 +0100 Subject: [PATCH 18/66] Renamed the exporting class name to match the file name. --- client/app/components/SearchObject.jsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/app/components/SearchObject.jsx b/client/app/components/SearchObject.jsx index b06208c..2be95d3 100644 --- a/client/app/components/SearchObject.jsx +++ b/client/app/components/SearchObject.jsx @@ -13,7 +13,7 @@ import { fetchJSON } from './http.jsx'; import Interactive from 'react-interactive'; -class MovieObject { +class SearchObject { constructor(object) { this.id = object.id; this.title = object.title; @@ -134,4 +134,4 @@ class MovieObject { } } -export default MovieObject; \ No newline at end of file +export default SearchObject; \ No newline at end of file From 45db534681f77c7d5248cc9f2816cb63dc0d9918 Mon Sep 17 00:00:00 2001 From: KevinMidboe Date: Sat, 2 Dec 2017 12:17:00 +0100 Subject: [PATCH 19/66] When a requested element from the sidebar is selected this is where the detailed info is displayed. Now it is possible to change the status of a item by using the dropdown. This is where piratesearch also gets the query name through props. --- .../app/components/admin/AdminRequestInfo.jsx | 143 ++++++++++++------ .../components/styles/adminRequestInfo.jsx | 11 ++ 2 files changed, 105 insertions(+), 49 deletions(-) create mode 100644 client/app/components/styles/adminRequestInfo.jsx diff --git a/client/app/components/admin/AdminRequestInfo.jsx b/client/app/components/admin/AdminRequestInfo.jsx index a3a89f4..2f51113 100644 --- a/client/app/components/admin/AdminRequestInfo.jsx +++ b/client/app/components/admin/AdminRequestInfo.jsx @@ -1,10 +1,31 @@ import React, { Component } from 'react'; + +import { fetchJSON } from '../http.jsx'; + import PirateSearch from './PirateSearch.jsx' +// Stylesheets +import requestInfoCSS from '../styles/adminRequestInfo.jsx' +import buttonsCSS from '../styles/buttons.jsx'; + +// Interactive button +import Interactive from 'react-interactive'; + class AdminRequestInfo extends Component { constructor() { super(); + + this.state = { + statusValue: '', + } + + this.requestInfo = ''; + } + + componentWillReceiveProps(props) { + this.requestInfo = props.selectedRequest; + this.state.statusValue = this.requestInfo.status; } userAgent(agent) { @@ -19,6 +40,35 @@ class AdminRequestInfo extends Component { return ''; } + generateStatusDropdown() { + return ( + + ) + } + + updateRequestStatus(event) { + const eventValue = event.target.value; + const itemID = this.requestInfo.id; + + const apiData = { + type: this.requestInfo.type, + status: eventValue, + } + + fetchJSON('https://apollo.kevinmidboe.com/api/v1/plex/request/' + itemID, 'PUT', apiData) + .then((response) => { + console.log('Response, updateRequestStatus: ', response) + }) + + this.setState({ + statusValue: eventValue + }) + } + requested_by_user(request_user) { if (request_user === 'NULL') return undefined @@ -28,57 +78,52 @@ class AdminRequestInfo extends Component { ) } - displayInfo() { - let adminIndexStyle = { - wrapper: { - width: '100%', - }, - headerWrapper: { - width: '100%', - }, - poster: { - float: 'left', - minHeight: '450px', - }, - info: { - float: 'left', - minHeight: '450px', - } - } - const request = this.props.selectedRequest; + displayInfo() { + const request = this.props.selectedRequest; - if (request) { - return ( -
-
- {request.name} - {request.year} -
-
- -
-
- type: {request.type}
- status: {request.status}
- ip: {request.ip}
- user_agent: {this.userAgent(request.user_agent)}
- request_date: {request.requested_date}
- { this.requested_by_user(request.requested_by) } -
- - - -
- ) - } - } + if (request) { + return ( +
+
+ {request.name} + {request.year} +
- render() { - return ( -
{this.displayInfo()}
- ); - } +
+ type: {request.type}
+ + {this.generateStatusDropdown()}
+ + status: {request.status}
+ ip: {request.ip}
+ user_agent: {this.userAgent(request.user_agent)}
+ request_date: {request.requested_date}
+ { this.requested_by_user(request.requested_by) } +
+ +
+ {}} + style={buttonsCSS.edit} + focus={buttonsCSS.edit_hover} + hover={buttonsCSS.edit_hover}> + + Show info + + + +
+
+ ) + } + } + + render() { + return ( +
{this.displayInfo()}
+ ); + } } export default AdminRequestInfo; \ No newline at end of file diff --git a/client/app/components/styles/adminRequestInfo.jsx b/client/app/components/styles/adminRequestInfo.jsx new file mode 100644 index 0000000..e11b3d4 --- /dev/null +++ b/client/app/components/styles/adminRequestInfo.jsx @@ -0,0 +1,11 @@ +export default = { + wrapper: { + width: '100%', + }, + headerWrapper: { + width: '100%', + }, + poster: { + minHeight: '450px', + }, +} \ No newline at end of file From 7bde2821d0b1973c1324860ea1a85980c6d97acd Mon Sep 17 00:00:00 2001 From: KevinMidboe Date: Sat, 2 Dec 2017 13:13:35 +0100 Subject: [PATCH 20/66] Followed with the renaming of MovieObject to now be refered to as SearchObject. Also change the loading animation for InfiniteScroll. It is the same loading animation imported as we have in torrent search. --- client/app/components/SearchRequest.jsx | 48 ++++++++++++------------- 1 file changed, 23 insertions(+), 25 deletions(-) diff --git a/client/app/components/SearchRequest.jsx b/client/app/components/SearchRequest.jsx index dcd1cdd..b5b6554 100644 --- a/client/app/components/SearchRequest.jsx +++ b/client/app/components/SearchRequest.jsx @@ -1,14 +1,14 @@ import React from 'react'; -import MovieObject from './MovieObject.jsx'; - -// StyleComponents -import searchStyle from './styles/searchRequestStyle.jsx'; -import movieStyle from './styles/movieObjectStyle.jsx'; - import URI from 'urijs'; import InfiniteScroll from 'react-infinite-scroller'; +// StyleComponents +import searchStyle from './styles/searchRequestStyle.jsx'; + +import SearchObject from './SearchObject.jsx'; +import Loading from './images/loading.jsx' + import { fetchJSON } from './http.jsx'; import { getCookie } from './Cookie.jsx'; @@ -29,7 +29,8 @@ class SearchRequest extends React.Component { page: 1, resultHeader: '', loadResults: false, - scrollHasMore: true + scrollHasMore: true, + loading: false, } this.allowedListTypes = ['discover', 'popular', 'nowplaying', 'upcoming'] @@ -88,9 +89,9 @@ class SearchRequest extends React.Component { this.state.page = 1; } - writeLoading() { + setLoading(value) { this.setState({ - responseMovieList: 'Loading...' + loading: value }); } @@ -122,10 +123,7 @@ class SearchRequest extends React.Component { // Here we first call api for a search with the input uri, handle any errors // and fill the reponseData from api into the state of reponseMovieList as movieObjects - callSearchFillMovieList(uri) { - // Write loading animation - // this.writeLoading(); - + callSearchFillMovieList(uri) { Promise.resolve() .then(() => this.callURI(uri, 'GET')) .then(response => { @@ -152,7 +150,7 @@ class SearchRequest extends React.Component { } // Convert to json and update the state of responseMovieList with the results of the api call - // mapped as a movieObject. + // mapped as a SearchObject. response.json() .then(responseData => { if (this.state.page === 1) { @@ -180,7 +178,6 @@ class SearchRequest extends React.Component { callListFillMovieList(uri) { // Write loading animation - // this.writeLoading(); Promise.resolve() .then(() => this.callURI(uri, 'GET', undefined)) @@ -198,7 +195,7 @@ class SearchRequest extends React.Component { } // Convert to json and update the state of responseMovieList with the results of the api call - // mapped as a movieObject. + // mapped as a SearchObject. response.json() .then(responseData => { if (this.state.page === 1) { @@ -218,6 +215,7 @@ class SearchRequest extends React.Component { }) .catch((error) => { console.log('Something went wrong when fetching query.', error) + }) } @@ -287,10 +285,10 @@ class SearchRequest extends React.Component { } } - // When called passes the variable to MovieObject and calls it's interal function for + // When called passes the variable to SearchObject and calls it's interal function for // generating the wanted HTML createMovieObjects(item, index) { - let movie = new MovieObject(item); + let movie = new SearchObject(item); return movie.getElement(index); } @@ -379,7 +377,7 @@ class SearchRequest extends React.Component { pageStart={0} loadMore={this.pageForwards.bind(this)} hasMore={this.state.scrollHasMore} - loader={loader} + loader={} initialLoad={this.state.loadResults}> @@ -403,10 +401,10 @@ class SearchRequest extends React.Component {
- {this.state.resultHeader} -



- - {this.state.responseMovieList} + {this.state.resultHeader} +



+ + {this.state.responseMovieList}
@@ -435,7 +433,7 @@ class SearchRequest extends React.Component { {this.state.resultHeader}



- {this.state.responseMovieList} + {this.state.responseMovieList}
@@ -446,4 +444,4 @@ class SearchRequest extends React.Component { } -export default SearchRequest; \ No newline at end of file +export default SearchRequest; From 580cdc430f550444a248bce19d20a779bb47b9fd Mon Sep 17 00:00:00 2001 From: KevinMidboe Date: Sat, 2 Dec 2017 13:15:23 +0100 Subject: [PATCH 21/66] Removed a lot of unused css classes. --- .../components/styles/searchRequestStyle.jsx | 75 ++----------------- 1 file changed, 5 insertions(+), 70 deletions(-) diff --git a/client/app/components/styles/searchRequestStyle.jsx b/client/app/components/styles/searchRequestStyle.jsx index 45d5cff..17b5b11 100644 --- a/client/app/components/styles/searchRequestStyle.jsx +++ b/client/app/components/styles/searchRequestStyle.jsx @@ -11,12 +11,13 @@ export default { backgroundLargeHeader: { width: '100%', minHeight: '400px', - backgroundColor: '#011c23', + backgroundColor: 'rgb(1, 28, 35)', + // backgroundImage: 'radial-gradient(circle, #004c67 0, #005771 120%)', zIndex: 1, marginBottom: '-100px' }, - backgroundSmallHeader: { + backgroundSmallHeader: { width: '100%', minHeight: '300px', backgroundColor: '#011c23', @@ -31,7 +32,7 @@ export default { backgroundColor: 'white', position: 'relative', zIndex: '10', - boxShadow: '0 4px 2px black' + boxShadow: '0 1px 2px grey', }, pageTitle: { @@ -43,7 +44,7 @@ export default { pageTitleLargeSpan: { color: 'white', - fontSize: '3em', + fontSize: '4em', marginTop: '4vh', marginBottom: '6vh' }, @@ -133,70 +134,4 @@ export default { color: 'black', fontSize: '1.4em', }, - - row: { - width: '100%' - }, - - itemDivider: { - width: '90%', - borderBottom: '1px solid grey', - margin: '1rem auto' - }, - - - pageNavigationBar: { - width: '100%', - display: 'flex', - alignItems: 'center', - justifyContent: 'center' - }, - - pageNavigationButton: { - margin: '0 auto', - }, - - hvrUnderlineFromCenter: { - color: 'white', - fontSize: '1em', - paddingTop: '12px', - marginBottom: '12px', - marginLeft: '10px', - cursor: 'pointer', - display: 'inline-block', - verticalAlign: 'middle', - WebkitTransform: 'perspective(1px) translateZ(0)', - transform: 'perspective(1px) translateZ(0)', - boxShadow: '0 0 1px transparent', - position: 'relative', - overflow: 'hidden', - ':before': { - content: "", - position: 'absolute', - zIndex: '-1', - left: '50%', - right: '50%', - bottom: '0', - background: '#00d17c', - height: '2px', - WebkitTransitionProperty: 'left, right', - transitionProperty: 'left, right', - WebkitTransitionDuration: '0.3s', - transitionDuration: '0.3s', - WebkitTransitionTimingFunction: 'ease-out', - transitionTimingFunction: 'ease-out' - }, - ':hover:before': { - left: 0, - right: 0 - }, - 'focus:before': { - left: 0, - right: 0 - }, - 'active:before': { - left: 0, - right: 0 - } - } } \ No newline at end of file From 246abd7020c5df5847827922495bf14034193370 Mon Sep 17 00:00:00 2001 From: KevinMidboe Date: Sat, 2 Dec 2017 13:16:57 +0100 Subject: [PATCH 22/66] Renamed the variable name for our stylesheet. --- client/app/components/SearchRequest.jsx | 50 ++++++++++++------------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/client/app/components/SearchRequest.jsx b/client/app/components/SearchRequest.jsx index b5b6554..39cdce2 100644 --- a/client/app/components/SearchRequest.jsx +++ b/client/app/components/SearchRequest.jsx @@ -4,7 +4,7 @@ import URI from 'urijs'; import InfiniteScroll from 'react-infinite-scroller'; // StyleComponents -import searchStyle from './styles/searchRequestStyle.jsx'; +import searchRequestCSS from './styles/searchRequestStyle.jsx'; import SearchObject from './SearchObject.jsx'; import Loading from './images/loading.jsx' @@ -343,12 +343,12 @@ class SearchRequest extends React.Component { movieToggle() { if (this.state.movieFilter) - return {this.toggleFilter('movies')}} id="category_active">Movies else - return {this.toggleFilter('movies')}} id="category_active">Movies @@ -356,12 +356,12 @@ class SearchRequest extends React.Component { showToggle() { if (this.state.showFilter) - return {this.toggleFilter('shows')}} id="category_active">TV Shows else - return {this.toggleFilter('shows')}} id="category_active">TV Shows @@ -381,17 +381,17 @@ class SearchRequest extends React.Component { initialLoad={this.state.loadResults}> -
-
-
- Request new content +
+
+
+ Request new content
-
-
- +
+
+ - this._handleQueryKeyPress(event)} onChange={event => this.updateQueryState(event)} value={this.state.searchQuery}/> @@ -400,8 +400,8 @@ class SearchRequest extends React.Component {
-
- {this.state.resultHeader} +
+ {this.state.resultHeader}



{this.state.responseMovieList} @@ -410,17 +410,17 @@ class SearchRequest extends React.Component { -
-
-
- Request new content +
+
+
+ Request new content
-
-
- +
+
+ - this._handleQueryKeyPress(event)} onChange={event => this.updateQueryState(event)} value={this.state.searchQuery}/> @@ -429,8 +429,8 @@ class SearchRequest extends React.Component {
-
- {this.state.resultHeader} +
+ {this.state.resultHeader}



{this.state.responseMovieList} From 7849a8ce3f93d1a3a6bac880da4624d7f8aa23cc Mon Sep 17 00:00:00 2001 From: KevinMidboe Date: Sat, 2 Dec 2017 13:17:17 +0100 Subject: [PATCH 23/66] Fixed syntax error. --- client/app/components/styles/adminRequestInfo.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/app/components/styles/adminRequestInfo.jsx b/client/app/components/styles/adminRequestInfo.jsx index e11b3d4..0d13af2 100644 --- a/client/app/components/styles/adminRequestInfo.jsx +++ b/client/app/components/styles/adminRequestInfo.jsx @@ -1,4 +1,4 @@ -export default = { +export default { wrapper: { width: '100%', }, From c65cdd84b7f185d1a3d3bf6c35c0a13a93e61d80 Mon Sep 17 00:00:00 2001 From: KevinMidboe Date: Sat, 2 Dec 2017 13:18:20 +0100 Subject: [PATCH 24/66] Changed our gitignore file for the api folder. --- seasoned_api/.gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/seasoned_api/.gitignore b/seasoned_api/.gitignore index 9f7246b..ef087ec 100644 --- a/seasoned_api/.gitignore +++ b/seasoned_api/.gitignore @@ -60,4 +60,4 @@ typings/ # - - - - - # My own gitignore files and folders -conf +conf/ From de90b48430f2effe0eb35fb729f6cdbcf03891ad Mon Sep 17 00:00:00 2001 From: Kevin Date: Sat, 2 Dec 2017 15:39:02 +0100 Subject: [PATCH 25/66] Update README.md --- README.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 8b34d0b..ffa6c70 100644 --- a/README.md +++ b/README.md @@ -2,8 +2,14 @@ Your customly seasoned movie and show requester, downloader and organizer ## About +The goal of this project is to create a full custom stack that can to everything surround downloading, organizing and notifiyng of new media. From the top down we have a website using [tmdb](https://www.themoviedb.com) api to search for from over 350k movies and 70k tv shows. Using [hjone72](https://github.com/hjone72/PlexAuth) great PHP reverse proxy we can have a secure way of allowing users to login with their plex credentials which limits request capabilites to only users that are authenticated to use your plex library. seasonedShows is a intelligent organizer for your tv show episodes. It is made to automate and simplify to process of renaming and moving newly downloaded tv show episodes following Plex file naming and placement. +So this is a multipart system that lets your plex users request movies, and then from the admin page the owner can. + +## Installation +There are two main ways of + ## Architecture The flow of the system will first check for new folders in your tv shows directory, if a new file is found it's contents are analyzed, stored and tweets suggested changes to it's contents to use_admin. @@ -27,4 +33,4 @@ After approval by user the files are modified and moved to folders in resptected + Parse directories for new content. + Extract and save in db information about stray item. + Move a confirmed stray item. - + (+) Search for torrents matching new content. \ No newline at end of file + + (+) Search for torrents matching new content. From 317aa69f18855c792b9fee1bd30af9ba292bd417 Mon Sep 17 00:00:00 2001 From: KevinMidboe Date: Sat, 2 Dec 2017 15:42:23 +0100 Subject: [PATCH 26/66] Re-added a CSS class that was removed. --- client/app/components/styles/searchObject.jsx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/client/app/components/styles/searchObject.jsx b/client/app/components/styles/searchObject.jsx index 4d4ed72..a1dc4d0 100644 --- a/client/app/components/styles/searchObject.jsx +++ b/client/app/components/styles/searchObject.jsx @@ -42,6 +42,10 @@ export default { width: '100%' }, + buttons: { + paddingTop: '20px', + }, + summary: { fontSize: '15px', }, From 790f5d6588134908103e22cf8880e8f9d93713ab Mon Sep 17 00:00:00 2001 From: KevinMidboe Date: Wed, 20 Dec 2017 20:46:15 +0100 Subject: [PATCH 27/66] Added travis support to api. --- .gitignore | 2 - .travis.yml | 14 + seasoned_api/yarn.lock | 2403 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 2417 insertions(+), 2 deletions(-) create mode 100644 .travis.yml create mode 100644 seasoned_api/yarn.lock diff --git a/.gitignore b/.gitignore index e85cd5a..be0f9f0 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,4 @@ env shows.db -yarn.lock -*/yarn.lock */package-lock.json diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..3140bb8 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,14 @@ +{ + 'dist': 'trusty', + 'language': 'node_js', + 'node_js': '6.9.5', + 'cache': 'yarn', + 'scripts': [ + npm run test + ], + 'before_install': [ + 'cd seasoned_api', + ], + 'before_script': 'yarn', + 'os': 'linux', +} \ No newline at end of file diff --git a/seasoned_api/yarn.lock b/seasoned_api/yarn.lock new file mode 100644 index 0000000..f1d3980 --- /dev/null +++ b/seasoned_api/yarn.lock @@ -0,0 +1,2403 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +abbrev@1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8" + +abbrev@1.0.x: + version "1.0.9" + resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.0.9.tgz#91b4792588a7738c25f35dd6f63752a2f8776135" + +accepts@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.0.0.tgz#3604c765586c3b9cf7877b6937cdbd4587f947dc" + dependencies: + mime "~1.2.11" + negotiator "~0.3.0" + +acorn-jsx@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-3.0.1.tgz#afdf9488fb1ecefc8348f6fb22f464e32a58b36b" + dependencies: + acorn "^3.0.4" + +acorn@^3.0.4: + version "3.3.0" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-3.3.0.tgz#45e37fb39e8da3f25baee3ff5369e2bb5f22017a" + +acorn@^5.1.1: + version "5.1.2" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.1.2.tgz#911cb53e036807cf0fa778dc5d370fbd864246d7" + +ajv-keywords@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-2.1.0.tgz#a296e17f7bfae7c1ce4f7e0de53d29cb32162df0" + +ajv@^4.9.1: + version "4.11.8" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-4.11.8.tgz#82ffb02b29e662ae53bdc20af15947706739c536" + dependencies: + co "^4.6.0" + json-stable-stringify "^1.0.1" + +ajv@^5.1.0: + version "5.2.3" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-5.2.3.tgz#c06f598778c44c6b161abafe3466b81ad1814ed2" + dependencies: + co "^4.6.0" + fast-deep-equal "^1.0.0" + json-schema-traverse "^0.3.0" + json-stable-stringify "^1.0.1" + +ajv@^5.2.0, ajv@^5.2.3: + version "5.3.0" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-5.3.0.tgz#4414ff74a50879c208ee5fdc826e32c303549eda" + dependencies: + co "^4.6.0" + fast-deep-equal "^1.0.0" + fast-json-stable-stringify "^2.0.0" + json-schema-traverse "^0.3.0" + +align-text@^0.1.1, align-text@^0.1.3: + version "0.1.4" + resolved "https://registry.yarnpkg.com/align-text/-/align-text-0.1.4.tgz#0cd90a561093f35d0a99256c22b7069433fad117" + dependencies: + kind-of "^3.0.2" + longest "^1.0.1" + repeat-string "^1.5.2" + +amdefine@>=0.0.4: + version "1.0.1" + resolved "https://registry.yarnpkg.com/amdefine/-/amdefine-1.0.1.tgz#4a5282ac164729e93619bcfd3ad151f817ce91f5" + +ansi-escapes@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-3.0.0.tgz#ec3e8b4e9f8064fc02c3ac9b65f1c275bda8ef92" + +ansi-regex@^2.0.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df" + +ansi-regex@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.0.tgz#ed0317c322064f79466c02966bddb605ab37d998" + +ansi-styles@^2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe" + +ansi-styles@^3.1.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.0.tgz#c159b8d5be0f9e5a6f346dab94f16ce022161b88" + dependencies: + color-convert "^1.9.0" + +aproba@^1.0.3: + version "1.2.0" + resolved "https://registry.yarnpkg.com/aproba/-/aproba-1.2.0.tgz#6802e6264efd18c790a1b0d517f0f2627bf2c94a" + +are-we-there-yet@~1.1.2: + version "1.1.4" + resolved "https://registry.yarnpkg.com/are-we-there-yet/-/are-we-there-yet-1.1.4.tgz#bb5dca382bb94f05e15194373d16fd3ba1ca110d" + dependencies: + delegates "^1.0.0" + readable-stream "^2.0.6" + +argparse@^1.0.7: + version "1.0.9" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.9.tgz#73d83bc263f86e97f8cc4f6bae1b0e90a7d22c86" + dependencies: + sprintf-js "~1.0.2" + +array-union@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/array-union/-/array-union-1.0.2.tgz#9a34410e4f4e3da23dea375be5be70f24778ec39" + dependencies: + array-uniq "^1.0.1" + +array-uniq@^1.0.1: + version "1.0.3" + resolved "https://registry.yarnpkg.com/array-uniq/-/array-uniq-1.0.3.tgz#af6ac877a25cc7f74e058894753858dfdb24fdb6" + +arrify@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/arrify/-/arrify-1.0.1.tgz#898508da2226f380df904728456849c1501a4b0d" + +asn1@~0.2.3: + version "0.2.3" + resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.3.tgz#dac8787713c9966849fc8180777ebe9c1ddf3b86" + +assert-plus@1.0.0, assert-plus@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-1.0.0.tgz#f12e0f3c5d77b0b1cdd9146942e4e96c1e4dd525" + +assert-plus@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-0.2.0.tgz#d74e1b87e7affc0db8aadb7021f3fe48101ab234" + +async@0.9.0: + version "0.9.0" + resolved "https://registry.yarnpkg.com/async/-/async-0.9.0.tgz#ac3613b1da9bed1b47510bb4651b8931e47146c7" + +async@1.x, async@^1.4.0, async@^1.5.2: + version "1.5.2" + resolved "https://registry.yarnpkg.com/async/-/async-1.5.2.tgz#ec6a61ae56480c0c3cb241c95618e20892f9672a" + +asynckit@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" + +aws-sign2@~0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.6.0.tgz#14342dd38dbcc94d0e5b87d763cd63612c0e794f" + +aws-sign2@~0.7.0: + version "0.7.0" + resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.7.0.tgz#b46e890934a9591f2d2f6f86d7e6a9f1b3fe76a8" + +aws4@^1.2.1, aws4@^1.6.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.6.0.tgz#83ef5ca860b2b32e4a0deedee8c771b9db57471e" + +babel-code-frame@^6.22.0: + version "6.26.0" + resolved "https://registry.yarnpkg.com/babel-code-frame/-/babel-code-frame-6.26.0.tgz#63fd43f7dc1e3bb7ce35947db8fe369a3f58c74b" + dependencies: + chalk "^1.1.3" + esutils "^2.0.2" + js-tokens "^3.0.2" + +balanced-match@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" + +base64url@2.0.0, base64url@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/base64url/-/base64url-2.0.0.tgz#eac16e03ea1438eff9423d69baa36262ed1f70bb" + +bcrypt-nodejs@^0.0.3: + version "0.0.3" + resolved "https://registry.yarnpkg.com/bcrypt-nodejs/-/bcrypt-nodejs-0.0.3.tgz#c60917f26dc235661566c681061c303c2b28842b" + +bcrypt-pbkdf@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.1.tgz#63bc5dcb61331b92bc05fd528953c33462a06f8d" + dependencies: + tweetnacl "^0.14.3" + +block-stream@*: + version "0.0.9" + resolved "https://registry.yarnpkg.com/block-stream/-/block-stream-0.0.9.tgz#13ebfe778a03205cfe03751481ebb4b3300c126a" + dependencies: + inherits "~2.0.0" + +bluebird@^3.3.1, bluebird@^3.5.0: + version "3.5.1" + resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.1.tgz#d9551f9de98f1fcda1e683d17ee91a0602ee2eb9" + +body-parser@~1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.0.2.tgz#3461479a3278fe00fcaebec3314bb54fc4f7b47c" + dependencies: + qs "~0.6.6" + raw-body "~1.1.2" + type-is "~1.1.0" + +boom@2.x.x: + version "2.10.1" + resolved "https://registry.yarnpkg.com/boom/-/boom-2.10.1.tgz#39c8918ceff5799f83f9492a848f625add0c766f" + dependencies: + hoek "2.x.x" + +boom@4.x.x: + version "4.3.1" + resolved "https://registry.yarnpkg.com/boom/-/boom-4.3.1.tgz#4f8a3005cb4a7e3889f749030fd25b96e01d2e31" + dependencies: + hoek "4.x.x" + +boom@5.x.x: + version "5.2.0" + resolved "https://registry.yarnpkg.com/boom/-/boom-5.2.0.tgz#5dd9da6ee3a5f302077436290cb717d3f4a54e02" + dependencies: + hoek "4.x.x" + +brace-expansion@^1.1.7: + version "1.1.8" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.8.tgz#c07b211c7c952ec1f8efd51a77ef0d1d3990a292" + dependencies: + balanced-match "^1.0.0" + concat-map "0.0.1" + +browser-stdout@1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/browser-stdout/-/browser-stdout-1.3.0.tgz#f351d32969d32fa5d7a5567154263d928ae3bd1f" + +bson@~0.2: + version "0.2.22" + resolved "https://registry.yarnpkg.com/bson/-/bson-0.2.22.tgz#fcda103f26d0c074d5a52d50927db80fd02b4b39" + dependencies: + nan "~1.8" + +buffer-crc32@0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.1.tgz#be3e5382fc02b6d6324956ac1af98aa98b08534c" + +buffer-equal-constant-time@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz#f8e71132f7ffe6e01a5c9697a4c6f3e48d5cc819" + +builtin-modules@^1.0.0, builtin-modules@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-1.1.1.tgz#270f076c5a72c02f5b65a47df94c5fe3a278892f" + +bytes@1: + version "1.0.0" + resolved "https://registry.yarnpkg.com/bytes/-/bytes-1.0.0.tgz#3569ede8ba34315fab99c3e92cb04c7220de1fa8" + +caller-path@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/caller-path/-/caller-path-0.1.0.tgz#94085ef63581ecd3daa92444a8fe94e82577751f" + dependencies: + callsites "^0.2.0" + +callsites@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/callsites/-/callsites-0.2.0.tgz#afab96262910a7f33c19a5775825c69f34e350ca" + +camelcase@^1.0.2: + version "1.2.1" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-1.2.1.tgz#9bb5304d2e0b56698b2c758b08a3eaa9daa58a39" + +caseless@~0.12.0: + version "0.12.0" + resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc" + +center-align@^0.1.1: + version "0.1.3" + resolved "https://registry.yarnpkg.com/center-align/-/center-align-0.1.3.tgz#aa0d32629b6ee972200411cbd4461c907bc2b7ad" + dependencies: + align-text "^0.1.3" + lazy-cache "^1.0.3" + +chalk@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98" + dependencies: + ansi-styles "^2.2.1" + escape-string-regexp "^1.0.2" + has-ansi "^2.0.0" + strip-ansi "^3.0.0" + supports-color "^2.0.0" + +chalk@^2.0.0, chalk@^2.1.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.3.0.tgz#b5ea48efc9c1793dccc9b4767c93914d3f2d52ba" + dependencies: + ansi-styles "^3.1.0" + escape-string-regexp "^1.0.5" + supports-color "^4.0.0" + +circular-json@^0.3.1: + version "0.3.3" + resolved "https://registry.yarnpkg.com/circular-json/-/circular-json-0.3.3.tgz#815c99ea84f6809529d2f45791bdf82711352d66" + +cli-cursor@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-2.1.0.tgz#b35dac376479facc3e94747d41d0d0f5238ffcb5" + dependencies: + restore-cursor "^2.0.0" + +cli-width@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-2.2.0.tgz#ff19ede8a9a5e579324147b0c11f0fbcbabed639" + +cliui@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/cliui/-/cliui-2.1.0.tgz#4b475760ff80264c762c3a1719032e91c7fea0d1" + dependencies: + center-align "^0.1.1" + right-align "^0.1.1" + wordwrap "0.0.2" + +clone@2.x: + version "2.1.1" + resolved "https://registry.yarnpkg.com/clone/-/clone-2.1.1.tgz#d217d1e961118e3ac9a4b8bba3285553bf647cdb" + +co@^4.6.0: + version "4.6.0" + resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184" + +code-point-at@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77" + +color-convert@^1.9.0: + version "1.9.0" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.0.tgz#1accf97dd739b983bf994d56fec8f95853641b7a" + dependencies: + color-name "^1.1.1" + +color-name@^1.1.1: + version "1.1.3" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" + +combined-stream@^1.0.5, combined-stream@~1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.5.tgz#938370a57b4a51dea2c77c15d5c5fdf895164009" + dependencies: + delayed-stream "~1.0.0" + +commander@2.9.0: + version "2.9.0" + resolved "https://registry.yarnpkg.com/commander/-/commander-2.9.0.tgz#9c99094176e12240cb22d6c5146098400fe0f7d4" + dependencies: + graceful-readlink ">= 1.0.0" + +component-emitter@^1.2.0: + version "1.2.1" + resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.2.1.tgz#137918d6d78283f7df7a6b7c5a63e140e69425e6" + +concat-map@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" + +concat-stream@^1.6.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.0.tgz#0aac662fd52be78964d5532f694784e70110acf7" + dependencies: + inherits "^2.0.3" + readable-stream "^2.2.2" + typedarray "^0.0.6" + +console-control-strings@^1.0.0, console-control-strings@~1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e" + +contains-path@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/contains-path/-/contains-path-0.1.0.tgz#fe8cf184ff6670b6baef01a9d4861a5cbec4120a" + +cookie-signature@1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.3.tgz#91cd997cc51fb641595738c69cda020328f50ff9" + +cookie@0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.1.0.tgz#90eb469ddce905c866de687efc43131d8801f9d0" + +cookie@0.3.1: + version "0.3.1" + resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.3.1.tgz#e7e0a1f9ef43b4c8ba925c5c5a96e806d16873bb" + +cookiejar@^2.0.6: + version "2.1.1" + resolved "https://registry.yarnpkg.com/cookiejar/-/cookiejar-2.1.1.tgz#41ad57b1b555951ec171412a81942b1e8200d34a" + +core-util-is@1.0.2, core-util-is@~1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" + +cross-env@^3.1.3: + version "3.2.4" + resolved "https://registry.yarnpkg.com/cross-env/-/cross-env-3.2.4.tgz#9e0585f277864ed421ce756f81a980ff0d698aba" + dependencies: + cross-spawn "^5.1.0" + is-windows "^1.0.0" + +cross-spawn@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-5.1.0.tgz#e8bd0efee58fcff6f8f94510a0a554bbfa235449" + dependencies: + lru-cache "^4.0.1" + shebang-command "^1.2.0" + which "^1.2.9" + +cryptiles@2.x.x: + version "2.0.5" + resolved "https://registry.yarnpkg.com/cryptiles/-/cryptiles-2.0.5.tgz#3bdfecdc608147c1c67202fa291e7dca59eaa3b8" + dependencies: + boom "2.x.x" + +cryptiles@3.x.x: + version "3.1.2" + resolved "https://registry.yarnpkg.com/cryptiles/-/cryptiles-3.1.2.tgz#a89fbb220f5ce25ec56e8c4aa8a4fd7b5b0d29fe" + dependencies: + boom "5.x.x" + +dashdash@^1.12.0: + version "1.14.1" + resolved "https://registry.yarnpkg.com/dashdash/-/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0" + dependencies: + assert-plus "^1.0.0" + +debug@*, debug@^3.0.1: + version "3.1.0" + resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261" + dependencies: + ms "2.0.0" + +debug@0.7.4: + version "0.7.4" + resolved "https://registry.yarnpkg.com/debug/-/debug-0.7.4.tgz#06e1ea8082c2cb14e39806e22e2f6f757f92af39" + +debug@2.6.8: + version "2.6.8" + resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.8.tgz#e731531ca2ede27d188222427da17821d68ff4fc" + dependencies: + ms "2.0.0" + +"debug@>= 0.7.3 < 1": + version "0.8.1" + resolved "https://registry.yarnpkg.com/debug/-/debug-0.8.1.tgz#20ff4d26f5e422cb68a1bacbbb61039ad8c1c130" + +debug@^2.2.0, debug@^2.6.8: + version "2.6.9" + resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" + dependencies: + ms "2.0.0" + +decamelize@^1.0.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" + +deep-extend@~0.4.0: + version "0.4.2" + resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.4.2.tgz#48b699c27e334bf89f10892be432f6e4c7d34a7f" + +deep-is@~0.1.3: + version "0.1.3" + resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34" + +del@^2.0.2: + version "2.2.2" + resolved "https://registry.yarnpkg.com/del/-/del-2.2.2.tgz#c12c981d067846c84bcaf862cff930d907ffd1a8" + dependencies: + globby "^5.0.0" + is-path-cwd "^1.0.0" + is-path-in-cwd "^1.0.0" + object-assign "^4.0.1" + pify "^2.0.0" + pinkie-promise "^2.0.0" + rimraf "^2.2.8" + +delayed-stream@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" + +delegates@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a" + +diff@3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/diff/-/diff-3.2.0.tgz#c9ce393a4b7cbd0b058a725c93df299027868ff9" + +doctrine@1.5.0: + version "1.5.0" + resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-1.5.0.tgz#379dce730f6166f76cefa4e6707a159b02c5a6fa" + dependencies: + esutils "^2.0.2" + isarray "^1.0.0" + +doctrine@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-2.0.0.tgz#c73d8d2909d22291e1a007a395804da8b665fe63" + dependencies: + esutils "^2.0.2" + isarray "^1.0.0" + +ecc-jsbn@~0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz#0fc73a9ed5f0d53c38193398523ef7e543777505" + dependencies: + jsbn "~0.1.0" + +ecdsa-sig-formatter@1.0.9: + version "1.0.9" + resolved "https://registry.yarnpkg.com/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.9.tgz#4bc926274ec3b5abb5016e7e1d60921ac262b2a1" + dependencies: + base64url "^2.0.0" + safe-buffer "^5.0.1" + +error-ex@^1.2.0: + version "1.3.1" + resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.1.tgz#f855a86ce61adc4e8621c3cda21e7a7612c3a8dc" + dependencies: + is-arrayish "^0.2.1" + +escape-html@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.1.tgz#181a286ead397a39a92857cfb1d43052e356bff0" + +escape-string-regexp@1.0.5, escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" + +escodegen@1.8.x: + version "1.8.1" + resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-1.8.1.tgz#5a5b53af4693110bebb0867aa3430dd3b70a1018" + dependencies: + esprima "^2.7.1" + estraverse "^1.9.1" + esutils "^2.0.2" + optionator "^0.8.1" + optionalDependencies: + source-map "~0.2.0" + +eslint-config-airbnb-base@^12.1.0: + version "12.1.0" + resolved "https://registry.yarnpkg.com/eslint-config-airbnb-base/-/eslint-config-airbnb-base-12.1.0.tgz#386441e54a12ccd957b0a92564a4bafebd747944" + dependencies: + eslint-restricted-globals "^0.1.1" + +eslint-import-resolver-node@^0.3.1: + version "0.3.1" + resolved "https://registry.yarnpkg.com/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.1.tgz#4422574cde66a9a7b099938ee4d508a199e0e3cc" + dependencies: + debug "^2.6.8" + resolve "^1.2.0" + +eslint-module-utils@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/eslint-module-utils/-/eslint-module-utils-2.1.1.tgz#abaec824177613b8a95b299639e1b6facf473449" + dependencies: + debug "^2.6.8" + pkg-dir "^1.0.0" + +eslint-plugin-import@^2.8.0: + version "2.8.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.8.0.tgz#fa1b6ef31fcb3c501c09859c1b86f1fc5b986894" + dependencies: + builtin-modules "^1.1.1" + contains-path "^0.1.0" + debug "^2.6.8" + doctrine "1.5.0" + eslint-import-resolver-node "^0.3.1" + eslint-module-utils "^2.1.1" + has "^1.0.1" + lodash.cond "^4.3.0" + minimatch "^3.0.3" + read-pkg-up "^2.0.0" + +eslint-restricted-globals@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/eslint-restricted-globals/-/eslint-restricted-globals-0.1.1.tgz#35f0d5cbc64c2e3ed62e93b4b1a7af05ba7ed4d7" + +eslint-scope@^3.7.1: + version "3.7.1" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-3.7.1.tgz#3d63c3edfda02e06e01a452ad88caacc7cdcb6e8" + dependencies: + esrecurse "^4.1.0" + estraverse "^4.1.1" + +eslint@^4.9.0: + version "4.9.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-4.9.0.tgz#76879d274068261b191fe0f2f56c74c2f4208e8b" + dependencies: + ajv "^5.2.0" + babel-code-frame "^6.22.0" + chalk "^2.1.0" + concat-stream "^1.6.0" + cross-spawn "^5.1.0" + debug "^3.0.1" + doctrine "^2.0.0" + eslint-scope "^3.7.1" + espree "^3.5.1" + esquery "^1.0.0" + estraverse "^4.2.0" + esutils "^2.0.2" + file-entry-cache "^2.0.0" + functional-red-black-tree "^1.0.1" + glob "^7.1.2" + globals "^9.17.0" + ignore "^3.3.3" + imurmurhash "^0.1.4" + inquirer "^3.0.6" + is-resolvable "^1.0.0" + js-yaml "^3.9.1" + json-stable-stringify "^1.0.1" + levn "^0.3.0" + lodash "^4.17.4" + minimatch "^3.0.2" + mkdirp "^0.5.1" + natural-compare "^1.4.0" + optionator "^0.8.2" + path-is-inside "^1.0.2" + pluralize "^7.0.0" + progress "^2.0.0" + require-uncached "^1.0.3" + semver "^5.3.0" + strip-ansi "^4.0.0" + strip-json-comments "~2.0.1" + table "^4.0.1" + text-table "~0.2.0" + +espree@^3.5.1: + version "3.5.1" + resolved "https://registry.yarnpkg.com/espree/-/espree-3.5.1.tgz#0c988b8ab46db53100a1954ae4ba995ddd27d87e" + dependencies: + acorn "^5.1.1" + acorn-jsx "^3.0.0" + +esprima@2.7.x, esprima@^2.7.1: + version "2.7.3" + resolved "https://registry.yarnpkg.com/esprima/-/esprima-2.7.3.tgz#96e3b70d5779f6ad49cd032673d1c312767ba581" + +esprima@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.0.tgz#4499eddcd1110e0b218bacf2fa7f7f59f55ca804" + +esquery@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.0.0.tgz#cfba8b57d7fba93f17298a8a006a04cda13d80fa" + dependencies: + estraverse "^4.0.0" + +esrecurse@^4.1.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.2.0.tgz#fa9568d98d3823f9a41d91e902dcab9ea6e5b163" + dependencies: + estraverse "^4.1.0" + object-assign "^4.0.1" + +estraverse@^1.9.1: + version "1.9.3" + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-1.9.3.tgz#af67f2dc922582415950926091a4005d29c9bb44" + +estraverse@^4.0.0, estraverse@^4.1.0, estraverse@^4.1.1, estraverse@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.2.0.tgz#0dee3fed31fcd469618ce7342099fc1afa0bdb13" + +esutils@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.2.tgz#0abf4f1caa5bcb1f7a9d8acc6dea4faaa04bac9b" + +express@~4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/express/-/express-4.0.0.tgz#274dc82933c9f574cc38a0ce5ea8172be9c6b094" + dependencies: + accepts "1.0.0" + buffer-crc32 "0.2.1" + cookie "0.1.0" + cookie-signature "1.0.3" + debug ">= 0.7.3 < 1" + escape-html "1.0.1" + fresh "0.2.2" + merge-descriptors "0.0.2" + methods "0.1.0" + parseurl "1.0.1" + path-to-regexp "0.1.2" + qs "0.6.6" + range-parser "1.0.0" + send "0.2.0" + serve-static "1.0.1" + type-is "1.0.0" + utils-merge "1.0.0" + +extend@^3.0.0, extend@~3.0.0, extend@~3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.1.tgz#a755ea7bc1adfcc5a31ce7e762dbaadc5e636444" + +external-editor@^2.0.4: + version "2.0.5" + resolved "https://registry.yarnpkg.com/external-editor/-/external-editor-2.0.5.tgz#52c249a3981b9ba187c7cacf5beb50bf1d91a6bc" + dependencies: + iconv-lite "^0.4.17" + jschardet "^1.4.2" + tmp "^0.0.33" + +extsprintf@1.3.0, extsprintf@^1.2.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.3.0.tgz#96918440e3041a7a414f8c52e3c574eb3c3e1e05" + +fast-deep-equal@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-1.0.0.tgz#96256a3bc975595eb36d82e9929d060d893439ff" + +fast-json-stable-stringify@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz#d5142c0caee6b1189f87d3a76111064f86c8bbf2" + +fast-levenshtein@~2.0.4: + version "2.0.6" + resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" + +figures@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/figures/-/figures-2.0.0.tgz#3ab1a2d2a62c8bfb431a0c94cb797a2fce27c962" + dependencies: + escape-string-regexp "^1.0.5" + +file-entry-cache@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-2.0.0.tgz#c392990c3e684783d838b8c84a45d8a048458361" + dependencies: + flat-cache "^1.2.1" + object-assign "^4.0.1" + +find-up@^1.0.0: + version "1.1.2" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-1.1.2.tgz#6b2e9822b1a2ce0a60ab64d610eccad53cb24d0f" + dependencies: + path-exists "^2.0.0" + pinkie-promise "^2.0.0" + +find-up@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-2.1.0.tgz#45d1b7e506c717ddd482775a2b77920a3c0c57a7" + dependencies: + locate-path "^2.0.0" + +flat-cache@^1.2.1: + version "1.3.0" + resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-1.3.0.tgz#d3030b32b38154f4e3b7e9c709f490f7ef97c481" + dependencies: + circular-json "^0.3.1" + del "^2.0.2" + graceful-fs "^4.1.2" + write "^0.2.1" + +forever-agent@~0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91" + +form-data@1.0.0-rc4: + version "1.0.0-rc4" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-1.0.0-rc4.tgz#05ac6bc22227b43e4461f488161554699d4f8b5e" + dependencies: + async "^1.5.2" + combined-stream "^1.0.5" + mime-types "^2.1.10" + +form-data@~2.1.1: + version "2.1.4" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.1.4.tgz#33c183acf193276ecaa98143a69e94bfee1750d1" + dependencies: + asynckit "^0.4.0" + combined-stream "^1.0.5" + mime-types "^2.1.12" + +form-data@~2.3.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.1.tgz#6fb94fbd71885306d73d15cc497fe4cc4ecd44bf" + dependencies: + asynckit "^0.4.0" + combined-stream "^1.0.5" + mime-types "^2.1.12" + +formidable@^1.0.17: + version "1.1.1" + resolved "https://registry.yarnpkg.com/formidable/-/formidable-1.1.1.tgz#96b8886f7c3c3508b932d6bd70c4d3a88f35f1a9" + +fresh@0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.2.0.tgz#bfd9402cf3df12c4a4c310c79f99a3dde13d34a7" + +fresh@0.2.2: + version "0.2.2" + resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.2.2.tgz#9731dcf5678c7faeb44fb903c4f72df55187fa77" + +fresh@~0.2.1: + version "0.2.4" + resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.2.4.tgz#3582499206c9723714190edd74b4604feb4a614c" + +fs.realpath@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" + +fstream-ignore@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/fstream-ignore/-/fstream-ignore-1.0.5.tgz#9c31dae34767018fe1d249b24dada67d092da105" + dependencies: + fstream "^1.0.0" + inherits "2" + minimatch "^3.0.0" + +fstream@^1.0.0, fstream@^1.0.10, fstream@^1.0.2: + version "1.0.11" + resolved "https://registry.yarnpkg.com/fstream/-/fstream-1.0.11.tgz#5c1fb1f117477114f0632a0eb4b71b3cb0fd3171" + dependencies: + graceful-fs "^4.1.2" + inherits "~2.0.0" + mkdirp ">=0.5 0" + rimraf "2" + +function-bind@^1.0.2: + version "1.1.1" + resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" + +functional-red-black-tree@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz#1b0ab3bd553b2a0d6399d29c0e3ea0b252078327" + +gauge@~2.7.3: + version "2.7.4" + resolved "https://registry.yarnpkg.com/gauge/-/gauge-2.7.4.tgz#2c03405c7538c39d7eb37b317022e325fb018bf7" + dependencies: + aproba "^1.0.3" + console-control-strings "^1.0.0" + has-unicode "^2.0.0" + object-assign "^4.1.0" + signal-exit "^3.0.0" + string-width "^1.0.1" + strip-ansi "^3.0.1" + wide-align "^1.1.0" + +getpass@^0.1.1: + version "0.1.7" + resolved "https://registry.yarnpkg.com/getpass/-/getpass-0.1.7.tgz#5eff8e3e684d569ae4cb2b1282604e8ba62149fa" + dependencies: + assert-plus "^1.0.0" + +glob@7.1.1: + version "7.1.1" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.1.tgz#805211df04faaf1c63a3600306cdf5ade50b2ec8" + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.0.2" + once "^1.3.0" + path-is-absolute "^1.0.0" + +glob@^5.0.15: + version "5.0.15" + resolved "https://registry.yarnpkg.com/glob/-/glob-5.0.15.tgz#1bc936b9e02f4a603fcc222ecf7633d30b8b93b1" + dependencies: + inflight "^1.0.4" + inherits "2" + minimatch "2 || 3" + once "^1.3.0" + path-is-absolute "^1.0.0" + +glob@^7.0.3, glob@^7.0.5, glob@^7.1.2: + version "7.1.2" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.2.tgz#c19c9df9a028702d678612384a6552404c636d15" + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.0.4" + once "^1.3.0" + path-is-absolute "^1.0.0" + +globals@^9.17.0: + version "9.18.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-9.18.0.tgz#aa3896b3e69b487f17e31ed2143d69a8e30c2d8a" + +globby@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/globby/-/globby-5.0.0.tgz#ebd84667ca0dbb330b99bcfc68eac2bc54370e0d" + dependencies: + array-union "^1.0.1" + arrify "^1.0.0" + glob "^7.0.3" + object-assign "^4.0.1" + pify "^2.0.0" + pinkie-promise "^2.0.0" + +graceful-fs@^4.1.2: + version "4.1.11" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.11.tgz#0e8bdfe4d1ddb8854d64e04ea7c00e2a026e5658" + +"graceful-readlink@>= 1.0.0": + version "1.0.1" + resolved "https://registry.yarnpkg.com/graceful-readlink/-/graceful-readlink-1.0.1.tgz#4cafad76bc62f02fa039b2f94e9a3dd3a391a725" + +growl@1.9.2: + version "1.9.2" + resolved "https://registry.yarnpkg.com/growl/-/growl-1.9.2.tgz#0ea7743715db8d8de2c5ede1775e1b45ac85c02f" + +handlebars@^4.0.1: + version "4.0.11" + resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.0.11.tgz#630a35dfe0294bc281edae6ffc5d329fc7982dcc" + dependencies: + async "^1.4.0" + optimist "^0.6.1" + source-map "^0.4.4" + optionalDependencies: + uglify-js "^2.6" + +har-schema@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-1.0.5.tgz#d263135f43307c02c602afc8fe95970c0151369e" + +har-schema@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-2.0.0.tgz#a94c2224ebcac04782a0d9035521f24735b7ec92" + +har-validator@~4.2.1: + version "4.2.1" + resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-4.2.1.tgz#33481d0f1bbff600dd203d75812a6a5fba002e2a" + dependencies: + ajv "^4.9.1" + har-schema "^1.0.5" + +har-validator@~5.0.3: + version "5.0.3" + resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-5.0.3.tgz#ba402c266194f15956ef15e0fcf242993f6a7dfd" + dependencies: + ajv "^5.1.0" + har-schema "^2.0.0" + +has-ansi@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/has-ansi/-/has-ansi-2.0.0.tgz#34f5049ce1ecdf2b0649af3ef24e45ed35416d91" + dependencies: + ansi-regex "^2.0.0" + +has-flag@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-1.0.0.tgz#9d9e793165ce017a00f00418c43f942a7b1d11fa" + +has-flag@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-2.0.0.tgz#e8207af1cc7b30d446cc70b734b5e8be18f88d51" + +has-unicode@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9" + +has@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/has/-/has-1.0.1.tgz#8461733f538b0837c9361e39a9ab9e9704dc2f28" + dependencies: + function-bind "^1.0.2" + +hawk@3.1.3, hawk@~3.1.3: + version "3.1.3" + resolved "https://registry.yarnpkg.com/hawk/-/hawk-3.1.3.tgz#078444bd7c1640b0fe540d2c9b73d59678e8e1c4" + dependencies: + boom "2.x.x" + cryptiles "2.x.x" + hoek "2.x.x" + sntp "1.x.x" + +hawk@~6.0.2: + version "6.0.2" + resolved "https://registry.yarnpkg.com/hawk/-/hawk-6.0.2.tgz#af4d914eb065f9b5ce4d9d11c1cb2126eecc3038" + dependencies: + boom "4.x.x" + cryptiles "3.x.x" + hoek "4.x.x" + sntp "2.x.x" + +he@1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/he/-/he-1.1.1.tgz#93410fd21b009735151f8868c2f271f3427e23fd" + +hoek@2.x.x: + version "2.16.3" + resolved "https://registry.yarnpkg.com/hoek/-/hoek-2.16.3.tgz#20bb7403d3cea398e91dc4710a8ff1b8274a25ed" + +hoek@4.x.x: + version "4.2.0" + resolved "https://registry.yarnpkg.com/hoek/-/hoek-4.2.0.tgz#72d9d0754f7fe25ca2d01ad8f8f9a9449a89526d" + +hooks@0.3.2: + version "0.3.2" + resolved "https://registry.yarnpkg.com/hooks/-/hooks-0.3.2.tgz#a31f060c2026cea6cf1ca3eb178430e718e1c4a3" + +hosted-git-info@^2.1.4: + version "2.5.0" + resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.5.0.tgz#6d60e34b3abbc8313062c3b798ef8d901a07af3c" + +http-signature@~1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.1.1.tgz#df72e267066cd0ac67fb76adf8e134a8fbcf91bf" + dependencies: + assert-plus "^0.2.0" + jsprim "^1.2.2" + sshpk "^1.7.0" + +http-signature@~1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.2.0.tgz#9aecd925114772f3d95b65a60abb8f7c18fbace1" + dependencies: + assert-plus "^1.0.0" + jsprim "^1.2.2" + sshpk "^1.7.0" + +iconv-lite@^0.4.17: + version "0.4.19" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.19.tgz#f7468f60135f5e5dad3399c0a81be9a1603a082b" + +ignore@^3.3.3: + version "3.3.6" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-3.3.6.tgz#b6f3196b38ed92f0c86e52f6f79b7fc4c8266c8d" + +imurmurhash@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" + +inflight@^1.0.4: + version "1.0.6" + resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" + dependencies: + once "^1.3.0" + wrappy "1" + +inherits@2, inherits@^2.0.3, inherits@~2.0.0, inherits@~2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" + +ini@~1.3.0: + version "1.3.4" + resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.4.tgz#0537cb79daf59b59a1a517dff706c86ec039162e" + +inquirer@^3.0.6: + version "3.3.0" + resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-3.3.0.tgz#9dd2f2ad765dcab1ff0443b491442a20ba227dc9" + dependencies: + ansi-escapes "^3.0.0" + chalk "^2.0.0" + cli-cursor "^2.1.0" + cli-width "^2.0.0" + external-editor "^2.0.4" + figures "^2.0.0" + lodash "^4.3.0" + mute-stream "0.0.7" + run-async "^2.2.0" + rx-lite "^4.0.8" + rx-lite-aggregates "^4.0.8" + string-width "^2.1.0" + strip-ansi "^4.0.0" + through "^2.3.6" + +is-arrayish@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" + +is-buffer@^1.1.5: + version "1.1.6" + resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be" + +is-builtin-module@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-builtin-module/-/is-builtin-module-1.0.0.tgz#540572d34f7ac3119f8f76c30cbc1b1e037affbe" + dependencies: + builtin-modules "^1.0.0" + +is-fullwidth-code-point@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz#ef9e31386f031a7f0d643af82fde50c457ef00cb" + dependencies: + number-is-nan "^1.0.0" + +is-fullwidth-code-point@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f" + +is-path-cwd@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-path-cwd/-/is-path-cwd-1.0.0.tgz#d225ec23132e89edd38fda767472e62e65f1106d" + +is-path-in-cwd@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-path-in-cwd/-/is-path-in-cwd-1.0.0.tgz#6477582b8214d602346094567003be8a9eac04dc" + dependencies: + is-path-inside "^1.0.0" + +is-path-inside@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-1.0.0.tgz#fc06e5a1683fbda13de667aff717bbc10a48f37f" + dependencies: + path-is-inside "^1.0.1" + +is-promise@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-promise/-/is-promise-2.1.0.tgz#79a2a9ece7f096e80f36d2b2f3bc16c1ff4bf3fa" + +is-resolvable@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-resolvable/-/is-resolvable-1.0.0.tgz#8df57c61ea2e3c501408d100fb013cf8d6e0cc62" + dependencies: + tryit "^1.0.1" + +is-typedarray@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" + +is-windows@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.1.tgz#310db70f742d259a16a369202b51af84233310d9" + +isarray@^1.0.0, isarray@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" + +isexe@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" + +isstream@~0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a" + +istanbul@^0.4.5: + version "0.4.5" + resolved "https://registry.yarnpkg.com/istanbul/-/istanbul-0.4.5.tgz#65c7d73d4c4da84d4f3ac310b918fb0b8033733b" + dependencies: + abbrev "1.0.x" + async "1.x" + escodegen "1.8.x" + esprima "2.7.x" + glob "^5.0.15" + handlebars "^4.0.1" + js-yaml "3.x" + mkdirp "0.5.x" + nopt "3.x" + once "1.x" + resolve "1.1.x" + supports-color "^3.1.0" + which "^1.1.1" + wordwrap "^1.0.0" + +js-tokens@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-3.0.2.tgz#9866df395102130e38f7f996bceb65443209c25b" + +js-yaml@3.x, js-yaml@^3.9.1: + version "3.10.0" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.10.0.tgz#2e78441646bd4682e963f22b6e92823c309c62dc" + dependencies: + argparse "^1.0.7" + esprima "^4.0.0" + +jsbn@~0.1.0: + version "0.1.1" + resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513" + +jschardet@^1.4.2: + version "1.5.1" + resolved "https://registry.yarnpkg.com/jschardet/-/jschardet-1.5.1.tgz#c519f629f86b3a5bedba58a88d311309eec097f9" + +json-schema-traverse@^0.3.0: + version "0.3.1" + resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz#349a6d44c53a51de89b40805c5d5e59b417d3340" + +json-schema@0.2.3: + version "0.2.3" + resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.2.3.tgz#b480c892e59a2f05954ce727bd3f2a4e882f9e13" + +json-stable-stringify@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz#9a759d39c5f2ff503fd5300646ed445f88c4f9af" + dependencies: + jsonify "~0.0.0" + +json-stringify-safe@~5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" + +json3@3.3.2: + version "3.3.2" + resolved "https://registry.yarnpkg.com/json3/-/json3-3.3.2.tgz#3c0434743df93e2f5c42aee7b19bcb483575f4e1" + +jsonify@~0.0.0: + version "0.0.0" + resolved "https://registry.yarnpkg.com/jsonify/-/jsonify-0.0.0.tgz#2c74b6ee41d93ca51b7b5aaee8f503631d252a73" + +jsonwebtoken@^8.0.1: + version "8.1.0" + resolved "https://registry.yarnpkg.com/jsonwebtoken/-/jsonwebtoken-8.1.0.tgz#c6397cd2e5fd583d65c007a83dc7bb78e6982b83" + dependencies: + jws "^3.1.4" + lodash.includes "^4.3.0" + lodash.isboolean "^3.0.3" + lodash.isinteger "^4.0.4" + lodash.isnumber "^3.0.3" + lodash.isplainobject "^4.0.6" + lodash.isstring "^4.0.1" + lodash.once "^4.0.0" + ms "^2.0.0" + xtend "^4.0.1" + +jsprim@^1.2.2: + version "1.4.1" + resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.1.tgz#313e66bc1e5cc06e438bc1b7499c2e5c56acb6a2" + dependencies: + assert-plus "1.0.0" + extsprintf "1.3.0" + json-schema "0.2.3" + verror "1.10.0" + +jwa@^1.1.4: + version "1.1.5" + resolved "https://registry.yarnpkg.com/jwa/-/jwa-1.1.5.tgz#a0552ce0220742cd52e153774a32905c30e756e5" + dependencies: + base64url "2.0.0" + buffer-equal-constant-time "1.0.1" + ecdsa-sig-formatter "1.0.9" + safe-buffer "^5.0.1" + +jws@^3.1.4: + version "3.1.4" + resolved "https://registry.yarnpkg.com/jws/-/jws-3.1.4.tgz#f9e8b9338e8a847277d6444b1464f61880e050a2" + dependencies: + base64url "^2.0.0" + jwa "^1.1.4" + safe-buffer "^5.0.1" + +kareem@0.0.4: + version "0.0.4" + resolved "https://registry.yarnpkg.com/kareem/-/kareem-0.0.4.tgz#a8475defd74cf829b0071d20e6971bf15d911d2b" + +kerberos@0.0.4: + version "0.0.4" + resolved "https://registry.yarnpkg.com/kerberos/-/kerberos-0.0.4.tgz#11836638f729a2f6c5bae056a7d7a15898c9ba7c" + +kind-of@^3.0.2: + version "3.2.2" + resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-3.2.2.tgz#31ea21a734bab9bbb0f32466d893aea51e4a3c64" + dependencies: + is-buffer "^1.1.5" + +lazy-cache@^1.0.3: + version "1.0.4" + resolved "https://registry.yarnpkg.com/lazy-cache/-/lazy-cache-1.0.4.tgz#a1d78fc3a50474cb80845d3b3b6e1da49a446e8e" + +levn@^0.3.0, levn@~0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/levn/-/levn-0.3.0.tgz#3b09924edf9f083c0490fdd4c0bc4421e04764ee" + dependencies: + prelude-ls "~1.1.2" + type-check "~0.3.2" + +load-json-file@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-2.0.0.tgz#7947e42149af80d696cbf797bcaabcfe1fe29ca8" + dependencies: + graceful-fs "^4.1.2" + parse-json "^2.2.0" + pify "^2.0.0" + strip-bom "^3.0.0" + +locate-path@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-2.0.0.tgz#2b568b265eec944c6d9c0de9c3dbbbca0354cd8e" + dependencies: + p-locate "^2.0.0" + path-exists "^3.0.0" + +lodash._baseassign@^3.0.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/lodash._baseassign/-/lodash._baseassign-3.2.0.tgz#8c38a099500f215ad09e59f1722fd0c52bfe0a4e" + dependencies: + lodash._basecopy "^3.0.0" + lodash.keys "^3.0.0" + +lodash._basecopy@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/lodash._basecopy/-/lodash._basecopy-3.0.1.tgz#8da0e6a876cf344c0ad8a54882111dd3c5c7ca36" + +lodash._basecreate@^3.0.0: + version "3.0.3" + resolved "https://registry.yarnpkg.com/lodash._basecreate/-/lodash._basecreate-3.0.3.tgz#1bc661614daa7fc311b7d03bf16806a0213cf821" + +lodash._getnative@^3.0.0: + version "3.9.1" + resolved "https://registry.yarnpkg.com/lodash._getnative/-/lodash._getnative-3.9.1.tgz#570bc7dede46d61cdcde687d65d3eecbaa3aaff5" + +lodash._isiterateecall@^3.0.0: + version "3.0.9" + resolved "https://registry.yarnpkg.com/lodash._isiterateecall/-/lodash._isiterateecall-3.0.9.tgz#5203ad7ba425fae842460e696db9cf3e6aac057c" + +lodash.cond@^4.3.0: + version "4.5.2" + resolved "https://registry.yarnpkg.com/lodash.cond/-/lodash.cond-4.5.2.tgz#f471a1da486be60f6ab955d17115523dd1d255d5" + +lodash.create@3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/lodash.create/-/lodash.create-3.1.1.tgz#d7f2849f0dbda7e04682bb8cd72ab022461debe7" + dependencies: + lodash._baseassign "^3.0.0" + lodash._basecreate "^3.0.0" + lodash._isiterateecall "^3.0.0" + +lodash.includes@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/lodash.includes/-/lodash.includes-4.3.0.tgz#60bb98a87cb923c68ca1e51325483314849f553f" + +lodash.isarguments@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz#2f573d85c6a24289ff00663b491c1d338ff3458a" + +lodash.isarray@^3.0.0: + version "3.0.4" + resolved "https://registry.yarnpkg.com/lodash.isarray/-/lodash.isarray-3.0.4.tgz#79e4eb88c36a8122af86f844aa9bcd851b5fbb55" + +lodash.isboolean@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz#6c2e171db2a257cd96802fd43b01b20d5f5870f6" + +lodash.isinteger@^4.0.4: + version "4.0.4" + resolved "https://registry.yarnpkg.com/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz#619c0af3d03f8b04c31f5882840b77b11cd68343" + +lodash.isnumber@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz#3ce76810c5928d03352301ac287317f11c0b1ffc" + +lodash.isplainobject@^4.0.6: + version "4.0.6" + resolved "https://registry.yarnpkg.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz#7c526a52d89b45c45cc690b88163be0497f550cb" + +lodash.isstring@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/lodash.isstring/-/lodash.isstring-4.0.1.tgz#d527dfb5456eca7cc9bb95d5daeaf88ba54a5451" + +lodash.keys@^3.0.0: + version "3.1.2" + resolved "https://registry.yarnpkg.com/lodash.keys/-/lodash.keys-3.1.2.tgz#4dbc0472b156be50a0b286855d1bd0b0c656098a" + dependencies: + lodash._getnative "^3.0.0" + lodash.isarguments "^3.0.0" + lodash.isarray "^3.0.0" + +lodash.once@^4.0.0: + version "4.1.1" + resolved "https://registry.yarnpkg.com/lodash.once/-/lodash.once-4.1.1.tgz#0dd3971213c7c56df880977d504c88fb471a97ac" + +lodash@4.x, lodash@^4.13.1, lodash@^4.17.4, lodash@^4.3.0: + version "4.17.4" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.4.tgz#78203a4d1c328ae1d86dca6460e369b57f4055ae" + +longest@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/longest/-/longest-1.0.1.tgz#30a0b2da38f73770e8294a0d22e6625ed77d0097" + +lru-cache@^4.0.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.1.1.tgz#622e32e82488b49279114a4f9ecf45e7cd6bba55" + dependencies: + pseudomap "^1.0.2" + yallist "^2.1.2" + +lsmod@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/lsmod/-/lsmod-1.0.0.tgz#9a00f76dca36eb23fa05350afe1b585d4299e64b" + +merge-descriptors@0.0.2: + version "0.0.2" + resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-0.0.2.tgz#c36a52a781437513c57275f39dd9d317514ac8c7" + +methods@0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/methods/-/methods-0.1.0.tgz#335d429eefd21b7bacf2e9c922a8d2bd14a30e4f" + +methods@1.x, methods@^1.1.1: + version "1.1.2" + resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" + +mime-db@~1.30.0: + version "1.30.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.30.0.tgz#74c643da2dd9d6a45399963465b26d5ca7d71f01" + +mime-types@^2.1.10, mime-types@^2.1.12, mime-types@~2.1.17, mime-types@~2.1.7: + version "2.1.17" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.17.tgz#09d7a393f03e995a79f8af857b70a9e0ab16557a" + dependencies: + mime-db "~1.30.0" + +mime@^1.3.4: + version "1.4.1" + resolved "https://registry.yarnpkg.com/mime/-/mime-1.4.1.tgz#121f9ebc49e3766f311a76e1fa1c8003c4b03aa6" + +mime@~1.2.11, mime@~1.2.9: + version "1.2.11" + resolved "https://registry.yarnpkg.com/mime/-/mime-1.2.11.tgz#58203eed86e3a5ef17aed2b7d9ebd47f0a60dd10" + +mimic-fn@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-1.1.0.tgz#e667783d92e89dbd342818b5230b9d62a672ad18" + +"minimatch@2 || 3", minimatch@^3.0.0, minimatch@^3.0.2, minimatch@^3.0.3, minimatch@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" + dependencies: + brace-expansion "^1.1.7" + +minimist@0.0.8: + version "0.0.8" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d" + +minimist@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284" + +minimist@~0.0.1: + version "0.0.10" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.10.tgz#de3f98543dbf96082be48ad1a0c7cda836301dcf" + +mkdirp@0.5.1, mkdirp@0.5.x, "mkdirp@>=0.5 0", mkdirp@^0.5.1: + version "0.5.1" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903" + dependencies: + minimist "0.0.8" + +mocha@^3.1.0: + version "3.5.3" + resolved "https://registry.yarnpkg.com/mocha/-/mocha-3.5.3.tgz#1e0480fe36d2da5858d1eb6acc38418b26eaa20d" + dependencies: + browser-stdout "1.3.0" + commander "2.9.0" + debug "2.6.8" + diff "3.2.0" + escape-string-regexp "1.0.5" + glob "7.1.1" + growl "1.9.2" + he "1.1.1" + json3 "3.3.2" + lodash.create "3.1.1" + mkdirp "0.5.1" + supports-color "3.1.2" + +mongodb@1.4.12: + version "1.4.12" + resolved "https://registry.yarnpkg.com/mongodb/-/mongodb-1.4.12.tgz#65cdd46ec127861e941168fdccf82bf17ad71c4d" + dependencies: + bson "~0.2" + optionalDependencies: + kerberos "0.0.4" + readable-stream latest + +mongoose@^3.6.13: + version "3.9.7" + resolved "https://registry.yarnpkg.com/mongoose/-/mongoose-3.9.7.tgz#b315e6ebe5cefcce3843504b791f038828048da6" + dependencies: + async "0.9.0" + hooks "0.3.2" + kareem "0.0.4" + mongodb "1.4.12" + mpath "0.1.1" + mpromise "0.5.4" + mquery "1.0.0" + ms "0.1.0" + muri "0.3.1" + regexp-clone "0.0.1" + sliced "0.0.5" + +moviedb@^0.2.10: + version "0.2.10" + resolved "https://registry.yarnpkg.com/moviedb/-/moviedb-0.2.10.tgz#53238d403608478b8ba69e8d8dad19e3f0af78e8" + dependencies: + superagent "^2.3.0" + +mpath@0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/mpath/-/mpath-0.1.1.tgz#23da852b7c232ee097f4759d29c0ee9cd22d5e46" + +mpromise@0.5.4: + version "0.5.4" + resolved "https://registry.yarnpkg.com/mpromise/-/mpromise-0.5.4.tgz#b610613ec6de37419f944b35f0783b4de9f5dc75" + +mquery@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/mquery/-/mquery-1.0.0.tgz#6940a46d643368fe8e5abddeb94bd8dd32013f5b" + dependencies: + debug "0.7.4" + regexp-clone "0.0.1" + sliced "0.0.5" + +ms@0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/ms/-/ms-0.1.0.tgz#f21fac490daf1d7667fd180fe9077389cc9442b2" + +ms@2.0.0, ms@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" + +muri@0.3.1: + version "0.3.1" + resolved "https://registry.yarnpkg.com/muri/-/muri-0.3.1.tgz#861889c5c857f1a43700bee85d50731f61727c9a" + +mute-stream@0.0.7: + version "0.0.7" + resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.7.tgz#3075ce93bc21b8fab43e1bc4da7e8115ed1e7bab" + +nan@~1.8: + version "1.8.4" + resolved "https://registry.yarnpkg.com/nan/-/nan-1.8.4.tgz#3c76b5382eab33e44b758d2813ca9d92e9342f34" + +nan@~2.7.0: + version "2.7.0" + resolved "https://registry.yarnpkg.com/nan/-/nan-2.7.0.tgz#d95bf721ec877e08db276ed3fc6eb78f9083ad46" + +natural-compare@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" + +negotiator@~0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.3.0.tgz#706d692efeddf574d57ea9fb1ab89a4fa7ee8f60" + +node-cache@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/node-cache/-/node-cache-4.1.1.tgz#08524645ee4039dedc3dcc1dd7c6b979e0619e44" + dependencies: + clone "2.x" + lodash "4.x" + +node-pre-gyp@~0.6.38: + version "0.6.38" + resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.6.38.tgz#e92a20f83416415bb4086f6d1fb78b3da73d113d" + dependencies: + hawk "3.1.3" + mkdirp "^0.5.1" + nopt "^4.0.1" + npmlog "^4.0.2" + rc "^1.1.7" + request "2.81.0" + rimraf "^2.6.1" + semver "^5.3.0" + tar "^2.2.1" + tar-pack "^3.4.0" + +nodemailer@^4.0.1: + version "4.2.0" + resolved "https://registry.yarnpkg.com/nodemailer/-/nodemailer-4.2.0.tgz#310781d30130bc5b7bf756bb626ec27564c5079b" + +nopt@3.x: + version "3.0.6" + resolved "https://registry.yarnpkg.com/nopt/-/nopt-3.0.6.tgz#c6465dbf08abcd4db359317f79ac68a646b28ff9" + dependencies: + abbrev "1" + +nopt@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/nopt/-/nopt-4.0.1.tgz#d0d4685afd5415193c8c7505602d0d17cd64474d" + dependencies: + abbrev "1" + osenv "^0.1.4" + +normalize-package-data@^2.3.2: + version "2.4.0" + resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.4.0.tgz#12f95a307d58352075a04907b84ac8be98ac012f" + dependencies: + hosted-git-info "^2.1.4" + is-builtin-module "^1.0.0" + semver "2 || 3 || 4 || 5" + validate-npm-package-license "^3.0.1" + +npmlog@^4.0.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-4.1.2.tgz#08a7f2a8bf734604779a9efa4ad5cc717abb954b" + dependencies: + are-we-there-yet "~1.1.2" + console-control-strings "~1.1.0" + gauge "~2.7.3" + set-blocking "~2.0.0" + +number-is-nan@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/number-is-nan/-/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d" + +oauth-sign@~0.8.1, oauth-sign@~0.8.2: + version "0.8.2" + resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.8.2.tgz#46a6ab7f0aead8deae9ec0565780b7d4efeb9d43" + +object-assign@^4.0.1, object-assign@^4.1.0: + version "4.1.1" + resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" + +once@1.x, once@^1.3.0, once@^1.3.3: + version "1.4.0" + resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" + dependencies: + wrappy "1" + +onetime@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/onetime/-/onetime-2.0.1.tgz#067428230fd67443b2794b22bba528b6867962d4" + dependencies: + mimic-fn "^1.0.0" + +optimist@^0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/optimist/-/optimist-0.6.1.tgz#da3ea74686fa21a19a111c326e90eb15a0196686" + dependencies: + minimist "~0.0.1" + wordwrap "~0.0.2" + +optionator@^0.8.1, optionator@^0.8.2: + version "0.8.2" + resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.8.2.tgz#364c5e409d3f4d6301d6c0b4c05bba50180aeb64" + dependencies: + deep-is "~0.1.3" + fast-levenshtein "~2.0.4" + levn "~0.3.0" + prelude-ls "~1.1.2" + type-check "~0.3.2" + wordwrap "~1.0.0" + +os-homedir@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/os-homedir/-/os-homedir-1.0.2.tgz#ffbc4988336e0e833de0c168c7ef152121aa7fb3" + +os-tmpdir@^1.0.0, os-tmpdir@~1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" + +osenv@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/osenv/-/osenv-0.1.4.tgz#42fe6d5953df06c8064be6f176c3d05aaaa34644" + dependencies: + os-homedir "^1.0.0" + os-tmpdir "^1.0.0" + +p-limit@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-1.1.0.tgz#b07ff2d9a5d88bec806035895a2bab66a27988bc" + +p-locate@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-2.0.0.tgz#20a0103b222a70c8fd39cc2e580680f3dde5ec43" + dependencies: + p-limit "^1.1.0" + +parse-json@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-2.2.0.tgz#f480f40434ef80741f8469099f8dea18f55a4dc9" + dependencies: + error-ex "^1.2.0" + +parseurl@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.0.1.tgz#2e57dce6efdd37c3518701030944c22bf388b7b4" + +path-exists@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-2.1.0.tgz#0feb6c64f0fc518d9a754dd5efb62c7022761f4b" + dependencies: + pinkie-promise "^2.0.0" + +path-exists@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-3.0.0.tgz#ce0ebeaa5f78cb18925ea7d810d7b59b010fd515" + +path-is-absolute@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" + +path-is-inside@^1.0.1, path-is-inside@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/path-is-inside/-/path-is-inside-1.0.2.tgz#365417dede44430d1c11af61027facf074bdfc53" + +path-parse@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.5.tgz#3c1adf871ea9cd6c9431b6ea2bd74a0ff055c4c1" + +path-to-regexp@0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.2.tgz#9b2b151f9cc3018c9eea50ca95729e05781712b4" + +path-type@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/path-type/-/path-type-2.0.0.tgz#f012ccb8415b7096fc2daa1054c3d72389594c73" + dependencies: + pify "^2.0.0" + +performance-now@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-0.2.0.tgz#33ef30c5c77d4ea21c5a53869d91b56d8f2555e5" + +performance-now@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b" + +pify@^2.0.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c" + +pinkie-promise@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/pinkie-promise/-/pinkie-promise-2.0.1.tgz#2135d6dfa7a358c069ac9b178776288228450ffa" + dependencies: + pinkie "^2.0.0" + +pinkie@^2.0.0: + version "2.0.4" + resolved "https://registry.yarnpkg.com/pinkie/-/pinkie-2.0.4.tgz#72556b80cfa0d48a974e80e77248e80ed4f7f870" + +pkg-dir@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-1.0.0.tgz#7a4b508a8d5bb2d629d447056ff4e9c9314cf3d4" + dependencies: + find-up "^1.0.0" + +pluralize@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/pluralize/-/pluralize-7.0.0.tgz#298b89df8b93b0221dbf421ad2b1b1ea23fc6777" + +prelude-ls@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54" + +process-nextick-args@~1.0.6: + version "1.0.7" + resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-1.0.7.tgz#150e20b756590ad3f91093f25a4f2ad8bff30ba3" + +progress@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.0.tgz#8a1be366bf8fc23db2bd23f10c6fe920b4389d1f" + +pseudomap@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/pseudomap/-/pseudomap-1.0.2.tgz#f052a28da70e618917ef0a8ac34c1ae5a68286b3" + +punycode@^1.4.1: + version "1.4.1" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e" + +python-shell@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/python-shell/-/python-shell-0.4.0.tgz#259c5470d885292b22e906a57b085f651752f956" + +qs@0.6.6, qs@~0.6.6: + version "0.6.6" + resolved "https://registry.yarnpkg.com/qs/-/qs-0.6.6.tgz#6e015098ff51968b8a3c819001d5f2c89bc4b107" + +qs@^6.1.0, qs@~6.5.1: + version "6.5.1" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.1.tgz#349cdf6eef89ec45c12d7d5eb3fc0c870343a6d8" + +qs@~6.4.0: + version "6.4.0" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.4.0.tgz#13e26d28ad6b0ffaa91312cd3bf708ed351e7233" + +range-parser@0.0.4: + version "0.0.4" + resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-0.0.4.tgz#c0427ffef51c10acba0782a46c9602e744ff620b" + +range-parser@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.0.0.tgz#a4b264cfe0be5ce36abe3765ac9c2a248746dbc0" + +range-parser@~1.0.0: + version "1.0.3" + resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.0.3.tgz#6872823535c692e2c2a0103826afd82c2e0ff175" + +raven@^2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/raven/-/raven-2.2.1.tgz#57c7fbe68a80147ec527def3d7c01575cf948fe3" + dependencies: + cookie "0.3.1" + lsmod "1.0.0" + stack-trace "0.0.9" + timed-out "4.0.1" + uuid "3.0.0" + +raw-body@~1.1.2: + version "1.1.7" + resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-1.1.7.tgz#1d027c2bfa116acc6623bca8f00016572a87d425" + dependencies: + bytes "1" + string_decoder "0.10" + +rc@^1.1.7: + version "1.2.2" + resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.2.tgz#d8ce9cb57e8d64d9c7badd9876c7c34cbe3c7077" + dependencies: + deep-extend "~0.4.0" + ini "~1.3.0" + minimist "^1.2.0" + strip-json-comments "~2.0.1" + +read-pkg-up@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-2.0.0.tgz#6b72a8048984e0c41e79510fd5e9fa99b3b549be" + dependencies: + find-up "^2.0.0" + read-pkg "^2.0.0" + +read-pkg@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-2.0.0.tgz#8ef1c0623c6a6db0dc6713c4bfac46332b2368f8" + dependencies: + load-json-file "^2.0.0" + normalize-package-data "^2.3.2" + path-type "^2.0.0" + +readable-stream@^2.0.5, readable-stream@^2.0.6, readable-stream@^2.1.4, readable-stream@^2.2.2, readable-stream@latest: + version "2.3.3" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.3.tgz#368f2512d79f9d46fdfc71349ae7878bbc1eb95c" + dependencies: + core-util-is "~1.0.0" + inherits "~2.0.3" + isarray "~1.0.0" + process-nextick-args "~1.0.6" + safe-buffer "~5.1.1" + string_decoder "~1.0.3" + util-deprecate "~1.0.1" + +regexp-clone@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/regexp-clone/-/regexp-clone-0.0.1.tgz#a7c2e09891fdbf38fbb10d376fb73003e68ac589" + +repeat-string@^1.5.2: + version "1.6.1" + resolved "https://registry.yarnpkg.com/repeat-string/-/repeat-string-1.6.1.tgz#8dcae470e1c88abc2d600fff4a776286da75e637" + +request-promise-core@1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/request-promise-core/-/request-promise-core-1.1.1.tgz#3eee00b2c5aa83239cfb04c5700da36f81cd08b6" + dependencies: + lodash "^4.13.1" + +request-promise@^4.2: + version "4.2.2" + resolved "https://registry.yarnpkg.com/request-promise/-/request-promise-4.2.2.tgz#d1ea46d654a6ee4f8ee6a4fea1018c22911904b4" + dependencies: + bluebird "^3.5.0" + request-promise-core "1.1.1" + stealthy-require "^1.1.0" + tough-cookie ">=2.3.3" + +request@2.81.0: + version "2.81.0" + resolved "https://registry.yarnpkg.com/request/-/request-2.81.0.tgz#c6928946a0e06c5f8d6f8a9333469ffda46298a0" + dependencies: + aws-sign2 "~0.6.0" + aws4 "^1.2.1" + caseless "~0.12.0" + combined-stream "~1.0.5" + extend "~3.0.0" + forever-agent "~0.6.1" + form-data "~2.1.1" + har-validator "~4.2.1" + hawk "~3.1.3" + http-signature "~1.1.0" + is-typedarray "~1.0.0" + isstream "~0.1.2" + json-stringify-safe "~5.0.1" + mime-types "~2.1.7" + oauth-sign "~0.8.1" + performance-now "^0.2.0" + qs "~6.4.0" + safe-buffer "^5.0.1" + stringstream "~0.0.4" + tough-cookie "~2.3.0" + tunnel-agent "^0.6.0" + uuid "^3.0.0" + +request@^2.81.0: + version "2.83.0" + resolved "https://registry.yarnpkg.com/request/-/request-2.83.0.tgz#ca0b65da02ed62935887808e6f510381034e3356" + dependencies: + aws-sign2 "~0.7.0" + aws4 "^1.6.0" + caseless "~0.12.0" + combined-stream "~1.0.5" + extend "~3.0.1" + forever-agent "~0.6.1" + form-data "~2.3.1" + har-validator "~5.0.3" + hawk "~6.0.2" + http-signature "~1.2.0" + is-typedarray "~1.0.0" + isstream "~0.1.2" + json-stringify-safe "~5.0.1" + mime-types "~2.1.17" + oauth-sign "~0.8.2" + performance-now "^2.1.0" + qs "~6.5.1" + safe-buffer "^5.1.1" + stringstream "~0.0.5" + tough-cookie "~2.3.3" + tunnel-agent "^0.6.0" + uuid "^3.1.0" + +require-uncached@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/require-uncached/-/require-uncached-1.0.3.tgz#4e0d56d6c9662fd31e43011c4b95aa49955421d3" + dependencies: + caller-path "^0.1.0" + resolve-from "^1.0.0" + +resolve-from@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-1.0.1.tgz#26cbfe935d1aeeeabb29bc3fe5aeb01e93d44226" + +resolve@1.1.x: + version "1.1.7" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.1.7.tgz#203114d82ad2c5ed9e8e0411b3932875e889e97b" + +resolve@^1.2.0: + version "1.5.0" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.5.0.tgz#1f09acce796c9a762579f31b2c1cc4c3cddf9f36" + dependencies: + path-parse "^1.0.5" + +restore-cursor@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-2.0.0.tgz#9f7ee287f82fd326d4fd162923d62129eee0dfaf" + dependencies: + onetime "^2.0.0" + signal-exit "^3.0.2" + +right-align@^0.1.1: + version "0.1.3" + resolved "https://registry.yarnpkg.com/right-align/-/right-align-0.1.3.tgz#61339b722fe6a3515689210d24e14c96148613ef" + dependencies: + align-text "^0.1.1" + +rimraf@2, rimraf@^2.2.8, rimraf@^2.5.1, rimraf@^2.6.1: + version "2.6.2" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.2.tgz#2ed8150d24a16ea8651e6d6ef0f47c4158ce7a36" + dependencies: + glob "^7.0.5" + +run-async@^2.2.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/run-async/-/run-async-2.3.0.tgz#0371ab4ae0bdd720d4166d7dfda64ff7a445a6c0" + dependencies: + is-promise "^2.1.0" + +rx-lite-aggregates@^4.0.8: + version "4.0.8" + resolved "https://registry.yarnpkg.com/rx-lite-aggregates/-/rx-lite-aggregates-4.0.8.tgz#753b87a89a11c95467c4ac1626c4efc4e05c67be" + dependencies: + rx-lite "*" + +rx-lite@*, rx-lite@^4.0.8: + version "4.0.8" + resolved "https://registry.yarnpkg.com/rx-lite/-/rx-lite-4.0.8.tgz#0b1e11af8bc44836f04a6407e92da42467b79444" + +safe-buffer@^5.0.1, safe-buffer@^5.1.1, safe-buffer@~5.1.0, safe-buffer@~5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.1.tgz#893312af69b2123def71f57889001671eeb2c853" + +"semver@2 || 3 || 4 || 5", semver@^5.3.0: + version "5.4.1" + resolved "https://registry.yarnpkg.com/semver/-/semver-5.4.1.tgz#e059c09d8571f0540823733433505d3a2f00b18e" + +send@0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/send/-/send-0.1.4.tgz#be70d8d1be01de61821af13780b50345a4f71abd" + dependencies: + debug "*" + fresh "0.2.0" + mime "~1.2.9" + range-parser "0.0.4" + +send@0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/send/-/send-0.2.0.tgz#067abf45cff8bffb29cbdb7439725b32388a2c58" + dependencies: + debug "*" + fresh "~0.2.1" + mime "~1.2.9" + range-parser "~1.0.0" + +serve-static@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.0.1.tgz#10dcbfd44b3e0291a131fc9ab4ab25a9f5a78a42" + dependencies: + send "0.1.4" + +set-blocking@~2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" + +shebang-command@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea" + dependencies: + shebang-regex "^1.0.0" + +shebang-regex@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3" + +signal-exit@^3.0.0, signal-exit@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.2.tgz#b5fdc08f1287ea1178628e415e25132b73646c6d" + +slice-ansi@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-1.0.0.tgz#044f1a49d8842ff307aad6b505ed178bd950134d" + dependencies: + is-fullwidth-code-point "^2.0.0" + +sliced@0.0.5: + version "0.0.5" + resolved "https://registry.yarnpkg.com/sliced/-/sliced-0.0.5.tgz#5edc044ca4eb6f7816d50ba2fc63e25d8fe4707f" + +sntp@1.x.x: + version "1.0.9" + resolved "https://registry.yarnpkg.com/sntp/-/sntp-1.0.9.tgz#6541184cc90aeea6c6e7b35e2659082443c66198" + dependencies: + hoek "2.x.x" + +sntp@2.x.x: + version "2.0.2" + resolved "https://registry.yarnpkg.com/sntp/-/sntp-2.0.2.tgz#5064110f0af85f7cfdb7d6b67a40028ce52b4b2b" + dependencies: + hoek "4.x.x" + +source-map@^0.4.4: + version "0.4.4" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.4.4.tgz#eba4f5da9c0dc999de68032d8b4f76173652036b" + dependencies: + amdefine ">=0.0.4" + +source-map@~0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.2.0.tgz#dab73fbcfc2ba819b4de03bd6f6eaa48164b3f9d" + dependencies: + amdefine ">=0.0.4" + +source-map@~0.5.1: + version "0.5.7" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" + +spdx-correct@~1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-1.0.2.tgz#4b3073d933ff51f3912f03ac5519498a4150db40" + dependencies: + spdx-license-ids "^1.0.2" + +spdx-expression-parse@~1.0.0: + version "1.0.4" + resolved "https://registry.yarnpkg.com/spdx-expression-parse/-/spdx-expression-parse-1.0.4.tgz#9bdf2f20e1f40ed447fbe273266191fced51626c" + +spdx-license-ids@^1.0.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-1.2.2.tgz#c9df7a3424594ade6bd11900d596696dc06bac57" + +sprintf-js@~1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" + +sqlite3@3.1.13: + version "3.1.13" + resolved "https://registry.yarnpkg.com/sqlite3/-/sqlite3-3.1.13.tgz#d990a05627392768de6278bafd1a31fdfe907dd9" + dependencies: + nan "~2.7.0" + node-pre-gyp "~0.6.38" + +sqlite@^2.9.0: + version "2.9.0" + resolved "https://registry.yarnpkg.com/sqlite/-/sqlite-2.9.0.tgz#33f03c646ea0f7a5d506e645a19b9c5352dd9fa4" + dependencies: + sqlite3 "3.1.13" + +sshpk@^1.7.0: + version "1.13.1" + resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.13.1.tgz#512df6da6287144316dc4c18fe1cf1d940739be3" + dependencies: + asn1 "~0.2.3" + assert-plus "^1.0.0" + dashdash "^1.12.0" + getpass "^0.1.1" + optionalDependencies: + bcrypt-pbkdf "^1.0.0" + ecc-jsbn "~0.1.1" + jsbn "~0.1.0" + tweetnacl "~0.14.0" + +stack-trace@0.0.9: + version "0.0.9" + resolved "https://registry.yarnpkg.com/stack-trace/-/stack-trace-0.0.9.tgz#a8f6eaeca90674c333e7c43953f275b451510695" + +stealthy-require@^1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/stealthy-require/-/stealthy-require-1.1.1.tgz#35b09875b4ff49f26a777e509b3090a3226bf24b" + +string-width@^1.0.1, string-width@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-1.0.2.tgz#118bdf5b8cdc51a2a7e70d211e07e2b0b9b107d3" + dependencies: + code-point-at "^1.0.0" + is-fullwidth-code-point "^1.0.0" + strip-ansi "^3.0.0" + +string-width@^2.1.0, string-width@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e" + dependencies: + is-fullwidth-code-point "^2.0.0" + strip-ansi "^4.0.0" + +string_decoder@0.10: + version "0.10.31" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-0.10.31.tgz#62e203bc41766c6c28c9fc84301dab1c5310fa94" + +string_decoder@~1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.0.3.tgz#0fc67d7c141825de94282dd536bec6b9bce860ab" + dependencies: + safe-buffer "~5.1.0" + +stringstream@~0.0.4, stringstream@~0.0.5: + version "0.0.5" + resolved "https://registry.yarnpkg.com/stringstream/-/stringstream-0.0.5.tgz#4e484cd4de5a0bbbee18e46307710a8a81621878" + +strip-ansi@^3.0.0, strip-ansi@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf" + dependencies: + ansi-regex "^2.0.0" + +strip-ansi@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-4.0.0.tgz#a8479022eb1ac368a871389b635262c505ee368f" + dependencies: + ansi-regex "^3.0.0" + +strip-bom@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3" + +strip-json-comments@~2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" + +superagent@^2.0.0, superagent@^2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/superagent/-/superagent-2.3.0.tgz#703529a0714e57e123959ddefbce193b2e50d115" + dependencies: + component-emitter "^1.2.0" + cookiejar "^2.0.6" + debug "^2.2.0" + extend "^3.0.0" + form-data "1.0.0-rc4" + formidable "^1.0.17" + methods "^1.1.1" + mime "^1.3.4" + qs "^6.1.0" + readable-stream "^2.0.5" + +supertest-as-promised@^4.0.1: + version "4.0.2" + resolved "https://registry.yarnpkg.com/supertest-as-promised/-/supertest-as-promised-4.0.2.tgz#0464f2bd256568d4a59bce84269c0548f6879f1a" + dependencies: + bluebird "^3.3.1" + methods "^1.1.1" + +supertest@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/supertest/-/supertest-2.0.1.tgz#a058081d788f1515d4700d7502881e6b759e44cd" + dependencies: + methods "1.x" + superagent "^2.0.0" + +supports-color@3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-3.1.2.tgz#72a262894d9d408b956ca05ff37b2ed8a6e2a2d5" + dependencies: + has-flag "^1.0.0" + +supports-color@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7" + +supports-color@^3.1.0: + version "3.2.3" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-3.2.3.tgz#65ac0504b3954171d8a64946b2ae3cbb8a5f54f6" + dependencies: + has-flag "^1.0.0" + +supports-color@^4.0.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-4.5.0.tgz#be7a0de484dec5c5cddf8b3d59125044912f635b" + dependencies: + has-flag "^2.0.0" + +table@^4.0.1: + version "4.0.2" + resolved "https://registry.yarnpkg.com/table/-/table-4.0.2.tgz#a33447375391e766ad34d3486e6e2aedc84d2e36" + dependencies: + ajv "^5.2.3" + ajv-keywords "^2.1.0" + chalk "^2.1.0" + lodash "^4.17.4" + slice-ansi "1.0.0" + string-width "^2.1.1" + +tar-pack@^3.4.0: + version "3.4.0" + resolved "https://registry.yarnpkg.com/tar-pack/-/tar-pack-3.4.0.tgz#23be2d7f671a8339376cbdb0b8fe3fdebf317984" + dependencies: + debug "^2.2.0" + fstream "^1.0.10" + fstream-ignore "^1.0.5" + once "^1.3.3" + readable-stream "^2.1.4" + rimraf "^2.5.1" + tar "^2.2.1" + uid-number "^0.0.6" + +tar@^2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/tar/-/tar-2.2.1.tgz#8e4d2a256c0e2185c6b18ad694aec968b83cb1d1" + dependencies: + block-stream "*" + fstream "^1.0.2" + inherits "2" + +text-table@~0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" + +through@^2.3.6: + version "2.3.8" + resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" + +timed-out@4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/timed-out/-/timed-out-4.0.1.tgz#f32eacac5a175bea25d7fab565ab3ed8741ef56f" + +tmp@^0.0.33: + version "0.0.33" + resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9" + dependencies: + os-tmpdir "~1.0.2" + +tough-cookie@>=2.3.3, tough-cookie@~2.3.0, tough-cookie@~2.3.3: + version "2.3.3" + resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.3.3.tgz#0b618a5565b6dea90bf3425d04d55edc475a7561" + dependencies: + punycode "^1.4.1" + +tryit@^1.0.1: + version "1.0.3" + resolved "https://registry.yarnpkg.com/tryit/-/tryit-1.0.3.tgz#393be730a9446fd1ead6da59a014308f36c289cb" + +tunnel-agent@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd" + dependencies: + safe-buffer "^5.0.1" + +tweetnacl@^0.14.3, tweetnacl@~0.14.0: + version "0.14.5" + resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64" + +type-check@~0.3.2: + version "0.3.2" + resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.3.2.tgz#5884cab512cf1d355e3fb784f30804b2b520db72" + dependencies: + prelude-ls "~1.1.2" + +type-is@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.0.0.tgz#4ff424e97349a1ee1910b4bfc488595ecdc443fc" + dependencies: + mime "~1.2.11" + +type-is@~1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.1.0.tgz#d0245ec8b2676668d59dd0cf3255060676a57db6" + dependencies: + mime "~1.2.11" + +typedarray@^0.0.6: + version "0.0.6" + resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" + +uglify-js@^2.6: + version "2.8.29" + resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-2.8.29.tgz#29c5733148057bb4e1f75df35b7a9cb72e6a59dd" + dependencies: + source-map "~0.5.1" + yargs "~3.10.0" + optionalDependencies: + uglify-to-browserify "~1.0.0" + +uglify-to-browserify@~1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/uglify-to-browserify/-/uglify-to-browserify-1.0.2.tgz#6e0924d6bda6b5afe349e39a6d632850a0f882b7" + +uid-number@^0.0.6: + version "0.0.6" + resolved "https://registry.yarnpkg.com/uid-number/-/uid-number-0.0.6.tgz#0ea10e8035e8eb5b8e4449f06da1c730663baa81" + +util-deprecate@~1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" + +utils-merge@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.0.tgz#0294fb922bb9375153541c4f7096231f287c8af8" + +uuid@3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.0.0.tgz#6728fc0459c450d796a99c31837569bdf672d728" + +uuid@^3.0.0, uuid@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.1.0.tgz#3dd3d3e790abc24d7b0d3a034ffababe28ebbc04" + +validate-npm-package-license@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/validate-npm-package-license/-/validate-npm-package-license-3.0.1.tgz#2804babe712ad3379459acfbe24746ab2c303fbc" + dependencies: + spdx-correct "~1.0.0" + spdx-expression-parse "~1.0.0" + +verror@1.10.0: + version "1.10.0" + resolved "https://registry.yarnpkg.com/verror/-/verror-1.10.0.tgz#3a105ca17053af55d6e270c1f8288682e18da400" + dependencies: + assert-plus "^1.0.0" + core-util-is "1.0.2" + extsprintf "^1.2.0" + +which@^1.1.1, which@^1.2.9: + version "1.3.0" + resolved "https://registry.yarnpkg.com/which/-/which-1.3.0.tgz#ff04bdfc010ee547d780bec38e1ac1c2777d253a" + dependencies: + isexe "^2.0.0" + +wide-align@^1.1.0: + version "1.1.2" + resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.2.tgz#571e0f1b0604636ebc0dfc21b0339bbe31341710" + dependencies: + string-width "^1.0.2" + +window-size@0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/window-size/-/window-size-0.1.0.tgz#5438cd2ea93b202efa3a19fe8887aee7c94f9c9d" + +wordwrap@0.0.2: + version "0.0.2" + resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-0.0.2.tgz#b79669bb42ecb409f83d583cad52ca17eaa1643f" + +wordwrap@^1.0.0, wordwrap@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb" + +wordwrap@~0.0.2: + version "0.0.3" + resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-0.0.3.tgz#a3d5da6cd5c0bc0008d37234bbaf1bed63059107" + +wrappy@1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" + +write@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/write/-/write-0.2.1.tgz#5fc03828e264cea3fe91455476f7a3c566cb0757" + dependencies: + mkdirp "^0.5.1" + +xtend@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.1.tgz#a5c6d532be656e23db820efb943a1f04998d63af" + +yallist@^2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-2.1.2.tgz#1c11f9218f076089a47dd512f93c6699a6a81d52" + +yargs@~3.10.0: + version "3.10.0" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-3.10.0.tgz#f7ee7bd857dd7c1d2d38c0e74efbd681d1431fd1" + dependencies: + camelcase "^1.0.2" + cliui "^2.1.0" + decamelize "^1.0.0" + window-size "0.1.0" From 99ddb61c3773483a719b6eb2c46fa3b9f59f85e8 Mon Sep 17 00:00:00 2001 From: KevinMidboe Date: Wed, 20 Dec 2017 20:47:32 +0100 Subject: [PATCH 28/66] Started by adding some system tests for the api. --- .../interstellar-query-success-response.json | 89 +++++ .../popular-movies-success-response.json | 375 ++++++++++++++++++ seasoned_api/test/helpers/createCacheEntry.js | 10 + seasoned_api/test/helpers/createToken.js | 10 + seasoned_api/test/helpers/createUser.js | 12 + seasoned_api/test/helpers/resetDatabase.js | 11 + .../asADeveloperIWantTheServerToRegister.js | 16 + .../asADeveloperIWantTheServerToStart.js | 14 + .../test/system/asADeveloperIWantToLogin.js | 25 ++ ...antAForbiddenErrorIfTheTokenIsMalformed.js | 16 + .../system/asAUserIWantToGetPopularMovies.js | 18 + .../system/asAUserIWantToRequestAMovie.js | 17 + ...asAnAnonymousUserIWantToSearchForAMovie.js | 16 + 13 files changed, 629 insertions(+) create mode 100644 seasoned_api/test/fixtures/interstellar-query-success-response.json create mode 100644 seasoned_api/test/fixtures/popular-movies-success-response.json create mode 100644 seasoned_api/test/helpers/createCacheEntry.js create mode 100644 seasoned_api/test/helpers/createToken.js create mode 100644 seasoned_api/test/helpers/createUser.js create mode 100644 seasoned_api/test/helpers/resetDatabase.js create mode 100644 seasoned_api/test/system/asADeveloperIWantTheServerToRegister.js create mode 100644 seasoned_api/test/system/asADeveloperIWantTheServerToStart.js create mode 100644 seasoned_api/test/system/asADeveloperIWantToLogin.js create mode 100644 seasoned_api/test/system/asAUserIWantAForbiddenErrorIfTheTokenIsMalformed.js create mode 100644 seasoned_api/test/system/asAUserIWantToGetPopularMovies.js create mode 100644 seasoned_api/test/system/asAUserIWantToRequestAMovie.js create mode 100644 seasoned_api/test/system/asAnAnonymousUserIWantToSearchForAMovie.js diff --git a/seasoned_api/test/fixtures/interstellar-query-success-response.json b/seasoned_api/test/fixtures/interstellar-query-success-response.json new file mode 100644 index 0000000..b56f1f2 --- /dev/null +++ b/seasoned_api/test/fixtures/interstellar-query-success-response.json @@ -0,0 +1,89 @@ +{ + "number_of_items_on_page": 5, + "page": 1, + "results": [ + { + "background": "/xu9zaAevzQ5nnrsXN6JcahLnG4i.jpg", + "genre": [ + 12, + 18, + 878 + ], + "id": 157336, + "matchedInPlex": false, + "popularity": 50.262329, + "poster": "/nBNZadXqJSdt05SHLqgT0HuC5Gm.jpg", + "rating": 8.1, + "summary": "Interstellar chronicles the adventures of a group of explorers who make use of a newly discovered wormhole to surpass the limitations on human space travel and conquer the vast distances involved in an interstellar voyage.", + "title": "Interstellar", + "type": "movie", + "vote_count": 12095, + "year": 2014 + }, + { + "background": "/bT5jpIZE50MI0COE8pOeq0kMpQo.jpg", + "genre": [ + 99 + ], + "id": 301959, + "matchedInPlex": false, + "popularity": 6.174326, + "poster": "/xZwUIPqBHyJ2QIfMPANOZ1mAld6.jpg", + "rating": 7.9, + "summary": "Behind the scenes of Christopher Nolan's sci-fi drama, which stars Matthew McConaughey and Anne Hathaway", + "title": "Interstellar: Nolan's Odyssey", + "type": "movie", + "vote_count": 102, + "year": 2014 + }, + { + "background": "/yTnHa6lgIv8rNneSNBDkBe8MnZe.jpg", + "genre": [ + 878 + ], + "id": 398188, + "matchedInPlex": false, + "popularity": 3.847981, + "poster": "/cjvTebuqD8wmhchHE286ltVcbX6.jpg", + "rating": 4.7, + "summary": "For Millennia the Aliien force has watched and waited, a brooding menace that has now at last decided to take over the Earth. Communications systems worldwide are sent into chaos by a strange atmospheric interference and this has turned into a global phenomenon. A massive spaceship headed towards Earth and smaller spaceships began to cover entire cities around the world. Suddenly, the wonder turns into horror as the spaceships destroy the cities with energy weapons. When the world counterattacks, the alien ships are invincible to military weapons. The survivors have to use their wits to kill the aliens, or die.", + "title": "Interstellar Wars", + "type": "movie", + "vote_count": 5, + "year": 2016 + }, + { + "background": "/mgb6tVEieDYLpQt666ACzGz5cyE.jpg", + "genre": [ + 35 + ], + "id": 287954, + "matchedInPlex": false, + "popularity": 2.778622, + "poster": "/buoq7zYO4J3ttkEAqEMWelPDC0G.jpg", + "rating": 7, + "summary": "An undeniably beautiful alien is sent to Earth to study the complex mating rituals of human beings, which leads to the young interstellar traveler experiencing the passion that surrounds the centuries-old ritual of the species.", + "title": "Lolita from Interstellar Space", + "type": "movie", + "vote_count": 1, + "year": 2014 + }, + { + "background": null, + "genre": [ + 99 + ], + "id": 336592, + "matchedInPlex": false, + "popularity": 2.147155, + "poster": "/6KBD7YSBjCfgBgHwpsQo3G3GGdx.jpg", + "rating": 7.8, + "summary": "The science of Christopher Nolan's sci-fi, Interstellar.", + "title": "The Science of Interstellar", + "type": "movie", + "vote_count": 6, + "year": 2014 + } + ], + "total_pages": 1 +} \ No newline at end of file diff --git a/seasoned_api/test/fixtures/popular-movies-success-response.json b/seasoned_api/test/fixtures/popular-movies-success-response.json new file mode 100644 index 0000000..3a906d4 --- /dev/null +++ b/seasoned_api/test/fixtures/popular-movies-success-response.json @@ -0,0 +1,375 @@ +{ + "page": 1, + "results": [ + { + "background": "/tcheoA2nPATCm2vvXw2hVQoaEFD.jpg", + "genre": [ + 18, + 14, + 27, + 53 + ], + "id": 346364, + "matchedInPlex": false, + "popularity": 847.49142, + "poster": "/9E2y5Q7WlCVNEhP5GiVTjhEhx1o.jpg", + "rating": 7.2, + "summary": "In a small town in Maine, seven children known as The Losers Club come face to face with life problems, bullies and a monster that takes the shape of a clown called Pennywise.", + "title": "It", + "type": "movie", + "vote_count": 4647, + "year": 2017 + }, + { + "background": "/askg3SMvhqEl4OL52YuvdtY40Yb.jpg", + "genre": [ + 12, + 16, + 10751 + ], + "id": 354912, + "matchedInPlex": false, + "popularity": 545.354595, + "poster": "/eKi8dIrr8voobbaGzDpe8w0PVbC.jpg", + "rating": 7.5, + "summary": "Despite his family’s baffling generations-old ban on music, Miguel dreams of becoming an accomplished musician like his idol, Ernesto de la Cruz. Desperate to prove his talent, Miguel finds himself in the stunning and colorful Land of the Dead following a mysterious chain of events. Along the way, he meets charming trickster Hector, and together, they set off on an extraordinary journey to unlock the real story behind Miguel's family history.", + "title": "Coco", + "type": "movie", + "vote_count": 532, + "year": 2017 + }, + { + "background": "/5Iw7zQTHVRBOYpA0V6z0yypOPZh.jpg", + "genre": [ + 28, + 12, + 14, + 878 + ], + "id": 181808, + "matchedInPlex": false, + "popularity": 510.708216, + "poster": "/xGWVjewoXnJhvxKW619cMzppJDQ.jpg", + "rating": 7.5, + "summary": "Rey develops her newly discovered abilities with the guidance of Luke Skywalker, who is unsettled by the strength of her powers. Meanwhile, the Resistance prepares to do battle with the First Order.", + "title": "Star Wars: The Last Jedi", + "type": "movie", + "vote_count": 1054, + "year": 2017 + }, + { + "background": "/o5T8rZxoWSBMYwjsUFUqTt6uMQB.jpg", + "genre": [ + 28, + 12, + 14, + 878 + ], + "id": 141052, + "matchedInPlex": false, + "popularity": 423.487801, + "poster": "/9rtrRGeRnL0JKtu9IMBWsmlmmZz.jpg", + "rating": 6.6, + "summary": "Fueled by his restored faith in humanity and inspired by Superman's selfless act, Bruce Wayne and Diana Prince assemble a team of metahumans consisting of Barry Allen, Arthur Curry, and Victor Stone to face the catastrophic threat of Steppenwolf and the Parademons who are on the hunt for three Mother Boxes on Earth.", + "title": "Justice League", + "type": "movie", + "vote_count": 1805, + "year": 2017 + }, + { + "background": "/52lVqTDhIeNTjT7EiJuovXgw6iE.jpg", + "genre": [ + 12, + 14, + 10751 + ], + "id": 8844, + "matchedInPlex": false, + "popularity": 372.129434, + "poster": "/8wBKXZNod4frLZjAKSDuAcQ2dEU.jpg", + "rating": 6.9, + "summary": "When siblings Judy and Peter discover an enchanted board game that opens the door to a magical world, they unwittingly invite Alan -- an adult who's been trapped inside the game for 26 years -- into their living room. Alan's only hope for freedom is to finish the game, which proves risky as all three find themselves running from giant rhinoceroses, evil monkeys and other terrifying creatures.", + "title": "Jumanji", + "type": "movie", + "vote_count": 2907, + "year": 1995 + }, + { + "background": "/qLmdjn2fv0FV2Mh4NBzMArdA0Uu.jpg", + "genre": [ + 10751, + 16, + 12, + 35 + ], + "id": 211672, + "matchedInPlex": false, + "popularity": 345.173187, + "poster": "/q0R4crx2SehcEEQEkYObktdeFy.jpg", + "rating": 6.4, + "summary": "Minions Stuart, Kevin and Bob are recruited by Scarlet Overkill, a super-villain who, alongside her inventor husband Herb, hatches a plot to take over the world.", + "title": "Minions", + "type": "movie", + "vote_count": 5237, + "year": 2015 + }, + { + "background": "/6aUWe0GSl69wMTSWWexsorMIvwU.jpg", + "genre": [ + 10751, + 14, + 10749 + ], + "id": 321612, + "matchedInPlex": false, + "popularity": 297.124109, + "poster": "/tWqifoYuwLETmmasnGHO7xBjEtt.jpg", + "rating": 6.8, + "summary": "A live-action adaptation of Disney's version of the classic tale of a cursed prince and a beautiful young woman who helps him break the spell.", + "title": "Beauty and the Beast", + "type": "movie", + "vote_count": 6318, + "year": 2017 + }, + { + "background": "/lMDyuHzBhx3zNAv48vYzmgcJCCD.jpg", + "genre": [ + 18, + 35 + ], + "id": 419680, + "matchedInPlex": false, + "popularity": 278.025258, + "poster": "/rF2IoKL0IFmumEXQFUuB8LajTYP.jpg", + "rating": 5.7, + "summary": "Brad and Dusty must deal with their intrusive fathers during the holidays.", + "title": "Daddy's Home 2", + "type": "movie", + "vote_count": 408, + "year": 2017 + }, + { + "background": "/tvKcA4OFUiZkNeTJmmTkNqKHMMg.jpg", + "genre": [ + 80, + 18, + 9648 + ], + "id": 392044, + "matchedInPlex": false, + "popularity": 259.276687, + "poster": "/iBlfxlw8qwtUS0R8YjIU7JtM6LM.jpg", + "rating": 6.8, + "summary": "Genius Belgian detective Hercule Poirot investigates the murder of an American tycoon aboard the Orient Express train.", + "title": "Murder on the Orient Express", + "type": "movie", + "vote_count": 878, + "year": 2017 + }, + { + "background": "/ulMscezy9YX0bhknvJbZoUgQxO5.jpg", + "genre": [ + 18, + 878, + 10752 + ], + "id": 281338, + "matchedInPlex": false, + "popularity": 252.304349, + "poster": "/3vYhLLxrTtZLysXtIWktmd57Snv.jpg", + "rating": 6.7, + "summary": "Caesar and his apes are forced into a deadly conflict with an army of humans led by a ruthless Colonel. After the apes suffer unimaginable losses, Caesar wrestles with his darker instincts and begins his own mythic quest to avenge his kind. As the journey finally brings them face to face, Caesar and the Colonel are pitted against each other in an epic battle that will determine the fate of both their species and the future of the planet.", + "title": "War for the Planet of the Apes", + "type": "movie", + "vote_count": 2692, + "year": 2017 + }, + { + "background": "/rz3TAyd5kmiJmozp3GUbYeB5Kep.jpg", + "genre": [ + 28, + 12, + 35, + 10751 + ], + "id": 353486, + "matchedInPlex": false, + "popularity": 250.35028, + "poster": "/bXrZ5iHBEjH7WMidbUDQ0U2xbmr.jpg", + "rating": 5.6, + "summary": "The tables are turned as four teenagers are sucked into Jumanji's world - pitted against rhinos, black mambas and an endless variety of jungle traps and puzzles. To survive, they'll play as characters from the game.", + "title": "Jumanji: Welcome to the Jungle", + "type": "movie", + "vote_count": 128, + "year": 2017 + }, + { + "background": "/iJ5dkwIHQnq8dfmwSLh7dpETNhi.jpg", + "genre": [ + 35, + 16, + 12 + ], + "id": 355547, + "matchedInPlex": false, + "popularity": 250.28269, + "poster": "/zms2RpkqjAtCsbjndTG9gAGWvnx.jpg", + "rating": 4.6, + "summary": "A small but brave donkey and his animal friends become the unsung heroes of the greatest story ever told, the first Christmas.", + "title": "The Star", + "type": "movie", + "vote_count": 78, + "year": 2017 + }, + { + "background": "/2SEgJ0mHJ7TSdVDbkGU061tR33K.jpg", + "genre": [ + 18, + 53, + 28, + 878 + ], + "id": 347882, + "matchedInPlex": false, + "popularity": 210.896389, + "poster": "/wridRvGxDqGldhzAIh3IcZhHT5F.jpg", + "rating": 5.4, + "summary": "A young street magician is left to take care of his little sister after his mother's passing and turns to drug dealing in the Los Angeles party scene to keep a roof over their heads. When he gets into trouble with his supplier, his sister is kidnapped and he is forced to rely on both his sleight of hand and brilliant mind to save her.", + "title": "Sleight", + "type": "movie", + "vote_count": 156, + "year": 2017 + }, + { + "background": "/5wNUJs23rT5rTBacNyf5h83AynM.jpg", + "genre": [ + 28, + 12, + 35, + 14, + 878 + ], + "id": 284053, + "matchedInPlex": false, + "popularity": 210.575092, + "poster": "/oSLd5GYGsiGgzDPKTwQh7wamO8t.jpg", + "rating": 7.5, + "summary": "Thor is imprisoned on the other side of the universe and finds himself in a race against time to get back to Asgard to stop Ragnarok, the prophecy of destruction to his homeworld and the end of Asgardian civilization, at the hands of an all-powerful new threat, the ruthless Hela.", + "title": "Thor: Ragnarok", + "type": "movie", + "vote_count": 2598, + "year": 2017 + }, + { + "background": "/uExPmkOHJySrbJyJDJylHDqaT58.jpg", + "genre": [ + 28, + 12, + 35 + ], + "id": 343668, + "matchedInPlex": false, + "popularity": 190.179283, + "poster": "/34xBL6BXNYFqtHO9zhcgoakS4aP.jpg", + "rating": 7.1, + "summary": "When an attack on the Kingsman headquarters takes place and a new villain rises, Eggsy and Merlin are forced to work together with the American agency known as the Statesman to save the world.", + "title": "Kingsman: The Golden Circle", + "type": "movie", + "vote_count": 1714, + "year": 2017 + }, + { + "background": "/bAI7aPHQcvSZXvt7L11kMJdS0Gm.jpg", + "genre": [ + 18, + 35, + 36 + ], + "id": 371638, + "matchedInPlex": false, + "popularity": 187.757689, + "poster": "/uCH6FOFsDW6pfvbbmIIswuvuNtM.jpg", + "rating": 7.2, + "summary": "An aspiring actor in Hollywood meets an enigmatic stranger by the name of Tommy Wiseau, the meeting leads the actor down a path nobody could have predicted; creating the worst movie ever made.", + "title": "The Disaster Artist", + "type": "movie", + "vote_count": 87, + "year": 2017 + }, + { + "background": "/2BXd0t9JdVqCp9sKf6kzMkr7QjB.jpg", + "genre": [ + 12, + 10751, + 16, + 28, + 35 + ], + "id": 177572, + "matchedInPlex": false, + "popularity": 180.209866, + "poster": "/9gLu47Zw5ertuFTZaxXOvNfy78T.jpg", + "rating": 7.7, + "summary": "The special bond that develops between plus-sized inflatable robot Baymax, and prodigy Hiro Hamada, who team up with a group of friends to form a band of high-tech heroes.", + "title": "Big Hero 6", + "type": "movie", + "vote_count": 6872, + "year": 2014 + }, + { + "background": "/6iUNJZymJBMXXriQyFZfLAKnjO6.jpg", + "genre": [ + 28, + 12, + 14 + ], + "id": 297762, + "matchedInPlex": false, + "popularity": 176.828995, + "poster": "/imekS7f1OuHyUP2LAiTEM0zBzUz.jpg", + "rating": 7.2, + "summary": "An Amazon princess comes to the world of Man to become the greatest of the female superheroes.", + "title": "Wonder Woman", + "type": "movie", + "vote_count": 6535, + "year": 2017 + }, + { + "background": "/umC04Cozevu8nn3JTDJ1pc7PVTn.jpg", + "genre": [ + 28, + 53 + ], + "id": 245891, + "matchedInPlex": false, + "popularity": 171.364116, + "poster": "/5vHssUeVe25bMrof1HyaPyWgaP.jpg", + "rating": 7, + "summary": "Ex-hitman John Wick comes out of retirement to track down the gangsters that took everything from him.", + "title": "John Wick", + "type": "movie", + "vote_count": 6117, + "year": 2014 + }, + { + "background": "/vc8bCGjdVp0UbMNLzHnHSLRbBWQ.jpg", + "genre": [ + 28, + 12, + 35, + 878 + ], + "id": 315635, + "matchedInPlex": false, + "popularity": 157.789584, + "poster": "/ApYhuwBWzl29Oxe9JJsgL7qILbD.jpg", + "rating": 7.3, + "summary": "Following the events of Captain America: Civil War, Peter Parker, with the help of his mentor Tony Stark, tries to balance his life as an ordinary high school student in Queens, New York City, with fighting crime as his superhero alter ego Spider-Man as a new threat, the Vulture, emerges.", + "title": "Spider-Man: Homecoming", + "type": "movie", + "vote_count": 5218, + "year": 2017 + } + ], + "total_pages": 992 +} \ No newline at end of file diff --git a/seasoned_api/test/helpers/createCacheEntry.js b/seasoned_api/test/helpers/createCacheEntry.js new file mode 100644 index 0000000..afc7889 --- /dev/null +++ b/seasoned_api/test/helpers/createCacheEntry.js @@ -0,0 +1,10 @@ +const Cache = require('src/tmdb/cache'); +const SqliteDatabase = require('src/database/sqliteDatabase'); + +function createCacheEntry(key, value) { + const database = new SqliteDatabase(':memory:'); + const cache = new Cache(database); + return cache.set(key, value); +} + +module.exports = createCacheEntry; diff --git a/seasoned_api/test/helpers/createToken.js b/seasoned_api/test/helpers/createToken.js new file mode 100644 index 0000000..0435968 --- /dev/null +++ b/seasoned_api/test/helpers/createToken.js @@ -0,0 +1,10 @@ +const User = require('src/user/user'); +const Token = require('src/user/token'); + +function createToken(username, secret) { + const user = new User(username); + const token = new Token(user); + return token.toString(secret); +} + +module.exports = createToken; diff --git a/seasoned_api/test/helpers/createUser.js b/seasoned_api/test/helpers/createUser.js new file mode 100644 index 0000000..417d71d --- /dev/null +++ b/seasoned_api/test/helpers/createUser.js @@ -0,0 +1,12 @@ +const User = require('src/user/user'); +const UserSecurity = require('src/user/userSecurity'); +const SqliteDatabase = require('src/database/sqliteDatabase'); + +function createUser(username, email, password) { + const database = new SqliteDatabase(':memory:'); + const userSecurity = new UserSecurity(database); + const user = new User(username, email); + return userSecurity.createNewUser(user, password); +} + +module.exports = createUser; \ No newline at end of file diff --git a/seasoned_api/test/helpers/resetDatabase.js b/seasoned_api/test/helpers/resetDatabase.js new file mode 100644 index 0000000..2498852 --- /dev/null +++ b/seasoned_api/test/helpers/resetDatabase.js @@ -0,0 +1,11 @@ +const SqliteDatabase = require('src/database/sqliteDatabase'); + +function resetDatabase() { + const database = new SqliteDatabase(':memory:'); + return Promise.resolve() + .then(() => database.connect()) + .then(() => database.tearDown()) + .then(() => database.setUp()); +} + +module.exports = resetDatabase; diff --git a/seasoned_api/test/system/asADeveloperIWantTheServerToRegister.js b/seasoned_api/test/system/asADeveloperIWantTheServerToRegister.js new file mode 100644 index 0000000..6b0a60a --- /dev/null +++ b/seasoned_api/test/system/asADeveloperIWantTheServerToRegister.js @@ -0,0 +1,16 @@ +const assert = require('assert'); +const request = require('supertest-as-promised'); +const app = require('src/webserver/app'); +const resetDatabase = require('test/helpers/resetDatabase'); + +describe('As a user I want to register', () => { + before(() => resetDatabase()); + + it('should return 200 and a message indicating success', () => + request(app) + .post('/api/v1/user') + .send({ username: 'test', email: 'test@gmail.com', password: 'password' }) + .expect(200) + .then(response => assert.equal(response.body.message, 'Welcome to Seasoned!')) + ); +}); diff --git a/seasoned_api/test/system/asADeveloperIWantTheServerToStart.js b/seasoned_api/test/system/asADeveloperIWantTheServerToStart.js new file mode 100644 index 0000000..aa2d7ee --- /dev/null +++ b/seasoned_api/test/system/asADeveloperIWantTheServerToStart.js @@ -0,0 +1,14 @@ +/* eslint-disable no-return-assign */ +const net = require('net'); + +xdescribe('As a developer I want the server to start', () => { + beforeEach(() => + this.server = require('src/webserver/server')); + + it('should listen on port 31459', (done) => { + net.createConnection(31459, done); + }); + + afterEach(() => + this.server.close()); +}); diff --git a/seasoned_api/test/system/asADeveloperIWantToLogin.js b/seasoned_api/test/system/asADeveloperIWantToLogin.js new file mode 100644 index 0000000..3a48e7a --- /dev/null +++ b/seasoned_api/test/system/asADeveloperIWantToLogin.js @@ -0,0 +1,25 @@ +const assert = require('assert'); +const request = require('supertest-as-promised'); +const app = require('src/webserver/app'); +const createUser = require('test/helpers/createUser'); +const resetDatabase = require('test/helpers/resetDatabase'); + +describe('As a user I want to log in', () => { + before(() => resetDatabase()); + before(() => createUser('test_user', 'test@gmail.com', 'password')); + + it('should return 200 with a token if correct credentials are given', () => + request(app) + .post('/api/v1/user/login') + .send({ username: 'test_user', password: 'password' }) + .expect(200) + .then(response => assert.equal(typeof response.body.token, 'string')) + ); + + it('should return 401 if incorrect credentials are given', () => + request(app) + .post('/api/v1/user/login') + .send({ username: 'test_user', password: 'anti-password' }) + .expect(401) + ); +}); diff --git a/seasoned_api/test/system/asAUserIWantAForbiddenErrorIfTheTokenIsMalformed.js b/seasoned_api/test/system/asAUserIWantAForbiddenErrorIfTheTokenIsMalformed.js new file mode 100644 index 0000000..73811b2 --- /dev/null +++ b/seasoned_api/test/system/asAUserIWantAForbiddenErrorIfTheTokenIsMalformed.js @@ -0,0 +1,16 @@ +const assert = require('assert'); +const resetDatabase = require('test/helpers/resetDatabase'); +const app = require('src/webserver/app'); +const request = require('supertest-as-promised'); + +describe('As a user I want a forbidden error if the token is malformed', () => { + before(() => resetDatabase()); + + it('should return 401', () => + request(app) + .get('/api/v1/plex/requests/all') + .set('Authorization', 'maLfOrMed TOKEN') + .expect(401) + .then(response => assert.equal(response.body.error, 'You must be logged in.')) + ); +}); diff --git a/seasoned_api/test/system/asAUserIWantToGetPopularMovies.js b/seasoned_api/test/system/asAUserIWantToGetPopularMovies.js new file mode 100644 index 0000000..642a84f --- /dev/null +++ b/seasoned_api/test/system/asAUserIWantToGetPopularMovies.js @@ -0,0 +1,18 @@ +const assert = require('assert'); +const createCacheEntry = require('test/helpers/createCacheEntry'); +const resetDatabase = require('test/helpers/resetDatabase'); +const request = require('supertest-as-promised'); +const app = require('src/webserver/app'); +const popularMoviesSuccess = require('test/fixtures/popular-movies-success-response.json'); + +describe('As a user I want to get popular movies', () => { + before(() => resetDatabase()); + before(() => createCacheEntry('p:movie::1', popularMoviesSuccess)); + + it('should return 200 with the information', () => + request(app) + .get('/api/v1/tmdb/list/popular') + .expect(200) + .then(response => assert.equal(response.body.results.length, 20)) + ); +}); \ No newline at end of file diff --git a/seasoned_api/test/system/asAUserIWantToRequestAMovie.js b/seasoned_api/test/system/asAUserIWantToRequestAMovie.js new file mode 100644 index 0000000..fda5f0a --- /dev/null +++ b/seasoned_api/test/system/asAUserIWantToRequestAMovie.js @@ -0,0 +1,17 @@ +const resetDatabase = require('test/helpers/resetDatabase'); +const app = require('src/webserver/app'); +const request = require('supertest-as-promised'); +const createUser = require('test/helpers/createUser'); +const createToken = require('test/helpers/createToken'); + +describe('As a user I want to request a movie', () => { + before(() => resetDatabase()); + before(() => createUser('test_user', 'test@gmail.com', 'password')); + + it('should return 200 when item is requested', () => + request(app) + .post('/api/v1/plex/request/31749') + .set('Authorization', createToken('test_user', 'secret')) + .expect(200) + ); +}); diff --git a/seasoned_api/test/system/asAnAnonymousUserIWantToSearchForAMovie.js b/seasoned_api/test/system/asAnAnonymousUserIWantToSearchForAMovie.js new file mode 100644 index 0000000..360adab --- /dev/null +++ b/seasoned_api/test/system/asAnAnonymousUserIWantToSearchForAMovie.js @@ -0,0 +1,16 @@ +const createCacheEntry = require('test/helpers/createCacheEntry'); +const resetDatabase = require('test/helpers/resetDatabase'); +const request = require('supertest-as-promised'); +const app = require('src/webserver/app'); +const interstellarQuerySuccess = require('test/fixtures/interstellar-query-success-response.json'); + +describe('As an anonymous user I want to search for a movie', () => { + before(() => resetDatabase()); + before(() => createCacheEntry('s:1:movie:interstellar', interstellarQuerySuccess)); + + it('should return 200 with the search results even if user is not logged in', () => + request(app) + .get('/api/v1/tmdb/search?query=interstellar&page=1') + .expect(200) + ); +}); From 00ad5cf7a8f42cdc0298f0bc78f35c27efdf2e90 Mon Sep 17 00:00:00 2001 From: KevinMidboe Date: Wed, 20 Dec 2017 21:07:13 +0100 Subject: [PATCH 29/66] Added mocha, istanbul and supertest packages. Added coverange and test script. --- seasoned_api/package.json | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/seasoned_api/package.json b/seasoned_api/package.json index 8ac0fe3..a93104f 100644 --- a/seasoned_api/package.json +++ b/seasoned_api/package.json @@ -3,13 +3,14 @@ "main": "webserver/server.js", "scripts": { "start": "cross-env SEASONED_CONFIG=conf/development.json NODE_PATH=. node src/webserver/server.js", + "test": "cross-env SEASONED_CONFIG=conf/development.json NODE_PATH=. mocha --recursive test/unit test/system", + "coverage": "cross-env PLANFLIX_CONFIG=conf/test.json NODE_PATH=. istanbul cover -x script/autogenerate-documentation.js --include-all-sources --dir test/.coverage node_modules/mocha/bin/_mocha --recursive test/**/* -- --report lcovonly && cat test/.coverage/lcov.info | coveralls && rm -rf test/.coverage", "lint": "./node_modules/.bin/eslint src/webserver/" }, "dependencies": { "bcrypt-nodejs": "^0.0.3", "body-parser": "~1.0.1", "cross-env": "^3.1.3", - "eslint": "^4.9.0", "express": "~4.0.0", "jsonwebtoken": "^8.0.1", "mongoose": "^3.6.13", @@ -20,11 +21,16 @@ "raven": "^2.2.1", "request": "^2.81.0", "request-promise": "^4.2", - "sqlite": "^2.2.1", - "sqlite3": "^2.5.0" + "sqlite": "^2.9.0" }, "devDependencies": { "eslint-config-airbnb-base": "^12.1.0", - "eslint-plugin-import": "^2.8.0" + "eslint-plugin-import": "^2.8.0", + "eslint": "^4.9.0", + "istanbul": "^0.4.5", + "mocha": "^3.1.0", + "supertest": "^2.0.1", + "supertest-as-promised": "^4.0.1" + } } From f6c27482e4adfe4128a6d8c9e233eec0b0ca9af3 Mon Sep 17 00:00:00 2001 From: KevinMidboe Date: Wed, 20 Dec 2017 21:07:33 +0100 Subject: [PATCH 30/66] Added script for teardown of database. --- seasoned_api/src/database/sqliteDatabase.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/seasoned_api/src/database/sqliteDatabase.js b/seasoned_api/src/database/sqliteDatabase.js index 5ac1330..a68e207 100644 --- a/seasoned_api/src/database/sqliteDatabase.js +++ b/seasoned_api/src/database/sqliteDatabase.js @@ -69,6 +69,15 @@ class SqliteDatabase { return this.execute(setupSchema); } + /** + * Tears down the database by running tearDown.sql file in schemas/. + * @returns {Promise} + */ + tearDown() { + const tearDownSchema = this.readSqlFile('tearDown.sql'); + return this.execute(tearDownSchema); + } + /** * Returns the file contents of a SQL file in schemas/. * @returns {String} From e19b604a78d87aeb9c2ed0e9e252f1d19840a04b Mon Sep 17 00:00:00 2001 From: KevinMidboe Date: Wed, 20 Dec 2017 21:08:35 +0100 Subject: [PATCH 31/66] Our teardown sql script. --- seasoned_api/src/database/schemas/teardown.sql | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 seasoned_api/src/database/schemas/teardown.sql diff --git a/seasoned_api/src/database/schemas/teardown.sql b/seasoned_api/src/database/schemas/teardown.sql new file mode 100644 index 0000000..35fef21 --- /dev/null +++ b/seasoned_api/src/database/schemas/teardown.sql @@ -0,0 +1,3 @@ +DROP TABLE IF EXISTS user; +DROP TABLE IF EXISTS search_history; +DROP TABLE IF EXISTS requests; \ No newline at end of file From e4573e6808d2f00ea39507f665d9bc2644ec16a4 Mon Sep 17 00:00:00 2001 From: KevinMidboe Date: Wed, 20 Dec 2017 21:13:24 +0100 Subject: [PATCH 32/66] Forgot to remove path without test scripts yet. --- seasoned_api/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/seasoned_api/package.json b/seasoned_api/package.json index a93104f..596fd38 100644 --- a/seasoned_api/package.json +++ b/seasoned_api/package.json @@ -3,7 +3,7 @@ "main": "webserver/server.js", "scripts": { "start": "cross-env SEASONED_CONFIG=conf/development.json NODE_PATH=. node src/webserver/server.js", - "test": "cross-env SEASONED_CONFIG=conf/development.json NODE_PATH=. mocha --recursive test/unit test/system", + "test": "cross-env SEASONED_CONFIG=conf/development.json NODE_PATH=. mocha --recursive test/system", "coverage": "cross-env PLANFLIX_CONFIG=conf/test.json NODE_PATH=. istanbul cover -x script/autogenerate-documentation.js --include-all-sources --dir test/.coverage node_modules/mocha/bin/_mocha --recursive test/**/* -- --report lcovonly && cat test/.coverage/lcov.info | coveralls && rm -rf test/.coverage", "lint": "./node_modules/.bin/eslint src/webserver/" }, From 625717f7adee1a8748c378703c805f24b7d90658 Mon Sep 17 00:00:00 2001 From: KevinMidboe Date: Wed, 20 Dec 2017 22:16:36 +0100 Subject: [PATCH 33/66] Now when checking config file if the value is blank it checks env variables for a variable defined as uppcase section + option with a separator of _. --- seasoned_api/src/config/configuration.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/seasoned_api/src/config/configuration.js b/seasoned_api/src/config/configuration.js index c8aa9a2..bd9d4c3 100644 --- a/seasoned_api/src/config/configuration.js +++ b/seasoned_api/src/config/configuration.js @@ -27,6 +27,11 @@ class Config { const field = new Field(this.fields[section][option]) + if (field.value === '') { + const envField = process.env[[section.toUpperCase(), option.toUpperCase()].join('_')] + if (envField !== undefined && envField.length !== 0) { return envField } + } + if (field.value === undefined) { throw new Error(`${section} => ${option} is empty.`); } From fe64af856e512dea6b9300dd8a08eb0cee86dc01 Mon Sep 17 00:00:00 2001 From: KevinMidboe Date: Wed, 20 Dec 2017 22:16:57 +0100 Subject: [PATCH 34/66] Fixed a old bug. --- seasoned_api/src/config/field.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/seasoned_api/src/config/field.js b/seasoned_api/src/config/field.js index 2dbfc58..1a8b79a 100644 --- a/seasoned_api/src/config/field.js +++ b/seasoned_api/src/config/field.js @@ -4,7 +4,7 @@ const EnvironmentVariables = require('./environmentVariables.js'); class Field { constructor(rawValue, environmentVariables) { - this.rawValue, rawValue; + this.rawValue = rawValue; this.filters = new Filters(rawValue); this.valueWithoutFilters = this.filters.removeFiltersFromValue(); this.environmentVariables = new EnvironmentVariables(environmentVariables); From fba38455233dc19d59c11a0e1d62d0686e703a0b Mon Sep 17 00:00:00 2001 From: KevinMidboe Date: Wed, 20 Dec 2017 22:17:45 +0100 Subject: [PATCH 35/66] Now we check if the values length is 0 not the filters varaible. --- seasoned_api/src/config/filters.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/seasoned_api/src/config/filters.js b/seasoned_api/src/config/filters.js index ebd813d..fbeab07 100644 --- a/seasoned_api/src/config/filters.js +++ b/seasoned_api/src/config/filters.js @@ -10,7 +10,7 @@ class Filters { } isEmpty() { - return !this.hasValidType() || this.filters.length === 0; + return !this.hasValidType() || this.value.length === 0; } has(filter) { From 93e43d99543174653dcbbd736d5057a5fa5c4677 Mon Sep 17 00:00:00 2001 From: KevinMidboe Date: Wed, 20 Dec 2017 22:22:25 +0100 Subject: [PATCH 36/66] Added a template development.json file for build purposes. --- seasoned_api/conf/development.json | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 seasoned_api/conf/development.json diff --git a/seasoned_api/conf/development.json b/seasoned_api/conf/development.json new file mode 100644 index 0000000..abf8399 --- /dev/null +++ b/seasoned_api/conf/development.json @@ -0,0 +1,24 @@ +{ + "database": { + "host": "../shows.db" + }, + "webserver": { + "port": 31459 + }, + "tmdb": { + "apiKey": "" + }, + "raven": { + "DSN": "" + }, + "mail": { + "host": "", + "user": "", + "password": "", + "user_pi": "", + "password_pi": "" + }, + "authentication": { + "secret": "secret" + } +} From 54eca33dfae16869da7a0b082bbb47848d2e53b9 Mon Sep 17 00:00:00 2001 From: KevinMidboe Date: Wed, 20 Dec 2017 22:27:20 +0100 Subject: [PATCH 37/66] Forgot to update the node version in travis config file. --- .travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 3140bb8..4f8b8e0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,7 @@ { 'dist': 'trusty', 'language': 'node_js', - 'node_js': '6.9.5', + 'node_js': '8.7.0', 'cache': 'yarn', 'scripts': [ npm run test @@ -11,4 +11,4 @@ ], 'before_script': 'yarn', 'os': 'linux', -} \ No newline at end of file +} From bced4e052d603ed46f49ee74adeda8826075f6bf Mon Sep 17 00:00:00 2001 From: KevinMidboe Date: Wed, 20 Dec 2017 22:42:53 +0100 Subject: [PATCH 38/66] There was a weird thing with the file permissions so it would not be read by travis. Now it should be fixed --- seasoned_api/src/database/schemas/teardown.sql | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/seasoned_api/src/database/schemas/teardown.sql b/seasoned_api/src/database/schemas/teardown.sql index 35fef21..34d52bd 100644 --- a/seasoned_api/src/database/schemas/teardown.sql +++ b/seasoned_api/src/database/schemas/teardown.sql @@ -1,3 +1 @@ -DROP TABLE IF EXISTS user; -DROP TABLE IF EXISTS search_history; -DROP TABLE IF EXISTS requests; \ No newline at end of file +teardown.sql.bak From fb225767086483b045ee64f8797c0c04f80118c4 Mon Sep 17 00:00:00 2001 From: KevinMidboe Date: Wed, 20 Dec 2017 22:51:08 +0100 Subject: [PATCH 39/66] Travis is still not reading our teardown file. --- seasoned_api/src/database/schemas/teardown.sql | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/seasoned_api/src/database/schemas/teardown.sql b/seasoned_api/src/database/schemas/teardown.sql index 34d52bd..ebc1ac9 100644 --- a/seasoned_api/src/database/schemas/teardown.sql +++ b/seasoned_api/src/database/schemas/teardown.sql @@ -1 +1,4 @@ -teardown.sql.bak +DROP TABLE IF EXISTS user; +DROP TABLE IF EXISTS search_history; +DROP TABLE IF EXISTS list; +DROP TABLE IF EXISTS list_content; From 6a725e3921f8824650c8fab5a36c29db71b7c191 Mon Sep 17 00:00:00 2001 From: KevinMidboe Date: Wed, 20 Dec 2017 23:06:02 +0100 Subject: [PATCH 40/66] Added tons of console.logs to try find out where the problem with travis not being able to read file lies. --- seasoned_api/src/database/sqliteDatabase.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/seasoned_api/src/database/sqliteDatabase.js b/seasoned_api/src/database/sqliteDatabase.js index a68e207..c30ae26 100644 --- a/seasoned_api/src/database/sqliteDatabase.js +++ b/seasoned_api/src/database/sqliteDatabase.js @@ -75,6 +75,7 @@ class SqliteDatabase { */ tearDown() { const tearDownSchema = this.readSqlFile('tearDown.sql'); + console.log(tearDownSchema); return this.execute(tearDownSchema); } @@ -83,8 +84,11 @@ class SqliteDatabase { * @returns {String} */ readSqlFile(filename) { + console.log(filename) const schemaPath = path.join(this.schemaDirectory, filename); + console.log(schemaPath) const schema = fs.readFileSync(schemaPath).toString('utf-8'); + console.log(schema) return schema; } } From 0459fb93125a01f1298e116626f5289d29a03712 Mon Sep 17 00:00:00 2001 From: KevinMidboe Date: Wed, 20 Dec 2017 23:18:13 +0100 Subject: [PATCH 41/66] More testing for this bug. --- seasoned_api/src/database/sqliteDatabase.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/seasoned_api/src/database/sqliteDatabase.js b/seasoned_api/src/database/sqliteDatabase.js index c30ae26..d30f9f1 100644 --- a/seasoned_api/src/database/sqliteDatabase.js +++ b/seasoned_api/src/database/sqliteDatabase.js @@ -88,7 +88,7 @@ class SqliteDatabase { const schemaPath = path.join(this.schemaDirectory, filename); console.log(schemaPath) const schema = fs.readFileSync(schemaPath).toString('utf-8'); - console.log(schema) + console.log('schema: ', schema) return schema; } } From f0e8f84e1262e5b5278d0f4d5329aba377ae0ff2 Mon Sep 17 00:00:00 2001 From: KevinMidboe Date: Wed, 20 Dec 2017 23:20:49 +0100 Subject: [PATCH 42/66] Building with osx now --- .travis.yml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 4f8b8e0..359cacb 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,14 +1,13 @@ { - 'dist': 'trusty', 'language': 'node_js', 'node_js': '8.7.0', 'cache': 'yarn', 'scripts': [ - npm run test + ' npm run test', ], 'before_install': [ 'cd seasoned_api', ], 'before_script': 'yarn', - 'os': 'linux', + 'os': 'osx', } From e6a34a0503d97869fac1b27b735afdd438b523a6 Mon Sep 17 00:00:00 2001 From: KevinMidboe Date: Thu, 21 Dec 2017 00:38:46 +0100 Subject: [PATCH 43/66] Back to linux. plz fix plzzzzz --- .travis.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 359cacb..6f664eb 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,13 +1,14 @@ { + 'dist': 'trusty', 'language': 'node_js', 'node_js': '8.7.0', 'cache': 'yarn', 'scripts': [ - ' npm run test', + 'npm run test' ], 'before_install': [ 'cd seasoned_api', ], 'before_script': 'yarn', - 'os': 'osx', + 'os': 'linux', } From 908fca6dd0d6c9156f57787b8a2c64f761d2faac Mon Sep 17 00:00:00 2001 From: KevinMidboe Date: Thu, 21 Dec 2017 00:42:44 +0100 Subject: [PATCH 44/66] Trying to remove teardown between tests. --- seasoned_api/test/helpers/resetDatabase.js | 1 - 1 file changed, 1 deletion(-) diff --git a/seasoned_api/test/helpers/resetDatabase.js b/seasoned_api/test/helpers/resetDatabase.js index 2498852..8fd83a7 100644 --- a/seasoned_api/test/helpers/resetDatabase.js +++ b/seasoned_api/test/helpers/resetDatabase.js @@ -4,7 +4,6 @@ function resetDatabase() { const database = new SqliteDatabase(':memory:'); return Promise.resolve() .then(() => database.connect()) - .then(() => database.tearDown()) .then(() => database.setUp()); } From 2623649c5d426180c59b59847bf47f4cb8040918 Mon Sep 17 00:00:00 2001 From: KevinMidboe Date: Thu, 21 Dec 2017 00:46:52 +0100 Subject: [PATCH 45/66] Tried to re-add teardown and updated it to drop only tables that are in the database. Crossing fingers for a second pass. --- seasoned_api/src/database/schemas/teardown.sql | 3 +-- seasoned_api/test/helpers/resetDatabase.js | 3 ++- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/seasoned_api/src/database/schemas/teardown.sql b/seasoned_api/src/database/schemas/teardown.sql index ebc1ac9..750cbab 100644 --- a/seasoned_api/src/database/schemas/teardown.sql +++ b/seasoned_api/src/database/schemas/teardown.sql @@ -1,4 +1,3 @@ DROP TABLE IF EXISTS user; DROP TABLE IF EXISTS search_history; -DROP TABLE IF EXISTS list; -DROP TABLE IF EXISTS list_content; +DROP TABLE IF EXISTS requests; diff --git a/seasoned_api/test/helpers/resetDatabase.js b/seasoned_api/test/helpers/resetDatabase.js index 8fd83a7..06a5152 100644 --- a/seasoned_api/test/helpers/resetDatabase.js +++ b/seasoned_api/test/helpers/resetDatabase.js @@ -3,7 +3,8 @@ const SqliteDatabase = require('src/database/sqliteDatabase'); function resetDatabase() { const database = new SqliteDatabase(':memory:'); return Promise.resolve() - .then(() => database.connect()) + .then(() => database.connect()); + .then(() => database.tearDown()); .then(() => database.setUp()); } From 9e160a7a9150303395944b930ba304c4875790f2 Mon Sep 17 00:00:00 2001 From: Kevin Date: Thu, 21 Dec 2017 00:52:34 +0100 Subject: [PATCH 46/66] Added build status to radme --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index ffa6c70..3a29c52 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ # 🌶 seasonedShows -Your customly seasoned movie and show requester, downloader and organizer +[![Build Status](https://travis-ci.org/KevinMidboe/seasonedShows.svg?branch=testing)](https://travis-ci.org/KevinMidboe/seasonedShows) + +Your customly *seasoned* movie and show requester, downloader and organizer. ## About The goal of this project is to create a full custom stack that can to everything surround downloading, organizing and notifiyng of new media. From the top down we have a website using [tmdb](https://www.themoviedb.com) api to search for from over 350k movies and 70k tv shows. Using [hjone72](https://github.com/hjone72/PlexAuth) great PHP reverse proxy we can have a secure way of allowing users to login with their plex credentials which limits request capabilites to only users that are authenticated to use your plex library. From b4352ad72176b119e2a60103f3f42c130e0c6693 Mon Sep 17 00:00:00 2001 From: KevinMidboe Date: Thu, 21 Dec 2017 00:53:38 +0100 Subject: [PATCH 47/66] Too many ; bro --- seasoned_api/test/helpers/resetDatabase.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/seasoned_api/test/helpers/resetDatabase.js b/seasoned_api/test/helpers/resetDatabase.js index 06a5152..2498852 100644 --- a/seasoned_api/test/helpers/resetDatabase.js +++ b/seasoned_api/test/helpers/resetDatabase.js @@ -3,8 +3,8 @@ const SqliteDatabase = require('src/database/sqliteDatabase'); function resetDatabase() { const database = new SqliteDatabase(':memory:'); return Promise.resolve() - .then(() => database.connect()); - .then(() => database.tearDown()); + .then(() => database.connect()) + .then(() => database.tearDown()) .then(() => database.setUp()); } From 601440d5406564de39c7cd0b1f92fa8cb3df1803 Mon Sep 17 00:00:00 2001 From: KevinMidboe Date: Thu, 21 Dec 2017 01:00:47 +0100 Subject: [PATCH 48/66] Ok. I can explain this bug. It seems commenting out teardown in resetDatabase solves our issue that travis cannot find our teardown script eventough it outputs the exact path it should be at. Will come back to this as it seems it works everywhere else. Good night. --- seasoned_api/test/helpers/resetDatabase.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/seasoned_api/test/helpers/resetDatabase.js b/seasoned_api/test/helpers/resetDatabase.js index 2498852..fdb5c0b 100644 --- a/seasoned_api/test/helpers/resetDatabase.js +++ b/seasoned_api/test/helpers/resetDatabase.js @@ -4,7 +4,7 @@ function resetDatabase() { const database = new SqliteDatabase(':memory:'); return Promise.resolve() .then(() => database.connect()) - .then(() => database.tearDown()) + // .then(() => database.tearDown()) .then(() => database.setUp()); } From d92e6d8c7845564f44608af5a31a3d04062b0eaa Mon Sep 17 00:00:00 2001 From: KevinMidboe Date: Thu, 21 Dec 2017 01:01:43 +0100 Subject: [PATCH 49/66] Psst. Removed console logs from debug hunt. --- seasoned_api/src/database/sqliteDatabase.js | 4 ---- 1 file changed, 4 deletions(-) diff --git a/seasoned_api/src/database/sqliteDatabase.js b/seasoned_api/src/database/sqliteDatabase.js index d30f9f1..a68e207 100644 --- a/seasoned_api/src/database/sqliteDatabase.js +++ b/seasoned_api/src/database/sqliteDatabase.js @@ -75,7 +75,6 @@ class SqliteDatabase { */ tearDown() { const tearDownSchema = this.readSqlFile('tearDown.sql'); - console.log(tearDownSchema); return this.execute(tearDownSchema); } @@ -84,11 +83,8 @@ class SqliteDatabase { * @returns {String} */ readSqlFile(filename) { - console.log(filename) const schemaPath = path.join(this.schemaDirectory, filename); - console.log(schemaPath) const schema = fs.readFileSync(schemaPath).toString('utf-8'); - console.log('schema: ', schema) return schema; } } From 76069f4fea6411e9daf9159dfcb7c3016149f5be Mon Sep 17 00:00:00 2001 From: Kevin Date: Thu, 21 Dec 2017 01:04:00 +0100 Subject: [PATCH 50/66] Added license badfge --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 3a29c52..cde5c89 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,6 @@ # 🌶 seasonedShows [![Build Status](https://travis-ci.org/KevinMidboe/seasonedShows.svg?branch=testing)](https://travis-ci.org/KevinMidboe/seasonedShows) +[![DUB](https://img.shields.io/dub/l/vibe-d.svg)]() Your customly *seasoned* movie and show requester, downloader and organizer. From 976d5cf69f6f17b49bbcd2333d07b4fd4a723c87 Mon Sep 17 00:00:00 2001 From: KevinMidboe Date: Tue, 2 Jan 2018 15:30:23 +0100 Subject: [PATCH 51/66] TorrentTable gets passed a json list of torrents and this displays it in a table view. --- client/app/components/admin/torrentTable.jsx | 85 ++++++++++++++++++++ 1 file changed, 85 insertions(+) create mode 100644 client/app/components/admin/torrentTable.jsx diff --git a/client/app/components/admin/torrentTable.jsx b/client/app/components/admin/torrentTable.jsx new file mode 100644 index 0000000..94d0f43 --- /dev/null +++ b/client/app/components/admin/torrentTable.jsx @@ -0,0 +1,85 @@ +import React, { Component } from 'react'; + +import { fetchJSON } from '../http.jsx'; + +class TorrentTable extends Component { + + constructor() { + super(); + this.state = { + torrentResponse: '', + showResults: false, + } + } + + componentWillReceiveProps(props) { + if (props.response !== undefined) { + this.setState({ + torrentResponse: props.response.map((torrent, index) => { + return ( + + {torrent.name} + {torrent.size} + {torrent.seed} + + + ) + }), + showResults: true, + }) + } + else { + this.setState({ + showResults: false, + }) + } + } + + sendToDownload(torrent) { + let data = {magnet: torrent.magnet} + fetchJSON('https://apollo.kevinmidboe.com/api/v1/pirate/add', 'POST', data) + .then((response) => { + // TODO: Show a card with response that the item has been sent, and the status of response. + console.log(response) + }) + .catch((error) => { + console.error(error); + }) + } + + generateTable() { + let style = { + table: { + width: '80%', + marginRight: 'auto', + marginLeft: 'auto', + }, + } + + return ( + + + + + + + + + + + { this.state.torrentResponse } + +
NameSizeSeedAdd
+ ); + } + + render() { + return ( +
+ { this.state.showResults ? this.generateTable() : null } +
+ ) + } +} + +export default TorrentTable \ No newline at end of file From 4d99cae74c92d992862acabb097110f4dc231e3c Mon Sep 17 00:00:00 2001 From: KevinMidboe Date: Tue, 9 Jan 2018 15:52:28 +0100 Subject: [PATCH 52/66] Pulled our info button out to its own component, changed the size of the poster that is fetched from tmdb from width of 300 to 185 pixels. Also some minor changes to the info displayed, no also has type listed in the object. --- client/app/components/SearchObject.jsx | 23 ++++++----------------- 1 file changed, 6 insertions(+), 17 deletions(-) diff --git a/client/app/components/SearchObject.jsx b/client/app/components/SearchObject.jsx index 2be95d3..b61f4ea 100644 --- a/client/app/components/SearchObject.jsx +++ b/client/app/components/SearchObject.jsx @@ -5,6 +5,7 @@ import Notifications, {notify} from 'react-notify-toast'; // StyleComponents import searchObjectCSS from './styles/searchObject.jsx'; import buttonsCSS from './styles/buttons.jsx'; +import InfoButton from './buttons/InfoButton.jsx'; var MediaQuery = require('react-responsive'); @@ -48,7 +49,7 @@ class SearchObject { if (this.poster == null || this.poster == undefined) { var posterPath = 'https://openclipart.org/image/2400px/svg_to_png/211479/Simple-Image-Not-Found-Icon.png' } else { - var posterPath = 'https://image.tmdb.org/t/p/w300' + this.poster; + var posterPath = 'https://image.tmdb.org/t/p/w185' + this.poster; } var backgroundPath = 'https://image.tmdb.org/t/p/w640_and_h360_bestv2/' + this.background; @@ -75,11 +76,6 @@ class SearchObject { ; } - if (this.type === 'movie') - var themoviedbLink = 'https://www.themoviedb.org/movie/' + this.id - else if (this.type === 'show') - var themoviedbLink = 'https://www.themoviedb.org/tv/' + this.id - // TODO go away from using mediaQuery, and create custom resizer return (
@@ -92,7 +88,9 @@ class SearchObject {
{this.title}

- Released: { this.year } | Rating: {this.rating} + + Released: { this.year } | Rating: {this.rating} | Type: {this.type} +

{this.summary}

@@ -108,16 +106,7 @@ class SearchObject {
{foundInPlex} - - - - Info - - +
From 33cb6f5f09c1843d8b7369933133447f409f5e89 Mon Sep 17 00:00:00 2001 From: KevinMidboe Date: Tue, 9 Jan 2018 16:03:11 +0100 Subject: [PATCH 53/66] In admin we added a updatehandler so that a child can update this parent element. Also added our new sidebar for filtering our requested items by query and category. --- client/app/components/admin/Admin.jsx | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/client/app/components/admin/Admin.jsx b/client/app/components/admin/Admin.jsx index 50047b1..d2faeef 100644 --- a/client/app/components/admin/Admin.jsx +++ b/client/app/components/admin/Admin.jsx @@ -20,10 +20,16 @@ class AdminComponent extends React.Component { this.state = { requested_objects: '', } + + this.updateHandler = this.updateHandler.bind(this) } // Fetches all requested elements and updates the state with response componentWillMount() { + this.fetchRequestedItems() + } + + fetchRequestedItems() { fetchJSON('https://apollo.kevinmidboe.com/api/v1/plex/requests/all', 'GET') .then(result => { this.setState({ @@ -32,6 +38,10 @@ class AdminComponent extends React.Component { }) } + updateHandler() { + this.fetchRequestedItems() + } + // Displays loginform if not logged in and passes response from // api call to sidebar and infoPanel through props verifyLoggedIn() { @@ -52,18 +62,19 @@ class AdminComponent extends React.Component { return (
+
+ +
-
- -
) } From 279b004aad1344bc7ba5cc831b7a366beb7c1d52 Mon Sep 17 00:00:00 2001 From: KevinMidboe Date: Tue, 9 Jan 2018 16:19:43 +0100 Subject: [PATCH 54/66] We re-wrote most off the look and feel of this page. No we get a header that is generated with all the info for a requested element. This has a indicator generated by generateStatusIndicator, showing if the element is requested, downloading or downloaded. Then we have a function for generating the type icon, that is movie/show that is displayed in the header. The summary for a movie might be long so if it is over 180 chars than it is cut and a show more button is displayed. After the info on the item we import PirateSearch that takes a name input and displays a search result for that query in a table view. --- .../app/components/admin/AdminRequestInfo.jsx | 165 ++++++++++++++---- 1 file changed, 128 insertions(+), 37 deletions(-) diff --git a/client/app/components/admin/AdminRequestInfo.jsx b/client/app/components/admin/AdminRequestInfo.jsx index 2f51113..c483fc3 100644 --- a/client/app/components/admin/AdminRequestInfo.jsx +++ b/client/app/components/admin/AdminRequestInfo.jsx @@ -3,6 +3,7 @@ import React, { Component } from 'react'; import { fetchJSON } from '../http.jsx'; import PirateSearch from './PirateSearch.jsx' +import InfoButton from '../buttons/InfoButton.jsx'; // Stylesheets import requestInfoCSS from '../styles/adminRequestInfo.jsx' @@ -11,6 +12,12 @@ import buttonsCSS from '../styles/buttons.jsx'; // Interactive button import Interactive from 'react-interactive'; + +String.prototype.capitalize = function() { + return this.charAt(0).toUpperCase() + this.slice(1); +} + + class AdminRequestInfo extends Component { constructor() { @@ -18,6 +25,8 @@ class AdminRequestInfo extends Component { this.state = { statusValue: '', + movieInfo: undefined, + expandedSummary: false, } this.requestInfo = ''; @@ -26,12 +35,14 @@ class AdminRequestInfo extends Component { componentWillReceiveProps(props) { this.requestInfo = props.selectedRequest; this.state.statusValue = this.requestInfo.status; + this.state.expandedSummary = false; + this.fetchIteminfo() } userAgent(agent) { if (agent) { try { - return agent.split(" ")[1].replace(/[\(\;]/g, ''); + return agent.split(" ")[1].replace(/[\(\;]/g, ''); } catch(e) { return agent; @@ -62,60 +73,140 @@ class AdminRequestInfo extends Component { fetchJSON('https://apollo.kevinmidboe.com/api/v1/plex/request/' + itemID, 'PUT', apiData) .then((response) => { console.log('Response, updateRequestStatus: ', response) - }) - - this.setState({ - statusValue: eventValue + this.props.updateHandler() }) } + generateStatusIndicator(status) { + switch (status) { + case 'requested': + // Yellow + return 'linear-gradient(to right, rgb(63, 195, 243) 0, rgb(63, 195, 243) 10px, #fff 4px, #fff 100%) no-repeat' + case 'downloading': + // Blue + return 'linear-gradient(to right, rgb(255, 225, 77) 0, rgb(255, 225, 77) 10px, #fff 4px, #fff 100%) no-repeat' + case 'downloaded': + // Green + return 'linear-gradient(to right, #39aa56 0, #39aa56 10px, #fff 4px, #fff 100%) no-repeat' + default: + return 'linear-gradient(to right, grey 0, grey 10px, #fff 4px, #fff 100%) no-repeat' + } + } + + generateTypeIcon(type) { + if (type === 'show') + return ( + + ) + else if (type === 'movie') + return ( + + ) + else + return ( + + ) + } + + toggleSummmaryLength() { + this.setState({ + expandedSummary: !this.state.expandedSummary + }) + } + + generateSummary() { + // { this.state.movieInfo != undefined ? this.state.movieInfo.summary : 'Loading...' } + const info = this.state.movieInfo; + if (info !== undefined) { + const summary = this.state.movieInfo.summary + const summary_short = summary.slice(0, 180); + + return ( +
+ Matched: {String(info.matchedInPlex)}
+ Rating: {info.rating}
+ Popularity: {info.popularity}
+ { + (summary.length > 180 && this.state.expandedSummary === false) ? + Summary: { summary_short } this.toggleSummmaryLength()}>... Show more + : + Summary: { summary } this.toggleSummmaryLength()}> Show less + + } +
+ ) + } else { + return Loading... + } + } + requested_by_user(request_user) { if (request_user === 'NULL') return undefined return ( - Requested by: {request_user} + Requested by: {request_user} ) } + fetchIteminfo() { + const itemID = this.requestInfo.id; + const type = this.requestInfo.type; + + fetchJSON('https://apollo.kevinmidboe.com/api/v1/tmdb/' + itemID +'&type='+type, 'GET') + .then((response) => { + console.log('Response, getInfo:', response) + this.setState({ + movieInfo: response + }); + console.log(this.state.movieInfo) + }) + } + displayInfo() { const request = this.props.selectedRequest; if (request) { - return ( -
-
- {request.name} - {request.year} -
+ requestInfoCSS.info.background = this.generateStatusIndicator(request.status); -
- type: {request.type}
+ return ( +
- {this.generateStatusDropdown()}
- - status: {request.status}
- ip: {request.ip}
- user_agent: {this.userAgent(request.user_agent)}
- request_date: {request.requested_date}
- { this.requested_by_user(request.requested_by) } +
+ {request.name} {request.year} + + {this.generateTypeIcon(request.type)} + {/*{request.type.capitalize()}
*/} +
+
+ +
+
+ Movie poster image +
+ +
+

Request info

+ + status:{ request.status }
+ ip:{ request.ip }
+ user_agent:{ this.userAgent(request.user_agent) }
+ request_date:{ request.requested_date}
+ { this.requested_by_user(request.requested_by) }
+ { this.generateStatusDropdown() }
+
+ +
+

Movie info

+ + { this.generateSummary() } +
+
+ + +
- -
- {}} - style={buttonsCSS.edit} - focus={buttonsCSS.edit_hover} - hover={buttonsCSS.edit_hover}> - - Show info - - - -
-
- ) + ) } } From f7c6f6603b9a46a8ddbce43c1219d0b0d270ed42 Mon Sep 17 00:00:00 2001 From: KevinMidboe Date: Tue, 9 Jan 2018 16:20:18 +0100 Subject: [PATCH 55/66] Renamed style variable to match the imported stylesheet. --- client/app/components/admin/AdminRequestInfo.jsx | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/client/app/components/admin/AdminRequestInfo.jsx b/client/app/components/admin/AdminRequestInfo.jsx index c483fc3..a67e7e1 100644 --- a/client/app/components/admin/AdminRequestInfo.jsx +++ b/client/app/components/admin/AdminRequestInfo.jsx @@ -3,15 +3,13 @@ import React, { Component } from 'react'; import { fetchJSON } from '../http.jsx'; import PirateSearch from './PirateSearch.jsx' +// No in use! import InfoButton from '../buttons/InfoButton.jsx'; // Stylesheets import requestInfoCSS from '../styles/adminRequestInfo.jsx' import buttonsCSS from '../styles/buttons.jsx'; -// Interactive button -import Interactive from 'react-interactive'; - String.prototype.capitalize = function() { return this.charAt(0).toUpperCase() + this.slice(1); @@ -175,7 +173,7 @@ class AdminRequestInfo extends Component {
{request.name} {request.year} - {this.generateTypeIcon(request.type)} + {this.generateTypeIcon(request.type)} {/*{request.type.capitalize()}
*/}
From e64d88bfdf93c44b959833bf13103424d8d2a205 Mon Sep 17 00:00:00 2001 From: KevinMidboe Date: Tue, 9 Jan 2018 16:20:41 +0100 Subject: [PATCH 56/66] The stylesheet for all of requestInfo page. --- .../components/styles/adminRequestInfo.jsx | 59 +++++++++++++++++-- 1 file changed, 53 insertions(+), 6 deletions(-) diff --git a/client/app/components/styles/adminRequestInfo.jsx b/client/app/components/styles/adminRequestInfo.jsx index 0d13af2..d3a525f 100644 --- a/client/app/components/styles/adminRequestInfo.jsx +++ b/client/app/components/styles/adminRequestInfo.jsx @@ -2,10 +2,57 @@ export default { wrapper: { width: '100%', }, - headerWrapper: { - width: '100%', - }, - poster: { - minHeight: '450px', - }, + stick: { + marginBottom: '1em', + }, + + title: { + fontSize: '2em', + }, + image: { + width: '105px', + borderRadius: '4px', + }, + + info: { + paddingTop: '1em', + paddingBottom: '0.5em', + marginRight: '2em', + backgroundColor: 'white', + border: '1px solid #d0d0d0', + borderRadius: '2px', + display: 'flex', + }, + + type_icon: { + marginLeft: '-1.9em', + marginRight: '0.7em', + }, + type_text: { + verticalAlign: 'super', + }, + + + info_poster: { + marginLeft: '2em', + flex: '0 1 10%' + }, + + info_request: { + flex: '0 1 auto' + }, + info_request_header: { + margin: '0', + marginBottom: '0.5em', + }, + + info_movie: { + maxWidth: '70%', + marginLeft: '1em', + flex: '0 1 auto', + }, + info_movie_header: { + margin: '0', + marginBottom: '0.5em', + } } \ No newline at end of file From 262093d196f6bd8d9863411db615d6a3de8ec2d9 Mon Sep 17 00:00:00 2001 From: KevinMidboe Date: Tue, 9 Jan 2018 16:25:52 +0100 Subject: [PATCH 57/66] Completely re-wrote our sidebar. Now we can filter by search and category on top and a list of all elements are displayed below in a scrollable list. Each element has a indicator showing if the item is requested, downloading or downloaded. There are several generator functions that are to be pulled out in their seperate components. --- client/app/components/admin/Sidebar.jsx | 387 ++++++++++-------- client/app/components/styles/adminSidebar.jsx | 117 +++++- 2 files changed, 321 insertions(+), 183 deletions(-) diff --git a/client/app/components/admin/Sidebar.jsx b/client/app/components/admin/Sidebar.jsx index 7d48397..187e72c 100644 --- a/client/app/components/admin/Sidebar.jsx +++ b/client/app/components/admin/Sidebar.jsx @@ -7,207 +7,242 @@ import sidebarCSS from '../styles/adminSidebar.jsx' class SidebarComponent extends Component { - constructor(props){ - super(props) - // Constructor with states holding the search query and the element of reponse. - this.state = { - filterValue: '', - filterQuery: '', - requestItemsToBeDisplayed: [], - listItemSelected: '', - } - } + constructor(props){ + super(props) + // Constructor with states holding the search query and the element of reponse. + this.state = { + filterValue: '', + filterQuery: '', + requestItemsToBeDisplayed: [], + listItemSelected: '', + height: '0', + } - // Where we wait for api response to be delivered from parent through props - componentWillReceiveProps(props) { - this.state.listItemSelected = props.listItemSelected; - this.displayRequestedElementsInfo(props.requested_objects); - } + this.updateWindowDimensions = this.updateWindowDimensions.bind(this); + } - // Inputs a date and returns a text string that matches how long it was since - convertDateToDaysSince(date) { - var oneDay = 24*60*60*1000; - var firstDate = new Date(date); - var secondDate = new Date(); + // Where we wait for api response to be delivered from parent through props + componentWillReceiveProps(props) { + this.state.listItemSelected = props.listItemSelected; + this.displayRequestedElementsInfo(props.requested_objects); + } - var diffDays = Math.round(Math.abs((firstDate.getTime() - secondDate.getTime()) / oneDay)); - - switch (diffDays) { - case 0: - return 'Today'; - case 1: - return '1 day ago' - default: - return diffDays + ' days ago' - } - } + componentDidMount() { + this.updateWindowDimensions(); + window.addEventListener('resize', this.updateWindowDimensions); + } - // Called from our dropdown, receives a filter string and checks it with status field - // of our request objects. - filterItems(filterValue) { - let filteredRequestElements = this.props.requested_objects.map((item, index) => { - if (item.status === filterValue || filterValue === 'all') - return this.generateListElements(index, item); - }) + componentWillUnmount() { + window.removeEventListener('resize', this.updateWindowDimensions); + } - this.setState({ - requestItemsToBeDisplayed: filteredRequestElements, - filterValue: filterValue, - }) - } + updateWindowDimensions() { + this.setState({ height: window.innerHeight }); + } + + // Inputs a date and returns a text string that matches how long it was since + convertDateToDaysSince(date) { + var oneDay = 24*60*60*1000; + var firstDate = new Date(date); + var secondDate = new Date(); + + var diffDays = Math.round(Math.abs((firstDate.getTime() - secondDate.getTime()) / oneDay)); + + switch (diffDays) { + case 0: + return 'Today'; + case 1: + return '1 day ago' + default: + return diffDays + ' days ago' + } + } + + // Called from our dropdown, receives a filter string and checks it with status field + // of our request objects. + filterItems(filterValue) { + let filteredRequestElements = this.props.requested_objects.map((item, index) => { + if (item.status === filterValue || filterValue === 'all') + return this.generateListElements(index, item); + }) + + this.setState({ + requestItemsToBeDisplayed: filteredRequestElements, + filterValue: filterValue, + }) + } - // Updates the internal state of the query filter and updates the list to only - // display names matching the query. This is real-time filtering. - updateFilterQuery(event) { - const query = event.target.value; + // Updates the internal state of the query filter and updates the list to only + // display names matching the query. This is real-time filtering. + updateFilterQuery(event) { + const query = event.target.value; - let filteredByQuery = this.props.requested_objects.map((item, index) => { - if (item.name.toLowerCase().indexOf(query.toLowerCase()) != -1) - return this.generateListElements(index, item); - }) + let filteredByQuery = this.props.requested_objects.map((item, index) => { + if (item.name.toLowerCase().indexOf(query.toLowerCase()) != -1) + return this.generateListElements(index, item); + }) - this.setState({ - requestItemsToBeDisplayed: filteredByQuery, - filterQuery: query, - }); - } + console.log(filteredByQuery) + this.setState({ + requestItemsToBeDisplayed: filteredByQuery, + filterQuery: query, + }); + } - generateFilterDropdown() { - return ( - - ) - } + generateFilterSearch() { + return ( +
+
+ this.updateFilterQuery(event)} + value={this.state.filterQuery}/> + + + + + + + + +
+
+ ) + } - generateFilterSearchbar() { - return ( - this.updateFilterQuery(event)} - value={this.state.filterQuery}/> - ) - } + generateNav() { + let filterValue = this.state.filterValue; - // A colored bar indicating the status of a item by color. - generateRequestIndicator(status) { - let statusColor; + return ( + + ) + } + + generateBody(cards) { + let style = sidebarCSS.ulCard; + style.maxHeight = this.state.height - 160; + + return ( +
    + { cards } +
+ ) + } - generateListElements(index, item) { - if (index == this.state.listItemSelected) { - return ( -
-
- {item.name } -
- { this.convertDateToDaysSince(item.requested_date) } -
-
- Status: { item.status } -
- Matches found: 0 - { this.generateRequestIndicator(item.status) } -
- ) - } - else - return ( - - + generateListElements(index, item) { + let statusBar; - {item.name } -
- { this.convertDateToDaysSince(item.requested_date) } -
-
- { this.generateRequestIndicator(item.status) } -
- - ) - } + switch (item.status) { + case 'requested': + // Yellow + statusBar = { background: 'linear-gradient(to right, rgb(63, 195, 243) 0, rgb(63, 195, 243) 4px, #fff 4px, #fff 100%) no-repeat' } + break; + case 'downloading': + // Blue + statusBar = { background: 'linear-gradient(to right, rgb(255, 225, 77) 0, rgb(255, 225, 77) 4px, #fff 4px, #fff 100%) no-repeat' } + break; + case 'downloaded': + // Green + statusBar = { background: 'linear-gradient(to right, #39aa56 0, #39aa56 4px, #fff 4px, #fff 100%) no-repeat' } + break; + default: + statusBar = { background: 'linear-gradient(to right, grey 0, grey 4px, #fff 4px, #fff 100%) no-repeat' } + } - // This is our main loader that gets called when we receive api response through props from parent - displayRequestedElementsInfo(requested_objects) { - let requestedElement = requested_objects.map((item, index) => { - if (['requested', 'downloading', 'downloaded'].indexOf(this.state.filterValue) != -1) { - if (item.status === this.state.filterValue){ - return this.generateListElements(index, item); - } - } + statusBar.listStyleType = 'none'; - else if (this.state.filterQuery !== '') { - if (item.name.toLowerCase().indexOf(this.state.filterQuery.toLowerCase()) != -1) - return this.generateListElements(index, item); - } + return ( + +
  • + - else - return this.generateListElements(index, item); - }) +

    + { item.name } +

    - this.setState({ - requestItemsToBeDisplayed: requestedElement, - }) - } +

    + Requested: + + +

    +
    +
  • + + ) + } - render() { - let bodyCSS = sidebarCSS.body; - if (typeof InstallTrigger !== 'undefined') - bodyCSS.width = '-moz-min-content'; + // This is our main loader that gets called when we receive api response through props from parent + displayRequestedElementsInfo(requested_objects) { + let requestedElement = requested_objects.map((item, index) => { + if (['requested', 'downloading', 'downloaded'].indexOf(this.state.filterValue) != -1) { + if (item.status === this.state.filterValue){ + return this.generateListElements(index, item); + } + } + else if (this.state.filterQuery !== '') { + if (item.name.toLowerCase().indexOf(this.state.filterQuery.toLowerCase()) != -1) + return this.generateListElements(index, item); + } + else + return this.generateListElements(index, item); + }) - return ( -
    -

    Hello from the sidebar:

    - { this.generateFilterDropdown() } - { this.generateFilterSearchbar() } -
    - { this.state.requestItemsToBeDisplayed } -
    -
    - ); - } + this.setState({ + requestItemsToBeDisplayed: this.generateBody(requestedElement) + }) + } + + render() { + // if (typeof InstallTrigger !== 'undefined') + // bodyCSS.width = '-moz-min-content'; + + return ( +
    +

    Requested items

    + { this.generateFilterSearch() } + { this.generateNav() } + +
    + { this.state.requestItemsToBeDisplayed } +
    +
    + ); + } } export default SidebarComponent; \ No newline at end of file diff --git a/client/app/components/styles/adminSidebar.jsx b/client/app/components/styles/adminSidebar.jsx index c10fe49..16f2371 100644 --- a/client/app/components/styles/adminSidebar.jsx +++ b/client/app/components/styles/adminSidebar.jsx @@ -1,13 +1,15 @@ export default { + header: { + textAlign: 'center', + }, body: { backgroundColor: 'white', - width: 'min-content', }, parentElement: { display: 'inline-block', - width: '300px', - border: '1px solid black', + width: '100%', + border: '1px solid grey', borderRadius: '2px', padding: '4px', margin: '4px', @@ -16,7 +18,8 @@ export default { }, parentElement_hover: { - marginLeft: '10px', + backgroundColor: '#f8f8f8', + pointer: 'hand', }, parentElement_active: { @@ -25,8 +28,8 @@ export default { parentElement_selected: { display: 'inline-block', - width: '300px', - border: '1px solid black', + width: '100%', + border: '1px solid grey', borderRadius: '2px', padding: '4px', margin: '4px 0px 4px 4px', @@ -35,7 +38,7 @@ export default { }, title: { - maxWidth: '70%', + maxWidth: '65%', display: 'inline-flex', }, @@ -47,4 +50,104 @@ export default { rightContainer: { float: 'right', }, + + + + searchSidebar: { + height: '4em', + }, + searchInner: { + top: '0', + right: '0', + left: '0', + bottom: '0', + margin: 'auto', + width: '90%', + minWidth: '280px', + height: '30px', + border: '1px solid #d0d0d0', + borderRadius: '4px', + overflow: 'hidden' + }, + searchTextField: { + display: 'inline-block', + width: '90%', + padding: '.3em', + verticalAlign: 'middle', + border: 'none', + background: '#fff', + fontSize: '14px', + marginTop: '-7px', + }, + searchIcon: { + width: '15px', + height: '16px', + marginRight: '4px', + marginTop: '7px', + }, + searchSVGIcon: { + fill: 'none', + stroke: '#9d9d9d', + strokeLinecap: 'round', + strokeLinejoin: 'round', + strokeMiterlimit: '10', + }, + + + ulFilterSelectors: { + borderBottom: '2px solid #f1f1f1', + display: 'flex', + padding: '0', + margin: '0', + listStyle: 'none', + justifyContent: 'space-evenly', + }, + aFilterSelectors: { + color: '#3eaaaf', + fontSize: '16px', + cursor: 'pointer', + }, + spanFilterSelectors: { + content: '""', + bottom: '-2px', + display: 'block', + width: '100%', + height: '2px', + backgroundColor: '#3eaaaa', + }, + + + ulCard: { + margin: '1em 0 0 0', + padding: '0', + listStyle: 'none', + borderBottom: '.46rem solid #f1f1f', + backgroundColor: '#f1f1f1', + overflow: 'scroll', + }, + + + card: { + padding: '.1em .5em .8em 1.5em', + marginBottom: '.26rem', + height: 'auto', + cursor: 'pointer', + }, + cardSelected: { + padding: '.1em .5em .8em 1.5em', + marginBottom: '.26rem', + height: 'auto', + cursor: 'pointer', + + backgroundColor: '#f9f9f9', + }, + titleCard: { + fontSize: '15px', + fontWeight: '400', + whiteSpace: 'no-wrap', + textDecoration: 'none', + }, + pCard: { + margin: '0', + }, } \ No newline at end of file From c8a2ea9907ea21a4a901b105bb67e7d4140d3188 Mon Sep 17 00:00:00 2001 From: KevinMidboe Date: Tue, 9 Jan 2018 16:28:34 +0100 Subject: [PATCH 58/66] This now contains the button and request function for getting torrents matching a serach query given by the name of the elment. The result is passed to the child component torrentTable which renders the result in a table view. --- client/app/components/admin/PirateSearch.jsx | 130 ++++++++++--------- 1 file changed, 69 insertions(+), 61 deletions(-) diff --git a/client/app/components/admin/PirateSearch.jsx b/client/app/components/admin/PirateSearch.jsx index 25917fa..fcca252 100644 --- a/client/app/components/admin/PirateSearch.jsx +++ b/client/app/components/admin/PirateSearch.jsx @@ -1,6 +1,9 @@ import React, { Component } from 'react'; import { fetchJSON } from '../http.jsx'; +// Components +import TorrentTable from './torrentTable.jsx' + // Stylesheets import btnStylesheet from '../styles/buttons.jsx'; @@ -10,72 +13,77 @@ import Interactive from 'react-interactive'; import Loading from '../images/loading.jsx' class PirateSearch extends Component { - constructor() { - super(); - this.state = { - response: [], - name: '', - loading: '', - } - } + constructor() { + super(); + this.state = { + torrentResponse: undefined, + name: '', + loading: null, + showButton: true, + } + } - sendToDownload(torrent) { - let data = {magnet: torrent.magnet} - fetchJSON('https://apollo.kevinmidboe.com/api/v1/pirate/add', 'POST', data) - .then((response) => { - console.log(response) - }) - } + componentWillReceiveProps(props) { + if (props.name != this.state.name) { + this.setState({ + torrentResponse: undefined, + showButton: true, + }) + } + } - searchTheBay() { - const query = this.props.name; - const type = this.props.type; + searchTheBay() { + const query = this.props.name; + const type = this.props.type; - this.setState({ - loading: - }) + this.setState({ + showButton: false, + loading: , + }) - fetchJSON('https://apollo.kevinmidboe.com/api/v1/pirate/search?query='+query+'&type='+type, 'GET') - .then((response) => { - console.log(response.torrents) - this.setState({ - loading: '', - response: response.torrents.map((torrent, index) => { - return ( -
    - {torrent.name}
    - {torrent.size}
    - {torrent.seed}
    - -

    -
    - ) - }) - }) - }) - } + fetchJSON('https://apollo.kevinmidboe.com/api/v1/pirate/search?query='+query+'&type='+type, 'GET') + // fetchJSON('http://localhost:31459/api/v1/pirate/search?query='+query+'&type='+type, 'GET') + .then((response) => { + this.setState({ + torrentResponse: response.torrents, + loading: null, + }) + }) + .catch((error) => { + console.error(error); + this.setState({ + showButton: true, + }) + }) + } - render() { - return ( -
    -
    - {this.searchTheBay()}} - style={btnStylesheet.submit} - focus={btnStylesheet.submit_hover} - hover={btnStylesheet.submit_hover}> - - Search for torrents - -
    + render() { + btnStylesheet.submit.top = '50%' + btnStylesheet.submit.position = 'absolute' + btnStylesheet.submit.marginLeft = '-75px' - { this.state.loading } - - {this.state.response} -
    - ) - } + return ( +
    + { this.state.showButton ? +
    + {this.searchTheBay()}} + style={btnStylesheet.submit} + focus={btnStylesheet.submit_hover} + hover={btnStylesheet.submit_hover}> + + Search for torrents + +
    + : null } + + { this.state.loading } + + +
    + ) + } } -export default PirateSearch \ No newline at end of file +export default PirateSearch From da54ad006672f788b3e6ad8186d1d29f1b13ea59 Mon Sep 17 00:00:00 2001 From: KevinMidboe Date: Tue, 9 Jan 2018 16:30:38 +0100 Subject: [PATCH 59/66] Changed import name of torrentTable to correct TorrentTable --- client/app/components/admin/PirateSearch.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/app/components/admin/PirateSearch.jsx b/client/app/components/admin/PirateSearch.jsx index fcca252..c60e7c2 100644 --- a/client/app/components/admin/PirateSearch.jsx +++ b/client/app/components/admin/PirateSearch.jsx @@ -2,7 +2,7 @@ import React, { Component } from 'react'; import { fetchJSON } from '../http.jsx'; // Components -import TorrentTable from './torrentTable.jsx' +import TorrentTable from './TorrentTable.jsx' // Stylesheets import btnStylesheet from '../styles/buttons.jsx'; From 10ef1bfa697c68ae3ba78c49897b39aa119cc9b8 Mon Sep 17 00:00:00 2001 From: KevinMidboe Date: Tue, 9 Jan 2018 16:33:46 +0100 Subject: [PATCH 60/66] This receives a response of list of torrents as props and displays them in a table view. From here we can send to download and also filter the results by query. --- .../components/styles/adminTorrentTable.jsx | 48 +++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 client/app/components/styles/adminTorrentTable.jsx diff --git a/client/app/components/styles/adminTorrentTable.jsx b/client/app/components/styles/adminTorrentTable.jsx new file mode 100644 index 0000000..f6098bd --- /dev/null +++ b/client/app/components/styles/adminTorrentTable.jsx @@ -0,0 +1,48 @@ +export default { + table: { + width: '80%', + marginRight: 'auto', + marginLeft: 'auto', + }, + + searchSidebar: { + height: '4em', + marginTop: '1em', + }, + searchInner: { + top: '0', + right: '0', + left: '0', + bottom: '0', + margin: 'auto', + width: '50%', + minWidth: '280px', + height: '30px', + border: '1px solid #d0d0d0', + borderRadius: '4px', + overflow: 'hidden' + }, + searchTextField: { + display: 'inline-block', + width: '95%', + padding: '.3em', + verticalAlign: 'middle', + border: 'none', + background: '#fff', + fontSize: '14px', + marginTop: '-7px', + }, + searchIcon: { + width: '15px', + height: '16px', + marginRight: '4px', + marginTop: '7px', + }, + searchSVGIcon: { + fill: 'none', + stroke: '#9d9d9d', + strokeLinecap: 'round', + strokeLinejoin: 'round', + strokeMiterlimit: '10', + }, +} \ No newline at end of file From 519eba8fda175042846ad7cc216cfdd763e6474e Mon Sep 17 00:00:00 2001 From: KevinMidboe Date: Tue, 9 Jan 2018 16:36:24 +0100 Subject: [PATCH 61/66] Added some css for admin component that defines the styles for parent elements of imported components. --- client/app/components/admin/torrentTable.jsx | 85 ------------------- .../app/components/styles/adminComponent.jsx | 12 ++- 2 files changed, 10 insertions(+), 87 deletions(-) delete mode 100644 client/app/components/admin/torrentTable.jsx diff --git a/client/app/components/admin/torrentTable.jsx b/client/app/components/admin/torrentTable.jsx deleted file mode 100644 index 94d0f43..0000000 --- a/client/app/components/admin/torrentTable.jsx +++ /dev/null @@ -1,85 +0,0 @@ -import React, { Component } from 'react'; - -import { fetchJSON } from '../http.jsx'; - -class TorrentTable extends Component { - - constructor() { - super(); - this.state = { - torrentResponse: '', - showResults: false, - } - } - - componentWillReceiveProps(props) { - if (props.response !== undefined) { - this.setState({ - torrentResponse: props.response.map((torrent, index) => { - return ( - - {torrent.name} - {torrent.size} - {torrent.seed} - - - ) - }), - showResults: true, - }) - } - else { - this.setState({ - showResults: false, - }) - } - } - - sendToDownload(torrent) { - let data = {magnet: torrent.magnet} - fetchJSON('https://apollo.kevinmidboe.com/api/v1/pirate/add', 'POST', data) - .then((response) => { - // TODO: Show a card with response that the item has been sent, and the status of response. - console.log(response) - }) - .catch((error) => { - console.error(error); - }) - } - - generateTable() { - let style = { - table: { - width: '80%', - marginRight: 'auto', - marginLeft: 'auto', - }, - } - - return ( - - - - - - - - - - - { this.state.torrentResponse } - -
    NameSizeSeedAdd
    - ); - } - - render() { - return ( -
    - { this.state.showResults ? this.generateTable() : null } -
    - ) - } -} - -export default TorrentTable \ No newline at end of file diff --git a/client/app/components/styles/adminComponent.jsx b/client/app/components/styles/adminComponent.jsx index 9e131aa..721a740 100644 --- a/client/app/components/styles/adminComponent.jsx +++ b/client/app/components/styles/adminComponent.jsx @@ -1,8 +1,16 @@ export default { sidebar: { - float: 'left', + float: 'left', + width: '18%', + minWidth: '250px', + fontFamily: '"Open Sans", sans-serif', + fontSize: '14px', + borderRight: '2px solid #f2f2f2', }, selectedObjectPanel: { - float: 'left', + width: '80%', + float: 'right', + fontFamily: '"Open Sans", sans-serif', + marginTop: '1em', } } \ No newline at end of file From 21a94d88a9f69b95ebaf72fcc5b7536a0db1b932 Mon Sep 17 00:00:00 2001 From: KevinMidboe Date: Tue, 9 Jan 2018 16:39:16 +0100 Subject: [PATCH 62/66] Added a seperate component for the info button. --- client/app/components/buttons/InfoButton.jsx | 52 ++++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 client/app/components/buttons/InfoButton.jsx diff --git a/client/app/components/buttons/InfoButton.jsx b/client/app/components/buttons/InfoButton.jsx new file mode 100644 index 0000000..fa8f4f4 --- /dev/null +++ b/client/app/components/buttons/InfoButton.jsx @@ -0,0 +1,52 @@ +import React, { Component } from 'react'; +import Interactive from 'react-interactive'; + +import buttonsCSS from '../styles/buttons.jsx'; + + +class InfoButton extends Component { + constructor(props) { + super(props); + + if (props) { + this.state = { + id: props.id, + type: props.type, + } + } + } + + componentWillReceiveProps(props) { + this.setState({ + id: props.id, + type: props.type, + }) + } + + getTMDBLink() { + const id = this.state.id; + const type = this.state.type; + + if (type === 'movie') + return 'https://www.themoviedb.org/movie/' + id + else if (type === 'show') + return 'https://www.themoviedb.org/tv/' + id + } + + render() { + return ( + + + + More info + + + ); + } +} + +export default InfoButton; \ No newline at end of file From b8fb71854060e321d6547cc8c27ecdb9d73a4589 Mon Sep 17 00:00:00 2001 From: KevinMidboe Date: Tue, 9 Jan 2018 16:40:49 +0100 Subject: [PATCH 63/66] Added NODE_ENV environmental variable production when building. --- client/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/package.json b/client/package.json index f166ff8..3529ce3 100644 --- a/client/package.json +++ b/client/package.json @@ -7,7 +7,7 @@ "license": "MIT", "scripts": { "start": "webpack-dev-server --open --config webpack.dev.js", - "build": "webpack --config webpack.prod.js", + "build": "NODE_ENV=production webpack --config webpack.prod.js", "build_dev": "webpack --config webpack.dev.js" }, "dependencies": { From e9f6f3a656b03ae557ab90a516c964f458efa0ec Mon Sep 17 00:00:00 2001 From: KevinMidboe Date: Tue, 9 Jan 2018 16:44:10 +0100 Subject: [PATCH 64/66] We added a new python app for fetching torrents. The api is now changed to use the new project. IMPORTANT that a check for if jackett is set up is done, if else this is will not run correctly. --- seasoned_api/src/pirate/pirateRepository.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/seasoned_api/src/pirate/pirateRepository.js b/seasoned_api/src/pirate/pirateRepository.js index 97bb77c..ab4ca3c 100644 --- a/seasoned_api/src/pirate/pirateRepository.js +++ b/seasoned_api/src/pirate/pirateRepository.js @@ -6,12 +6,12 @@ var PythonShell = require('python-shell'); async function find(searchterm, callback) { var options = { - pythonPath: '/usr/bin/python3', - // pythonPath: '/Library/Frameworks/Python.framework/Versions/3.6/bin/python3', - args: [searchterm] + // pythonPath: '/usr/bin/python3', + pythonPath: '/Library/Frameworks/Python.framework/Versions/3.6/bin/python3', + args: [searchterm, '-s', 'jackett', '--print'] } - PythonShell.run('../app/pirateSearch.py', options, callback); + PythonShell.run('../app/torrent_search/torrentSearch/search.py', options, callback); // PythonShell does not support return }; @@ -29,6 +29,7 @@ async function callPythonAddMagnet(magnet, callback) { async function SearchPiratebay(query) { return await new Promise((resolve) => { return find(query, function(err, results) { + console.log('err', err, '. result', results); resolve(JSON.parse(results, null, '\t')); }) }) From 65bce27a2b21629a09106a7befa566a7378a2295 Mon Sep 17 00:00:00 2001 From: KevinMidboe Date: Tue, 9 Jan 2018 16:49:24 +0100 Subject: [PATCH 65/66] Added our torrent_search repo as a submodule in our app folder. This will need to be set up automatically with build setting when the project is to be downloaded. --- app/torrent_search | 1 + 1 file changed, 1 insertion(+) create mode 160000 app/torrent_search diff --git a/app/torrent_search b/app/torrent_search new file mode 160000 index 0000000..3deaed4 --- /dev/null +++ b/app/torrent_search @@ -0,0 +1 @@ +Subproject commit 3deaed48b71adf97172e7bf899af87972cc7f4e2 From e0da166406fe72e75fa69af6fe456d22953a1f2a Mon Sep 17 00:00:00 2001 From: KevinMidboe Date: Tue, 9 Jan 2018 16:53:41 +0100 Subject: [PATCH 66/66] Our request page is redesigned with a new header and search bar. It also has a better way of redrawing when the page size is changed to a mobile device. This is not very reactonic, but this file contains the intire search and request logic of the page, but this means we still have things todo :) --- client/app/components/SearchRequest.jsx | 43 ++++++--- .../components/styles/searchRequestStyle.jsx | 87 ++++++++++++++----- 2 files changed, 93 insertions(+), 37 deletions(-) diff --git a/client/app/components/SearchRequest.jsx b/client/app/components/SearchRequest.jsx index 39cdce2..bdb5098 100644 --- a/client/app/components/SearchRequest.jsx +++ b/client/app/components/SearchRequest.jsx @@ -47,7 +47,7 @@ class SearchRequest extends React.Component { // this.setState({responseMovieList: null}) this.resetPageNumber(); this.state.loadResults = true; - this.fetchTmdbList('upcoming'); + this.fetchTmdbList('discover'); } // Handles all errors of the response of a fetch call @@ -384,25 +384,28 @@ class SearchRequest extends React.Component {
    - Request new content + Request new content for plex
    -
    -
    - +
    + - this._handleQueryKeyPress(event)} - onChange={event => this.updateQueryState(event)} - value={this.state.searchQuery}/> + this._handleQueryKeyPress(event)} + onChange={event => this.updateQueryState(event)} + value={this.state.searchQuery}/> -
    - {this.state.resultHeader} -



    +
    +
    {this.state.resultHeader}
    + + +
    + +

    {this.state.responseMovieList}
    @@ -441,7 +444,21 @@ class SearchRequest extends React.Component { ) } - + //
    + // + //
    + + //
    + // + //
    } export default SearchRequest; diff --git a/client/app/components/styles/searchRequestStyle.jsx b/client/app/components/styles/searchRequestStyle.jsx index 17b5b11..0700a4e 100644 --- a/client/app/components/styles/searchRequestStyle.jsx +++ b/client/app/components/styles/searchRequestStyle.jsx @@ -10,19 +10,19 @@ export default { backgroundLargeHeader: { width: '100%', - minHeight: '400px', + minHeight: '180px', backgroundColor: 'rgb(1, 28, 35)', // backgroundImage: 'radial-gradient(circle, #004c67 0, #005771 120%)', zIndex: 1, - marginBottom: '-100px' + marginBottom: '80px' }, backgroundSmallHeader: { width: '100%', - minHeight: '300px', + minHeight: '120px', backgroundColor: '#011c23', zIndex: 1, - marginBottom: '-100px' + marginBottom: '40px' }, requestWrapper: { @@ -44,7 +44,7 @@ export default { pageTitleLargeSpan: { color: 'white', - fontSize: '4em', + fontSize: '3em', marginTop: '4vh', marginBottom: '6vh' }, @@ -54,39 +54,35 @@ export default { fontSize: '2em', marginTop: '3vh', marginBottom: '3vh' - }, - - box: { - height: '50px', }, searchLargeContainer: { - margin: '0 25%', + height: '52px', + width: '77%', + paddingLeft: '23%', + backgroundColor: 'white', }, searchSmallContainer: { - margin: '0 10%', }, searchIcon: { position: 'absolute', - fontSize: '1.2em', - marginTop: '12px', - marginLeft: '-13px', - color: '#4f5b66' + fontSize: '1.6em', + marginTop: '7px', + color: '#4f5b66', + display: 'block', }, searchLargeBar: { - width: '100%', + width: '50%', height: '50px', background: '#ffffff', border: 'none', - fontSize: '10pt', + fontSize: '12pt', float: 'left', color: '#63717f', - paddingLeft: '45px', - marginLeft: '-25px', - borderRadius: '5px', + paddingLeft: '40px', }, searchSmallBar: { @@ -94,14 +90,58 @@ export default { height: '50px', background: '#ffffff', border: 'none', - fontSize: '13pt', + fontSize: '11pt', float: 'left', color: '#63717f', - paddingLeft: '45px', + paddingLeft: '65px', marginLeft: '-25px', borderRadius: '5px', }, + + // Dropdown for selecting tmdb lists + controls: { + textAlign: 'left', + paddingTop: '8px', + width: '33.3333%', + marginLeft: '0', + marginRight: '0', + }, + + withData: { + boxSizing: 'border-box', + marginBottom: '0', + display: 'block', + padding: '0', + verticalAlign: 'baseline', + font: 'inherit', + textAlign: 'left', + boxSizing: 'border-box', + }, + + sortOptions: { + border: '1px solid #000', + maxWidth: '100%', + overflow: 'hidden', + lineHeight: 'normal', + textAlign: 'left', + padding: '4px 12px', + paddingRight: '2rem', + backgroundImage: 'url("data:image/svg+xml;base64,PHN2ZyBpZD0iTGF5ZXJfMSIgZGF0YS1uYW1lPSJMYXllciAxIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxOCAxOCI+CiAgPHRpdGxlPmFycm93LWRvd24tbWljcm88L3RpdGxlPgogIDxwb2x5bGluZSBwb2ludHM9IjE0IDQuNjcgOSAxMy4zMyA0IDQuNjciIHN0eWxlPSJmaWxsOiBub25lO3N0cm9rZTogIzAwMDtzdHJva2UtbWl0ZXJsaW1pdDogMTA7c3Ryb2tlLXdpZHRoOiAycHgiLz4KPC9zdmc+Cg==")', + backgroundSize: '18px 18px', + backgroundPosition: 'right 8px center', + backgroundRepeat: 'no-repeat', + width: 'auto', + display: 'inline-block', + outline: 'none', + boxSizing: 'border-box', + fontSize: '15px', + WebkitAppearance: 'none', + MozAppearance: 'none', + appearance: 'none', + }, + + searchFilterActive: { color: '#00d17c', fontSize: '1em', @@ -116,7 +156,6 @@ export default { cursor: 'pointer' }, - filter: { color: 'white', paddingLeft: '40px', @@ -124,9 +163,9 @@ export default { }, resultLargeHeader: { - paddingLeft: '30px', color: 'black', fontSize: '1.6em', + width: '20%', }, resultSmallHeader: {