diff --git a/routers/api/v1/repo/issue.go b/routers/api/v1/repo/issue.go index c3d9dfac1d8..a8a6bb1ddf2 100644 --- a/routers/api/v1/repo/issue.go +++ b/routers/api/v1/repo/issue.go @@ -22,7 +22,6 @@ import ( "gitea.dev/modules/optional" "gitea.dev/modules/setting" api "gitea.dev/modules/structs" - "gitea.dev/modules/timeutil" "gitea.dev/modules/util" "gitea.dev/modules/web" "gitea.dev/routers/api/v1/utils" @@ -568,25 +567,20 @@ func CreateIssue(ctx *context.APIContext) { // "$ref": "#/responses/repoArchivedError" form := web.GetForm[*api.CreateIssueOption](ctx) - var deadlineUnix timeutil.TimeStamp - if form.Deadline != nil && ctx.Repo.Permission.CanWrite(unit.TypeIssues) { - deadlineUnix = timeutil.TimeStamp(form.Deadline.Unix()) - } - issue := &issues_model.Issue{ - RepoID: ctx.Repo.Repository.ID, - Repo: ctx.Repo.Repository, - Title: form.Title, - PosterID: ctx.Doer.ID, - Poster: ctx.Doer, - Content: form.Body, - Ref: form.Ref, - DeadlineUnix: deadlineUnix, + RepoID: ctx.Repo.Repository.ID, + Repo: ctx.Repo.Repository, + Title: form.Title, + PosterID: ctx.Doer.ID, + Poster: ctx.Doer, + Content: form.Body, + Ref: form.Ref, } assigneeIDs := make([]int64, 0) var err error if ctx.Repo.Permission.CanWrite(unit.TypeIssues) { + issue.DeadlineUnix = common.ParseAPIDeadlineToEndOfDay(form.Deadline) issue.MilestoneID = form.Milestone assigneeIDs, err = issues_model.MakeIDsFromAPIAssigneesToAdd(ctx, form.Assignee, form.Assignees) if err != nil { @@ -760,26 +754,8 @@ func EditIssue(ctx *context.APIContext) { } // Update or remove the deadline, only if set and allowed - if (form.Deadline != nil || form.RemoveDeadline != nil) && canWrite { - var deadlineUnix timeutil.TimeStamp - - if form.RemoveDeadline == nil || !*form.RemoveDeadline { - if form.Deadline == nil { - ctx.APIError(http.StatusBadRequest, "The due_date cannot be empty") - return - } - if !form.Deadline.IsZero() { - deadline := time.Date(form.Deadline.Year(), form.Deadline.Month(), form.Deadline.Day(), - 23, 59, 59, 0, form.Deadline.Location()) - deadlineUnix = timeutil.TimeStamp(deadline.Unix()) - } - } - - if err := issues_model.UpdateIssueDeadline(ctx, issue, deadlineUnix, ctx.Doer); err != nil { - ctx.APIErrorInternal(err) - return - } - issue.DeadlineUnix = deadlineUnix + if canWrite && !editIssueDeadline(ctx, issue, form.Deadline, form.RemoveDeadline) { + return } // Add/delete assignees @@ -911,6 +887,25 @@ func DeleteIssue(ctx *context.APIContext) { ctx.Status(http.StatusNoContent) } +func editIssueDeadline(ctx *context.APIContext, issue *issues_model.Issue, deadline *time.Time, removeDeadline *bool) bool { + if deadline == nil && removeDeadline == nil { + return true + } + if removeDeadline != nil && *removeDeadline { + deadline = nil + } else if deadline == nil { + ctx.APIError(http.StatusBadRequest, "The due_date cannot be empty") + return false + } + deadlineUnix := common.ParseAPIDeadlineToEndOfDay(deadline) + if err := issues_model.UpdateIssueDeadline(ctx, issue, deadlineUnix, ctx.Doer); err != nil { + ctx.APIErrorInternal(err) + return false + } + issue.DeadlineUnix = deadlineUnix + return true +} + // UpdateIssueDeadline updates an issue deadline func UpdateIssueDeadline(ctx *context.APIContext) { // swagger:operation POST /repos/{owner}/{repo}/issues/{index}/deadline issue issueEditIssueDeadline @@ -964,7 +959,7 @@ func UpdateIssueDeadline(ctx *context.APIContext) { return } - deadlineUnix, _ := common.ParseAPIDeadlineToEndOfDay(form.Deadline) + deadlineUnix := common.ParseAPIDeadlineToEndOfDay(form.Deadline) if err := issues_model.UpdateIssueDeadline(ctx, issue, deadlineUnix, ctx.Doer); err != nil { ctx.APIErrorInternal(err) return diff --git a/routers/api/v1/repo/milestone.go b/routers/api/v1/repo/milestone.go index 2b19d43b889..41a7361e3ca 100644 --- a/routers/api/v1/repo/milestone.go +++ b/routers/api/v1/repo/milestone.go @@ -149,16 +149,11 @@ func CreateMilestone(ctx *context.APIContext) { // "$ref": "#/responses/notFound" form := web.GetForm[*api.CreateMilestoneOption](ctx) - var deadlineUnix int64 - if form.Deadline != nil { - deadlineUnix = form.Deadline.Unix() - } - milestone := &issues_model.Milestone{ RepoID: ctx.Repo.Repository.ID, Name: form.Title, Content: form.Description, - DeadlineUnix: timeutil.TimeStamp(deadlineUnix), + DeadlineUnix: common.ParseAPIDeadlineToEndOfDay(form.Deadline), } if form.State == "closed" { @@ -219,7 +214,7 @@ func EditMilestone(ctx *context.APIContext) { if form.Description != nil { milestone.Content = *form.Description } - milestone.DeadlineUnix, _ = common.ParseAPIDeadlineToEndOfDay(form.Deadline) + milestone.DeadlineUnix = common.ParseAPIDeadlineToEndOfDay(form.Deadline) oldIsClosed := milestone.IsClosed if form.State != nil { diff --git a/routers/api/v1/repo/pull.go b/routers/api/v1/repo/pull.go index 0063287f77f..4cda175700d 100644 --- a/routers/api/v1/repo/pull.go +++ b/routers/api/v1/repo/pull.go @@ -10,7 +10,6 @@ import ( "net/http" "strconv" "strings" - "time" git_model "gitea.dev/models/git" issues_model "gitea.dev/models/issues" @@ -26,7 +25,6 @@ import ( "gitea.dev/modules/optional" "gitea.dev/modules/setting" api "gitea.dev/modules/structs" - "gitea.dev/modules/timeutil" "gitea.dev/modules/util" "gitea.dev/modules/web" "gitea.dev/routers/api/v1/utils" @@ -491,11 +489,6 @@ func CreatePullRequest(ctx *context.APIContext) { milestoneID = milestone.ID } - var deadlineUnix timeutil.TimeStamp - if form.Deadline != nil { - deadlineUnix = timeutil.TimeStamp(form.Deadline.Unix()) - } - unitPullRequest, err := ctx.Repo.Repository.GetUnit(ctx, unit.TypePullRequests) if err != nil { ctx.APIErrorInternal(err) @@ -510,7 +503,7 @@ func CreatePullRequest(ctx *context.APIContext) { MilestoneID: milestoneID, IsPull: true, Content: form.Body, - DeadlineUnix: deadlineUnix, + DeadlineUnix: common.ParseAPIDeadlineToEndOfDay(form.Deadline), } pr := &issues_model.PullRequest{ HeadRepoID: compareResult.HeadRepo.ID, @@ -616,6 +609,8 @@ func EditPullRequest(ctx *context.APIContext) { // responses: // "201": // "$ref": "#/responses/PullRequest" + // "400": + // "$ref": "#/responses/error" // "403": // "$ref": "#/responses/forbidden" // "404": @@ -690,19 +685,8 @@ func EditPullRequest(ctx *context.APIContext) { } // Update or remove deadline if set - if form.Deadline != nil || form.RemoveDeadline != nil { - var deadlineUnix timeutil.TimeStamp - if (form.RemoveDeadline == nil || !*form.RemoveDeadline) && !form.Deadline.IsZero() { - deadline := time.Date(form.Deadline.Year(), form.Deadline.Month(), form.Deadline.Day(), - 23, 59, 59, 0, form.Deadline.Location()) - deadlineUnix = timeutil.TimeStamp(deadline.Unix()) - } - - if err := issues_model.UpdateIssueDeadline(ctx, issue, deadlineUnix, ctx.Doer); err != nil { - ctx.APIErrorInternal(err) - return - } - issue.DeadlineUnix = deadlineUnix + if !editIssueDeadline(ctx, issue, form.Deadline, form.RemoveDeadline) { + return } // Add/delete assignees diff --git a/routers/common/deadline.go b/routers/common/deadline.go index b8a1449a687..43b30462835 100644 --- a/routers/common/deadline.go +++ b/routers/common/deadline.go @@ -22,10 +22,9 @@ func ParseDeadlineDateToEndOfDay(date string) (timeutil.TimeStamp, error) { return timeutil.TimeStamp(deadline.Unix()), nil } -func ParseAPIDeadlineToEndOfDay(t *time.Time) (timeutil.TimeStamp, error) { +func ParseAPIDeadlineToEndOfDay(t *time.Time) timeutil.TimeStamp { if t == nil || t.IsZero() || t.Unix() == 0 { - return 0, nil + return 0 } - deadline := time.Date(t.Year(), t.Month(), t.Day(), 23, 59, 59, 0, setting.DefaultUILocation) - return timeutil.TimeStamp(deadline.Unix()), nil + return timeutil.TimeStamp(time.Date(t.Year(), t.Month(), t.Day(), 23, 59, 59, 0, setting.DefaultUILocation).Unix()) } diff --git a/templates/swagger/v1-openapi3.generated.json b/templates/swagger/v1-openapi3.generated.json index b92a5a134c6..46fcfccaeab 100644 --- a/templates/swagger/v1-openapi3.generated.json +++ b/templates/swagger/v1-openapi3.generated.json @@ -29212,6 +29212,9 @@ "201": { "$ref": "#/components/responses/PullRequest" }, + "400": { + "$ref": "#/components/responses/error" + }, "403": { "$ref": "#/components/responses/forbidden" }, diff --git a/templates/swagger/v1-swagger.generated.json b/templates/swagger/v1-swagger.generated.json index 7ef18edf193..7ffc1ebd418 100644 --- a/templates/swagger/v1-swagger.generated.json +++ b/templates/swagger/v1-swagger.generated.json @@ -16853,6 +16853,9 @@ "201": { "$ref": "#/responses/PullRequest" }, + "400": { + "$ref": "#/responses/error" + }, "403": { "$ref": "#/responses/forbidden" }, diff --git a/tests/integration/api_issue_test.go b/tests/integration/api_issue_test.go index b77775d1730..69c5c412cfe 100644 --- a/tests/integration/api_issue_test.go +++ b/tests/integration/api_issue_test.go @@ -126,11 +126,13 @@ func testAPICreateIssue(t *testing.T) { Body: body, Title: title, Assignee: owner.Name, + Deadline: new(time.Date(2026, 5, 11, 12, 0, 0, 0, time.FixedZone("", -10*3600))), }).AddTokenAuth(token) resp := MakeRequest(t, req, http.StatusCreated) apiIssue := DecodeJSON(t, resp, &api.Issue{}) assert.Equal(t, body, apiIssue.Body) assert.Equal(t, title, apiIssue.Title) + assert.Equal(t, time.Date(2026, 5, 11, 23, 59, 59, 0, setting.DefaultUILocation).Unix(), apiIssue.Deadline.Unix()) unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ RepoID: repoBefore.ID, diff --git a/tests/integration/api_pull_test.go b/tests/integration/api_pull_test.go index e9ccbe45831..aa4aed97895 100644 --- a/tests/integration/api_pull_test.go +++ b/tests/integration/api_pull_test.go @@ -449,13 +449,15 @@ func TestAPIEditPull(t *testing.T) { token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) title := "create a success pr" req := NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls", owner10.Name, repo10.Name), &api.CreatePullRequestOption{ - Head: "develop", - Base: "master", - Title: title, + Head: "develop", + Base: "master", + Title: title, + Deadline: new(time.Date(2026, 5, 11, 12, 0, 0, 0, time.FixedZone("", -10*3600))), }).AddTokenAuth(token) resp := MakeRequest(t, req, http.StatusCreated) apiPull := DecodeJSON(t, resp, &api.PullRequest{}) assert.Equal(t, "master", apiPull.Base.Name) + assert.Equal(t, time.Date(2026, 5, 11, 23, 59, 59, 0, setting.DefaultUILocation).Unix(), apiPull.Deadline.Unix()) newTitle := "edit a this pr" newBody := "edited body" @@ -480,6 +482,11 @@ func TestAPIEditPull(t *testing.T) { }).AddTokenAuth(token) MakeRequest(t, req, http.StatusNotFound) + req = NewRequestWithJSON(t, http.MethodPatch, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d", owner10.Name, repo10.Name, pull.Index), &api.EditPullRequestOption{ + RemoveDeadline: new(false), + }).AddTokenAuth(token) + MakeRequest(t, req, http.StatusBadRequest) + t.Run("PullContentVersion", func(t *testing.T) { testAPIPullContentVersion(t, pull.ID) })