forked from
tangled.org/core
Monorepo for Tangled
1package pulls
2
3import (
4 "fmt"
5 "net/http"
6
7 "tangled.org/core/appview/db"
8 "tangled.org/core/appview/models"
9 "tangled.org/core/appview/reporesolver"
10 "tangled.org/core/orm"
11
12 "github.com/bluesky-social/indigo/atproto/syntax"
13)
14
15func (s *Pulls) ClosePull(w http.ResponseWriter, r *http.Request) {
16 l := s.logger.With("handler", "ClosePull")
17
18 user := s.oauth.GetMultiAccountUser(r)
19 if user == nil {
20 l.Error("nil user")
21 s.pages.Notice(w, "pull-action-error", "You must be logged in to close this pull.")
22 return
23 }
24 l = l.With("user", user.Did)
25
26 f, err := s.repoResolver.Resolve(r)
27 if err != nil {
28 l.Error("failed to resolve repo", "err", err)
29 return
30 }
31
32 pull, ok := r.Context().Value("pull").(*models.Pull)
33 if !ok {
34 l.Error("failed to get pull")
35 s.pages.Notice(w, "pull-action-error", "Failed to close pull. Try again later.")
36 return
37 }
38 l = l.With("pull_id", pull.PullId, "pull_owner", pull.OwnerDid)
39
40 // auth filter: only owner or collaborators can close
41 roles := s.acl.RolesInRepo(r.Context(), f, user.Did)
42 isOwner := roles.IsOwner()
43 isCollaborator := roles.IsCollaborator()
44 isPullAuthor := user.Did == pull.OwnerDid
45 isCloseAllowed := isOwner || isCollaborator || isPullAuthor
46 if !isCloseAllowed {
47 l.Error("unauthorized to close pull", "is_owner", isOwner, "is_collaborator", isCollaborator, "is_pull_author", isPullAuthor)
48 s.pages.Notice(w, "pull-action-error", "You are unauthorized to close this pull.")
49 return
50 }
51
52 // if this PR is stacked, then we want to close all PRs above this one on the stack
53 stack := r.Context().Value("stack").(models.Stack)
54 pullsToClose := stack.Above(pull)
55 var atUris []syntax.ATURI
56 for _, p := range pullsToClose {
57 atUris = append(atUris, p.AtUri())
58 p.State = models.PullClosed
59 }
60
61 if err := s.writePullStatusRecords(r, user.Did, atUris, models.StateClosed); err != nil {
62 l.Error("failed to write pull status records", "err", err)
63 s.pages.Notice(w, "pull-action-error", "Failed to close pull. Try again later.")
64 return
65 }
66
67 tx, err := s.db.BeginTx(r.Context(), nil)
68 if err != nil {
69 l.Error("failed to start transaction", "err", err)
70 s.pages.Notice(w, "pull-action-error", "Failed to close pull.")
71 return
72 }
73 defer tx.Rollback()
74
75 err = db.ClosePulls(
76 tx,
77 orm.FilterEq("repo_did", string(f.RepoDid)),
78 orm.FilterIn("at_uri", atUris),
79 )
80 if err != nil {
81 l.Error("failed to close pulls in database", "err", err, "pulls_to_close", len(pullsToClose))
82 s.pages.Notice(w, "pull-action-error", "Failed to close pull.")
83 return
84 }
85
86 // Commit the transaction
87 if err = tx.Commit(); err != nil {
88 l.Error("failed to commit transaction", "err", err)
89 s.pages.Notice(w, "pull-action-error", "Failed to close pull.")
90 return
91 }
92
93 for _, p := range pullsToClose {
94 s.notifier.NewPullState(r.Context(), syntax.DID(user.Did), p)
95 }
96
97 ownerSlashRepo := reporesolver.GetBaseRepoPath(r, f)
98 s.pages.HxLocation(w, fmt.Sprintf("/%s/pulls/%d", ownerSlashRepo, pull.PullId))
99}
100
101func (s *Pulls) ReopenPull(w http.ResponseWriter, r *http.Request) {
102 l := s.logger.With("handler", "ReopenPull")
103
104 user := s.oauth.GetMultiAccountUser(r)
105 if user == nil {
106 l.Error("nil user")
107 s.pages.Notice(w, "pull-action-error", "You must be logged in to reopen this pull.")
108 return
109 }
110 l = l.With("user", user.Did)
111
112 f, err := s.repoResolver.Resolve(r)
113 if err != nil {
114 l.Error("failed to resolve repo", "err", err)
115 s.pages.Notice(w, "pull-action-error", "Failed to reopen pull.")
116 return
117 }
118
119 pull, ok := r.Context().Value("pull").(*models.Pull)
120 if !ok {
121 l.Error("failed to get pull")
122 s.pages.Notice(w, "pull-action-error", "Failed to reopen pull. Try again later.")
123 return
124 }
125 l = l.With("pull_id", pull.PullId, "pull_owner", pull.OwnerDid, "state", pull.State)
126
127 // auth filter: only owner or collaborators can close
128 roles := s.acl.RolesInRepo(r.Context(), f, user.Did)
129 isOwner := roles.IsOwner()
130 isCollaborator := roles.IsCollaborator()
131 isPullAuthor := user.Did == pull.OwnerDid
132 isCloseAllowed := isOwner || isCollaborator || isPullAuthor
133 if !isCloseAllowed {
134 l.Error("unauthorized to reopen pull", "is_owner", isOwner, "is_collaborator", isCollaborator, "is_pull_author", isPullAuthor)
135 s.pages.Notice(w, "pull-action-error", "You are unauthorized to reopen this pull.")
136 return
137 }
138
139 // if this PR is stacked, then we want to reopen all PRs above this one on the stack
140 stack := r.Context().Value("stack").(models.Stack)
141 pullsToReopen := stack.Below(pull)
142 var atUris []syntax.ATURI
143 for _, p := range pullsToReopen {
144 atUris = append(atUris, p.AtUri())
145 p.State = models.PullOpen
146 }
147
148 if err := s.writePullStatusRecords(r, user.Did, atUris, models.StateOpen); err != nil {
149 l.Error("failed to write pull status records", "err", err)
150 s.pages.Notice(w, "pull-action-error", "Failed to reopen pull. Try again later.")
151 return
152 }
153
154 tx, err := s.db.BeginTx(r.Context(), nil)
155 if err != nil {
156 l.Error("failed to start transaction", "err", err)
157 s.pages.Notice(w, "pull-action-error", "Failed to reopen pull.")
158 return
159 }
160 defer tx.Rollback()
161
162 err = db.ReopenPulls(
163 tx,
164 orm.FilterEq("repo_did", string(f.RepoDid)),
165 orm.FilterIn("at_uri", atUris),
166 )
167 if err != nil {
168 l.Error("failed to reopen pulls in database", "err", err, "pulls_to_reopen", len(pullsToReopen))
169 s.pages.Notice(w, "pull-action-error", "Failed to reopen pull.")
170 return
171 }
172
173 // Commit the transaction
174 if err = tx.Commit(); err != nil {
175 l.Error("failed to commit transaction", "err", err)
176 s.pages.Notice(w, "pull-action-error", "Failed to reopen pull.")
177 return
178 }
179
180 for _, p := range pullsToReopen {
181 s.notifier.NewPullState(r.Context(), syntax.DID(user.Did), p)
182 }
183
184 ownerSlashRepo := reporesolver.GetBaseRepoPath(r, f)
185 s.pages.HxLocation(w, fmt.Sprintf("/%s/pulls/%d", ownerSlashRepo, pull.PullId))
186}