From 0a4d09d7c5407a2d94c8a8bbb55df4cd287ee768 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9goire=20Delattre?= Date: Wed, 23 Nov 2016 19:14:52 +0100 Subject: [PATCH 1/9] Add get details button for movies --- src/internal/movies/handlers.go | 14 ++++++++++++++ src/main.go | 1 + 2 files changed, 15 insertions(+) diff --git a/src/internal/movies/handlers.go b/src/internal/movies/handlers.go index 8fe2a4d..08c14e8 100644 --- a/src/internal/movies/handlers.go +++ b/src/internal/movies/handlers.go @@ -6,6 +6,7 @@ import ( "net/http" "net/url" + "github.com/gorilla/mux" "github.com/odwrtw/papi" polochon "github.com/odwrtw/polochon/lib" "github.com/odwrtw/polochon/modules/pam" @@ -134,3 +135,16 @@ func ExplorePopular(env *web.Env, w http.ResponseWriter, r *http.Request) error return env.RenderJSON(w, movies) } + +// GetDetailsHandler retrieves details for a movie +func GetDetailsHandler(env *web.Env, w http.ResponseWriter, r *http.Request) error { + vars := mux.Vars(r) + id := vars["id"] + + m := New(id) + if err := m.GetDetails(env, true); err != nil { + return err + } + + return env.RenderJSON(w, m) +} diff --git a/src/main.go b/src/main.go index 413482e..f43ad06 100644 --- a/src/main.go +++ b/src/main.go @@ -75,6 +75,7 @@ func main() { env.Handle("/movies/polochon", movies.FromPolochon).WithRole(users.UserRole) env.Handle("/movies/explore/popular", movies.ExplorePopular).WithRole(users.UserRole) + env.Handle("/movies/{id:tt[0-9]+}/get_details", movies.GetDetailsHandler).WithRole(users.UserRole) n := negroni.Classic() n.Use(authMiddleware) From 77da13200e9f62f9a3acbf32556431fe7b1f2e63 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9goire=20Delattre?= Date: Wed, 23 Nov 2016 21:03:10 +0100 Subject: [PATCH 2/9] Resize image while downloading and create image directory --- src/internal/web/download.go | 35 +++++++++++++++++++++++++++++------ 1 file changed, 29 insertions(+), 6 deletions(-) diff --git a/src/internal/web/download.go b/src/internal/web/download.go index ce84275..6002d3f 100644 --- a/src/internal/web/download.go +++ b/src/internal/web/download.go @@ -1,19 +1,33 @@ package web import ( + "image" + "image/jpeg" "io" "net/http" "os" + "path" + + "github.com/nfnt/resize" ) // Download used for downloading file var Download = func(srcURL, dest string) error { + if err := createDirectory(dest); err != nil { + return err + } + resp, err := http.Get(srcURL) if err != nil { return err } defer resp.Body.Close() + img, err := resizeImage(resp.Body) + if err != nil { + return err + } + // Create the file file, err := os.Create(dest) if err != nil { @@ -21,10 +35,19 @@ var Download = func(srcURL, dest string) error { } defer file.Close() - // Write from the net to the file - _, err = io.Copy(file, resp.Body) - if err != nil { - return err - } - return nil + return jpeg.Encode(file, img, nil) +} + +// createDirectory creates the destination directory +func createDirectory(dest string) error { + return os.MkdirAll(path.Dir(dest), os.ModePerm) +} + +func resizeImage(img io.Reader) (image.Image, error) { + image, _, err := image.Decode(img) + if err != nil { + return nil, err + } + + return resize.Resize(300, 0, image, resize.Lanczos3), nil } From fa48dcac5d8e31caa6bb79f0f1a31422d91ffa49 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9goire=20Delattre?= Date: Wed, 23 Nov 2016 21:03:50 +0100 Subject: [PATCH 3/9] Add button to refresh the movie details --- src/public/js/actions/actionCreators.js | 7 ++++ src/public/js/components/movies/list.js | 52 +++++++++++++++++++++---- src/public/js/reducers/movies.js | 34 +++++++++++++--- 3 files changed, 81 insertions(+), 12 deletions(-) diff --git a/src/public/js/actions/actionCreators.js b/src/public/js/actions/actionCreators.js index 1de4db5..c9ba18c 100644 --- a/src/public/js/actions/actionCreators.js +++ b/src/public/js/actions/actionCreators.js @@ -69,6 +69,13 @@ export function getUserInfos() { ) } +export function getMovieDetails(imdb_id) { + return request( + 'MOVIE_GET_DETAILS', + configureAxios().get(`/movies/${imdb_id}/get_details`) + ) +} + export function fetchMovies(url) { return request( 'MOVIE_LIST_FETCH', diff --git a/src/public/js/components/movies/list.js b/src/public/js/components/movies/list.js index 889b760..23a86ec 100644 --- a/src/public/js/components/movies/list.js +++ b/src/public/js/components/movies/list.js @@ -64,29 +64,63 @@ function MovieDetails(props) { return (
-

