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}