Sessions with gorilla

This commit is contained in:
Las Zenow 2012-08-18 02:06:43 +02:00
parent 44114bd91d
commit c513344169
15 changed files with 251 additions and 47 deletions

1
.gitignore vendored
View file

@ -7,3 +7,4 @@ upload/upload
upload/books
upload/cover
upload/new
adduser/adduser

30
admin.go Normal file
View file

@ -0,0 +1,30 @@
package main
import (
"labix.org/v2/mgo"
"labix.org/v2/mgo/bson"
"net/http"
"os"
)
func deleteHandler(coll *mgo.Collection) func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
if SessionUser(r) == "" {
http.NotFound(w, r)
return
}
var id bson.ObjectId = bson.ObjectIdHex(r.URL.Path[len("/delete/"):])
var book Book
if coll.Find(bson.M{"_id": id}).One(&book) != nil {
http.NotFound(w, r)
return
}
os.RemoveAll(book.Path)
os.RemoveAll(book.Cover[1:])
os.RemoveAll(book.CoverSmall[1:])
coll.Remove(bson.M{"_id": id})
http.Redirect(w, r, "/", 307)
//TODO: notify deleted
}
}

View file

@ -1,6 +1,7 @@
package main
type Book struct {
Id string `bson:"_id"`
Title string
Author []string
Contributor string

View file

@ -31,7 +31,7 @@ func buildQuery(q string) bson.M {
}
type searchData struct {
Search string
S Status
Found int
Books []Book
Page int
@ -59,7 +59,8 @@ func searchHandler(coll *mgo.Collection) func(http.ResponseWriter, *http.Request
}
var data searchData
data.Search = req
data.S.User = SessionUser(r)
data.S.Search = req
data.Found = len(res)
if len(res) > ITEMS_PAGE*(page+1) {
data.Books = res[ITEMS_PAGE*page : ITEMS_PAGE*(page+1)]

43
session.go Normal file
View file

@ -0,0 +1,43 @@
package main
import (
"net/http"
"code.google.com/p/gorilla/sessions"
"code.google.com/p/gorilla/securecookie"
)
var sesStore = sessions.NewCookieStore(securecookie.GenerateRandomKey(64))
func CreateSession(user string, w http.ResponseWriter, r *http.Request) {
session, _ := sesStore.Get(r, "admin")
session.Values["user"] = user
session.Save(r, w)
}
func SessionUser(r *http.Request) string {
session, err := sesStore.New(r, "admin")
if err != nil {
return ""
}
if session.IsNew {
return ""
}
user, ok := session.Values["user"].(string)
if !ok {
return ""
}
return user
}
func LogOut(w http.ResponseWriter, r *http.Request) {
session, err := sesStore.Get(r, "admin")
if err != nil {
return
}
if session.IsNew {
return
}
session.Values["user"] = ""
session.Options.MaxAge = -1
session.Save(r, w)
}

View file

@ -9,6 +9,14 @@ const (
TEMPLATE_DIR = "templates/"
)
type Status struct {
Search string
User string
Home bool
About bool
Upload bool
}
func loadTemplate(w http.ResponseWriter, tmpl string, data interface{}) {
// TODO: when finish devel conver to global:
var templates = template.Must(template.ParseFiles(TEMPLATE_DIR+"header.html",

View file

@ -1,4 +1,4 @@
{{template "header.html"}}
{{template "header.html" .S}}
<p>The <strong>Imperial Library of Trantor</strong> (also known as <em>Galactic Library</em>) is a repository of ebooks on ePub format.</p>

View file

@ -1,10 +1,27 @@
{{template "header.html"}}
{{template "header.html" .S}}
<div class="row">
{{$user := .S.User}}
{{with .Book}}
<script>
function delBook(){
var div = document.getElementById('delete');
div.innerHTML =
'<div class="alert alert-error fade in"> \
<a class="close" data-dismiss="alert">×</a> \
<h4 class="alert-heading">Do you really want to delete it?</h4> \
<p>Remove a book is permanent, you won\'t be able to get it back</p> \
<a class="btn btn-danger" href="/delete/{{.Id}}">Remove it</a> \
<a class="btn" href="#" data-dismiss="alert">Cancel</a> \
</div>';
}
</script>
<div id="delete"></div>
<header class="row">
<div class="span8 offset4">
<h1>{{.Title}}</h1>
</div>
</div>
</header>
<div class="row">
{{if .Cover}}
<div class="span4">
@ -15,7 +32,7 @@
<div class="span8">
<div class="row"><p></p></div>
<div class="row">
<div class="span4">
<div class="span6">
<ul class="unstyled">
{{if .Author}}<li><strong>Author:</strong> {{range .Author}}<a href="/search/?q=author:{{.}}">{{.}}</a>, {{end}}</li>{{end}}
{{if .Publisher}}<li><strong>Publisher:</strong> <a href="/search/?q=publisher:{{.Publisher}}">{{.Publisher}}</a></li>{{end}}
@ -24,8 +41,19 @@
{{if .Lang}}<li><strong>Lang:</strong> {{range .Lang}}<a href="/search/?q=lang:{{.}}">{{.}}</a> {{end}}</li>{{end}}
</ul>
</div>
<div class="span4">
<div class="span2">
{{if $user}}
<div class="row">
<div class="btn-group pull-right">
<a href="/edit/{{.Id}}" class="btn btn-primary"><i class="icon-pencil"></i> Edit</a>
<a href="#" onClick="delBook();" class="btn btn-danger"><i class="icon-trash"></i> Delete</a>
</div>
</div>
<div class="row"><p></p></div>
{{end}}
<div class="row">
<p><a href="/{{.Path}}" class="btn btn-large btn-inverse pull-right"><i class="icon-download-alt icon-white"></i> download</a></p>
</div>
</div>
</div>
@ -35,7 +63,7 @@
</div>
</div>
</div>
</div>
{{end}}
{{template "footer.html"}}

View file

@ -7,17 +7,8 @@
<!-- Placed at the end of the document so the pages load faster -->
<script src="/js/jquery.js"></script>
<script src="/js/bootstrap-transition.js"></script>
<script src="/js/bootstrap-alert.js"></script>
<script src="/js/bootstrap-modal.js"></script>
<script src="/js/bootstrap-dropdown.js"></script>
<script src="/js/bootstrap-scrollspy.js"></script>
<script src="/js/bootstrap-tab.js"></script>
<script src="/js/bootstrap-tooltip.js"></script>
<script src="/js/bootstrap-popover.js"></script>
<script src="/js/bootstrap-button.js"></script>
<script src="/js/bootstrap-collapse.js"></script>
<script src="/js/bootstrap-carousel.js"></script>
<script src="/js/bootstrap-typeahead.js"></script>
<script src="/js/bootstrap-alert.js"></script>
</body>
</html>

View file

@ -9,24 +9,72 @@
<title>Imperial Library of Trantor</title>
</head>
<body>
<!-- login screen -->
<div class="modal hide" id="login">
<div class="modal-header">
<a type="button" class="close" data-dismiss="modal">×</a>
<h3>Log In</h3>
</div>
<form class="form-horizontal" method="POST" action="/login/" enctype="application/x-www-form-urlencoded">
<div class="modal-body">
<fieldset>
<div class="control-group">
<div class="controls">
<div class="input-prepend">
<div class="add-on"><i class="icon-user"></i></div>
<input style="height:28px;" type="text" placeholder="Username" size="16" name="user" autofocus="autofocus">
</div>
</div>
</div>
<div class="control-group">
<div class="controls">
<div class="input-prepend">
<div class="add-on"><i class="icon-lock"></i></div>
<input style="height:28px;" type="password" placeholder="Password" size="16" name="pass">
</div>
</div>
</div>
</fieldset>
</div>
<div class="modal-footer">
<fieldset>
<a href="#" class="btn" data-dismiss="modal">Cancel</a>
<input class="btn btn-primary" type="submit" name="submit" value="Log In"/>
</fieldset>
</div>
</form>
</div>
<div class="navbar">
<div class="navbar-inner">
<div class="container">
<a class="btn btn-navbar" data-toggle="collapse" data-target=".nav-collapse">
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</a>
<a class="brand" href="/">Imperial Library</a>
<div class="nav-collapse">
<ul class="nav">
<li><a href="/">Home</a></li>
<li><a href="/about/">About</a></li>
<li><a href="/upload/">Upload your epub</a></li>
<li {{if .Home}}class="active"{{end}}><a href="/">Home</a></li>
<li {{if .About}}class="active"{{end}}><a href="/about/">About</a></li>
<li {{if .Upload}}class="active"{{end}}><a href="/upload/">Upload your epub</a></li>
</ul>
<ul class="nav pull-right">
<li class="divider-vertical"></li>
{{if .User}}
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown">
<i class="icon-user icon-white"></i> {{.User}}<b class="caret"></b>
</a>
<ul class="dropdown-menu">
<li class="divider"></li>
<li><a href="/logout/"><i class="icon-off"></i> Log Out</a></li>
</ul>
</li>
{{else}}
<li><a data-toggle="modal" href="#login"><i class="icon-share-alt icon-white"></i></a></li>
{{end}}
</ul>
</div><!--/.nav-collapse -->
<form class="navbar-search pull-right" action="/search/">
<input type="search" class="search-query span3" name="q" {{if .}}value="{{.}}"{{else}}placeholder="Search"{{end}} />
<input type="search" class="search-query span3" name="q" {{if .Search}}value="{{.Search}}"{{else}}placeholder="Search"{{end}} />
</form>
</div>
</div>

View file

@ -1,10 +1,4 @@
{{template "header.html"}}
<!--<p><img src="/img/library.jpg" alt="library" /></p>
<form action="/search/">
<input type="search" name="q" />
<input type="submit" value="Search">
</form>-->
{{template "header.html" .S}}
<div class="row">
<div class="span8">

View file

@ -1,4 +1,4 @@
{{template "header.html" .Search}}
{{template "header.html" .S}}
<p class="centered">Found {{.Found}} books. Page {{.Page}}</p>
@ -18,10 +18,10 @@
{{with .Books}}
{{range .}}
<div class="row">
<div class="span2">
<div class="span1">
<p class="pull-right"><a href="/book/{{.Title}}">{{if .CoverSmall}}<img src="{{.CoverSmall}}" alt="{{.Title}}" />{{end}}</a></p>
</div>
<div class="span10">
<div class="span11">
<p class="well"><a href="/book/{{.Title}}"><strong>{{.Title}}</strong></a><br />
{{range .Author}}{{.}}, {{end}}</p>
</div>

View file

@ -1,6 +1,6 @@
{{template "header.html"}}
{{template "header.html" .S}}
{{if .}}<p>{{.}}</p>{{end}}
{{if .Msg}}<p>{{.Msg}}</p>{{end}}
<form method="POST" enctype="multipart/form-data">
<input accept="application/epub+zip" type="file" name="epub" />

View file

@ -1,6 +1,7 @@
package main
import (
"crypto/md5"
"labix.org/v2/mgo"
"labix.org/v2/mgo/bson"
"net/http"
@ -10,17 +11,60 @@ const (
IP = "127.0.0.1"
DB_NAME = "trantor"
BOOKS_COLL = "books"
USERS_COLL = "users"
PASS_SALT = "ImperialLibSalt"
)
type aboutData struct {
S Status
}
func aboutHandler(w http.ResponseWriter, r *http.Request) {
loadTemplate(w, "about", nil)
var data aboutData
data.S.User = SessionUser(r)
data.S.About = true
loadTemplate(w, "about", data)
}
func logoutHandler(w http.ResponseWriter, r *http.Request) {
LogOut(w, r)
http.Redirect(w, r, "/", 307)
}
func loginHandler(coll *mgo.Collection) func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
if r.Method == "POST" {
user := r.FormValue("user")
pass := r.FormValue("pass")
h := md5.New()
hash := h.Sum(([]byte)(PASS_SALT + pass))
n, _ := coll.Find(bson.M{"user":user, "pass":hash}).Count()
if n != 0 {
// TODO: display success
CreateSession(user, w, r)
} else {
// TODO: display error
}
}
http.Redirect(w, r, "/", 307)
}
}
type bookData struct {
S Status
Book Book
}
func bookHandler(coll *mgo.Collection) func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
var book Book
coll.Find(bson.M{"title": r.URL.Path[len("/book/"):]}).One(&book)
loadTemplate(w, "book", book)
var data bookData
data.S.User = SessionUser(r)
if coll.Find(bson.M{"title": r.URL.Path[len("/book/"):]}).One(&data.Book) != nil {
http.NotFound(w, r)
return
}
data.Book.Id = bson.ObjectId(data.Book.Id).Hex()
loadTemplate(w, "book", data)
}
}
@ -30,6 +74,7 @@ func fileHandler(path string) {
}
type indexData struct {
S Status
Books []Book
Count int
}
@ -37,6 +82,8 @@ type indexData struct {
func indexHandler(coll *mgo.Collection) func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
var data indexData
data.S.User = SessionUser(r)
data.S.Home = true
data.Count, _ = coll.Count()
coll.Find(bson.M{}).Sort("-_id").Limit(6).All(&data.Books)
loadTemplate(w, "index", data)
@ -50,10 +97,14 @@ func main() {
}
defer session.Close()
coll := session.DB(DB_NAME).C(BOOKS_COLL)
userColl := session.DB(DB_NAME).C(USERS_COLL)
http.HandleFunc("/book/", bookHandler(coll))
http.HandleFunc("/search/", searchHandler(coll))
http.HandleFunc("/upload/", uploadHandler(coll))
http.HandleFunc("/login/", loginHandler(userColl))
http.HandleFunc("/logout/", logoutHandler)
http.HandleFunc("/delete/", deleteHandler(coll))
http.HandleFunc("/about/", aboutHandler)
fileHandler("/img/")
fileHandler("/cover/")

View file

@ -42,18 +42,26 @@ func storeFile(r *http.Request) error {
return nil
}
type uploadData struct {
S Status
Msg string
}
func uploadHandler(coll *mgo.Collection) func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
status := ""
var data uploadData
data.S.User = SessionUser(r)
data.S.Upload = true
data.Msg = ""
if r.Method == "POST" {
err := storeFile(r)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
status = "Upload successful."
data.Msg = "Upload successful."
}
loadTemplate(w, "upload", status)
loadTemplate(w, "upload", data)
}
}