A community based topic aggregation platform built on atproto
at main 83 lines 3.1 kB view raw
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}