mirror of https://github.com/jlelse/GoBlog
Advance the blogstats even more
This commit is contained in:
parent
47ddcc4028
commit
e15d33b0dd
54
blogstats.go
54
blogstats.go
|
@ -1,83 +1,95 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
servertiming "github.com/mitchellh/go-server-timing"
|
||||
)
|
||||
|
||||
func serveBlogStats(w http.ResponseWriter, r *http.Request) {
|
||||
blog := r.Context().Value(blogContextKey).(string)
|
||||
// Start timing
|
||||
t := servertiming.FromContext(r.Context()).NewMetric("sq").Start()
|
||||
// Build query
|
||||
prq := &postsRequestConfig{
|
||||
blog: blog,
|
||||
status: statusPublished,
|
||||
}
|
||||
query, params := buildPostsQuery(prq)
|
||||
postCount := "count(distinct path) as postcount"
|
||||
charCount := "sum(length(distinct content))"
|
||||
wordCount := "sum(wordcount(distinct content)) as wordcount"
|
||||
wordsPerPost := "round(wordcount/postcount,0)"
|
||||
type statsTableType struct {
|
||||
Name, Posts, Chars, Words, WordsPerPost string
|
||||
}
|
||||
// Count total posts
|
||||
row, err := appDbQueryRow("select count(distinct path) from ("+query+")", params...)
|
||||
row, err := appDbQueryRow("select *, "+wordsPerPost+" from (select "+postCount+", "+charCount+", "+wordCount+" from ("+query+"))", params...)
|
||||
if err != nil {
|
||||
serveError(w, r, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
var totalCount int
|
||||
if err = row.Scan(&totalCount); err != nil {
|
||||
total := statsTableType{}
|
||||
if err = row.Scan(&total.Posts, &total.Chars, &total.Words, &total.WordsPerPost); err != nil {
|
||||
serveError(w, r, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
// Count posts per year
|
||||
rows, err := appDbQuery("select substr(published, 1, 4) as year, count(distinct path) as count from ("+query+") where published != '' group by year order by year desc", params...)
|
||||
rows, err := appDbQuery("select *, "+wordsPerPost+" from (select substr(published, 1, 4) as year, "+postCount+", "+charCount+", "+wordCount+" from ("+query+") where published != '' group by year order by year desc)", params...)
|
||||
if err != nil {
|
||||
serveError(w, r, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
var years []stringPair
|
||||
var years []statsTableType
|
||||
year := statsTableType{}
|
||||
for rows.Next() {
|
||||
var year, count string
|
||||
if err = rows.Scan(&year, &count); err == nil {
|
||||
years = append(years, stringPair{year, count})
|
||||
if err = rows.Scan(&year.Name, &year.Posts, &year.Chars, &year.Words, &year.WordsPerPost); err == nil {
|
||||
years = append(years, year)
|
||||
} else {
|
||||
serveError(w, r, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
}
|
||||
// Count posts without date
|
||||
row, err = appDbQueryRow("select count(distinct path) from ("+query+") where published = ''", params...)
|
||||
row, err = appDbQueryRow("select *, "+wordsPerPost+" from (select "+postCount+", "+charCount+", "+wordCount+" from ("+query+") where published = '')", params...)
|
||||
if err != nil {
|
||||
serveError(w, r, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
var noDateCount int
|
||||
if err = row.Scan(&noDateCount); err != nil {
|
||||
noDate := statsTableType{}
|
||||
if err = row.Scan(&noDate.Posts, &noDate.Chars, &noDate.Words, &noDate.WordsPerPost); err != nil {
|
||||
serveError(w, r, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
// Count posts per month per year
|
||||
months := map[string][]stringPair{}
|
||||
months := map[string][]statsTableType{}
|
||||
month := statsTableType{}
|
||||
for _, year := range years {
|
||||
prq.publishedYear, _ = strconv.Atoi(year.First)
|
||||
query, params = buildPostsQuery(prq)
|
||||
rows, err = appDbQuery("select substr(published, 6, 2) as month, count(distinct path) as count from ("+query+") where published != '' group by month order by month desc", params...)
|
||||
rows, err = appDbQuery("select *, "+wordsPerPost+" from (select substr(published, 6, 2) as month, "+postCount+", "+charCount+", "+wordCount+" from ("+query+") where published != '' and substr(published, 1, 4) = @year group by month order by month desc)", append(params, sql.Named("year", year.Name))...)
|
||||
if err != nil {
|
||||
serveError(w, r, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
for rows.Next() {
|
||||
var month, count string
|
||||
if err = rows.Scan(&month, &count); err == nil {
|
||||
months[year.First] = append(months[year.First], stringPair{month, count})
|
||||
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 {
|
||||
serveError(w, r, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
// Stop timing
|
||||
t.Stop()
|
||||
// Render
|
||||
render(w, r, templateBlogStats, &renderData{
|
||||
BlogString: blog,
|
||||
Canonical: blogPath(blog) + appConfig.Blogs[blog].BlogStats.Path,
|
||||
Data: map[string]interface{}{
|
||||
"total": totalCount,
|
||||
"total": total,
|
||||
"years": years,
|
||||
"withoutdate": noDateCount,
|
||||
"withoutdate": noDate,
|
||||
"months": months,
|
||||
},
|
||||
})
|
||||
|
|
|
@ -22,6 +22,9 @@ func initDatabase() (err error) {
|
|||
if err := c.RegisterFunc("tolocal", toLocalSafe, true); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := c.RegisterFunc("wordcount", wordCount, true); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
},
|
||||
})
|
||||
|
|
7
go.mod
7
go.mod
|
@ -13,6 +13,7 @@ require (
|
|||
github.com/dgraph-io/ristretto v0.0.4-0.20210311064603-e4f298c8aa88
|
||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible
|
||||
github.com/elnormous/contenttype v1.0.0
|
||||
github.com/felixge/httpsnoop v1.0.2 // indirect
|
||||
github.com/go-chi/chi/v5 v5.0.2
|
||||
github.com/go-fed/httpsig v1.1.0
|
||||
github.com/go-sql-driver/mysql v1.5.0 // indirect
|
||||
|
@ -47,7 +48,7 @@ require (
|
|||
github.com/spf13/jwalterweatherman v1.1.0 // indirect
|
||||
github.com/spf13/viper v1.7.1
|
||||
github.com/tdewolff/minify/v2 v2.9.16
|
||||
github.com/tdewolff/parse/v2 v2.5.15 // indirect
|
||||
github.com/tdewolff/parse/v2 v2.5.16 // indirect
|
||||
github.com/thoas/go-funk v0.8.0
|
||||
github.com/tomnomnom/linkheader v0.0.0-20180905144013-02ca5825eb80
|
||||
github.com/vcraescu/go-paginator v1.0.1-0.20201114172518-2cfc59fe05c2
|
||||
|
@ -58,9 +59,9 @@ require (
|
|||
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b // indirect
|
||||
golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5 // indirect
|
||||
golang.org/x/mod v0.4.1 // indirect
|
||||
golang.org/x/net v0.0.0-20210421230115-4e50805a0758
|
||||
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da // indirect
|
||||
golang.org/x/sys v0.0.0-20210426230700-d19ff857e887 // indirect
|
||||
golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf // indirect
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
|
||||
gopkg.in/ini.v1 v1.62.0 // indirect
|
||||
|
|
13
go.sum
13
go.sum
|
@ -73,8 +73,9 @@ github.com/elnormous/contenttype v1.0.0 h1:cTLou7K7uQMsPEmRiTJosAznsPcYuoBmXMrFA
|
|||
github.com/elnormous/contenttype v1.0.0/go.mod h1:ngVcyGGU8pnn4QJ5sL4StrNgc/wmXZXy5IQSBuHOFPg=
|
||||
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
||||
github.com/felixge/httpsnoop v1.0.0/go.mod h1:3+D9sFq0ahK/JeJPhCBUV1xlf4/eIYrUQaxulT0VzX8=
|
||||
github.com/felixge/httpsnoop v1.0.1 h1:lvB5Jl89CsZtGIWuTcDM1E/vkVs49/Ml7JJe07l8SPQ=
|
||||
github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
||||
github.com/felixge/httpsnoop v1.0.2 h1:+nS9g82KMXccJ/wp0zyRW9ZBHFETmMGtkk+2CTTrW4o=
|
||||
github.com/felixge/httpsnoop v1.0.2/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
||||
github.com/fsnotify/fsnotify v1.4.3-0.20170329110642-4da3e2cfbabc/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
|
||||
|
@ -325,8 +326,8 @@ github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69
|
|||
github.com/tdewolff/minify/v2 v2.9.16 h1:2Pv8pFRX/ZfjTRYX2xzcuNrkEJqU5TfriNJJYOeN3rI=
|
||||
github.com/tdewolff/minify/v2 v2.9.16/go.mod h1:cjMkr4ZgFjqxXAQ1kR9Fm4l1046mmONd2g6yMzGuN/w=
|
||||
github.com/tdewolff/parse/v2 v2.5.14/go.mod h1:WzaJpRSbwq++EIQHYIRTpbYKNA3gn9it1Ik++q4zyho=
|
||||
github.com/tdewolff/parse/v2 v2.5.15 h1:hYZKJZ0KfHMGhN3+hER4R9gQM/umJThkeeyJNtsO86o=
|
||||
github.com/tdewolff/parse/v2 v2.5.15/go.mod h1:WzaJpRSbwq++EIQHYIRTpbYKNA3gn9it1Ik++q4zyho=
|
||||
github.com/tdewolff/parse/v2 v2.5.16 h1:ADVB3h2AR2jkLhY1LttDqRj3FFPDgSa0RZUBq5+q/t8=
|
||||
github.com/tdewolff/parse/v2 v2.5.16/go.mod h1:WzaJpRSbwq++EIQHYIRTpbYKNA3gn9it1Ik++q4zyho=
|
||||
github.com/tdewolff/test v1.0.6 h1:76mzYJQ83Op284kMT+63iCNCI7NEERsIN8dLM+RiKr4=
|
||||
github.com/tdewolff/test v1.0.6/go.mod h1:6DAvZliBAAnD7rhVgwaM7DE5/d9NMOAJ09SqYqeK4QE=
|
||||
github.com/thoas/go-funk v0.8.0 h1:JP9tKSvnpFVclYgDM0Is7FD9M4fhPvqA0s0BsXmzSRQ=
|
||||
|
@ -414,8 +415,9 @@ golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLL
|
|||
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210421230115-4e50805a0758 h1:aEpZnXcAmXkd6AvLb2OPt+EN1Zu/8Ne3pCqPjja5PXY=
|
||||
golang.org/x/net v0.0.0-20210421230115-4e50805a0758/go.mod h1:72T/g9IO56b78aLF+1Kcs5dz7/ng1VjMUvfKvpfy+jM=
|
||||
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781 h1:DzZ89McO9/gWPsQXS/FVKAlG02ZjaQ6AlZRBimEYOd0=
|
||||
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
|
||||
golang.org/x/oauth2 v0.0.0-20170912212905-13449ad91cb2/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
|
@ -451,8 +453,9 @@ golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7w
|
|||
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210420072515-93ed5bcd2bfe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da h1:b3NXsE2LusjYGGjL5bxEVZZORm/YEFFrWFjR8eFrw/c=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210426230700-d19ff857e887 h1:dXfMednGJh/SUUFjTLsWJz3P+TQt9qnR11GgeI3vWKs=
|
||||
golang.org/x/sys v0.0.0-20210426230700-d19ff857e887/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf h1:MZ2shdL+ZM/XzY3ZGOnh4Nlpnxz5GSOhOmtHo3iPU6M=
|
||||
golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
|
|
|
@ -10,30 +10,45 @@
|
|||
<thead>
|
||||
<tr>
|
||||
<th class="tal">{{ string .Blog.Lang "year" }}</th>
|
||||
<th class="tar">{{ string .Blog.Lang "count" }}</th>
|
||||
<th class="tar">{{ string .Blog.Lang "posts" }}</th>
|
||||
<th class="tar">~{{ string .Blog.Lang "chars" }}</th>
|
||||
<th class="tar">~{{ string .Blog.Lang "words" }}</th>
|
||||
<th class="tar">~{{ string .Blog.Lang "wordsperpost" }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{ $months := .Data.months }}
|
||||
{{ range $year := .Data.years }}
|
||||
<tr class="statsyear" data-year="{{ $year.First }}">
|
||||
<td class="tal"><b>{{ $year.First }}</b></td>
|
||||
<td class="tar">{{ $year.Second }}</td>
|
||||
<tr class="statsyear" data-year="{{ $year.Name }}">
|
||||
<td class="tal">{{ $year.Name }}</td>
|
||||
<td class="tar">{{ $year.Posts }}</td>
|
||||
<td class="tar">{{ $year.Chars }}</td>
|
||||
<td class="tar">{{ $year.Words }}</td>
|
||||
<td class="tar">{{ $year.WordsPerPost }}</td>
|
||||
</tr>
|
||||
{{ range $month := (index $months $year.First) }}
|
||||
<tr class="statsmonth hide" data-year="{{ $year.First }}">
|
||||
<td class="tal">{{ $month.First }}</td>
|
||||
<td class="tar">{{ $month.Second }}</td>
|
||||
{{ range $month := (index $months $year.Name) }}
|
||||
<tr class="statsmonth hide" data-year="{{ $year.Name }}">
|
||||
<td class="tal">{{ $year.Name }}-{{ $month.Name }}</td>
|
||||
<td class="tar">{{ $month.Posts }}</td>
|
||||
<td class="tar">{{ $month.Chars }}</td>
|
||||
<td class="tar">{{ $month.Words }}</td>
|
||||
<td class="tar">{{ $month.WordsPerPost }}</td>
|
||||
</tr>
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
<tr>
|
||||
<td class="tal"><b>{{ string .Blog.Lang "withoutdate" }}</b></td>
|
||||
<td class="tar">{{ .Data.withoutdate }}</td>
|
||||
<td class="tal">{{ string .Blog.Lang "withoutdate" }}</td>
|
||||
<td class="tar">{{ .Data.withoutdate.Posts }}</td>
|
||||
<td class="tar">{{ .Data.withoutdate.Chars }}</td>
|
||||
<td class="tar">{{ .Data.withoutdate.Words }}</td>
|
||||
<td class="tar">{{ .Data.withoutdate.WordsPerPost }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="tal"><u><b>{{ string .Blog.Lang "total" }}</b></u></td>
|
||||
<td class="tar">{{ .Data.total }}</td>
|
||||
<td class="tal"><b>{{ string .Blog.Lang "total" }}</b></td>
|
||||
<td class="tar">{{ .Data.total.Posts }}</td>
|
||||
<td class="tar">{{ .Data.total.Chars }}</td>
|
||||
<td class="tar">{{ .Data.total.Words }}</td>
|
||||
<td class="tar">{{ .Data.total.WordsPerPost }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
acommentby: "Ein Kommentar von"
|
||||
chars: "Buchstaben"
|
||||
comment: "Kommentar"
|
||||
comments: "Kommentare"
|
||||
count: "Anzahl"
|
||||
create: "Erstellen"
|
||||
delete: "Löschen"
|
||||
docomment: "Kommentieren"
|
||||
|
@ -13,6 +13,7 @@ likeof: "Gefällt mir von"
|
|||
next: "Weiter"
|
||||
noposts: "Hier sind keine Posts."
|
||||
oldcontent: "⚠️ Dieser Eintrag ist bereits über ein Jahr alt. Er ist möglicherweise nicht mehr aktuell. Meinungen können sich geändert haben."
|
||||
posts: "Posts"
|
||||
prev: "Zurück"
|
||||
publishedon: "Veröffentlicht am"
|
||||
replyto: "Antwort an"
|
||||
|
@ -30,4 +31,6 @@ updatedon: "Aktualisiert am"
|
|||
upload: "Hochladen"
|
||||
view: "Anschauen"
|
||||
withoutdate: "Ohne Datum"
|
||||
words: "Wörter"
|
||||
wordsperpost: "Wörter pro Post"
|
||||
year: "Jahr"
|
|
@ -4,9 +4,9 @@ approved: "Approved"
|
|||
authenticate: "Authenticate"
|
||||
captcha: "Captcha"
|
||||
captchainstructions: "Please enter the digits from the image above"
|
||||
chars: "Characters"
|
||||
comment: "Comment"
|
||||
comments: "Comments"
|
||||
count: "Count"
|
||||
create: "Create"
|
||||
delete: "Delete"
|
||||
docomment: "Comment"
|
||||
|
@ -24,6 +24,7 @@ noposts: "There are no posts here."
|
|||
notifications: "Notifications"
|
||||
oldcontent: "⚠️ This entry is already over one year old. It may no longer be up to date. Opinions may have changed."
|
||||
password: "Password"
|
||||
posts: "Posts"
|
||||
prev: "Previous"
|
||||
publishedon: "Published on"
|
||||
replyto: "Reply to"
|
||||
|
@ -48,4 +49,6 @@ view: "View"
|
|||
webmentions: "Webmentions"
|
||||
websiteopt: "Website (optional)"
|
||||
withoutdate: "Without date"
|
||||
words: "Words"
|
||||
wordsperpost: "Words per post"
|
||||
year: "Year"
|
Loading…
Reference in New Issue