A community based topic aggregation platform built on atproto
1package community 2 3import ( 4 "Coves/internal/api/middleware" 5 "Coves/internal/core/communities" 6 "encoding/json" 7 "net/http" 8) 9 10// CreateHandler handles community creation 11type CreateHandler struct { 12 service communities.Service 13 allowedCommunityCreators map[string]bool // nil = allow all 14} 15 16// NewCreateHandler creates a new create handler 17// allowedCreators is a list of DIDs that can create communities. If empty, anyone can create. 18func NewCreateHandler(service communities.Service, allowedCreators []string) *CreateHandler { 19 var allowedMap map[string]bool 20 if len(allowedCreators) > 0 { 21 allowedMap = make(map[string]bool) 22 for _, did := range allowedCreators { 23 if did != "" { // Skip empty strings 24 allowedMap[did] = true 25 } 26 } 27 // If all entries were empty, treat as no restriction 28 if len(allowedMap) == 0 { 29 allowedMap = nil 30 } 31 } 32 return &CreateHandler{ 33 service: service, 34 allowedCommunityCreators: allowedMap, 35 } 36} 37 38// HandleCreate creates a new community 39// POST /xrpc/social.coves.community.create 40// Body matches CreateCommunityRequest 41func (h *CreateHandler) HandleCreate(w http.ResponseWriter, r *http.Request) { 42 if r.Method != http.MethodPost { 43 http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) 44 return 45 } 46 47 // Parse request body 48 var req communities.CreateCommunityRequest 49 if err := json.NewDecoder(r.Body).Decode(&req); err != nil { 50 writeError(w, http.StatusBadRequest, "InvalidRequest", "Invalid request body") 51 return 52 } 53 54 // Extract authenticated user DID from request context (injected by auth middleware) 55 userDID := middleware.GetUserDID(r) 56 if userDID == "" { 57 writeError(w, http.StatusUnauthorized, "AuthRequired", "Authentication required") 58 return 59 } 60 61 // Check if user is allowed to create communities (if restriction is enabled) 62 if h.allowedCommunityCreators != nil && !h.allowedCommunityCreators[userDID] { 63 writeError(w, http.StatusForbidden, "CommunityCreationRestricted", 64 "Community creation is restricted to authorized users") 65 return 66 } 67 68 // Client should not send createdByDid - we derive it from authenticated user 69 if req.CreatedByDID != "" { 70 writeError(w, http.StatusBadRequest, "InvalidRequest", 71 "createdByDid must not be provided - derived from authenticated user") 72 return 73 } 74 75 // Client should not send hostedByDid - we derive it from the instance 76 if req.HostedByDID != "" { 77 writeError(w, http.StatusBadRequest, "InvalidRequest", 78 "hostedByDid must not be provided - derived from instance") 79 return 80 } 81 82 // Set the authenticated user as the creator 83 req.CreatedByDID = userDID 84 // Note: hostedByDID will be set by the service layer based on instance configuration 85 86 // Create community via service (write-forward to PDS) 87 community, err := h.service.CreateCommunity(r.Context(), req) 88 if err != nil { 89 handleServiceError(w, err) 90 return 91 } 92 93 // Return success response matching lexicon output 94 response := map[string]interface{}{ 95 "uri": community.RecordURI, 96 "cid": community.RecordCID, 97 "did": community.DID, 98 "handle": community.Handle, 99 } 100 101 w.Header().Set("Content-Type", "application/json") 102 w.WriteHeader(http.StatusOK) 103 if err := json.NewEncoder(w).Encode(response); err != nil { 104 // Log encoding errors but don't return error response (headers already sent) 105 // This follows Go's standard practice for HTTP handlers 106 _ = err 107 } 108}