A community based topic aggregation platform built on atproto
at main 5.6 kB view raw
1package community 2 3import ( 4 "Coves/internal/api/middleware" 5 "Coves/internal/core/communities" 6 "encoding/json" 7 "log" 8 "net/http" 9) 10 11// BlockHandler handles community blocking operations 12type BlockHandler struct { 13 service communities.Service 14} 15 16// NewBlockHandler creates a new block handler 17func NewBlockHandler(service communities.Service) *BlockHandler { 18 return &BlockHandler{ 19 service: service, 20 } 21} 22 23// HandleBlock blocks a community 24// POST /xrpc/social.coves.community.blockCommunity 25// 26// Request body: { "community": "at-identifier" } 27// Accepts DIDs (did:plc:xxx), handles (@gaming.community.coves.social), or scoped (!gaming@coves.social) 28// The block record's "subject" field requires format: "did", so we resolve the identifier internally. 29func (h *BlockHandler) HandleBlock(w http.ResponseWriter, r *http.Request) { 30 if r.Method != http.MethodPost { 31 http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) 32 return 33 } 34 35 // Parse request body 36 var req struct { 37 Community string `json:"community"` // at-identifier (DID or handle) 38 } 39 40 if err := json.NewDecoder(r.Body).Decode(&req); err != nil { 41 writeError(w, http.StatusBadRequest, "InvalidRequest", "Invalid request body") 42 return 43 } 44 45 if req.Community == "" { 46 writeError(w, http.StatusBadRequest, "InvalidRequest", "community is required") 47 return 48 } 49 50 // Extract authenticated user DID and access token from request context (injected by auth middleware) 51 userDID := middleware.GetUserDID(r) 52 if userDID == "" { 53 writeError(w, http.StatusUnauthorized, "AuthRequired", "Authentication required") 54 return 55 } 56 57 userAccessToken := middleware.GetUserAccessToken(r) 58 if userAccessToken == "" { 59 writeError(w, http.StatusUnauthorized, "AuthRequired", "Missing access token") 60 return 61 } 62 63 // Resolve community identifier (handle or DID) to DID 64 // This allows users to block by handle: @gaming.community.coves.social or !gaming@coves.social 65 communityDID, err := h.service.ResolveCommunityIdentifier(r.Context(), req.Community) 66 if err != nil { 67 if communities.IsNotFound(err) { 68 writeError(w, http.StatusNotFound, "CommunityNotFound", "Community not found") 69 return 70 } 71 if communities.IsValidationError(err) { 72 writeError(w, http.StatusBadRequest, "InvalidRequest", err.Error()) 73 return 74 } 75 log.Printf("Failed to resolve community identifier %s: %v", req.Community, err) 76 writeError(w, http.StatusInternalServerError, "InternalError", "Failed to resolve community") 77 return 78 } 79 80 // Block via service (write-forward to PDS) using resolved DID 81 block, err := h.service.BlockCommunity(r.Context(), userDID, userAccessToken, communityDID) 82 if err != nil { 83 handleServiceError(w, err) 84 return 85 } 86 87 // Return success response (following atProto conventions for block responses) 88 response := map[string]interface{}{ 89 "block": map[string]interface{}{ 90 "recordUri": block.RecordURI, 91 "recordCid": block.RecordCID, 92 }, 93 } 94 95 w.Header().Set("Content-Type", "application/json") 96 w.WriteHeader(http.StatusOK) 97 if err := json.NewEncoder(w).Encode(response); err != nil { 98 log.Printf("Failed to encode response: %v", err) 99 } 100} 101 102// HandleUnblock unblocks a community 103// POST /xrpc/social.coves.community.unblockCommunity 104// 105// Request body: { "community": "at-identifier" } 106// Accepts DIDs (did:plc:xxx), handles (@gaming.community.coves.social), or scoped (!gaming@coves.social) 107func (h *BlockHandler) HandleUnblock(w http.ResponseWriter, r *http.Request) { 108 if r.Method != http.MethodPost { 109 http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) 110 return 111 } 112 113 // Parse request body 114 var req struct { 115 Community string `json:"community"` // at-identifier (DID or handle) 116 } 117 118 if err := json.NewDecoder(r.Body).Decode(&req); err != nil { 119 writeError(w, http.StatusBadRequest, "InvalidRequest", "Invalid request body") 120 return 121 } 122 123 if req.Community == "" { 124 writeError(w, http.StatusBadRequest, "InvalidRequest", "community is required") 125 return 126 } 127 128 // Extract authenticated user DID and access token from request context (injected by auth middleware) 129 userDID := middleware.GetUserDID(r) 130 if userDID == "" { 131 writeError(w, http.StatusUnauthorized, "AuthRequired", "Authentication required") 132 return 133 } 134 135 userAccessToken := middleware.GetUserAccessToken(r) 136 if userAccessToken == "" { 137 writeError(w, http.StatusUnauthorized, "AuthRequired", "Missing access token") 138 return 139 } 140 141 // Resolve community identifier (handle or DID) to DID 142 // This allows users to unblock by handle: @gaming.community.coves.social or !gaming@coves.social 143 communityDID, err := h.service.ResolveCommunityIdentifier(r.Context(), req.Community) 144 if err != nil { 145 if communities.IsNotFound(err) { 146 writeError(w, http.StatusNotFound, "CommunityNotFound", "Community not found") 147 return 148 } 149 if communities.IsValidationError(err) { 150 writeError(w, http.StatusBadRequest, "InvalidRequest", err.Error()) 151 return 152 } 153 log.Printf("Failed to resolve community identifier %s: %v", req.Community, err) 154 writeError(w, http.StatusInternalServerError, "InternalError", "Failed to resolve community") 155 return 156 } 157 158 // Unblock via service (delete record on PDS) using resolved DID 159 err = h.service.UnblockCommunity(r.Context(), userDID, userAccessToken, communityDID) 160 if err != nil { 161 handleServiceError(w, err) 162 return 163 } 164 165 // Return success response 166 w.Header().Set("Content-Type", "application/json") 167 w.WriteHeader(http.StatusOK) 168 if err := json.NewEncoder(w).Encode(map[string]interface{}{ 169 "success": true, 170 }); err != nil { 171 log.Printf("Failed to encode response: %v", err) 172 } 173}