2021-01-04 19:29:49 +00:00
package main
import (
2021-04-28 18:03:20 +00:00
"database/sql"
2021-05-10 15:37:34 +00:00
"encoding/json"
"log"
2021-01-04 19:29:49 +00:00
"net/http"
2021-04-28 18:03:20 +00:00
2021-05-09 07:35:37 +00:00
"golang.org/x/sync/singleflight"
2021-01-04 19:29:49 +00:00
)
2021-05-10 15:37:34 +00:00
func initBlogStats ( ) {
f := func ( p * post ) {
resetBlogStats ( p . Blog )
}
postHooks [ postPostHook ] = append ( postHooks [ postPostHook ] , f )
postHooks [ postUpdateHook ] = append ( postHooks [ postPostHook ] , f )
postHooks [ postDeleteHook ] = append ( postHooks [ postPostHook ] , f )
}
2021-03-22 07:20:56 +00:00
func serveBlogStats ( w http . ResponseWriter , r * http . Request ) {
2021-05-09 07:08:31 +00:00
blog := r . Context ( ) . Value ( blogContextKey ) . ( string )
canonical := blogPath ( blog ) + appConfig . Blogs [ blog ] . BlogStats . Path
render ( w , r , templateBlogStats , & renderData {
BlogString : blog ,
Canonical : canonical ,
Data : map [ string ] interface { } {
"TableUrl" : canonical + ".table.html" ,
} ,
} )
}
2021-05-09 07:35:37 +00:00
var blogStatsCacheGroup singleflight . Group
2021-05-09 07:08:31 +00:00
func serveBlogStatsTable ( w http . ResponseWriter , r * http . Request ) {
2021-03-22 07:20:56 +00:00
blog := r . Context ( ) . Value ( blogContextKey ) . ( string )
2021-05-09 07:35:37 +00:00
data , err , _ := blogStatsCacheGroup . Do ( blog , func ( ) ( interface { } , error ) {
return getBlogStats ( blog )
} )
if err != nil {
serveError ( w , r , err . Error ( ) , http . StatusInternalServerError )
return
}
// Render
render ( w , r , templateBlogStatsTable , & renderData {
BlogString : blog ,
Data : data ,
} )
}
func getBlogStats ( blog string ) ( data map [ string ] interface { } , err error ) {
2021-05-10 15:37:34 +00:00
if stats := loadBlogStatsCache ( blog ) ; stats != nil {
return stats , nil
2021-05-09 07:35:37 +00:00
}
2021-03-22 07:20:56 +00:00
// Build query
2021-04-23 18:52:12 +00:00
prq := & postsRequestConfig {
2021-03-22 07:20:56 +00:00
blog : blog ,
status : statusPublished ,
2021-04-23 18:52:12 +00:00
}
query , params := buildPostsQuery ( prq )
2021-05-09 07:08:31 +00:00
query = "select path, mdtext(content) as content, published, substr(published, 1, 4) as year, substr(published, 6, 2) as month from (" + query + ")"
2021-04-30 16:42:10 +00:00
postCount := "coalesce(count(distinct path), 0) as postcount"
charCount := "coalesce(sum(coalesce(length(distinct content), 0)), 0)"
wordCount := "coalesce(sum(wordcount(distinct content)), 0) as wordcount"
wordsPerPost := "coalesce(round(wordcount/postcount,0), 0)"
2021-04-28 18:03:20 +00:00
type statsTableType struct {
Name , Posts , Chars , Words , WordsPerPost string
}
2021-03-22 07:20:56 +00:00
// Count total posts
2021-04-28 18:03:20 +00:00
row , err := appDbQueryRow ( "select *, " + wordsPerPost + " from (select " + postCount + ", " + charCount + ", " + wordCount + " from (" + query + "))" , params ... )
2021-03-22 07:20:56 +00:00
if err != nil {
2021-05-09 07:35:37 +00:00
return nil , err
2021-03-22 07:20:56 +00:00
}
2021-04-28 18:03:20 +00:00
total := statsTableType { }
if err = row . Scan ( & total . Posts , & total . Chars , & total . Words , & total . WordsPerPost ) ; err != nil {
2021-05-09 07:35:37 +00:00
return nil , err
2021-03-22 07:20:56 +00:00
}
// Count posts per year
2021-05-09 07:08:31 +00:00
rows , err := appDbQuery ( "select *, " + wordsPerPost + " from (select year, " + postCount + ", " + charCount + ", " + wordCount + " from (" + query + ") where published != '' group by year order by year desc)" , params ... )
2021-03-22 07:20:56 +00:00
if err != nil {
2021-05-09 07:35:37 +00:00
return nil , err
2021-03-22 07:20:56 +00:00
}
2021-04-28 18:03:20 +00:00
var years [ ] statsTableType
year := statsTableType { }
2021-03-22 07:20:56 +00:00
for rows . Next ( ) {
2021-04-28 18:03:20 +00:00
if err = rows . Scan ( & year . Name , & year . Posts , & year . Chars , & year . Words , & year . WordsPerPost ) ; err == nil {
years = append ( years , year )
2021-04-23 18:52:12 +00:00
} else {
2021-05-09 07:35:37 +00:00
return nil , err
2021-04-23 18:52:12 +00:00
}
}
// Count posts without date
2021-04-28 18:03:20 +00:00
row , err = appDbQueryRow ( "select *, " + wordsPerPost + " from (select " + postCount + ", " + charCount + ", " + wordCount + " from (" + query + ") where published = '')" , params ... )
2021-04-23 18:52:12 +00:00
if err != nil {
2021-05-09 07:35:37 +00:00
return nil , err
2021-04-23 18:52:12 +00:00
}
2021-04-28 18:03:20 +00:00
noDate := statsTableType { }
if err = row . Scan ( & noDate . Posts , & noDate . Chars , & noDate . Words , & noDate . WordsPerPost ) ; err != nil {
2021-05-09 07:35:37 +00:00
return nil , err
2021-04-23 18:52:12 +00:00
}
// Count posts per month per year
2021-04-28 18:03:20 +00:00
months := map [ string ] [ ] statsTableType { }
month := statsTableType { }
2021-04-23 18:52:12 +00:00
for _ , year := range years {
2021-05-09 07:08:31 +00:00
rows , err = appDbQuery ( "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 ) ) ... )
2021-04-23 18:52:12 +00:00
if err != nil {
2021-05-09 07:35:37 +00:00
return nil , err
2021-04-23 18:52:12 +00:00
}
for rows . Next ( ) {
2021-04-28 18:03:20 +00:00
if err = rows . Scan ( & month . Name , & month . Posts , & month . Chars , & month . Words , & month . WordsPerPost ) ; err == nil {
months [ year . Name ] = append ( months [ year . Name ] , month )
2021-04-23 18:52:12 +00:00
} else {
2021-05-09 07:35:37 +00:00
return nil , err
2021-04-23 18:52:12 +00:00
}
2021-01-04 19:29:49 +00:00
}
}
2021-05-10 15:37:34 +00:00
data = map [ string ] interface { } {
2021-05-09 07:35:37 +00:00
"total" : total ,
"years" : years ,
"withoutdate" : noDate ,
"months" : months ,
}
2021-05-10 15:37:34 +00:00
cacheBlogStats ( blog , data )
2021-05-09 07:35:37 +00:00
return data , nil
}
2021-05-10 15:37:34 +00:00
func cacheBlogStats ( blog string , stats map [ string ] interface { } ) {
jb , _ := json . Marshal ( stats )
cachePersistently ( "blogstats_" + blog , jb )
}
func loadBlogStatsCache ( blog string ) ( stats map [ string ] interface { } ) {
data , err := retrievePersistentCache ( "blogstats_" + blog )
if err != nil || data == nil {
return nil
}
err = json . Unmarshal ( data , & stats )
if err != nil {
log . Println ( err )
}
return stats
}
func resetBlogStats ( blog string ) {
clearPersistentCache ( "blogstats_" + blog )
2021-01-04 19:29:49 +00:00
}