From eae4097677e5592ebd60b8733b7ca65c2d6681ef Mon Sep 17 00:00:00 2001 From: Rob Loranger Date: Thu, 29 Aug 2019 15:05:59 -0700 Subject: [PATCH 01/51] add update checks includes cache of latest version and page to view if updates are available with a link to the latest update's release notes and a link to check for the latest update now, refreshing the cache manually. --- admin.go | 37 ++- app.go | 5 +- config.ini.example | 1 + config/config.go | 6 +- go.mod | 2 +- routes.go | 8 +- semver.go | 315 ++++++++++++++++++++++++++ templates/user/admin/app-updates.tmpl | 23 ++ templates/user/include/header.tmpl | 1 + updates.go | 103 +++++++++ 10 files changed, 490 insertions(+), 11 deletions(-) create mode 100644 semver.go create mode 100644 templates/user/admin/app-updates.tmpl create mode 100644 updates.go diff --git a/admin.go b/admin.go index fe19ad5..9436ff5 100644 --- a/admin.go +++ b/admin.go @@ -13,16 +13,17 @@ package writefreely import ( "database/sql" "fmt" + "net/http" + "runtime" + "strconv" + "time" + "github.com/gogits/gogs/pkg/tool" "github.com/gorilla/mux" "github.com/writeas/impart" "github.com/writeas/web-core/auth" "github.com/writeas/web-core/log" "github.com/writeas/writefreely/config" - "net/http" - "runtime" - "strconv" - "time" ) var ( @@ -112,7 +113,6 @@ func handleViewAdminDash(app *App, u *User, w http.ResponseWriter, r *http.Reque Message: r.FormValue("m"), ConfigMessage: r.FormValue("cm"), } - showUserPage(w, "admin", p) return nil } @@ -451,3 +451,30 @@ func adminResetPassword(app *App, u *User, newPass string) error { } return nil } + +func handleViewAdminUpdates(app *App, u *User, w http.ResponseWriter, r *http.Request) error { + check := r.URL.Query().Get("check") + + if check == "now" && app.cfg.App.UpdateChecks { + app.updates.CheckNow() + } + + p := struct { + *UserPage + LastChecked string + LatestVersion string + LatestReleaseURL string + UpdateAvailable bool + }{ + UserPage: NewUserPage(app, r, u, "Updates", nil), + } + if app.cfg.App.UpdateChecks { + p.LastChecked = app.updates.lastCheck.Format("January 2, 2006, 3:04 PM") + p.LatestVersion = app.updates.LatestVersion() + p.LatestReleaseURL = app.updates.ReleaseURL() + p.UpdateAvailable = app.updates.AreAvailable() + } + + showUserPage(w, "app-updates", p) + return nil +} diff --git a/app.go b/app.go index c52f59d..dea31bf 100644 --- a/app.go +++ b/app.go @@ -30,7 +30,7 @@ import ( "github.com/gorilla/schema" "github.com/gorilla/sessions" "github.com/manifoldco/promptui" - "github.com/writeas/go-strip-markdown" + stripmd "github.com/writeas/go-strip-markdown" "github.com/writeas/impart" "github.com/writeas/web-core/auth" "github.com/writeas/web-core/converter" @@ -72,6 +72,7 @@ type App struct { keys *key.Keychain sessionStore *sessions.CookieStore formDecoder *schema.Decoder + updates *updatesCache timeline *localTimeline } @@ -346,6 +347,8 @@ func Initialize(apper Apper, debug bool) (*App, error) { if err != nil { return nil, fmt.Errorf("init keys: %s", err) } + apper.App().InitUpdates() + apper.App().InitSession() apper.App().InitDecoder() diff --git a/config.ini.example b/config.ini.example index dcbd6ee..7ac944e 100644 --- a/config.ini.example +++ b/config.ini.example @@ -23,4 +23,5 @@ max_blogs = 1 federation = true public_stats = true private = false +update_checks = true diff --git a/config/config.go b/config/config.go index e27ffb9..f915431 100644 --- a/config/config.go +++ b/config/config.go @@ -12,8 +12,9 @@ package config import ( - "gopkg.in/ini.v1" "strings" + + "gopkg.in/ini.v1" ) const ( @@ -89,6 +90,9 @@ type ( // Defaults DefaultVisibility string `ini:"default_visibility"` + + // Check for Updates + UpdateChecks bool `ini:"update_checks"` } // Config holds the complete configuration for running a writefreely instance diff --git a/go.mod b/go.mod index 5e040ba..bde8334 100644 --- a/go.mod +++ b/go.mod @@ -67,7 +67,7 @@ require ( golang.org/x/lint v0.0.0-20181217174547-8f45f776aaf1 // indirect golang.org/x/net v0.0.0-20190206173232-65e2d4e15006 // indirect golang.org/x/sys v0.0.0-20190209173611-3b5209105503 // indirect - golang.org/x/tools v0.0.0-20190208222737-3744606dbb67 // indirect + golang.org/x/tools v0.0.0-20190208222737-3744606dbb67 google.golang.org/appengine v1.4.0 // indirect gopkg.in/alecthomas/kingpin.v3-unstable v3.0.0-20180810215634-df19058c872c // indirect gopkg.in/bufio.v1 v1.0.0-20140618132640-567b2bfa514e // indirect diff --git a/routes.go b/routes.go index 724c532..937f8bc 100644 --- a/routes.go +++ b/routes.go @@ -11,13 +11,14 @@ package writefreely import ( + "net/http" + "path/filepath" + "strings" + "github.com/gorilla/mux" "github.com/writeas/go-webfinger" "github.com/writeas/web-core/log" "github.com/writefreely/go-nodeinfo" - "net/http" - "path/filepath" - "strings" ) // InitStaticRoutes adds routes for serving static files. @@ -147,6 +148,7 @@ func InitRoutes(apper Apper, r *mux.Router) *mux.Router { write.HandleFunc("/admin/page/{slug}", handler.Admin(handleViewAdminPage)).Methods("GET") write.HandleFunc("/admin/update/config", handler.AdminApper(handleAdminUpdateConfig)).Methods("POST") write.HandleFunc("/admin/update/{page}", handler.Admin(handleAdminUpdateSite)).Methods("POST") + write.HandleFunc("/admin/updates", handler.Admin(handleViewAdminUpdates)).Methods("GET") // Handle special pages first write.HandleFunc("/login", handler.Web(viewLogin, UserLevelNoneRequired)) diff --git a/semver.go b/semver.go new file mode 100644 index 0000000..18fb276 --- /dev/null +++ b/semver.go @@ -0,0 +1,315 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package semver implements comparison of semantic version strings. +// In this package, semantic version strings must begin with a leading "v", +// as in "v1.0.0". +// +// The general form of a semantic version string accepted by this package is +// +// vMAJOR[.MINOR[.PATCH[-PRERELEASE][+BUILD]]] +// +// where square brackets indicate optional parts of the syntax; +// MAJOR, MINOR, and PATCH are decimal integers without extra leading zeros; +// PRERELEASE and BUILD are each a series of non-empty dot-separated identifiers +// using only alphanumeric characters and hyphens; and +// all-numeric PRERELEASE identifiers must not have leading zeros. +// +// This package follows Semantic Versioning 2.0.0 (see semver.org) +// with two exceptions. First, it requires the "v" prefix. Second, it recognizes +// vMAJOR and vMAJOR.MINOR (with no prerelease or build suffixes) +// as shorthands for vMAJOR.0.0 and vMAJOR.MINOR.0. + +// Package writefreely +// copied from +// https://github.com/golang/tools/blob/master/internal/semver/semver.go +// slight modifications made +package writefreely + +// parsed returns the parsed form of a semantic version string. +type parsed struct { + major string + minor string + patch string + short string + prerelease string + build string + err string +} + +// IsValid reports whether v is a valid semantic version string. +func IsValid(v string) bool { + _, ok := semParse(v) + return ok +} + +// CompareSemver returns an integer comparing two versions according to +// according to semantic version precedence. +// The result will be 0 if v == w, -1 if v < w, or +1 if v > w. +// +// An invalid semantic version string is considered less than a valid one. +// All invalid semantic version strings compare equal to each other. +func CompareSemver(v, w string) int { + pv, ok1 := semParse(v) + pw, ok2 := semParse(w) + if !ok1 && !ok2 { + return 0 + } + if !ok1 { + return -1 + } + if !ok2 { + return +1 + } + if c := compareInt(pv.major, pw.major); c != 0 { + return c + } + if c := compareInt(pv.minor, pw.minor); c != 0 { + return c + } + if c := compareInt(pv.patch, pw.patch); c != 0 { + return c + } + return comparePrerelease(pv.prerelease, pw.prerelease) +} + +func semParse(v string) (p parsed, ok bool) { + if v == "" || v[0] != 'v' { + p.err = "missing v prefix" + return + } + p.major, v, ok = parseInt(v[1:]) + if !ok { + p.err = "bad major version" + return + } + if v == "" { + p.minor = "0" + p.patch = "0" + p.short = ".0.0" + return + } + if v[0] != '.' { + p.err = "bad minor prefix" + ok = false + return + } + p.minor, v, ok = parseInt(v[1:]) + if !ok { + p.err = "bad minor version" + return + } + if v == "" { + p.patch = "0" + p.short = ".0" + return + } + if v[0] != '.' { + p.err = "bad patch prefix" + ok = false + return + } + p.patch, v, ok = parseInt(v[1:]) + if !ok { + p.err = "bad patch version" + return + } + if len(v) > 0 && v[0] == '-' { + p.prerelease, v, ok = parsePrerelease(v) + if !ok { + p.err = "bad prerelease" + return + } + } + if len(v) > 0 && v[0] == '+' { + p.build, v, ok = parseBuild(v) + if !ok { + p.err = "bad build" + return + } + } + if v != "" { + p.err = "junk on end" + ok = false + return + } + ok = true + return +} + +func parseInt(v string) (t, rest string, ok bool) { + if v == "" { + return + } + if v[0] < '0' || '9' < v[0] { + return + } + i := 1 + for i < len(v) && '0' <= v[i] && v[i] <= '9' { + i++ + } + if v[0] == '0' && i != 1 { + return + } + return v[:i], v[i:], true +} + +func parsePrerelease(v string) (t, rest string, ok bool) { + // "A pre-release version MAY be denoted by appending a hyphen and + // a series of dot separated identifiers immediately following the patch version. + // Identifiers MUST comprise only ASCII alphanumerics and hyphen [0-9A-Za-z-]. + // Identifiers MUST NOT be empty. Numeric identifiers MUST NOT include leading zeroes." + if v == "" || v[0] != '-' { + return + } + i := 1 + start := 1 + for i < len(v) && v[i] != '+' { + if !isIdentChar(v[i]) && v[i] != '.' { + return + } + if v[i] == '.' { + if start == i || isBadNum(v[start:i]) { + return + } + start = i + 1 + } + i++ + } + if start == i || isBadNum(v[start:i]) { + return + } + return v[:i], v[i:], true +} + +func parseBuild(v string) (t, rest string, ok bool) { + if v == "" || v[0] != '+' { + return + } + i := 1 + start := 1 + for i < len(v) { + if !isIdentChar(v[i]) { + return + } + if v[i] == '.' { + if start == i { + return + } + start = i + 1 + } + i++ + } + if start == i { + return + } + return v[:i], v[i:], true +} + +func isIdentChar(c byte) bool { + return 'A' <= c && c <= 'Z' || 'a' <= c && c <= 'z' || '0' <= c && c <= '9' || c == '-' +} + +func isBadNum(v string) bool { + i := 0 + for i < len(v) && '0' <= v[i] && v[i] <= '9' { + i++ + } + return i == len(v) && i > 1 && v[0] == '0' +} + +func isNum(v string) bool { + i := 0 + for i < len(v) && '0' <= v[i] && v[i] <= '9' { + i++ + } + return i == len(v) +} + +func compareInt(x, y string) int { + if x == y { + return 0 + } + if len(x) < len(y) { + return -1 + } + if len(x) > len(y) { + return +1 + } + if x < y { + return -1 + } else { + return +1 + } +} + +func comparePrerelease(x, y string) int { + // "When major, minor, and patch are equal, a pre-release version has + // lower precedence than a normal version. + // Example: 1.0.0-alpha < 1.0.0. + // Precedence for two pre-release versions with the same major, minor, + // and patch version MUST be determined by comparing each dot separated + // identifier from left to right until a difference is found as follows: + // identifiers consisting of only digits are compared numerically and + // identifiers with letters or hyphens are compared lexically in ASCII + // sort order. Numeric identifiers always have lower precedence than + // non-numeric identifiers. A larger set of pre-release fields has a + // higher precedence than a smaller set, if all of the preceding + // identifiers are equal. + // Example: 1.0.0-alpha < 1.0.0-alpha.1 < 1.0.0-alpha.beta < + // 1.0.0-beta < 1.0.0-beta.2 < 1.0.0-beta.11 < 1.0.0-rc.1 < 1.0.0." + if x == y { + return 0 + } + if x == "" { + return +1 + } + if y == "" { + return -1 + } + for x != "" && y != "" { + x = x[1:] // skip - or . + y = y[1:] // skip - or . + var dx, dy string + dx, x = nextIdent(x) + dy, y = nextIdent(y) + if dx != dy { + ix := isNum(dx) + iy := isNum(dy) + if ix != iy { + if ix { + return -1 + } else { + return +1 + } + } + if ix { + if len(dx) < len(dy) { + return -1 + } + if len(dx) > len(dy) { + return +1 + } + } + if dx < dy { + return -1 + } else { + return +1 + } + } + } + if x == "" { + return -1 + } else { + return +1 + } +} + +func nextIdent(x string) (dx, rest string) { + i := 0 + for i < len(x) && x[i] != '.' { + i++ + } + return x[:i], x[i:] +} diff --git a/templates/user/admin/app-updates.tmpl b/templates/user/admin/app-updates.tmpl new file mode 100644 index 0000000..540fb7c --- /dev/null +++ b/templates/user/admin/app-updates.tmpl @@ -0,0 +1,23 @@ +{{define "app-updates"}} +{{template "header" .}} + + + +
+ {{template "admin-header" .}} + + {{if not .UpdateAvailable}} +

WriteFreely is up to date.

+ {{else}} +

WriteFreely {{.LatestVersion}} is available.

+
+ For details on features, bug fixes or notes on upgrading, read the release notes. +
+ {{end}} +

Last checked at: {{.LastChecked}}. Check now.

