Files
VeillaxandClaude Sonnet 5.5 e3147597e8
CI / test (push) Successful in 2m9s
Release / release (push) Successful in 2m16s
Rename Nurse to Mako
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-01 00:07:43 -06:00

155 lines
3.9 KiB
Go

package web
import (
"errors"
"fmt"
"net/http"
"strconv"
"time"
"git.veillax.com/Veillax/mako/internal/store"
)
func (s *Server) pages(w http.ResponseWriter, r *http.Request, p principal) {
counts, tags := p.st.Summary()
writeJSON(w, map[string]any{"pages": p.st.Pages(), "counts": counts, "tags": tags})
}
func (s *Server) listNotes(w http.ResponseWriter, r *http.Request, p principal) {
q := r.URL.Query()
limit, _ := strconv.Atoi(q.Get("limit"))
if limit <= 0 || limit > 200 {
limit = 50
}
offset, _ := strconv.Atoi(q.Get("offset"))
text := q.Get("q")
if len(text) > 200 {
text = text[:200]
}
notes, total := p.st.List(store.Query{Text: text, Tag: q.Get("tag"), View: q.Get("view"), Offset: offset, Limit: limit})
writeJSON(w, map[string]any{"notes": notes, "total": total})
}
func (s *Server) find(w http.ResponseWriter, r *http.Request, p principal) {
q := r.URL.Query().Get("q")
if len(q) > 200 {
q = q[:200]
}
writeJSON(w, map[string]any{"hits": p.st.Find(q, 12)})
}
func (s *Server) createNote(w http.ResponseWriter, r *http.Request, p principal) {
var in struct{ Title, Body, Parent string }
if !decode(w, r, &in) {
return
}
n, err := p.st.Create(store.NewNote{Title: in.Title, Body: in.Body, Parent: in.Parent})
if err != nil {
storeFail(w, err)
return
}
writeJSONStatus(w, http.StatusCreated, n)
}
// page is a note with the pages that link to it.
type page struct {
store.Note
Backlinks []store.Ref `json:"backlinks"`
}
func (s *Server) getNote(w http.ResponseWriter, r *http.Request, p principal) {
n, err := p.st.Get(r.PathValue("id"))
if err != nil {
storeFail(w, err)
return
}
writeJSON(w, page{n, p.st.Backlinks(n.ID)})
}
func (s *Server) updateNote(w http.ResponseWriter, r *http.Request, p principal) {
var in struct {
Title *string `json:"title"`
Body *string `json:"body"`
Pinned *bool `json:"pinned"`
Parent *string `json:"parent"`
Base string `json:"base"`
}
if !decode(w, r, &in) {
return
}
var base time.Time
if in.Base != "" {
t, err := time.Parse(time.RFC3339Nano, in.Base)
if err != nil {
fail(w, http.StatusBadRequest, "base must be the page's updated time")
return
}
base = t
}
n, err := p.st.Update(r.PathValue("id"), store.Change{Title: in.Title, Body: in.Body, Pinned: in.Pinned, Parent: in.Parent}, base)
if errors.Is(err, store.ErrConflict) {
writeJSONStatus(w, http.StatusConflict, map[string]any{"error": "This page changed somewhere else", "note": n})
return
}
if err != nil {
storeFail(w, err)
return
}
writeJSON(w, page{n, p.st.Backlinks(n.ID)})
}
func (s *Server) deleteNote(w http.ResponseWriter, r *http.Request, p principal) {
id := r.PathValue("id")
var err error
if r.URL.Query().Get("purge") == "1" {
err = p.st.Purge(id)
} else {
_, err = p.st.Trash(id)
}
if err != nil {
storeFail(w, err)
return
}
writeJSON(w, map[string]bool{"ok": true})
}
func (s *Server) restoreNote(w http.ResponseWriter, r *http.Request, p principal) {
n, err := p.st.Restore(r.PathValue("id"))
if err != nil {
storeFail(w, err)
return
}
writeJSON(w, n)
}
func (s *Server) emptyTrash(w http.ResponseWriter, r *http.Request, p principal) {
n, err := p.st.EmptyTrash()
if err != nil {
storeFail(w, err)
return
}
writeJSON(w, map[string]int{"deleted": n})
}
func (s *Server) daily(w http.ResponseWriter, r *http.Request, p principal) {
var in struct{ Date string }
if !decode(w, r, &in) {
return
}
n, err := p.st.Daily(in.Date)
if err != nil {
storeFail(w, err)
return
}
writeJSON(w, page{n, p.st.Backlinks(n.ID)})
}
func (s *Server) export(w http.ResponseWriter, r *http.Request, p principal) {
w.Header().Set("Content-Type", "application/zip")
w.Header().Set("Content-Disposition", fmt.Sprintf(`attachment; filename="mako-%s.zip"`, time.Now().Format("2006-01-02")))
if err := p.st.WriteZip(w); err != nil {
// The download has started, so all that can be done is to cut it short.
panic(http.ErrAbortHandler)
}
}