Merge pull request #33 from KevinMidboe/update/frontend_logic

Update/frontend logic
This commit is contained in:
2017-09-27 00:47:25 +02:00
committed by GitHub
12 changed files with 558 additions and 191 deletions

1
.gitignore vendored
View File

@@ -11,3 +11,4 @@ client/dist
src/webserver/access.log src/webserver/access.log
conf/development.json conf/development.json
yarn-error.log yarn-error.log
*/yarn.lock

View File

@@ -1,8 +1,12 @@
import React from 'react'; import React from 'react';
import Notifications, {notify} from 'react-notify-toast';
// StyleComponents // StyleComponents
import movieStyle from './styles/movieObjectStyle.jsx'; import movieStyle from './styles/movieObjectStyle.jsx';
var MediaQuery = require('react-responsive');
class MovieObject { class MovieObject {
constructor(object) { constructor(object) {
this.id = object.id; this.id = object.id;
@@ -10,7 +14,9 @@ class MovieObject {
this.year = object.year; this.year = object.year;
this.type = object.type; this.type = object.type;
// Check if object.poster != undefined // Check if object.poster != undefined
this.rating = object.rating;
this.poster = object.poster; this.poster = object.poster;
this.background = object.background;
this.matchedInPlex = object.matchedInPlex; this.matchedInPlex = object.matchedInPlex;
this.summary = object.summary; this.summary = object.summary;
} }
@@ -20,10 +26,12 @@ class MovieObject {
} }
requestMovie() { 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' method: 'POST'
}); });
notify.show(this.title + ' requested!', 'success', 3000);
} }
getElement() { getElement() {
@@ -31,8 +39,10 @@ class MovieObject {
if (this.poster == null || this.poster == undefined) { if (this.poster == null || this.poster == undefined) {
var posterPath = 'https://openclipart.org/image/2400px/svg_to_png/211479/Simple-Image-Not-Found-Icon.png' var posterPath = 'https://openclipart.org/image/2400px/svg_to_png/211479/Simple-Image-Not-Found-Icon.png'
} else { } 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; var foundInPlex;
if (this.matchedInPlex) { if (this.matchedInPlex) {
foundInPlex = <button onClick={() => {this.requestExisting(this)}} foundInPlex = <button onClick={() => {this.requestExisting(this)}}
@@ -47,15 +57,31 @@ class MovieObject {
return ( return (
<div> <div>
<Notifications />
<div style={movieStyle.resultItem} key={this.id}> <div style={movieStyle.resultItem} key={this.id}>
<MediaQuery minWidth={600}>
<div style={movieStyle.resultPoster}> <div style={movieStyle.resultPoster}>
<img style={movieStyle.resultPosterImg} id='poster' src={posterPath}></img> <img style={movieStyle.resultPosterImg} id='poster' src={posterPath}></img>
</div> </div>
</MediaQuery>
<div> <div>
<span style={movieStyle.resultTitle}>{this.title} ({this.year})</span> <MediaQuery minWidth={600}>
<span style={movieStyle.resultTitleLarge}>{this.title}</span>
<br></br> <br></br>
<span>{this.summary}</span> <span style={movieStyle.yearRatingLarge}>Released: { this.year } | Rating: {this.rating}</span>
<br></br> <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 className='imdbLogo'>
</span> </span>
@@ -68,6 +94,9 @@ class MovieObject {
</div> </div>
</div> </div>
</div> </div>
<MediaQuery maxWidth={600}>
<br></br>
</MediaQuery>
<div style={movieStyle.row}> <div style={movieStyle.row}>
<div style={movieStyle.itemDivider}></div> <div style={movieStyle.itemDivider}></div>
</div> </div>

View File

@@ -4,8 +4,12 @@ import MovieObject from './MovieObject.jsx';
// StyleComponents // StyleComponents
import searchStyle from './styles/searchRequestStyle.jsx'; import searchStyle from './styles/searchRequestStyle.jsx';
import movieStyle from './styles/movieObjectStyle.jsx';
import URI from 'urijs'; 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 // TODO add option for searching multi, movies or tv shows
class SearchRequest extends React.Component { class SearchRequest extends React.Component {
@@ -13,24 +17,28 @@ class SearchRequest extends React.Component {
super(props) super(props)
// Constructor with states holding the search query and the element of reponse. // Constructor with states holding the search query and the element of reponse.
this.state = { this.state = {
lastApiCallURI: '',
searchQuery: '', searchQuery: '',
responseMovieList: null, responseMovieList: null,
movieFilter: true, movieFilter: false,
showFilter: false, showFilter: false,
discoverType: '', discoverType: '',
page: 1 page: 1,
resultHeader: '',
loadResults: false,
scrollHasMore: true
} }
this.allowedDiscoverTypes = [ this.allowedListTypes = [
'discover', 'popular', 'nowplaying', 'upcoming' '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.baseUrl = 'http://localhost:31459/api/v1/tmdb/';
this.URLs = { this.URLs = {
request: 'https://apollo.kevinmidboe.com/api/v1/plex/request?page='+this.state.page+'&query=', searchRequest: 'https://apollo.kevinmidboe.com/api/v1/plex/request',
// request: 'http://localhost:31459/api/v1/plex/request?page='+this.state.page+'&query=', // searchRequest: 'http://localhost:31459/api/v1/plex/request',
upcoming: 'https://apollo.kevinmidboe.com/api/v1/tmdb/upcoming', upcoming: 'https://apollo.kevinmidboe.com/api/v1/tmdb/upcoming',
// upcoming: 'http://localhost:31459/api/v1/tmdb/upcoming', // upcoming: 'http://localhost:31459/api/v1/tmdb/upcoming',
sendRequest: 'https://apollo.kevinmidboe.com/api/v1/plex/request?query=' sendRequest: 'https://apollo.kevinmidboe.com/api/v1/plex/request?query='
@@ -42,89 +50,225 @@ class SearchRequest extends React.Component {
componentDidMount(){ componentDidMount(){
var that = this; var that = this;
// this.setState({responseMovieList: null}) // 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 // Handles all errors of the response of a fetch call
handleErrors(response) { handleErrors(response) {
if (!response.ok) { if (!response.ok)
throw Error(response.status); throw Error(response.status);
return response;
}
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; return response;
} }
// Unpacks the query value of a uri
findQueryValueInURI(uri) {
let uriSearchValues = uri.query(true);
let queryValue = uriSearchValues['query']
fetchDiscover(queryDiscoverType) { return queryValue;
if (this.allowedDiscoverTypes.indexOf(queryDiscoverType) === -1) }
throw Error('Invalid discover type: ' + queryDiscoverType);
var uri = new URI(this.baseUrl); // Unpacks the page value of a uri
uri.segment(queryDiscoverType) findPageValueInURI(uri) {
uri = uri.setSearch('page', this.state.page); let uriSearchValues = uri.query(true);
if (this.state.showFilter) let queryValue = uriSearchValues['page']
uri = uri.addSearch('type', 'show');
console.log(uri) return queryValue;
}
resetPageNumber() {
this.state.page = 1;
}
writeLoading() {
this.setState({ this.setState({
responseMovieList: 'Loading...' 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()); // 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
fetchQuery() { fillResponseMovieListWithError(msg) {
let url = this.URLs.request + this.state.searchQuery this.setState({
if (this.state.showFilter) { responseMovieList: <h1>{ msg }</h1>
url = url + '&type=tv' })
} }
fetch(url)
// Check if the response is ok // Here we first call api for a search with the input uri, handle any errors
.then(response => this.handleErrors(response)) // and fill the reponseData from api into the state of reponseMovieList as movieObjects
.then(response => response.json()) // Convert to json object and pass to next then callSearchFillMovieList(uri) {
.then(data => { // Parse the data of the JSON response // Write loading animation
// If it is something here it updates the state variable with the HTML list of all // this.writeLoading();
// movie objects that where returned by the search request
if (data.results.length > 0) { 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({ this.setState({
responseMovieList: data.results.map(item => this.createMovieObjects(item)) 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
}) })
} }
}) })
// If the -------- .catch((error) => {
.catch(error => { console.log('CallSearchFillMovieList: ', error)
console.log(error)
this.setState({
responseMovieList: <h1>Not Found</h1>
}) })
})
.catch(() => {
throw Error('Something went wrong when fetching query.')
})
}
console.log('Error submit: ', error.toString()); 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);
} }
// Updates the internal state of the query search field. // Updates the internal state of the query search field.
updateQueryState(event){ updateQueryState(event){
this.setState({ this.setState({
@@ -135,7 +279,10 @@ class SearchRequest extends React.Component {
// For checking if the enter key was pressed in the search field. // For checking if the enter key was pressed in the search field.
_handleQueryKeyPress(e) { _handleQueryKeyPress(e) {
if (e.key === 'Enter') { if (e.key === 'Enter') {
this.fetchQuery(); // this.fetchQuery();
// Reset page number for a new search
this.resetPageNumber();
this.searchSeasonedRequest();
} }
} }
@@ -163,63 +310,135 @@ class SearchRequest extends React.Component {
pageBackwards() { pageBackwards() {
if (this.state.page > 1) { if (this.state.page > 1) {
console.log('backwards'); let pageNumber = this.state.page - 1;
this.state.page--; let uri = this.state.lastApiCallURI;
this.getUpcoming();
// 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;
} }
console.log(this.state.page)
} }
// TODO need to get total page number and save in a state to not overflow
pageForwards() { pageForwards() {
this.state.page++; // Wrap this in the check
this.getUpcoming(); let pageNumber = this.state.page + 1;
console.log('forwards'); let uri = this.state.lastApiCallURI;
console.log(this.state.page)
// 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(){ render(){
return( const loader = <div className="loader">Loading ...<br></br></div>;
<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>
<div className='backgroundHeader' style={searchStyle.backgroundHeader}>
return(
<InfiniteScroll
pageStart={0}
loadMore={this.pageForwards.bind(this)}
hasMore={this.state.scrollHasMore}
loader={loader}
initialLoad={this.state.loadResults}>
<MediaQuery minWidth={600}>
<div style={searchStyle.body}>
<div className='backgroundHeader' style={searchStyle.backgroundLargeHeader}>
<div className='pageTitle' style={searchStyle.pageTitle}> <div className='pageTitle' style={searchStyle.pageTitle}>
<span style={searchStyle.pageTitleSpan}>Request new movies or tv shows</span> <span style={searchStyle.pageTitleLargeSpan}>Request new content</span>
</div> </div>
<div className='box' style={searchStyle.box}> <div className='box' style={searchStyle.box}>
<div style={searchStyle.container}> <div style={searchStyle.searchLargeContainer}>
<span style={searchStyle.searchIcon}><i className="fa fa-search"></i></span> <span style={searchStyle.searchIcon}><i className="fa fa-search"></i></span>
<input style={searchStyle.searchBar} type="text" id="search" placeholder="Search for new content..." <input style={searchStyle.searchLargeBar} type="text" id="search" placeholder="Search for new content..."
onKeyPress={(event) => this._handleQueryKeyPress(event)} onKeyPress={(event) => this._handleQueryKeyPress(event)}
onChange={event => this.updateQueryState(event)} onChange={event => this.updateQueryState(event)}
value={this.state.searchQuery}/> value={this.state.searchQuery}/>
<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>
</div> </div>
<div id='requestMovieList' ref='requestMovieList' style={searchStyle.requestWrapper}> <div id='requestMovieList' ref='requestMovieList' style={searchStyle.requestWrapper}>
<span style={searchStyle.resultLargeHeader}>{this.state.resultHeader}</span>
<br></br><br></br>
{this.state.responseMovieList} {this.state.responseMovieList}
</div> </div>
<div> </div>
<button onClick={() => {this.pageBackwards()}}>Back</button> </MediaQuery>
<button onClick={() => {this.pageForwards()}}>Forward</button>
<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>
</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>
) )
} }

View File

@@ -6,21 +6,24 @@ export default {
minHeight: '230px' minHeight: '230px'
}, },
resultItem: {
maxWidth: '95%',
margin: '0 auto',
minHeight: '230px'
},
movie_content: { movie_content: {
marginLeft: '15px' marginLeft: '15px'
}, },
resultTitle: { resultTitleLarge: {
color: 'black', color: 'black',
fontSize: '2em', fontSize: '2em',
}, },
resultTitleSmall: {
color: 'black',
fontSize: '22px',
},
yearRatingLarge: {
fontSize: '0.8em'
},
resultPoster: { resultPoster: {
float: 'left', float: 'left',
zIndex: '3', zIndex: '3',
@@ -28,12 +31,30 @@ export default {
marginRight: '30px' marginRight: '30px'
}, },
background: {
width: '100%'
},
yearRatingSmall: {
marginTop: '5px',
fontSize: '0.8em'
},
resultPosterImg: { resultPosterImg: {
border: '2px none', border: '2px none',
borderRadius: '2px', borderRadius: '2px',
width: '150px' width: '150px'
}, },
cornerRibbon: {
position: 'absolute',
width: '450px',
},
summary: {
fontSize: '15px',
},
buttons: { buttons: {
paddingTop: '20px' paddingTop: '20px'
}, },

View File

@@ -6,23 +6,29 @@ export default {
margin: 0, margin: 0,
padding: 0, padding: 0,
minHeight: '100%', minHeight: '100%',
position: 'relative'
}, },
backgroundHeader: { backgroundLargeHeader: {
width: '100%', width: '100%',
minHeight: '400px', minHeight: '400px',
backgroundColor: '#011c23', backgroundColor: '#011c23',
zIndex: 1, zIndex: 1,
position: 'absolute' marginBottom: '-100px'
},
backgroundSmallHeader: {
width: '100%',
minHeight: '300px',
backgroundColor: '#011c23',
zIndex: 1,
marginBottom: '-100px'
}, },
requestWrapper: { requestWrapper: {
top: '300px',
width: '90%', width: '90%',
maxWidth: '1200px', maxWidth: '1200px',
margin: 'auto', margin: 'auto',
paddingTop: '20px', // paddingTop: '20px',
backgroundColor: 'white', backgroundColor: 'white',
position: 'relative', position: 'relative',
zIndex: '10', zIndex: '10',
@@ -32,42 +38,46 @@ export default {
pageTitle: { pageTitle: {
display: 'flex', display: 'flex',
alignItems: 'center', alignItems: 'center',
justifyContent: 'center' justifyContent: 'center',
textAlign: 'center'
}, },
pageTitleSpan: { pageTitleLargeSpan: {
color: 'white', color: 'white',
fontSize: '3em', fontSize: '3em',
marginTop: '4vh', marginTop: '4vh',
marginBottom: '6vh' marginBottom: '6vh'
}, },
box: { pageTitleSmallSpan: {
width: '90%', color: 'white',
height: '50px', fontSize: '2em',
maxWidth: '1200px', marginTop: '3vh',
margin: '0 auto' marginBottom: '3vh'
}, },
container: { box: {
verticalAlign: 'middle', height: '50px',
whiteSpace: 'nowrap', },
position: 'relative',
display: 'flex', searchLargeContainer: {
justifyContent: 'center' margin: '0 25%',
},
searchSmallContainer: {
margin: '0 10%',
}, },
searchIcon: { searchIcon: {
position: 'absolute', position: 'absolute',
marginLeft: '17px', fontSize: '1.2em',
marginTop: '17px', marginTop: '12px',
zIndex: '1', marginLeft: '-13px',
color: '#4f5b66' color: '#4f5b66'
}, },
searchBar: { searchLargeBar: {
width: '60%', width: '100%',
minWidth: '120px',
height: '50px', height: '50px',
background: '#ffffff', background: '#ffffff',
border: 'none', border: 'none',
@@ -75,19 +85,78 @@ export default {
float: 'left', float: 'left',
color: '#63717f', color: '#63717f',
paddingLeft: '45px', paddingLeft: '45px',
marginLeft: '-25px',
borderRadius: '5px', borderRadius: '5px',
marginRight: '15px'
}, },
searchFilter: { searchSmallBar: {
color: 'white', 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', fontSize: '1em',
paddingTop: '12px',
marginBottom: '12px',
marginLeft: '10px', marginLeft: '10px',
cursor: 'pointer' 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: { hvrUnderlineFromCenter: {
color: 'white', color: 'white',
fontSize: '1em', fontSize: '1em',

View File

@@ -4,6 +4,7 @@
<meta charset="utf-8"> <meta charset="utf-8">
<link href="https://fonts.googleapis.com/css?family=Open+Sans:300" rel="stylesheet"> <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> <title>seasoned Shows</title>
</head> </head>
<body style='margin: 0'> <body style='margin: 0'>

View File

@@ -13,7 +13,12 @@
"html-webpack-plugin": "^2.28.0", "html-webpack-plugin": "^2.28.0",
"path": "^0.12.7", "path": "^0.12.7",
"react": "^15.6.1", "react": "^15.6.1",
"react-burger-menu": "^2.1.6",
"react-dom": "^15.5.4", "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", "webfontloader": "^1.6.28",
"webpack": "^3.5.5", "webpack": "^3.5.5",
"webpack-dev-server": "^2.4.5" "webpack-dev-server": "^2.4.5"

View File

@@ -2,7 +2,7 @@ class mailTemplate {
constructor(mediaItem) { constructor(mediaItem) {
this.mediaItem = mediaItem; this.mediaItem = mediaItem;
this.posterURL = 'https://image.tmdb.org/t/p/w600/'; this.posterURL = 'https://image.tmdb.org/t/p/w600';
} }
toText() { toText() {

View File

@@ -7,6 +7,8 @@ const tmdb = new TMDB(configuration.get('tmdb', 'apiKey'));
var Promise = require('bluebird'); var Promise = require('bluebird');
var rp = require('request-promise'); var rp = require('request-promise');
const establishedDatabase = require('src/database/database');
const MailTemplate = require('src/plex/mailTemplate') const MailTemplate = require('src/plex/mailTemplate')
var pythonShell = require('python-shell'); var pythonShell = require('python-shell');
@@ -15,6 +17,13 @@ const nodemailer = require('nodemailer');
class RequestRepository { class RequestRepository {
constructor(database) {
this.database = database || establishedDatabase;
this.queries = {
'insertRequest': "INSERT INTO requests VALUES (?, ?, ?, ?, ?, ?, CURRENT_DATE)"
}
}
searchRequest(query, page, type) { searchRequest(query, page, type) {
// TODO get from cache // TODO get from cache
// STRIP METADATA THAT IS NOT ALLOWED // 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 * @param {identifier, type} the id of the media object and type of media must be defined
* @returns {Promise} If nothing has gone wrong. * @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 add to DB so can have a admin page
// TODO try a cache hit on the movie item // TODO try a cache hit on the movie item
tmdb.lookup(identifier, type).then(movie => { 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 // create reusable transporter object using the default SMTP transport
let transporter = nodemailer.createTransport({ let transporter = nodemailer.createTransport({
host: configuration.get('mail', 'host'), service: 'gmail',
port: 26,
ignoreTLS: true,
tls :{rejectUnauthorized: false},
secure: false, // secure:true for port 465, secure:false for port 587
auth: { auth: {
user: configuration.get('mail', 'user'), user: configuration.get('mail', 'user_pi'),
pass: configuration.get('mail', 'password') 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) const mailTemplate = new MailTemplate(movie)
@@ -123,7 +140,7 @@ class RequestRepository {
// setup email data with unicode symbols // setup email data with unicode symbols
let mailOptions = { let mailOptions = {
// TODO get the mail adr from global location (easy to add) // 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 to: 'kevin.midboe@gmail.com', // list of receivers
subject: 'Download request', // Subject line subject: 'Download request', // Subject line
text: mailTemplate.toText(), text: mailTemplate.toText(),

View File

@@ -2,10 +2,13 @@ const Movie = require('src/media_classes/movie');
const Show = require('src/media_classes/show'); const Show = require('src/media_classes/show');
function convertTmdbToSeasoned(tmdbObject, strictType=undefined) { 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; var mediaType = tmdbObject.media_type;
else else if (strictType !== undefined)
var mediaType = strictType; var mediaType = strictType;
else
var mediaType = 'movie';
// There are many diff types of content, we only want to look at movies and tv shows // There are many diff types of content, we only want to look at movies and tv shows
if (mediaType === 'movie') { if (mediaType === 'movie') {

View File

@@ -22,15 +22,16 @@ class TMDB {
return Promise.resolve() return Promise.resolve()
.then(() => this.tmdb(type, query)) // Search the tmdb api .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 .catch(() => { throw new Error('Could not search for movies.'); }) // If any error at all when fetching
.then((reponse) => { .then((response) => {
try { try {
// We want to filter because there are movies really low rated that are not interesting to us. // 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)) 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 // Here we convert the filtered result from the tmdb api to seaonsed objects
let seasonedItems = filteredTmdbItems.map((tmdbItem) => { let seasonedItems = filteredTmdbItems.map((tmdbItem) => {
if (type === 'movie') if (type === 'movie')
return convertTmdbToSeasoned(tmdbItem, 'movie'); return convertTmdbToSeasoned(tmdbItem, 'movie');
else if (type === 'show') else if (type === 'show')
@@ -40,7 +41,7 @@ class TMDB {
}); });
// TODO add page number if results are larger than 20 // 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 }; 'page': 1, 'total_pages': 1 };
} catch (parseError) { } catch (parseError) {

View File

@@ -12,8 +12,9 @@ function submitRequestController(req, res) {
// This is the id that is the param of the url // This is the id that is the param of the url
const id = req.params.mediaId; const id = req.params.mediaId;
const type = req.query.type; 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(() => { .then(() => {
res.send({ success: true, message: 'Media item sucessfully requested!' }); res.send({ success: true, message: 'Media item sucessfully requested!' });
}) })