package web import ( "donniemarko/internal/note" "donniemarko/internal/render" "donniemarko/internal/service" "html/template" "net/http" "strings" ) type Handler struct { notesService *service.NotesService templates *render.TemplateManager mux *http.ServeMux } func NewHandler(ns *service.NotesService, tm *render.TemplateManager) *Handler { return &Handler{ notesService: ns, templates: tm, mux: http.NewServeMux(), } } func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { path := r.URL.Path // Handle root and note list if path == "/" { h.handleRoot(w, r) return } // Handle individual notes if strings.HasPrefix(path, "/notes/") { h.handleNotes(w, r) return } // Handle 404 for other paths http.NotFound(w, r) } // ViewState is built per-request, not shared type ViewState struct { Notes []*note.Note Note *note.Note RenderedNote template.HTML SortBy string SearchTerm string LastActive string } func (h *Handler) handleRoot(w http.ResponseWriter, r *http.Request) { // Build view state from query params state, err := h.buildViewState(r) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } // Render with state h.templates.Render(w, "index", state) } func (h *Handler) buildViewState(r *http.Request) (*ViewState, error) { query := r.URL.Query() // Extract params sortBy := query.Get("sort") if sortBy == "" { sortBy = "recent" } searchTerm := query.Get("search") // Get notes from service var notes []*note.Note var err error if searchTerm != "" { opts := service.QueryOptions{ SearchTerm: searchTerm, SortBy: sortBy, } notes, err = h.notesService.QueryNotes(opts) if err != nil { return nil, err } } else { notes = h.notesService.GetNotes() // Apply sorting switch sortBy { case "recent": service.SortByDate(notes) case "oldest": service.SortByDateAsc(notes) case "alpha": service.SortByTitle(notes) case "ralpha": service.SortByTitleAsc(notes) default: service.SortByDate(notes) } } return &ViewState{ Notes: notes, SortBy: sortBy, SearchTerm: searchTerm, }, nil } func (h *Handler) SetupRoutes() { // Set the handler as the main handler for http.DefaultServeMux http.Handle("/", h) http.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir("internal/web/static")))) } func extractHash(path string) string { // Extract hash from /notes/{hash} parts := strings.Split(strings.Trim(path, "/"), "/") if len(parts) < 2 || parts[0] != "notes" { return "" } return parts[1] } func (h *Handler) handleNotes(w http.ResponseWriter, r *http.Request) { // Build base state state, err := h.buildViewState(r) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } // Extract hash from URL hash := extractHash(r.URL.Path) // Get specific note note, err := h.notesService.GetNoteByHash(hash) if err != nil { http.Error(w, "Note not found", http.StatusNotFound) return } // Convert markdown to HTML htmlContent, err := render.RenderMarkdown([]byte(note.Content)) if err != nil { http.Error(w, "Failed to render markdown", http.StatusInternalServerError) return } // Add to state state.Note = note state.RenderedNote = htmlContent state.LastActive = hash h.templates.Render(w, "index", state) }