mirror of https://github.com/jlelse/GoBlog
Fix blogstats
This commit is contained in:
parent
f04e731efc
commit
87f574991e
82
blogstats.go
82
blogstats.go
|
@ -1,10 +1,12 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"encoding/json"
|
"encoding/gob"
|
||||||
"log"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
|
servertiming "github.com/mitchellh/go-server-timing"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (a *goBlog) initBlogStats() {
|
func (a *goBlog) initBlogStats() {
|
||||||
|
@ -31,9 +33,11 @@ func (a *goBlog) serveBlogStats(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
func (a *goBlog) serveBlogStatsTable(w http.ResponseWriter, r *http.Request) {
|
func (a *goBlog) serveBlogStatsTable(w http.ResponseWriter, r *http.Request) {
|
||||||
blog := r.Context().Value(blogContextKey).(string)
|
blog := r.Context().Value(blogContextKey).(string)
|
||||||
|
t := servertiming.FromContext(r.Context()).NewMetric("bs").Start()
|
||||||
data, err, _ := a.blogStatsCacheGroup.Do(blog, func() (interface{}, error) {
|
data, err, _ := a.blogStatsCacheGroup.Do(blog, func() (interface{}, error) {
|
||||||
return a.db.getBlogStats(blog)
|
return a.db.getBlogStats(blog)
|
||||||
})
|
})
|
||||||
|
t.Stop()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
a.serveError(w, r, err.Error(), http.StatusInternalServerError)
|
a.serveError(w, r, err.Error(), http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
|
@ -49,13 +53,19 @@ const blogStatsSql = `
|
||||||
with filtered as (
|
with filtered as (
|
||||||
select
|
select
|
||||||
path,
|
path,
|
||||||
coalesce(published, '') as pub,
|
pub,
|
||||||
substr(published, 1, 4) as year,
|
substr(pub, 1, 4) as year,
|
||||||
substr(published, 6, 2) as month,
|
substr(pub, 6, 2) as month,
|
||||||
wordcount(coalesce(content, '')) as words,
|
wordcount(content) as words,
|
||||||
charcount(coalesce(content, '')) as chars
|
charcount(content) as chars
|
||||||
from posts
|
from (
|
||||||
where status = @status and blog = @blog
|
select
|
||||||
|
path,
|
||||||
|
coalesce(published, '') as pub,
|
||||||
|
mdtext(coalesce(content, '')) as content
|
||||||
|
from posts
|
||||||
|
where status = @status and blog = @blog
|
||||||
|
)
|
||||||
)
|
)
|
||||||
select *
|
select *
|
||||||
from (
|
from (
|
||||||
|
@ -116,7 +126,18 @@ from (
|
||||||
);
|
);
|
||||||
`
|
`
|
||||||
|
|
||||||
func (db *database) getBlogStats(blog string) (data map[string]interface{}, err error) {
|
type blogStatsRow struct {
|
||||||
|
Name, Posts, Chars, Words, WordsPerPost string
|
||||||
|
}
|
||||||
|
|
||||||
|
type blogStatsData struct {
|
||||||
|
Total blogStatsRow
|
||||||
|
NoDate blogStatsRow
|
||||||
|
Years []blogStatsRow
|
||||||
|
Months map[string][]blogStatsRow
|
||||||
|
}
|
||||||
|
|
||||||
|
func (db *database) getBlogStats(blog string) (data *blogStatsData, err error) {
|
||||||
// Check cache
|
// Check cache
|
||||||
if stats := db.loadBlogStatsCache(blog); stats != nil {
|
if stats := db.loadBlogStatsCache(blog); stats != nil {
|
||||||
return stats, nil
|
return stats, nil
|
||||||
|
@ -124,18 +145,13 @@ func (db *database) getBlogStats(blog string) (data map[string]interface{}, err
|
||||||
// Prevent creating posts while getting stats
|
// Prevent creating posts while getting stats
|
||||||
db.pcm.Lock()
|
db.pcm.Lock()
|
||||||
defer db.pcm.Unlock()
|
defer db.pcm.Unlock()
|
||||||
// Stats type to hold the stats data for a single row
|
|
||||||
type statsTableType struct {
|
|
||||||
Name, Posts, Chars, Words, WordsPerPost string
|
|
||||||
}
|
|
||||||
// Scan objects
|
// Scan objects
|
||||||
currentStats := statsTableType{}
|
currentStats := blogStatsRow{}
|
||||||
var currentMonth, currentYear string
|
var currentMonth, currentYear string
|
||||||
// Data to later return
|
// Data to later return
|
||||||
var total statsTableType
|
data = &blogStatsData{
|
||||||
var noDate statsTableType
|
Months: map[string][]blogStatsRow{},
|
||||||
var years []statsTableType
|
}
|
||||||
months := map[string][]statsTableType{}
|
|
||||||
// Query and scan
|
// Query and scan
|
||||||
rows, err := db.query(blogStatsSql, sql.Named("status", statusPublished), sql.Named("blog", blog))
|
rows, err := db.query(blogStatsSql, sql.Named("status", statusPublished), sql.Named("blog", blog))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -144,21 +160,21 @@ func (db *database) getBlogStats(blog string) (data map[string]interface{}, err
|
||||||
for rows.Next() {
|
for rows.Next() {
|
||||||
err = rows.Scan(¤tYear, ¤tMonth, ¤tStats.Posts, ¤tStats.Words, ¤tStats.Chars, ¤tStats.WordsPerPost)
|
err = rows.Scan(¤tYear, ¤tMonth, ¤tStats.Posts, ¤tStats.Words, ¤tStats.Chars, ¤tStats.WordsPerPost)
|
||||||
if currentYear == "A" && currentMonth == "A" {
|
if currentYear == "A" && currentMonth == "A" {
|
||||||
total = statsTableType{
|
data.Total = blogStatsRow{
|
||||||
Posts: currentStats.Posts,
|
Posts: currentStats.Posts,
|
||||||
Words: currentStats.Words,
|
Words: currentStats.Words,
|
||||||
Chars: currentStats.Chars,
|
Chars: currentStats.Chars,
|
||||||
WordsPerPost: currentStats.WordsPerPost,
|
WordsPerPost: currentStats.WordsPerPost,
|
||||||
}
|
}
|
||||||
} else if currentYear == "N" && currentMonth == "N" {
|
} else if currentYear == "N" && currentMonth == "N" {
|
||||||
noDate = statsTableType{
|
data.NoDate = blogStatsRow{
|
||||||
Posts: currentStats.Posts,
|
Posts: currentStats.Posts,
|
||||||
Words: currentStats.Words,
|
Words: currentStats.Words,
|
||||||
Chars: currentStats.Chars,
|
Chars: currentStats.Chars,
|
||||||
WordsPerPost: currentStats.WordsPerPost,
|
WordsPerPost: currentStats.WordsPerPost,
|
||||||
}
|
}
|
||||||
} else if currentMonth == "A" {
|
} else if currentMonth == "A" {
|
||||||
years = append(years, statsTableType{
|
data.Years = append(data.Years, blogStatsRow{
|
||||||
Name: currentYear,
|
Name: currentYear,
|
||||||
Posts: currentStats.Posts,
|
Posts: currentStats.Posts,
|
||||||
Words: currentStats.Words,
|
Words: currentStats.Words,
|
||||||
|
@ -166,7 +182,7 @@ func (db *database) getBlogStats(blog string) (data map[string]interface{}, err
|
||||||
WordsPerPost: currentStats.WordsPerPost,
|
WordsPerPost: currentStats.WordsPerPost,
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
months[currentYear] = append(months[currentYear], statsTableType{
|
data.Months[currentYear] = append(data.Months[currentYear], blogStatsRow{
|
||||||
Name: currentMonth,
|
Name: currentMonth,
|
||||||
Posts: currentStats.Posts,
|
Posts: currentStats.Posts,
|
||||||
Words: currentStats.Words,
|
Words: currentStats.Words,
|
||||||
|
@ -175,29 +191,25 @@ func (db *database) getBlogStats(blog string) (data map[string]interface{}, err
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
data = map[string]interface{}{
|
|
||||||
"total": total,
|
|
||||||
"years": years,
|
|
||||||
"withoutdate": noDate,
|
|
||||||
"months": months,
|
|
||||||
}
|
|
||||||
db.cacheBlogStats(blog, data)
|
db.cacheBlogStats(blog, data)
|
||||||
return data, nil
|
return data, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (db *database) cacheBlogStats(blog string, stats map[string]interface{}) {
|
func (db *database) cacheBlogStats(blog string, stats *blogStatsData) {
|
||||||
jb, _ := json.Marshal(stats)
|
var buf bytes.Buffer
|
||||||
_ = db.cachePersistently("blogstats_"+blog, jb)
|
_ = gob.NewEncoder(&buf).Encode(stats)
|
||||||
|
_ = db.cachePersistently("blogstats_"+blog, buf.Bytes())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (db *database) loadBlogStatsCache(blog string) (stats map[string]interface{}) {
|
func (db *database) loadBlogStatsCache(blog string) (stats *blogStatsData) {
|
||||||
data, err := db.retrievePersistentCache("blogstats_" + blog)
|
data, err := db.retrievePersistentCache("blogstats_" + blog)
|
||||||
if err != nil || data == nil {
|
if err != nil || data == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
err = json.Unmarshal(data, &stats)
|
stats = &blogStatsData{}
|
||||||
|
err = gob.NewDecoder(bytes.NewReader(data)).Decode(stats)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println(err)
|
return nil
|
||||||
}
|
}
|
||||||
return stats
|
return stats
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,8 +10,8 @@
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{{ $months := .Data.months }}
|
{{ $months := .Data.Months }}
|
||||||
{{ range $year := .Data.years }}
|
{{ range $year := .Data.Years }}
|
||||||
<tr class="statsyear" data-year="{{ $year.Name }}">
|
<tr class="statsyear" data-year="{{ $year.Name }}">
|
||||||
<td class="tal">{{ $year.Name }}</td>
|
<td class="tal">{{ $year.Name }}</td>
|
||||||
<td class="tar">{{ $year.Posts }}</td>
|
<td class="tar">{{ $year.Posts }}</td>
|
||||||
|
@ -31,17 +31,17 @@
|
||||||
{{ end }}
|
{{ end }}
|
||||||
<tr>
|
<tr>
|
||||||
<td class="tal">{{ string .Blog.Lang "withoutdate" }}</td>
|
<td class="tal">{{ string .Blog.Lang "withoutdate" }}</td>
|
||||||
<td class="tar">{{ .Data.withoutdate.Posts }}</td>
|
<td class="tar">{{ .Data.NoDate.Posts }}</td>
|
||||||
<td class="tar">{{ .Data.withoutdate.Chars }}</td>
|
<td class="tar">{{ .Data.NoDate.Chars }}</td>
|
||||||
<td class="tar">{{ .Data.withoutdate.Words }}</td>
|
<td class="tar">{{ .Data.NoDate.Words }}</td>
|
||||||
<td class="tar">{{ .Data.withoutdate.WordsPerPost }}</td>
|
<td class="tar">{{ .Data.NoDate.WordsPerPost }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td class="tal"><b>{{ string .Blog.Lang "total" }}</b></td>
|
<td class="tal"><b>{{ string .Blog.Lang "total" }}</b></td>
|
||||||
<td class="tar">{{ .Data.total.Posts }}</td>
|
<td class="tar">{{ .Data.Total.Posts }}</td>
|
||||||
<td class="tar">{{ .Data.total.Chars }}</td>
|
<td class="tar">{{ .Data.Total.Chars }}</td>
|
||||||
<td class="tar">{{ .Data.total.Words }}</td>
|
<td class="tar">{{ .Data.Total.Words }}</td>
|
||||||
<td class="tar">{{ .Data.total.WordsPerPost }}</td>
|
<td class="tar">{{ .Data.Total.WordsPerPost }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|
Loading…
Reference in New Issue