Add basic html/rss support for book lists
This commit is contained in:
parent
c0a70a18e1
commit
46762ea17b
11 changed files with 254 additions and 78 deletions
|
@ -80,7 +80,7 @@ func (db *pgDB) UpdateBookList(listID, title string, description []string) error
|
|||
func (db *pgDB) GetBookList(listID string) (*BookList, error) {
|
||||
var bookList BookList
|
||||
err := db.sql.Model(&bookList).
|
||||
Column("Books").
|
||||
Column("Books", "User").
|
||||
Where("list_id = ?", listID).
|
||||
Select()
|
||||
return &bookList, err
|
||||
|
@ -95,7 +95,7 @@ func (db *pgDB) GetListsByUser(username string) ([]BookList, error) {
|
|||
}
|
||||
|
||||
err = db.sql.Model(&bookLists).
|
||||
Column("Books").
|
||||
Column("Books", "User").
|
||||
Where("user_id = ?", user.ID).
|
||||
Select()
|
||||
return bookLists, err
|
||||
|
@ -108,7 +108,7 @@ func (db *pgDB) GetListsByBook(bookID string) ([]BookList, error) {
|
|||
err := db.sql.Model(&bookListEntries).
|
||||
Where("book_id = ?", bookID).
|
||||
Select()
|
||||
if err != nil {
|
||||
if err != nil || len(bookListEntries) == 0 {
|
||||
return bookLists, err
|
||||
}
|
||||
|
||||
|
@ -118,11 +118,10 @@ func (db *pgDB) GetListsByBook(bookID string) ([]BookList, error) {
|
|||
whereQuery += "?"
|
||||
if i < len(bookListEntries)-1 {
|
||||
whereQuery += ", "
|
||||
} else {
|
||||
whereQuery += ")"
|
||||
}
|
||||
listIDs[i] = entry.ID
|
||||
}
|
||||
whereQuery += ")"
|
||||
|
||||
err = db.sql.Model(&bookLists).
|
||||
Column("Books").
|
||||
|
|
86
lib/list.go
Normal file
86
lib/list.go
Normal file
|
@ -0,0 +1,86 @@
|
|||
package trantor
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"gitlab.com/trantor/trantor/lib/database"
|
||||
|
||||
log "github.com/cihub/seelog"
|
||||
"github.com/gorilla/mux"
|
||||
)
|
||||
|
||||
func listHandler(h handler) {
|
||||
listID := mux.Vars(h.r)["listID"]
|
||||
list, err := h.db.GetBookList(listID)
|
||||
if err != nil {
|
||||
log.Error("Error loading list ", listID, ": ", err)
|
||||
h.sess.Notify("Something went wrong!", "Could not load the list list", "error")
|
||||
http.Redirect(h.w, h.r, h.r.Referer(), http.StatusFound)
|
||||
return
|
||||
}
|
||||
if list == nil {
|
||||
notFound(h)
|
||||
return
|
||||
}
|
||||
|
||||
var data listData
|
||||
data.S = GetStatus(h)
|
||||
data.S.Title = list.Title + " -- " + data.S.Title
|
||||
data.List = list
|
||||
h.load("list", data)
|
||||
}
|
||||
|
||||
type listData struct {
|
||||
S Status
|
||||
List *database.BookList
|
||||
}
|
||||
|
||||
func listPostHandler(h handler) {
|
||||
if h.sess.User == "" {
|
||||
notFound(h)
|
||||
return
|
||||
}
|
||||
|
||||
listTitle := h.r.FormValue("list")
|
||||
bookID := h.r.FormValue("book_id")
|
||||
userLists, err := h.db.GetListsByUser(h.sess.User)
|
||||
if err != nil {
|
||||
log.Error("Error loading user (", h.sess.User, ") lists: ", err)
|
||||
h.sess.Notify("Something went wrong!", "Could not add book to the list", "error")
|
||||
http.Redirect(h.w, h.r, h.r.Referer(), http.StatusFound)
|
||||
return
|
||||
}
|
||||
|
||||
var list *database.BookList
|
||||
for _, l := range userLists {
|
||||
if strings.ToLower(l.Title) == strings.ToLower(listTitle) {
|
||||
list = &l
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
var listID string
|
||||
if list == nil {
|
||||
listID = genID()
|
||||
err = h.db.NewBookList(listID, listTitle, h.sess.User, []string{})
|
||||
if err != nil {
|
||||
log.Error("Error creating list ", listTitle, " by user ", h.sess.User, ": ", err)
|
||||
h.sess.Notify("Something went wrong!", "Could not add book to the list", "error")
|
||||
http.Redirect(h.w, h.r, h.r.Referer(), http.StatusFound)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
listID = list.ListID
|
||||
}
|
||||
|
||||
err = h.db.AddBookToList(listID, bookID)
|
||||
if err != nil {
|
||||
log.Error("Error adding book ", bookID, " to list ", listTitle, "(", listID, "): ", err)
|
||||
h.sess.Notify("Something went wrong!", "Could not add book to the list", "error")
|
||||
http.Redirect(h.w, h.r, h.r.Referer(), http.StatusFound)
|
||||
return
|
||||
}
|
||||
|
||||
http.Redirect(h.w, h.r, "/list/"+listID, http.StatusFound)
|
||||
}
|
|
@ -4,6 +4,8 @@ import (
|
|||
html_tmpl "html/template"
|
||||
txt_tmpl "text/template"
|
||||
|
||||
log "github.com/cihub/seelog"
|
||||
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"net/http"
|
||||
|
@ -51,40 +53,31 @@ type Template struct {
|
|||
}
|
||||
|
||||
func InitTemplate(assetsPath string) *Template {
|
||||
var err error
|
||||
var t Template
|
||||
templatePath := path.Join(assetsPath, "templates")
|
||||
|
||||
t.html = html_tmpl.Must(html_tmpl.ParseFiles(
|
||||
path.Join(templatePath, "header.html"),
|
||||
path.Join(templatePath, "footer.html"),
|
||||
path.Join(templatePath, "404.html"),
|
||||
path.Join(templatePath, "index.html"),
|
||||
path.Join(templatePath, "about.html"),
|
||||
path.Join(templatePath, "news.html"),
|
||||
path.Join(templatePath, "edit_news.html"),
|
||||
path.Join(templatePath, "book.html"),
|
||||
path.Join(templatePath, "search.html"),
|
||||
path.Join(templatePath, "upload.html"),
|
||||
path.Join(templatePath, "submission.html"),
|
||||
path.Join(templatePath, "login.html"),
|
||||
path.Join(templatePath, "new.html"),
|
||||
path.Join(templatePath, "read.html"),
|
||||
path.Join(templatePath, "edit.html"),
|
||||
path.Join(templatePath, "dashboard.html"),
|
||||
path.Join(templatePath, "settings.html"),
|
||||
path.Join(templatePath, "help.html"),
|
||||
path.Join(templatePath, "user_admin.html"),
|
||||
))
|
||||
t.html, err = html_tmpl.ParseGlob(path.Join(templatePath, "*.html"))
|
||||
if err != nil {
|
||||
log.Critical("Error loading html templates: ", err)
|
||||
}
|
||||
|
||||
t.rss = txt_tmpl.Must(txt_tmpl.ParseFiles(
|
||||
path.Join(templatePath, "search.rss"),
|
||||
path.Join(templatePath, "news.rss"),
|
||||
))
|
||||
t.rss, err = txt_tmpl.New("rss").Funcs(txt_tmpl.FuncMap{
|
||||
"book_list": func(books, baseURL interface{}) (map[string]interface{}, error) {
|
||||
data := make(map[string]interface{}, 2)
|
||||
data["Books"] = books
|
||||
data["BaseURL"] = baseURL
|
||||
return data, nil
|
||||
},
|
||||
}).ParseGlob(path.Join(templatePath, "*.rss"))
|
||||
if err != nil {
|
||||
log.Critical("Error loading rss templates: ", err)
|
||||
}
|
||||
|
||||
t.opds = txt_tmpl.Must(txt_tmpl.ParseFiles(
|
||||
path.Join(templatePath, "index.opds"),
|
||||
path.Join(templatePath, "search.opds"),
|
||||
))
|
||||
t.opds, err = txt_tmpl.ParseGlob(path.Join(templatePath, "*.opds"))
|
||||
if err != nil {
|
||||
log.Critical("Error loading opds templates: ", err)
|
||||
}
|
||||
|
||||
return &t
|
||||
}
|
||||
|
@ -101,6 +94,8 @@ func loadJson(w http.ResponseWriter, tmpl string, data interface{}) error {
|
|||
res, err = newsJson(data)
|
||||
case "search":
|
||||
res, err = searchJson(data)
|
||||
case "list":
|
||||
res, err = listJson(data)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -178,6 +173,25 @@ func searchJson(data interface{}) ([]byte, error) {
|
|||
})
|
||||
}
|
||||
|
||||
func listJson(data interface{}) ([]byte, error) {
|
||||
list, ok := data.(listData)
|
||||
if !ok {
|
||||
return nil, errors.New("Data is not valid")
|
||||
}
|
||||
|
||||
books := make([]map[string]interface{}, len(list.List.Books))
|
||||
for i, book := range list.List.Books {
|
||||
books[i] = bookJsonRaw(book)
|
||||
}
|
||||
return json.Marshal(map[string]interface{}{
|
||||
"ID": list.List.ListID,
|
||||
"title": list.List.Title,
|
||||
"description": list.List.Description,
|
||||
"user ": list.List.User.Username,
|
||||
"books": books,
|
||||
})
|
||||
}
|
||||
|
||||
func bookJsonRaw(book database.Book) map[string]interface{} {
|
||||
cover := ""
|
||||
coverSmall := ""
|
||||
|
|
|
@ -52,6 +52,8 @@ type bookData struct {
|
|||
S Status
|
||||
Book database.Book
|
||||
Description []string
|
||||
Lists []database.BookList
|
||||
UserLists []string
|
||||
}
|
||||
|
||||
func bookHandler(h handler) {
|
||||
|
@ -71,6 +73,21 @@ func bookHandler(h handler) {
|
|||
}
|
||||
data.S.Title = book.Title + author + " -- " + data.S.Title
|
||||
|
||||
data.Lists, err = h.db.GetListsByBook(id)
|
||||
if err != nil {
|
||||
log.Error("Error getting lists: ", err)
|
||||
}
|
||||
if h.sess.User != "" {
|
||||
userLists, err := h.db.GetListsByUser(h.sess.User)
|
||||
if err != nil {
|
||||
log.Error("Error getting lists: ", err)
|
||||
} else {
|
||||
data.UserLists = make([]string, len(userLists))
|
||||
for i, l := range userLists {
|
||||
data.UserLists[i] = l.Title
|
||||
}
|
||||
}
|
||||
}
|
||||
data.Description = strings.Split(data.Book.Description, "\n")
|
||||
h.load("book", data)
|
||||
}
|
||||
|
@ -191,6 +208,9 @@ func InitRouter(db database.DB, sg *StatsGatherer, assetsPath string) http.Handl
|
|||
r.HandleFunc("/dashboard/", sg.Gather(dashboardHandler))
|
||||
r.HandleFunc("/settings/", sg.Gather(settingsHandler))
|
||||
|
||||
r.HandleFunc("/list/{listID:"+idPattern+"}", sg.Gather(listHandler)).Methods("GET")
|
||||
r.HandleFunc("/list/", sg.Gather(listPostHandler)).Methods("POST")
|
||||
|
||||
r.HandleFunc("/new/", sg.Gather(newHandler))
|
||||
r.HandleFunc("/save/{id:"+idPattern+"}", sg.Gather(saveHandler)).Methods("POST")
|
||||
r.HandleFunc("/edit/{id:"+idPattern+"}", sg.Gather(editHandler))
|
||||
|
|
|
@ -76,4 +76,31 @@ function delBook(){
|
|||
</div>
|
||||
</div>
|
||||
|
||||
{{if .Lists}}
|
||||
<br />
|
||||
<div class="span10 offset1">
|
||||
<h4>Book in lists:</h4>
|
||||
<ul>
|
||||
{{range .Lists}}
|
||||
<li><a href="/list/{{.ListID}}">{{.Title}}</a> ({{len .Books}})</li>
|
||||
{{end}}
|
||||
</ul>
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
{{if .S.User}}
|
||||
<br />
|
||||
<form class="span6 offset3 form-inline" method="POST" action="/list/">
|
||||
<input type="hidden" id="book_id" name="book_id" value="{{.Book.ID}}">
|
||||
<input type="text" data-provide="typeahead" id="list" name="list">
|
||||
<button type="submit" class="btn btn-primary">Add to list</button>
|
||||
</form>
|
||||
|
||||
<script>
|
||||
$(window).load(function() {
|
||||
$('.typeahead').typeahead(source={{.UserLists}})
|
||||
})
|
||||
</script>
|
||||
{{end}}
|
||||
|
||||
{{template "footer.html"}}
|
||||
|
|
25
templates/book_list.html
Normal file
25
templates/book_list.html
Normal file
|
@ -0,0 +1,25 @@
|
|||
{{range .}}
|
||||
<div class="row">
|
||||
<div class="span1">
|
||||
<p class="pull-right"><a href="/book/{{.ID}}">{{if .Cover}}<img class="img-rounded" src="/cover/{{.ID}}/small/{{.Title}}.jpg" alt="{{.Title}}" />{{end}}</a></p>
|
||||
</div>
|
||||
<div class="span10 well">
|
||||
<div class="row">
|
||||
<div class="span7">
|
||||
<p>
|
||||
<span class="muted">[{{if .Lang}}{{.Lang}}{{end}}]</span>
|
||||
<a href="/book/{{.ID}}"><strong>{{.Title}}</strong></a>
|
||||
<span class="muted">{{if .Publisher}}{{.Publisher}}{{end}}</span><br />
|
||||
{{range .Authors}}{{.}}, {{end}}
|
||||
</p>
|
||||
</div>
|
||||
<div class="span3">
|
||||
<div class="btn-group pull-right">
|
||||
<a href="/download/{{.ID}}/{{.Title}}.epub" class="btn btn-inverse"><i class="icon-download-alt icon-white"></i> download</a>
|
||||
<a href="/read/{{.ID}}" class="btn btn-warning"><i class="icon-eye-open icon-white"></i> read it!</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
17
templates/book_list.rss
Normal file
17
templates/book_list.rss
Normal file
|
@ -0,0 +1,17 @@
|
|||
{{$baseURL := .BaseURL}}
|
||||
{{range .Books}}
|
||||
<item>
|
||||
<title>{{.Title}} - {{index .Authors 0}}</title>
|
||||
<description>{{.Description}}</description>
|
||||
<link>{{$baseURL}}/book/{{.ID}}</link>
|
||||
{{if .Isbn}}
|
||||
<guid isPermaLink="false">ISBN: {{.Isbn}}</guid>
|
||||
{{end}}
|
||||
<enclosure url="{{$baseURL}}/download/{{.ID}}/{{.Title}}.epub" length="{{.FileSize}}" type="application/epub+zip" />
|
||||
{{range .Authors}}
|
||||
{{if .}}
|
||||
<category>{{.}}</category>
|
||||
{{end}}
|
||||
{{end}}
|
||||
</item>
|
||||
{{end}}
|
16
templates/list.html
Normal file
16
templates/list.html
Normal file
|
@ -0,0 +1,16 @@
|
|||
{{template "header.html" .S}}
|
||||
|
||||
<div class="row">
|
||||
<h4 class="span10">{{.List.Title}} <a href="/list/{{.List.ListID}}?fmt=rss"><img src="/img/feed.png"/></a></h4>
|
||||
<p class="span2 pull-right">By {{.List.User.Username}}</p>
|
||||
</div>
|
||||
<br />
|
||||
|
||||
{{range .List.Description}}
|
||||
<p>{{.}}</p>
|
||||
{{end}}
|
||||
|
||||
|
||||
{{template "book_list.html" .List.Books}}
|
||||
|
||||
{{template "footer.html"}}
|
14
templates/list.rss
Normal file
14
templates/list.rss
Normal file
|
@ -0,0 +1,14 @@
|
|||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<rss version="2.0">
|
||||
<channel>
|
||||
<title>{{.S.Title}}</title>
|
||||
<description>{{range .List.Description}}
|
||||
{{.}}
|
||||
{{end}}</description>
|
||||
<link>{{.S.BaseURL}}/list/{{.List.ListID}}</link>
|
||||
<managingEditor>{{.List.User.Username}}</managingEditor>
|
||||
|
||||
{{template "book_list.rss" book_list .List.Books .S.BaseURL}}
|
||||
|
||||
</channel>
|
||||
</rss>
|
|
@ -17,33 +17,7 @@
|
|||
{{end}}
|
||||
</ul>
|
||||
|
||||
{{with .Books}}
|
||||
{{range .}}
|
||||
<div class="row">
|
||||
<div class="span1">
|
||||
<p class="pull-right"><a href="/book/{{.ID}}">{{if .Cover}}<img class="img-rounded" src="/cover/{{.ID}}/small/{{.Title}}.jpg" alt="{{.Title}}" />{{end}}</a></p>
|
||||
</div>
|
||||
<div class="span10 well">
|
||||
<div class="row">
|
||||
<div class="span7">
|
||||
<p>
|
||||
<span class="muted">[{{if .Lang}}{{.Lang}}{{end}}]</span>
|
||||
<a href="/book/{{.ID}}"><strong>{{.Title}}</strong></a>
|
||||
<span class="muted">{{if .Publisher}}{{.Publisher}}{{end}}</span><br />
|
||||
{{range .Authors}}{{.}}, {{end}}
|
||||
</p>
|
||||
</div>
|
||||
<div class="span3">
|
||||
<div class="btn-group pull-right">
|
||||
<a href="/download/{{.ID}}/{{.Title}}.epub" class="btn btn-inverse"><i class="icon-download-alt icon-white"></i> download</a>
|
||||
<a href="/read/{{.ID}}" class="btn btn-warning"><i class="icon-eye-open icon-white"></i> read it!</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
{{end}}
|
||||
{{template "book_list.html" .Books}}
|
||||
|
||||
<ul class="pager">
|
||||
{{if .Prev}}
|
||||
|
|
|
@ -11,23 +11,7 @@
|
|||
<link>{{.BaseURL}}</link>
|
||||
{{end}}
|
||||
|
||||
{{$baseURL := .S.BaseURL}}
|
||||
{{range .Books}}
|
||||
<item>
|
||||
<title>{{.Title}} - {{index .Authors 0}}</title>
|
||||
<description>{{.Description}}</description>
|
||||
<link>{{$baseURL}}/book/{{.ID}}</link>
|
||||
{{if .Isbn}}
|
||||
<guid isPermaLink="false">ISBN: {{.Isbn}}</guid>
|
||||
{{end}}
|
||||
<enclosure url="{{$baseURL}}/download/{{.ID}}/{{.Title}}.epub" length="{{.FileSize}}" type="application/epub+zip" />
|
||||
{{range .Authors}}
|
||||
{{if .}}
|
||||
<category>{{.}}</category>
|
||||
{{end}}
|
||||
{{end}}
|
||||
</item>
|
||||
{{end}}
|
||||
{{template "book_list.rss" book_list .Books .S.BaseURL}}
|
||||
|
||||
</channel>
|
||||
</rss>
|
||||
|
|
Reference in a new issue