{props.data.title}

-

{props.data.title}

+

{props.movie.title}

+

{props.movie.title}

- {props.data.runtime} min + {props.movie.runtime} min

- {props.data.rating} ({props.data.votes} counts) + {props.movie.rating} ({props.movie.votes} counts)

-

{props.data.plot}

+

{props.movie.plot}

+
); } +class MovieButtons extends React.Component { + constructor(props) { + super(props); + this.handleClick = this.handleClick.bind(this); + } + handleClick(e) { + e.preventDefault(); + this.props.getMovieDetails(this.props.movie.imdb_id); + } + render() { + const imdb_link = 'http://www.imdb.com/title/' + this.props.movie.imdb_id; + return ( +
+ + {this.props.fetching || + + Refresh + + } + {this.props.fetching && + + Refreshing + + } + + + IMDB + +
+ ); + } +} + export default class MovieList extends React.Component { componentWillMount() { this.props.fetchMovies(this.props.moviesUrl); } render() { const movies = this.props.movieStore.movies; - const index = this.props.movieStore.selectedMovieIndex; + const index = this.props.movieStore.selectedMovie.index; const selectedMovie = movies[index]; return (
@@ -96,7 +130,11 @@ export default class MovieList extends React.Component { onClick={this.props.selectMovie} /> {selectedMovie && - + }
); diff --git a/src/public/js/reducers/movies.js b/src/public/js/reducers/movies.js index 716a71c..be83b97 100644 --- a/src/public/js/reducers/movies.js +++ b/src/public/js/reducers/movies.js @@ -1,6 +1,9 @@ const defaultState = { movies: [], - selectedMovieIndex: 0, + selectedMovie: { + index: 0, + fetchingDetails: false, + }, }; export default function movieStore(state = defaultState, action) { @@ -9,11 +12,32 @@ export default function movieStore(state = defaultState, action) { return Object.assign({}, state, { movies: action.payload.data, }) - case 'MOVIE_LIST_FETCH_PENDING': - return state - case 'SELECT_MOVIE': + case 'MOVIE_GET_DETAILS_PENDING': return Object.assign({}, state, { - selectedMovieIndex: action.index, + selectedMovie: Object.assign({}, state.selectedMovie, { + fetchingDetails: true, + }), + }) + case 'MOVIE_GET_DETAILS_FULFILLED': + let movies = state.movies.slice(); + movies[state.selectedMovie.index] = action.payload.data; + return Object.assign({}, state, { + movies: movies, + selectedMovie: Object.assign({}, state.selectedMovie, { + fetchingDetails: false, + }), + }) + case 'SELECT_MOVIE': + // Don't select the movie if we're fetching another movie's details + if (state.selectedMovie.fetchingDetails) { + return state + } + + const selectedMovie = Object.assign({}, state.selectedMovie, { + index: action.index, + }) + return Object.assign({}, state, { + selectedMovie: selectedMovie, }) default: return state From 884af0f8b8bf69a3e2758c0f68940022ab9d509b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9goire=20Delattre?= Date: Wed, 23 Nov 2016 22:06:13 +0100 Subject: [PATCH 4/9] Return the movie URL from the API --- src/internal/movies/movies.go | 42 ++++++++++++++++++++----- src/public/js/components/movies/list.js | 39 ++++++++--------------- src/public/js/reducers/movies.js | 7 ++--- 3 files changed, 51 insertions(+), 37 deletions(-) diff --git a/src/internal/movies/movies.go b/src/internal/movies/movies.go index f1f60bd..92148ef 100644 --- a/src/internal/movies/movies.go +++ b/src/internal/movies/movies.go @@ -2,6 +2,7 @@ package movies import ( "fmt" + "os" "path/filepath" "github.com/Sirupsen/logrus" @@ -50,7 +51,8 @@ var ( type Movie struct { sqly.BaseModel polochon.Movie - PolochonURL string + PolochonURL string `json:"polochon_url"` + PosterURL string `json:"poster_url"` } func New(imdbID string) *Movie { @@ -62,12 +64,12 @@ func New(imdbID string) *Movie { } // Get returns show details in database from id or imdbid or an error -func (m *Movie) Get(db *sqlx.DB) error { +func (m *Movie) Get(env *web.Env) error { var err error if m.ID != "" { - err = db.QueryRowx(getMovieQueryByID, m.ID).StructScan(m) + err = env.Database.QueryRowx(getMovieQueryByID, m.ID).StructScan(m) } else if m.ImdbID != "" { - err = db.QueryRowx(getMovieQueryByImdbID, m.ImdbID).StructScan(m) + err = env.Database.QueryRowx(getMovieQueryByImdbID, m.ImdbID).StructScan(m) } else { err = fmt.Errorf("Can't get movie details, you have to specify an ID or ImdbID") } @@ -77,6 +79,10 @@ func (m *Movie) Get(db *sqlx.DB) error { } return err } + + // Set the poster url + m.PosterURL = m.GetPosterURL(env) + return nil } @@ -101,7 +107,7 @@ func (m *Movie) GetDetails(env *web.Env, force bool) error { var dbFunc func(db *sqlx.DB) error var err error - err = m.Get(env.Database) + err = m.Get(env) switch err { case nil: log.Debug("movie found in database") @@ -133,14 +139,16 @@ func (m *Movie) GetDetails(env *web.Env, force bool) error { log.Debug("movie added in database") // Download poster - imgPath := filepath.Join(env.Config.PublicDir, "img", "movies", m.ImdbID+".jpg") - err = web.Download(m.Thumb, imgPath) + err = web.Download(m.Thumb, m.imgFile(env)) if err != nil { return err } log.Debug("poster downloaded") + // Set the poster url + m.PosterURL = m.GetPosterURL(env) + return nil } @@ -177,3 +185,23 @@ func (m *Movie) Delete(db *sqlx.DB) error { } return nil } + +// imgURL returns the default image url +func (m *Movie) imgURL(env *web.Env) string { + return fmt.Sprintf("img/movies/%s.jpg", m.ImdbID) +} + +// imgFile returns the image location on disk +func (m *Movie) imgFile(env *web.Env) string { + return filepath.Join(env.Config.PublicDir, m.imgURL(env)) +} + +// GetPosterURL returns the image URL or the default image if the poster is not yet downloaded +func (m *Movie) GetPosterURL(env *web.Env) string { + // Check if the movie image exists + if _, err := os.Stat(m.imgFile(env)); os.IsNotExist(err) { + // TODO image in the config ? + return "img/noimage.png" + } + return m.imgURL(env) +} diff --git a/src/public/js/components/movies/list.js b/src/public/js/components/movies/list.js index 23a86ec..02e06e4 100644 --- a/src/public/js/components/movies/list.js +++ b/src/public/js/components/movies/list.js @@ -32,32 +32,19 @@ function MoviePosters(props) { ); } -class MoviePoster extends React.Component { - constructor(props) { - super(props); - this.state = { - src: `/img/movies/${this.props.data.imdb_id}.jpg`, - } - this.handleError = this.handleError.bind(this); - } - handleError() { - this.setState({ src: '/img/noimage.png' }); - } - render() { - const selected = this.props.selected ? ' thumbnail-selected' : ''; - const imgClass = 'thumbnail' + selected; - return ( -
- - - -
- ); - } +function MoviePoster(props) { + const selected = props.selected ? ' thumbnail-selected' : ''; + const imgClass = 'thumbnail' + selected; + return ( +
+ + + +
+ ); } function MovieDetails(props) { diff --git a/src/public/js/reducers/movies.js b/src/public/js/reducers/movies.js index be83b97..220b9cd 100644 --- a/src/public/js/reducers/movies.js +++ b/src/public/js/reducers/movies.js @@ -33,11 +33,10 @@ export default function movieStore(state = defaultState, action) { return state } - const selectedMovie = Object.assign({}, state.selectedMovie, { - index: action.index, - }) return Object.assign({}, state, { - selectedMovie: selectedMovie, + selectedMovie: Object.assign({}, state.selectedMovie, { + index: action.index, + }), }) default: return state From 9c98046c5ab8252bdd6152808bedf6ca11966590 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9goire=20Delattre?= Date: Wed, 23 Nov 2016 22:06:23 +0100 Subject: [PATCH 5/9] Add a button to download movies form polochon --- src/public/js/components/movies/list.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/public/js/components/movies/list.js b/src/public/js/components/movies/list.js index 02e06e4..42e7981 100644 --- a/src/public/js/components/movies/list.js +++ b/src/public/js/components/movies/list.js @@ -93,6 +93,11 @@ class MovieButtons extends React.Component { } + {this.props.movie.polochon_url !== "" && + + Download + + } IMDB From 819abba77ede26b6cde14ddd1a623800d65a88b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9goire=20Delattre?= Date: Wed, 23 Nov 2016 22:32:16 +0100 Subject: [PATCH 6/9] Remove useless div and add some responsiveness --- src/public/js/app.js | 4 +--- src/public/js/components/movies/list.js | 12 ++++++------ src/public/js/components/navbar.js | 3 +-- 3 files changed, 8 insertions(+), 11 deletions(-) diff --git a/src/public/js/app.js b/src/public/js/app.js index 32574ef..be8e084 100644 --- a/src/public/js/app.js +++ b/src/public/js/app.js @@ -43,9 +43,7 @@ class Main extends React.Component {
-
- {React.cloneElement(this.props.children, this.props)} -
+ {React.cloneElement(this.props.children, this.props)}
); diff --git a/src/public/js/components/movies/list.js b/src/public/js/components/movies/list.js index 42e7981..afd19f9 100644 --- a/src/public/js/components/movies/list.js +++ b/src/public/js/components/movies/list.js @@ -36,7 +36,7 @@ function MoviePoster(props) { const selected = props.selected ? ' thumbnail-selected' : ''; const imgClass = 'thumbnail' + selected; return ( -
+
- + @@ -115,7 +115,7 @@ export default class MovieList extends React.Component { const index = this.props.movieStore.selectedMovie.index; const selectedMovie = movies[index]; return ( -
+
- + Canapé @@ -53,7 +53,6 @@ export default class NavBar extends React.Component {
- ); } } From 9c68857934540b4df954aba860cfa344cb096dde Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9goire=20Delattre?= Date: Thu, 24 Nov 2016 00:12:34 +0100 Subject: [PATCH 7/9] Add a bootswatch theme --- package.json | 1 + src/public/js/components/movies/list.js | 4 ++-- src/public/js/components/navbar.js | 2 +- src/public/less/app.less | 12 +++++++++--- yarn.lock | 4 ++++ 5 files changed, 17 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index b7fb322..f8f40e3 100644 --- a/package.json +++ b/package.json @@ -6,6 +6,7 @@ "dependencies": { "babel-polyfill": "^6.16.0", "bootstrap": "^3.3.6", + "bootswatch": "^3.3.7", "font-awesome": "^4.7.0", "history": "^4.4.0", "jquery": "^2.2.4", diff --git a/src/public/js/components/movies/list.js b/src/public/js/components/movies/list.js index afd19f9..bbcda28 100644 --- a/src/public/js/components/movies/list.js +++ b/src/public/js/components/movies/list.js @@ -55,11 +55,11 @@ function MovieDetails(props) {

{props.movie.title}

- {props.movie.runtime} min +  {props.movie.runtime} min

- {props.movie.rating} ({props.movie.votes} counts) +  {props.movie.rating} ({props.movie.votes} counts)

{props.movie.plot}

diff --git a/src/public/js/components/navbar.js b/src/public/js/components/navbar.js index 830fa8d..859ca48 100644 --- a/src/public/js/components/navbar.js +++ b/src/public/js/components/navbar.js @@ -12,7 +12,7 @@ export default class NavBar extends React.Component { const isLoggedIn = username !== "" ? true : false; return (
- + Canapé diff --git a/src/public/less/app.less b/src/public/less/app.less index dabe803..7f7cf31 100644 --- a/src/public/less/app.less +++ b/src/public/less/app.less @@ -1,13 +1,15 @@ @import "~bootstrap/less/bootstrap.less"; +@import "~bootswatch/superhero/variables.less"; +@import "~bootswatch/superhero/bootswatch.less"; @import "~font-awesome/less/font-awesome.less"; body { - padding-top: 70px; + padding-top: @navbar-height + 10px; } .thumbnail-selected { - border-color:#f1c40f; - background-color:#f1c40f; + border-color: @brand-primary; + background-color: @brand-primary; } .movie-plot { @@ -20,3 +22,7 @@ body { bottom: 1%; right: 1%; } + +.navbar { + opacity: 0.95; +} diff --git a/yarn.lock b/yarn.lock index 11d8cf9..59f4ae6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -773,6 +773,10 @@ bootstrap@^3.3.6: version "3.3.7" resolved "https://registry.yarnpkg.com/bootstrap/-/bootstrap-3.3.7.tgz#5a389394549f23330875a3b150656574f8a9eb71" +bootswatch: + version "3.3.7" + resolved "https://registry.yarnpkg.com/bootswatch/-/bootswatch-3.3.7.tgz#eb6f9a9a8523b87a706ea91deec3e0d7eaa8ab1f" + brace-expansion@^1.0.0: version "1.1.6" resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.6.tgz#7197d7eaa9b87e648390ea61fc66c84427420df9" From c69946647478be3639fa29db979b6443cb10f45f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9goire=20Delattre?= Date: Thu, 24 Nov 2016 13:22:05 +0100 Subject: [PATCH 8/9] Fix movie refresh button while fetching --- src/public/js/components/movies/list.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/public/js/components/movies/list.js b/src/public/js/components/movies/list.js index bbcda28..bd93cc7 100644 --- a/src/public/js/components/movies/list.js +++ b/src/public/js/components/movies/list.js @@ -75,6 +75,9 @@ class MovieButtons extends React.Component { } handleClick(e) { e.preventDefault(); + if (this.props.fetching) { + return + } this.props.getMovieDetails(this.props.movie.imdb_id); } render() { From 07a5e0caaac996c676ced8bcfd5871818aeb448e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9goire=20Delattre?= Date: Thu, 24 Nov 2016 13:36:39 +0100 Subject: [PATCH 9/9] Fix missing imdb link in movie buttons --- src/public/js/components/movies/list.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/public/js/components/movies/list.js b/src/public/js/components/movies/list.js index bd93cc7..0baccf0 100644 --- a/src/public/js/components/movies/list.js +++ b/src/public/js/components/movies/list.js @@ -81,7 +81,7 @@ class MovieButtons extends React.Component { this.props.getMovieDetails(this.props.movie.imdb_id); } render() { - const imdb_link = 'http://www.imdb.com/title/' + this.props.movie.imdb_id; + const imdb_link = `http://www.imdb.com/title/${this.props.movie.imdb_id}`; return (