A community based topic aggregation platform built on atproto
1package community
2
3import (
4 "Coves/internal/core/communities"
5 "encoding/json"
6 "log"
7 "net/http"
8)
9
10// SubscribeHandler handles community subscriptions
11type SubscribeHandler struct {
12 service communities.Service
13}
14
15// NewSubscribeHandler creates a new subscribe handler
16func NewSubscribeHandler(service communities.Service) *SubscribeHandler {
17 return &SubscribeHandler{
18 service: service,
19 }
20}
21
22// HandleSubscribe subscribes a user to a community
23// POST /xrpc/social.coves.community.subscribe
24// Body: { "community": "did:plc:xxx" or "!gaming@coves.social" }
25func (h *SubscribeHandler) HandleSubscribe(w http.ResponseWriter, r *http.Request) {
26 if r.Method != http.MethodPost {
27 http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
28 return
29 }
30
31 // Parse request body
32 var req struct {
33 Community string `json:"community"`
34 }
35
36 if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
37 writeError(w, http.StatusBadRequest, "InvalidRequest", "Invalid request body")
38 return
39 }
40
41 if req.Community == "" {
42 writeError(w, http.StatusBadRequest, "InvalidRequest", "community is required")
43 return
44 }
45
46 // TODO(Communities-OAuth): Extract authenticated user DID from request context
47 // This MUST be replaced with OAuth middleware before production deployment
48 // Expected implementation:
49 // userDID := r.Context().Value("authenticated_user_did").(string)
50 // For now, we read from header (INSECURE - allows impersonation)
51 userDID := r.Header.Get("X-User-DID")
52 if userDID == "" {
53 writeError(w, http.StatusUnauthorized, "AuthRequired", "Authentication required")
54 return
55 }
56
57 // Subscribe via service (write-forward to PDS)
58 subscription, err := h.service.SubscribeToCommunity(r.Context(), userDID, req.Community)
59 if err != nil {
60 handleServiceError(w, err)
61 return
62 }
63
64 // Return success response
65 response := map[string]interface{}{
66 "uri": subscription.RecordURI,
67 "cid": subscription.RecordCID,
68 "existing": false, // Would be true if already subscribed
69 }
70
71 w.Header().Set("Content-Type", "application/json")
72 w.WriteHeader(http.StatusOK)
73 if err := json.NewEncoder(w).Encode(response); err != nil {
74 log.Printf("Failed to encode response: %v", err)
75 }
76}
77
78// HandleUnsubscribe unsubscribes a user from a community
79// POST /xrpc/social.coves.community.unsubscribe
80// Body: { "community": "did:plc:xxx" or "!gaming@coves.social" }
81func (h *SubscribeHandler) HandleUnsubscribe(w http.ResponseWriter, r *http.Request) {
82 if r.Method != http.MethodPost {
83 http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
84 return
85 }
86
87 // Parse request body
88 var req struct {
89 Community string `json:"community"`
90 }
91
92 if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
93 writeError(w, http.StatusBadRequest, "InvalidRequest", "Invalid request body")
94 return
95 }
96
97 if req.Community == "" {
98 writeError(w, http.StatusBadRequest, "InvalidRequest", "community is required")
99 return
100 }
101
102 // TODO(Communities-OAuth): Extract authenticated user DID from request context
103 // This MUST be replaced with OAuth middleware before production deployment
104 // Expected implementation:
105 // userDID := r.Context().Value("authenticated_user_did").(string)
106 // For now, we read from header (INSECURE - allows impersonation)
107 userDID := r.Header.Get("X-User-DID")
108 if userDID == "" {
109 writeError(w, http.StatusUnauthorized, "AuthRequired", "Authentication required")
110 return
111 }
112
113 // Unsubscribe via service (delete record on PDS)
114 err := h.service.UnsubscribeFromCommunity(r.Context(), userDID, req.Community)
115 if err != nil {
116 handleServiceError(w, err)
117 return
118 }
119
120 // Return success response
121 w.Header().Set("Content-Type", "application/json")
122 w.WriteHeader(http.StatusOK)
123 if err := json.NewEncoder(w).Encode(map[string]interface{}{
124 "success": true,
125 }); err != nil {
126 log.Printf("Failed to encode response: %v", err)
127 }
128}