Merge pull request #33 from KevinMidboe/update/frontend_logic
Update/frontend logic
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -11,3 +11,4 @@ client/dist
|
||||
src/webserver/access.log
|
||||
conf/development.json
|
||||
yarn-error.log
|
||||
*/yarn.lock
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
import React from 'react';
|
||||
|
||||
import Notifications, {notify} from 'react-notify-toast';
|
||||
|
||||
// StyleComponents
|
||||
import movieStyle from './styles/movieObjectStyle.jsx';
|
||||
|
||||
var MediaQuery = require('react-responsive');
|
||||
|
||||
class MovieObject {
|
||||
constructor(object) {
|
||||
this.id = object.id;
|
||||
@@ -10,7 +14,9 @@ class MovieObject {
|
||||
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;
|
||||
}
|
||||
@@ -20,10 +26,12 @@ class MovieObject {
|
||||
}
|
||||
|
||||
requestMovie() {
|
||||
// fetch('https://apollo.kevinmidboe.com/api/v1/plex/request/' + id, {
|
||||
fetch('http://localhost:31459/api/v1/plex/request/' + this.id + '?type='+this.type, {
|
||||
// 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'
|
||||
});
|
||||
|
||||
notify.show(this.title + ' requested!', 'success', 3000);
|
||||
}
|
||||
|
||||
getElement() {
|
||||
@@ -31,8 +39,10 @@ class MovieObject {
|
||||
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/w154' + this.poster;
|
||||
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 = <button onClick={() => {this.requestExisting(this)}}
|
||||
@@ -47,15 +57,31 @@ class MovieObject {
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Notifications />
|
||||
<div style={movieStyle.resultItem} key={this.id}>
|
||||
<MediaQuery minWidth={600}>
|
||||
<div style={movieStyle.resultPoster}>
|
||||
<img style={movieStyle.resultPosterImg} id='poster' src={posterPath}></img>
|
||||
</div>
|
||||
</MediaQuery>
|
||||
<div>
|
||||
<span style={movieStyle.resultTitle}>{this.title} ({this.year})</span>
|
||||
<br></br>
|
||||
<span>{this.summary}</span>
|
||||
<br></br>
|
||||
<MediaQuery minWidth={600}>
|
||||
<span style={movieStyle.resultTitleLarge}>{this.title}</span>
|
||||
<br></br>
|
||||
<span style={movieStyle.yearRatingLarge}>Released: { this.year } | Rating: {this.rating}</span>
|
||||
<br></br>
|
||||
<span style={movieStyle.summary}>{this.summary}</span>
|
||||
<br></br>
|
||||
</MediaQuery>
|
||||
|
||||
|
||||
<MediaQuery maxWidth={600}>
|
||||
<img src={ backgroundPath } style={movieStyle.background}></img>
|
||||
<span style={movieStyle.resultTitleSmall}>{this.title}</span>
|
||||
<br></br>
|
||||
<span style={movieStyle.yearRatingSmall}>Released: {this.year} | Rating: {this.rating}</span>
|
||||
</MediaQuery>
|
||||
|
||||
|
||||
<span className='imdbLogo'>
|
||||
</span>
|
||||
@@ -68,6 +94,9 @@ class MovieObject {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<MediaQuery maxWidth={600}>
|
||||
<br></br>
|
||||
</MediaQuery>
|
||||
<div style={movieStyle.row}>
|
||||
<div style={movieStyle.itemDivider}></div>
|
||||
</div>
|
||||
|
||||
@@ -4,8 +4,12 @@ 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';
|
||||
|
||||
var MediaQuery = require('react-responsive');
|
||||
|
||||
// TODO add option for searching multi, movies or tv shows
|
||||
class SearchRequest extends React.Component {
|
||||
@@ -13,24 +17,28 @@ class SearchRequest extends React.Component {
|
||||
super(props)
|
||||
// Constructor with states holding the search query and the element of reponse.
|
||||
this.state = {
|
||||
lastApiCallURI: '',
|
||||
searchQuery: '',
|
||||
responseMovieList: null,
|
||||
movieFilter: true,
|
||||
movieFilter: false,
|
||||
showFilter: false,
|
||||
discoverType: '',
|
||||
page: 1
|
||||
page: 1,
|
||||
resultHeader: '',
|
||||
loadResults: false,
|
||||
scrollHasMore: true
|
||||
}
|
||||
|
||||
this.allowedDiscoverTypes = [
|
||||
this.allowedListTypes = [
|
||||
'discover', 'popular', 'nowplaying', 'upcoming'
|
||||
]
|
||||
|
||||
this.baseUrl = 'https://apollo.kevinmidboe.com/api/v1/tmdb';
|
||||
this.baseUrl = 'https://apollo.kevinmidboe.com/api/v1/tmdb/';
|
||||
// this.baseUrl = 'http://localhost:31459/api/v1/tmdb/';
|
||||
|
||||
this.URLs = {
|
||||
request: 'https://apollo.kevinmidboe.com/api/v1/plex/request?page='+this.state.page+'&query=',
|
||||
// request: 'http://localhost:31459/api/v1/plex/request?page='+this.state.page+'&query=',
|
||||
searchRequest: 'https://apollo.kevinmidboe.com/api/v1/plex/request',
|
||||
// searchRequest: 'http://localhost:31459/api/v1/plex/request',
|
||||
upcoming: 'https://apollo.kevinmidboe.com/api/v1/tmdb/upcoming',
|
||||
// upcoming: 'http://localhost:31459/api/v1/tmdb/upcoming',
|
||||
sendRequest: 'https://apollo.kevinmidboe.com/api/v1/plex/request?query='
|
||||
@@ -42,88 +50,224 @@ class SearchRequest extends React.Component {
|
||||
componentDidMount(){
|
||||
var that = this;
|
||||
// this.setState({responseMovieList: null})
|
||||
this.fetchDiscover('upcoming');
|
||||
this.resetPageNumber();
|
||||
this.state.loadResults = true;
|
||||
this.fetchTmdbList('upcoming');
|
||||
}
|
||||
|
||||
// Handles all errors of the response of a fetch call
|
||||
handleErrors(response) {
|
||||
if (!response.ok) {
|
||||
throw Error(response.status);
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
||||
|
||||
fetchDiscover(queryDiscoverType) {
|
||||
if (this.allowedDiscoverTypes.indexOf(queryDiscoverType) === -1)
|
||||
throw Error('Invalid discover type: ' + queryDiscoverType);
|
||||
|
||||
var uri = new URI(this.baseUrl);
|
||||
uri.segment(queryDiscoverType)
|
||||
uri = uri.setSearch('page', this.state.page);
|
||||
if (this.state.showFilter)
|
||||
uri = uri.addSearch('type', 'show');
|
||||
|
||||
console.log(uri)
|
||||
|
||||
this.setState({
|
||||
responseMovieList: 'Loading...'
|
||||
});
|
||||
|
||||
fetch(uri)
|
||||
// Check if the response is ok
|
||||
.then(response => this.handleErrors(response))
|
||||
.then(response => response.json()) // Convert to json object and pass to next then
|
||||
.then(data => { // Parse the data of the JSON response
|
||||
// If it is something here it updates the state variable with the HTML list of all
|
||||
// movie objects that where returned by the search request
|
||||
if (data.results.length > 0) {
|
||||
this.setState({
|
||||
responseMovieList: data.results.map(item => this.createMovieObjects(item))
|
||||
})
|
||||
}
|
||||
})
|
||||
// If the --------
|
||||
.catch(error => {
|
||||
console.log(error)
|
||||
this.setState({
|
||||
responseMovieList: <h1>Not Found</h1>
|
||||
})
|
||||
|
||||
console.log('Error submit: ', error.toString());
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
fetchQuery() {
|
||||
let url = this.URLs.request + this.state.searchQuery
|
||||
if (this.state.showFilter) {
|
||||
url = url + '&type=tv'
|
||||
// Handles all errors of the response of a fetch call
|
||||
handleErrors(response) {
|
||||
if (!response.ok)
|
||||
throw Error(response.status);
|
||||
return response;
|
||||
}
|
||||
|
||||
fetch(url)
|
||||
// Check if the response is ok
|
||||
.then(response => this.handleErrors(response))
|
||||
.then(response => response.json()) // Convert to json object and pass to next then
|
||||
.then(data => { // Parse the data of the JSON response
|
||||
// If it is something here it updates the state variable with the HTML list of all
|
||||
// movie objects that where returned by the search request
|
||||
if (data.results.length > 0) {
|
||||
this.setState({
|
||||
responseMovieList: data.results.map(item => this.createMovieObjects(item))
|
||||
})
|
||||
}
|
||||
})
|
||||
// If the --------
|
||||
.catch(error => {
|
||||
console.log(error)
|
||||
this.setState({
|
||||
responseMovieList: <h1>Not Found</h1>
|
||||
})
|
||||
handleQueryError(response) {
|
||||
if (!response.ok) {
|
||||
if (response.status === 404) {
|
||||
this.setState({
|
||||
responseMovieList: <h1>Nothing found for search query: { this.findQueryInURI(uri) }</h1>
|
||||
})
|
||||
}
|
||||
console.log('handleQueryError: ', error);
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
||||
// Unpacks the query value of a uri
|
||||
findQueryValueInURI(uri) {
|
||||
let uriSearchValues = uri.query(true);
|
||||
let queryValue = uriSearchValues['query']
|
||||
|
||||
return queryValue;
|
||||
}
|
||||
|
||||
// Unpacks the page value of a uri
|
||||
findPageValueInURI(uri) {
|
||||
let uriSearchValues = uri.query(true);
|
||||
let queryValue = uriSearchValues['page']
|
||||
|
||||
return queryValue;
|
||||
}
|
||||
|
||||
resetPageNumber() {
|
||||
this.state.page = 1;
|
||||
}
|
||||
|
||||
writeLoading() {
|
||||
this.setState({
|
||||
responseMovieList: 'Loading...'
|
||||
});
|
||||
}
|
||||
|
||||
// Test this by calling missing endpoint or 404 query and see what code
|
||||
// and filter the error message based on the code.
|
||||
// Calls a uri and returns the response as json
|
||||
callURI(uri) {
|
||||
return fetch(uri)
|
||||
.then(response => { return response })
|
||||
.catch(error => {
|
||||
throw Error('Something went wrong while fetching URI.');
|
||||
});
|
||||
}
|
||||
|
||||
// Saves the input string as a h1 element in responseMovieList state
|
||||
fillResponseMovieListWithError(msg) {
|
||||
this.setState({
|
||||
responseMovieList: <h1>{ msg }</h1>
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
// 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();
|
||||
|
||||
Promise.resolve()
|
||||
.then(() => this.callURI(uri))
|
||||
.then(response => {
|
||||
// If we get a error code for the request
|
||||
if (!response.ok) {
|
||||
if (response.status === 404) {
|
||||
if (this.findPageValueInURI(new URI(response.url)) > 1) {
|
||||
this.state.scrollHasMore = false;
|
||||
console.log(this.state.scrollHasMore)
|
||||
return null
|
||||
let returnMessage = 'this is the return mesasge than will never be delivered'
|
||||
let theSecondReturnMsg = 'this is the second return messag ethat will NEVE be delivered'
|
||||
}
|
||||
else {
|
||||
|
||||
let errorMsg = 'Nothing found for the search query: ' + this.findQueryValueInURI(uri);
|
||||
this.fillResponseMovieListWithError(errorMsg)
|
||||
}
|
||||
}
|
||||
else {
|
||||
let errorMsg = 'Error fetching query from server ' + this.response.status;
|
||||
this.fillResponseMovieListWithError(errorMsg)
|
||||
}
|
||||
}
|
||||
|
||||
// Convert to json and update the state of responseMovieList with the results of the api call
|
||||
// mapped as a movieObject.
|
||||
response.json()
|
||||
.then(responseData => {
|
||||
if (this.state.page === 1) {
|
||||
this.setState({
|
||||
responseMovieList: responseData.results.map(searchResultItem => this.createMovieObjects(searchResultItem)),
|
||||
lastApiCallURI: uri // Save the value of the last sucessfull api call
|
||||
})
|
||||
} else {
|
||||
let responseMovieObjects = responseData.results.map(searchResultItem => this.createMovieObjects(searchResultItem));
|
||||
let growingReponseMovieObjectList = this.state.responseMovieList.concat(responseMovieObjects);
|
||||
this.setState({
|
||||
responseMovieList: growingReponseMovieObjectList,
|
||||
lastApiCallURI: uri // Save the value of the last sucessfull api call
|
||||
})
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
console.log('CallSearchFillMovieList: ', error)
|
||||
})
|
||||
})
|
||||
.catch(() => {
|
||||
throw Error('Something went wrong when fetching query.')
|
||||
})
|
||||
}
|
||||
|
||||
callListFillMovieList(uri) {
|
||||
// Write loading animation
|
||||
// this.writeLoading();
|
||||
|
||||
Promise.resolve()
|
||||
.then(() => this.callURI(uri))
|
||||
.then(response => {
|
||||
// If we get a error code for the request
|
||||
if (!response.ok) {
|
||||
if (response.status === 404) {
|
||||
let errorMsg = 'List not found';
|
||||
this.fillResponseMovieListWithError(errorMsg)
|
||||
}
|
||||
else {
|
||||
let errorMsg = 'Error fetching list from server ' + this.response.status;
|
||||
this.fillResponseMovieListWithError(errorMsg)
|
||||
}
|
||||
}
|
||||
|
||||
// Convert to json and update the state of responseMovieList with the results of the api call
|
||||
// mapped as a movieObject.
|
||||
response.json()
|
||||
.then(responseData => {
|
||||
if (this.state.page === 1) {
|
||||
this.setState({
|
||||
responseMovieList: responseData.results.map(searchResultItem => this.createMovieObjects(searchResultItem)),
|
||||
lastApiCallURI: uri // Save the value of the last sucessfull api call
|
||||
})
|
||||
} else {
|
||||
let responseMovieObjects = responseData.results.map(searchResultItem => this.createMovieObjects(searchResultItem));
|
||||
let growingReponseMovieObjectList = this.state.responseMovieList.concat(responseMovieObjects);
|
||||
this.setState({
|
||||
responseMovieList: growingReponseMovieObjectList,
|
||||
lastApiCallURI: uri // Save the value of the last sucessfull api call
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
.catch(() => {
|
||||
throw Error('Something went wrong when fetching query.')
|
||||
})
|
||||
}
|
||||
|
||||
searchSeasonedRequest() {
|
||||
this.state.resultHeader = 'Search result for: ' + this.state.searchQuery;
|
||||
|
||||
// Build uri with the url for searching requests
|
||||
var uri = new URI(this.URLs.searchRequest);
|
||||
// Add input of search query and page count to the uri payload
|
||||
uri = uri.search({ 'query': this.state.searchQuery, 'page': this.state.page });
|
||||
|
||||
if (this.state.showFilter)
|
||||
uri = uri.addSearch('type', 'show');
|
||||
else
|
||||
if (this.state.movieFilter)
|
||||
uri = uri.addSearch('type', 'movie')
|
||||
|
||||
// Send uri to call and fill the response list with movie/show objects
|
||||
this.callSearchFillMovieList(uri);
|
||||
}
|
||||
|
||||
fetchTmdbList(tmdbListType) {
|
||||
console.log(tmdbListType)
|
||||
// Check if it is a whitelisted list, this should be replaced with checking if the return call is 500
|
||||
if (this.allowedListTypes.indexOf(tmdbListType) === -1)
|
||||
throw Error('Invalid discover type: ' + tmdbListType);
|
||||
|
||||
this.state.responseMovieList = []
|
||||
// Captialize the first letter of and save the discoverQueryType to resultHeader state.
|
||||
this.state.resultHeader = tmdbListType.toLowerCase().replace(/\b[a-z]/g, function(letter) {
|
||||
return letter.toUpperCase();
|
||||
});
|
||||
|
||||
// Build uri with the url for searching requests
|
||||
var uri = new URI(this.baseUrl);
|
||||
uri.segment(tmdbListType);
|
||||
// Add input of search query and page count to the uri payload
|
||||
uri = uri.search({ 'page': this.state.page });
|
||||
|
||||
if (this.state.showFilter)
|
||||
uri = uri.addSearch('type', 'show');
|
||||
|
||||
// Send uri to call and fill the response list with movie/show objects
|
||||
this.callListFillMovieList(uri);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
console.log('Error submit: ', error.toString());
|
||||
});
|
||||
}
|
||||
|
||||
// Updates the internal state of the query search field.
|
||||
updateQueryState(event){
|
||||
@@ -135,7 +279,10 @@ class SearchRequest extends React.Component {
|
||||
// For checking if the enter key was pressed in the search field.
|
||||
_handleQueryKeyPress(e) {
|
||||
if (e.key === 'Enter') {
|
||||
this.fetchQuery();
|
||||
// this.fetchQuery();
|
||||
// Reset page number for a new search
|
||||
this.resetPageNumber();
|
||||
this.searchSeasonedRequest();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -161,65 +308,137 @@ class SearchRequest extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
pageBackwards() {
|
||||
if (this.state.page > 1) {
|
||||
console.log('backwards');
|
||||
this.state.page--;
|
||||
this.getUpcoming();
|
||||
}
|
||||
console.log(this.state.page)
|
||||
}
|
||||
pageBackwards() {
|
||||
if (this.state.page > 1) {
|
||||
let pageNumber = this.state.page - 1;
|
||||
let uri = this.state.lastApiCallURI;
|
||||
|
||||
// Augment the page number of the uri with a callback
|
||||
uri.search(function(data) {
|
||||
data.page = pageNumber;
|
||||
});
|
||||
|
||||
pageForwards() {
|
||||
this.state.page++;
|
||||
this.getUpcoming();
|
||||
console.log('forwards');
|
||||
console.log(this.state.page)
|
||||
}
|
||||
// Call the api with the new uri
|
||||
this.callSearchFillMovieList(uri);
|
||||
// Update state of our page number after the call is done
|
||||
this.state.page = pageNumber;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO need to get total page number and save in a state to not overflow
|
||||
pageForwards() {
|
||||
// Wrap this in the check
|
||||
let pageNumber = this.state.page + 1;
|
||||
let uri = this.state.lastApiCallURI;
|
||||
|
||||
// Augment the page number of the uri with a callback
|
||||
uri.search(function(data) {
|
||||
data.page = pageNumber;
|
||||
});
|
||||
|
||||
// Call the api with the new uri
|
||||
this.callSearchFillMovieList(uri);
|
||||
// Update state of our page number after the call is done
|
||||
this.state.page = pageNumber;
|
||||
}
|
||||
|
||||
movieToggle() {
|
||||
if (this.state.movieFilter)
|
||||
return <span style={searchStyle.searchFilterActive}
|
||||
className="search_category hvrUnderlineFromCenter"
|
||||
onClick={() => {this.toggleFilter('movies')}}
|
||||
id="category_active">Movies</span>
|
||||
else
|
||||
return <span style={searchStyle.searchFilterNotActive}
|
||||
className="search_category hvrUnderlineFromCenter"
|
||||
onClick={() => {this.toggleFilter('movies')}}
|
||||
id="category_active">Movies</span>
|
||||
}
|
||||
|
||||
showToggle() {
|
||||
if (this.state.showFilter)
|
||||
return <span style={searchStyle.searchFilterActive}
|
||||
className="search_category hvrUnderlineFromCenter"
|
||||
onClick={() => {this.toggleFilter('shows')}}
|
||||
id="category_active">TV Shows</span>
|
||||
else
|
||||
return <span style={searchStyle.searchFilterNotActive}
|
||||
className="search_category hvrUnderlineFromCenter"
|
||||
onClick={() => {this.toggleFilter('shows')}}
|
||||
id="category_active">TV Shows</span>
|
||||
}
|
||||
|
||||
|
||||
render(){
|
||||
const loader = <div className="loader">Loading ...<br></br></div>;
|
||||
|
||||
|
||||
return(
|
||||
<div style={searchStyle.body}>
|
||||
<button onClick={() => {this.fetchDiscover('discover')}}>Discover</button>
|
||||
<button onClick={() => {this.fetchDiscover('popular')}}>Popular</button>
|
||||
<button onClick={() => {this.fetchDiscover('nowplaying')}}>Nowplaying</button>
|
||||
<button onClick={() => {this.fetchDiscover('upcoming')}}>Upcoming</button>
|
||||
<InfiniteScroll
|
||||
pageStart={0}
|
||||
loadMore={this.pageForwards.bind(this)}
|
||||
hasMore={this.state.scrollHasMore}
|
||||
loader={loader}
|
||||
initialLoad={this.state.loadResults}>
|
||||
|
||||
<div className='backgroundHeader' style={searchStyle.backgroundHeader}>
|
||||
<div className='pageTitle' style={searchStyle.pageTitle}>
|
||||
<span style={searchStyle.pageTitleSpan}>Request new movies or tv shows</span>
|
||||
</div>
|
||||
<MediaQuery minWidth={600}>
|
||||
<div style={searchStyle.body}>
|
||||
<div className='backgroundHeader' style={searchStyle.backgroundLargeHeader}>
|
||||
<div className='pageTitle' style={searchStyle.pageTitle}>
|
||||
<span style={searchStyle.pageTitleLargeSpan}>Request new content</span>
|
||||
</div>
|
||||
|
||||
<div className='box' style={searchStyle.box}>
|
||||
<div style={searchStyle.searchLargeContainer}>
|
||||
<span style={searchStyle.searchIcon}><i className="fa fa-search"></i></span>
|
||||
|
||||
<div className='box' style={searchStyle.box}>
|
||||
<div style={searchStyle.container}>
|
||||
<span style={searchStyle.searchIcon}><i className="fa fa-search"></i></span>
|
||||
<input style={searchStyle.searchLargeBar} type="text" id="search" placeholder="Search for new content..."
|
||||
onKeyPress={(event) => this._handleQueryKeyPress(event)}
|
||||
onChange={event => this.updateQueryState(event)}
|
||||
value={this.state.searchQuery}/>
|
||||
|
||||
<input style={searchStyle.searchBar} type="text" id="search" placeholder="Search for new content..."
|
||||
onKeyPress={(event) => this._handleQueryKeyPress(event)}
|
||||
onChange={event => this.updateQueryState(event)}
|
||||
value={this.state.searchQuery}/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<span style={searchStyle.searchFilter}
|
||||
className="search_category hvrUnderlineFromCenter"
|
||||
onClick={() => {this.toggleFilter('movies')}}
|
||||
id="category_active">Movies</span>
|
||||
<span style={searchStyle.searchFilter}
|
||||
className="search_category hvrUnderlineFromCenter"
|
||||
onClick={() => {this.toggleFilter('shows')}}
|
||||
id="category_inactive">TV Shows</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id='requestMovieList' ref='requestMovieList' style={searchStyle.requestWrapper}>
|
||||
<span style={searchStyle.resultLargeHeader}>{this.state.resultHeader}</span>
|
||||
<br></br><br></br>
|
||||
|
||||
{this.state.responseMovieList}
|
||||
</div>
|
||||
</div>
|
||||
</MediaQuery>
|
||||
|
||||
<div id='requestMovieList' ref='requestMovieList' style={searchStyle.requestWrapper}>
|
||||
{this.state.responseMovieList}
|
||||
</div>
|
||||
<div>
|
||||
<button onClick={() => {this.pageBackwards()}}>Back</button>
|
||||
<button onClick={() => {this.pageForwards()}}>Forward</button>
|
||||
</div>
|
||||
</div>
|
||||
<MediaQuery maxWidth={600}>
|
||||
<div style={searchStyle.body}>
|
||||
<div className='backgroundHeader' style={searchStyle.backgroundSmallHeader}>
|
||||
<div className='pageTitle' style={searchStyle.pageTitle}>
|
||||
<span style={searchStyle.pageTitleSmallSpan}>Request new content</span>
|
||||
</div>
|
||||
|
||||
<div className='box' style={searchStyle.box}>
|
||||
<div style={searchStyle.searchSmallContainer}>
|
||||
<span style={searchStyle.searchIcon}><i className="fa fa-search"></i></span>
|
||||
|
||||
<input style={searchStyle.searchSmallBar} type="text" id="search" placeholder="Search for new content..."
|
||||
onKeyPress={(event) => this._handleQueryKeyPress(event)}
|
||||
onChange={event => this.updateQueryState(event)}
|
||||
value={this.state.searchQuery}/>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id='requestMovieList' ref='requestMovieList' style={searchStyle.requestWrapper}>
|
||||
<span style={searchStyle.resultSmallHeader}>{this.state.resultHeader}</span>
|
||||
<br></br><br></br>
|
||||
|
||||
{this.state.responseMovieList}
|
||||
</div>
|
||||
</div>
|
||||
</MediaQuery>
|
||||
</InfiniteScroll>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -6,21 +6,24 @@ export default {
|
||||
minHeight: '230px'
|
||||
},
|
||||
|
||||
resultItem: {
|
||||
maxWidth: '95%',
|
||||
margin: '0 auto',
|
||||
minHeight: '230px'
|
||||
},
|
||||
|
||||
movie_content: {
|
||||
marginLeft: '15px'
|
||||
},
|
||||
|
||||
resultTitle: {
|
||||
resultTitleLarge: {
|
||||
color: 'black',
|
||||
fontSize: '2em',
|
||||
},
|
||||
|
||||
resultTitleSmall: {
|
||||
color: 'black',
|
||||
fontSize: '22px',
|
||||
},
|
||||
|
||||
yearRatingLarge: {
|
||||
fontSize: '0.8em'
|
||||
},
|
||||
|
||||
resultPoster: {
|
||||
float: 'left',
|
||||
zIndex: '3',
|
||||
@@ -28,12 +31,30 @@ export default {
|
||||
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'
|
||||
},
|
||||
|
||||
@@ -6,23 +6,29 @@ export default {
|
||||
margin: 0,
|
||||
padding: 0,
|
||||
minHeight: '100%',
|
||||
position: 'relative'
|
||||
},
|
||||
|
||||
backgroundHeader: {
|
||||
backgroundLargeHeader: {
|
||||
width: '100%',
|
||||
minHeight: '400px',
|
||||
backgroundColor: '#011c23',
|
||||
zIndex: 1,
|
||||
position: 'absolute'
|
||||
marginBottom: '-100px'
|
||||
},
|
||||
|
||||
backgroundSmallHeader: {
|
||||
width: '100%',
|
||||
minHeight: '300px',
|
||||
backgroundColor: '#011c23',
|
||||
zIndex: 1,
|
||||
marginBottom: '-100px'
|
||||
},
|
||||
|
||||
requestWrapper: {
|
||||
top: '300px',
|
||||
width: '90%',
|
||||
maxWidth: '1200px',
|
||||
margin: 'auto',
|
||||
paddingTop: '20px',
|
||||
// paddingTop: '20px',
|
||||
backgroundColor: 'white',
|
||||
position: 'relative',
|
||||
zIndex: '10',
|
||||
@@ -32,42 +38,46 @@ export default {
|
||||
pageTitle: {
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center'
|
||||
justifyContent: 'center',
|
||||
textAlign: 'center'
|
||||
},
|
||||
|
||||
pageTitleSpan: {
|
||||
pageTitleLargeSpan: {
|
||||
color: 'white',
|
||||
fontSize: '3em',
|
||||
marginTop: '4vh',
|
||||
marginBottom: '6vh'
|
||||
},
|
||||
|
||||
pageTitleSmallSpan: {
|
||||
color: 'white',
|
||||
fontSize: '2em',
|
||||
marginTop: '3vh',
|
||||
marginBottom: '3vh'
|
||||
},
|
||||
|
||||
box: {
|
||||
width: '90%',
|
||||
height: '50px',
|
||||
maxWidth: '1200px',
|
||||
margin: '0 auto'
|
||||
},
|
||||
|
||||
container: {
|
||||
verticalAlign: 'middle',
|
||||
whiteSpace: 'nowrap',
|
||||
position: 'relative',
|
||||
display: 'flex',
|
||||
justifyContent: 'center'
|
||||
searchLargeContainer: {
|
||||
margin: '0 25%',
|
||||
},
|
||||
|
||||
searchSmallContainer: {
|
||||
margin: '0 10%',
|
||||
},
|
||||
|
||||
searchIcon: {
|
||||
position: 'absolute',
|
||||
marginLeft: '17px',
|
||||
marginTop: '17px',
|
||||
zIndex: '1',
|
||||
fontSize: '1.2em',
|
||||
marginTop: '12px',
|
||||
marginLeft: '-13px',
|
||||
color: '#4f5b66'
|
||||
},
|
||||
|
||||
searchBar: {
|
||||
width: '60%',
|
||||
minWidth: '120px',
|
||||
searchLargeBar: {
|
||||
width: '100%',
|
||||
height: '50px',
|
||||
background: '#ffffff',
|
||||
border: 'none',
|
||||
@@ -75,19 +85,78 @@ export default {
|
||||
float: 'left',
|
||||
color: '#63717f',
|
||||
paddingLeft: '45px',
|
||||
marginLeft: '-25px',
|
||||
borderRadius: '5px',
|
||||
marginRight: '15px'
|
||||
},
|
||||
|
||||
searchFilter: {
|
||||
color: 'white',
|
||||
searchSmallBar: {
|
||||
width: '100%',
|
||||
height: '50px',
|
||||
background: '#ffffff',
|
||||
border: 'none',
|
||||
fontSize: '13pt',
|
||||
float: 'left',
|
||||
color: '#63717f',
|
||||
paddingLeft: '45px',
|
||||
marginLeft: '-25px',
|
||||
borderRadius: '5px',
|
||||
},
|
||||
|
||||
searchFilterActive: {
|
||||
color: '#00d17c',
|
||||
fontSize: '1em',
|
||||
paddingTop: '12px',
|
||||
marginBottom: '12px',
|
||||
marginLeft: '10px',
|
||||
cursor: 'pointer'
|
||||
},
|
||||
|
||||
searchFilterNotActive: {
|
||||
color: 'white',
|
||||
fontSize: '1em',
|
||||
marginLeft: '10px',
|
||||
cursor: 'pointer'
|
||||
},
|
||||
|
||||
|
||||
filter: {
|
||||
color: 'white',
|
||||
paddingLeft: '40px',
|
||||
width: '60%',
|
||||
},
|
||||
|
||||
resultLargeHeader: {
|
||||
paddingLeft: '30px',
|
||||
color: 'black',
|
||||
fontSize: '2em',
|
||||
},
|
||||
|
||||
resultSmallHeader: {
|
||||
paddingLeft: '30px',
|
||||
color: 'black',
|
||||
fontSize: '1.7em',
|
||||
},
|
||||
|
||||
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',
|
||||
|
||||
@@ -3,7 +3,8 @@
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<link href="https://fonts.googleapis.com/css?family=Open+Sans:300" rel="stylesheet">
|
||||
<link href="https://maxcdn.bootstrapcdn.com/font-awesome/4.1.0/css/font-awesome.min.css" rel="stylesheet">
|
||||
<link href="https://maxcdn.bootstrapcdn.com/font-awesome/4.1.0/css/font-awesome.min.css" rel="stylesheet">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>seasoned Shows</title>
|
||||
</head>
|
||||
<body style='margin: 0'>
|
||||
|
||||
@@ -13,7 +13,12 @@
|
||||
"html-webpack-plugin": "^2.28.0",
|
||||
"path": "^0.12.7",
|
||||
"react": "^15.6.1",
|
||||
"react-burger-menu": "^2.1.6",
|
||||
"react-dom": "^15.5.4",
|
||||
"react-infinite-scroller": "^1.0.15",
|
||||
"react-notify-toast": "^0.3.2",
|
||||
"react-responsive": "^1.3.4",
|
||||
"urijs": "^1.18.12",
|
||||
"webfontloader": "^1.6.28",
|
||||
"webpack": "^3.5.5",
|
||||
"webpack-dev-server": "^2.4.5"
|
||||
|
||||
@@ -2,7 +2,7 @@ class mailTemplate {
|
||||
|
||||
constructor(mediaItem) {
|
||||
this.mediaItem = mediaItem;
|
||||
this.posterURL = 'https://image.tmdb.org/t/p/w600/';
|
||||
this.posterURL = 'https://image.tmdb.org/t/p/w600';
|
||||
}
|
||||
|
||||
toText() {
|
||||
|
||||
@@ -7,6 +7,8 @@ const tmdb = new TMDB(configuration.get('tmdb', 'apiKey'));
|
||||
var Promise = require('bluebird');
|
||||
var rp = require('request-promise');
|
||||
|
||||
const establishedDatabase = require('src/database/database');
|
||||
|
||||
const MailTemplate = require('src/plex/mailTemplate')
|
||||
|
||||
var pythonShell = require('python-shell');
|
||||
@@ -15,6 +17,13 @@ const nodemailer = require('nodemailer');
|
||||
|
||||
class RequestRepository {
|
||||
|
||||
constructor(database) {
|
||||
this.database = database || establishedDatabase;
|
||||
this.queries = {
|
||||
'insertRequest': "INSERT INTO requests VALUES (?, ?, ?, ?, ?, ?, CURRENT_DATE)"
|
||||
}
|
||||
}
|
||||
|
||||
searchRequest(query, page, type) {
|
||||
// TODO get from cache
|
||||
// STRIP METADATA THAT IS NOT ALLOWED
|
||||
@@ -99,23 +108,31 @@ class RequestRepository {
|
||||
* @param {identifier, type} the id of the media object and type of media must be defined
|
||||
* @returns {Promise} If nothing has gone wrong.
|
||||
*/
|
||||
sendRequest(identifier, type) {
|
||||
sendRequest(identifier, type, ip) {
|
||||
// TODO add to DB so can have a admin page
|
||||
// TODO try a cache hit on the movie item
|
||||
|
||||
tmdb.lookup(identifier, type).then(movie => {
|
||||
|
||||
// Add request to database
|
||||
this.database.run(this.queries.insertRequest, [movie.id, movie.title, movie.year, movie.poster, 'NULL', ip])
|
||||
|
||||
|
||||
//
|
||||
|
||||
|
||||
// create reusable transporter object using the default SMTP transport
|
||||
let transporter = nodemailer.createTransport({
|
||||
host: configuration.get('mail', 'host'),
|
||||
port: 26,
|
||||
ignoreTLS: true,
|
||||
tls :{rejectUnauthorized: false},
|
||||
secure: false, // secure:true for port 465, secure:false for port 587
|
||||
service: 'gmail',
|
||||
auth: {
|
||||
user: configuration.get('mail', 'user'),
|
||||
pass: configuration.get('mail', 'password')
|
||||
user: configuration.get('mail', 'user_pi'),
|
||||
pass: configuration.get('mail', 'password_pi')
|
||||
}
|
||||
// host: configuration.get('mail', 'host'),
|
||||
// port: 26,
|
||||
// ignoreTLS: true,
|
||||
// tls :{rejectUnauthorized: false},
|
||||
// secure: false, // secure:true for port 465, secure:false for port 587
|
||||
});
|
||||
|
||||
const mailTemplate = new MailTemplate(movie)
|
||||
@@ -123,7 +140,7 @@ class RequestRepository {
|
||||
// setup email data with unicode symbols
|
||||
let mailOptions = {
|
||||
// TODO get the mail adr from global location (easy to add)
|
||||
from: 'MovieRequester <support@kevinmidboe.com>', // sender address
|
||||
from: 'MovieRequester <pi.midboe@gmail.com>', // sender address
|
||||
to: 'kevin.midboe@gmail.com', // list of receivers
|
||||
subject: 'Download request', // Subject line
|
||||
text: mailTemplate.toText(),
|
||||
|
||||
@@ -2,10 +2,13 @@ const Movie = require('src/media_classes/movie');
|
||||
const Show = require('src/media_classes/show');
|
||||
|
||||
function convertTmdbToSeasoned(tmdbObject, strictType=undefined) {
|
||||
if (strictType === undefined)
|
||||
// TODO create a default fallback class to set the when falls to else as both are undefined
|
||||
if (tmdbObject.media_type !== undefined)
|
||||
var mediaType = tmdbObject.media_type;
|
||||
else if (strictType !== undefined)
|
||||
var mediaType = strictType;
|
||||
else
|
||||
var mediaType = strictType;
|
||||
var mediaType = 'movie';
|
||||
|
||||
// There are many diff types of content, we only want to look at movies and tv shows
|
||||
if (mediaType === 'movie') {
|
||||
|
||||
@@ -22,15 +22,16 @@ class TMDB {
|
||||
return Promise.resolve()
|
||||
.then(() => this.tmdb(type, query)) // Search the tmdb api
|
||||
.catch(() => { throw new Error('Could not search for movies.'); }) // If any error at all when fetching
|
||||
.then((reponse) => {
|
||||
.then((response) => {
|
||||
try {
|
||||
// We want to filter because there are movies really low rated that are not interesting to us.
|
||||
let filteredTmdbItems = reponse.results.filter(function(tmdbResultItem) {
|
||||
let filteredTmdbItems = response.results.filter(function(tmdbResultItem) {
|
||||
return ((tmdbResultItem.vote_count >= 80 || tmdbResultItem.popularity > 18) && (tmdbResultItem.release_date !== undefined || tmdbResultItem.first_air_date !== undefined))
|
||||
})
|
||||
|
||||
// Here we convert the filtered result from the tmdb api to seaonsed objects
|
||||
let seasonedItems = filteredTmdbItems.map((tmdbItem) => {
|
||||
|
||||
if (type === 'movie')
|
||||
return convertTmdbToSeasoned(tmdbItem, 'movie');
|
||||
else if (type === 'show')
|
||||
@@ -40,7 +41,7 @@ class TMDB {
|
||||
});
|
||||
|
||||
// TODO add page number if results are larger than 20
|
||||
return { 'results': seasonedItems, 'number_of_items_on_page': seasonedItems,
|
||||
return { 'results': seasonedItems, 'number_of_items_on_page': seasonedItems.length,
|
||||
'page': 1, 'total_pages': 1 };
|
||||
|
||||
} catch (parseError) {
|
||||
|
||||
@@ -12,8 +12,9 @@ function submitRequestController(req, res) {
|
||||
// This is the id that is the param of the url
|
||||
const id = req.params.mediaId;
|
||||
const type = req.query.type;
|
||||
var ip = req.headers['x-forwarded-for'] || req.connection.remoteAddress;
|
||||
|
||||
requestRepository.sendRequest(id, type)
|
||||
requestRepository.sendRequest(id, type, ip)
|
||||
.then(() => {
|
||||
res.send({ success: true, message: 'Media item sucessfully requested!' });
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user