A community based topic aggregation platform built on atproto
1package aggregator
2
3import (
4 "Coves/internal/core/aggregators"
5 "Coves/internal/core/communities"
6 "bytes"
7 "encoding/json"
8 "log"
9 "net/http"
10)
11
12// ErrorResponse represents an XRPC error response
13type ErrorResponse struct {
14 Error string `json:"error"`
15 Message string `json:"message"`
16}
17
18// writeJSONResponse buffers the JSON encoding before sending headers.
19// This ensures that encoding failures don't result in partial responses
20// with already-sent headers. Returns true if the response was written
21// successfully, false otherwise.
22func writeJSONResponse(w http.ResponseWriter, statusCode int, data interface{}) bool {
23 // Buffer the JSON first to detect encoding errors before sending headers
24 var buf bytes.Buffer
25 if err := json.NewEncoder(&buf).Encode(data); err != nil {
26 log.Printf("ERROR: Failed to encode JSON response: %v", err)
27 // Send a proper error response since we haven't sent headers yet
28 w.Header().Set("Content-Type", "application/json")
29 w.WriteHeader(http.StatusInternalServerError)
30 _, _ = w.Write([]byte(`{"error":"InternalServerError","message":"Failed to encode response"}`))
31 return false
32 }
33
34 w.Header().Set("Content-Type", "application/json")
35 w.WriteHeader(statusCode)
36 if _, err := w.Write(buf.Bytes()); err != nil {
37 log.Printf("ERROR: Failed to write response body: %v", err)
38 return false
39 }
40 return true
41}
42
43// writeError writes a JSON error response with proper buffering
44func writeError(w http.ResponseWriter, statusCode int, errorType, message string) {
45 writeJSONResponse(w, statusCode, ErrorResponse{
46 Error: errorType,
47 Message: message,
48 })
49}
50
51// handleServiceError maps service errors to HTTP responses
52// Handles errors from both aggregators and communities packages
53func handleServiceError(w http.ResponseWriter, err error) {
54 if err == nil {
55 return
56 }
57
58 // Map domain errors to HTTP status codes
59 // Check community errors first (for ResolveCommunityIdentifier calls)
60 switch {
61 case communities.IsNotFound(err):
62 writeError(w, http.StatusNotFound, "CommunityNotFound", err.Error())
63 case communities.IsValidationError(err):
64 writeError(w, http.StatusBadRequest, "InvalidRequest", err.Error())
65 case aggregators.IsNotFound(err):
66 writeError(w, http.StatusNotFound, "NotFound", err.Error())
67 case aggregators.IsValidationError(err):
68 writeError(w, http.StatusBadRequest, "InvalidRequest", err.Error())
69 case aggregators.IsUnauthorized(err):
70 writeError(w, http.StatusForbidden, "Forbidden", err.Error())
71 case aggregators.IsConflict(err):
72 writeError(w, http.StatusConflict, "Conflict", err.Error())
73 case aggregators.IsRateLimited(err):
74 writeError(w, http.StatusTooManyRequests, "RateLimitExceeded", err.Error())
75 case aggregators.IsNotImplemented(err):
76 writeError(w, http.StatusNotImplemented, "NotImplemented", "This feature is not yet available (Phase 2)")
77 default:
78 // Internal errors - don't leak details
79 log.Printf("ERROR: Aggregator service error: %v", err)
80 writeError(w, http.StatusInternalServerError, "InternalServerError",
81 "An internal error occurred")
82 }
83}