diff --git a/.gitignore b/.gitignore index c27f732..616b799 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ static node_modules tmp +config.yml diff --git a/auth/auth.go b/auth/auth.go index 41f2b8b..b081a1f 100644 --- a/auth/auth.go +++ b/auth/auth.go @@ -30,28 +30,29 @@ type User interface { // Authorizer handle sesssion type Authorizer struct { - backend UserBackend - cookiejar *sessions.CookieStore - cookieName string - peeper string - cost int + Params +} + +// Params for Authorizer creation +type Params struct { + Backend UserBackend + Cookiejar *sessions.CookieStore + CookieName string + Peeper string + Cost int } // New Authorizer peeper is like a salt but not stored in database, // cost is the bcrypt cost for hashing the password -func New(backend UserBackend, peeper, cookieName, cookieKey string, cost int) *Authorizer { +func New(params Params) *Authorizer { return &Authorizer{ - backend: backend, - cookiejar: sessions.NewCookieStore([]byte(cookieKey)), - cookieName: cookieName, - peeper: peeper, - cost: cost, + Params: params, } } // GenHash generates a new hash from a password func (a *Authorizer) GenHash(password string) (string, error) { - b, err := bcrypt.GenerateFromPassword([]byte(password+a.peeper), a.cost) + b, err := bcrypt.GenerateFromPassword([]byte(password+a.Peeper), a.Cost) if err != nil { return "", err } @@ -60,17 +61,17 @@ func (a *Authorizer) GenHash(password string) (string, error) { // Login cheks password and updates cookie info func (a *Authorizer) Login(rw http.ResponseWriter, req *http.Request, username, password string) error { - cookie, err := a.cookiejar.Get(req, a.cookieName) + cookie, err := a.Cookiejar.Get(req, a.CookieName) if err != nil { return err } - u, err := a.backend.Get(username) + u, err := a.Backend.Get(username) if err != nil { return err } - err = bcrypt.CompareHashAndPassword([]byte(u.GetHash()), []byte(password+a.peeper)) + err = bcrypt.CompareHashAndPassword([]byte(u.GetHash()), []byte(password+a.Peeper)) if err != nil { return ErrInvalidPassword } @@ -78,7 +79,7 @@ func (a *Authorizer) Login(rw http.ResponseWriter, req *http.Request, username, cookie.Values["username"] = username // genereate secret - b, err := bcrypt.GenerateFromPassword([]byte(u.GetHash()), a.cost) + b, err := bcrypt.GenerateFromPassword([]byte(u.GetHash()), a.Cost) if err != nil { return err } @@ -93,12 +94,12 @@ func (a *Authorizer) Login(rw http.ResponseWriter, req *http.Request, username, // RegenSecret update secret in cookie with user info, usefull when updating password func (a *Authorizer) RegenSecret(user User, w http.ResponseWriter, r *http.Request) error { - cookie, err := a.cookiejar.Get(r, a.cookieName) + cookie, err := a.Cookiejar.Get(r, a.CookieName) if err != nil { return err } // genereate secret - b, err := bcrypt.GenerateFromPassword([]byte(user.GetHash()), a.cost) + b, err := bcrypt.GenerateFromPassword([]byte(user.GetHash()), a.Cost) if err != nil { return err } @@ -113,7 +114,7 @@ func (a *Authorizer) RegenSecret(user User, w http.ResponseWriter, r *http.Reque // Logout remove cookie info func (a *Authorizer) Logout(rw http.ResponseWriter, req *http.Request) error { - cookie, err := a.cookiejar.Get(req, a.cookieName) + cookie, err := a.Cookiejar.Get(req, a.CookieName) if err != nil { return err } @@ -129,7 +130,7 @@ func (a *Authorizer) Logout(rw http.ResponseWriter, req *http.Request) error { // CurrentUser returns the logged in username from session func (a *Authorizer) CurrentUser(rw http.ResponseWriter, req *http.Request) (User, error) { - cookie, err := a.cookiejar.Get(req, a.cookieName) + cookie, err := a.Cookiejar.Get(req, a.CookieName) if err != nil { return nil, err } @@ -146,7 +147,7 @@ func (a *Authorizer) CurrentUser(rw http.ResponseWriter, req *http.Request) (Use return nil, ErrCorrupted } - u, err := a.backend.Get(username) + u, err := a.Backend.Get(username) if err != nil { return nil, err } diff --git a/auth/auth_test.go b/auth/auth_test.go index 169ebaa..6a5d08e 100644 --- a/auth/auth_test.go +++ b/auth/auth_test.go @@ -14,7 +14,7 @@ import ( const ( peeper = "polp" - key = "plop" + ckey = "plop" cookieName = "auth" cost = 10 @@ -55,7 +55,7 @@ func getBackend() *Backend { } func login(w http.ResponseWriter, r *http.Request) { - a := New(getBackend(), peeper, cookieName, key, cost) + a := New(getBackend(), peeper, cookieName, ckey, cost) err := a.Login(w, r, username, password) if err != nil { fmt.Fprintf(w, "%s", err) @@ -66,7 +66,7 @@ func login(w http.ResponseWriter, r *http.Request) { } func logout(w http.ResponseWriter, r *http.Request) { - a := New(getBackend(), peeper, cookieName, key, cost) + a := New(getBackend(), peeper, cookieName, ckey, cost) err := a.Logout(w, r) if err != nil { fmt.Fprintf(w, "%s", err) diff --git a/auth/middleware.go b/auth/middleware.go index a52a29a..71af311 100644 --- a/auth/middleware.go +++ b/auth/middleware.go @@ -43,7 +43,7 @@ func (m *MiddlewareRole) ServeHTTP(w http.ResponseWriter, r *http.Request, next user := GetCurrentUser(r) if user == nil || !user.HasRole(m.role) { - cookie, err := m.authorizer.cookiejar.Get(r, "rlogin") + cookie, err := m.authorizer.Cookiejar.Get(r, "rlogin") if err != nil { panic(err) } @@ -60,7 +60,7 @@ func (m *MiddlewareRole) ServeHTTP(w http.ResponseWriter, r *http.Request, next } func GetPostLoginRedirect(a *Authorizer, w http.ResponseWriter, r *http.Request) (string, error) { - cookie, err := a.cookiejar.Get(r, "rlogin") + cookie, err := a.Cookiejar.Get(r, "rlogin") if err != nil { return "", err } diff --git a/config.yml.exemple b/config.yml.exemple new file mode 100644 index 0000000..05e2870 --- /dev/null +++ b/config.yml.exemple @@ -0,0 +1,8 @@ +authorizer: + cookie_name: auth + cookie_key: mysecretkey + peeper: peeper + cost: 10 +pgdsn: postgres://test:test@127.0.0.1:5432/dev?sslmode=disable +trakttv_client_id: my_trakttv_client_id +listen_port: 3000 diff --git a/config/canape.go b/config/canape.go new file mode 100644 index 0000000..72b937a --- /dev/null +++ b/config/canape.go @@ -0,0 +1,44 @@ +package config + +import ( + "io/ioutil" + "os" + + "gopkg.in/yaml.v2" +) + +type Config struct { + Authorizer AuthorizerConfig `yaml:"authorizer"` + PGDSN string `yaml:"pgdsn"` + Port string `yaml:"listen_port"` + TraktTVClientID string `yaml:"trakttv_client_id"` +} + +type AuthorizerConfig struct { + CookieName string `yaml:"cookie_name"` + Key string `yaml:"cookie_key"` + Peeper string `yaml:"peeper"` + Cost int `yaml:"cost"` +} + +func Load(path string) (*Config, error) { + file, err := os.Open(path) + if err != nil { + return nil, err + } + defer file.Close() + + cf := &Config{} + + b, err := ioutil.ReadAll(file) + if err != nil { + return nil, err + } + + err = yaml.Unmarshal(b, cf) + if err != nil { + return nil, err + } + + return cf, nil +} diff --git a/main.go b/main.go index 6cc9250..44b1ff2 100644 --- a/main.go +++ b/main.go @@ -4,12 +4,14 @@ import ( "net/http" "gitlab.quimbo.fr/odwrtw/canape-sql/auth" + "gitlab.quimbo.fr/odwrtw/canape-sql/config" "gitlab.quimbo.fr/odwrtw/canape-sql/movies" "gitlab.quimbo.fr/odwrtw/canape-sql/users" "gitlab.quimbo.fr/odwrtw/canape-sql/web" "github.com/Sirupsen/logrus" "github.com/codegangsta/negroni" + "github.com/gorilla/sessions" "github.com/jmoiron/sqlx" _ "github.com/lib/pq" ) @@ -24,24 +26,44 @@ func (b *UserBackend) Get(username string) (auth.User, error) { func main() { log := logrus.NewEntry(logrus.New()) - - pgdsn := "postgres://test:test@127.0.0.1:5432/dev?sslmode=disable" - db, err := sqlx.Connect("postgres", pgdsn) + cf, err := config.Load("config.yml") if err != nil { log.Panic(err) } - uBackend := &UserBackend{db} - authorizer := auth.New(uBackend, "peeper", "cookieName", "cookieKey", 10) - env := web.NewEnv(db, authorizer, log, "./templates") + db, err := sqlx.Connect("postgres", cf.PGDSN) + if err != nil { + log.Panic(err) + } + uBackend := &UserBackend{Database: db} + + authParams := auth.Params{ + Backend: uBackend, + Peeper: cf.Authorizer.Peeper, + CookieName: cf.Authorizer.CookieName, + Cookiejar: sessions.NewCookieStore([]byte(cf.Authorizer.Key)), + Cost: cf.Authorizer.Cost, + } + authorizer := auth.New(authParams) + + env := web.NewEnv(web.EnvParams{ + Database: db, + Auth: authorizer, + Log: log, + Config: cf, + TemplatesDir: "./templates", + }) + authMiddleware := auth.NewMiddleware(env.Auth) - env.Handle("users.login", "/users/login", users.LoginHandler) - env.Handle("users.logout", "/users/logout", users.LogoutHandler) - env.HandleRole("users.details", "/users/details", users.DetailsHandler, users.UserRole) - env.HandleRole("users.edit", "/users/edit", users.EditHandler, users.UserRole) + env.Handle("/users/login", users.LoginGETHandler).Name("users.login").Methods("GET") + env.Handle("/users/login", users.LoginPOSTHandler).Name("users.login").Methods("POST") + env.Handle("/users/logout", users.LogoutHandler).Name("users.logout") + env.Handle("/users/details", users.DetailsHandler).Name("users.details").WithRole(users.UserRole) + env.Handle("/users/edit", users.EditHandler).Name("users.edit").WithRole(users.UserRole) - env.HandleRole("movies.polochon", "/movies/polochon", movies.FromPolochon, users.UserRole) + env.Handle("/movies/polochon", movies.FromPolochon).Name("movies.polochon").WithRole(users.UserRole) + env.Handle("/movies/explore/popular", movies.ExplorePopular).Name("movies.explore.popular").WithRole(users.UserRole) err = env.SetLoginRoute("users.login") if err != nil { @@ -52,5 +74,5 @@ func main() { n.Use(authMiddleware) n.Use(negroni.NewStatic(http.Dir("./static"))) n.UseHandler(env.Router) - n.Run(":3000") + n.Run(":" + cf.Port) } diff --git a/movies/explorer.go b/movies/explorer.go new file mode 100644 index 0000000..208e590 --- /dev/null +++ b/movies/explorer.go @@ -0,0 +1,7 @@ +package movies + +import "gitlab.quimbo.fr/odwrtw/canape-sql/web" + +func updatePopular(env *web.Env, clientID string) error { + return nil +} diff --git a/movies/handlers.go b/movies/handlers.go index bbc275b..f5afafe 100644 --- a/movies/handlers.go +++ b/movies/handlers.go @@ -2,11 +2,15 @@ package movies import ( "fmt" + "net" "net/http" + "net/url" "github.com/odwrtw/papi" "github.com/odwrtw/polochon/lib" "github.com/odwrtw/polochon/modules/mock" + traktdetailer "github.com/odwrtw/polochon/modules/trakttv" + "github.com/odwrtw/trakttv" "gitlab.quimbo.fr/odwrtw/canape-sql/auth" "gitlab.quimbo.fr/odwrtw/canape-sql/config" @@ -14,6 +18,40 @@ import ( "gitlab.quimbo.fr/odwrtw/canape-sql/web" ) +var ErrPolochonUnavailable = fmt.Errorf("Invalid polochon address") + +func getPolochonMovies(user *users.User) ([]*Movie, error) { + movies := []*Movie{} + + var polochonConfig config.UserPolochon + err := user.GetConfig("polochon", &polochonConfig) + if err != nil { + return movies, err + } + + client, err := papi.New(polochonConfig.URL, polochonConfig.Token) + if err != nil { + return movies, err + } + pmovies, err := client.MoviesByID() + if err != nil { + // Catch network error for accessing specified polochon address + if uerr, ok := err.(*url.Error); ok { + if nerr, ok := uerr.Err.(*net.OpError); ok { + if nerr.Op == "dial" { + return movies, ErrPolochonUnavailable + } + + } + } + return movies, err + } + for _, pmovie := range pmovies { + movies = append(movies, New(pmovie.ImdbID)) + } + return movies, nil +} + func FromPolochon(env *web.Env, w http.ResponseWriter, r *http.Request) error { v := auth.GetCurrentUser(r) @@ -21,21 +59,19 @@ func FromPolochon(env *web.Env, w http.ResponseWriter, r *http.Request) error { if !ok { return fmt.Errorf("invalid user type") } - var polochonConfig config.UserPolochon - err := user.GetConfig("polochon", &polochonConfig) - if err != nil { - return err - } + web.SetData(r, "user", user) + + movies, err := getPolochonMovies(user) - client, err := papi.New(polochonConfig.URL, polochonConfig.Token) if err != nil { + // Catch network error for accessing specified polochon address + if err == ErrPolochonUnavailable { + web.SetData(r, "error", "Invalid address") + return env.Rends(w, r, "movies/library") + } + return err } - pmovies, err := client.MoviesByID() - if err != nil { - return err - } - movies := []*Movie{} //TODO use configurable detailer // detailer, err := tmdb.New(&tmdb.Params{"57be344f84917b3f32c68a678f1482eb"}) @@ -44,17 +80,55 @@ func FromPolochon(env *web.Env, w http.ResponseWriter, r *http.Request) error { return err } - for _, pm := range pmovies { - m := New(pm.ImdbID) + for _, m := range movies { m.Detailers = []polochon.Detailer{detailer} err := m.GetDetails(env.Database, env.Log) if err != nil { env.Log.Error(err) } - movies = append(movies, m) } env.Log.Info(movies) return nil } + +func ExplorePopular(env *web.Env, w http.ResponseWriter, r *http.Request) error { + + queryOption := trakttv.QueryOption{ + ExtendedInfos: []trakttv.ExtendedInfo{ + trakttv.ExtendedInfoMin, + }, + Pagination: trakttv.Pagination{ + Page: 1, + Limit: 20, + }, + } + trakt := trakttv.New(env.Config.TraktTVClientID) + trakt.Endpoint = trakttv.ProductionEndpoint + tmovies, err := trakt.PopularMovies(queryOption) + if err != nil { + return err + } + + detailer, err := traktdetailer.New(&traktdetailer.Params{env.Config.TraktTVClientID}) + if err != nil { + return err + } + + movies := []*Movie{} + ids := []string{} + for _, m := range tmovies { + movie := New(m.IDs.ImDB) + movie.Detailers = []polochon.Detailer{detailer} + err := movie.GetDetails(env.Database, env.Log) + if err != nil { + env.Log.Error(err) + continue + } + movies = append(movies, movie) + ids = append(ids, m.IDs.ImDB) + } + web.SetData(r, "movies", movies) + return env.Rends(w, r, "movies/library") +} diff --git a/movies/movies.go b/movies/movies.go index 3b97216..7fa5178 100644 --- a/movies/movies.go +++ b/movies/movies.go @@ -2,11 +2,13 @@ package movies import ( "fmt" + "path/filepath" "github.com/Sirupsen/logrus" "github.com/jmoiron/sqlx" "github.com/odwrtw/polochon/lib" "gitlab.quimbo.fr/odwrtw/canape-sql/sqly" + "gitlab.quimbo.fr/odwrtw/canape-sql/web" ) const ( @@ -97,6 +99,13 @@ func (m *Movie) GetDetails(db *sqlx.DB, log *logrus.Entry) error { if err != nil { return err } + + // Download poster + err = web.Download(m.Thumb, filepath.Join("./static/img/movies", m.ImdbID+".jpg")) + if err != nil { + return err + } + return nil } diff --git a/src/js/app.js b/src/js/app.js index 650139d..db07378 100644 --- a/src/js/app.js +++ b/src/js/app.js @@ -2,3 +2,34 @@ var $ = require('jquery'); global.jQuery = global.$ = $; var bootstrap = require('bootstrap'); + +if($('#movie-library').length >0 ){ + listSelector(); +} + +// Help select elements form the list views +function listSelector() { + $first = $(".thumbnail").first(); + $first.addClass('thumbnail-selected'); + + // Get the detail + var $detail = $("#"+$first.data("imdbid")+"-detail" ); + // Show it + $detail.removeClass("hidden"); + $detail.addClass("show"); + + $(".thumbnail").click(function(e) { + e.preventDefault(); + // Hide previous details + $(".movie-detail.show" ).addClass("hidden").removeClass("show"); + + // Change border on selected item + $('.thumbnail-selected').removeClass('thumbnail-selected'); + $(this).addClass('thumbnail-selected'); + + // Get the detail + var $detail = $("#"+$(this).data("imdbid")+"-detail" ); + // Show it + $detail.removeClass("hidden").addClass("show"); + }); +} diff --git a/src/less/app.less b/src/less/app.less index b22b632..50ebdda 100644 --- a/src/less/app.less +++ b/src/less/app.less @@ -1 +1,12 @@ @import "bootstrap/less/bootstrap.less"; + +body { + padding-top: 70px; +} + +.thumbnail-selected { + border-color:#f1c40f; + background-color:#f1c40f; +} + + diff --git a/templates/layout.tmpl b/templates/layout.tmpl index 4ce3170..8d38326 100644 --- a/templates/layout.tmpl +++ b/templates/layout.tmpl @@ -10,7 +10,10 @@
+ {{ template "navbar" $ }} +