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