Sessions with gorilla
This commit is contained in:
parent
44114bd91d
commit
c513344169
15 changed files with 251 additions and 47 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -7,3 +7,4 @@ upload/upload
|
|||
upload/books
|
||||
upload/cover
|
||||
upload/new
|
||||
adduser/adduser
|
||||
|
|
30
admin.go
Normal file
30
admin.go
Normal 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
|
||||
}
|
||||
}
|
|
@ -1,6 +1,7 @@
|
|||
package main
|
||||
|
||||
type Book struct {
|
||||
Id string `bson:"_id"`
|
||||
Title string
|
||||
Author []string
|
||||
Contributor string
|
||||
|
|
|
@ -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
43
session.go
Normal 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)
|
||||
}
|
|
@ -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",
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
|
@ -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"}}
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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" />
|
||||
|
|
59
trantor.go
59
trantor.go
|
@ -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/")
|
||||
|
|
14
upload.go
14
upload.go
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
Reference in a new issue