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}