aboutsummaryrefslogtreecommitdiff
path: root/cmd
diff options
context:
space:
mode:
Diffstat (limited to 'cmd')
-rw-r--r--cmd/server/app.go13
-rw-r--r--cmd/server/auth.go7
-rw-r--r--cmd/server/conditionalMiddleware.go18
-rw-r--r--cmd/server/edit.go11
-rw-r--r--cmd/server/feed.go84
-rw-r--r--cmd/server/handlers.go59
-rw-r--r--cmd/server/main.go9
-rw-r--r--cmd/server/middleware.go22
8 files changed, 140 insertions, 83 deletions
diff --git a/cmd/server/app.go b/cmd/server/app.go
index 9a00628..c694753 100644
--- a/cmd/server/app.go
+++ b/cmd/server/app.go
@@ -7,14 +7,17 @@ import (
"github.com/gomodule/redigo/redis"
"gopkg.in/yaml.v3"
+ "riedstra.dev/mitch/go-website/mapcache"
"riedstra.dev/mitch/go-website/page"
)
var FeedPrefixDefault = ".feeds"
type App struct {
+ mapCache bool
redisPool *redis.Pool
RedisKey string
+ cache *mapcache.Cache
ReIndexPath string
StaticDirectory string
@@ -86,6 +89,16 @@ func loadConf(fn string) (*App, error) {
return app, nil
}
+func (a *App) ClearCache() error {
+ if a.redisPool != nil {
+ return a.ClearRedis()
+ }
+
+ a.cache.Clear()
+
+ return nil
+}
+
// ClearRedis is a little helper function that allows us to easily clear
// the redis cache at runtime.
func (a *App) ClearRedis() error {
diff --git a/cmd/server/auth.go b/cmd/server/auth.go
index 635b6e2..1adc366 100644
--- a/cmd/server/auth.go
+++ b/cmd/server/auth.go
@@ -13,6 +13,9 @@ import (
"riedstra.dev/mitch/go-website/users"
)
+const tokenKeyBytes = 128 / 8
+const authConfFileMode = 0600
+
type Auth struct {
Users []*users.SiteUser `json:"Users"`
// How long are JWTs valid?
@@ -29,7 +32,7 @@ type Auth struct {
}
func GenTokenKey() string {
- r := make([]byte, 16) // 128 bits
+ r := make([]byte, tokenKeyBytes)
_, err := rand.Read(r)
if err != nil {
@@ -89,7 +92,7 @@ func (a *App) ReadAuth(fn string) error { //nolint
}
write:
- fh, err = os.OpenFile(fn, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0600)
+ fh, err = os.OpenFile(fn, os.O_RDWR|os.O_CREATE|os.O_TRUNC, authConfFileMode)
if err != nil {
return fmt.Errorf("opening file %s: %w", fn, err)
diff --git a/cmd/server/conditionalMiddleware.go b/cmd/server/conditionalMiddleware.go
new file mode 100644
index 0000000..60b1398
--- /dev/null
+++ b/cmd/server/conditionalMiddleware.go
@@ -0,0 +1,18 @@
+package main
+
+import "net/http"
+
+type conditionalMiddlewareFunc func(r *http.Request) bool
+
+// conditionalMiddleware simply uses handler a if condition is true and handler
+// b if condition is false.
+func conditionalMiddleware(condition conditionalMiddlewareFunc,
+ a, b http.Handler) http.Handler {
+ return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ if condition(r) {
+ a.ServeHTTP(w, r)
+ } else {
+ b.ServeHTTP(w, r)
+ }
+ })
+}
diff --git a/cmd/server/edit.go b/cmd/server/edit.go
index 10d109a..5b40f51 100644
--- a/cmd/server/edit.go
+++ b/cmd/server/edit.go
@@ -14,17 +14,19 @@ import (
"riedstra.dev/mitch/go-website/page"
)
+const editFileMode = 0644
+
func (a *App) EditPage() http.Handler {
getFunc := a.GetEditPage().ServeHTTP
postFunc := a.SaveEditPage().ServeHTTP
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
switch r.Method {
- case "GET":
+ case "GET": //nolint:goconst
getFunc(w, r)
return
- case "POST":
+ case "POST": //nolint:goconst
postFunc(w, r)
return
@@ -98,7 +100,8 @@ func (a *App) SaveEditPage() http.Handler {
fn := "./" + p + page.Suffix
- fh, err := os.OpenFile(fn, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644)
+ fh, err := os.OpenFile(fn, os.O_RDWR|os.O_CREATE|os.O_TRUNC,
+ editFileMode)
if err != nil {
log.Printf("opening file %s for writing: %s", fn, err)
a.Err500Default(w, r)
@@ -119,7 +122,7 @@ func (a *App) SaveEditPage() http.Handler {
http.Redirect(w, r, "/"+r.URL.Path, http.StatusFound)
- err = a.ClearRedis() // Clear out the cache if any
+ err = a.ClearCache() // Clear out the cache if any
if err != nil {
log.Printf("after editing %s: %s", fn, err)
}
diff --git a/cmd/server/feed.go b/cmd/server/feed.go
index 7e36cb3..822d13a 100644
--- a/cmd/server/feed.go
+++ b/cmd/server/feed.go
@@ -16,7 +16,7 @@ import (
type Author struct {
Name string `xml:"name"` // Required
- Uri string `xml:"uri,omitempty"` //nolint:golint,stylecheck
+ Uri string `xml:"uri,omitempty"` //nolint:revive,stylecheck
Email string `xml:"email,omitempty"`
}
@@ -36,7 +36,7 @@ type Content struct {
type Entry struct {
// Spec requires this, autogenerated from Title and updated if otherwise
// left empty
- Id string `xml:"id"` //nolint:golint,stylecheck
+ Id string `xml:"id"` //nolint:revive,stylecheck
Title string `xml:"title"` // Required
Updated *time.Time `xml:"updated"` // Required
@@ -68,10 +68,10 @@ func (i Entry) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
i2 := (*Alias)(&i)
- return e.EncodeElement(i2, start)
+ return e.EncodeElement(i2, start) //nolint:wrapcheck
}
-//nolint:stylecheck,golint
+//nolint:stylecheck,revive
type Atom struct {
Ns string `xml:"xmlns,attr"`
Title string `xml:"title"` // Required
@@ -109,7 +109,7 @@ func (a Atom) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
a2 := (*Alias)(&a)
- return e.EncodeElement(a2, start)
+ return e.EncodeElement(a2, start) //nolint:wrapcheck
}
// FeedHandler takes care of pulling from the index all of the relevant posts
@@ -119,8 +119,8 @@ func (a Atom) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
//
// "content" if unset, or set to false content is omitted from the feed
// "limit=n" stop at "n" and return the feed.
-func (a *App) FeedHandler() http.Handler {
- return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { //nolint:funlen
+func (a *App) FeedHandler() http.Handler { //nolint:funlen,gocognit
+ return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
var (
addContent bool
limit int
@@ -179,40 +179,10 @@ func (a *App) FeedHandler() http.Handler {
break
}
- if !p.Published {
- continue
- }
-
- content := &bytes.Buffer{}
-
- err := p.Render(content)
+ err = a.addPageToEntries(&entries, p, addContent)
if err != nil {
- log.Println(err)
http.Error(w, "Internal server error", http.StatusInternalServerError)
-
- return
- }
-
- entry := Entry{
- Title: p.Title(),
- Updated: &p.Date.Time,
- Links: []Link{{Href: strings.Join([]string{a.SiteURL, p.Path()}, "/")}},
- }
-
- if p.AuthorName != "" {
- entry.Author = &Author{
- Name: p.AuthorName,
- }
- if p.AuthorEmail != "" {
- entry.Author.Email = p.AuthorEmail
- }
- }
-
- if addContent {
- entry.Content = &Content{Type: "html", Data: content.String()}
}
-
- entries = append(entries, entry)
}
feed.Entries = entries
@@ -237,3 +207,41 @@ func (a *App) FeedHandler() http.Handler {
}
})
}
+
+func (a *App) addPageToEntries(target *[]Entry, p *page.Page, addContent bool) error {
+ if !p.Published {
+ return nil
+ }
+
+ content := &bytes.Buffer{}
+
+ err := p.Render(content)
+ if err != nil {
+ log.Println(err)
+
+ return err //nolint:wrapcheck
+ }
+
+ entry := Entry{
+ Title: p.Title(),
+ Updated: &p.Date.Time,
+ Links: []Link{{Href: strings.Join([]string{a.SiteURL, p.Path()}, "/")}},
+ }
+
+ if p.AuthorName != "" {
+ entry.Author = &Author{
+ Name: p.AuthorName,
+ }
+ if p.AuthorEmail != "" {
+ entry.Author.Email = p.AuthorEmail
+ }
+ }
+
+ if addContent {
+ entry.Content = &Content{Type: "html", Data: content.String()}
+ }
+
+ *target = append(*target, entry)
+
+ return nil
+}
diff --git a/cmd/server/handlers.go b/cmd/server/handlers.go
index 3c767a4..bb174d8 100644
--- a/cmd/server/handlers.go
+++ b/cmd/server/handlers.go
@@ -6,6 +6,7 @@ import (
"path/filepath"
"strings"
+ "riedstra.dev/mitch/go-website/mapcache"
"riedstra.dev/mitch/go-website/page"
"riedstra.dev/mitch/go-website/rediscache"
)
@@ -28,17 +29,29 @@ func (a *App) addCacheableRoutes(r *http.ServeMux) *http.ServeMux {
strings.TrimSuffix(strings.TrimPrefix(a.FeedPrefix, "/"), "/"))
routes := map[string]http.Handler{
- "/_json/": a.PageJsonHandler(),
+ "/_json/": a.PageJSONHandler(),
"/_md/": a.PageMarkdownHandler(),
a.FeedPrefix: http.StripPrefix(a.FeedPrefix, a.FeedHandler()),
"/": a.PageHandler(),
}
+ if a.cache == nil {
+ a.cache = mapcache.New()
+ }
+
for route, handler := range routes {
- if a.redisPool != nil {
- r.Handle(route, rediscache.Handle(a.redisPool, a.RedisKey,
- handler))
- } else {
+ switch {
+ case a.mapCache:
+ r.Handle(route, conditionalMiddleware(
+ a.IsLoggedIn,
+ handler,
+ a.cache.Handle(handler)))
+ case a.redisPool != nil:
+ r.Handle(route, conditionalMiddleware(
+ a.IsLoggedIn,
+ handler,
+ rediscache.Handle(a.redisPool, a.RedisKey, handler)))
+ default:
r.Handle(route, handler)
}
}
@@ -52,10 +65,7 @@ func (a *App) Handler() http.Handler {
func (a *App) PageHandler() http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- u := r.URL.Path
- if u == "/" {
- u = "/index"
- }
+ u := page.GetURLPath(r)
loggedIn := a.IsLoggedIn(r)
@@ -68,7 +78,7 @@ func (a *App) PageHandler() http.Handler {
!loggedIn {
page.Render4xx(w, r, map[string]interface{}{
"LoggedIn": loggedIn,
- }, 404)
+ }, http.StatusNotFound)
return
}
@@ -81,52 +91,43 @@ func (a *App) PageHandler() http.Handler {
})
}
-func (a *App) PageJsonHandler() http.Handler {
+func (a *App) PageJSONHandler() http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- u := r.URL.Path
- if u == "/" {
- u = "/index"
- }
+ u := page.GetURLPath(r)
// Skip template directory
if strings.HasPrefix(u[1:], filepath.Clean(page.TemplateDirectory)) {
- page.Render4xx(w, r, map[string]interface{}{}, 404)
+ page.Render4xx(w, r, map[string]interface{}{}, http.StatusNotFound)
+
return
}
u = filepath.Join(".", u)
- page.RenderJson(w, r, u, map[string]interface{}{}, 200)
+ page.RenderJSON(w, r, u, map[string]interface{}{}, http.StatusOK)
})
}
func (a *App) PageMarkdownHandler() http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- u := r.URL.Path
- if u == "/" {
- u = "/index"
- }
+ u := page.GetURLPath(r)
// Skip template directory
if strings.HasPrefix(u[1:], filepath.Clean(page.TemplateDirectory)) {
- page.Render4xx(w, r, map[string]interface{}{}, 404)
+ page.Render4xx(w, r, map[string]interface{}{}, http.StatusNotFound)
+
return
}
u = filepath.Join(".", u)
- page.RenderMarkdown(w, r, u, map[string]interface{}{}, 200)
+ page.RenderMarkdown(w, r, u, map[string]interface{}{}, http.StatusOK)
})
}
func (a *App) RebuildIndexHandler() http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- u := r.URL.Path
- if u == "/" {
- u = "/index"
- }
-
- u = filepath.Join(".", u)
+ u := page.GetPagePath(r)
p := page.NewPage("index")
err := p.RebuildIndex()
diff --git a/cmd/server/main.go b/cmd/server/main.go
index d50c468..10bbbf0 100644
--- a/cmd/server/main.go
+++ b/cmd/server/main.go
@@ -44,6 +44,7 @@ func main() { //nolint:funlen
redisKey = "go-website"
pageTimeout = 15
genhash = false
+ mapcache = false
)
fl := flag.NewFlagSet("Website", flag.ExitOnError)
@@ -73,6 +74,8 @@ func main() { //nolint:funlen
"If set to false, do not cache the page index"))
logIfErr(envflag.Bool(fl, &genhash, "genhash", "INTERACTIVE_HASH_GEN",
"If set to true, interactively generate a password hash"))
+ logIfErr(envflag.Bool(fl, &mapcache, "mapcache", "USE_MAP_CACHE",
+ "Instead of utilizing redis, utilize a map internally for caching"))
_ = fl.Parse(os.Args[1:])
@@ -96,6 +99,10 @@ func main() { //nolint:funlen
app = &App{}
}
+ if mapcache {
+ app.mapCache = true
+ }
+
err = app.ReadAuth(authConfFn)
if err != nil {
logger.Println(err)
@@ -134,7 +141,7 @@ func main() { //nolint:funlen
os.Stderr.Write(b)
}
- page.Funcs["ClearRedis"] = app.ClearRedis
+ page.Funcs["ClearCache"] = app.ClearCache
srv := &http.Server{
Handler: app.Handler(),
diff --git a/cmd/server/middleware.go b/cmd/server/middleware.go
index d0957fd..17717c2 100644
--- a/cmd/server/middleware.go
+++ b/cmd/server/middleware.go
@@ -2,6 +2,7 @@ package main
import (
"errors"
+ "fmt"
"log"
"net/http"
"net/url"
@@ -12,6 +13,8 @@ import (
"riedstra.dev/mitch/go-website/users"
)
+var ErrInvalidJWTToken = errors.New("invalid JWT token")
+
func (a *App) Err5xx(w http.ResponseWriter, r *http.Request,
statusCode int, title, desc string) {
page.Render5xx(w, r, map[string]interface{}{
@@ -33,15 +36,15 @@ func (a *App) LogoutHandler() http.Handler {
SameSite: a.auth.SameSiteStrict,
Secure: a.auth.Secure,
Value: "logout",
- Expires: time.Now().Add(time.Second), //nolint
+ Expires: time.Now().Add(time.Second),
})
http.Redirect(w, r, "/", http.StatusFound)
})
}
-func (a *App) LoginHandler() http.Handler {
- return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { //nolint
+func (a *App) LoginHandler() http.Handler { //nolint
+ return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
loggedIn := a.IsLoggedIn(r)
next, _ := url.Parse(r.URL.Query().Get("next"))
@@ -74,7 +77,7 @@ func (a *App) LoginHandler() http.Handler {
password := r.FormValue("password")
var (
- err error = nil
+ err error
u *users.SiteUser
found = false
)
@@ -125,16 +128,19 @@ func (a *App) IsLoggedIn(r *http.Request) bool {
_, err := a.GetAuthToken(r)
if err != nil {
log.Printf("%s IsLoggedIn: false", r.URL.Path)
+
return false
}
+
log.Printf("%s IsLoggedIn: true", r.URL.Path)
+
return true
}
func (a *App) GetAuthToken(r *http.Request) (*jwt.Token, error) {
c, err := r.Cookie("Auth")
if err != nil {
- return nil, err
+ return nil, fmt.Errorf("getting auth token: %w", err)
}
token, err := jwt.Parse(c.Value,
@@ -144,11 +150,11 @@ func (a *App) GetAuthToken(r *http.Request) (*jwt.Token, error) {
)
if err != nil {
- return nil, err
+ return nil, fmt.Errorf("while parsing jwt %w", err)
}
if !token.Valid {
- return token, errors.New("IsLoggedIn: token not valid")
+ return token, ErrInvalidJWTToken
}
return token, nil
@@ -156,7 +162,6 @@ func (a *App) GetAuthToken(r *http.Request) (*jwt.Token, error) {
func (a *App) RequiresLogin(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
-
if !a.IsLoggedIn(r) {
log.Printf("Unauthorized request %s %s", r.Method, r.URL.Path)
page.Render(w, r, "login", map[string]interface{}{
@@ -167,6 +172,5 @@ func (a *App) RequiresLogin(next http.Handler) http.Handler {
}
next.ServeHTTP(w, r)
-
})
}