2021-01-04 19:29:49 +00:00
package main
import (
2021-04-28 18:03:20 +00:00
"database/sql"
2021-01-04 19:29:49 +00:00
"net/http"
2021-05-09 07:35:37 +00:00
"sync"
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-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
var blogStatsCache = map [ string ] map [ string ] interface { } { }
var blogStatsCacheMutex sync . RWMutex
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 ) {
blogStatsCacheMutex . RLock ( )
if data , ok := blogStatsCache [ blog ] ; ok && data != nil {
blogStatsCacheMutex . RUnlock ( )
return data , nil
}
blogStatsCacheMutex . RUnlock ( )
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-09 07:35:37 +00:00
blogStatsCacheMutex . Lock ( )
blogStatsCache [ blog ] = map [ string ] interface { } {
"total" : total ,
"years" : years ,
"withoutdate" : noDate ,
"months" : months ,
}
data = blogStatsCache [ blog ]
blogStatsCacheMutex . Unlock ( )
return data , nil
}
func clearBlogStatsCache ( ) {
blogStatsCacheMutex . Lock ( )
blogStatsCache = map [ string ] map [ string ] interface { } { }
blogStatsCacheMutex . Unlock ( )
2021-01-04 19:29:49 +00:00
}