package bouquins import ( "database/sql" "encoding/json" "github.com/c2h5oh/datasize" "html/template" "log" "net/http" "net/url" "strconv" "strings" ) const ( TPL_BOOKS = "book.html" TPL_AUTHORS = "author.html" TPL_SERIES = "series.html" TPL_INDEX = "index.html" PARAM_LIST = "list" PARAM_ORDER = "order" PARAM_SORT = "sort" PARAM_LIMIT = "limit" PARAM_OFFSET = "offset" LIST_AUTHORS = "authors" LIST_SERIES = "series" LIST_BOOKS = "books" URL_INDEX = "/" URL_BOOKS = "/books/" URL_AUTHORS = "/authors/" URL_SERIES = "/series/" URL_JS = "/js/" URL_CSS = "/css/" URL_FONTS = "/fonts/" ) type Bouquins struct { *template.Template *sql.DB } /* * A book series. */ type Series struct { Id int64 `json:"id,omitempty"` Name string `json:"name,omitempty"` } /* * A book. Generic data. */ type Book struct { Id int64 `json:"id,omitempty"` Title string `json:"title,omitempty"` SeriesIndex float64 `json:"series_idx,omitempty"` Series *Series `json:"series,omitempty"` } /* * An author. */ type Author struct { Id int64 `json:"id,omitempty"` Name string `json:"name,omitempty"` } /* * Author and number of books. */ type AuthorAdv struct { Author Count int `json:"count,omitempty"` } /* * Downloadable book data. */ type BookData struct { Size int64 `json:"size,omitempty"` Format string `json:"format,omitempty"` Name string `json:"name,omitempty"` } /* * A book. Advanced data: authors, tags. */ type BookAdv struct { Book Authors []*Author `json:"authors,omitempty"` Tags []string `json:"tags,omitempty"` } type AuthorFull struct { Author Books []*Book `json:"books,omitempty"` Series []*Series `json:"series,omitempty"` CoAuthors []*Author `json:"coauthors,omitempty"` } type BookFull struct { BookAdv 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"` } type SeriesAdv struct { Series Count int64 `json:"count,omitempty"` Authors []*Author `json:"authors,omitempty"` } type SeriesFull struct { SeriesAdv Books []*Book `json:"books,omitempty"` } type BouquinsModel struct { Title string `json:"title,omitempty"` PageJs string `json:"-"` } // Constructor BouquinsModel func NewBouquinsModel(title string) *BouquinsModel { return NewBouquinsModelJs(title, "") } func NewBouquinsModelJs(title, js string) *BouquinsModel { return &BouquinsModel{ title, js, } } type IndexModel struct { BouquinsModel BooksCount int64 `json:"count"` Books []*BookAdv `json:"books,omitempty"` Series []*SeriesAdv `json:"series,omitempty"` Authors []*AuthorAdv `json:"authors,omitempty"` } // Constructor IndexModel func NewIndexModel(title string, count int64) *IndexModel { return &IndexModel{ *NewBouquinsModel(title), count, nil, nil, nil, } } type BookModel struct { BouquinsModel *BookFull } type SeriesModel struct { BouquinsModel *SeriesFull } type AuthorModel struct { BouquinsModel *AuthorFull } func (app *Bouquins) render(res http.ResponseWriter, tpl string, model interface{}) { err := app.Template.ExecuteTemplate(res, tpl, model) if err != nil { log.Print(err) } } func TemplatesFunc() *template.Template { tpl := template.New("") tpl.Funcs(template.FuncMap{ "humanSize": func(sz int64) string { return datasize.ByteSize(sz).HumanReadable() }, "bookLink": func(data *BookData, book *BookFull) string { return "/images/" + url.QueryEscape(book.Path) + "/" + url.QueryEscape(data.Name) + "." + strings.ToLower(data.Format) }, }) return tpl } func paramInt(name string, req *http.Request) int { val := req.URL.Query().Get(name) valInt, err := strconv.Atoi(val) if err != nil { log.Print("Invalid value for", name, ":", val) 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 "" } // ROUTES // func (app *Bouquins) IndexPage(res http.ResponseWriter, req *http.Request) { count, err := app.BookCount() if err != nil { log.Print(err) } model := NewIndexModel("", count) 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) } if req.Header.Get("Accept") == "application/json" { res.Header().Set("Content-Type", "application/json") enc := json.NewEncoder(res) err := enc.Encode(model) if err != nil { log.Println(err) http.Error(res, err.Error(), 500) } } else { app.render(res, TPL_INDEX, model) } } func (app *Bouquins) BooksPage(res http.ResponseWriter, req *http.Request) { if !strings.HasPrefix(req.URL.Path, URL_BOOKS) { // FIXME 404 log.Fatalln("Invalid URL") } id, err := strconv.Atoi(req.URL.Path[len(URL_BOOKS):]) if err != nil { // FIXME 404 log.Fatalln(err) } book, err := app.BookFull(int64(id)) if err != nil { // FIXME 500 log.Fatalln(err) } model := &BookModel{ *NewBouquinsModel(book.Title), book, } app.render(res, TPL_BOOKS, model) } func (app *Bouquins) AuthorsPage(res http.ResponseWriter, req *http.Request) { if !strings.HasPrefix(req.URL.Path, URL_AUTHORS) { // FIXME 404 log.Fatalln("Invalid URL") } id, err := strconv.Atoi(req.URL.Path[len(URL_AUTHORS):]) if err != nil { // FIXME 404 log.Fatalln(err) } author, err := app.AuthorFull(int64(id)) if err != nil { // FIXME 500 log.Fatalln(err) } app.render(res, TPL_AUTHORS, &AuthorModel{ *NewBouquinsModelJs(author.Name, "author.js"), author, }) } func (app *Bouquins) SeriesPage(res http.ResponseWriter, req *http.Request) { 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)) if err != nil { // FIXME 500 log.Fatalln(err) } app.render(res, TPL_SERIES, &SeriesModel{ *NewBouquinsModel(series.Name), series, }) }