Merge branch 'frontend' into 'master'
Add frontend, config file, and code clean See merge request !7
This commit is contained in:
commit
9ad2b54a45
1
.gitignore
vendored
1
.gitignore
vendored
@ -1,3 +1,4 @@
|
|||||||
static
|
static
|
||||||
node_modules
|
node_modules
|
||||||
tmp
|
tmp
|
||||||
|
config.yml
|
||||||
|
43
auth/auth.go
43
auth/auth.go
@ -30,28 +30,29 @@ type User interface {
|
|||||||
|
|
||||||
// Authorizer handle sesssion
|
// Authorizer handle sesssion
|
||||||
type Authorizer struct {
|
type Authorizer struct {
|
||||||
backend UserBackend
|
Params
|
||||||
cookiejar *sessions.CookieStore
|
}
|
||||||
cookieName string
|
|
||||||
peeper string
|
// Params for Authorizer creation
|
||||||
cost int
|
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,
|
// New Authorizer peeper is like a salt but not stored in database,
|
||||||
// cost is the bcrypt cost for hashing the password
|
// 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{
|
return &Authorizer{
|
||||||
backend: backend,
|
Params: params,
|
||||||
cookiejar: sessions.NewCookieStore([]byte(cookieKey)),
|
|
||||||
cookieName: cookieName,
|
|
||||||
peeper: peeper,
|
|
||||||
cost: cost,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// GenHash generates a new hash from a password
|
// GenHash generates a new hash from a password
|
||||||
func (a *Authorizer) GenHash(password string) (string, error) {
|
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 {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
@ -60,17 +61,17 @@ func (a *Authorizer) GenHash(password string) (string, error) {
|
|||||||
|
|
||||||
// Login cheks password and updates cookie info
|
// Login cheks password and updates cookie info
|
||||||
func (a *Authorizer) Login(rw http.ResponseWriter, req *http.Request, username, password string) error {
|
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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
u, err := a.backend.Get(username)
|
u, err := a.Backend.Get(username)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
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 {
|
if err != nil {
|
||||||
return ErrInvalidPassword
|
return ErrInvalidPassword
|
||||||
}
|
}
|
||||||
@ -78,7 +79,7 @@ func (a *Authorizer) Login(rw http.ResponseWriter, req *http.Request, username,
|
|||||||
cookie.Values["username"] = username
|
cookie.Values["username"] = username
|
||||||
|
|
||||||
// genereate secret
|
// genereate secret
|
||||||
b, err := bcrypt.GenerateFromPassword([]byte(u.GetHash()), a.cost)
|
b, err := bcrypt.GenerateFromPassword([]byte(u.GetHash()), a.Cost)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
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
|
// 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 {
|
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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
// genereate secret
|
// genereate secret
|
||||||
b, err := bcrypt.GenerateFromPassword([]byte(user.GetHash()), a.cost)
|
b, err := bcrypt.GenerateFromPassword([]byte(user.GetHash()), a.Cost)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -113,7 +114,7 @@ func (a *Authorizer) RegenSecret(user User, w http.ResponseWriter, r *http.Reque
|
|||||||
|
|
||||||
// Logout remove cookie info
|
// Logout remove cookie info
|
||||||
func (a *Authorizer) Logout(rw http.ResponseWriter, req *http.Request) error {
|
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 {
|
if err != nil {
|
||||||
return err
|
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
|
// CurrentUser returns the logged in username from session
|
||||||
func (a *Authorizer) CurrentUser(rw http.ResponseWriter, req *http.Request) (User, error) {
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -146,7 +147,7 @@ func (a *Authorizer) CurrentUser(rw http.ResponseWriter, req *http.Request) (Use
|
|||||||
return nil, ErrCorrupted
|
return nil, ErrCorrupted
|
||||||
}
|
}
|
||||||
|
|
||||||
u, err := a.backend.Get(username)
|
u, err := a.Backend.Get(username)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -14,7 +14,7 @@ import (
|
|||||||
|
|
||||||
const (
|
const (
|
||||||
peeper = "polp"
|
peeper = "polp"
|
||||||
key = "plop"
|
ckey = "plop"
|
||||||
cookieName = "auth"
|
cookieName = "auth"
|
||||||
cost = 10
|
cost = 10
|
||||||
|
|
||||||
@ -55,7 +55,7 @@ func getBackend() *Backend {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func login(w http.ResponseWriter, r *http.Request) {
|
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)
|
err := a.Login(w, r, username, password)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintf(w, "%s", err)
|
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) {
|
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)
|
err := a.Logout(w, r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintf(w, "%s", err)
|
fmt.Fprintf(w, "%s", err)
|
||||||
|
@ -43,7 +43,7 @@ func (m *MiddlewareRole) ServeHTTP(w http.ResponseWriter, r *http.Request, next
|
|||||||
user := GetCurrentUser(r)
|
user := GetCurrentUser(r)
|
||||||
|
|
||||||
if user == nil || !user.HasRole(m.role) {
|
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 {
|
if err != nil {
|
||||||
panic(err)
|
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) {
|
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 {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
8
config.yml.exemple
Normal file
8
config.yml.exemple
Normal file
@ -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
|
44
config/canape.go
Normal file
44
config/canape.go
Normal file
@ -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
|
||||||
|
}
|
46
main.go
46
main.go
@ -4,12 +4,14 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"gitlab.quimbo.fr/odwrtw/canape-sql/auth"
|
"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/movies"
|
||||||
"gitlab.quimbo.fr/odwrtw/canape-sql/users"
|
"gitlab.quimbo.fr/odwrtw/canape-sql/users"
|
||||||
"gitlab.quimbo.fr/odwrtw/canape-sql/web"
|
"gitlab.quimbo.fr/odwrtw/canape-sql/web"
|
||||||
|
|
||||||
"github.com/Sirupsen/logrus"
|
"github.com/Sirupsen/logrus"
|
||||||
"github.com/codegangsta/negroni"
|
"github.com/codegangsta/negroni"
|
||||||
|
"github.com/gorilla/sessions"
|
||||||
"github.com/jmoiron/sqlx"
|
"github.com/jmoiron/sqlx"
|
||||||
_ "github.com/lib/pq"
|
_ "github.com/lib/pq"
|
||||||
)
|
)
|
||||||
@ -24,24 +26,44 @@ func (b *UserBackend) Get(username string) (auth.User, error) {
|
|||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
log := logrus.NewEntry(logrus.New())
|
log := logrus.NewEntry(logrus.New())
|
||||||
|
cf, err := config.Load("config.yml")
|
||||||
pgdsn := "postgres://test:test@127.0.0.1:5432/dev?sslmode=disable"
|
|
||||||
db, err := sqlx.Connect("postgres", pgdsn)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Panic(err)
|
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)
|
authMiddleware := auth.NewMiddleware(env.Auth)
|
||||||
|
|
||||||
env.Handle("users.login", "/users/login", users.LoginHandler)
|
env.Handle("/users/login", users.LoginGETHandler).Name("users.login").Methods("GET")
|
||||||
env.Handle("users.logout", "/users/logout", users.LogoutHandler)
|
env.Handle("/users/login", users.LoginPOSTHandler).Name("users.login").Methods("POST")
|
||||||
env.HandleRole("users.details", "/users/details", users.DetailsHandler, users.UserRole)
|
env.Handle("/users/logout", users.LogoutHandler).Name("users.logout")
|
||||||
env.HandleRole("users.edit", "/users/edit", users.EditHandler, users.UserRole)
|
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")
|
err = env.SetLoginRoute("users.login")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -52,5 +74,5 @@ func main() {
|
|||||||
n.Use(authMiddleware)
|
n.Use(authMiddleware)
|
||||||
n.Use(negroni.NewStatic(http.Dir("./static")))
|
n.Use(negroni.NewStatic(http.Dir("./static")))
|
||||||
n.UseHandler(env.Router)
|
n.UseHandler(env.Router)
|
||||||
n.Run(":3000")
|
n.Run(":" + cf.Port)
|
||||||
}
|
}
|
||||||
|
7
movies/explorer.go
Normal file
7
movies/explorer.go
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
package movies
|
||||||
|
|
||||||
|
import "gitlab.quimbo.fr/odwrtw/canape-sql/web"
|
||||||
|
|
||||||
|
func updatePopular(env *web.Env, clientID string) error {
|
||||||
|
return nil
|
||||||
|
}
|
@ -2,11 +2,15 @@ package movies
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
|
||||||
"github.com/odwrtw/papi"
|
"github.com/odwrtw/papi"
|
||||||
"github.com/odwrtw/polochon/lib"
|
"github.com/odwrtw/polochon/lib"
|
||||||
"github.com/odwrtw/polochon/modules/mock"
|
"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/auth"
|
||||||
"gitlab.quimbo.fr/odwrtw/canape-sql/config"
|
"gitlab.quimbo.fr/odwrtw/canape-sql/config"
|
||||||
@ -14,6 +18,40 @@ import (
|
|||||||
"gitlab.quimbo.fr/odwrtw/canape-sql/web"
|
"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 {
|
func FromPolochon(env *web.Env, w http.ResponseWriter, r *http.Request) error {
|
||||||
|
|
||||||
v := auth.GetCurrentUser(r)
|
v := auth.GetCurrentUser(r)
|
||||||
@ -21,21 +59,19 @@ func FromPolochon(env *web.Env, w http.ResponseWriter, r *http.Request) error {
|
|||||||
if !ok {
|
if !ok {
|
||||||
return fmt.Errorf("invalid user type")
|
return fmt.Errorf("invalid user type")
|
||||||
}
|
}
|
||||||
var polochonConfig config.UserPolochon
|
web.SetData(r, "user", user)
|
||||||
err := user.GetConfig("polochon", &polochonConfig)
|
|
||||||
|
movies, err := getPolochonMovies(user)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
// Catch network error for accessing specified polochon address
|
||||||
|
if err == ErrPolochonUnavailable {
|
||||||
|
web.SetData(r, "error", "Invalid address")
|
||||||
|
return env.Rends(w, r, "movies/library")
|
||||||
}
|
}
|
||||||
|
|
||||||
client, err := papi.New(polochonConfig.URL, polochonConfig.Token)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
pmovies, err := client.MoviesByID()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
movies := []*Movie{}
|
|
||||||
|
|
||||||
//TODO use configurable detailer
|
//TODO use configurable detailer
|
||||||
// detailer, err := tmdb.New(&tmdb.Params{"57be344f84917b3f32c68a678f1482eb"})
|
// 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
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, pm := range pmovies {
|
for _, m := range movies {
|
||||||
m := New(pm.ImdbID)
|
|
||||||
m.Detailers = []polochon.Detailer{detailer}
|
m.Detailers = []polochon.Detailer{detailer}
|
||||||
err := m.GetDetails(env.Database, env.Log)
|
err := m.GetDetails(env.Database, env.Log)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
env.Log.Error(err)
|
env.Log.Error(err)
|
||||||
}
|
}
|
||||||
movies = append(movies, m)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
env.Log.Info(movies)
|
env.Log.Info(movies)
|
||||||
|
|
||||||
return nil
|
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")
|
||||||
|
}
|
||||||
|
@ -2,11 +2,13 @@ package movies
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
"github.com/Sirupsen/logrus"
|
"github.com/Sirupsen/logrus"
|
||||||
"github.com/jmoiron/sqlx"
|
"github.com/jmoiron/sqlx"
|
||||||
"github.com/odwrtw/polochon/lib"
|
"github.com/odwrtw/polochon/lib"
|
||||||
"gitlab.quimbo.fr/odwrtw/canape-sql/sqly"
|
"gitlab.quimbo.fr/odwrtw/canape-sql/sqly"
|
||||||
|
"gitlab.quimbo.fr/odwrtw/canape-sql/web"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -97,6 +99,13 @@ func (m *Movie) GetDetails(db *sqlx.DB, log *logrus.Entry) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Download poster
|
||||||
|
err = web.Download(m.Thumb, filepath.Join("./static/img/movies", m.ImdbID+".jpg"))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,3 +2,34 @@ var $ = require('jquery');
|
|||||||
global.jQuery = global.$ = $;
|
global.jQuery = global.$ = $;
|
||||||
|
|
||||||
var bootstrap = require('bootstrap');
|
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");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
@ -1 +1,12 @@
|
|||||||
@import "bootstrap/less/bootstrap.less";
|
@import "bootstrap/less/bootstrap.less";
|
||||||
|
|
||||||
|
body {
|
||||||
|
padding-top: 70px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.thumbnail-selected {
|
||||||
|
border-color:#f1c40f;
|
||||||
|
background-color:#f1c40f;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -10,7 +10,10 @@
|
|||||||
|
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
{{ template "navbar" $ }}
|
||||||
|
<div class="container-fluid">
|
||||||
{{ yield }}
|
{{ yield }}
|
||||||
|
</div>
|
||||||
<script src="/js/app.js"></script>
|
<script src="/js/app.js"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
32
templates/movies/library.tmpl
Normal file
32
templates/movies/library.tmpl
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
{{ if $.Data.error }}
|
||||||
|
{{ if eq $.Data.error "Invalid address"}}
|
||||||
|
<div class="alert alert-danger" role="alert">The polochon API adress specified in your configuration is invalid or unreachable, <a href="{{ URL "users.edit"}}">change it</a></div>
|
||||||
|
{{ end }}
|
||||||
|
|
||||||
|
{{ else }}
|
||||||
|
|
||||||
|
<div class="row" id="movie-library">
|
||||||
|
|
||||||
|
<div class="col-xs-5 col-md-8">
|
||||||
|
<div class="row">
|
||||||
|
{{ range $.Data.movies }}
|
||||||
|
<div class="col-xs-12 col-md-3">
|
||||||
|
<a href="#" class="thumbnail" data-imdbid="{{.ImdbID}}">
|
||||||
|
<img src="/img/movies/{{.ImdbID}}.jpg">
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
{{ end }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-xs-7 col-md-4">
|
||||||
|
{{ range $.Data.movies}}
|
||||||
|
<div id="{{.ImdbID}}-detail" class="hidden movie-detail affix">
|
||||||
|
<h1>{{.Title}}</h1>
|
||||||
|
</div>
|
||||||
|
{{ end}}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{ end }}
|
45
templates/navbar.tmpl
Normal file
45
templates/navbar.tmpl
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
<nav class="navbar navbar-inverse navbar-fixed-top">
|
||||||
|
<div class="container-fluid">
|
||||||
|
<!-- Brand and toggle get grouped for better mobile display -->
|
||||||
|
<div class="navbar-header">
|
||||||
|
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1" aria-expanded="false">
|
||||||
|
<span class="sr-only">Toggle navigation</span>
|
||||||
|
<span class="icon-bar"></span>
|
||||||
|
<span class="icon-bar"></span>
|
||||||
|
<span class="icon-bar"></span>
|
||||||
|
</button>
|
||||||
|
<a class="navbar-brand" href="#">Canape</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Collect the nav links, forms, and other content for toggling -->
|
||||||
|
<div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
|
||||||
|
<ul class="nav navbar-nav">
|
||||||
|
<li><p class="navbar-text">Library: </p></li>
|
||||||
|
<li {{ if eq $.Route "movies.polochon" }}class="active"{{ end }}>
|
||||||
|
<a href="{{ URL "movies.polochon" }}">Movies{{ if eq $.Route "movies.polochon" }}<span class="sr-only">(current)</span>{{ end }}</a>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li><a href="#">TV Shows</a></li>
|
||||||
|
|
||||||
|
<li><p class="navbar-text">Explore: </p></li>
|
||||||
|
<li><a href="{{ URL "movies.explore.popular"}}">Movies</a></li>
|
||||||
|
<li><a href="#">TV Shows</a></li>
|
||||||
|
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<ul class="nav navbar-nav navbar-right">
|
||||||
|
{{ if $.Data.user }}
|
||||||
|
<li class="dropdown">
|
||||||
|
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">{{$.Data.user.Name}} <span class="caret"></span></a>
|
||||||
|
<ul class="dropdown-menu">
|
||||||
|
<li><a href="{{ URL "users.edit"}}">Edit</a></li>
|
||||||
|
<li role="separator" class="divider"></li>
|
||||||
|
<li><a href="{{ URL "users.logout"}}">Logout</a></li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
{{ end}}
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
</div><!-- /.navbar-collapse -->
|
||||||
|
</div><!-- /.container-fluid -->
|
||||||
|
</nav>
|
@ -1,5 +1,5 @@
|
|||||||
<div class="container">
|
<div class="container">
|
||||||
<div id="user-details" class="content-fluid">
|
<div id="user-details" class="content-fluid">
|
||||||
|
|
||||||
<div class="col-md-6 col-md-offset-3 col-xs-12">
|
<div class="col-md-6 col-md-offset-3 col-xs-12">
|
||||||
<h2>{{ $.Data.user.Name }}'s informations</h2>
|
<h2>{{ $.Data.user.Name }}'s informations</h2>
|
||||||
@ -22,5 +22,5 @@
|
|||||||
<a class="btn btn-default pull-left" href="{{ URL "users.edit" }}">Edit</a>
|
<a class="btn btn-default pull-left" href="{{ URL "users.edit" }}">Edit</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -12,12 +12,11 @@ import (
|
|||||||
"gitlab.quimbo.fr/odwrtw/canape-sql/web"
|
"gitlab.quimbo.fr/odwrtw/canape-sql/web"
|
||||||
)
|
)
|
||||||
|
|
||||||
// LoginHandler login user
|
func LoginGETHandler(e *web.Env, w http.ResponseWriter, r *http.Request) error {
|
||||||
func LoginHandler(e *web.Env, w http.ResponseWriter, r *http.Request) error {
|
|
||||||
if r.Method == "GET" {
|
|
||||||
return e.Rends(w, r, "users/login")
|
return e.Rends(w, r, "users/login")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func LoginPOSTHandler(e *web.Env, w http.ResponseWriter, r *http.Request) error {
|
||||||
type loginForm struct {
|
type loginForm struct {
|
||||||
Username string
|
Username string
|
||||||
Password string
|
Password string
|
||||||
|
30
web/download.go
Normal file
30
web/download.go
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
package web
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Download used for downloading file
|
||||||
|
var Download = func(srcURL, dest string) error {
|
||||||
|
resp, err := http.Get(srcURL)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
// Create the file
|
||||||
|
file, err := os.Create(dest)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
// Write from the net to the file
|
||||||
|
_, err = io.Copy(file, resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
60
web/env.go
60
web/env.go
@ -4,6 +4,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"gitlab.quimbo.fr/odwrtw/canape-sql/auth"
|
"gitlab.quimbo.fr/odwrtw/canape-sql/auth"
|
||||||
|
"gitlab.quimbo.fr/odwrtw/canape-sql/config"
|
||||||
|
|
||||||
"github.com/Sirupsen/logrus"
|
"github.com/Sirupsen/logrus"
|
||||||
"github.com/codegangsta/negroni"
|
"github.com/codegangsta/negroni"
|
||||||
@ -19,16 +20,27 @@ type Env struct {
|
|||||||
Router *mux.Router
|
Router *mux.Router
|
||||||
Render *render.Render
|
Render *render.Render
|
||||||
Auth *auth.Authorizer
|
Auth *auth.Authorizer
|
||||||
|
Config *config.Config
|
||||||
loginRoute string
|
loginRoute string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// EnvParams represents parameters for NewEnv
|
||||||
|
type EnvParams struct {
|
||||||
|
Database *sqlx.DB
|
||||||
|
Auth *auth.Authorizer
|
||||||
|
Log *logrus.Entry
|
||||||
|
Config *config.Config
|
||||||
|
TemplatesDir string
|
||||||
|
}
|
||||||
|
|
||||||
// NewEnv returns a new *Env
|
// NewEnv returns a new *Env
|
||||||
func NewEnv(db *sqlx.DB, auth *auth.Authorizer, log *logrus.Entry, templatesDir string) *Env {
|
func NewEnv(p EnvParams) *Env {
|
||||||
e := &Env{
|
e := &Env{
|
||||||
Database: db,
|
Database: p.Database,
|
||||||
Log: log,
|
Log: p.Log,
|
||||||
Router: mux.NewRouter(),
|
Router: mux.NewRouter(),
|
||||||
Auth: auth,
|
Auth: p.Auth,
|
||||||
|
Config: p.Config,
|
||||||
}
|
}
|
||||||
|
|
||||||
tmplFuncs = append(tmplFuncs, map[string]interface{}{
|
tmplFuncs = append(tmplFuncs, map[string]interface{}{
|
||||||
@ -38,7 +50,7 @@ func NewEnv(db *sqlx.DB, auth *auth.Authorizer, log *logrus.Entry, templatesDir
|
|||||||
})
|
})
|
||||||
|
|
||||||
e.Render = render.New(render.Options{
|
e.Render = render.New(render.Options{
|
||||||
Directory: templatesDir,
|
Directory: p.TemplatesDir,
|
||||||
Layout: "layout",
|
Layout: "layout",
|
||||||
Funcs: tmplFuncs,
|
Funcs: tmplFuncs,
|
||||||
DisableHTTPErrorRendering: true,
|
DisableHTTPErrorRendering: true,
|
||||||
@ -47,17 +59,37 @@ func NewEnv(db *sqlx.DB, auth *auth.Authorizer, log *logrus.Entry, templatesDir
|
|||||||
return e
|
return e
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle add route
|
type Route struct {
|
||||||
func (e *Env) Handle(name, route string, H HandlerFunc) {
|
env *Env
|
||||||
e.Router.Handle(route, Handler{e, H}).Name(name)
|
mRoute *mux.Route
|
||||||
}
|
}
|
||||||
|
|
||||||
// HandleRole add route and take care of user's role
|
func (r *Route) Name(name string) *Route {
|
||||||
func (e *Env) HandleRole(name, route string, H HandlerFunc, role string) {
|
r.mRoute.Name(name)
|
||||||
e.Router.Handle(route, negroni.New(
|
return r
|
||||||
auth.NewMiddlewareRole(e.Auth, e.GetLoginRouteGetter(), role),
|
}
|
||||||
negroni.Wrap(Handler{e, H}),
|
|
||||||
)).Name(name)
|
func (r *Route) Methods(methods ...string) *Route {
|
||||||
|
r.mRoute.Methods(methods...)
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Route) WithRole(role string) *Route {
|
||||||
|
handler := r.mRoute.GetHandler()
|
||||||
|
newHandler := negroni.New(
|
||||||
|
auth.NewMiddlewareRole(r.env.Auth, r.env.GetLoginRouteGetter(), role),
|
||||||
|
negroni.Wrap(handler))
|
||||||
|
r.mRoute.Handler(newHandler)
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle add route
|
||||||
|
func (e *Env) Handle(route string, H HandlerFunc) *Route {
|
||||||
|
mRoute := e.Router.Handle(route, Handler{e, H})
|
||||||
|
return &Route{
|
||||||
|
env: e,
|
||||||
|
mRoute: mRoute,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetURL returns URL string from URL name and params
|
// GetURL returns URL string from URL name and params
|
||||||
|
@ -26,7 +26,7 @@ func AddTmplFunc(name string, f interface{}) error {
|
|||||||
|
|
||||||
// TemplateData represents object passed to template renderer
|
// TemplateData represents object passed to template renderer
|
||||||
type TemplateData struct {
|
type TemplateData struct {
|
||||||
RouteName string
|
Route string
|
||||||
Data map[string]interface{}
|
Data map[string]interface{}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -34,13 +34,13 @@ type TemplateData struct {
|
|||||||
func (e *Env) Rends(w http.ResponseWriter, r *http.Request, template string) error {
|
func (e *Env) Rends(w http.ResponseWriter, r *http.Request, template string) error {
|
||||||
if r.Header.Get("Accept") == "application/json" {
|
if r.Header.Get("Accept") == "application/json" {
|
||||||
return e.Render.JSON(w, http.StatusOK, TemplateData{
|
return e.Render.JSON(w, http.StatusOK, TemplateData{
|
||||||
RouteName: mux.CurrentRoute(r).GetName(),
|
Route: mux.CurrentRoute(r).GetName(),
|
||||||
Data: GetAllData(r),
|
Data: GetAllData(r),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
return e.Render.HTML(w, http.StatusOK, template, TemplateData{
|
return e.Render.HTML(w, http.StatusOK, template, TemplateData{
|
||||||
RouteName: mux.CurrentRoute(r).GetName(),
|
Route: mux.CurrentRoute(r).GetName(),
|
||||||
Data: GetAllData(r),
|
Data: GetAllData(r),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user