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