package main import ( "encoding/json" "net/http" "strings" ) func writeJSON(w http.ResponseWriter, code int, v any) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(code) _ = json.NewEncoder(w).Encode(v) } func jsonError(w http.ResponseWriter, code int, msg string) { writeJSON(w, code, map[string]string{"error": msg}) } func (s *Server) handleHealth(w http.ResponseWriter, r *http.Request) { count, errors := s.store.FlushStats() mem := s.store.MemUsage() writeJSON(w, 200, map[string]any{ "status": "ok", "flushes": count, "flush_errors": errors, "mem": mem, }) } func (s *Server) handleListPosts(w http.ResponseWriter, r *http.Request) { channel := r.URL.Query().Get("channel") posts := s.store.ListPosts(channel, 20) if posts == nil { posts = []*Post{} } writeJSON(w, 200, map[string]any{"posts": posts}) } func (s *Server) handleGetPost(w http.ResponseWriter, r *http.Request) { id := strings.TrimPrefix(r.URL.Path, "/api/posts/") if id == "" || strings.Contains(id, "/") { jsonError(w, 400, "bad id") return } post, ok := s.store.GetPost(id) if !ok { jsonError(w, 404, "not found") return } comments := s.store.ListComments(id) if comments == nil { comments = []*Comment{} } writeJSON(w, 200, PostWithComments{Post: *post, Comments: comments}) } func (s *Server) handleCreatePost(w http.ResponseWriter, r *http.Request) { author := r.Header.Get("X-Posts-Author") if author == "" { jsonError(w, 401, "X-Posts-Author required") return } var req CreatePostRequest if err := json.NewDecoder(r.Body).Decode(&req); err != nil { jsonError(w, 400, "bad json") return } m := &CreatePost{ AuthorID: author, Title: req.Title, Body: req.Body, Channels: req.Channels, } if err := Submit(r.Context(), s.store, m); err != nil { jsonError(w, 400, err.Error()) return } post, _ := s.store.GetPost(m.ResolvedPostID()) writeJSON(w, 201, post) } func (s *Server) handleCreateComment(w http.ResponseWriter, r *http.Request) { author := r.Header.Get("X-Posts-Author") if author == "" { jsonError(w, 401, "X-Posts-Author required") return } parts := strings.Split(strings.Trim(r.URL.Path, "/"), "/") if len(parts) != 4 || parts[0] != "api" || parts[1] != "posts" || parts[3] != "comments" { jsonError(w, 400, "bad path") return } postID := parts[2] var req CreateCommentRequest if err := json.NewDecoder(r.Body).Decode(&req); err != nil { jsonError(w, 400, "bad json") return } m := &AddComment{ PostID: postID, ParentID: req.ParentID, AuthorID: author, Body: req.Body, } if err := Submit(r.Context(), s.store, m); err != nil { jsonError(w, 400, err.Error()) return } comments := s.store.ListComments(postID) var created *Comment for _, c := range comments { if c.ID == m.ResolvedCommentID() { created = c break } } writeJSON(w, 201, created) } func (s *Server) handleUpsertAuthor(w http.ResponseWriter, r *http.Request) { var req struct { ID string `json:"id"` Kind string `json:"kind"` DisplayName string `json:"display_name"` AvatarURL string `json:"avatar_url,omitempty"` } if err := json.NewDecoder(r.Body).Decode(&req); err != nil { jsonError(w, 400, "bad json") return } m := &UpsertAuthor{ID: req.ID, AuthorKind: req.Kind, DisplayName: req.DisplayName, AvatarURL: req.AvatarURL} if err := Submit(r.Context(), s.store, m); err != nil { jsonError(w, 400, err.Error()) return } a, _ := s.store.GetAuthor(m.ID) writeJSON(w, 200, a) } func (s *Server) handleUpsertChannel(w http.ResponseWriter, r *http.Request) { var req struct { Slug string `json:"slug"` Name string `json:"name"` Description string `json:"description,omitempty"` } if err := json.NewDecoder(r.Body).Decode(&req); err != nil { jsonError(w, 400, "bad json") return } m := &UpsertChannel{Slug: req.Slug, Name: req.Name, Description: req.Description} if err := Submit(r.Context(), s.store, m); err != nil { jsonError(w, 400, err.Error()) return } c, _ := s.store.GetChannel(m.Slug) writeJSON(w, 200, c) }