diff --git a/api/handler.go b/api/handler.go new file mode 100644 index 0000000..eeade22 --- /dev/null +++ b/api/handler.go @@ -0,0 +1,119 @@ +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}) +} + +// healthz — for k8s probes / load balancer. +func (s *Server) handleHealth(w http.ResponseWriter, r *http.Request) { + writeJSON(w, 200, map[string]string{"status": "ok"}) +} + +func (s *Server) handleListPosts(w http.ResponseWriter, r *http.Request) { + channel := r.URL.Query().Get("channel") + posts, err := s.store.ListPosts(channel, 20) + if err != nil { + jsonError(w, 500, err.Error()) + return + } + if posts == nil { + posts = []Post{} // never return null to clients + } + 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, err := s.store.GetPost(id) + if err != nil { + jsonError(w, 404, "not found") + return + } + comments, err := s.store.ListComments(id) + if err != nil { + jsonError(w, 500, err.Error()) + return + } + if comments == nil { + comments = []Comment{} + } + writeJSON(w, 200, PostWithComments{Post: *post, Comments: comments}) +} + +func (s *Server) handleCreatePost(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodPost { + jsonError(w, 405, "POST only") + return + } + author := r.Header.Get("X-Posts-Author") + if author == "" { + jsonError(w, 401, "X-Posts-Author required (v0: no auth, just header)") + return + } + var req CreatePostRequest + if err := json.NewDecoder(r.Body).Decode(&req); err != nil { + jsonError(w, 400, "bad json") + return + } + if strings.TrimSpace(req.Body) == "" { + jsonError(w, 400, "body required") + return + } + post, err := s.store.CreatePost(author, req.Title, req.Body, req.Channels) + if err != nil { + jsonError(w, 500, err.Error()) + return + } + writeJSON(w, 201, post) +} + +func (s *Server) handleCreateComment(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodPost { + jsonError(w, 405, "POST only") + return + } + author := r.Header.Get("X-Posts-Author") + if author == "" { + jsonError(w, 401, "X-Posts-Author required") + return + } + // path: /api/posts/:id/comments + parts := strings.Split(strings.Trim(r.URL.Path, "/"), "/") + // expected: ["api","posts",":id","comments"] + if len(parts) != 4 { + 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 + } + if strings.TrimSpace(req.Body) == "" { + jsonError(w, 400, "body required") + return + } + c, err := s.store.AddComment(postID, req.ParentID, author, req.Body) + if err != nil { + jsonError(w, 400, err.Error()) + return + } + writeJSON(w, 201, c) +}