2024-03-28 10:21:04 +03:00
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
|
|
|
"errors"
|
|
|
|
"github.com/go-chi/chi/v5"
|
|
|
|
"html/template"
|
|
|
|
"log/slog"
|
|
|
|
"net/http"
|
|
|
|
"os"
|
2024-03-30 14:22:03 +03:00
|
|
|
"strings"
|
2024-03-28 10:21:04 +03:00
|
|
|
"time"
|
|
|
|
)
|
|
|
|
|
2024-03-30 13:51:29 +03:00
|
|
|
type EntryList struct {
|
2024-04-23 14:13:45 +03:00
|
|
|
Title string
|
|
|
|
Description string
|
|
|
|
Entries []Entry
|
2024-03-28 10:21:04 +03:00
|
|
|
}
|
|
|
|
|
2024-03-30 13:51:29 +03:00
|
|
|
type Entry struct {
|
|
|
|
Title string
|
|
|
|
Content string
|
|
|
|
Link string
|
2024-03-28 10:21:04 +03:00
|
|
|
}
|
|
|
|
|
2024-04-23 14:13:45 +03:00
|
|
|
type formatEntries func([]string) []Entry
|
|
|
|
|
2024-03-28 10:21:04 +03:00
|
|
|
// NotFound returns a user-friendly 404 error page
|
|
|
|
func NotFound(w http.ResponseWriter, r *http.Request) {
|
|
|
|
w.WriteHeader(404)
|
|
|
|
http.ServeFile(w, r, "./pages/error/404.html")
|
|
|
|
}
|
|
|
|
|
|
|
|
// InternalError returns a user-friendly 500 error page
|
|
|
|
func InternalError(w http.ResponseWriter, r *http.Request) {
|
|
|
|
w.WriteHeader(500)
|
|
|
|
http.ServeFile(w, r, "./pages/error/500.html")
|
|
|
|
}
|
|
|
|
|
2024-03-30 14:22:03 +03:00
|
|
|
// GetToday renders HTML page for today's entry
|
2024-03-28 10:21:04 +03:00
|
|
|
func GetToday(w http.ResponseWriter, r *http.Request) {
|
|
|
|
day, err := ReadToday()
|
|
|
|
if err != nil {
|
|
|
|
if errors.Is(err, os.ErrNotExist) {
|
|
|
|
day = []byte("")
|
|
|
|
} else {
|
|
|
|
slog.Error("error reading today's file", "error", err)
|
|
|
|
InternalError(w, r)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-03-30 13:51:29 +03:00
|
|
|
files := []string{"./pages/base.html", "./pages/edit.html"}
|
2024-03-28 10:21:04 +03:00
|
|
|
ts, err := template.ParseFiles(files...)
|
|
|
|
if err != nil {
|
|
|
|
InternalError(w, r)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2024-03-30 13:51:29 +03:00
|
|
|
err = ts.ExecuteTemplate(w, "base", Entry{Title: "Your day so far", Content: string(day)})
|
2024-03-28 10:21:04 +03:00
|
|
|
if err != nil {
|
|
|
|
InternalError(w, r)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-03-28 10:42:52 +03:00
|
|
|
// PostToday saves today's entry from form and redirects back to GET
|
2024-03-28 10:21:04 +03:00
|
|
|
func PostToday(w http.ResponseWriter, r *http.Request) {
|
2024-03-30 13:51:29 +03:00
|
|
|
err := SaveToday([]byte(r.FormValue("text")))
|
2024-03-28 10:21:04 +03:00
|
|
|
if err != nil {
|
|
|
|
slog.Error("error saving today's file", "error", err)
|
|
|
|
}
|
|
|
|
http.Redirect(w, r, r.Header.Get("Referer"), 302)
|
|
|
|
}
|
|
|
|
|
2024-04-23 14:13:45 +03:00
|
|
|
// GetEntries is a generic HTML renderer for a list
|
|
|
|
func GetEntries(w http.ResponseWriter, r *http.Request, title string, description string, dir string, format formatEntries) {
|
|
|
|
filesList, err := ListFiles(dir)
|
2024-03-28 10:21:04 +03:00
|
|
|
if err != nil {
|
2024-04-23 14:13:45 +03:00
|
|
|
slog.Error("error reading file list", "directory", dir, "error", err)
|
2024-03-28 10:21:04 +03:00
|
|
|
InternalError(w, r)
|
|
|
|
return
|
|
|
|
}
|
2024-04-23 14:13:45 +03:00
|
|
|
var filesFormatted = format(filesList)
|
2024-03-28 10:21:04 +03:00
|
|
|
|
2024-03-30 13:51:29 +03:00
|
|
|
files := []string{"./pages/base.html", "./pages/list.html"}
|
2024-03-28 10:21:04 +03:00
|
|
|
ts, err := template.ParseFiles(files...)
|
|
|
|
if err != nil {
|
2024-03-30 14:22:03 +03:00
|
|
|
slog.Error("error parsing template files", "error", err)
|
2024-03-28 10:21:04 +03:00
|
|
|
InternalError(w, r)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2024-04-23 14:13:45 +03:00
|
|
|
err = ts.ExecuteTemplate(w, "base", EntryList{Title: title, Description: description, Entries: filesFormatted})
|
2024-03-28 10:21:04 +03:00
|
|
|
if err != nil {
|
2024-03-30 14:22:03 +03:00
|
|
|
slog.Error("error executing template", "error", err)
|
2024-03-28 10:21:04 +03:00
|
|
|
InternalError(w, r)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-04-23 14:13:45 +03:00
|
|
|
// GetDays renders HTML list of previous days' entries
|
|
|
|
func GetDays(w http.ResponseWriter, r *http.Request) {
|
|
|
|
GetEntries(w, r, "Previous days", "", "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")
|
|
|
|
}
|
|
|
|
if v == time.Now().Format(time.DateOnly) {
|
|
|
|
dayString = "Today"
|
|
|
|
}
|
|
|
|
filesFormatted = append(filesFormatted, Entry{Title: dayString, Link: "day/" + v})
|
|
|
|
}
|
|
|
|
return filesFormatted
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
// GetNotes renders HTML list of all notes
|
|
|
|
func GetNotes(w http.ResponseWriter, r *http.Request) {
|
|
|
|
GetEntries(w, r, "Notes", "/notes/<name> for a new note", "notes", func(files []string) []Entry {
|
|
|
|
var filesFormatted []Entry
|
|
|
|
for _, v := range files {
|
|
|
|
titleString := strings.Replace(v, "-", " ", -1) // FIXME: what if I need a hyphen?
|
|
|
|
filesFormatted = append(filesFormatted, Entry{Title: titleString, Link: "notes/" + v})
|
|
|
|
}
|
|
|
|
return filesFormatted
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2024-04-30 23:35:00 +03:00
|
|
|
func GetEntry(w http.ResponseWriter, r *http.Request, title string, filename string, editable bool) {
|
|
|
|
entry, err := ReadFile(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
|
|
|
|
}
|
2024-03-28 10:21:04 +03:00
|
|
|
}
|
2024-04-30 23:35:00 +03:00
|
|
|
|
|
|
|
files := []string{"./pages/base.html"}
|
|
|
|
if editable {
|
|
|
|
files = append(files, "./pages/edit.html")
|
|
|
|
} else {
|
|
|
|
files = append(files, "./pages/entry.html")
|
2024-03-29 14:48:55 +03:00
|
|
|
}
|
2024-04-30 23:35:00 +03:00
|
|
|
ts, err := template.ParseFiles(files...)
|
2024-03-28 10:21:04 +03:00
|
|
|
if err != nil {
|
|
|
|
InternalError(w, r)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2024-04-30 23:35:00 +03:00
|
|
|
err = ts.ExecuteTemplate(w, "base", Entry{Title: title, Content: string(entry)})
|
2024-03-28 10:21:04 +03:00
|
|
|
if err != nil {
|
|
|
|
InternalError(w, r)
|
|
|
|
return
|
|
|
|
}
|
2024-04-30 23:35:00 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
// GetDay renders HTML page for a specific day entry
|
|
|
|
func GetDay(w http.ResponseWriter, r *http.Request) {
|
|
|
|
dayString := chi.URLParam(r, "day")
|
|
|
|
if dayString == "" {
|
|
|
|
w.WriteHeader(http.StatusBadRequest)
|
|
|
|
HandleWrite(w.Write([]byte("day not specified")))
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if dayString == time.Now().Format(time.DateOnly) { // Today can still be edited
|
|
|
|
http.Redirect(w, r, "/", 302)
|
|
|
|
return
|
|
|
|
}
|
2024-03-28 10:21:04 +03:00
|
|
|
|
2024-04-30 23:35:00 +03:00
|
|
|
title := dayString
|
2024-03-28 10:21:04 +03:00
|
|
|
t, err := time.Parse(time.DateOnly, dayString)
|
|
|
|
if err == nil { // This is low priority so silently fail
|
2024-04-30 23:35:00 +03:00
|
|
|
title = t.Format("02 Jan 2006")
|
2024-03-28 10:21:04 +03:00
|
|
|
}
|
|
|
|
|
2024-04-30 23:35:00 +03:00
|
|
|
GetEntry(w, r, title, "day/"+dayString, false)
|
2024-03-28 10:21:04 +03:00
|
|
|
}
|
2024-03-30 14:22:03 +03:00
|
|
|
|
|
|
|
// GetNote renders HTML page for a note
|
|
|
|
func GetNote(w http.ResponseWriter, r *http.Request) {
|
|
|
|
noteString := chi.URLParam(r, "note")
|
|
|
|
if noteString == "" {
|
|
|
|
w.WriteHeader(http.StatusBadRequest)
|
|
|
|
HandleWrite(w.Write([]byte("note not specified")))
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2024-04-30 23:35:00 +03:00
|
|
|
GetEntry(w, r, noteString, "notes/"+noteString, true)
|
2024-03-30 14:22:03 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
// PostNote saves a note form and redirects back to GET
|
|
|
|
func PostNote(w http.ResponseWriter, r *http.Request) {
|
|
|
|
noteString := chi.URLParam(r, "note")
|
|
|
|
if noteString == "" {
|
|
|
|
w.WriteHeader(http.StatusBadRequest)
|
|
|
|
HandleWrite(w.Write([]byte("note not specified")))
|
|
|
|
return
|
|
|
|
}
|
|
|
|
err := SaveFile("notes/"+noteString, []byte(r.FormValue("text")))
|
|
|
|
if err != nil {
|
|
|
|
slog.Error("error saving a note", "note", noteString, "error", err)
|
|
|
|
}
|
|
|
|
http.Redirect(w, r, r.Header.Get("Referer"), 302)
|
|
|
|
}
|