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