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