go-bouquins/bouquins/bouquins.go

385 lines
8.7 KiB
Go
Raw Normal View History

2017-07-30 14:06:38 +00:00
package bouquins
2017-07-30 15:20:48 +00:00
import (
2017-07-30 18:14:20 +00:00
"database/sql"
2017-07-31 18:49:27 +00:00
"encoding/json"
2017-08-05 15:48:06 +00:00
"errors"
2017-08-04 18:06:10 +00:00
"fmt"
2017-07-30 15:20:48 +00:00
"html/template"
"log"
"net/http"
2017-08-04 17:47:15 +00:00
"net/url"
2017-08-01 18:00:20 +00:00
"strconv"
"strings"
2017-08-04 18:06:10 +00:00
"github.com/c2h5oh/datasize"
2017-07-30 15:20:48 +00:00
)
const (
2017-07-30 18:14:20 +00:00
TPL_BOOKS = "book.html"
TPL_AUTHORS = "author.html"
2017-07-30 15:20:48 +00:00
TPL_SERIES = "series.html"
TPL_INDEX = "index.html"
2017-07-31 18:14:57 +00:00
2017-08-02 18:17:12 +00:00
PARAM_LIST = "list"
PARAM_ORDER = "order"
PARAM_SORT = "sort"
PARAM_LIMIT = "limit"
PARAM_OFFSET = "offset"
2017-07-31 18:14:57 +00:00
LIST_AUTHORS = "authors"
LIST_SERIES = "series"
LIST_BOOKS = "books"
2017-08-01 18:00:20 +00:00
URL_INDEX = "/"
URL_BOOKS = "/books/"
URL_AUTHORS = "/authors/"
URL_SERIES = "/series/"
URL_JS = "/js/"
URL_CSS = "/css/"
URL_FONTS = "/fonts/"
2017-08-04 18:06:10 +00:00
URL_CALIBRE = "/calibre/"
2017-07-30 15:20:48 +00:00
)
2017-07-30 14:06:38 +00:00
type Bouquins struct {
2017-07-30 15:20:48 +00:00
*template.Template
2017-07-30 18:14:20 +00:00
*sql.DB
2017-07-30 14:06:38 +00:00
}
2017-07-30 14:42:18 +00:00
/*
* A book series.
*/
type Series struct {
2017-07-31 18:49:27 +00:00
Id int64 `json:"id,omitempty"`
Name string `json:"name,omitempty"`
2017-07-30 14:42:18 +00:00
}
/*
* A book. Generic data.
*/
type Book struct {
2017-07-31 18:49:27 +00:00
Id int64 `json:"id,omitempty"`
Title string `json:"title,omitempty"`
2017-08-01 18:00:20 +00:00
SeriesIndex float64 `json:"series_idx,omitempty"`
2017-07-31 18:49:27 +00:00
Series *Series `json:"series,omitempty"`
2017-07-30 14:42:18 +00:00
}
/*
* An author.
*/
type Author struct {
2017-07-31 18:49:27 +00:00
Id int64 `json:"id,omitempty"`
Name string `json:"name,omitempty"`
2017-07-30 14:42:18 +00:00
}
/*
* Author and number of books.
*/
type AuthorAdv struct {
Author
2017-07-31 18:49:27 +00:00
Count int `json:"count,omitempty"`
2017-07-30 14:42:18 +00:00
}
/*
* Downloadable book data.
*/
type BookData struct {
2017-07-31 18:49:27 +00:00
Size int64 `json:"size,omitempty"`
Format string `json:"format,omitempty"`
Name string `json:"name,omitempty"`
2017-07-30 14:42:18 +00:00
}
/*
* A book. Advanced data: authors, tags.
*/
type BookAdv struct {
Book
2017-07-31 18:49:27 +00:00
Authors []*Author `json:"authors,omitempty"`
Tags []string `json:"tags,omitempty"`
2017-07-30 14:42:18 +00:00
}
type AuthorFull struct {
Author
2017-08-04 16:57:15 +00:00
Books []*Book `json:"books,omitempty"`
Series []*Series `json:"series,omitempty"`
CoAuthors []*Author `json:"coauthors,omitempty"`
2017-07-30 14:42:18 +00:00
}
type BookFull struct {
BookAdv
2017-08-01 18:00:20 +00:00
Data []*BookData `json:"data,omitempty"`
Timestamp int64 `json:"timestamp,omitempty"`
Pubdate int64 `json:"pubdate,omitempty"`
Isbn string `json:"isbn,omitempty"`
Lccn string `json:"lccn,omitempty"`
Path string `json:"path,omitempty"`
Uuid string `json:"uuid,omitempty"`
Has_cover bool `json:"has_cover,omitempty"`
Lang string `json:"lang,omitempty"`
Publisher string `json:"publisher,omitempty"`
2017-07-30 14:42:18 +00:00
}
type SeriesAdv struct {
Series
2017-07-31 18:49:27 +00:00
Count int64 `json:"count,omitempty"`
Authors []*Author `json:"authors,omitempty"`
2017-07-30 14:42:18 +00:00
}
type SeriesFull struct {
SeriesAdv
2017-07-31 18:49:27 +00:00
Books []*Book `json:"books,omitempty"`
2017-07-30 15:20:48 +00:00
}
2017-07-30 16:09:27 +00:00
type BouquinsModel struct {
2017-08-04 16:57:15 +00:00
Title string `json:"title,omitempty"`
PageJs string `json:"-"`
2017-07-30 16:09:27 +00:00
}
// Constructor BouquinsModel
func NewBouquinsModel(title string) *BouquinsModel {
2017-08-04 16:57:15 +00:00
return NewBouquinsModelJs(title, "")
}
func NewBouquinsModelJs(title, js string) *BouquinsModel {
2017-07-30 16:09:27 +00:00
return &BouquinsModel{
title,
2017-08-04 16:57:15 +00:00
js,
2017-07-30 16:09:27 +00:00
}
}
2017-07-30 15:20:48 +00:00
type IndexModel struct {
2017-07-30 16:09:27 +00:00
BouquinsModel
2017-08-05 14:44:05 +00:00
BooksCount int64 `json:"count"`
2017-07-30 14:42:18 +00:00
}
2017-07-30 16:09:27 +00:00
// Constructor IndexModel
2017-08-05 09:16:19 +00:00
func NewIndexModel(title, js string, count int64) *IndexModel {
2017-07-30 16:09:27 +00:00
return &IndexModel{
2017-08-05 09:16:19 +00:00
*NewBouquinsModelJs(title, js),
2017-07-30 16:09:27 +00:00
count,
2017-07-30 15:20:48 +00:00
}
2017-07-30 16:09:27 +00:00
}
2017-08-05 15:48:06 +00:00
type ResultsModel struct {
Type string `json:"type,omitempty"`
}
2017-08-05 14:44:05 +00:00
type BooksResultsModel struct {
2017-08-05 15:48:06 +00:00
ResultsModel
2017-08-05 14:44:05 +00:00
Results []*BookAdv `json:"results,omitempty"`
}
func NewBooksResultsModel(books []*BookAdv) *BooksResultsModel {
2017-08-05 15:48:06 +00:00
return &BooksResultsModel{ResultsModel{"books"}, books}
}
type AuthorsResultsModel struct {
ResultsModel
Results []*AuthorAdv `json:"results,omitempty"`
}
func NewAuthorsResultsModel(authors []*AuthorAdv) *AuthorsResultsModel {
return &AuthorsResultsModel{ResultsModel{"authors"}, authors}
}
type SerieResultsModel struct {
ResultsModel
Results []*SeriesAdv `json:"results,omitempty"`
2017-08-05 14:44:05 +00:00
}
2017-08-05 15:48:06 +00:00
func NewSerieResultsModel(series []*SeriesAdv) *SerieResultsModel {
return &SerieResultsModel{ResultsModel{"series"}, series}
}
2017-08-05 14:44:05 +00:00
2017-08-01 13:38:23 +00:00
type BookModel struct {
BouquinsModel
*BookFull
}
2017-08-03 17:51:56 +00:00
type SeriesModel struct {
BouquinsModel
*SeriesFull
}
2017-08-03 18:36:46 +00:00
type AuthorModel struct {
BouquinsModel
*AuthorFull
}
2017-08-05 15:48:06 +00:00
func (app *Bouquins) render(res http.ResponseWriter, tpl string, model interface{}) error {
return app.Template.ExecuteTemplate(res, tpl, model)
2017-07-30 14:06:38 +00:00
}
2017-08-04 17:47:15 +00:00
func TemplatesFunc() *template.Template {
tpl := template.New("")
tpl.Funcs(template.FuncMap{
"humanSize": func(sz int64) string {
return datasize.ByteSize(sz).HumanReadable()
},
2017-08-04 18:06:10 +00:00
"bookCover": func(book *BookFull) string {
fmt.Println(book.Path)
return "/calibre/" + url.PathEscape(book.Path) + "/cover.jpg"
},
2017-08-04 17:47:15 +00:00
"bookLink": func(data *BookData, book *BookFull) string {
2017-08-04 18:06:10 +00:00
return "/calibre/" + url.PathEscape(book.Path) + "/" + url.PathEscape(data.Name) + "." + strings.ToLower(data.Format)
2017-08-04 17:47:15 +00:00
},
})
return tpl
}
2017-07-30 16:09:27 +00:00
2017-08-05 15:48:06 +00:00
func writeJson(res http.ResponseWriter, model interface{}) error {
res.Header().Set("Content-Type", "application/json")
enc := json.NewEncoder(res)
return enc.Encode(model)
}
func isJson(req *http.Request) bool {
return req.Header.Get("Accept") == "application/json"
}
2017-08-02 18:17:12 +00:00
func paramInt(name string, req *http.Request) int {
val := req.URL.Query().Get(name)
valInt, err := strconv.Atoi(val)
if err != nil {
2017-08-05 09:16:19 +00:00
log.Println("Invalid value for", name, ":", val)
2017-08-02 18:17:12 +00:00
return 0
}
return valInt
}
func paramOrder(req *http.Request) string {
val := req.URL.Query().Get(PARAM_ORDER)
if val == "desc" || val == "asc" {
return val
}
return ""
}
2017-07-30 18:14:20 +00:00
// ROUTES //
2017-07-30 16:09:27 +00:00
func (app *Bouquins) IndexPage(res http.ResponseWriter, req *http.Request) {
2017-07-30 18:14:20 +00:00
count, err := app.BookCount()
if err != nil {
log.Print(err)
}
2017-08-05 09:16:19 +00:00
model := NewIndexModel("", "index.js", count)
2017-08-05 14:44:05 +00:00
/*
order, sort := paramOrder(req), req.URL.Query().Get(PARAM_SORT)
limit, offset := paramInt(PARAM_LIMIT, req), paramInt(PARAM_OFFSET, req)
switch req.URL.Query().Get(PARAM_LIST) {
case LIST_AUTHORS:
model.Authors, err = app.AuthorsAdv(limit, offset, sort, order)
case LIST_SERIES:
model.Series, err = app.SeriesAdv(limit, offset, sort, order)
case LIST_BOOKS:
fallthrough
default:
model.Books, err = app.BooksAdv(limit, offset, sort, order)
}
if err != nil {
log.Print(err)
}
*/
2017-08-05 15:48:06 +00:00
if isJson(req) {
err := writeJson(res, model)
2017-07-31 18:49:27 +00:00
if err != nil {
log.Println(err)
http.Error(res, err.Error(), 500)
}
} else {
app.render(res, TPL_INDEX, model)
}
2017-07-30 16:09:27 +00:00
}
2017-08-05 15:48:06 +00:00
func (app *Bouquins) BooksListPage(res http.ResponseWriter, req *http.Request) error {
if isJson(req) {
books, err := app.BooksAdv(10, 0, "", "")
if err != nil {
return err
}
return writeJson(res, NewBooksResultsModel(books))
}
return errors.New("Invalid mime")
}
func (app *Bouquins) BookPage(idParam string, res http.ResponseWriter, req *http.Request) error {
id, err := strconv.Atoi(idParam)
if err != nil {
return nil
}
book, err := app.BookFull(int64(id))
if err != nil {
return nil
}
return app.render(res, TPL_BOOKS, &BookModel{*NewBouquinsModel(book.Title), book})
}
2017-07-30 15:20:48 +00:00
func (app *Bouquins) BooksPage(res http.ResponseWriter, req *http.Request) {
2017-08-05 15:48:06 +00:00
var err error
var idParam = ""
if strings.HasPrefix(req.URL.Path, URL_BOOKS) {
idParam = req.URL.Path[len(URL_BOOKS):]
} else {
err = errors.New("Invalid URL") // FIXME 404
2017-08-01 18:00:20 +00:00
}
2017-08-05 09:16:19 +00:00
if len(idParam) == 0 {
2017-08-05 15:48:06 +00:00
err = app.BooksListPage(res, req)
2017-08-05 09:16:19 +00:00
} else {
2017-08-05 15:48:06 +00:00
err = app.BookPage(idParam, res, req)
}
if err != nil {
log.Println(err)
http.Error(res, err.Error(), 500)
}
}
func (app *Bouquins) AuthorsListPage(res http.ResponseWriter, req *http.Request) error {
if isJson(req) {
authors, err := app.AuthorsAdv(10, 0, "", "")
2017-08-05 09:16:19 +00:00
if err != nil {
2017-08-05 15:48:06 +00:00
return err
2017-08-05 09:16:19 +00:00
}
2017-08-05 15:48:06 +00:00
return writeJson(res, NewAuthorsResultsModel(authors))
2017-08-01 13:38:23 +00:00
}
2017-08-05 15:48:06 +00:00
return errors.New("Invalid mime")
2017-07-30 14:06:38 +00:00
}
2017-08-05 15:48:06 +00:00
func (app *Bouquins) AuthorPage(idParam string, res http.ResponseWriter, req *http.Request) error {
id, err := strconv.Atoi(idParam)
2017-08-03 18:36:46 +00:00
if err != nil {
2017-08-05 15:48:06 +00:00
return err
2017-08-03 18:36:46 +00:00
}
author, err := app.AuthorFull(int64(id))
if err != nil {
2017-08-05 15:48:06 +00:00
return err
}
return app.render(res, TPL_AUTHORS, &AuthorModel{*NewBouquinsModelJs(author.Name, "author.js"), author})
}
func (app *Bouquins) AuthorsPage(res http.ResponseWriter, req *http.Request) {
var err error
var idParam = ""
if strings.HasPrefix(req.URL.Path, URL_AUTHORS) {
idParam = req.URL.Path[len(URL_AUTHORS):]
} else {
err = errors.New("Invalid URL") // FIXME 404
}
if len(idParam) == 0 {
err = app.AuthorsListPage(res, req)
} else {
err = app.AuthorPage(idParam, res, req)
}
if err != nil {
log.Println(err)
http.Error(res, err.Error(), 500)
2017-08-03 18:36:46 +00:00
}
2017-07-30 14:06:38 +00:00
}
2017-07-30 15:20:48 +00:00
func (app *Bouquins) SeriesPage(res http.ResponseWriter, req *http.Request) {
2017-08-03 17:51:56 +00:00
if !strings.HasPrefix(req.URL.Path, URL_SERIES) {
// FIXME 404
log.Fatalln("Invalid URL")
}
id, err := strconv.Atoi(req.URL.Path[len(URL_SERIES):])
if err != nil {
// FIXME 404
log.Fatalln(err)
}
series, err := app.SeriesFull(int64(id))
2017-08-03 18:36:46 +00:00
if err != nil {
// FIXME 500
log.Fatalln(err)
}
2017-08-03 17:51:56 +00:00
app.render(res, TPL_SERIES, &SeriesModel{
*NewBouquinsModel(series.Name),
series,
})
2017-07-30 14:06:38 +00:00
}