Monorepo for Tangled
0

Configure Feed

Select the types of activity you want to include in your feed.

core / appview / pulls / lifecycle.go
5.8 kB 186 lines
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}