From d2c71bdb425b7c73111e276d8e7f55a2c1536aa5 Mon Sep 17 00:00:00 2001 From: Brittany Ellich Date: Mon, 1 Jun 2026 08:10:13 -0700 Subject: [PATCH] feat(connections): POST /connections/{did}/event endpoint Adds SetConnectionEvent handler and route to toggle whether a viewer's connection record with a target DID is tagged with their current event. The event URI is always re-derived server-side from checkin.Current; the client only sends {"associate": bool}. Co-Authored-By: Claude Opus 4.8 --- features/connections/handlers.go | 71 ++++++++++++++++++++++++++++++++ features/connections/routes.go | 2 + 2 files changed, 73 insertions(+) diff --git a/features/connections/handlers.go b/features/connections/handlers.go index ab9ab18..1fdb0b1 100644 --- a/features/connections/handlers.go +++ b/features/connections/handlers.go @@ -729,6 +729,77 @@ func (h *Handlers) SaveNotes(w http.ResponseWriter, r *http.Request) { _ = json.NewEncoder(w).Encode(map[string]string{"status": "ok"}) } +// SetConnectionEvent handles POST /connections/{did}/event — toggles whether +// the viewer's connection record with {did} is tagged with the event the +// viewer is currently checked into. Body: {"associate": bool}. +// +// The event is always re-derived server-side from checkin.Current; the client +// only sends the boolean. Updates the most-recent connection record in place. +func (h *Handlers) SetConnectionEvent(w http.ResponseWriter, r *http.Request) { + viewerDID, sess, ok := h.Auth.RequireSession(w, r) + if !ok { + return // RequireSession wrote the redirect/response + } + + targetStr, err := url.PathUnescape(chi.URLParam(r, "did")) + if err != nil { + http.Error(w, "invalid DID", http.StatusBadRequest) + return + } + target, err := syntax.ParseDID(targetStr) + if err != nil { + http.Error(w, "invalid DID", http.StatusBadRequest) + return + } + + var body struct { + Associate bool `json:"associate"` + } + if err := json.NewDecoder(http.MaxBytesReader(w, r.Body, 4*1024)).Decode(&body); err != nil { + http.Error(w, "invalid JSON", http.StatusBadRequest) + return + } + + // Re-derive the current event server-side. + eventURI := "" + if body.Associate { + evURI, checked, cErr := checkin.Current(r.Context(), h.DB, viewerDID) + if cErr != nil { + slog.Warn("set connection event: checkin.Current", "err", cErr) + http.Error(w, "failed to resolve current event", http.StatusInternalServerError) + return + } + if !checked || evURI == "" { + http.Error(w, "not checked into an ongoing event", http.StatusBadRequest) + return + } + eventURI = evURI + } + + // Find the most-recent connection record for this target. + viewerPDS := h.lookupPDSForDID(r, viewerDID) + entries, err := connection.List(r.Context(), viewerPDS, viewerDID) + if err != nil { + slog.Warn("set connection event: list connections", "err", err) + http.Error(w, "failed to load connection", http.StatusInternalServerError) + return + } + rec, found := connection.FindForTarget(entries, target) + if !found { + http.Error(w, "no connection record found", http.StatusNotFound) + return + } + + if err := connection.SetEvent(r.Context(), sess, rec.URI, rec.With, rec.ConnectedAt, eventURI); err != nil { + slog.Warn("set connection event: putRecord", "uri", rec.URI, "err", err) + http.Error(w, "failed to update record", http.StatusInternalServerError) + return + } + + w.Header().Set("Content-Type", "application/json") + _ = json.NewEncoder(w).Encode(map[string]string{"status": "ok"}) +} + // fetchLocalConnections fetches connections from the local_connections table // for the given viewer (ATProto DID or local ID). func (h *Handlers) fetchLocalConnections(ctx context.Context, viewerID string) ([]pages.ConnectionItem, error) { diff --git a/features/connections/routes.go b/features/connections/routes.go index b554279..0a5c045 100644 --- a/features/connections/routes.go +++ b/features/connections/routes.go @@ -13,10 +13,12 @@ import ( // - GET /connections — list of all connections // - GET /connections/{did} — other person's profile view // - POST /connections/{did}/notes — save private notes + follow-up flag +// - POST /connections/{did}/event — associate/clear the current event tag func SetupRoutes(router chi.Router, conn *sql.DB, authH *auth.Handlers) { h := NewHandlers(conn, authH) router.Get("/connections", h.List) router.Get("/connections/{did}", h.View) router.Post("/connections/{did}/notes", h.SaveNotes) + router.Post("/connections/{did}/event", h.SetConnectionEvent) } -- 2.51.2