Keep count of downladed/viewed books
This commit is contained in:
parent
68ffe6cf5b
commit
71d975c2f7
7 changed files with 109 additions and 91 deletions
11
createdb.sql
11
createdb.sql
|
@ -105,7 +105,6 @@ SELECT
|
||||||
$$ LANGUAGE sql IMMUTABLE;
|
$$ LANGUAGE sql IMMUTABLE;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
CREATE TABLE news (
|
CREATE TABLE news (
|
||||||
id serial unique,
|
id serial unique,
|
||||||
date timestamp,
|
date timestamp,
|
||||||
|
@ -120,3 +119,13 @@ CREATE TABLE users (
|
||||||
salt bytea,
|
salt bytea,
|
||||||
role varchar(255)
|
role varchar(255)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
|
CREATE TABLE visits (
|
||||||
|
id serial unique,
|
||||||
|
downloads integer,
|
||||||
|
views integer,
|
||||||
|
book_id varchar(16) unique REFERENCES books(id) ON DELETE CASCADE
|
||||||
|
);
|
||||||
|
CREATE INDEX CONCURRENTLY visits_downloads_idx on visits(downloads DESC NULLS LAST);
|
||||||
|
CREATE INDEX CONCURRENTLY visits_views_idx on visits(views DESC NULLS LAST);
|
||||||
|
|
|
@ -24,6 +24,7 @@ type Book struct {
|
||||||
Active bool `sql:",notnull"`
|
Active bool `sql:",notnull"`
|
||||||
UploadDate time.Time
|
UploadDate time.Time
|
||||||
Tsv string
|
Tsv string
|
||||||
|
Visit *Visit
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddBook to the database
|
// AddBook to the database
|
||||||
|
|
|
@ -24,9 +24,10 @@ type DB interface {
|
||||||
AddNews(text string) error
|
AddNews(text string) error
|
||||||
AddRawNews(text string, date time.Time) error
|
AddRawNews(text string, date time.Time) error
|
||||||
GetNews(num int, days int) (news []New, err error)
|
GetNews(num int, days int) (news []New, err error)
|
||||||
AddStats(stats interface{}) error
|
IncViews(ID string) error
|
||||||
GetVisitedBooks() (books []Book, err error)
|
IncDownloads(ID string) error
|
||||||
GetDownloadedBooks() (books []Book, err error)
|
GetVisitedBooks(num int) (books []Book, err error)
|
||||||
|
GetDownloadedBooks(num int) (books []Book, err error)
|
||||||
GetTags() ([]string, error)
|
GetTags() ([]string, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -77,16 +77,20 @@ func (db *roDB) GetNews(num int, days int) (news []New, err error) {
|
||||||
return db.db.GetNews(num, days)
|
return db.db.GetNews(num, days)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (db *roDB) AddStats(stats interface{}) error {
|
func (db *roDB) IncViews(ID string) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (db *roDB) GetVisitedBooks() (books []Book, err error) {
|
func (db *roDB) IncDownloads(ID string) error {
|
||||||
return db.db.GetVisitedBooks()
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (db *roDB) GetDownloadedBooks() (books []Book, err error) {
|
func (db *roDB) GetVisitedBooks(num int) (books []Book, err error) {
|
||||||
return db.db.GetDownloadedBooks()
|
return db.db.GetVisitedBooks(num)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (db *roDB) GetDownloadedBooks(num int) (books []Book, err error) {
|
||||||
|
return db.db.GetDownloadedBooks(num)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (db *roDB) GetTags() ([]string, error) {
|
func (db *roDB) GetTags() ([]string, error) {
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
// TODO
|
|
||||||
package database
|
package database
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
@ -7,20 +6,60 @@ import (
|
||||||
log "github.com/cihub/seelog"
|
log "github.com/cihub/seelog"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (db *pgDB) AddStats(stats interface{}) error {
|
type Visit struct {
|
||||||
return nil
|
ID int
|
||||||
|
Downloads int `sql:",notnull"`
|
||||||
|
Views int `sql:",notnull"`
|
||||||
|
BookID string
|
||||||
|
Book *Book
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Get the most visited books
|
func (db *pgDB) IncViews(ID string) error {
|
||||||
*/
|
visit := &Visit{
|
||||||
func (db *pgDB) GetVisitedBooks() (books []Book, err error) {
|
Downloads: 0,
|
||||||
return []Book{}, nil
|
Views: 1,
|
||||||
|
BookID: ID,
|
||||||
|
}
|
||||||
|
_, err := db.sql.Model(visit).
|
||||||
|
OnConflict("(book_id) DO UPDATE").
|
||||||
|
Set("views = Visit.views + 1").
|
||||||
|
Insert()
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Get the most downloaded books
|
func (db *pgDB) IncDownloads(ID string) error {
|
||||||
|
visit := &Visit{
|
||||||
|
Downloads: 1,
|
||||||
|
Views: 0,
|
||||||
|
BookID: ID,
|
||||||
|
}
|
||||||
|
_, err := db.sql.Model(visit).
|
||||||
|
OnConflict("(book_id) DO UPDATE").
|
||||||
|
Set("downloads = Visit.downloads + 1").
|
||||||
|
Insert()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
/* GetVisitedBooks get the most visited books
|
||||||
*/
|
*/
|
||||||
func (db *pgDB) GetDownloadedBooks() (books []Book, err error) {
|
func (db *pgDB) GetVisitedBooks(num int) (books []Book, err error) {
|
||||||
return []Book{}, nil
|
err = db.sql.Model(&books).
|
||||||
|
Column("Visit").
|
||||||
|
Order("views DESC NULLS LAST").
|
||||||
|
Limit(num).
|
||||||
|
Select()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
/* GetDownloadedBooks the most downloaded books
|
||||||
|
*/
|
||||||
|
func (db *pgDB) GetDownloadedBooks(num int) (books []Book, err error) {
|
||||||
|
err = db.sql.Model(&books).
|
||||||
|
Column("Visit").
|
||||||
|
Order("downloads DESC NULLS LAST").
|
||||||
|
Limit(num).
|
||||||
|
Select()
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (db *pgDB) GetTags() ([]string, error) {
|
func (db *pgDB) GetTags() ([]string, error) {
|
||||||
|
|
87
lib/stats.go
87
lib/stats.go
|
@ -5,7 +5,6 @@ import (
|
||||||
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
"gitlab.com/trantor/trantor/lib/database"
|
"gitlab.com/trantor/trantor/lib/database"
|
||||||
|
@ -67,82 +66,34 @@ func (sg StatsGatherer) Gather(function func(handler)) func(http.ResponseWriter,
|
||||||
}
|
}
|
||||||
|
|
||||||
function(h)
|
function(h)
|
||||||
sg.channel <- statsRequest{time.Now(), mux.Vars(r), h.sess, r}
|
sg.channel <- statsRequest{r}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type statsRequest struct {
|
type statsRequest struct {
|
||||||
date time.Time
|
r *http.Request
|
||||||
vars map[string]string
|
|
||||||
sess *Session
|
|
||||||
r *http.Request
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (sg StatsGatherer) worker() {
|
func (sg StatsGatherer) worker() {
|
||||||
for req := range sg.channel {
|
for req := range sg.channel {
|
||||||
stats := make(map[string]interface{})
|
var err error
|
||||||
appendFiles(req.r, stats)
|
pattern := strings.Split(req.r.URL.Path, "/")
|
||||||
appendMuxVars(req.vars, stats)
|
id := mux.Vars(req.r)["id"]
|
||||||
appendUrl(req.r, stats)
|
if len(pattern) > 1 && pattern[1] != "" && id != "" {
|
||||||
appendSession(req.sess, stats)
|
switch pattern[1] {
|
||||||
stats["version"] = stats_version
|
case "download":
|
||||||
stats["method"] = req.r.Method
|
id := mux.Vars(req.r)["id"]
|
||||||
stats["date"] = req.date
|
err = sg.db.IncDownloads(id)
|
||||||
sg.db.AddStats(stats)
|
case "book":
|
||||||
}
|
id := mux.Vars(req.r)["id"]
|
||||||
}
|
err = sg.db.IncViews(id)
|
||||||
|
case "read":
|
||||||
func appendFiles(r *http.Request, stats map[string]interface{}) {
|
id := mux.Vars(req.r)["id"]
|
||||||
if r.Method == "POST" && r.MultipartForm != nil {
|
err = sg.db.IncViews(id)
|
||||||
files := r.MultipartForm.File
|
|
||||||
for key := range files {
|
|
||||||
list := make([]string, len(files[key]))
|
|
||||||
for i, f := range files[key] {
|
|
||||||
list[i] = f.Filename
|
|
||||||
}
|
}
|
||||||
stats[key] = list
|
}
|
||||||
|
if err != nil {
|
||||||
|
log.Warn("Problem incrementing visits: ", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func appendMuxVars(vars map[string]string, stats map[string]interface{}) {
|
|
||||||
for key, value := range vars {
|
|
||||||
switch {
|
|
||||||
case key == "id":
|
|
||||||
stats["id"] = value
|
|
||||||
case key == "ids":
|
|
||||||
var objectIds []string
|
|
||||||
ids := strings.Split(value, "/")
|
|
||||||
for _, id := range ids {
|
|
||||||
objectIds = append(objectIds, id)
|
|
||||||
}
|
|
||||||
if len(objectIds) > 0 {
|
|
||||||
stats["ids"] = objectIds
|
|
||||||
stats["id"] = objectIds[0]
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
stats[key] = value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func appendUrl(r *http.Request, stats map[string]interface{}) {
|
|
||||||
for key, value := range r.URL.Query() {
|
|
||||||
stats[key] = value
|
|
||||||
}
|
|
||||||
stats["host"] = r.Host
|
|
||||||
stats["path"] = r.URL.Path
|
|
||||||
pattern := strings.Split(r.URL.Path, "/")
|
|
||||||
if len(pattern) > 1 && pattern[1] != "" {
|
|
||||||
stats["section"] = pattern[1]
|
|
||||||
} else {
|
|
||||||
stats["section"] = "/"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func appendSession(sess *Session, stats map[string]interface{}) {
|
|
||||||
stats["session"] = sess.Id()
|
|
||||||
if sess.User != "" {
|
|
||||||
stats["user"] = sess.User
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -110,13 +110,26 @@ type indexData struct {
|
||||||
|
|
||||||
func indexHandler(h handler) {
|
func indexHandler(h handler) {
|
||||||
var data indexData
|
var data indexData
|
||||||
|
var err error
|
||||||
|
|
||||||
data.Tags, _ = h.db.GetTags()
|
data.Tags, err = h.db.GetTags()
|
||||||
|
if err != nil {
|
||||||
|
log.Warn("Problem getting tags: ", err)
|
||||||
|
}
|
||||||
data.S = GetStatus(h)
|
data.S = GetStatus(h)
|
||||||
data.S.Home = true
|
data.S.Home = true
|
||||||
data.Books, data.Count, _ = h.db.GetBooks("", booksFrontPage, 0)
|
data.Books, data.Count, err = h.db.GetBooks("", booksFrontPage, 0)
|
||||||
data.VisitedBooks, _ = h.db.GetVisitedBooks()
|
if err != nil {
|
||||||
data.DownloadedBooks, _ = h.db.GetDownloadedBooks()
|
log.Warn("Problem getting front books: ", err)
|
||||||
|
}
|
||||||
|
data.VisitedBooks, err = h.db.GetVisitedBooks(booksFrontPage)
|
||||||
|
if err != nil {
|
||||||
|
log.Warn("Problem getting visited books: ", err)
|
||||||
|
}
|
||||||
|
data.DownloadedBooks, err = h.db.GetDownloadedBooks(booksFrontPage)
|
||||||
|
if err != nil {
|
||||||
|
log.Warn("Problem getting downloaded books: ", err)
|
||||||
|
}
|
||||||
data.News = getNews(1, daysNewsIndexpage, h.db)
|
data.News = getNews(1, daysNewsIndexpage, h.db)
|
||||||
h.template.load(h, "index", data)
|
h.template.load(h, "index", data)
|
||||||
}
|
}
|
||||||
|
|
Reference in a new issue