diff --git a/app/api/controller/pullreq/pr_find.go b/app/api/controller/pullreq/pr_find.go index be885ccc1..cbd048e8b 100644 --- a/app/api/controller/pullreq/pr_find.go +++ b/app/api/controller/pullreq/pr_find.go @@ -58,3 +58,50 @@ func (c *Controller) Find( return pr, nil } + +// Find returns a pull request from the provided repository. +func (c *Controller) FindByBranches( + ctx context.Context, + session *auth.Session, + repoRef, + sourceRepoRef, + sourceBranch, + targetBranch string, +) (*types.PullReq, error) { + if sourceBranch == "" || targetBranch == "" { + return nil, usererror.BadRequest("A valid source/target branch must be provided.") + } + + targetRepo, err := c.getRepoCheckAccess(ctx, session, repoRef, enum.PermissionRepoView) + if err != nil { + return nil, fmt.Errorf("failed to acquire access to the repo: %w", err) + } + + sourceRepo := targetRepo + if sourceRepoRef != repoRef { + sourceRepo, err = c.getRepoCheckAccess(ctx, session, sourceRepoRef, enum.PermissionRepoPush) + if err != nil { + return nil, fmt.Errorf("failed to acquire access to source repo: %w", err) + } + } + + prs, err := c.pullreqStore.List(ctx, &types.PullReqFilter{ + SourceRepoID: sourceRepo.ID, + SourceBranch: sourceBranch, + TargetRepoID: targetRepo.ID, + TargetBranch: targetBranch, + States: []enum.PullReqState{enum.PullReqStateOpen}, + Size: 1, + Sort: enum.PullReqSortNumber, + Order: enum.OrderAsc, + }) + if err != nil { + return nil, fmt.Errorf("failed to fetch existing pull request: %w", err) + } + + if len(prs) == 0 { + return nil, usererror.ErrNotFound + } + + return prs[0], nil +} diff --git a/app/api/handler/pullreq/pr_find.go b/app/api/handler/pullreq/pr_find.go index 437443f75..84ac37163 100644 --- a/app/api/handler/pullreq/pr_find.go +++ b/app/api/handler/pullreq/pr_find.go @@ -49,3 +49,39 @@ func HandleFind(pullreqCtrl *pullreq.Controller) http.HandlerFunc { render.JSON(w, http.StatusOK, pr) } } + +// HandleFind returns a http.HandlerFunc that finds a pull request. +func HandleFindByBranches(pullreqCtrl *pullreq.Controller) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + session, _ := request.AuthSessionFrom(ctx) + + repoRef, err := request.GetRepoRefFromPath(r) + if err != nil { + render.TranslatedUserError(ctx, w, err) + return + } + + sourceRepoRef := request.GetSourceRepoRefFromQueryOrDefault(r, repoRef) + + sourceBranch, err := request.GetPullReqSourceBranchFromPath(r) + if err != nil { + render.TranslatedUserError(ctx, w, err) + return + } + + targetBranch, err := request.GetPullReqTargetBranchFromPath(r) + if err != nil { + render.TranslatedUserError(ctx, w, err) + return + } + + pr, err := pullreqCtrl.FindByBranches(ctx, session, repoRef, sourceRepoRef, sourceBranch, targetBranch) + if err != nil { + render.TranslatedUserError(ctx, w, err) + return + } + + render.JSON(w, http.StatusOK, pr) + } +} diff --git a/app/api/openapi/pullreq.go b/app/api/openapi/pullreq.go index 6d7ee5993..fe6d58b9d 100644 --- a/app/api/openapi/pullreq.go +++ b/app/api/openapi/pullreq.go @@ -47,6 +47,12 @@ type getPullReqRequest struct { pullReqRequest } +type getPullReqByBranchesRequest struct { + repoRequest + SourceBranch string `path:"source_branch"` + TargetBranch string `path:"target_branch"` +} + type updatePullReqRequest struct { pullReqRequest pullreq.UpdateInput @@ -163,7 +169,7 @@ var queryParameterQueryPullRequest = openapi3.ParameterOrRef{ var queryParameterSourceRepoRefPullRequest = openapi3.ParameterOrRef{ Parameter: &openapi3.Parameter{ - Name: "source_repo_ref", + Name: request.QueryParamSourceRepoRef, In: openapi3.ParameterInQuery, Description: ptr.String("Source repository ref of the pull requests."), Required: ptr.Bool(false), @@ -518,6 +524,20 @@ func pullReqOperations(reflector *openapi3.Reflector) { _ = reflector.SetJSONResponse(&getPullReq, new(usererror.Error), http.StatusForbidden) _ = reflector.Spec.AddOperation(http.MethodGet, "/repos/{repo_ref}/pullreq/{pullreq_number}", getPullReq) + getPullReqByBranches := openapi3.Operation{} + getPullReqByBranches.WithTags("pullreq") + getPullReqByBranches.WithMapOfAnything(map[string]interface{}{"operationId": "getPullReqByBranches"}) + getPullReqByBranches.WithParameters(queryParameterSourceRepoRefPullRequest) + _ = reflector.SetRequest(&getPullReqByBranches, new(getPullReqByBranchesRequest), http.MethodGet) + _ = reflector.SetJSONResponse(&getPullReqByBranches, new(types.PullReq), http.StatusOK) + _ = reflector.SetJSONResponse(&getPullReqByBranches, new(usererror.Error), http.StatusBadRequest) + _ = reflector.SetJSONResponse(&getPullReqByBranches, new(usererror.Error), http.StatusUnauthorized) + _ = reflector.SetJSONResponse(&getPullReqByBranches, new(usererror.Error), http.StatusForbidden) + _ = reflector.SetJSONResponse(&getPullReqByBranches, new(usererror.Error), http.StatusNotFound) + _ = reflector.SetJSONResponse(&getPullReqByBranches, new(usererror.Error), http.StatusInternalServerError) + _ = reflector.Spec.AddOperation(http.MethodGet, + "/repos/{repo_ref}/pullreq/{target_branch}...{source_branch}", getPullReqByBranches) + putPullReq := openapi3.Operation{} putPullReq.WithTags("pullreq") putPullReq.WithMapOfAnything(map[string]interface{}{"operationId": "updatePullReq"}) diff --git a/app/api/request/pullreq.go b/app/api/request/pullreq.go index 9ed670d30..fd61fda1d 100644 --- a/app/api/request/pullreq.go +++ b/app/api/request/pullreq.go @@ -28,6 +28,8 @@ const ( PathParamPullReqCommentID = "pullreq_comment_id" PathParamReviewerID = "pullreq_reviewer_id" PathParamUserGroupID = "user_group_id" + PathParamSourceBranch = "source_branch" + PathParamTargetBranch = "target_branch" QueryParamAuthorID = "author_id" QueryParamCommenterID = "commenter_id" @@ -35,6 +37,7 @@ const ( QueryParamReviewDecision = "review_decision" QueryParamMentionedID = "mentioned_id" QueryParamExcludeDescription = "exclude_description" + QueryParamSourceRepoRef = "source_repo_ref" ) func GetPullReqNumberFromPath(r *http.Request) (int64, error) { @@ -52,6 +55,18 @@ func GetPullReqCommentIDPath(r *http.Request) (int64, error) { return PathParamAsPositiveInt64(r, PathParamPullReqCommentID) } +func GetPullReqSourceBranchFromPath(r *http.Request) (string, error) { + return PathParamOrError(r, PathParamSourceBranch) +} + +func GetPullReqTargetBranchFromPath(r *http.Request) (string, error) { + return PathParamOrError(r, PathParamTargetBranch) +} + +func GetSourceRepoRefFromQueryOrDefault(r *http.Request, deflt string) string { + return QueryParamOrDefault(r, QueryParamSourceRepoRef, deflt) +} + // ParseSortPullReq extracts the pull request sort parameter from the url. func ParseSortPullReq(r *http.Request) enum.PullReqSort { result, _ := enum.PullReqSort(r.URL.Query().Get(QueryParamSort)).Sanitize() diff --git a/app/router/api.go b/app/router/api.go index fa7779d44..41bf3b247 100644 --- a/app/router/api.go +++ b/app/router/api.go @@ -638,6 +638,10 @@ func SetupPullReq(r chi.Router, pullreqCtrl *pullreq.Controller) { r.Route("/pullreq", func(r chi.Router) { r.Post("/", handlerpullreq.HandleCreate(pullreqCtrl)) r.Get("/", handlerpullreq.HandleList(pullreqCtrl)) + r.Get( + fmt.Sprintf("/{%s}...{%s}", request.PathParamTargetBranch, request.PathParamSourceBranch), + handlerpullreq.HandleFindByBranches(pullreqCtrl), + ) r.Route(fmt.Sprintf("/{%s}", request.PathParamPullReqNumber), func(r chi.Router) { r.Get("/", handlerpullreq.HandleFind(pullreqCtrl))