mirror of https://github.com/jlelse/GoBlog
144 lines
4.5 KiB
Go
144 lines
4.5 KiB
Go
package main
|
|
|
|
import (
|
|
"database/sql"
|
|
"encoding/json"
|
|
"log"
|
|
"net/http"
|
|
)
|
|
|
|
func (a *goBlog) initBlogStats() {
|
|
f := func(p *post) {
|
|
a.db.resetBlogStats(p.Blog)
|
|
}
|
|
a.pPostHooks = append(a.pPostHooks, f)
|
|
a.pUpdateHooks = append(a.pUpdateHooks, f)
|
|
a.pDeleteHooks = append(a.pDeleteHooks, f)
|
|
}
|
|
|
|
func (a *goBlog) serveBlogStats(w http.ResponseWriter, r *http.Request) {
|
|
blog := r.Context().Value(blogContextKey).(string)
|
|
bc := a.cfg.Blogs[blog]
|
|
canonical := bc.getRelativePath(bc.BlogStats.Path)
|
|
a.render(w, r, templateBlogStats, &renderData{
|
|
BlogString: blog,
|
|
Canonical: a.getFullAddress(canonical),
|
|
Data: map[string]interface{}{
|
|
"TableUrl": canonical + ".table.html",
|
|
},
|
|
})
|
|
}
|
|
|
|
func (a *goBlog) serveBlogStatsTable(w http.ResponseWriter, r *http.Request) {
|
|
blog := r.Context().Value(blogContextKey).(string)
|
|
data, err, _ := a.blogStatsCacheGroup.Do(blog, func() (interface{}, error) {
|
|
return a.db.getBlogStats(blog)
|
|
})
|
|
if err != nil {
|
|
a.serveError(w, r, err.Error(), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
// Render
|
|
a.render(w, r, templateBlogStatsTable, &renderData{
|
|
BlogString: blog,
|
|
Data: data,
|
|
})
|
|
}
|
|
|
|
func (db *database) getBlogStats(blog string) (data map[string]interface{}, err error) {
|
|
if stats := db.loadBlogStatsCache(blog); stats != nil {
|
|
return stats, nil
|
|
}
|
|
// Build query
|
|
prq := &postsRequestConfig{
|
|
blog: blog,
|
|
status: statusPublished,
|
|
}
|
|
query, params := buildPostsQuery(prq)
|
|
query = "select path, mdtext(content) as content, published, substr(published, 1, 4) as year, substr(published, 6, 2) as month from (" + query + ")"
|
|
postCount := "coalesce(count(distinct path), 0) as postcount"
|
|
charCount := "coalesce(sum(coalesce(charcount(distinct content), 0)), 0)"
|
|
wordCount := "coalesce(sum(wordcount(distinct content)), 0) as wordcount"
|
|
wordsPerPost := "coalesce(round(wordcount/postcount,0), 0)"
|
|
type statsTableType struct {
|
|
Name, Posts, Chars, Words, WordsPerPost string
|
|
}
|
|
// Count total posts
|
|
row, err := db.queryRow("select *, "+wordsPerPost+" from (select "+postCount+", "+charCount+", "+wordCount+" from ("+query+"))", params...)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
total := statsTableType{}
|
|
if err = row.Scan(&total.Posts, &total.Chars, &total.Words, &total.WordsPerPost); err != nil {
|
|
return nil, err
|
|
}
|
|
// Count posts per year
|
|
rows, err := db.query("select *, "+wordsPerPost+" from (select year, "+postCount+", "+charCount+", "+wordCount+" from ("+query+") where published != '' group by year order by year desc)", params...)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
var years []statsTableType
|
|
year := statsTableType{}
|
|
for rows.Next() {
|
|
if err = rows.Scan(&year.Name, &year.Posts, &year.Chars, &year.Words, &year.WordsPerPost); err == nil {
|
|
years = append(years, year)
|
|
} else {
|
|
return nil, err
|
|
}
|
|
}
|
|
// Count posts without date
|
|
row, err = db.queryRow("select *, "+wordsPerPost+" from (select "+postCount+", "+charCount+", "+wordCount+" from ("+query+") where published = '')", params...)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
noDate := statsTableType{}
|
|
if err = row.Scan(&noDate.Posts, &noDate.Chars, &noDate.Words, &noDate.WordsPerPost); err != nil {
|
|
return nil, err
|
|
}
|
|
// Count posts per month per year
|
|
months := map[string][]statsTableType{}
|
|
month := statsTableType{}
|
|
for _, year := range years {
|
|
rows, err = db.query("select *, "+wordsPerPost+" from (select month, "+postCount+", "+charCount+", "+wordCount+" from ("+query+") where published != '' and year = @year group by month order by month desc)", append(params, sql.Named("year", year.Name))...)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
for rows.Next() {
|
|
if err = rows.Scan(&month.Name, &month.Posts, &month.Chars, &month.Words, &month.WordsPerPost); err == nil {
|
|
months[year.Name] = append(months[year.Name], month)
|
|
} else {
|
|
return nil, err
|
|
}
|
|
}
|
|
}
|
|
data = map[string]interface{}{
|
|
"total": total,
|
|
"years": years,
|
|
"withoutdate": noDate,
|
|
"months": months,
|
|
}
|
|
db.cacheBlogStats(blog, data)
|
|
return data, nil
|
|
}
|
|
|
|
func (db *database) cacheBlogStats(blog string, stats map[string]interface{}) {
|
|
jb, _ := json.Marshal(stats)
|
|
_ = db.cachePersistently("blogstats_"+blog, jb)
|
|
}
|
|
|
|
func (db *database) loadBlogStatsCache(blog string) (stats map[string]interface{}) {
|
|
data, err := db.retrievePersistentCache("blogstats_" + blog)
|
|
if err != nil || data == nil {
|
|
return nil
|
|
}
|
|
err = json.Unmarshal(data, &stats)
|
|
if err != nil {
|
|
log.Println(err)
|
|
}
|
|
return stats
|
|
}
|
|
|
|
func (db *database) resetBlogStats(blog string) {
|
|
_ = db.clearPersistentCache("blogstats_" + blog)
|
|
}
|