Refactor server routes
This commit is contained in:
parent
ca9b6e05b7
commit
5cbb20dcc4
21 changed files with 429 additions and 390 deletions
66
internal/server/routes/days.go
Normal file
66
internal/server/routes/days.go
Normal file
|
@ -0,0 +1,66 @@
|
|||
package routes
|
||||
|
||||
import (
|
||||
"html/template"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"git.a71.su/Andrew71/hibiscus-txt/internal/config"
|
||||
"git.a71.su/Andrew71/hibiscus-txt/internal/files"
|
||||
"git.a71.su/Andrew71/hibiscus-txt/internal/lang"
|
||||
"git.a71.su/Andrew71/hibiscus-txt/internal/server/util"
|
||||
"github.com/go-chi/chi/v5"
|
||||
)
|
||||
|
||||
// getDays calls getEntries for previous days' entries.
|
||||
func getDays(w http.ResponseWriter, r *http.Request) {
|
||||
description := template.HTML(
|
||||
"<a href=\"#footer\">" + template.HTMLEscapeString(lang.Translate("prompt.days")) + "</a>")
|
||||
getEntries(w, r, lang.Translate("title.days"), description, "day", func(files []string) []Entry {
|
||||
var filesFormatted []Entry
|
||||
for i := range files {
|
||||
v := files[len(files)-1-i] // This is suboptimal, but reverse order is better here
|
||||
dayString := v
|
||||
t, err := time.Parse(time.DateOnly, v)
|
||||
if err == nil {
|
||||
dayString = t.Format("02 Jan 2006")
|
||||
}
|
||||
|
||||
// Fancy text for today and tomorrow
|
||||
// This looks bad, but strings.Title is deprecated, and I'm not importing a golang.org/x package for this...
|
||||
// (chances we ever run into tomorrow are really low)
|
||||
if v == config.Cfg.TodayDate() {
|
||||
dayString = lang.Translate("link.today")
|
||||
dayString = strings.ToTitle(string([]rune(dayString)[0])) + string([]rune(dayString)[1:])
|
||||
} else if v > config.Cfg.TodayDate() {
|
||||
dayString = lang.Translate("link.tomorrow")
|
||||
dayString = strings.ToTitle(string([]rune(dayString)[0])) + string([]rune(dayString)[1:])
|
||||
}
|
||||
filesFormatted = append(filesFormatted, Entry{Title: dayString, Link: "day/" + v})
|
||||
}
|
||||
return filesFormatted
|
||||
})
|
||||
}
|
||||
|
||||
// getDay calls getEntry for a day entry.
|
||||
func getDay(w http.ResponseWriter, r *http.Request) {
|
||||
dayString := chi.URLParam(r, "day")
|
||||
if dayString == "" {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
util.HandleWrite(w.Write([]byte("day not specified")))
|
||||
return
|
||||
}
|
||||
if dayString == config.Cfg.TodayDate() { // Today can still be edited
|
||||
http.Redirect(w, r, "/", http.StatusFound)
|
||||
return
|
||||
}
|
||||
|
||||
title := dayString
|
||||
t, err := time.Parse(time.DateOnly, dayString)
|
||||
if err == nil { // This is low priority so silently fail
|
||||
title = t.Format("02 Jan 2006")
|
||||
}
|
||||
|
||||
getEntry(w, r, title, files.DataFile("day/"+dayString), false)
|
||||
}
|
80
internal/server/routes/entries.go
Normal file
80
internal/server/routes/entries.go
Normal file
|
@ -0,0 +1,80 @@
|
|||
package routes
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"html/template"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"os"
|
||||
|
||||
"git.a71.su/Andrew71/hibiscus-txt/internal/files"
|
||||
"git.a71.su/Andrew71/hibiscus-txt/internal/templates"
|
||||
)
|
||||
|
||||
type EntryList struct {
|
||||
Title string
|
||||
Description template.HTML
|
||||
Entries []Entry
|
||||
}
|
||||
|
||||
type Entry struct {
|
||||
Title string
|
||||
Content string
|
||||
Link string
|
||||
}
|
||||
|
||||
type formatEntries func([]string) []Entry
|
||||
|
||||
// getEntries handles showing a list.
|
||||
func getEntries(w http.ResponseWriter, r *http.Request, title string, description template.HTML, dir string, format formatEntries) {
|
||||
filesList, err := files.List(dir)
|
||||
if err != nil {
|
||||
slog.Error("error reading file list", "directory", dir, "error", err)
|
||||
InternalError(w, r)
|
||||
return
|
||||
}
|
||||
var filesFormatted = format(filesList)
|
||||
|
||||
err = templates.List.ExecuteTemplate(w, "base", EntryList{Title: title, Description: description, Entries: filesFormatted})
|
||||
if err != nil {
|
||||
slog.Error("error executing template", "error", err)
|
||||
InternalError(w, r)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// getEntry handles showing a single file, editable or otherwise.
|
||||
func getEntry(w http.ResponseWriter, r *http.Request, title string, filename string, editable bool) {
|
||||
entry, err := files.Read(filename)
|
||||
if err != nil {
|
||||
if editable && errors.Is(err, os.ErrNotExist) {
|
||||
entry = []byte("")
|
||||
} else {
|
||||
slog.Error("error reading entry file", "error", err, "file", filename)
|
||||
InternalError(w, r)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if editable {
|
||||
err = templates.Edit.ExecuteTemplate(w, "base", Entry{Title: title, Content: string(entry)})
|
||||
} else {
|
||||
err = templates.View.ExecuteTemplate(w, "base", Entry{Title: title, Content: string(entry)})
|
||||
}
|
||||
if err != nil {
|
||||
InternalError(w, r)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// postEntry saves value of "text" HTML form component to a file and redirects back to Referer if present.
|
||||
func postEntry(filename string, w http.ResponseWriter, r *http.Request) {
|
||||
err := files.Save(filename, []byte(r.FormValue("text")))
|
||||
if err != nil {
|
||||
slog.Error("error saving file", "error", err, "file", filename)
|
||||
}
|
||||
if r.Referer() != "" {
|
||||
http.Redirect(w, r, r.Header.Get("Referer"), http.StatusFound)
|
||||
return
|
||||
}
|
||||
}
|
32
internal/server/routes/errors.go
Normal file
32
internal/server/routes/errors.go
Normal file
|
@ -0,0 +1,32 @@
|
|||
package routes
|
||||
|
||||
import (
|
||||
"log/slog"
|
||||
"net/http"
|
||||
|
||||
"git.a71.su/Andrew71/hibiscus-txt/internal/templates"
|
||||
)
|
||||
|
||||
// NotFound returns a user-friendly 404 error page.
|
||||
func NotFound(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(404)
|
||||
|
||||
err := templates.Template404.Execute(w, nil)
|
||||
if err != nil {
|
||||
slog.Error("error rendering error 404 page", "error", err)
|
||||
InternalError(w, r)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// InternalError returns a user-friendly 500 error page.
|
||||
func InternalError(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(500)
|
||||
|
||||
err := templates.Template500.Execute(w, nil)
|
||||
if err != nil { // Well this is awkward
|
||||
slog.Error("error rendering error 500 page", "error", err)
|
||||
http.Error(w, "500. Something went *very* wrong.", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
}
|
38
internal/server/routes/main.go
Normal file
38
internal/server/routes/main.go
Normal file
|
@ -0,0 +1,38 @@
|
|||
package routes
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"git.a71.su/Andrew71/hibiscus-txt/internal/config"
|
||||
"git.a71.su/Andrew71/hibiscus-txt/internal/files"
|
||||
"git.a71.su/Andrew71/hibiscus-txt/internal/lang"
|
||||
"git.a71.su/Andrew71/hibiscus-txt/internal/server/auth"
|
||||
"github.com/go-chi/chi/v5"
|
||||
)
|
||||
|
||||
var UserRouter *chi.Mux
|
||||
|
||||
func init() {
|
||||
UserRouter = chi.NewRouter()
|
||||
UserRouter.Use(auth.BasicAuth)
|
||||
UserRouter.Get("/", func(w http.ResponseWriter, r *http.Request) {
|
||||
getEntry(w, r, lang.Translate("title.today"), files.DataFile("day/"+config.Cfg.TodayDate()), true)
|
||||
})
|
||||
UserRouter.Post("/", func(w http.ResponseWriter, r *http.Request) {
|
||||
postEntry(files.DataFile("day/"+config.Cfg.TodayDate()), w, r)
|
||||
})
|
||||
UserRouter.Get("/day", getDays)
|
||||
UserRouter.Get("/day/{day}", getDay)
|
||||
UserRouter.Get("/notes", getNotes)
|
||||
UserRouter.Get("/notes/{note}", getNote)
|
||||
UserRouter.Post("/notes/{note}", postNote)
|
||||
UserRouter.Get("/info", getInfo)
|
||||
UserRouter.Get("/readme", func(w http.ResponseWriter, r *http.Request) {
|
||||
getEntry(w, r, "readme.txt", files.DataFile("readme"), true)
|
||||
})
|
||||
UserRouter.Post("/readme", func(w http.ResponseWriter, r *http.Request) { postEntry(files.DataFile("readme"), w, r) })
|
||||
UserRouter.Get("/config", func(w http.ResponseWriter, r *http.Request) {
|
||||
getEntry(w, r, "config.txt", config.ConfigFile, true)
|
||||
})
|
||||
UserRouter.Post("/config", postConfig)
|
||||
}
|
28
internal/server/routes/misc.go
Normal file
28
internal/server/routes/misc.go
Normal file
|
@ -0,0 +1,28 @@
|
|||
package routes
|
||||
|
||||
import (
|
||||
"log/slog"
|
||||
"net/http"
|
||||
|
||||
"git.a71.su/Andrew71/hibiscus-txt/internal/config"
|
||||
"git.a71.su/Andrew71/hibiscus-txt/internal/templates"
|
||||
)
|
||||
|
||||
// postConfig calls postEntry for config file, then reloads the config.
|
||||
func postConfig(w http.ResponseWriter, r *http.Request) {
|
||||
postEntry(config.ConfigFile, w, r)
|
||||
err := config.Cfg.Reload()
|
||||
if err != nil {
|
||||
slog.Error("error reloading config", "error", err)
|
||||
}
|
||||
}
|
||||
|
||||
// getInfo renders the info page.
|
||||
func getInfo(w http.ResponseWriter, r *http.Request) {
|
||||
err := templates.Info.ExecuteTemplate(w, "base", config.Info)
|
||||
if err != nil {
|
||||
slog.Error("error executing template", "error", err)
|
||||
InternalError(w, r)
|
||||
return
|
||||
}
|
||||
}
|
55
internal/server/routes/notes.go
Normal file
55
internal/server/routes/notes.go
Normal file
|
@ -0,0 +1,55 @@
|
|||
package routes
|
||||
|
||||
import (
|
||||
"html/template"
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
||||
"git.a71.su/Andrew71/hibiscus-txt/internal/files"
|
||||
"git.a71.su/Andrew71/hibiscus-txt/internal/lang"
|
||||
"git.a71.su/Andrew71/hibiscus-txt/internal/server/util"
|
||||
"github.com/go-chi/chi/v5"
|
||||
)
|
||||
|
||||
// getNotes calls getEntries for all notes.
|
||||
func getNotes(w http.ResponseWriter, r *http.Request) {
|
||||
// This is suboptimal, but will do...
|
||||
description := template.HTML(
|
||||
"<a href=\"#\" onclick='newNote(\"" + template.HTMLEscapeString(lang.Translate("prompt.notes")) + "\")'>" + template.HTMLEscapeString(lang.Translate("button.notes")) + "</a>" +
|
||||
" <noscript>(" + template.HTMLEscapeString(lang.Translate("noscript.notes")) + ")</noscript>")
|
||||
getEntries(w, r, lang.Translate("title.notes"), description, "notes", func(files []string) []Entry {
|
||||
var filesFormatted []Entry
|
||||
for _, v := range files {
|
||||
// titleString := strings.Replace(v, "-", " ", -1) // This would be cool, but what if I need a hyphen?
|
||||
filesFormatted = append(filesFormatted, Entry{Title: v, Link: "notes/" + v})
|
||||
}
|
||||
return filesFormatted
|
||||
})
|
||||
}
|
||||
|
||||
// getNote calls getEntry for a note.
|
||||
func getNote(w http.ResponseWriter, r *http.Request) {
|
||||
noteString := chi.URLParam(r, "note")
|
||||
if noteString == "" {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
util.HandleWrite(w.Write([]byte("note not specified")))
|
||||
return
|
||||
}
|
||||
// Handle non-latin note names
|
||||
if decodedNote, err := url.QueryUnescape(noteString); err == nil {
|
||||
noteString = decodedNote
|
||||
}
|
||||
|
||||
getEntry(w, r, noteString, files.DataFile("notes/"+noteString), true)
|
||||
}
|
||||
|
||||
// postNote calls postEntry for a note.
|
||||
func postNote(w http.ResponseWriter, r *http.Request) {
|
||||
noteString := chi.URLParam(r, "note")
|
||||
if noteString == "" {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
util.HandleWrite(w.Write([]byte("note not specified")))
|
||||
return
|
||||
}
|
||||
postEntry(files.DataFile("notes/"+noteString), w, r)
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue