handler.go — JSON API Handlers
REST handlers for the /api/bookmarks endpoints. Uses writeJSON and writeError helpers for consistent responses.
package main
import (
"encoding/json"
"errors"
"log/slog"
"net/http"
"strconv"
)
func writeJSON(w http.ResponseWriter, status int, data any) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(status)
json.NewEncoder(w).Encode(data)
}
func writeError(w http.ResponseWriter, status int, message string) {
writeJSON(w, status, map[string]string{"error": message})
}
func handleListBookmarks(store *BookmarkStore) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
bookmarks, err := store.List(r.Context())
if err != nil {
slog.Error("list bookmarks", "err", err)
writeError(w, http.StatusInternalServerError, "internal error")
return
}
if bookmarks == nil {
bookmarks = []Bookmark{}
}
writeJSON(w, http.StatusOK, bookmarks)
}
}
func handleCreateBookmark(store *BookmarkStore) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var input struct {
URL string `json:"url"`
Title string `json:"title"`
}
if err := json.NewDecoder(r.Body).Decode(&input); err != nil {
writeError(w, http.StatusBadRequest, "invalid JSON")
return
}
if input.URL == "" {
writeError(w, http.StatusBadRequest, "url is required")
return
}
if input.Title == "" {
writeError(w, http.StatusBadRequest, "title is required")
return
}
bookmark, err := store.Create(r.Context(), input.URL, input.Title)
if err != nil {
slog.Error("create bookmark", "err", err)
writeError(w, http.StatusInternalServerError, "internal error")
return
}
slog.Info("bookmark created", "id", bookmark.ID, "url", bookmark.URL)
writeJSON(w, http.StatusCreated, bookmark)
}
}
func handleGetBookmark(store *BookmarkStore) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
id, err := strconv.Atoi(r.PathValue("id"))
if err != nil {
writeError(w, http.StatusBadRequest, "invalid id")
return
}
bookmark, err := store.GetByID(r.Context(), id)
if errors.Is(err, ErrNotFound) {
writeError(w, http.StatusNotFound, "bookmark not found")
return
}
if err != nil {
slog.Error("get bookmark", "err", err, "id", id)
writeError(w, http.StatusInternalServerError, "internal error")
return
}
writeJSON(w, http.StatusOK, bookmark)
}
}
func handleDeleteBookmark(store *BookmarkStore) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
id, err := strconv.Atoi(r.PathValue("id"))
if err != nil {
writeError(w, http.StatusBadRequest, "invalid id")
return
}
err = store.Delete(r.Context(), id)
if errors.Is(err, ErrNotFound) {
writeError(w, http.StatusNotFound, "bookmark not found")
return
}
if err != nil {
slog.Error("delete bookmark", "err", err, "id", id)
writeError(w, http.StatusInternalServerError, "internal error")
return
}
w.WriteHeader(http.StatusNoContent)
}
}