Lots of changes to the website
This commit is contained in:
@@ -1,7 +1,6 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"io"
|
||||
@@ -12,6 +11,7 @@ import (
|
||||
"time"
|
||||
|
||||
"ridgwaysystems.org/website/internal/blog"
|
||||
"ridgwaysystems.org/website/internal/changelog"
|
||||
"ridgwaysystems.org/website/internal/newsletter"
|
||||
"ridgwaysystems.org/website/internal/status"
|
||||
)
|
||||
@@ -55,6 +55,26 @@ func (h *Handler) AdminRouter(w http.ResponseWriter, r *http.Request) {
|
||||
h.requireAuth(h.adminStatusGet)(w, r)
|
||||
}
|
||||
|
||||
case path == "/admin/changelog":
|
||||
h.requireAuth(h.adminChangelogList)(w, r)
|
||||
|
||||
case path == "/admin/changelog/new":
|
||||
if r.Method == http.MethodPost {
|
||||
h.requireAuth(h.adminChangelogNewPost)(w, r)
|
||||
} else {
|
||||
h.requireAuth(h.adminChangelogNewGet)(w, r)
|
||||
}
|
||||
|
||||
case strings.HasPrefix(path, "/admin/changelog/edit/"):
|
||||
if r.Method == http.MethodPost {
|
||||
h.requireAuth(h.adminChangelogEditPost)(w, r)
|
||||
} else {
|
||||
h.requireAuth(h.adminChangelogEditGet)(w, r)
|
||||
}
|
||||
|
||||
case strings.HasPrefix(path, "/admin/changelog/delete/"):
|
||||
h.requireAuth(h.adminChangelogDelete)(w, r)
|
||||
|
||||
case path == "/admin/preview":
|
||||
h.requireAuth(h.adminPreview)(w, r)
|
||||
|
||||
@@ -208,7 +228,7 @@ func (h *Handler) adminDeletePost(w http.ResponseWriter, r *http.Request) {
|
||||
// --- Status editor ---
|
||||
|
||||
type adminStatusData struct {
|
||||
JSON string
|
||||
Page *status.Page
|
||||
Error string
|
||||
Flash string
|
||||
}
|
||||
@@ -219,9 +239,7 @@ func (h *Handler) adminStatusGet(w http.ResponseWriter, r *http.Request) {
|
||||
h.render(w, "admin-status", adminStatusData{Error: "Could not load status.json: " + err.Error()})
|
||||
return
|
||||
}
|
||||
raw, _ := json.MarshalIndent(p, "", " ")
|
||||
flash := r.URL.Query().Get("flash")
|
||||
h.render(w, "admin-status", adminStatusData{JSON: string(raw), Flash: flash})
|
||||
h.render(w, "admin-status", adminStatusData{Page: p, Flash: r.URL.Query().Get("flash")})
|
||||
}
|
||||
|
||||
func (h *Handler) adminStatusPost(w http.ResponseWriter, r *http.Request) {
|
||||
@@ -229,15 +247,19 @@ func (h *Handler) adminStatusPost(w http.ResponseWriter, r *http.Request) {
|
||||
h.renderErr(w, http.StatusBadRequest, "Bad form data.")
|
||||
return
|
||||
}
|
||||
raw := r.FormValue("json")
|
||||
var p status.Page
|
||||
if err := json.Unmarshal([]byte(raw), &p); err != nil {
|
||||
h.render(w, "admin-status", adminStatusData{JSON: raw, Error: "Invalid JSON: " + err.Error()})
|
||||
p, err := status.Load(filepath.Join(h.dataDir, "status.json"))
|
||||
if err != nil {
|
||||
h.render(w, "admin-status", adminStatusData{Error: "Could not load status: " + err.Error()})
|
||||
return
|
||||
}
|
||||
p.LastChecked = time.Now().UTC()
|
||||
if err := status.Save(filepath.Join(h.dataDir, "status.json"), &p); err != nil {
|
||||
h.render(w, "admin-status", adminStatusData{JSON: raw, Error: "Save failed: " + err.Error()})
|
||||
for i := range p.Services {
|
||||
if s := r.FormValue(fmt.Sprintf("status_%d", i)); s != "" {
|
||||
p.Services[i].Status = s
|
||||
}
|
||||
p.Services[i].Note = strings.TrimSpace(r.FormValue(fmt.Sprintf("note_%d", i)))
|
||||
}
|
||||
if err := status.Save(filepath.Join(h.dataDir, "status.json"), p); err != nil {
|
||||
h.render(w, "admin-status", adminStatusData{Page: p, Error: "Save failed: " + err.Error()})
|
||||
return
|
||||
}
|
||||
http.Redirect(w, r, "/admin/status?flash=Saved", http.StatusSeeOther)
|
||||
@@ -416,6 +438,121 @@ func (h *Handler) adminNewsletterDelete(w http.ResponseWriter, r *http.Request)
|
||||
http.Redirect(w, r, "/admin/newsletter?flash=Removed", http.StatusSeeOther)
|
||||
}
|
||||
|
||||
// --- Changelog admin ---
|
||||
|
||||
type adminChangelogData struct {
|
||||
Log *changelog.Log
|
||||
Entry *changelog.Entry
|
||||
Categories []string
|
||||
Error string
|
||||
Flash string
|
||||
IsNew bool
|
||||
}
|
||||
|
||||
func (h *Handler) adminChangelogList(w http.ResponseWriter, r *http.Request) {
|
||||
l, err := changelog.Load(filepath.Join(h.dataDir, "changelog.json"))
|
||||
if err != nil {
|
||||
l = &changelog.Log{}
|
||||
}
|
||||
h.render(w, "admin-changelog", adminChangelogData{
|
||||
Log: l,
|
||||
Flash: r.URL.Query().Get("flash"),
|
||||
})
|
||||
}
|
||||
|
||||
func (h *Handler) adminChangelogNewGet(w http.ResponseWriter, r *http.Request) {
|
||||
h.render(w, "admin-changelog-editor", adminChangelogData{
|
||||
Categories: changelog.Categories,
|
||||
IsNew: true,
|
||||
Entry: &changelog.Entry{Date: time.Now().Format("2006-01-02")},
|
||||
})
|
||||
}
|
||||
|
||||
func (h *Handler) adminChangelogNewPost(w http.ResponseWriter, r *http.Request) {
|
||||
if err := r.ParseForm(); err != nil {
|
||||
h.renderErr(w, http.StatusBadRequest, "Bad form data.")
|
||||
return
|
||||
}
|
||||
e := changelog.Entry{
|
||||
Date: r.FormValue("date"),
|
||||
Title: strings.TrimSpace(r.FormValue("title")),
|
||||
Description: strings.TrimSpace(r.FormValue("description")),
|
||||
Category: r.FormValue("category"),
|
||||
}
|
||||
if e.Title == "" || e.Date == "" {
|
||||
h.render(w, "admin-changelog-editor", adminChangelogData{
|
||||
Entry: &e, Categories: changelog.Categories, IsNew: true,
|
||||
Error: "Date and title are required.",
|
||||
})
|
||||
return
|
||||
}
|
||||
if err := changelog.Add(filepath.Join(h.dataDir, "changelog.json"), e); err != nil {
|
||||
h.render(w, "admin-changelog-editor", adminChangelogData{
|
||||
Entry: &e, Categories: changelog.Categories, IsNew: true,
|
||||
Error: "Failed to save: " + err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
http.Redirect(w, r, "/admin/changelog?flash=Entry+created", http.StatusSeeOther)
|
||||
}
|
||||
|
||||
func (h *Handler) adminChangelogEditGet(w http.ResponseWriter, r *http.Request) {
|
||||
id := strings.TrimPrefix(r.URL.Path, "/admin/changelog/edit/")
|
||||
e, err := changelog.Get(filepath.Join(h.dataDir, "changelog.json"), id)
|
||||
if err != nil {
|
||||
h.renderErr(w, http.StatusNotFound, "Entry not found.")
|
||||
return
|
||||
}
|
||||
h.render(w, "admin-changelog-editor", adminChangelogData{
|
||||
Entry: e,
|
||||
Categories: changelog.Categories,
|
||||
IsNew: false,
|
||||
})
|
||||
}
|
||||
|
||||
func (h *Handler) adminChangelogEditPost(w http.ResponseWriter, r *http.Request) {
|
||||
id := strings.TrimPrefix(r.URL.Path, "/admin/changelog/edit/")
|
||||
if err := r.ParseForm(); err != nil {
|
||||
h.renderErr(w, http.StatusBadRequest, "Bad form data.")
|
||||
return
|
||||
}
|
||||
e := changelog.Entry{
|
||||
ID: id,
|
||||
Date: r.FormValue("date"),
|
||||
Title: strings.TrimSpace(r.FormValue("title")),
|
||||
Description: strings.TrimSpace(r.FormValue("description")),
|
||||
Category: r.FormValue("category"),
|
||||
}
|
||||
if e.Title == "" || e.Date == "" {
|
||||
h.render(w, "admin-changelog-editor", adminChangelogData{
|
||||
Entry: &e, Categories: changelog.Categories,
|
||||
Error: "Date and title are required.",
|
||||
})
|
||||
return
|
||||
}
|
||||
if err := changelog.Update(filepath.Join(h.dataDir, "changelog.json"), e); err != nil {
|
||||
h.render(w, "admin-changelog-editor", adminChangelogData{
|
||||
Entry: &e, Categories: changelog.Categories,
|
||||
Error: "Failed to save: " + err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
http.Redirect(w, r, "/admin/changelog?flash=Entry+saved", http.StatusSeeOther)
|
||||
}
|
||||
|
||||
func (h *Handler) adminChangelogDelete(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodPost {
|
||||
h.renderErr(w, http.StatusMethodNotAllowed, "POST required.")
|
||||
return
|
||||
}
|
||||
id := strings.TrimPrefix(r.URL.Path, "/admin/changelog/delete/")
|
||||
if err := changelog.Delete(filepath.Join(h.dataDir, "changelog.json"), id); err != nil {
|
||||
h.renderErr(w, http.StatusInternalServerError, "Delete failed: "+err.Error())
|
||||
return
|
||||
}
|
||||
http.Redirect(w, r, "/admin/changelog?flash=Entry+deleted", http.StatusSeeOther)
|
||||
}
|
||||
|
||||
// sanitizeSlug ensures a slug is filesystem-safe.
|
||||
func sanitizeSlug(s string) string {
|
||||
s = strings.ToLower(strings.TrimSpace(s))
|
||||
|
||||
Reference in New Issue
Block a user