WIP: Add an 'updated_at' field to the EditIssueOption struct
This field adds the possibility to set the update date when modifying an issue through the API. A 'NoAutoDate' in-memory field is added in the Issue struct. If the update_at field is set, NoAutoDate is set to true and the Issue's UpdatedUnix field is filled. That information is passed down to the functions that actually updates the database, which have been modified to not auto update dates if requested. A guard is added to the 'EditIssue' API call, to checks that the udpate_at date is between the issue's creation date and the current date (to avoid 'malicious' changes). It also limits the new feature to project's owners and admins.
This commit is contained in:
parent
70fffdc61d
commit
c524d33402
9 changed files with 104 additions and 18 deletions
|
@ -823,6 +823,11 @@ func CreateComment(ctx context.Context, opts *CreateCommentOptions) (_ *Comment,
|
||||||
IsForcePush: opts.IsForcePush,
|
IsForcePush: opts.IsForcePush,
|
||||||
Invalidated: opts.Invalidated,
|
Invalidated: opts.Invalidated,
|
||||||
}
|
}
|
||||||
|
if opts.Issue.NoAutoTime {
|
||||||
|
comment.CreatedUnix = opts.Issue.UpdatedUnix
|
||||||
|
comment.UpdatedUnix = opts.Issue.UpdatedUnix
|
||||||
|
e.NoAutoTime()
|
||||||
|
}
|
||||||
if _, err = e.Insert(comment); err != nil {
|
if _, err = e.Insert(comment); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -125,6 +125,7 @@ type Issue struct {
|
||||||
CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`
|
CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`
|
||||||
UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"`
|
UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"`
|
||||||
ClosedUnix timeutil.TimeStamp `xorm:"INDEX"`
|
ClosedUnix timeutil.TimeStamp `xorm:"INDEX"`
|
||||||
|
NoAutoTime bool `xorm:"-"`
|
||||||
|
|
||||||
Attachments []*repo_model.Attachment `xorm:"-"`
|
Attachments []*repo_model.Attachment `xorm:"-"`
|
||||||
Comments CommentList `xorm:"-"`
|
Comments CommentList `xorm:"-"`
|
||||||
|
|
|
@ -27,7 +27,12 @@ import (
|
||||||
|
|
||||||
// UpdateIssueCols updates cols of issue
|
// UpdateIssueCols updates cols of issue
|
||||||
func UpdateIssueCols(ctx context.Context, issue *Issue, cols ...string) error {
|
func UpdateIssueCols(ctx context.Context, issue *Issue, cols ...string) error {
|
||||||
if _, err := db.GetEngine(ctx).ID(issue.ID).Cols(cols...).Update(issue); err != nil {
|
sess := db.GetEngine(ctx).ID(issue.ID)
|
||||||
|
if issue.NoAutoTime {
|
||||||
|
cols = append(cols, []string{"updated_unix"}...)
|
||||||
|
sess.NoAutoTime()
|
||||||
|
}
|
||||||
|
if _, err := sess.Cols(cols...).Update(issue); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
@ -71,7 +76,11 @@ func doChangeIssueStatus(ctx context.Context, issue *Issue, doer *user_model.Use
|
||||||
}
|
}
|
||||||
|
|
||||||
if issue.IsClosed {
|
if issue.IsClosed {
|
||||||
|
if issue.NoAutoTime {
|
||||||
|
issue.ClosedUnix = issue.UpdatedUnix
|
||||||
|
} else {
|
||||||
issue.ClosedUnix = timeutil.TimeStampNow()
|
issue.ClosedUnix = timeutil.TimeStampNow()
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
issue.ClosedUnix = 0
|
issue.ClosedUnix = 0
|
||||||
}
|
}
|
||||||
|
@ -92,10 +101,16 @@ func doChangeIssueStatus(ctx context.Context, issue *Issue, doer *user_model.Use
|
||||||
|
|
||||||
// Update issue count of milestone
|
// Update issue count of milestone
|
||||||
if issue.MilestoneID > 0 {
|
if issue.MilestoneID > 0 {
|
||||||
|
if issue.NoAutoTime {
|
||||||
|
if err := UpdateMilestoneCountersWithDate(ctx, issue.MilestoneID, issue.UpdatedUnix); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
if err := UpdateMilestoneCounters(ctx, issue.MilestoneID); err != nil {
|
if err := UpdateMilestoneCounters(ctx, issue.MilestoneID); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// update repository's issue closed number
|
// update repository's issue closed number
|
||||||
if err := repo_model.UpdateRepoIssueNumbers(ctx, issue.RepoID, issue.IsPull, true); err != nil {
|
if err := repo_model.UpdateRepoIssueNumbers(ctx, issue.RepoID, issue.IsPull, true); err != nil {
|
||||||
|
@ -449,10 +464,13 @@ func UpdateIssueByAPI(issue *Issue, doer *user_model.User) (statusChangeComment
|
||||||
return nil, false, err
|
return nil, false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := db.GetEngine(ctx).ID(issue.ID).Cols(
|
sess := db.GetEngine(ctx).ID(issue.ID)
|
||||||
"name", "content", "milestone_id", "priority",
|
cols := []string{"name", "content", "milestone_id", "priority", "deadline_unix", "is_locked"}
|
||||||
"deadline_unix", "updated_unix", "is_locked").
|
if issue.NoAutoTime {
|
||||||
Update(issue); err != nil {
|
cols = append(cols, "updated_unix")
|
||||||
|
sess.NoAutoTime()
|
||||||
|
}
|
||||||
|
if _, err := sess.Cols(cols...).Update(issue); err != nil {
|
||||||
return nil, false, err
|
return nil, false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -498,7 +516,7 @@ func UpdateIssueDeadline(issue *Issue, deadlineUnix timeutil.TimeStamp, doer *us
|
||||||
defer committer.Close()
|
defer committer.Close()
|
||||||
|
|
||||||
// Update the deadline
|
// Update the deadline
|
||||||
if err = UpdateIssueCols(ctx, &Issue{ID: issue.ID, DeadlineUnix: deadlineUnix}, "deadline_unix"); err != nil {
|
if err = UpdateIssueCols(ctx, &Issue{ID: issue.ID, DeadlineUnix: deadlineUnix, NoAutoTime: issue.NoAutoTime, UpdatedUnix: issue.UpdatedUnix}, "deadline_unix"); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -110,6 +110,10 @@ func (issue *Issue) createCrossReferences(stdCtx context.Context, ctx *crossRefe
|
||||||
if ctx.OrigComment != nil {
|
if ctx.OrigComment != nil {
|
||||||
refCommentID = ctx.OrigComment.ID
|
refCommentID = ctx.OrigComment.ID
|
||||||
}
|
}
|
||||||
|
if ctx.OrigIssue.NoAutoTime {
|
||||||
|
xref.Issue.NoAutoTime = true
|
||||||
|
xref.Issue.UpdatedUnix = ctx.OrigIssue.UpdatedUnix
|
||||||
|
}
|
||||||
opts := &CreateCommentOptions{
|
opts := &CreateCommentOptions{
|
||||||
Type: ctx.Type,
|
Type: ctx.Type,
|
||||||
Doer: ctx.Doer,
|
Doer: ctx.Doer,
|
||||||
|
|
|
@ -188,10 +188,9 @@ func updateMilestone(ctx context.Context, m *Milestone) error {
|
||||||
return UpdateMilestoneCounters(ctx, m.ID)
|
return UpdateMilestoneCounters(ctx, m.ID)
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdateMilestoneCounters calculates NumIssues, NumClosesIssues and Completeness
|
func updateMilestoneCounters(ctx context.Context, id int64, noAutoTime bool, updatedUnix timeutil.TimeStamp) error {
|
||||||
func UpdateMilestoneCounters(ctx context.Context, id int64) error {
|
|
||||||
e := db.GetEngine(ctx)
|
e := db.GetEngine(ctx)
|
||||||
_, err := e.ID(id).
|
sess := e.ID(id).
|
||||||
SetExpr("num_issues", builder.Select("count(*)").From("issue").Where(
|
SetExpr("num_issues", builder.Select("count(*)").From("issue").Where(
|
||||||
builder.Eq{"milestone_id": id},
|
builder.Eq{"milestone_id": id},
|
||||||
)).
|
)).
|
||||||
|
@ -200,8 +199,11 @@ func UpdateMilestoneCounters(ctx context.Context, id int64) error {
|
||||||
"milestone_id": id,
|
"milestone_id": id,
|
||||||
"is_closed": true,
|
"is_closed": true,
|
||||||
},
|
},
|
||||||
)).
|
))
|
||||||
Update(&Milestone{})
|
if noAutoTime {
|
||||||
|
sess.SetExpr("updated_unix", updatedUnix).NoAutoTime()
|
||||||
|
}
|
||||||
|
_, err := sess.Update(&Milestone{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -211,6 +213,16 @@ func UpdateMilestoneCounters(ctx context.Context, id int64) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UpdateMilestoneCounters calculates NumIssues, NumClosesIssues and Completeness
|
||||||
|
func UpdateMilestoneCounters(ctx context.Context, id int64) error {
|
||||||
|
return updateMilestoneCounters(ctx, id, false, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateMilestoneCountersWithDate calculates NumIssues, NumClosesIssues and Completeness and set the UpdatedUnix date
|
||||||
|
func UpdateMilestoneCountersWithDate(ctx context.Context, id int64, updatedUnix timeutil.TimeStamp) error {
|
||||||
|
return updateMilestoneCounters(ctx, id, true, updatedUnix)
|
||||||
|
}
|
||||||
|
|
||||||
// ChangeMilestoneStatusByRepoIDAndID changes a milestone open/closed status if the milestone ID is in the repo.
|
// ChangeMilestoneStatusByRepoIDAndID changes a milestone open/closed status if the milestone ID is in the repo.
|
||||||
func ChangeMilestoneStatusByRepoIDAndID(repoID, milestoneID int64, isClosed bool) error {
|
func ChangeMilestoneStatusByRepoIDAndID(repoID, milestoneID int64, isClosed bool) error {
|
||||||
ctx, committer, err := db.TxContext(db.DefaultContext)
|
ctx, committer, err := db.TxContext(db.DefaultContext)
|
||||||
|
|
|
@ -110,6 +110,8 @@ type EditIssueOption struct {
|
||||||
// swagger:strfmt date-time
|
// swagger:strfmt date-time
|
||||||
Deadline *time.Time `json:"due_date"`
|
Deadline *time.Time `json:"due_date"`
|
||||||
RemoveDeadline *bool `json:"unset_due_date"`
|
RemoveDeadline *bool `json:"unset_due_date"`
|
||||||
|
// swagger:strfmt date-time
|
||||||
|
Updated *time.Time `json:"updated_at"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// EditDeadlineOption options for creating a deadline
|
// EditDeadlineOption options for creating a deadline
|
||||||
|
|
|
@ -774,6 +774,33 @@ func EditIssue(ctx *context.APIContext) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// In order to be set a specific update time, the DB will be updated
|
||||||
|
// with NoAutoTime. The 'noAutoTime' bool will be propagated down to the
|
||||||
|
// DB update calls to apply autoupdate or not.
|
||||||
|
issue.NoAutoTime = false
|
||||||
|
if form.Updated != nil {
|
||||||
|
// Check if the poster is allowed to set an update date
|
||||||
|
perm, err := access_model.GetUserRepoPermission(ctx, issue.Repo, ctx.Doer)
|
||||||
|
if err != nil {
|
||||||
|
ctx.Status(http.StatusForbidden)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !perm.IsAdmin() && !perm.IsOwner() {
|
||||||
|
ctx.Error(http.StatusUnauthorized, "EditIssue", "user needs to have admin or owner right")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// A simple guard against potential inconsistent calls
|
||||||
|
updatedUnix := timeutil.TimeStamp(form.Updated.Unix())
|
||||||
|
if updatedUnix < issue.CreatedUnix || updatedUnix > timeutil.TimeStampNow() {
|
||||||
|
ctx.Error(http.StatusForbidden, "EditIssue", "unallowed update date")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
issue.UpdatedUnix = updatedUnix
|
||||||
|
issue.NoAutoTime = true
|
||||||
|
}
|
||||||
|
|
||||||
oldTitle := issue.Title
|
oldTitle := issue.Title
|
||||||
if len(form.Title) > 0 {
|
if len(form.Title) > 0 {
|
||||||
issue.Title = form.Title
|
issue.Title = form.Title
|
||||||
|
|
|
@ -30,16 +30,28 @@ func changeMilestoneAssign(ctx context.Context, doer *user_model.User, issue *is
|
||||||
}
|
}
|
||||||
|
|
||||||
if oldMilestoneID > 0 {
|
if oldMilestoneID > 0 {
|
||||||
|
if issue.NoAutoTime {
|
||||||
|
if err := issues_model.UpdateMilestoneCountersWithDate(ctx, oldMilestoneID, issue.UpdatedUnix); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
if err := issues_model.UpdateMilestoneCounters(ctx, oldMilestoneID); err != nil {
|
if err := issues_model.UpdateMilestoneCounters(ctx, oldMilestoneID); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if issue.MilestoneID > 0 {
|
if issue.MilestoneID > 0 {
|
||||||
|
if issue.NoAutoTime {
|
||||||
|
if err := issues_model.UpdateMilestoneCountersWithDate(ctx, issue.MilestoneID, issue.UpdatedUnix); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
if err := issues_model.UpdateMilestoneCounters(ctx, issue.MilestoneID); err != nil {
|
if err := issues_model.UpdateMilestoneCounters(ctx, issue.MilestoneID); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if oldMilestoneID > 0 || issue.MilestoneID > 0 {
|
if oldMilestoneID > 0 || issue.MilestoneID > 0 {
|
||||||
if err := issue.LoadRepo(ctx); err != nil {
|
if err := issue.LoadRepo(ctx); err != nil {
|
||||||
|
|
5
templates/swagger/v1_json.tmpl
generated
5
templates/swagger/v1_json.tmpl
generated
|
@ -18086,6 +18086,11 @@
|
||||||
"unset_due_date": {
|
"unset_due_date": {
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
"x-go-name": "RemoveDeadline"
|
"x-go-name": "RemoveDeadline"
|
||||||
|
},
|
||||||
|
"updated_at": {
|
||||||
|
"type": "string",
|
||||||
|
"format": "date-time",
|
||||||
|
"x-go-name": "Updated"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"x-go-package": "code.gitea.io/gitea/modules/structs"
|
"x-go-package": "code.gitea.io/gitea/modules/structs"
|
||||||
|
|
Loading…
Reference in a new issue