Merge branch 'frontend' into 'master'

Add frontend, config file, and code clean



See merge request !7
This commit is contained in:
Nicolas Duhamel 2016-06-03 21:08:43 +00:00
commit 9ad2b54a45
20 changed files with 447 additions and 98 deletions

1
.gitignore vendored
View File

@ -1,3 +1,4 @@
static static
node_modules node_modules
tmp tmp
config.yml

View File

@ -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
} }

View File

@ -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)

View File

@ -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
View 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
View 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
View File

@ -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
View 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
}

View File

@ -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")
}

View File

@ -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
} }

View File

@ -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");
});
}

View File

@ -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;
}

View File

@ -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>

View 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
View 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>

View File

@ -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>

View File

@ -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
View 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
}

View File

@ -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

View File

@ -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),
}) })
} }