Margin is an open annotation layer for the internet. Powered by the AT Protocol. margin.at
extension web atproto comments
at main 165 lines 5.0 kB view raw
1package api 2 3import ( 4 "context" 5 "encoding/json" 6 "fmt" 7 "net/http" 8 "time" 9 10 "margin.at/internal/config" 11 "margin.at/internal/db" 12 "margin.at/internal/xrpc" 13) 14 15type LabelerSubscription struct { 16 DID string `json:"did"` 17} 18 19type LabelPreference struct { 20 LabelerDID string `json:"labelerDid"` 21 Label string `json:"label"` 22 Visibility string `json:"visibility"` 23} 24 25type PreferencesResponse struct { 26 ExternalLinkSkippedHostnames []string `json:"externalLinkSkippedHostnames"` 27 SubscribedLabelers []LabelerSubscription `json:"subscribedLabelers"` 28 LabelPreferences []LabelPreference `json:"labelPreferences"` 29 DisableExternalLinkWarning bool `json:"disableExternalLinkWarning"` 30} 31 32func (h *Handler) GetPreferences(w http.ResponseWriter, r *http.Request) { 33 session, err := h.refresher.GetSessionWithAutoRefresh(r) 34 if err != nil { 35 http.Error(w, "Unauthorized", http.StatusUnauthorized) 36 return 37 } 38 39 prefs, err := h.db.GetPreferences(session.DID) 40 if err != nil { 41 http.Error(w, "Failed to fetch preferences", http.StatusInternalServerError) 42 return 43 } 44 45 hostnames := []string{} 46 if prefs != nil && prefs.ExternalLinkSkippedHostnames != nil { 47 json.Unmarshal([]byte(*prefs.ExternalLinkSkippedHostnames), &hostnames) 48 } 49 50 var labelers []LabelerSubscription 51 if prefs != nil && prefs.SubscribedLabelers != nil { 52 json.Unmarshal([]byte(*prefs.SubscribedLabelers), &labelers) 53 } 54 if labelers == nil { 55 labelers = []LabelerSubscription{} 56 serviceDID := config.Get().ServiceDID 57 if serviceDID != "" { 58 labelers = append(labelers, LabelerSubscription{DID: serviceDID}) 59 } 60 } 61 62 var labelPrefs []LabelPreference 63 if prefs != nil && prefs.LabelPreferences != nil { 64 json.Unmarshal([]byte(*prefs.LabelPreferences), &labelPrefs) 65 } 66 if labelPrefs == nil { 67 labelPrefs = []LabelPreference{} 68 } 69 70 disableWarning := false 71 if prefs != nil && prefs.DisableExternalLinkWarning != nil { 72 disableWarning = *prefs.DisableExternalLinkWarning 73 } 74 75 w.Header().Set("Content-Type", "application/json") 76 json.NewEncoder(w).Encode(PreferencesResponse{ 77 ExternalLinkSkippedHostnames: hostnames, 78 SubscribedLabelers: labelers, 79 LabelPreferences: labelPrefs, 80 DisableExternalLinkWarning: disableWarning, 81 }) 82} 83 84func (h *Handler) UpdatePreferences(w http.ResponseWriter, r *http.Request) { 85 session, err := h.refresher.GetSessionWithAutoRefresh(r) 86 if err != nil { 87 http.Error(w, "Unauthorized", http.StatusUnauthorized) 88 return 89 } 90 91 var input PreferencesResponse 92 if err := json.NewDecoder(r.Body).Decode(&input); err != nil { 93 http.Error(w, "Invalid input", http.StatusBadRequest) 94 return 95 } 96 97 var xrpcLabelers []xrpc.LabelerSubscription 98 for _, l := range input.SubscribedLabelers { 99 xrpcLabelers = append(xrpcLabelers, xrpc.LabelerSubscription{ 100 Type: "at.margin.preferences#labelerSubscription", 101 DID: l.DID, 102 }) 103 } 104 var xrpcLabelPrefs []xrpc.LabelPreference 105 for _, lp := range input.LabelPreferences { 106 xrpcLabelPrefs = append(xrpcLabelPrefs, xrpc.LabelPreference{ 107 Type: "at.margin.preferences#labelPreference", 108 LabelerDID: lp.LabelerDID, 109 Label: lp.Label, 110 Visibility: lp.Visibility, 111 }) 112 } 113 114 record := xrpc.NewPreferencesRecord(input.ExternalLinkSkippedHostnames, xrpcLabelers, xrpcLabelPrefs, &input.DisableExternalLinkWarning) 115 if err := record.Validate(); err != nil { 116 http.Error(w, fmt.Sprintf("Invalid record: %v", err), http.StatusBadRequest) 117 return 118 } 119 120 err = h.refresher.ExecuteWithAutoRefresh(r, session, func(client *xrpc.Client, did string) error { 121 ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) 122 defer cancel() 123 _, err := client.PutRecord(ctx, did, xrpc.CollectionPreferences, "self", record) 124 return err 125 }) 126 127 if err != nil { 128 fmt.Printf("[UpdatePreferences] PDS write failed: %v\n", err) 129 } 130 131 createdAt, _ := time.Parse(time.RFC3339, record.CreatedAt) 132 hostnamesJSON, _ := json.Marshal(input.ExternalLinkSkippedHostnames) 133 hostnamesStr := string(hostnamesJSON) 134 135 var subscribedLabelersPtr, labelPrefsPtr *string 136 if len(input.SubscribedLabelers) > 0 { 137 labelersJSON, _ := json.Marshal(input.SubscribedLabelers) 138 s := string(labelersJSON) 139 subscribedLabelersPtr = &s 140 } 141 if len(input.LabelPreferences) > 0 { 142 prefsJSON, _ := json.Marshal(input.LabelPreferences) 143 s := string(prefsJSON) 144 labelPrefsPtr = &s 145 } 146 147 uri := fmt.Sprintf("at://%s/%s/self", session.DID, xrpc.CollectionPreferences) 148 149 err = h.db.UpsertPreferences(&db.Preferences{ 150 URI: uri, 151 AuthorDID: session.DID, 152 ExternalLinkSkippedHostnames: &hostnamesStr, 153 SubscribedLabelers: subscribedLabelersPtr, 154 LabelPreferences: labelPrefsPtr, 155 DisableExternalLinkWarning: &input.DisableExternalLinkWarning, 156 CreatedAt: createdAt, 157 IndexedAt: time.Now(), 158 }) 159 160 if err != nil { 161 fmt.Printf("Failed to update local db preferences: %v\n", err) 162 } 163 164 w.WriteHeader(http.StatusOK) 165}