+ +{{template "footer" .}} + +{{template "body-end" .}} +{{end}} diff --git a/templates/user/include/header.tmpl b/templates/user/include/header.tmpl index 312d0b8..48946d6 100644 --- a/templates/user/include/header.tmpl +++ b/templates/user/include/header.tmpl @@ -69,6 +69,7 @@ {{if not .SingleUser}} Users Pages + {{if .UpdateChecks}}Updates{{end}} {{end}} diff --git a/updates.go b/updates.go new file mode 100644 index 0000000..248f691 --- /dev/null +++ b/updates.go @@ -0,0 +1,103 @@ +/* + * Copyright © 2018-2019 A Bunch Tell LLC. + * + * This file is part of WriteFreely. + * + * WriteFreely is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, included + * in the LICENSE file in this source code package. + */ + +package writefreely + +import ( + "io/ioutil" + "net/http" + "strings" + "sync" + "time" +) + +// updatesCacheTime is the default interval between cache updates for new +// software versions +const updatesCacheTime = 12 * time.Hour + +// updatesCache holds data about current and new releases of the writefreely +// software +type updatesCache struct { + mu sync.Mutex + frequency time.Duration + lastCheck time.Time + latestVersion string + currentVersion string +} + +// CheckNow asks for the latest released version of writefreely and updates +// the cache last checked time. If the version postdates the current 'latest' +// the version value is replaced. +func (uc *updatesCache) CheckNow() error { + uc.mu.Lock() + defer uc.mu.Unlock() + latestRemote, err := newVersionCheck(uc.currentVersion) + if err != nil { + return err + } + uc.lastCheck = time.Now() + if CompareSemver(latestRemote, uc.latestVersion) == 1 { + uc.latestVersion = latestRemote + } + return nil +} + +// AreAvailable updates the cache if the frequency duration has passed +// then returns if the latest release is newer than the current running version. +func (uc updatesCache) AreAvailable() bool { + if time.Since(uc.lastCheck) > uc.frequency { + uc.CheckNow() + } + return CompareSemver(uc.latestVersion, uc.currentVersion) == 1 +} + +// LatestVersion returns the latest stored version available. +func (uc updatesCache) LatestVersion() string { + return uc.latestVersion +} + +// ReleaseURL returns the full URL to the blog.writefreely.org release notes +// for the latest version as stored in the cache. +func (uc updatesCache) ReleaseURL() string { + ver := strings.TrimPrefix(uc.latestVersion, "v") + ver = strings.TrimSuffix(ver, ".0") + return "https://blog.writefreely.org/version-" + strings.ReplaceAll(ver, ".", "-") +} + +// newUpdatesCache returns an initialized updates cache +func newUpdatesCache() *updatesCache { + cache := updatesCache{ + frequency: updatesCacheTime, + currentVersion: "v" + softwareVer, + } + cache.CheckNow() + return &cache +} + +// InitUpdates initializes the updates cache, if the config value is set +func (app *App) InitUpdates() { + if app.cfg.App.UpdateChecks { + app.updates = newUpdatesCache() + } +} + +func newVersionCheck(serverVersion string) (string, error) { + res, err := http.Get("https://version.writefreely.org") + if err == nil && res.StatusCode == http.StatusOK { + defer res.Body.Close() + + body, err := ioutil.ReadAll(res.Body) + if err != nil { + return "", err + } + return string(body), nil + } + return "", err +} From 2a7a8298e19c8a809df69e03c1e6f02701b85e51 Mon Sep 17 00:00:00 2001 From: Rob Loranger Date: Thu, 29 Aug 2019 16:20:41 -0700 Subject: [PATCH 02/51] strings.ReplaceAll is not in go 1.11 --- updates.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/updates.go b/updates.go index 248f691..63b2378 100644 --- a/updates.go +++ b/updates.go @@ -68,7 +68,9 @@ func (uc updatesCache) LatestVersion() string { func (uc updatesCache) ReleaseURL() string { ver := strings.TrimPrefix(uc.latestVersion, "v") ver = strings.TrimSuffix(ver, ".0") - return "https://blog.writefreely.org/version-" + strings.ReplaceAll(ver, ".", "-") + // hack until go 1.12 in build/travis + seg := strings.Split(ver, ".") + return "https://blog.writefreely.org/version-" + strings.Join(seg, "-") } // newUpdatesCache returns an initialized updates cache From 908f009248ec540576f3dd02ecf6d35edb0c5dce Mon Sep 17 00:00:00 2001 From: Rob Loranger Date: Mon, 9 Sep 2019 10:24:29 -0700 Subject: [PATCH 03/51] clean up and add tests for updates cache - removes the parameter for newVersionCheck as was not being used - changes newUpdatesCache to take expiry parameter for possible future configuration option - adds basic test quite to verify all cache fucntions work as expected --- updates.go | 13 ++++---- updates_test.go | 82 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 89 insertions(+), 6 deletions(-) create mode 100644 updates_test.go diff --git a/updates.go b/updates.go index 63b2378..c33247b 100644 --- a/updates.go +++ b/updates.go @@ -20,7 +20,7 @@ import ( // updatesCacheTime is the default interval between cache updates for new // software versions -const updatesCacheTime = 12 * time.Hour +const defaultUpdatesCacheTime = 12 * time.Hour // updatesCache holds data about current and new releases of the writefreely // software @@ -38,7 +38,7 @@ type updatesCache struct { func (uc *updatesCache) CheckNow() error { uc.mu.Lock() defer uc.mu.Unlock() - latestRemote, err := newVersionCheck(uc.currentVersion) + latestRemote, err := newVersionCheck() if err != nil { return err } @@ -74,9 +74,9 @@ func (uc updatesCache) ReleaseURL() string { } // newUpdatesCache returns an initialized updates cache -func newUpdatesCache() *updatesCache { +func newUpdatesCache(expiry time.Duration) *updatesCache { cache := updatesCache{ - frequency: updatesCacheTime, + frequency: expiry, currentVersion: "v" + softwareVer, } cache.CheckNow() @@ -84,13 +84,14 @@ func newUpdatesCache() *updatesCache { } // InitUpdates initializes the updates cache, if the config value is set +// It uses the defaultUpdatesCacheTime for the cache expiry func (app *App) InitUpdates() { if app.cfg.App.UpdateChecks { - app.updates = newUpdatesCache() + app.updates = newUpdatesCache(defaultUpdatesCacheTime) } } -func newVersionCheck(serverVersion string) (string, error) { +func newVersionCheck() (string, error) { res, err := http.Get("https://version.writefreely.org") if err == nil && res.StatusCode == http.StatusOK { defer res.Body.Close() diff --git a/updates_test.go b/updates_test.go new file mode 100644 index 0000000..2cb9f92 --- /dev/null +++ b/updates_test.go @@ -0,0 +1,82 @@ +package writefreely + +import ( + "regexp" + "testing" + "time" +) + +func TestUpdatesRoundTrip(t *testing.T) { + cache := newUpdatesCache(defaultUpdatesCacheTime) + t.Run("New Updates Cache", func(t *testing.T) { + + if cache == nil { + t.Fatal("Returned nil cache") + } + + if cache.frequency != defaultUpdatesCacheTime { + t.Fatalf("Got cache expiry frequency: %s but expected: %s", cache.frequency, defaultUpdatesCacheTime) + } + + if cache.currentVersion != "v"+softwareVer { + t.Fatalf("Got current version: %s but expected: %s", cache.currentVersion, "v"+softwareVer) + } + }) + + t.Run("Release URL", func(t *testing.T) { + url := cache.ReleaseURL() + + reg, err := regexp.Compile(`^https:\/\/blog.writefreely.org\/version(-\d+){1,}$`) + if err != nil { + t.Fatalf("Test Case Error: Failed to compile regex: %v", err) + } + match := reg.MatchString(url) + + if !match { + t.Fatalf("Malformed Release URL: %s", url) + } + }) + + t.Run("Check Now", func(t *testing.T) { + // ensure time between init and next check + time.Sleep(1 * time.Second) + + prevLastCheck := cache.lastCheck + + // force to known older version for latest and current + prevLatestVer := "v0.8.1" + cache.latestVersion = prevLatestVer + cache.currentVersion = "v0.8.0" + + err := cache.CheckNow() + if err != nil { + t.Fatalf("Error should be nil, got: %v", err) + } + + if prevLastCheck == cache.lastCheck { + t.Fatal("Expected lastCheck to update") + } + + if cache.lastCheck.Before(prevLastCheck) { + t.Fatal("Last check should be newer than previous") + } + + if prevLatestVer == cache.latestVersion { + t.Fatal("expected latestVersion to update") + } + + }) + + t.Run("Are Available", func(t *testing.T) { + if !cache.AreAvailable() { + t.Fatalf("Cache reports not updates but Current is %s and Latest is %s", cache.currentVersion, cache.latestVersion) + } + }) + + t.Run("Latest Version", func(t *testing.T) { + gotLatest := cache.LatestVersion() + if gotLatest != cache.latestVersion { + t.Fatalf("Malformed latest version. Expected: %s but got: %s", cache.latestVersion, gotLatest) + } + }) +} From 25fe5285da016c43baa269f0cec267369fd6df20 Mon Sep 17 00:00:00 2001 From: Rob Loranger Date: Tue, 8 Oct 2019 09:39:39 -0700 Subject: [PATCH 04/51] lightly style tables in posts --- less/core.less | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/less/core.less b/less/core.less index f4332a9..99aaa0e 100644 --- a/less/core.less +++ b/less/core.less @@ -632,6 +632,30 @@ table.classy { } } +body#post article table { + border-spacing: 0; + border-collapse: collapse; + th { + border-bottom: 2px solid #ccc; + text-align: center; + } + th + th { + border-left: 1px solid #ccc; + } + tr:nth-child(even) { + background-color: #f6f6f6; + } + td + td { + border-left: 1px solid #ccc; + } + tr + tr td { + border-top: 1px solid #ccc; + } + td, th { + padding: .25rem .5rem; + } +} + body#collection article, body#subpage article { padding-top: 0; padding-bottom: 0; From fe82cbb96e3d5c57cfde0db76c28c4ea6dabfe50 Mon Sep 17 00:00:00 2001 From: Matt Baer Date: Sat, 8 Feb 2020 15:19:06 -0500 Subject: [PATCH 05/51] Fix post deletion on Pleroma See: https://git.pleroma.social/pleroma/pleroma/issues/1481 Fixes #223 --- activitypub.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/activitypub.go b/activitypub.go index a5b140d..d21959b 100644 --- a/activitypub.go +++ b/activitypub.go @@ -600,7 +600,12 @@ func deleteFederatedPost(app *App, p *PublicPost, collID int64) error { na.CC = append(na.CC, f) } - err = makeActivityPost(app.cfg.App.Host, actor, si, activitystreams.NewDeleteActivity(na)) + da := activitystreams.NewDeleteActivity(na) + // Make the ID unique to ensure it works in Pleroma + // See: https://git.pleroma.social/pleroma/pleroma/issues/1481 + da.ID += "#Delete" + + err = makeActivityPost(app.cfg.App.Host, actor, si, da) if err != nil { log.Error("Couldn't delete post! %v", err) } From 61734057943075ab8df30ce45c0f580340d0c0ba Mon Sep 17 00:00:00 2001 From: Matti R Date: Mon, 10 Feb 2020 01:46:58 -0500 Subject: [PATCH 06/51] switch to use urfave/cli for cli, which allows for auto-manpage generation and bash complete --- cmd/writefreely/config.go | 63 ++++++++++ cmd/writefreely/db.go | 50 ++++++++ cmd/writefreely/keys.go | 40 +++++++ cmd/writefreely/main.go | 238 +++++++++++++++++++++----------------- cmd/writefreely/user.go | 89 ++++++++++++++ cmd/writefreely/web.go | 49 ++++++++ go.mod | 4 +- go.sum | 9 ++ 8 files changed, 434 insertions(+), 108 deletions(-) create mode 100644 cmd/writefreely/config.go create mode 100644 cmd/writefreely/db.go create mode 100644 cmd/writefreely/keys.go create mode 100644 cmd/writefreely/user.go create mode 100644 cmd/writefreely/web.go diff --git a/cmd/writefreely/config.go b/cmd/writefreely/config.go new file mode 100644 index 0000000..d572036 --- /dev/null +++ b/cmd/writefreely/config.go @@ -0,0 +1,63 @@ +/* + * Copyright © 2020 A Bunch Tell LLC. + * + * This file is part of WriteFreely. + * + * WriteFreely is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, included + * in the LICENSE file in this source code package. + */ + +package main + +import ( + "github.com/writeas/writefreely" + + "github.com/urfave/cli/v2" +) + +var ( + cmdConfig cli.Command = cli.Command{ + Name: "config", + Usage: "config management tools", + Subcommands: []*cli.Command{ + &cmdConfigGenerate, + &cmdConfigInteractive, + }, + } + + cmdConfigGenerate cli.Command = cli.Command{ + Name: "generate", + Aliases: []string{"gen"}, + Usage: "Generate a basic configuration", + Action: genConfigAction, + } + + cmdConfigInteractive cli.Command = cli.Command{ + Name: "interactive", + Aliases: []string{"i"}, + Usage: "Interactive configuration process", + Action: interactiveConfigAction, + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "sections", + Value: "server db app", + Usage: "Which sections of the configuration to go through (requires --config)\n" + + "valid values are any combination of 'server', 'db' and 'app' \n" + + "example: writefreely --config --sections \"db app\"", + }, + }, + } + +) + +func genConfigAction(c *cli.Context) error { + app := writefreely.NewApp(c.String("c")) + return writefreely.CreateConfig(app) +} + +func interactiveConfigAction(c *cli.Context) error { + app := writefreely.NewApp(c.String("c")) + writefreely.DoConfig(app, c.String("sections")) + return nil +} \ No newline at end of file diff --git a/cmd/writefreely/db.go b/cmd/writefreely/db.go new file mode 100644 index 0000000..fa5110d --- /dev/null +++ b/cmd/writefreely/db.go @@ -0,0 +1,50 @@ +/* + * Copyright © 2020 A Bunch Tell LLC. + * + * This file is part of WriteFreely. + * + * WriteFreely is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, included + * in the LICENSE file in this source code package. + */ + +package main + +import ( + "github.com/writeas/writefreely" + + "github.com/urfave/cli/v2" +) + +var ( + cmdDB cli.Command = cli.Command{ + Name: "db", + Usage: "db management tools", + Subcommands: []*cli.Command{ + &cmdDBInit, + &cmdDBMigrate, + }, + } + + cmdDBInit cli.Command = cli.Command{ + Name: "init", + Usage: "Initialize Database", + Action: initDBAction, + } + + cmdDBMigrate cli.Command = cli.Command{ + Name: "migrate", + Usage: "Migrate Database", + Action: migrateDBAction, + } +) + +func initDBAction(c *cli.Context) error { + app := writefreely.NewApp(c.String("c")) + return writefreely.CreateSchema(app) +} + +func migrateDBAction(c *cli.Context) error { + app := writefreely.NewApp(c.String("c")) + return writefreely.Migrate(app) +} \ No newline at end of file diff --git a/cmd/writefreely/keys.go b/cmd/writefreely/keys.go new file mode 100644 index 0000000..7e81475 --- /dev/null +++ b/cmd/writefreely/keys.go @@ -0,0 +1,40 @@ +/* + * Copyright © 2020 A Bunch Tell LLC. + * + * This file is part of WriteFreely. + * + * WriteFreely is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, included + * in the LICENSE file in this source code package. + */ + +package main + +import ( + "github.com/writeas/writefreely" + + "github.com/urfave/cli/v2" +) + +var ( + cmdKeys cli.Command = cli.Command{ + Name: "keys", + Usage: "key management tools", + Subcommands: []*cli.Command{ + &cmdGenerateKeys, + }, + } + + cmdGenerateKeys cli.Command = cli.Command{ + Name: "generate", + Aliases: []string{"gen"}, + Usage: "Generate encryption and authentication keys", + Action: genKeysAction, + } + +) + +func genKeysAction(c *cli.Context) error { + app := writefreely.NewApp(c.String("c")) + return writefreely.GenerateKeyFiles(app) +} \ No newline at end of file diff --git a/cmd/writefreely/main.go b/cmd/writefreely/main.go index 7fc2342..55f97be 100644 --- a/cmd/writefreely/main.go +++ b/cmd/writefreely/main.go @@ -1,5 +1,5 @@ /* - * Copyright © 2018-2019 A Bunch Tell LLC. + * Copyright © 2018-2020 A Bunch Tell LLC. * * This file is part of WriteFreely. * @@ -11,122 +11,154 @@ package main import ( - "flag" "fmt" "os" "strings" - "github.com/gorilla/mux" "github.com/writeas/web-core/log" "github.com/writeas/writefreely" + + "github.com/gorilla/mux" + "github.com/urfave/cli/v2" ) func main() { - // General options usable with other commands - debugPtr := flag.Bool("debug", false, "Enables debug logging.") - configFile := flag.String("c", "config.ini", "The configuration file to use") + app := &cli.App{ + Name: "WriteFreely", + Usage: "A beautifully pared-down blogging platform", + Version: writefreely.FormatVersion(), + Action: legacyActions, // legacy due to use of flags for switching actions + Flags: []cli.Flag{ + &cli.BoolFlag{ + Name: "create-config", + Value: false, + Usage: "Generate a basic configuration", + Hidden: true, + }, + &cli.BoolFlag{ + Name: "config", + Value: false, + Usage: "Interactive configuration process", + Hidden: true, + }, + &cli.StringFlag{ + Name: "sections", + Value: "server db app", + Usage: "Which sections of the configuration to go through (requires --config)\n" + + "valid values are any combination of 'server', 'db' and 'app' \n" + + "example: writefreely --config --sections \"db app\"", + Hidden: true, + }, + &cli.BoolFlag{ + Name: "gen-keys", + Value: false, + Usage: "Generate encryption and authentication keys", + Hidden: true, + }, + &cli.BoolFlag{ + Name: "init-db", + Value: false, + Usage: "Initialize app database", + Hidden: true, + }, + &cli.BoolFlag{ + Name: "migrate", + Value: false, + Usage: "Migrate the database", + Hidden: true, + }, + &cli.StringFlag{ + Name: "create-admin", + Usage: "Create an admin with the given username:password", + Hidden: true, + }, + &cli.StringFlag{ + Name: "create-user", + Usage: "Create a regular user with the given username:password", + Hidden: true, + }, + &cli.StringFlag{ + Name: "delete-user", + Usage: "Delete a user with the given username", + Hidden: true, + }, + &cli.StringFlag{ + Name: "reset-pass", + Usage: "Reset the given user's password", + Hidden: true, + }, + }, // legacy flags (set to hidden to eventually switch to bash-complete compatible format) + } - // Setup actions - createConfig := flag.Bool("create-config", false, "Creates a basic configuration and exits") - doConfig := flag.Bool("config", false, "Run the configuration process") - configSections := flag.String("sections", "server db app", "Which sections of the configuration to go through (requires --config), "+ - "valid values are any combination of 'server', 'db' and 'app' "+ - "example: writefreely --config --sections \"db app\"") - genKeys := flag.Bool("gen-keys", false, "Generate encryption and authentication keys") - createSchema := flag.Bool("init-db", false, "Initialize app database") - migrate := flag.Bool("migrate", false, "Migrate the database") + defaultFlags := []cli.Flag{ + &cli.StringFlag{ + Name: "c", + Value: "config.ini", + Usage: "Load configuration from `FILE`", + }, + &cli.BoolFlag{ + Name: "debug", + Value: false, + Usage: "Enables debug logging", + }, + } - // Admin actions - createAdmin := flag.String("create-admin", "", "Create an admin with the given username:password") - createUser := flag.String("create-user", "", "Create a regular user with the given username:password") - deleteUsername := flag.String("delete-user", "", "Delete a user with the given username") - resetPassUser := flag.String("reset-pass", "", "Reset the given user's password") - outputVersion := flag.Bool("v", false, "Output the current version") - flag.Parse() + app.Flags = append(app.Flags, defaultFlags...) - app := writefreely.NewApp(*configFile) + app.Commands = []*cli.Command{ + &cmdUser, + &cmdDB, + &cmdConfig, + &cmdKeys, + &cmdServe, + } - if *outputVersion { - writefreely.OutputVersion() - os.Exit(0) - } else if *createConfig { - err := writefreely.CreateConfig(app) + err := app.Run(os.Args) + if err != nil { + log.Error(err.Error()) + os.Exit(1) + } +} + +func legacyActions(c *cli.Context) error { + app := writefreely.NewApp(c.String("c")) + + switch true { + case c.IsSet("create-config"): + return writefreely.CreateConfig(app) + case c.IsSet("config"): + writefreely.DoConfig(app, c.String("sections")) + return nil + case c.IsSet("gen-keys"): + return writefreely.GenerateKeyFiles(app) + case c.IsSet("init-db"): + return writefreely.CreateSchema(app) + case c.IsSet("migrate"): + return writefreely.Migrate(app) + case c.IsSet("create-admin"): + username, password, err := parseCredentials(c.String("create-admin")) if err != nil { - log.Error(err.Error()) - os.Exit(1) + return err } - os.Exit(0) - } else if *doConfig { - writefreely.DoConfig(app, *configSections) - os.Exit(0) - } else if *genKeys { - err := writefreely.GenerateKeyFiles(app) + return writefreely.CreateUser(app, username, password, true) + case c.IsSet("create-user"): + username, password, err := parseCredentials(c.String("create-user")) if err != nil { - log.Error(err.Error()) - os.Exit(1) + return err } - os.Exit(0) - } else if *createSchema { - err := writefreely.CreateSchema(app) - if err != nil { - log.Error(err.Error()) - os.Exit(1) - } - os.Exit(0) - } else if *createAdmin != "" { - username, password, err := userPass(*createAdmin, true) - if err != nil { - log.Error(err.Error()) - os.Exit(1) - } - err = writefreely.CreateUser(app, username, password, true) - if err != nil { - log.Error(err.Error()) - os.Exit(1) - } - os.Exit(0) - } else if *createUser != "" { - username, password, err := userPass(*createUser, false) - if err != nil { - log.Error(err.Error()) - os.Exit(1) - } - err = writefreely.CreateUser(app, username, password, false) - if err != nil { - log.Error(err.Error()) - os.Exit(1) - } - os.Exit(0) - } else if *resetPassUser != "" { - err := writefreely.ResetPassword(app, *resetPassUser) - if err != nil { - log.Error(err.Error()) - os.Exit(1) - } - os.Exit(0) - } else if *deleteUsername != "" { - err := writefreely.DoDeleteAccount(app, *deleteUsername) - if err != nil { - log.Error(err.Error()) - os.Exit(1) - } - os.Exit(0) - } else if *migrate { - err := writefreely.Migrate(app) - if err != nil { - log.Error(err.Error()) - os.Exit(1) - } - os.Exit(0) + return writefreely.CreateUser(app, username, password, false) + case c.IsSet("delete-user"): + return writefreely.DoDeleteAccount(app, c.String("delete-user")) + case c.IsSet("reset-pass"): + return writefreely.ResetPassword(app, c.String("reset-pass")) } // Initialize the application var err error log.Info("Starting %s...", writefreely.FormatVersion()) - app, err = writefreely.Initialize(app, *debugPtr) + app, err = writefreely.Initialize(app, c.Bool("debug")) if err != nil { - log.Error("%s", err) - os.Exit(1) + return err } // Set app routes @@ -136,20 +168,14 @@ func main() { // Serve the application writefreely.Serve(app, r) + + return nil } -func userPass(credStr string, isAdmin bool) (user string, pass string, err error) { - creds := strings.Split(credStr, ":") +func parseCredentials(credentialString string) (string, string, error) { + creds := strings.Split(credentialString, ":") if len(creds) != 2 { - c := "user" - if isAdmin { - c = "admin" - } - err = fmt.Errorf("usage: writefreely --create-%s username:password", c) - return + return "", "", fmt.Errorf("invalid format for passed credentials, must be username:password") } - - user = creds[0] - pass = creds[1] - return + return creds[0], creds[1], nil } diff --git a/cmd/writefreely/user.go b/cmd/writefreely/user.go new file mode 100644 index 0000000..c87129a --- /dev/null +++ b/cmd/writefreely/user.go @@ -0,0 +1,89 @@ +/* + * Copyright © 2020 A Bunch Tell LLC. + * + * This file is part of WriteFreely. + * + * WriteFreely is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, included + * in the LICENSE file in this source code package. + */ + +package main + +import ( + "github.com/writeas/writefreely" + + "github.com/urfave/cli/v2" +) + +var ( + cmdUser cli.Command = cli.Command{ + Name: "user", + Usage: "user management tools", + Subcommands: []*cli.Command{ + &cmdAddUser, + &cmdDelUser, + &cmdResetPass, + // TODO: possibly add a user list command + }, + } + + cmdAddUser cli.Command = cli.Command{ + Name: "add", + Usage: "Add new user", + Aliases: []string{"a"}, + Flags: []cli.Flag{ + &cli.BoolFlag{ + Name: "admin", + Value: false, + Usage: "Create admin user", + }, + }, + Action: addUserAction, + } + + cmdDelUser cli.Command = cli.Command{ + Name: "delete", + Usage: "Delete user", + Aliases: []string{"del", "d"}, + Action: delUserAction, + } + + cmdResetPass cli.Command = cli.Command{ + Name: "reset-pass", + Usage: "Reset user's password", + Aliases: []string{"resetpass", "reset"}, + Action: resetPassAction, + } +) + +func addUserAction(c *cli.Context) error { + credentials := "" + if c.NArg() > 0 { + credentials = c.Args().Get(0) + } + username, password, err := parseCredentials(credentials) + if err != nil { + return err + } + app := writefreely.NewApp(c.String("c")) + return writefreely.CreateUser(app, username, password, c.Bool("admin")) +} + +func delUserAction(c *cli.Context) error { + username := "" + if c.NArg() > 0 { + username = c.Args().Get(0) + } + app := writefreely.NewApp(c.String("c")) + return writefreely.DoDeleteAccount(app, username) +} + +func resetPassAction(c *cli.Context) error { + username := "" + if c.NArg() > 0 { + username = c.Args().Get(0) + } + app := writefreely.NewApp(c.String("c")) + return writefreely.ResetPassword(app, username) +} diff --git a/cmd/writefreely/web.go b/cmd/writefreely/web.go new file mode 100644 index 0000000..e848f8b --- /dev/null +++ b/cmd/writefreely/web.go @@ -0,0 +1,49 @@ +/* + * Copyright © 2020 A Bunch Tell LLC. + * + * This file is part of WriteFreely. + * + * WriteFreely is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, included + * in the LICENSE file in this source code package. + */ + +package main + +import ( + "github.com/writeas/web-core/log" + "github.com/writeas/writefreely" + + "github.com/gorilla/mux" + "github.com/urfave/cli/v2" +) + +var ( + cmdServe cli.Command = cli.Command{ + Name: "serve", + Aliases: []string{"web"}, + Usage: "Run web application", + Action: serveAction, + } +) + +func serveAction(c *cli.Context) error { + // Initialize the application + app := writefreely.NewApp(c.String("c")) + var err error + log.Info("Starting %s...", writefreely.FormatVersion()) + app, err = writefreely.Initialize(app, c.Bool("debug")) + if err != nil { + return err + } + + // Set app routes + r := mux.NewRouter() + writefreely.InitRoutes(app, r) + app.InitStaticRoutes(r) + + // Serve the application + writefreely.Serve(app, r) + + return nil +} \ No newline at end of file diff --git a/go.mod b/go.mod index 5da3da4..28c7105 100644 --- a/go.mod +++ b/go.mod @@ -1,7 +1,6 @@ module github.com/writeas/writefreely require ( - github.com/BurntSushi/toml v0.3.1 // indirect github.com/alecthomas/gometalinter v3.0.0+incompatible // indirect github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf // indirect github.com/captncraig/cors v0.0.0-20180620154129-376d45073b49 // indirect @@ -22,6 +21,7 @@ require ( github.com/guregu/null v3.4.0+incompatible github.com/hashicorp/go-multierror v1.0.0 github.com/ikeikeikeike/go-sitemap-generator/v2 v2.0.2 + github.com/jteeuwen/go-bindata v3.0.7+incompatible // indirect github.com/jtolds/gls v4.2.1+incompatible // indirect github.com/kylemcc/twitter-text-go v0.0.0-20180726194232-7f582f6736ec github.com/lunixbochs/vtclean v1.0.0 // indirect @@ -38,6 +38,7 @@ require ( github.com/smartystreets/assertions v0.0.0-20190116191733-b6c0e53d7304 // indirect github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c // indirect github.com/stretchr/testify v1.3.0 + github.com/urfave/cli/v2 v2.1.1 github.com/writeas/activity v0.1.2 github.com/writeas/activityserve v0.0.0-20191115095800-dd6d19cc8b89 github.com/writeas/go-strip-markdown v2.0.1+incompatible @@ -57,7 +58,6 @@ require ( google.golang.org/appengine v1.4.0 // indirect gopkg.in/alecthomas/kingpin.v3-unstable v3.0.0-20180810215634-df19058c872c // indirect gopkg.in/ini.v1 v1.41.0 - gopkg.in/yaml.v2 v2.2.2 // indirect src.techknowlogick.com/xgo v0.0.0-20200129005940-d0fae26e014b // indirect ) diff --git a/go.sum b/go.sum index 2d433ec..ab220f2 100644 --- a/go.sum +++ b/go.sum @@ -22,6 +22,8 @@ github.com/clbanning/mxj v1.8.4 h1:HuhwZtbyvyOw+3Z1AowPkU87JkJUSv751ELWaiTpj8I= github.com/clbanning/mxj v1.8.4/go.mod h1:BVjHeAH+rl9rs6f+QIpeRl0tfu10SXn1pUSa5PVGJng= github.com/client9/misspell v0.3.4 h1:ta993UF76GwbvJcIo3Y68y/M3WxlpEHPWIGDkJYwzJI= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSYgptZMwQh2aRr3LuazLJIa+Pg3Kc1ylSYVY= +github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -71,6 +73,8 @@ github.com/hashicorp/go-multierror v1.0.0 h1:iVjPR7a6H0tWELX5NxNe7bYopibicUzc7uP github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= github.com/ikeikeikeike/go-sitemap-generator/v2 v2.0.2 h1:wIdDEle9HEy7vBPjC6oKz6ejs3Ut+jmsYvuOoAW2pSM= github.com/ikeikeikeike/go-sitemap-generator/v2 v2.0.2/go.mod h1:WtaVKD9TeruTED9ydiaOJU08qGoEPP/LyzTKiD3jEsw= +github.com/jteeuwen/go-bindata v3.0.7+incompatible h1:91Uy4d9SYVr1kyTJ15wJsog+esAZZl7JmEfTkwmhJts= +github.com/jteeuwen/go-bindata v3.0.7+incompatible/go.mod h1:JVvhzYOiGBnFSYRyV00iY8q7/0PThjIYav1p9h5dmKs= github.com/jtolds/gls v4.2.1+incompatible h1:fSuqC+Gmlu6l/ZYAoZzx2pyucC8Xza35fpRVWLVmUEE= github.com/jtolds/gls v4.2.1+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/juju/ansiterm v0.0.0-20180109212912-720a0952cc2a h1:FaWFmfWdAUKbSCtOU2QjDaorUexogfaMgbipgYATUMU= @@ -112,6 +116,8 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rainycape/unidecode v0.0.0-20150907023854-cb7f23ec59be h1:ta7tUOvsPHVHGom5hKW5VXNc2xZIkfCKP8iaqOyYtUQ= github.com/rainycape/unidecode v0.0.0-20150907023854-cb7f23ec59be/go.mod h1:MIDFMn7db1kT65GmV94GzpX9Qdi7N/pQlwb+AN8wh+Q= +github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q= +github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/smartystreets/assertions v0.0.0-20190116191733-b6c0e53d7304 h1:Jpy1PXuP99tXNrhbq2BaPz9B+jNAvH1JPQQpG/9GCXY= @@ -124,6 +130,9 @@ github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0 github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/tsenart/deadcode v0.0.0-20160724212837-210d2dc333e9 h1:vY5WqiEon0ZSTGM3ayVVi+twaHKHDFUVloaQ/wug9/c= github.com/tsenart/deadcode v0.0.0-20160724212837-210d2dc333e9/go.mod h1:q+QjxYvZ+fpjMXqs+XEriussHjSYqeXVnAdSV1tkMYk= +github.com/urfave/cli v1.22.2 h1:gsqYFH8bb9ekPA12kRo0hfjngWQjkJPlN9R0N78BoUo= +github.com/urfave/cli/v2 v2.1.1 h1:Qt8FeAtxE/vfdrLmR3rxR6JRE0RoVmbXu8+6kZtYU4k= +github.com/urfave/cli/v2 v2.1.1/go.mod h1:SE9GqnLQmjVa0iPEY0f1w3ygNIYcIJ0OKPMoW2caLfQ= github.com/writeas/activity v0.1.2 h1:Y12B5lIrabfqKE7e7HFCWiXrlfXljr9tlkFm2mp7DgY= github.com/writeas/activity v0.1.2/go.mod h1:mYYgiewmEM+8tlifirK/vl6tmB2EbjYaxwb+ndUw5T0= github.com/writeas/activityserve v0.0.0-20191008122325-5fc3b48e70c5 h1:nG84xWpxBM8YU/FJchezJqg7yZH8ImSRow6NoYtbSII= From b0f0de3dde866f3f218c916d0ef70b766dfca1ec Mon Sep 17 00:00:00 2001 From: Matti R Date: Mon, 10 Feb 2020 01:57:57 -0500 Subject: [PATCH 07/51] go mod tidy --- go.mod | 1 - go.sum | 7 ------- 2 files changed, 8 deletions(-) diff --git a/go.mod b/go.mod index 28c7105..fe5b548 100644 --- a/go.mod +++ b/go.mod @@ -21,7 +21,6 @@ require ( github.com/guregu/null v3.4.0+incompatible github.com/hashicorp/go-multierror v1.0.0 github.com/ikeikeikeike/go-sitemap-generator/v2 v2.0.2 - github.com/jteeuwen/go-bindata v3.0.7+incompatible // indirect github.com/jtolds/gls v4.2.1+incompatible // indirect github.com/kylemcc/twitter-text-go v0.0.0-20180726194232-7f582f6736ec github.com/lunixbochs/vtclean v1.0.0 // indirect diff --git a/go.sum b/go.sum index ab220f2..b0a423a 100644 --- a/go.sum +++ b/go.sum @@ -73,8 +73,6 @@ github.com/hashicorp/go-multierror v1.0.0 h1:iVjPR7a6H0tWELX5NxNe7bYopibicUzc7uP github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= github.com/ikeikeikeike/go-sitemap-generator/v2 v2.0.2 h1:wIdDEle9HEy7vBPjC6oKz6ejs3Ut+jmsYvuOoAW2pSM= github.com/ikeikeikeike/go-sitemap-generator/v2 v2.0.2/go.mod h1:WtaVKD9TeruTED9ydiaOJU08qGoEPP/LyzTKiD3jEsw= -github.com/jteeuwen/go-bindata v3.0.7+incompatible h1:91Uy4d9SYVr1kyTJ15wJsog+esAZZl7JmEfTkwmhJts= -github.com/jteeuwen/go-bindata v3.0.7+incompatible/go.mod h1:JVvhzYOiGBnFSYRyV00iY8q7/0PThjIYav1p9h5dmKs= github.com/jtolds/gls v4.2.1+incompatible h1:fSuqC+Gmlu6l/ZYAoZzx2pyucC8Xza35fpRVWLVmUEE= github.com/jtolds/gls v4.2.1+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/juju/ansiterm v0.0.0-20180109212912-720a0952cc2a h1:FaWFmfWdAUKbSCtOU2QjDaorUexogfaMgbipgYATUMU= @@ -130,15 +128,10 @@ github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0 github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/tsenart/deadcode v0.0.0-20160724212837-210d2dc333e9 h1:vY5WqiEon0ZSTGM3ayVVi+twaHKHDFUVloaQ/wug9/c= github.com/tsenart/deadcode v0.0.0-20160724212837-210d2dc333e9/go.mod h1:q+QjxYvZ+fpjMXqs+XEriussHjSYqeXVnAdSV1tkMYk= -github.com/urfave/cli v1.22.2 h1:gsqYFH8bb9ekPA12kRo0hfjngWQjkJPlN9R0N78BoUo= github.com/urfave/cli/v2 v2.1.1 h1:Qt8FeAtxE/vfdrLmR3rxR6JRE0RoVmbXu8+6kZtYU4k= github.com/urfave/cli/v2 v2.1.1/go.mod h1:SE9GqnLQmjVa0iPEY0f1w3ygNIYcIJ0OKPMoW2caLfQ= github.com/writeas/activity v0.1.2 h1:Y12B5lIrabfqKE7e7HFCWiXrlfXljr9tlkFm2mp7DgY= github.com/writeas/activity v0.1.2/go.mod h1:mYYgiewmEM+8tlifirK/vl6tmB2EbjYaxwb+ndUw5T0= -github.com/writeas/activityserve v0.0.0-20191008122325-5fc3b48e70c5 h1:nG84xWpxBM8YU/FJchezJqg7yZH8ImSRow6NoYtbSII= -github.com/writeas/activityserve v0.0.0-20191008122325-5fc3b48e70c5/go.mod h1:Kz62mzYsCnrFTSTSFLXFj3fGYBQOntmBWTDDq57b46A= -github.com/writeas/activityserve v0.0.0-20191011072627-3a81f7784d5b h1:rd2wX/bTqD55hxtBjAhwLcUgaQE36c70KX3NzpDAwVI= -github.com/writeas/activityserve v0.0.0-20191011072627-3a81f7784d5b/go.mod h1:Kz62mzYsCnrFTSTSFLXFj3fGYBQOntmBWTDDq57b46A= github.com/writeas/activityserve v0.0.0-20191115095800-dd6d19cc8b89 h1:NJhzq9aTccL3SSSZMrcnYhkD6sObdY9otNZ1X6/ZKNE= github.com/writeas/activityserve v0.0.0-20191115095800-dd6d19cc8b89/go.mod h1:Kz62mzYsCnrFTSTSFLXFj3fGYBQOntmBWTDDq57b46A= github.com/writeas/go-strip-markdown v2.0.1+incompatible h1:IIqxTM5Jr7RzhigcL6FkrCNfXkvbR+Nbu1ls48pXYcw= From 5d754176e0e0bb37d57a2c36413e8e63967d8561 Mon Sep 17 00:00:00 2001 From: Matt Baer Date: Mon, 10 Feb 2020 15:06:13 -0500 Subject: [PATCH 08/51] Use h1 consistently on user pages instead of h2s. This also removes odd one-time class usages on Account Settings page. --- less/core.less | 3 --- templates/user/articles.tmpl | 2 +- templates/user/collections.tmpl | 2 +- templates/user/export.tmpl | 2 +- templates/user/settings.tmpl | 6 +++--- 5 files changed, 6 insertions(+), 9 deletions(-) diff --git a/less/core.less b/less/core.less index fe8a28d..931aaa8 100644 --- a/less/core.less +++ b/less/core.less @@ -794,9 +794,6 @@ input { &.snug { max-width: 40em; } - &.regular { - font-size: 1em; - } .app { + .app { margin-top: 1.5em; diff --git a/templates/user/articles.tmpl b/templates/user/articles.tmpl index be80cc7..e96d51e 100644 --- a/templates/user/articles.tmpl +++ b/templates/user/articles.tmpl @@ -10,7 +10,7 @@ {{template "user-silenced"}} {{end}} -

