Browse Source

Advance the blogstats even more

master
Jan-Lukas Else 2 weeks ago
parent
commit
e15d33b0dd
  1. 54
      blogstats.go
  2. 3
      database.go
  3. 7
      go.mod
  4. 13
      go.sum
  5. 39
      templates/blogstats.gohtml
  6. 5
      templates/strings/de.yaml
  7. 5
      templates/strings/default.yaml
  8. 4
      utils.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,
},
})

3
database.go

@ -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

@ -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

@ -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=

39
templates/blogstats.gohtml

@ -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>

5
templates/strings/de.yaml

@ -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"

5
templates/strings/default.yaml

@ -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"

4
utils.go

@ -164,3 +164,7 @@ func dateFormat(date string, format string) string {
type stringPair struct {
First, Second string
}
func wordCount(s string) int {
return len(strings.Fields(s))
}
Loading…
Cancel
Save