diff --git a/package.json b/package.json index ead9591..680f3bf 100644 --- a/package.json +++ b/package.json @@ -16,6 +16,7 @@ "react": "^15.3.2", "react-bootstrap": "^0.30.6", "react-dom": "^15.3.2", + "react-loading": "^0.0.9", "react-redux": "^4.4.6", "react-redux-form": "^1.2.4", "react-router": "^3.0.0", diff --git a/src/internal/external_medias/handlers.go b/src/internal/external_medias/handlers.go index c60f603..6ad4674 100644 --- a/src/internal/external_medias/handlers.go +++ b/src/internal/external_medias/handlers.go @@ -2,11 +2,14 @@ package extmedias import ( "database/sql" + "errors" "fmt" "net/http" + "gitlab.quimbo.fr/odwrtw/canape-sql/src/internal/auth" "gitlab.quimbo.fr/odwrtw/canape-sql/src/internal/movies" "gitlab.quimbo.fr/odwrtw/canape-sql/src/internal/shows" + "gitlab.quimbo.fr/odwrtw/canape-sql/src/internal/users" "gitlab.quimbo.fr/odwrtw/canape-sql/src/internal/web" @@ -89,12 +92,19 @@ func GetMediaIDs(env *web.Env, mediaType string, source string, category string, } // GetMovies get some movies -func GetMovies(env *web.Env, source string, category string, force bool) ([]*movies.Movie, error) { +func GetMovies(env *web.Env, user *users.User, source string, category string, force bool) ([]*movies.Movie, error) { movieIds, err := GetMediaIDs(env, "movie", source, category, force) if err != nil { return nil, err } + // Get the URLs from polochon, we don't really care if it fails for now + var urls map[string]string + urls, err = movies.GetPolochonMoviesURLs(user) + if err != nil { + env.Log.Errorf("error while getting polochon movies url: %s", err) + } + movieList := []*movies.Movie{} for _, id := range movieIds { movie := movies.New(id) @@ -108,6 +118,13 @@ func GetMovies(env *web.Env, source string, category string, force bool) ([]*mov env.Log.Errorf("error while getting movie torrents : %s", err) continue } + + if urls != nil { + if url, ok := urls[id]; ok { + movie.PolochonURL = url + } + } + movieList = append(movieList, movie) } return movieList, nil @@ -157,10 +174,16 @@ func Explore(env *web.Env, w http.ResponseWriter, r *http.Request) error { category = "popular" } + v := auth.GetCurrentUser(r, env.Log) + user, ok := v.(*users.User) + if !ok { + return env.RenderError(w, errors.New("invalid user")) + } + // Get the medias without trying to refresh them - movies, err := GetMovies(env, source, category, false) + movies, err := GetMovies(env, user, source, category, false) if err != nil { - return err + return env.RenderError(w, err) } return env.RenderJSON(w, movies) @@ -188,7 +211,7 @@ func ExploreShows(env *web.Env, w http.ResponseWriter, r *http.Request) error { // Get the medias without trying to refresh them shows, err := GetShows(env, source, category, false) if err != nil { - return err + return env.RenderError(w, err) } return env.RenderJSON(w, shows) @@ -218,13 +241,19 @@ func Refresh(env *web.Env, w http.ResponseWriter, r *http.Request) error { category = "popular" } + v := auth.GetCurrentUser(r, env.Log) + user, ok := v.(*users.User) + if !ok { + return fmt.Errorf("invalid user type") + } + // We'll refresh the medias for each sources for _, source := range MediaSources { env.Log.Debugf("refreshing %s", source) // GetMedias and refresh them - _, err := GetMovies(env, source, category, true) + _, err := GetMovies(env, user, source, category, true) if err != nil { - return err + return env.RenderError(w, err) } } @@ -250,7 +279,7 @@ func RefreshShows(env *web.Env, w http.ResponseWriter, r *http.Request) error { // GetMedias and refresh them _, err := GetShows(env, source, category, true) if err != nil { - return err + return env.RenderError(w, err) } } diff --git a/src/internal/movies/handlers.go b/src/internal/movies/handlers.go index 70e0e76..9416a4c 100644 --- a/src/internal/movies/handlers.go +++ b/src/internal/movies/handlers.go @@ -1,6 +1,7 @@ package movies import ( + "encoding/json" "errors" "fmt" "net" @@ -63,6 +64,21 @@ func getPolochonMovies(user *users.User) ([]*Movie, error) { return movies, nil } +// GetPolochonMoviesURLs returns the polochon urls associated with an imdb id +func GetPolochonMoviesURLs(user *users.User) (map[string]string, error) { + movies, err := getPolochonMovies(user) + if err != nil { + return nil, err + } + + urls := make(map[string]string, len(movies)) + for _, movie := range movies { + urls[movie.ImdbID] = movie.PolochonURL + } + + return urls, nil +} + // FromPolochon will returns movies from Polochon func FromPolochon(env *web.Env, w http.ResponseWriter, r *http.Request) error { v := auth.GetCurrentUser(r, env.Log) @@ -113,15 +129,28 @@ func GetDetailsHandler(env *web.Env, w http.ResponseWriter, r *http.Request) err // SearchMovie will search movie func SearchMovie(env *web.Env, w http.ResponseWriter, r *http.Request) error { - key := r.FormValue("key") - if key == "" { + var data struct { + Key string `json:"key"` + } + err := json.NewDecoder(r.Body).Decode(&data) + if err != nil { + return env.RenderError(w, errors.New("failed to get the search key")) + } + + if data.Key == "" { return env.RenderError(w, errors.New("no given key")) } + v := auth.GetCurrentUser(r, env.Log) + user, ok := v.(*users.User) + if !ok { + return env.RenderError(w, errors.New("invalid user")) + } + var movies []*polochon.Movie searchers := env.Config.MovieSearchers for _, searcher := range searchers { - result, err := searcher.SearchMovie(key, env.Log) + result, err := searcher.SearchMovie(data.Key, env.Log) if err != nil { env.Log.Errorf("error while searching movie : %s", err) continue @@ -129,7 +158,14 @@ func SearchMovie(env *web.Env, w http.ResponseWriter, r *http.Request) error { movies = append(movies, result...) } - env.Log.Debugf("got %d movies doing search %q", len(movies), key) + // Get the URLs from polochon, we don't really care if it fails for now + var urls map[string]string + urls, err = GetPolochonMoviesURLs(user) + if err != nil { + env.Log.Errorf("error while getting polochon movies url: %s", err) + } + + env.Log.Debugf("got %d movies doing search %q", len(movies), data.Key) movieList := []*Movie{} for _, m := range movies { movie := New(m.ImdbID) @@ -143,6 +179,13 @@ func SearchMovie(env *web.Env, w http.ResponseWriter, r *http.Request) error { env.Log.Errorf("error while getting movie torrents : %s", err) continue } + + if urls != nil { + if url, ok := urls[m.ImdbID]; ok { + movie.PolochonURL = url + } + } + movieList = append(movieList, movie) } diff --git a/src/internal/shows/handlers.go b/src/internal/shows/handlers.go index 88f52b9..29d17b8 100644 --- a/src/internal/shows/handlers.go +++ b/src/internal/shows/handlers.go @@ -1,6 +1,7 @@ package shows import ( + "encoding/json" "errors" "net/http" @@ -27,15 +28,21 @@ func GetDetailsHandler(env *web.Env, w http.ResponseWriter, r *http.Request) err // SearchShow will search a show func SearchShow(env *web.Env, w http.ResponseWriter, r *http.Request) error { - key := r.FormValue("key") - if key == "" { + var data struct { + Key string `json:"key"` + } + if err := json.NewDecoder(r.Body).Decode(&data); err != nil { + return env.RenderError(w, errors.New("failed to get the search key")) + } + + if data.Key == "" { return env.RenderError(w, errors.New("no given key")) } var shows []*polochon.Show searchers := env.Config.ShowSearchers for _, searcher := range searchers { - result, err := searcher.SearchShow(key, env.Log) + result, err := searcher.SearchShow(data.Key, env.Log) if err != nil { env.Log.Errorf("error while searching show : %s", err) continue @@ -43,7 +50,7 @@ func SearchShow(env *web.Env, w http.ResponseWriter, r *http.Request) error { shows = append(shows, result...) } - env.Log.Debugf("got %d shows doing search %q", len(shows), key) + env.Log.Debugf("got %d shows doing search %q", len(shows), data.Key) showList := []*Show{} for _, s := range shows { show := New(s.ImdbID) diff --git a/src/main.go b/src/main.go index cf5fb35..c4580eb 100644 --- a/src/main.go +++ b/src/main.go @@ -71,22 +71,24 @@ func main() { authMiddleware := auth.NewMiddleware(env.Auth, log) + // TODO: refresh sould be handled by admins only + env.Handle("/users/login", users.LoginPOSTHandler).Methods("POST") env.Handle("/users/signup", users.SignupPOSTHandler).Methods("POST") - env.Handle("/users/details", users.DetailsHandler).WithRole(users.UserRole) - env.Handle("/users/edit", users.EditHandler).WithRole(users.UserRole) + env.Handle("/users/details", users.DetailsHandler).WithRole(users.UserRole).Methods("GET") + env.Handle("/users/edit", users.EditHandler).WithRole(users.UserRole).Methods("POST") - env.Handle("/movies/polochon", movies.FromPolochon).WithRole(users.UserRole) - env.Handle("/movies/{id:tt[0-9]+}/get_details", movies.GetDetailsHandler).WithRole(users.UserRole) - env.Handle("/movies/explore", extmedias.Explore) - env.Handle("/movies/refresh", extmedias.Refresh) - env.Handle("/movies/search", movies.SearchMovie) + env.Handle("/movies/polochon", movies.FromPolochon).WithRole(users.UserRole).Methods("GET") + env.Handle("/movies/{id:tt[0-9]+}/get_details", movies.GetDetailsHandler).WithRole(users.UserRole).Methods("GET") + env.Handle("/movies/explore", extmedias.Explore).WithRole(users.UserRole).Methods("GET") + env.Handle("/movies/refresh", extmedias.Refresh).WithRole(users.UserRole).Methods("POST") + env.Handle("/movies/search", movies.SearchMovie).WithRole(users.UserRole).Methods("POST") // env.Handle("/shows/polochon", shows.FromPolochon).WithRole(users.UserRole) - env.Handle("/shows/{id:tt[0-9]+}", shows.GetDetailsHandler) - env.Handle("/shows/refresh", extmedias.RefreshShows) - env.Handle("/shows/explore", extmedias.ExploreShows) - env.Handle("/shows/search", shows.SearchShow) + env.Handle("/shows/{id:tt[0-9]+}", shows.GetDetailsHandler).WithRole(users.UserRole).Methods("GET") + env.Handle("/shows/refresh", extmedias.RefreshShows).WithRole(users.UserRole).Methods("POST") + env.Handle("/shows/explore", extmedias.ExploreShows).WithRole(users.UserRole).Methods("GET") + env.Handle("/shows/search", shows.SearchShow).WithRole(users.UserRole).Methods("POST") n := negroni.Classic() n.Use(authMiddleware) diff --git a/src/public/js/actions/actionCreators.js b/src/public/js/actions/actionCreators.js index e0c1665..9e96a87 100644 --- a/src/public/js/actions/actionCreators.js +++ b/src/public/js/actions/actionCreators.js @@ -80,6 +80,13 @@ export function selectMovie(imdbId) { } } +export function searchMovies(search) { + return request( + 'SEARCH_MOVIES', + configureAxios().post('/movies/search', search) + ) +} + export function getMovieDetails(imdbId) { return request( 'MOVIE_GET_DETAILS', @@ -105,6 +112,13 @@ export function fetchShows(url) { ) } +export function searchShows(search) { + return request( + 'SEARCH_SHOWS', + configureAxios().post('/shows/search', search) + ) +} + export function fetchShowDetails(imdbId) { return request( 'SHOW_FETCH_DETAILS', diff --git a/src/public/js/app.js b/src/public/js/app.js index b75f21e..bcd32ce 100644 --- a/src/public/js/app.js +++ b/src/public/js/app.js @@ -83,14 +83,19 @@ const MovieListPopular = (props) => ( const MovieListPolochon = (props) => ( ) +const MovieListSearch = (props) => ( + +) const ShowListPopular = (props) => ( ) - const ShowDetailsView = (props) => ( ) +const ShowListSearch = (props) => ( + +) ReactDOM.render(( @@ -100,8 +105,10 @@ ReactDOM.render(( + + diff --git a/src/public/js/components/list/details.js b/src/public/js/components/list/details.js index 12ca5e8..8bfc0fb 100644 --- a/src/public/js/components/list/details.js +++ b/src/public/js/components/list/details.js @@ -1,6 +1,10 @@ import React from 'react' export default function ListDetails(props) { + let genres; + if (props.data.genres) { + genres = props.data.genres.join(', '); + } return (
@@ -12,6 +16,12 @@ export default function ListDetails(props) {  {props.data.runtime} min

} + {props.data.genres && +

+ +  {genres} +

+ }

 {props.data.rating} ({props.data.votes} counts) diff --git a/src/public/js/components/loader/loader.js b/src/public/js/components/loader/loader.js new file mode 100644 index 0000000..0992754 --- /dev/null +++ b/src/public/js/components/loader/loader.js @@ -0,0 +1,17 @@ +import React from 'react' +import Loading from 'react-loading' + +export default function Loader() { + return ( +

+
+ +
+
+ ) +} diff --git a/src/public/js/components/movies/list.js b/src/public/js/components/movies/list.js index c1b4731..d66bffe 100644 --- a/src/public/js/components/movies/list.js +++ b/src/public/js/components/movies/list.js @@ -2,6 +2,7 @@ import React from 'react' import ListPosters from '../list/posters' import ListDetails from '../list/details' +import Loader from '../loader/loader' class MovieButtons extends React.Component { constructor(props) { @@ -53,7 +54,24 @@ class MovieButtons extends React.Component { export default class MovieList extends React.Component { componentWillMount() { - this.props.fetchMovies(this.props.moviesUrl); + if (this.props.moviesUrl) { + this.props.fetchMovies(this.props.moviesUrl); + } else if (this.props.params && this.props.params.search != "") { + this.props.searchMovies({ + key: this.props.params.search + }); + } + } + componentWillUpdate(nextProps, nextState) { + if (!nextProps.params || nextProps.params.search === "") { + return + } + if (this.props.params.search === nextProps.params.search) { + return + } + this.props.searchMovies({ + key: nextProps.params.search + }); } render() { const movies = this.props.movieStore.movies; @@ -63,6 +81,12 @@ export default class MovieList extends React.Component { index = 0; } const selectedMovie = movies[index]; + + // Loading + if (this.props.movieStore.loading) { + return (); + } + return (
-1) + { + displayMovieSearch = true; + } + if (isLoggedIn && location.indexOf("shows") > -1) + { + displayShowSearch = true; + } return (
@@ -53,6 +75,30 @@ export default class NavBar extends React.Component { } + {displayMovieSearch && + +
this.handleMovieSearch()}> + + +
+ } + {displayShowSearch && + +
this.handleShowSearch()}> + + +
+ }
diff --git a/src/public/js/components/shows/details.js b/src/public/js/components/shows/details.js index 9f33991..04043b0 100644 --- a/src/public/js/components/shows/details.js +++ b/src/public/js/components/shows/details.js @@ -1,10 +1,15 @@ import React from 'react' +import Loader from '../loader/loader' export default class ShowDetails extends React.Component { componentWillMount() { this.props.fetchShowDetails(this.props.params.imdbId); } render() { + // Loading + if (this.props.showStore.loading) { + return (); + } return (
diff --git a/src/public/js/components/shows/list.js b/src/public/js/components/shows/list.js index 1d83f0c..8b0703f 100644 --- a/src/public/js/components/shows/list.js +++ b/src/public/js/components/shows/list.js @@ -3,6 +3,7 @@ import { Link } from 'react-router' import ListDetails from '../list/details' import ListPosters from '../list/posters' +import Loader from '../loader/loader' function ShowButtons(props) { const imdbLink = `http://www.imdb.com/title/${props.show.imdb_id}`; @@ -20,7 +21,24 @@ function ShowButtons(props) { export default class ShowList extends React.Component { componentWillMount() { - this.props.fetchShows(this.props.showsUrl); + if (this.props.showsUrl) { + this.props.fetchShows(this.props.showsUrl); + } else if (this.props.params && this.props.params.search != "") { + this.props.searchShows({ + key: this.props.params.search + }); + } + } + componentWillUpdate(nextProps, nextState) { + if (!nextProps.params || nextProps.params.search === "") { + return + } + if (this.props.params.search === nextProps.params.search) { + return + } + this.props.searchShows({ + key: nextProps.params.search + }); } render() { const shows = this.props.showStore.shows; @@ -30,6 +48,12 @@ export default class ShowList extends React.Component { index = 0; } const selectedShow = shows[index]; + + // Loading + if (this.props.showStore.loading) { + return (); + } + return (