drafts

+

Drafts

{{ if .AnonymousPosts }}

These are your draft posts. You can share them individually (without a blog) or move them to your blog when you're ready.

diff --git a/templates/user/collections.tmpl b/templates/user/collections.tmpl index da68dcc..b2ce51a 100644 --- a/templates/user/collections.tmpl +++ b/templates/user/collections.tmpl @@ -10,7 +10,7 @@ {{if .Silenced}} {{template "user-silenced"}} {{end}} -

blogs

+

Blogs

    {{range $i, $el := .Collections}}
  • {{if .Title}}{{.Title}}{{else}}{{.Alias}}{{end}} diff --git a/templates/user/export.tmpl b/templates/user/export.tmpl index 015722c..fee97d6 100644 --- a/templates/user/export.tmpl +++ b/templates/user/export.tmpl @@ -2,7 +2,7 @@ {{template "header" .}}
    -

    Export

    +

    Export

    Your data on {{.SiteName}} is always free. Download and back-up your work any time.

    diff --git a/templates/user/settings.tmpl b/templates/user/settings.tmpl index ab60705..df85770 100644 --- a/templates/user/settings.tmpl +++ b/templates/user/settings.tmpl @@ -6,11 +6,11 @@ h3 { font-weight: normal; } .section > *:not(input) { font-size: 0.86em; } -
    +
    {{if .Silenced}} {{template "user-silenced"}} {{end}} -

    {{if .IsLogOut}}Before you go...{{else}}Account Settings {{if .IsAdmin}}admin settings{{end}}{{end}}

    +

    {{if .IsLogOut}}Before you go...{{else}}Account Settings {{if .IsAdmin}}admin settings{{end}}{{end}}

    {{if .Flashes}}
      {{range .Flashes}}
    • {{.}}
    • {{end}}
    {{end}} @@ -20,7 +20,7 @@ h3 { font-weight: normal; }

    Please add an email address and/or passphrase so you can log in again later.

    {{ else }} -
    +

    Change your account settings here.

    From b1d006fcf2582122990581764713c51bb55f6ab1 Mon Sep 17 00:00:00 2001 From: Matt Baer Date: Mon, 10 Feb 2020 15:08:42 -0500 Subject: [PATCH 09/51] Make Reader width consistent with other pages --- templates/read.tmpl | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/templates/read.tmpl b/templates/read.tmpl index ddcccdd..fe42f7b 100644 --- a/templates/read.tmpl +++ b/templates/read.tmpl @@ -75,11 +75,14 @@ body#collection header nav.tabs a:first-child { margin-left: 1em; } + body#collection article { + max-width: 40em !important; + } {{end}} {{define "body-attrs"}}id="collection"{{end}} {{define "content"}} -
    +

    {{.ContentTitle}}

    {{if .SelTopic}}#{{.SelTopic}} posts{{else}}{{.Content}}{{end}}

    From 33474cb1f1730b5b392e2b3a54d7ffbbb777f389 Mon Sep 17 00:00:00 2001 From: Rob Loranger Date: Tue, 11 Feb 2020 13:02:10 -0800 Subject: [PATCH 10/51] change to simpler style --- less/core.less | 25 +++++++++---------------- 1 file changed, 9 insertions(+), 16 deletions(-) diff --git a/less/core.less b/less/core.less index 99aaa0e..67fd08b 100644 --- a/less/core.less +++ b/less/core.less @@ -632,26 +632,19 @@ table.classy { } } -body#post article table { +article table { border-spacing: 0; border-collapse: collapse; + width: 100%; th { - border-bottom: 2px solid #ccc; - text-align: center; + border-width: 1px 1px 2px 1px; + border-style: solid; + border-color: #ccc; } - th + th { - border-left: 1px solid #ccc; - } - tr:nth-child(even) { - background-color: #f6f6f6; - } - td + td { - border-left: 1px solid #ccc; - } - tr + tr td { - border-top: 1px solid #ccc; - } - td, th { + td { + border-width: 0 1px 1px 1px; + border-style: solid; + border-color: #ccc; padding: .25rem .5rem; } } From 8ce7d4c9fcb8aaccbe176b1191661b3ce5b7ca59 Mon Sep 17 00:00:00 2001 From: Matt Baer Date: Wed, 12 Feb 2020 11:39:39 -0500 Subject: [PATCH 11/51] Add isIgnorableError to database-lib.go --- database-lib.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/database-lib.go b/database-lib.go index 58beb05..b6b4be2 100644 --- a/database-lib.go +++ b/database-lib.go @@ -1,7 +1,7 @@ // +build wflib /* - * Copyright © 2019 A Bunch Tell LLC. + * Copyright © 2019-2020 A Bunch Tell LLC. * * This file is part of WriteFreely. * @@ -18,3 +18,7 @@ package writefreely func (db *datastore) isDuplicateKeyErr(err error) bool { return false } + +func (db *datastore) isIgnorableError(err error) bool { + return false +} From 71224d68a268a50438e11a69a6850eba00e39300 Mon Sep 17 00:00:00 2001 From: Matt Baer Date: Fri, 14 Feb 2020 08:40:06 -0500 Subject: [PATCH 12/51] Change line-height to 1.5 --- less/core.less | 10 +++++----- less/new-core.less | 4 ++-- less/post-temp.less | 2 +- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/less/core.less b/less/core.less index fe8a28d..83a9d2a 100644 --- a/less/core.less +++ b/less/core.less @@ -524,12 +524,12 @@ pre, body#post article, #post .alert, #subpage .alert, body#collection article, margin-bottom: 1em; p { text-align: left; - line-height: 1.4; + line-height: 1.5; } } textarea, pre, body#post article, body#collection article p { &.norm, &.sans, &.wrap { - line-height: 1.4em; + line-height: 1.5; white-space: pre-wrap; /* CSS 3 */ white-space: -moz-pre-wrap; /* Mozilla, since 1999 */ white-space: -pre-wrap; /* Opera 4-6 */ @@ -644,7 +644,7 @@ body#collection article, body#subpage article { padding-bottom: 0; .book { h2 { - font-size: 1.4em; + font-size: 1.5; } a.hidden.action { color: #666; @@ -813,7 +813,7 @@ input { font-weight: normal; } p { - line-height: 1.4; + line-height: 1.5; } li { margin: 0.3em 0; @@ -1007,7 +1007,7 @@ footer.contain-me { } li { - line-height: 1.4; + line-height: 1.5; .item-desc, .prog-lang { font-size: 0.6em; diff --git a/less/new-core.less b/less/new-core.less index 802f34d..d618042 100644 --- a/less/new-core.less +++ b/less/new-core.less @@ -113,7 +113,7 @@ textarea { ul { margin: 0; padding: 0 0 0 1em; - line-height: 1.4; + line-height: 1.5; &.collections, &.posts, &.integrations { list-style: none; @@ -206,7 +206,7 @@ code, textarea#embed { font-weight: normal; } p { - line-height: 1.4; + line-height: 1.5; } li { margin: 0.3em 0; diff --git a/less/post-temp.less b/less/post-temp.less index 8173864..1a05280 100644 --- a/less/post-temp.less +++ b/less/post-temp.less @@ -58,7 +58,7 @@ body#post article, pre, .hljs { } } .article-p() { - line-height: 1.4em; + line-height: 1.5; white-space: pre-wrap; /* CSS 3 */ white-space: -moz-pre-wrap; /* Mozilla, since 1999 */ white-space: -pre-wrap; /* Opera 4-6 */ From 92da069ce4b704f28dd97457d1b025b31c5cd43e Mon Sep 17 00:00:00 2001 From: Matt Baer Date: Fri, 14 Feb 2020 13:55:24 -0500 Subject: [PATCH 13/51] Move admin dashboard sections into subpages This moves app config to a "Settings" page and the application monitor to a "Monitor" page. It also reworks the admin navigation bar a bit and adds some instance stats on the dashboard. Ref T694 --- admin.go | 51 ++++++++- less/admin.less | 8 +- routes.go | 2 + templates/user/admin.tmpl | 146 ++----------------------- templates/user/admin/app-settings.tmpl | 85 ++++++++++++++ templates/user/admin/monitor.tmpl | 105 ++++++++++++++++++ templates/user/include/header.tmpl | 4 +- 7 files changed, 259 insertions(+), 142 deletions(-) create mode 100644 templates/user/admin/app-settings.tmpl create mode 100644 templates/user/admin/monitor.tmpl diff --git a/admin.go b/admin.go index 5f7d244..33445ac 100644 --- a/admin.go +++ b/admin.go @@ -1,5 +1,5 @@ /* - * Copyright © 2018-2019 A Bunch Tell LLC. + * Copyright © 2018-2020 A Bunch Tell LLC. * * This file is part of WriteFreely. * @@ -100,6 +100,33 @@ func (c instanceContent) UpdatedFriendly() string { } func handleViewAdminDash(app *App, u *User, w http.ResponseWriter, r *http.Request) error { + p := struct { + *UserPage + Message string + + UsersCount, CollectionsCount, PostsCount int64 + }{ + UserPage: NewUserPage(app, r, u, "Admin", nil), + Message: r.FormValue("m"), + } + + // Get user stats + p.UsersCount = app.db.GetAllUsersCount() + var err error + p.CollectionsCount, err = app.db.GetTotalCollections() + if err != nil { + return err + } + p.PostsCount, err = app.db.GetTotalPosts() + if err != nil { + return err + } + + showUserPage(w, "admin", p) + return nil +} + +func handleViewAdminMonitor(app *App, u *User, w http.ResponseWriter, r *http.Request) error { updateAppStats() p := struct { *UserPage @@ -116,7 +143,25 @@ func handleViewAdminDash(app *App, u *User, w http.ResponseWriter, r *http.Reque ConfigMessage: r.FormValue("cm"), } - showUserPage(w, "admin", p) + showUserPage(w, "monitor", p) + return nil +} + +func handleViewAdminSettings(app *App, u *User, w http.ResponseWriter, r *http.Request) error { + p := struct { + *UserPage + Config config.AppCfg + + Message, ConfigMessage string + }{ + UserPage: NewUserPage(app, r, u, "Admin", nil), + Config: app.cfg.App, + + Message: r.FormValue("m"), + ConfigMessage: r.FormValue("cm"), + } + + showUserPage(w, "app-settings", p) return nil } @@ -475,7 +520,7 @@ func handleAdminUpdateConfig(apper Apper, u *User, w http.ResponseWriter, r *htt if err != nil { m = "?cm=" + err.Error() } - return impart.HTTPError{http.StatusFound, "/admin" + m + "#config"} + return impart.HTTPError{http.StatusFound, "/admin/settings" + m + "#config"} } func updateAppStats() { diff --git a/less/admin.less b/less/admin.less index 9c4a7c2..3cbf30b 100644 --- a/less/admin.less +++ b/less/admin.less @@ -13,11 +13,11 @@ nav#admin { display: block; margin: 0.5em 0; a { - color: @primary; - &:first-child { - margin-left: 0; - } + margin-left: 0; + .rounded(.25em); + border: 0; &.selected { + background: #dedede; font-weight: bold; } } diff --git a/routes.go b/routes.go index fcd00ec..e606fbc 100644 --- a/routes.go +++ b/routes.go @@ -152,6 +152,8 @@ func InitRoutes(apper Apper, r *mux.Router) *mux.Router { write.HandleFunc("/auth/login", handler.Web(webLogin, UserLevelNoneRequired)).Methods("POST") write.HandleFunc("/admin", handler.Admin(handleViewAdminDash)).Methods("GET") + write.HandleFunc("/admin/monitor", handler.Admin(handleViewAdminMonitor)).Methods("GET") + write.HandleFunc("/admin/settings", handler.Admin(handleViewAdminSettings)).Methods("GET") write.HandleFunc("/admin/users", handler.Admin(handleViewAdminUsers)).Methods("GET") write.HandleFunc("/admin/user/{username}", handler.Admin(handleViewAdminUser)).Methods("GET") write.HandleFunc("/admin/user/{username}/status", handler.Admin(handleAdminToggleUserStatus)).Methods("POST") diff --git a/templates/user/admin.tmpl b/templates/user/admin.tmpl index 3cb81be..1a0e6b4 100644 --- a/templates/user/admin.tmpl +++ b/templates/user/admin.tmpl @@ -35,6 +35,14 @@ form dt { p.docs { font-size: 0.86em; } +.stats { + font-size: 1.2em; + margin: 1em 0; +} +.num { + font-weight: bold; + font-size: 1.5em; +}
    @@ -42,142 +50,12 @@ p.docs { {{if .Message}}

    {{.Message}}

    {{end}} -

    On this page

    - - -

    Resources

    - - -
    - -

    App Configuration

    -

    Read more in the configuration docs.

    - - {{if .ConfigMessage}}

    {{.ConfigMessage}}

    {{end}} - -
    -
    -
    -
    - +
    +
    {{largeNumFmt .UsersCount}} {{pluralize "user" "users" .UsersCount}}
    +
    {{largeNumFmt .CollectionsCount}} {{pluralize "blog" "blogs" .CollectionsCount}}
    +
    {{largeNumFmt .PostsCount}} {{pluralize "post" "posts" .PostsCount}}
    - -
    - -

    Application

    - -
    -
    -
    WriteFreely
    -
    {{.Version}}
    -
    Server Uptime
    -
    {{.SysStatus.Uptime}}
    -
    Current Goroutines
    -
    {{.SysStatus.NumGoroutine}}
    -
    -
    Current memory usage
    -
    {{.SysStatus.MemAllocated}}
    -
    Total mem allocated
    -
    {{.SysStatus.MemTotal}}
    -
    Memory obtained
    -
    {{.SysStatus.MemSys}}
    -
    Pointer lookup times
    -
    {{.SysStatus.Lookups}}
    -
    Memory allocate times
    -
    {{.SysStatus.MemMallocs}}
    -
    Memory free times
    -
    {{.SysStatus.MemFrees}}
    -
    -
    Current heap usage
    -
    {{.SysStatus.HeapAlloc}}
    -
    Heap memory obtained
    -
    {{.SysStatus.HeapSys}}
    -
    Heap memory idle
    -
    {{.SysStatus.HeapIdle}}
    -
    Heap memory in use
    -
    {{.SysStatus.HeapInuse}}
    -
    Heap memory released
    -
    {{.SysStatus.HeapReleased}}
    -
    Heap objects
    -
    {{.SysStatus.HeapObjects}}
    -
    -
    Bootstrap stack usage
    -
    {{.SysStatus.StackInuse}}
    -
    Stack memory obtained
    -
    {{.SysStatus.StackSys}}
    -
    MSpan structures in use
    -
    {{.SysStatus.MSpanInuse}}
    -
    MSpan structures obtained
    -
    {{.SysStatus.HeapSys}}
    -
    MCache structures in use
    -
    {{.SysStatus.MCacheInuse}}
    -
    MCache structures obtained
    -
    {{.SysStatus.MCacheSys}}
    -
    Profiling bucket hash table obtained
    -
    {{.SysStatus.BuckHashSys}}
    -
    GC metadata obtained
    -
    {{.SysStatus.GCSys}}
    -
    Other system allocation obtained
    -
    {{.SysStatus.OtherSys}}
    -
    -
    Next GC recycle
    -
    {{.SysStatus.NextGC}}
    -
    Since last GC
    -
    {{.SysStatus.LastGC}}
    -
    Total GC pause
    -
    {{.SysStatus.PauseTotalNs}}
    -
    Last GC pause
    -
    {{.SysStatus.PauseNs}}
    -
    GC times
    -
    {{.SysStatus.NumGC}}
    -
    -
    + +{{template "footer" .}} + +{{template "body-end" .}} +{{end}} diff --git a/templates/user/admin/monitor.tmpl b/templates/user/admin/monitor.tmpl new file mode 100644 index 0000000..e803dd3 --- /dev/null +++ b/templates/user/admin/monitor.tmpl @@ -0,0 +1,105 @@ +{{define "monitor"}} +{{template "header" .}} + + + +
    + {{template "admin-header" .}} + + {{if .Message}}

    {{.Message}}

    {{end}} + +

    Application Monitor

    + +
    +
    +
    WriteFreely
    +
    {{.Version}}
    +
    Server Uptime
    +
    {{.SysStatus.Uptime}}
    +
    Current Goroutines
    +
    {{.SysStatus.NumGoroutine}}
    +
    +
    Current memory usage
    +
    {{.SysStatus.MemAllocated}}
    +
    Total mem allocated
    +
    {{.SysStatus.MemTotal}}
    +
    Memory obtained
    +
    {{.SysStatus.MemSys}}
    +
    Pointer lookup times
    +
    {{.SysStatus.Lookups}}
    +
    Memory allocate times
    +
    {{.SysStatus.MemMallocs}}
    +
    Memory free times
    +
    {{.SysStatus.MemFrees}}
    +
    +
    Current heap usage
    +
    {{.SysStatus.HeapAlloc}}
    +
    Heap memory obtained
    +
    {{.SysStatus.HeapSys}}
    +
    Heap memory idle
    +
    {{.SysStatus.HeapIdle}}
    +
    Heap memory in use
    +
    {{.SysStatus.HeapInuse}}
    +
    Heap memory released
    +
    {{.SysStatus.HeapReleased}}
    +
    Heap objects
    +
    {{.SysStatus.HeapObjects}}
    +
    +
    Bootstrap stack usage
    +
    {{.SysStatus.StackInuse}}
    +
    Stack memory obtained
    +
    {{.SysStatus.StackSys}}
    +
    MSpan structures in use
    +
    {{.SysStatus.MSpanInuse}}
    +
    MSpan structures obtained
    +
    {{.SysStatus.HeapSys}}
    +
    MCache structures in use
    +
    {{.SysStatus.MCacheInuse}}
    +
    MCache structures obtained
    +
    {{.SysStatus.MCacheSys}}
    +
    Profiling bucket hash table obtained
    +
    {{.SysStatus.BuckHashSys}}
    +
    GC metadata obtained
    +
    {{.SysStatus.GCSys}}
    +
    Other system allocation obtained
    +
    {{.SysStatus.OtherSys}}
    +
    +
    Next GC recycle
    +
    {{.SysStatus.NextGC}}
    +
    Since last GC
    +
    {{.SysStatus.LastGC}}
    +
    Total GC pause
    +
    {{.SysStatus.PauseTotalNs}}
    +
    Last GC pause
    +
    {{.SysStatus.PauseNs}}
    +
    GC times
    +
    {{.SysStatus.NumGC}}
    +
    +
    +
    + +{{template "footer" .}} + +{{template "body-end" .}} +{{end}} diff --git a/templates/user/include/header.tmpl b/templates/user/include/header.tmpl index 0704854..f7c06ef 100644 --- a/templates/user/include/header.tmpl +++ b/templates/user/include/header.tmpl @@ -97,12 +97,14 @@ {{define "admin-header"}}

    Admin

    -
    {{end}} From b58464addbe0be591c257f244e8bc5f53f15d89d Mon Sep 17 00:00:00 2001 From: Matt Baer Date: Fri, 14 Feb 2020 14:08:53 -0500 Subject: [PATCH 14/51] Optionally hide Monitor page in Admin nav This adds a new config option that signifies the admin doesn't need to see deeply technical things, like application resource usage. In the [app] section, set forest = true to enable this. Ref T694 --- config/config.go | 1 + templates/user/include/header.tmpl | 2 ++ 2 files changed, 3 insertions(+) diff --git a/config/config.go b/config/config.go index 2616e9e..2313732 100644 --- a/config/config.go +++ b/config/config.go @@ -93,6 +93,7 @@ type ( // Site functionality Chorus bool `ini:"chorus"` + Forest bool `ini:"forest"` // The admin cares about the forest, not the trees. Hide unnecessary technical info. DisableDrafts bool `ini:"disable_drafts"` // Users diff --git a/templates/user/include/header.tmpl b/templates/user/include/header.tmpl index f7c06ef..d818336 100644 --- a/templates/user/include/header.tmpl +++ b/templates/user/include/header.tmpl @@ -104,7 +104,9 @@ Users Pages {{end}} + {{if not .Forest}} Monitor + {{end}} {{end}} From 0d79057bae1dfb571ec0e7e55b04be57332da2af Mon Sep 17 00:00:00 2001 From: Matt Baer Date: Fri, 14 Feb 2020 16:13:54 -0500 Subject: [PATCH 15/51] Rename ReleaseURL() to ReleaseNotesURL() --- admin.go | 10 +++++----- templates/user/admin/app-updates.tmpl | 2 +- updates.go | 6 +++--- updates_test.go | 2 +- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/admin.go b/admin.go index 8d71aad..e3a1b33 100644 --- a/admin.go +++ b/admin.go @@ -583,17 +583,17 @@ func handleViewAdminUpdates(app *App, u *User, w http.ResponseWriter, r *http.Re p := struct { *UserPage - LastChecked string - LatestVersion string - LatestReleaseURL string - UpdateAvailable bool + LastChecked string + LatestVersion string + LatestReleaseNotesURL string + UpdateAvailable bool }{ UserPage: NewUserPage(app, r, u, "Updates", nil), } if app.cfg.App.UpdateChecks { p.LastChecked = app.updates.lastCheck.Format("January 2, 2006, 3:04 PM") p.LatestVersion = app.updates.LatestVersion() - p.LatestReleaseURL = app.updates.ReleaseURL() + p.LatestReleaseNotesURL = app.updates.ReleaseNotesURL() p.UpdateAvailable = app.updates.AreAvailable() } diff --git a/templates/user/admin/app-updates.tmpl b/templates/user/admin/app-updates.tmpl index 540fb7c..3988a50 100644 --- a/templates/user/admin/app-updates.tmpl +++ b/templates/user/admin/app-updates.tmpl @@ -12,7 +12,7 @@ {{else}}

    WriteFreely {{.LatestVersion}} is available.

    - For details on features, bug fixes or notes on upgrading, read the release notes. + For details on features, bug fixes or notes on upgrading, read the release notes.
    {{end}}

    Last checked at: {{.LastChecked}}. Check now.

    diff --git a/updates.go b/updates.go index c33247b..3a14233 100644 --- a/updates.go +++ b/updates.go @@ -1,5 +1,5 @@ /* - * Copyright © 2018-2019 A Bunch Tell LLC. + * Copyright © 2019-2020 A Bunch Tell LLC. * * This file is part of WriteFreely. * @@ -63,9 +63,9 @@ func (uc updatesCache) LatestVersion() string { return uc.latestVersion } -// ReleaseURL returns the full URL to the blog.writefreely.org release notes +// ReleaseNotesURL returns the full URL to the blog.writefreely.org release notes // for the latest version as stored in the cache. -func (uc updatesCache) ReleaseURL() string { +func (uc updatesCache) ReleaseNotesURL() string { ver := strings.TrimPrefix(uc.latestVersion, "v") ver = strings.TrimSuffix(ver, ".0") // hack until go 1.12 in build/travis diff --git a/updates_test.go b/updates_test.go index 2cb9f92..1c63f30 100644 --- a/updates_test.go +++ b/updates_test.go @@ -24,7 +24,7 @@ func TestUpdatesRoundTrip(t *testing.T) { }) t.Run("Release URL", func(t *testing.T) { - url := cache.ReleaseURL() + url := cache.ReleaseNotesURL() reg, err := regexp.Compile(`^https:\/\/blog.writefreely.org\/version(-\d+){1,}$`) if err != nil { From 602cd800202526ead1df9499944b91856dd6c604 Mon Sep 17 00:00:00 2001 From: Matt Baer Date: Fri, 14 Feb 2020 16:26:13 -0500 Subject: [PATCH 16/51] Fix mismatched span in user/admin/users.tmpl --- templates/user/admin/users.tmpl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/user/admin/users.tmpl b/templates/user/admin/users.tmpl index fb69d3a..51dd54a 100644 --- a/templates/user/admin/users.tmpl +++ b/templates/user/admin/users.tmpl @@ -4,7 +4,7 @@
    {{template "admin-header" .}} -

    Users {{.TotalUsers}} total

    +

    Users {{.TotalUsers}} total

    From 7d15b799f057a0da347d33348e5b1c62e0b240e3 Mon Sep 17 00:00:00 2001 From: Shlee Date: Sat, 15 Feb 2020 23:27:13 +0800 Subject: [PATCH 17/51] Update Dockerfile --- Dockerfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index 2ae05a6..fd6589d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,5 @@ # Build image -FROM golang:1.12-alpine as build +FROM golang:1.13-alpine as build RUN apk add --update nodejs nodejs-npm make g++ git sqlite-dev RUN npm install -g less less-plugin-clean-css @@ -22,7 +22,7 @@ RUN mkdir /stage && \ /stage # Final image -FROM alpine:3.8 +FROM alpine:3.11 RUN apk add --no-cache openssl ca-certificates COPY --from=build --chown=daemon:daemon /stage /go From 68e992a55e4037bdc0dc24de9939d9119c8a7464 Mon Sep 17 00:00:00 2001 From: Matt Baer Date: Sat, 15 Feb 2020 11:28:06 -0500 Subject: [PATCH 18/51] Fix bad #status anchor in view-user.tmpl --- templates/user/admin/view-user.tmpl | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/templates/user/admin/view-user.tmpl b/templates/user/admin/view-user.tmpl index 8e5d87b..95bc416 100644 --- a/templates/user/admin/view-user.tmpl +++ b/templates/user/admin/view-user.tmpl @@ -71,8 +71,7 @@ input.copy-text { - - +
    StatusStatus {{if .User.IsSilenced}}

    Silenced

    From 48ca695c463afdbe7ac6e96d2a0b1c8f83439685 Mon Sep 17 00:00:00 2001 From: Matt Baer Date: Sat, 15 Feb 2020 11:57:55 -0500 Subject: [PATCH 19/51] Show last update check time in local timezone --- admin.go | 2 ++ templates/user/admin/app-updates.tmpl | 9 ++++++++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/admin.go b/admin.go index e3a1b33..7be67e9 100644 --- a/admin.go +++ b/admin.go @@ -584,6 +584,7 @@ func handleViewAdminUpdates(app *App, u *User, w http.ResponseWriter, r *http.Re p := struct { *UserPage LastChecked string + LastChecked8601 string LatestVersion string LatestReleaseNotesURL string UpdateAvailable bool @@ -592,6 +593,7 @@ func handleViewAdminUpdates(app *App, u *User, w http.ResponseWriter, r *http.Re } if app.cfg.App.UpdateChecks { p.LastChecked = app.updates.lastCheck.Format("January 2, 2006, 3:04 PM") + p.LastChecked8601 = app.updates.lastCheck.Format("2006-01-02T15:04:05Z") p.LatestVersion = app.updates.LatestVersion() p.LatestReleaseNotesURL = app.updates.ReleaseNotesURL() p.UpdateAvailable = app.updates.AreAvailable() diff --git a/templates/user/admin/app-updates.tmpl b/templates/user/admin/app-updates.tmpl index 3988a50..5d91230 100644 --- a/templates/user/admin/app-updates.tmpl +++ b/templates/user/admin/app-updates.tmpl @@ -15,7 +15,14 @@ For details on features, bug fixes or notes on upgrading, read the release notes. {{end}} -

    Last checked at: {{.LastChecked}}. Check now.

    +

    Last checked at: . Check now.

    + + {{template "footer" .}} From a06bb457de77d9d1c397c1552ed331ceedaa3a62 Mon Sep 17 00:00:00 2001 From: Matt Baer Date: Sat, 15 Feb 2020 11:59:47 -0500 Subject: [PATCH 20/51] Change copy and design on WF Updates page - Tweak the copy - Include link to download latest release - Change the version status design a bit - Restyle some text --- admin.go | 2 ++ less/core.less | 5 +++++ templates/user/admin/app-updates.tmpl | 20 ++++++++++++-------- updates.go | 4 ++++ 4 files changed, 23 insertions(+), 8 deletions(-) diff --git a/admin.go b/admin.go index 7be67e9..89621ae 100644 --- a/admin.go +++ b/admin.go @@ -586,6 +586,7 @@ func handleViewAdminUpdates(app *App, u *User, w http.ResponseWriter, r *http.Re LastChecked string LastChecked8601 string LatestVersion string + LatestReleaseURL string LatestReleaseNotesURL string UpdateAvailable bool }{ @@ -595,6 +596,7 @@ func handleViewAdminUpdates(app *App, u *User, w http.ResponseWriter, r *http.Re p.LastChecked = app.updates.lastCheck.Format("January 2, 2006, 3:04 PM") p.LastChecked8601 = app.updates.lastCheck.Format("2006-01-02T15:04:05Z") p.LatestVersion = app.updates.LatestVersion() + p.LatestReleaseURL = app.updates.ReleaseURL() p.LatestReleaseNotesURL = app.updates.ReleaseNotesURL() p.UpdateAvailable = app.updates.AreAvailable() } diff --git a/less/core.less b/less/core.less index fe8a28d..0e5798b 100644 --- a/less/core.less +++ b/less/core.less @@ -1345,6 +1345,11 @@ div.row { } } +.check { + font-size: 1.125em; + color: #71D571; +} + @media all and (max-width: 450px) { body#post { header { diff --git a/templates/user/admin/app-updates.tmpl b/templates/user/admin/app-updates.tmpl index 5d91230..c7d9913 100644 --- a/templates/user/admin/app-updates.tmpl +++ b/templates/user/admin/app-updates.tmpl @@ -2,20 +2,24 @@ {{template "header" .}}
    {{template "admin-header" .}} {{if not .UpdateAvailable}} -

    WriteFreely is up to date.

    - {{else}} -

    WriteFreely {{.LatestVersion}} is available.

    -
    - For details on features, bug fixes or notes on upgrading, read the release notes. -
    - {{end}} -

    Last checked at: . Check now.

    +

    WriteFreely is up to date.

    +

    Installed version: {{.Version}} (release notes).

    + {{else}} +

    A new version of WriteFreely is available! Get {{.LatestVersion}}

    +

    + Read the release notes for details on features, bug fixes, and notes on upgrading from your current version, {{.Version}}. +

    + {{end}} +

    Last checked: . Check now.

    +{{ else }} +

    Automated update checks are disabled.

    +

    Installed version: {{.Version}} (release notes).

    +

    Learn about latest releases on the WriteFreely blog or forum.

    +{{ end }} {{template "footer" .}} diff --git a/updates.go b/updates.go index eba4810..e8f0d21 100644 --- a/updates.go +++ b/updates.go @@ -70,7 +70,11 @@ func (uc updatesCache) ReleaseURL() string { // ReleaseNotesURL returns the full URL to the blog.writefreely.org release notes // for the latest version as stored in the cache. func (uc updatesCache) ReleaseNotesURL() string { - ver := strings.TrimPrefix(uc.latestVersion, "v") + return wfReleaseNotesURL(uc.latestVersion) +} + +func wfReleaseNotesURL(v string) string { + ver := strings.TrimPrefix(v, "v") ver = strings.TrimSuffix(ver, ".0") // hack until go 1.12 in build/travis seg := strings.Split(ver, ".") From c2ece926e0bae96acfb9928860df35cbf121f278 Mon Sep 17 00:00:00 2001 From: Matt Baer Date: Sat, 15 Feb 2020 12:53:47 -0500 Subject: [PATCH 22/51] Show update notification in admin nav bar When a WriteFreely update is available, indicate this on the "Updates" navigation item Ref T572 --- admin.go | 47 ++++++++++++++++++++++-------- less/admin.less | 6 ++++ less/core.less | 2 +- templates/user/include/header.tmpl | 2 +- updates.go | 6 ++++ 5 files changed, 49 insertions(+), 14 deletions(-) diff --git a/admin.go b/admin.go index 55cfb17..c0ad436 100644 --- a/admin.go +++ b/admin.go @@ -90,6 +90,16 @@ type instanceContent struct { Updated time.Time } +type AdminPage struct { + UpdateAvailable bool +} + +func NewAdminPage(app *App) *AdminPage { + return &AdminPage{ + UpdateAvailable: app.updates.AreAvailableNoCheck(), + } +} + func (c instanceContent) UpdatedFriendly() string { /* // TODO: accept a locale in this method and use that for the format @@ -102,12 +112,14 @@ func (c instanceContent) UpdatedFriendly() string { func handleViewAdminDash(app *App, u *User, w http.ResponseWriter, r *http.Request) error { p := struct { *UserPage + *AdminPage Message string UsersCount, CollectionsCount, PostsCount int64 }{ - UserPage: NewUserPage(app, r, u, "Admin", nil), - Message: r.FormValue("m"), + UserPage: NewUserPage(app, r, u, "Admin", nil), + AdminPage: NewAdminPage(app), + Message: r.FormValue("m"), } // Get user stats @@ -130,12 +142,14 @@ func handleViewAdminMonitor(app *App, u *User, w http.ResponseWriter, r *http.Re updateAppStats() p := struct { *UserPage + *AdminPage SysStatus systemStatus Config config.AppCfg Message, ConfigMessage string }{ UserPage: NewUserPage(app, r, u, "Admin", nil), + AdminPage: NewAdminPage(app), SysStatus: sysStatus, Config: app.cfg.App, @@ -150,12 +164,14 @@ func handleViewAdminMonitor(app *App, u *User, w http.ResponseWriter, r *http.Re func handleViewAdminSettings(app *App, u *User, w http.ResponseWriter, r *http.Request) error { p := struct { *UserPage + *AdminPage Config config.AppCfg Message, ConfigMessage string }{ - UserPage: NewUserPage(app, r, u, "Admin", nil), - Config: app.cfg.App, + UserPage: NewUserPage(app, r, u, "Admin", nil), + AdminPage: NewAdminPage(app), + Config: app.cfg.App, Message: r.FormValue("m"), ConfigMessage: r.FormValue("cm"), @@ -168,6 +184,7 @@ func handleViewAdminSettings(app *App, u *User, w http.ResponseWriter, r *http.R func handleViewAdminUsers(app *App, u *User, w http.ResponseWriter, r *http.Request) error { p := struct { *UserPage + *AdminPage Config config.AppCfg Message string @@ -176,9 +193,10 @@ func handleViewAdminUsers(app *App, u *User, w http.ResponseWriter, r *http.Requ TotalUsers int64 TotalPages []int }{ - UserPage: NewUserPage(app, r, u, "Users", nil), - Config: app.cfg.App, - Message: r.FormValue("m"), + UserPage: NewUserPage(app, r, u, "Users", nil), + AdminPage: NewAdminPage(app), + Config: app.cfg.App, + Message: r.FormValue("m"), } p.TotalUsers = app.db.GetAllUsersCount() @@ -214,6 +232,7 @@ func handleViewAdminUser(app *App, u *User, w http.ResponseWriter, r *http.Reque p := struct { *UserPage + *AdminPage Config config.AppCfg Message string @@ -349,14 +368,16 @@ func handleAdminResetUserPass(app *App, u *User, w http.ResponseWriter, r *http. func handleViewAdminPages(app *App, u *User, w http.ResponseWriter, r *http.Request) error { p := struct { *UserPage + *AdminPage Config config.AppCfg Message string Pages []*instanceContent }{ - UserPage: NewUserPage(app, r, u, "Pages", nil), - Config: app.cfg.App, - Message: r.FormValue("m"), + UserPage: NewUserPage(app, r, u, "Pages", nil), + AdminPage: NewAdminPage(app), + Config: app.cfg.App, + Message: r.FormValue("m"), } var err error @@ -413,6 +434,7 @@ func handleViewAdminPage(app *App, u *User, w http.ResponseWriter, r *http.Reque p := struct { *UserPage + *AdminPage Config config.AppCfg Message string @@ -583,15 +605,16 @@ func handleViewAdminUpdates(app *App, u *User, w http.ResponseWriter, r *http.Re p := struct { *UserPage + *AdminPage CurReleaseNotesURL string LastChecked string LastChecked8601 string LatestVersion string LatestReleaseURL string LatestReleaseNotesURL string - UpdateAvailable bool }{ - UserPage: NewUserPage(app, r, u, "Updates", nil), + UserPage: NewUserPage(app, r, u, "Updates", nil), + AdminPage: NewAdminPage(app), } p.CurReleaseNotesURL = wfReleaseNotesURL(p.Version) if app.cfg.App.UpdateChecks { diff --git a/less/admin.less b/less/admin.less index 3cbf30b..2aa34dc 100644 --- a/less/admin.less +++ b/less/admin.less @@ -19,8 +19,14 @@ nav#admin { &.selected { background: #dedede; font-weight: bold; + .blip { + color: black; + } } } + .blip { + font-weight: bold; + } } .pager { display: flex; diff --git a/less/core.less b/less/core.less index 0e5798b..05a050c 100644 --- a/less/core.less +++ b/less/core.less @@ -1345,7 +1345,7 @@ div.row { } } -.check { +.check, .blip { font-size: 1.125em; color: #71D571; } diff --git a/templates/user/include/header.tmpl b/templates/user/include/header.tmpl index 4b53b32..16978ee 100644 --- a/templates/user/include/header.tmpl +++ b/templates/user/include/header.tmpl @@ -103,7 +103,7 @@ {{if not .SingleUser}} Users Pages - {{if .UpdateChecks}}Updates{{end}} + {{if .UpdateChecks}}Updates{{if .UpdateAvailable}}!{{end}}{{end}} {{end}} {{if not .Forest}} Monitor diff --git a/updates.go b/updates.go index e8f0d21..b1893fd 100644 --- a/updates.go +++ b/updates.go @@ -58,6 +58,12 @@ func (uc updatesCache) AreAvailable() bool { return CompareSemver(uc.latestVersion, uc.currentVersion) == 1 } +// AreAvailableNoCheck returns if the latest release is newer than the current +// running version. +func (uc updatesCache) AreAvailableNoCheck() bool { + return CompareSemver(uc.latestVersion, uc.currentVersion) == 1 +} + // LatestVersion returns the latest stored version available. func (uc updatesCache) LatestVersion() string { return uc.latestVersion From 37b7755c08f80cddfa0487f2a72a6a3dd722dfaf Mon Sep 17 00:00:00 2001 From: Matt Baer Date: Sat, 15 Feb 2020 12:56:33 -0500 Subject: [PATCH 23/51] Tell admin that automated check failed when necessary This keeps track when automated update check fails, and displays a relevant message to the admin on /admin/updates Ref T572 --- admin.go | 2 ++ less/core.less | 5 +++++ templates/user/admin/app-updates.tmpl | 6 +++++- updates.go | 4 +++- 4 files changed, 15 insertions(+), 2 deletions(-) diff --git a/admin.go b/admin.go index c0ad436..b1f1533 100644 --- a/admin.go +++ b/admin.go @@ -612,6 +612,7 @@ func handleViewAdminUpdates(app *App, u *User, w http.ResponseWriter, r *http.Re LatestVersion string LatestReleaseURL string LatestReleaseNotesURL string + CheckFailed bool }{ UserPage: NewUserPage(app, r, u, "Updates", nil), AdminPage: NewAdminPage(app), @@ -624,6 +625,7 @@ func handleViewAdminUpdates(app *App, u *User, w http.ResponseWriter, r *http.Re p.LatestReleaseURL = app.updates.ReleaseURL() p.LatestReleaseNotesURL = app.updates.ReleaseNotesURL() p.UpdateAvailable = app.updates.AreAvailable() + p.CheckFailed = app.updates.checkError != nil } showUserPage(w, "app-updates", p) diff --git a/less/core.less b/less/core.less index 05a050c..afccc9c 100644 --- a/less/core.less +++ b/less/core.less @@ -1350,6 +1350,11 @@ div.row { color: #71D571; } +.ex.failure { + font-weight: bold; + color: @dangerCol; +} + @media all and (max-width: 450px) { body#post { header { diff --git a/templates/user/admin/app-updates.tmpl b/templates/user/admin/app-updates.tmpl index 36e7f23..62fd83d 100644 --- a/templates/user/admin/app-updates.tmpl +++ b/templates/user/admin/app-updates.tmpl @@ -15,7 +15,11 @@ {{template "admin-header" .}} {{ if .UpdateChecks }} - {{if not .UpdateAvailable}} + {{if .CheckFailed}} +

    × Automated update check failed.

    +

    Installed version: {{.Version}} (release notes).

    +

    Learn about latest releases on the WriteFreely blog or forum.

    + {{else if not .UpdateAvailable}}

    WriteFreely is up to date.

    Installed version: {{.Version}} (release notes).

    {{else}} diff --git a/updates.go b/updates.go index b1893fd..e41b00b 100644 --- a/updates.go +++ b/updates.go @@ -30,6 +30,7 @@ type updatesCache struct { lastCheck time.Time latestVersion string currentVersion string + checkError error } // CheckNow asks for the latest released version of writefreely and updates @@ -38,11 +39,12 @@ type updatesCache struct { func (uc *updatesCache) CheckNow() error { uc.mu.Lock() defer uc.mu.Unlock() + uc.lastCheck = time.Now() latestRemote, err := newVersionCheck() if err != nil { + uc.checkError = err return err } - uc.lastCheck = time.Now() if CompareSemver(latestRemote, uc.latestVersion) == 1 { uc.latestVersion = latestRemote } From 987c74c93a33dd72de51c28ec1460c4577f05e3e Mon Sep 17 00:00:00 2001 From: Matt Baer Date: Sat, 15 Feb 2020 12:58:45 -0500 Subject: [PATCH 24/51] Add logging around automated update checks Logs requests when --debug enabled, and always logs errors from the check. Ref T572 --- updates.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/updates.go b/updates.go index e41b00b..9f4c3c0 100644 --- a/updates.go +++ b/updates.go @@ -11,6 +11,7 @@ package writefreely import ( + "github.com/writeas/web-core/log" "io/ioutil" "net/http" "strings" @@ -37,11 +38,15 @@ type updatesCache struct { // the cache last checked time. If the version postdates the current 'latest' // the version value is replaced. func (uc *updatesCache) CheckNow() error { + if debugging { + log.Info("[update check] Checking for update now.") + } uc.mu.Lock() defer uc.mu.Unlock() uc.lastCheck = time.Now() latestRemote, err := newVersionCheck() if err != nil { + log.Error("[update check] Failed: %v", err) uc.checkError = err return err } @@ -109,6 +114,9 @@ func (app *App) InitUpdates() { func newVersionCheck() (string, error) { res, err := http.Get("https://version.writefreely.org") + if debugging { + log.Info("[update check] GET https://version.writefreely.org") + } if err == nil && res.StatusCode == http.StatusOK { defer res.Body.Close() From 34d196376ead77fdce3ad93f575e12b99905dbf9 Mon Sep 17 00:00:00 2001 From: Matt Baer Date: Wed, 19 Feb 2020 16:38:50 -0500 Subject: [PATCH 25/51] Include extracted images in draft social metadata Previously, we didn't extract images for draft posts. This fixes that. --- posts.go | 10 ++++++++-- templates/post.tmpl | 4 ++-- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/posts.go b/posts.go index a0e4588..a9c8c11 100644 --- a/posts.go +++ b/posts.go @@ -62,6 +62,7 @@ type ( Description string Author string Views int64 + Images []string IsPlainText bool IsCode bool IsLinkable bool @@ -381,6 +382,7 @@ func handleViewPost(app *App, w http.ResponseWriter, r *http.Request) error { } if !isRaw { post.HTMLContent = template.HTML(applyMarkdown([]byte(content), "", app.cfg)) + post.Images = extractImages(post.Content) } } @@ -1544,7 +1546,11 @@ func (rp *RawPost) Created8601() string { var imageURLRegex = regexp.MustCompile(`(?i)^https?:\/\/[^ ]*\.(gif|png|jpg|jpeg|image)$`) func (p *Post) extractImages() { - matches := extract.ExtractUrls(p.Content) + p.Images = extractImages(p.Content) +} + +func extractImages(content string) []string { + matches := extract.ExtractUrls(content) urls := map[string]bool{} for i := range matches { u := matches[i].Text @@ -1558,5 +1564,5 @@ func (p *Post) extractImages() { for k := range urls { resURLs = append(resURLs, k) } - p.Images = resURLs + return resURLs } diff --git a/templates/post.tmpl b/templates/post.tmpl index e9edfed..15b479d 100644 --- a/templates/post.tmpl +++ b/templates/post.tmpl @@ -23,13 +23,13 @@ {{if gt .Views 1}} {{end}} - + {{if gt (len .Images) 0}}{{else}}{{end}} - + {{range .Images}}{{else}}{{end}} {{if .Author}}{{end}} {{template "highlighting" .}} From 563ea5b25b306ce0cde59f156a10eb4369a70c64 Mon Sep 17 00:00:00 2001 From: Matt Baer Date: Wed, 19 Feb 2020 17:07:02 -0500 Subject: [PATCH 26/51] Fix image extraction for URLs with query strings Previously, image extraction wouldn't catch images with a query string (or anything else) appended. This fixes that by parsing extracted URLs and only checking the path for what looks like an image file. --- posts.go | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/posts.go b/posts.go index a9c8c11..35e9bd3 100644 --- a/posts.go +++ b/posts.go @@ -16,6 +16,7 @@ import ( "fmt" "html/template" "net/http" + "net/url" "regexp" "strings" "time" @@ -1543,7 +1544,7 @@ func (rp *RawPost) Created8601() string { return rp.Created.Format("2006-01-02T15:04:05Z") } -var imageURLRegex = regexp.MustCompile(`(?i)^https?:\/\/[^ ]*\.(gif|png|jpg|jpeg|image)$`) +var imageURLRegex = regexp.MustCompile(`(?i)[^ ]+\.(gif|png|jpg|jpeg|image)$`) func (p *Post) extractImages() { p.Images = extractImages(p.Content) @@ -1553,11 +1554,17 @@ func extractImages(content string) []string { matches := extract.ExtractUrls(content) urls := map[string]bool{} for i := range matches { - u := matches[i].Text - if !imageURLRegex.MatchString(u) { + uRaw := matches[i].Text + // Parse the extracted text so we can examine the path + u, err := url.Parse(uRaw) + if err != nil { continue } - urls[u] = true + // Ensure the path looks like it leads to an image file + if !imageURLRegex.MatchString(u.Path) { + continue + } + urls[uRaw] = true } resURLs := make([]string, 0) From e6e8cb5944a34258a523ecf1475341e21d23ba26 Mon Sep 17 00:00:00 2001 From: Matt Baer Date: Sat, 22 Feb 2020 13:04:26 -0500 Subject: [PATCH 27/51] Add details and update CONTRIBUTING guide This updates the guide to explain our current practices and processes. It now focuses primarily on contributing code, instead of other types of contributions. --- CONTRIBUTING.md | 101 +++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 87 insertions(+), 14 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index efe343a..ea38748 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,26 +1,99 @@ # Contributing to WriteFreely -Welcome! We're glad you're interested in contributing to the WriteFreely project. +Welcome! We're glad you're interested in contributing to WriteFreely. -To start, we'd suggest checking out [our Phabricator board](https://phabricator.write.as/tag/write_freely/) to see where the project is at and where it's going. You can also [join the WriteFreely forums](https://discuss.write.as/c/writefreely) to start talking about what you'd like to do or see. +For **questions**, **help**, **feature requests**, and **general discussion**, please use [our forum](https://discuss.write.as). -## Asking Questions +For **bug reports**, please [open a GitHub issue](https://github.com/writeas/writefreely/issues/new). See our guide on [submitting bug reports](https://writefreely.org/contribute#bugs). -The best place to get answers to your questions is on [our forums](https://discuss.write.as/c/writefreely). You can quickly log in using your GitHub account and ask the community about anything. We're also there to answer your questions and discuss potential changes or features. +## Getting Started -## Submitting Bugs +There are many ways to contribute to WriteFreely, from code to documentation, to translations, to help in the community! -Please use the [GitHub issue tracker](https://github.com/writeas/writefreely/issues/new) to report any bugs you encounter. We're very responsive there and try to keep open issues to a minimum, so you can help by: +See our [Contributing Guide](https://writefreely.org/contribute) on WriteFreely.org for ways to contribute without writing code. Otherwise, please read on. -* **Only reporting bugs in the issue tracker** -* Providing as much information as possible to replicate the issue, including server logs around the incident -* Including the `[app]` section of your configuration, if related -* Breaking issues into smaller pieces if they're larger or have many parts +## Working on WriteFreely -## Contributing code +First, you'll want to clone the WriteFreely repo, install development dependencies, and build the application from source. Learn how to do this in our [Development Setup](https://writefreely.org/docs/latest/developer/setup) guide. -We gladly welcome development help, regardless of coding experience. We can also use help [translating the app](https://poeditor.com/join/project/TIZ6HFRFdE) and documenting it! +### Starting development -**Before writing or submitting any code**, please sign our [contributor's agreement](https://phabricator.write.as/L1) so we can accept your contributions. It is substantially similar to the _Apache Individual Contributor License Agreement_. If you'd like to know about the rationale behind this requirement, you can [read more about that here](https://phabricator.write.as/w/writefreely/cla/). +Next, [join our forum](https://discuss.write.as) so you can discuss development with the team. Then take a look at [our roadmap on Phabricator](https://phabricator.write.as/tag/write_freely/) to see where the project is today and where it's headed. -Once you've done that, please feel free to [submit a pull request](https://github.com/writeas/writefreely/pulls) for any small improvements. For larger projects, please [join our development discussions](https://discuss.write.as/c/writefreely) or [get in touch](https://write.as/contact) so we can talk about what you'd like to work on. +When you find something you want to work on, start a new topic on the forum or jump into an existing discussion, if there is one. The team will respond and continue the conversation there. + +Lastly, **before submitting any code**, please sign our [contributor's agreement](https://phabricator.write.as/L1) so we can accept your contributions. It is substantially similar to the _Apache Individual Contributor License Agreement_. If you'd like to know about the rationale behind this requirement, you can [read more about that here](https://phabricator.write.as/w/writefreely/cla/). + +### Branching + +All stable work lives on the `master` branch. We merge into it only when creating a release. Releases are tagged using semantic versioning. + +While developing, we primarily work from the `develop` branch, creating _feature branches_ off of it for new features and fixes. When starting a new feature or fix, you should also create a new branch off of `develop`. + +#### Branch naming + +For fixes and modifications to existing behavior, branch names should follow a similar pattern to commit messages (see below), such as `fix-post-rendering` or `update-documentation`. You can optionally append a task number, e.g. `fix-post-rendering-T000`. + +For new features, branches can be named after the new feature, e.g. `activitypub-mentions` or `import-zip`. + +#### Pull request scope + +The scope of work on each branch should be as small as possible -- one complete feature, one complete change, or one complete fix. This makes it easier for us to review and accept. + +### Writing code + +We value reliable, readable, and maintainable code over all else in our work. To help you write that kind of code, we offer a few guiding principles, as well as a few concrete guidelines. + +#### Guiding principles + +* Write code for other humans, not computers. +* The less complexity, the better. The more someone can understand code just by looking at it, the better. +* Functionality, readability, and maintainability over senseless elegance. +* Only abstract when necessary. +* Keep an eye to the future, but don't pre-optimize at the expense of today's simplicity. + +#### Code guidelines + +* Format all Go code with `go fmt` before committing (**important!**) +* Follow whitespace conventions established within the project (tabs vs. spaces) +* Add comments to exported Go functions and variables +* Follow Go naming conventions, like using [`mixedCaps`](https://golang.org/doc/effective_go.html#mixed-caps) +* Avoid new dependencies unless absolutely necessary + +### Commit messages + +We highly value commit messages that follow established form within the project. Generally speaking, we follow the practices [outlined](https://git-scm.com/book/en/v2/Distributed-Git-Contributing-to-a-Project#_commit_guidelines) in the Pro Git Book. A good commit message will look like the following: + +* **Line 1**: A short summary written in the present imperative tense. For example: + * ✔️ **Good**: "Fix post rendering bug" + * ❌ No: ~~"Fixes post rendering bug"~~ + * ❌ No: ~~"Fixing post rendering bug"~~ + * ❌ No: ~~"Fixed post rendering bug"~~ + * ❌ No: ~~"Post rendering bug is fixed now"~~ +* **Line 2**: _[left blank]_ +* **Line 3**: An added description of what changed, any rationale, etc. -- if necessary +* **Last line**: A mention of any applicable task or issue + * For Phabricator tasks: `Ref T000` or `Closes T000` + * For GitHub issues: `Ref #000` or `Fixes #000` + +#### Good examples + +When in doubt, look to our existing git history for examples of good commit messages. Here are a few: + +* [Rename Suspend status to Silence](https://github.com/writeas/writefreely/commit/7e014ca65958750ab703e317b1ce8cfc4aad2d6e) +* [Show 404 when remote user not found](https://github.com/writeas/writefreely/commit/867eb53b3596bd7b3f2be3c53a3faf857f4cd36d) +* [Fix post deletion on Pleroma](https://github.com/writeas/writefreely/commit/fe82cbb96e3d5c57cfde0db76c28c4ea6dabfe50) + +### Submitting pull requests + +Like our GitHub issues, we aim to keep our number of open pull requests to a minimum. You can follow a few guidelines to ensure changes are merged quickly. + +First, make sure your changes follow the established practices and good form outlined in this guide. This is crucial to our project, and ignoring our practices can delay otherwise important fixes. + +Beyond that, we prioritize pull requests in this order: + +1. Fixes to open GitHub issues +2. Superficial changes and improvements that don't adversely impact users +3. New features and changes that have been discussed before with the team + +Any pull requests that haven't previously been discussed with the team may be extensively delayed or closed, especially if they require a wider consideration before integrating into the project. When in doubt, please reach out [on the forum](https://discuss.write.as) before submitting a pull request. \ No newline at end of file From 6f3b502e6567b31b3c5f00866d62c7a301bc5c70 Mon Sep 17 00:00:00 2001 From: Matt Baer Date: Sat, 22 Feb 2020 19:46:36 -0500 Subject: [PATCH 28/51] Add 'X-Robots-Tag: noindex' header to invite URLs This instructs search engines to not index invite links. --- invites.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/invites.go b/invites.go index d5d024a..c1c7d95 100644 --- a/invites.go +++ b/invites.go @@ -170,6 +170,9 @@ func handleViewInvite(app *App, w http.ResponseWriter, r *http.Request) error { p.Error = "This invite link has expired." } + // Tell search engines not to index invite links + w.Header().Set("X-Robots-Tag", "noindex") + // Get error messages session, err := app.sessionStore.Get(r, cookieName) if err != nil { From 8933076296c51d2a97710e82049838c5cbf1e79e Mon Sep 17 00:00:00 2001 From: Matt Baer Date: Tue, 25 Feb 2020 13:06:55 -0500 Subject: [PATCH 29/51] Add invite button to admin Users page header Ref T694 --- less/admin.less | 7 +++++++ less/core.less | 2 +- templates/user/admin/users.tmpl | 5 ++++- 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/less/admin.less b/less/admin.less index 2aa34dc..ec49292 100644 --- a/less/admin.less +++ b/less/admin.less @@ -48,3 +48,10 @@ nav#admin { } } } + +.admin-actions { + .btn { + font-family: @sansFont; + font-size: 0.86em; + } +} \ No newline at end of file diff --git a/less/core.less b/less/core.less index afccc9c..231f373 100644 --- a/less/core.less +++ b/less/core.less @@ -1421,7 +1421,7 @@ div.row { } @media all and (max-width: 600px) { - div.row { + div.row:not(.admin-actions) { flex-direction: column; } .half { diff --git a/templates/user/admin/users.tmpl b/templates/user/admin/users.tmpl index 51dd54a..714fa24 100644 --- a/templates/user/admin/users.tmpl +++ b/templates/user/admin/users.tmpl @@ -4,7 +4,10 @@
    {{template "admin-header" .}} -

    Users {{.TotalUsers}} total

    +
    + {{.TotalUsers}} {{pluralize "user" "users" .TotalUsers}} + + Invite people +
    From 05aad04b21627bd2ef96c820a62eb7f16dde5e12 Mon Sep 17 00:00:00 2001 From: Matt Baer Date: Tue, 25 Feb 2020 13:13:36 -0500 Subject: [PATCH 30/51] Limit Reader posts by count, not publish date This changes the Reader to show the 250 most recent posts, with the 5-post-per-author limit still, instead of only posts from the last 3 months. --- read.go | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/read.go b/read.go index d708121..afe5651 100644 --- a/read.go +++ b/read.go @@ -1,5 +1,5 @@ /* - * Copyright © 2018-2019 A Bunch Tell LLC. + * Copyright © 2018-2020 A Bunch Tell LLC. * * This file is part of WriteFreely. * @@ -33,6 +33,8 @@ const ( tlAPIPageLimit = 10 tlMaxAuthorPosts = 5 tlPostsPerPage = 16 + tlMaxPostCache = 250 + tlCacheDur = 10 * time.Minute ) type localTimeline struct { @@ -60,19 +62,25 @@ type readPublication struct { func initLocalTimeline(app *App) { app.timeline = &localTimeline{ postsPerPage: tlPostsPerPage, - m: memo.New(app.FetchPublicPosts, 10*time.Minute), + m: memo.New(app.FetchPublicPosts, tlCacheDur), } } // satisfies memo.Func func (app *App) FetchPublicPosts() (interface{}, error) { + // Conditions + limit := fmt.Sprintf("LIMIT %d", tlMaxPostCache) + // This is better than the hard limit when limiting posts from individual authors + // ageCond := `p.created >= ` + app.db.dateSub(3, "month") + ` AND ` + // Finds all public posts and posts in a public collection published during the owner's active subscription period and within the last 3 months rows, err := app.db.Query(`SELECT p.id, alias, c.title, p.slug, p.title, p.content, p.text_appearance, p.language, p.rtl, p.created, p.updated FROM collections c LEFT JOIN posts p ON p.collection_id = c.id LEFT JOIN users u ON u.id = p.owner_id - WHERE c.privacy = 1 AND (p.created >= ` + app.db.dateSub(3, "month") + ` AND p.created <= ` + app.db.now() + ` AND pinned_position IS NULL) AND u.status = 0 - ORDER BY p.created DESC`) + WHERE c.privacy = 1 AND (p.created <= ` + app.db.now() + ` AND pinned_position IS NULL) AND u.status = 0 + ORDER BY p.created DESC + ` + limit) if err != nil { log.Error("Failed selecting from posts: %v", err) return nil, impart.HTTPError{http.StatusInternalServerError, "Couldn't retrieve collection posts." + err.Error()} From d17e82d34c39c79c48b0b091cd3b2fd805c90b0c Mon Sep 17 00:00:00 2001 From: Matt Baer Date: Tue, 25 Feb 2020 14:12:54 -0500 Subject: [PATCH 31/51] Prevent update check from slowing app init Previously, we'd wait for a response before finishing app initialization, meaning an overall slower startup. This fixes that. Ref T572 --- updates.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/updates.go b/updates.go index 9f4c3c0..8ecccf6 100644 --- a/updates.go +++ b/updates.go @@ -100,7 +100,7 @@ func newUpdatesCache(expiry time.Duration) *updatesCache { frequency: expiry, currentVersion: "v" + softwareVer, } - cache.CheckNow() + go cache.CheckNow() return &cache } @@ -117,6 +117,7 @@ func newVersionCheck() (string, error) { if debugging { log.Info("[update check] GET https://version.writefreely.org") } + // TODO: return error if statusCode != OK if err == nil && res.StatusCode == http.StatusOK { defer res.Body.Close() From 46dbb10433392a53abe715013a01ea63a0589d7a Mon Sep 17 00:00:00 2001 From: Matt Baer Date: Tue, 25 Feb 2020 14:28:34 -0500 Subject: [PATCH 32/51] Make Admin Settings page more user-friendly - Add a description to each config item - Change item names to make more sense Ref T694 --- less/admin.less | 29 +++++ less/core.less | 14 --- templates/user/admin/app-settings.tmpl | 147 ++++++++++++++++++------- 3 files changed, 137 insertions(+), 53 deletions(-) diff --git a/less/admin.less b/less/admin.less index ec49292..d9d659e 100644 --- a/less/admin.less +++ b/less/admin.less @@ -54,4 +54,33 @@ nav#admin { font-family: @sansFont; font-size: 0.86em; } +} + +.features { + margin: 1em 0; + + div { + &:first-child { + font-weight: bold; + } + &+div { + padding-left: 1em; + } + + p { + font-weight: normal; + margin: 0.5rem 0; + font-size: 0.86em; + color: #666; + } + } +} + +@media (max-width: 600px) { + div.row.features { + align-items: start; + } + .features div + div { + padding-left: 0; + } } \ No newline at end of file diff --git a/less/core.less b/less/core.less index 231f373..7232660 100644 --- a/less/core.less +++ b/less/core.less @@ -868,20 +868,6 @@ input { text-align: center; } } - div.features { - margin-top: 1.5em; - text-align: center; - font-size: 0.86em; - ul { - text-align: left; - max-width: 26em; - margin-left: auto !important; - margin-right: auto !important; - li.soon, span.soon { - color: lighten(#111, 40%); - } - } - } div.blurbs { >h2 { text-align: center; diff --git a/templates/user/admin/app-settings.tmpl b/templates/user/admin/app-settings.tmpl index 4af6a44..3e0fdf7 100644 --- a/templates/user/admin/app-settings.tmpl +++ b/templates/user/admin/app-settings.tmpl @@ -24,55 +24,124 @@ p.docs { {{if .ConfigMessage}}

    {{.ConfigMessage}}

    {{end}} -

    Read more in the configuration docs.

    - -
    -
    -
    + +
    +
    +