From fdc4b46591656942c24e04ff89af345d5fac4aa3 Mon Sep 17 00:00:00 2001 From: Anirudh Oppiliappan Date: Fri, 03 Jul 2026 10:13:55 +0000 Subject: [PATCH] appview/issues, appview/pulls: add subscribe/unsubscribe endpoints --- appview/issues/issues.go | 59 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ appview/issues/router.go | 1 + appview/pulls/router.go | 1 + appview/pulls/single.go | 60 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 4 file(s) changed, 121 insertion(s)(+), 0 deletion(s)(-) diff --git a/appview/issues/issues.go b/appview/issues/issues.go --- a/appview/issues/issues.go +++ b/appview/issues/issues.go @@ -161,6 +161,31 @@ defs[l.AtUri().String()] = &l } + var isSubscribed *bool + if user != nil { + sub, found, err2 := db.GetIssueSubscription(rp.db, user.Did, issue.Id) + if err2 == nil { + if found { + isSubscribed = &sub + } else { + // Implicitly subscribed when you're the author or a participant. + isAuthorOrParticipant := issue.Did == user.Did + if !isAuthorOrParticipant { + for _, p := range issue.Participants() { + if p.String() == user.Did { + isAuthorOrParticipant = true + break + } + } + } + if isAuthorOrParticipant { + t := true + isSubscribed = &t + } + } + } + } + err = rp.pages.RepoSingleIssue(w, pages.RepoSingleIssueParams{ BaseParams: pages.BaseParamsFromContext(r.Context()), RepoInfo: rp.repoResolver.GetRepoInfo(r, user), @@ -171,10 +196,44 @@ UserReacted: userReactions, LabelDefs: defs, VouchRelationships: vouchRelationships, + IsSubscribed: isSubscribed, }) if err != nil { l.Error("failed to render issue", "err", err) } +} + +// SubscribeIssue handles subscribe/unsubscribe for a specific issue. +func (rp *Issues) SubscribeIssue(w http.ResponseWriter, r *http.Request) { + l := rp.logger.With("handler", "SubscribeIssue") + user := rp.oauth.GetMultiAccountUser(r) + if user == nil { + w.WriteHeader(http.StatusUnauthorized) + return + } + + issue, ok := r.Context().Value("issue").(*models.Issue) + if !ok { + l.Error("failed to get issue from context") + w.WriteHeader(http.StatusNotFound) + return + } + + subscribe := r.FormValue("subscribe") != "false" + + if err := db.UpsertIssueSubscription(rp.db, user.Did, issue.Id, subscribe); err != nil { + l.Error("failed to update issue subscription", "err", err) + w.WriteHeader(http.StatusInternalServerError) + return + } + + // Return the updated subscription button fragment. + repoInfo := rp.repoResolver.GetRepoInfo(r, user) + rp.pages.IssueSubscribeFragment(w, pages.IssueSubscribeParams{ + RepoInfo: repoInfo, + IssueId: issue.IssueId, + IsSubscribed: &subscribe, + }) } func (rp *Issues) EditIssue(w http.ResponseWriter, r *http.Request) { diff --git a/appview/issues/router.go b/appview/issues/router.go --- a/appview/issues/router.go +++ b/appview/issues/router.go @@ -26,6 +26,7 @@ r.Delete("/", i.DeleteIssue) r.Post("/close", i.CloseIssue) r.Post("/reopen", i.ReopenIssue) + r.Post("/subscribe", i.SubscribeIssue) }) }) diff --git a/appview/pulls/router.go b/appview/pulls/router.go --- a/appview/pulls/router.go +++ b/appview/pulls/router.go @@ -44,6 +44,7 @@ // it is handled within the route r.Post("/close", s.ClosePull) r.Post("/reopen", s.ReopenPull) + r.Post("/subscribe", s.SubscribePull) // collaborators only r.Group(func(r chi.Router) { r.Use(mw.RepoPermissionMiddleware("repo:push")) diff --git a/appview/pulls/single.go b/appview/pulls/single.go --- a/appview/pulls/single.go +++ b/appview/pulls/single.go @@ -264,6 +264,32 @@ diff = s.combinedDiff(pull, roundIdInt) } + var isSubscribed *bool + if user != nil { + pullDbId := int64(pull.ID) + sub, found, err2 := db.GetPullSubscription(s.db, user.Did, pullDbId) + if err2 == nil { + if found { + isSubscribed = &sub + } else { + // Implicitly subscribed if author or participant. + isAuthorOrParticipant := pull.OwnerDid == user.Did + if !isAuthorOrParticipant { + for _, p := range pull.Participants() { + if p.String() == user.Did { + isAuthorOrParticipant = true + break + } + } + } + if isAuthorOrParticipant { + t := true + isSubscribed = &t + } + } + } + } + err = s.pages.RepoSinglePull(w, pages.RepoSinglePullParams{ BaseParams: pages.BaseParamsFromContext(r.Context()), RepoInfo: s.repoResolver.GetRepoInfo(r, user), @@ -285,10 +311,44 @@ LabelDefs: defs, VouchRelationships: vouchRelationships, VouchSkips: vouchSkips, + IsSubscribed: isSubscribed, }) if err != nil { l.Error("failed to render page", "err", err) } +} + +// SubscribePull handles subscribe/unsubscribe for a specific pull request. +func (s *Pulls) SubscribePull(w http.ResponseWriter, r *http.Request) { + l := s.logger.With("handler", "SubscribePull") + user := s.oauth.GetMultiAccountUser(r) + if user == nil { + w.WriteHeader(http.StatusUnauthorized) + return + } + + pull, ok := r.Context().Value("pull").(*models.Pull) + if !ok { + l.Error("failed to get pull from context") + w.WriteHeader(http.StatusNotFound) + return + } + + subscribe := r.FormValue("subscribe") != "false" + pullDbId := int64(pull.ID) + + if err := db.UpsertPullSubscription(s.db, user.Did, pullDbId, subscribe); err != nil { + l.Error("failed to update pull subscription", "err", err) + w.WriteHeader(http.StatusInternalServerError) + return + } + + repoInfo := s.repoResolver.GetRepoInfo(r, user) + s.pages.PullSubscribeFragment(w, pages.PullSubscribeParams{ + RepoInfo: repoInfo, + PullId: pull.PullId, + IsSubscribed: &subscribe, + }) } func (s *Pulls) combinedDiff(pull *models.Pull, round int) types.DiffRenderer { -- tangled.sh