From e15d33b0ddec121161a4b2fc1c3eb7322e62f3ad Mon Sep 17 00:00:00 2001 From: Jan-Lukas Else Date: Wed, 28 Apr 2021 20:03:20 +0200 Subject: [PATCH] Advance the blogstats even more --- blogstats.go | 54 +++++++++++++++++++++------------- database.go | 3 ++ go.mod | 7 +++-- go.sum | 13 ++++---- templates/blogstats.gohtml | 39 ++++++++++++++++-------- templates/strings/de.yaml | 5 +++- templates/strings/default.yaml | 5 +++- utils.go | 4 +++ 8 files changed, 87 insertions(+), 43 deletions(-) diff --git a/blogstats.go b/blogstats.go index 07b9a90..919f620 100644 --- a/blogstats.go +++ b/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, }, }) diff --git a/database.go b/database.go index abce096..8222d12 100644 --- a/database.go +++ b/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 }, }) diff --git a/go.mod b/go.mod index 10a637d..09da3fc 100644 --- a/go.mod +++ b/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 diff --git a/go.sum b/go.sum index ad3ea1f..bbf89fd 100644 --- a/go.sum +++ b/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= diff --git a/templates/blogstats.gohtml b/templates/blogstats.gohtml index 7e787f9..699a465 100644 --- a/templates/blogstats.gohtml +++ b/templates/blogstats.gohtml @@ -10,30 +10,45 @@ {{ string .Blog.Lang "year" }} - {{ string .Blog.Lang "count" }} + {{ string .Blog.Lang "posts" }} + ~{{ string .Blog.Lang "chars" }} + ~{{ string .Blog.Lang "words" }} + ~{{ string .Blog.Lang "wordsperpost" }} {{ $months := .Data.months }} {{ range $year := .Data.years }} - - {{ $year.First }} - {{ $year.Second }} + + {{ $year.Name }} + {{ $year.Posts }} + {{ $year.Chars }} + {{ $year.Words }} + {{ $year.WordsPerPost }} - {{ range $month := (index $months $year.First) }} - - {{ $month.First }} - {{ $month.Second }} + {{ range $month := (index $months $year.Name) }} + + {{ $year.Name }}-{{ $month.Name }} + {{ $month.Posts }} + {{ $month.Chars }} + {{ $month.Words }} + {{ $month.WordsPerPost }} {{ end }} {{ end }} - {{ string .Blog.Lang "withoutdate" }} - {{ .Data.withoutdate }} + {{ string .Blog.Lang "withoutdate" }} + {{ .Data.withoutdate.Posts }} + {{ .Data.withoutdate.Chars }} + {{ .Data.withoutdate.Words }} + {{ .Data.withoutdate.WordsPerPost }} - {{ string .Blog.Lang "total" }} - {{ .Data.total }} + {{ string .Blog.Lang "total" }} + {{ .Data.total.Posts }} + {{ .Data.total.Chars }} + {{ .Data.total.Words }} + {{ .Data.total.WordsPerPost }} diff --git a/templates/strings/de.yaml b/templates/strings/de.yaml index b3d804c..1a2b5de 100644 --- a/templates/strings/de.yaml +++ b/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" \ No newline at end of file diff --git a/templates/strings/default.yaml b/templates/strings/default.yaml index c7af5f4..f68170e 100644 --- a/templates/strings/default.yaml +++ b/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" \ No newline at end of file diff --git a/utils.go b/utils.go index 08a1986..f09a5ed 100644 --- a/utils.go +++ b/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)) +}