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