Merge pull request 'Add an 'updated_at' field to the EditIssueOption struct' (#764) from fluzz/forgejo:add_update_at into forgejo-development
Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/764
This commit is contained in:
commit
4af241b9fb
26 changed files with 795 additions and 32 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
|
||||||
}
|
}
|
||||||
|
@ -1101,9 +1106,17 @@ func UpdateComment(c *Comment, doer *user_model.User) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer committer.Close()
|
defer committer.Close()
|
||||||
sess := db.GetEngine(ctx)
|
|
||||||
|
|
||||||
if _, err := sess.ID(c.ID).AllCols().Update(c); err != nil {
|
sess := db.GetEngine(ctx).ID(c.ID).AllCols()
|
||||||
|
if c.Issue.NoAutoTime {
|
||||||
|
// update the DataBase
|
||||||
|
sess = sess.NoAutoTime().SetExpr("updated_unix", c.Issue.UpdatedUnix)
|
||||||
|
// the UpdatedUnix value of the Comment also has to be set,
|
||||||
|
// to return the adequate valuè
|
||||||
|
// see https://codeberg.org/forgejo/forgejo/pulls/764#issuecomment-1023801
|
||||||
|
c.UpdatedUnix = c.Issue.UpdatedUnix
|
||||||
|
}
|
||||||
|
if _, err := sess.Update(c); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := c.LoadIssue(ctx); err != nil {
|
if err := c.LoadIssue(ctx); err != nil {
|
||||||
|
|
|
@ -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 {
|
||||||
issue.ClosedUnix = timeutil.TimeStampNow()
|
if issue.NoAutoTime {
|
||||||
|
issue.ClosedUnix = issue.UpdatedUnix
|
||||||
|
} else {
|
||||||
|
issue.ClosedUnix = timeutil.TimeStampNow()
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
issue.ClosedUnix = 0
|
issue.ClosedUnix = 0
|
||||||
}
|
}
|
||||||
|
@ -92,8 +101,14 @@ 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 err := UpdateMilestoneCounters(ctx, issue.MilestoneID); err != nil {
|
if issue.NoAutoTime {
|
||||||
return nil, err
|
if err := UpdateMilestoneCountersWithDate(ctx, issue.MilestoneID, issue.UpdatedUnix); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if err := UpdateMilestoneCounters(ctx, issue.MilestoneID); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -259,8 +274,12 @@ func ChangeIssueContent(issue *Issue, doer *user_model.User, content string) (er
|
||||||
return fmt.Errorf("UpdateIssueCols: %w", err)
|
return fmt.Errorf("UpdateIssueCols: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
historyDate := timeutil.TimeStampNow()
|
||||||
|
if issue.NoAutoTime {
|
||||||
|
historyDate = issue.UpdatedUnix
|
||||||
|
}
|
||||||
if err = SaveIssueContentHistory(ctx, doer.ID, issue.ID, 0,
|
if err = SaveIssueContentHistory(ctx, doer.ID, issue.ID, 0,
|
||||||
timeutil.TimeStampNow(), issue.Content, false); err != nil {
|
historyDate, issue.Content, false); err != nil {
|
||||||
return fmt.Errorf("SaveIssueContentHistory: %w", err)
|
return fmt.Errorf("SaveIssueContentHistory: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -449,10 +468,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 +520,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)
|
||||||
|
|
|
@ -28,6 +28,7 @@ type Attachment struct {
|
||||||
Name string
|
Name string
|
||||||
DownloadCount int64 `xorm:"DEFAULT 0"`
|
DownloadCount int64 `xorm:"DEFAULT 0"`
|
||||||
Size int64 `xorm:"DEFAULT 0"`
|
Size int64 `xorm:"DEFAULT 0"`
|
||||||
|
NoAutoTime bool `xorm:"-"`
|
||||||
CreatedUnix timeutil.TimeStamp `xorm:"created"`
|
CreatedUnix timeutil.TimeStamp `xorm:"created"`
|
||||||
CustomDownloadURL string `xorm:"-"`
|
CustomDownloadURL string `xorm:"-"`
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -28,12 +28,16 @@ type Comment struct {
|
||||||
type CreateIssueCommentOption struct {
|
type CreateIssueCommentOption struct {
|
||||||
// required:true
|
// required:true
|
||||||
Body string `json:"body" binding:"Required"`
|
Body string `json:"body" binding:"Required"`
|
||||||
|
// swagger:strfmt date-time
|
||||||
|
Updated *time.Time `json:"updated_at"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// EditIssueCommentOption options for editing a comment
|
// EditIssueCommentOption options for editing a comment
|
||||||
type EditIssueCommentOption struct {
|
type EditIssueCommentOption struct {
|
||||||
// required: true
|
// required: true
|
||||||
Body string `json:"body" binding:"Required"`
|
Body string `json:"body" binding:"Required"`
|
||||||
|
// swagger:strfmt date-time
|
||||||
|
Updated *time.Time `json:"updated_at"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// TimelineComment represents a timeline comment (comment of any type) on a commit or issue
|
// TimelineComment represents a timeline comment (comment of any type) on a commit or issue
|
||||||
|
|
|
@ -4,6 +4,10 @@
|
||||||
|
|
||||||
package structs
|
package structs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
// Label a label to an issue or a pr
|
// Label a label to an issue or a pr
|
||||||
// swagger:model
|
// swagger:model
|
||||||
type Label struct {
|
type Label struct {
|
||||||
|
@ -45,10 +49,18 @@ type EditLabelOption struct {
|
||||||
IsArchived *bool `json:"is_archived"`
|
IsArchived *bool `json:"is_archived"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DeleteLabelOption options for deleting a label
|
||||||
|
type DeleteLabelsOption struct {
|
||||||
|
// swagger:strfmt date-time
|
||||||
|
Updated *time.Time `json:"updated_at"`
|
||||||
|
}
|
||||||
|
|
||||||
// IssueLabelsOption a collection of labels
|
// IssueLabelsOption a collection of labels
|
||||||
type IssueLabelsOption struct {
|
type IssueLabelsOption struct {
|
||||||
// list of label IDs
|
// list of label IDs
|
||||||
Labels []int64 `json:"labels"`
|
Labels []int64 `json:"labels"`
|
||||||
|
// swagger:strfmt date-time
|
||||||
|
Updated *time.Time `json:"updated_at"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// LabelTemplate info of a Label template
|
// LabelTemplate info of a Label template
|
||||||
|
|
|
@ -1201,8 +1201,8 @@ func Routes() *web.Route {
|
||||||
m.Combo("").Get(repo.ListIssueLabels).
|
m.Combo("").Get(repo.ListIssueLabels).
|
||||||
Post(reqToken(), bind(api.IssueLabelsOption{}), repo.AddIssueLabels).
|
Post(reqToken(), bind(api.IssueLabelsOption{}), repo.AddIssueLabels).
|
||||||
Put(reqToken(), bind(api.IssueLabelsOption{}), repo.ReplaceIssueLabels).
|
Put(reqToken(), bind(api.IssueLabelsOption{}), repo.ReplaceIssueLabels).
|
||||||
Delete(reqToken(), repo.ClearIssueLabels)
|
Delete(reqToken(), bind(api.DeleteLabelsOption{}), repo.ClearIssueLabels)
|
||||||
m.Delete("/{id}", reqToken(), repo.DeleteIssueLabel)
|
m.Delete("/{id}", reqToken(), bind(api.DeleteLabelsOption{}), repo.DeleteIssueLabel)
|
||||||
})
|
})
|
||||||
m.Group("/times", func() {
|
m.Group("/times", func() {
|
||||||
m.Combo("").
|
m.Combo("").
|
||||||
|
|
|
@ -774,6 +774,12 @@ func EditIssue(ctx *context.APIContext) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
err = issue_service.SetIssueUpdateDate(ctx, issue, form.Updated, ctx.Doer)
|
||||||
|
if err != nil {
|
||||||
|
ctx.Error(http.StatusForbidden, "SetIssueUpdateDate", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
oldTitle := issue.Title
|
oldTitle := issue.Title
|
||||||
if len(form.Title) > 0 {
|
if len(form.Title) > 0 {
|
||||||
issue.Title = form.Title
|
issue.Title = form.Title
|
||||||
|
|
|
@ -5,6 +5,7 @@ package repo
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"time"
|
||||||
|
|
||||||
issues_model "code.gitea.io/gitea/models/issues"
|
issues_model "code.gitea.io/gitea/models/issues"
|
||||||
repo_model "code.gitea.io/gitea/models/repo"
|
repo_model "code.gitea.io/gitea/models/repo"
|
||||||
|
@ -141,6 +142,11 @@ func CreateIssueAttachment(ctx *context.APIContext) {
|
||||||
// description: name of the attachment
|
// description: name of the attachment
|
||||||
// type: string
|
// type: string
|
||||||
// required: false
|
// required: false
|
||||||
|
// - name: updated_at
|
||||||
|
// in: query
|
||||||
|
// description: time of the attachment's creation. This is a timestamp in RFC 3339 format
|
||||||
|
// type: string
|
||||||
|
// format: date-time
|
||||||
// - name: attachment
|
// - name: attachment
|
||||||
// in: formData
|
// in: formData
|
||||||
// description: attachment to upload
|
// description: attachment to upload
|
||||||
|
@ -163,6 +169,20 @@ func CreateIssueAttachment(ctx *context.APIContext) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
updatedAt := ctx.Req.FormValue("updated_at")
|
||||||
|
if len(updatedAt) != 0 {
|
||||||
|
updated, err := time.Parse(time.RFC3339, updatedAt)
|
||||||
|
if err != nil {
|
||||||
|
ctx.Error(http.StatusInternalServerError, "time.Parse", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = issue_service.SetIssueUpdateDate(ctx, issue, &updated, ctx.Doer)
|
||||||
|
if err != nil {
|
||||||
|
ctx.Error(http.StatusForbidden, "SetIssueUpdateDate", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Get uploaded file from request
|
// Get uploaded file from request
|
||||||
file, header, err := ctx.Req.FormFile("attachment")
|
file, header, err := ctx.Req.FormFile("attachment")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -177,10 +197,12 @@ func CreateIssueAttachment(ctx *context.APIContext) {
|
||||||
}
|
}
|
||||||
|
|
||||||
attachment, err := attachment.UploadAttachment(file, setting.Attachment.AllowedTypes, header.Size, &repo_model.Attachment{
|
attachment, err := attachment.UploadAttachment(file, setting.Attachment.AllowedTypes, header.Size, &repo_model.Attachment{
|
||||||
Name: filename,
|
Name: filename,
|
||||||
UploaderID: ctx.Doer.ID,
|
UploaderID: ctx.Doer.ID,
|
||||||
RepoID: ctx.Repo.Repository.ID,
|
RepoID: ctx.Repo.Repository.ID,
|
||||||
IssueID: issue.ID,
|
IssueID: issue.ID,
|
||||||
|
NoAutoTime: issue.NoAutoTime,
|
||||||
|
CreatedUnix: issue.UpdatedUnix,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.Error(http.StatusInternalServerError, "UploadAttachment", err)
|
ctx.Error(http.StatusInternalServerError, "UploadAttachment", err)
|
||||||
|
|
|
@ -362,6 +362,12 @@ func CreateIssueComment(ctx *context.APIContext) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
err = issue_service.SetIssueUpdateDate(ctx, issue, form.Updated, ctx.Doer)
|
||||||
|
if err != nil {
|
||||||
|
ctx.Error(http.StatusForbidden, "SetIssueUpdateDate", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
comment, err := issue_service.CreateIssueComment(ctx, ctx.Doer, ctx.Repo.Repository, issue, form.Body, nil)
|
comment, err := issue_service.CreateIssueComment(ctx, ctx.Doer, ctx.Repo.Repository, issue, form.Body, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.Error(http.StatusInternalServerError, "CreateIssueComment", err)
|
ctx.Error(http.StatusInternalServerError, "CreateIssueComment", err)
|
||||||
|
@ -554,6 +560,17 @@ func editIssueComment(ctx *context.APIContext, form api.EditIssueCommentOption)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
err = comment.LoadIssue(ctx)
|
||||||
|
if err != nil {
|
||||||
|
ctx.Error(http.StatusInternalServerError, "LoadIssue", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = issue_service.SetIssueUpdateDate(ctx, comment.Issue, form.Updated, ctx.Doer)
|
||||||
|
if err != nil {
|
||||||
|
ctx.Error(http.StatusForbidden, "SetIssueUpdateDate", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
oldContent := comment.Content
|
oldContent := comment.Content
|
||||||
comment.Content = form.Body
|
comment.Content = form.Body
|
||||||
if err := issue_service.UpdateComment(ctx, comment, ctx.Doer, oldContent); err != nil {
|
if err := issue_service.UpdateComment(ctx, comment, ctx.Doer, oldContent); err != nil {
|
||||||
|
|
|
@ -5,6 +5,7 @@ package repo
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"time"
|
||||||
|
|
||||||
issues_model "code.gitea.io/gitea/models/issues"
|
issues_model "code.gitea.io/gitea/models/issues"
|
||||||
repo_model "code.gitea.io/gitea/models/repo"
|
repo_model "code.gitea.io/gitea/models/repo"
|
||||||
|
@ -144,6 +145,11 @@ func CreateIssueCommentAttachment(ctx *context.APIContext) {
|
||||||
// description: name of the attachment
|
// description: name of the attachment
|
||||||
// type: string
|
// type: string
|
||||||
// required: false
|
// required: false
|
||||||
|
// - name: updated_at
|
||||||
|
// in: query
|
||||||
|
// description: time of the attachment's creation. This is a timestamp in RFC 3339 format
|
||||||
|
// type: string
|
||||||
|
// format: date-time
|
||||||
// - name: attachment
|
// - name: attachment
|
||||||
// in: formData
|
// in: formData
|
||||||
// description: attachment to upload
|
// description: attachment to upload
|
||||||
|
@ -167,6 +173,25 @@ func CreateIssueCommentAttachment(ctx *context.APIContext) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
updatedAt := ctx.Req.FormValue("updated_at")
|
||||||
|
if len(updatedAt) != 0 {
|
||||||
|
updated, err := time.Parse(time.RFC3339, updatedAt)
|
||||||
|
if err != nil {
|
||||||
|
ctx.Error(http.StatusInternalServerError, "time.Parse", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = comment.LoadIssue(ctx)
|
||||||
|
if err != nil {
|
||||||
|
ctx.Error(http.StatusInternalServerError, "LoadIssue", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = issue_service.SetIssueUpdateDate(ctx, comment.Issue, &updated, ctx.Doer)
|
||||||
|
if err != nil {
|
||||||
|
ctx.Error(http.StatusForbidden, "SetIssueUpdateDate", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Get uploaded file from request
|
// Get uploaded file from request
|
||||||
file, header, err := ctx.Req.FormFile("attachment")
|
file, header, err := ctx.Req.FormFile("attachment")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -181,11 +206,13 @@ func CreateIssueCommentAttachment(ctx *context.APIContext) {
|
||||||
}
|
}
|
||||||
|
|
||||||
attachment, err := attachment.UploadAttachment(file, setting.Attachment.AllowedTypes, header.Size, &repo_model.Attachment{
|
attachment, err := attachment.UploadAttachment(file, setting.Attachment.AllowedTypes, header.Size, &repo_model.Attachment{
|
||||||
Name: filename,
|
Name: filename,
|
||||||
UploaderID: ctx.Doer.ID,
|
UploaderID: ctx.Doer.ID,
|
||||||
RepoID: ctx.Repo.Repository.ID,
|
RepoID: ctx.Repo.Repository.ID,
|
||||||
IssueID: comment.IssueID,
|
IssueID: comment.IssueID,
|
||||||
CommentID: comment.ID,
|
CommentID: comment.ID,
|
||||||
|
NoAutoTime: comment.Issue.NoAutoTime,
|
||||||
|
CreatedUnix: comment.Issue.UpdatedUnix,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.Error(http.StatusInternalServerError, "UploadAttachment", err)
|
ctx.Error(http.StatusInternalServerError, "UploadAttachment", err)
|
||||||
|
|
|
@ -149,6 +149,10 @@ func DeleteIssueLabel(ctx *context.APIContext) {
|
||||||
// type: integer
|
// type: integer
|
||||||
// format: int64
|
// format: int64
|
||||||
// required: true
|
// required: true
|
||||||
|
// - name: body
|
||||||
|
// in: body
|
||||||
|
// schema:
|
||||||
|
// "$ref": "#/definitions/DeleteLabelsOption"
|
||||||
// responses:
|
// responses:
|
||||||
// "204":
|
// "204":
|
||||||
// "$ref": "#/responses/empty"
|
// "$ref": "#/responses/empty"
|
||||||
|
@ -156,6 +160,7 @@ func DeleteIssueLabel(ctx *context.APIContext) {
|
||||||
// "$ref": "#/responses/forbidden"
|
// "$ref": "#/responses/forbidden"
|
||||||
// "422":
|
// "422":
|
||||||
// "$ref": "#/responses/validationError"
|
// "$ref": "#/responses/validationError"
|
||||||
|
form := web.GetForm(ctx).(*api.DeleteLabelsOption)
|
||||||
|
|
||||||
issue, err := issues_model.GetIssueByIndex(ctx, ctx.Repo.Repository.ID, ctx.ParamsInt64(":index"))
|
issue, err := issues_model.GetIssueByIndex(ctx, ctx.Repo.Repository.ID, ctx.ParamsInt64(":index"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -172,6 +177,11 @@ func DeleteIssueLabel(ctx *context.APIContext) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err := issue_service.SetIssueUpdateDate(ctx, issue, form.Updated, ctx.Doer); err != nil {
|
||||||
|
ctx.Error(http.StatusForbidden, "SetIssueUpdateDate", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
label, err := issues_model.GetLabelByID(ctx, ctx.ParamsInt64(":id"))
|
label, err := issues_model.GetLabelByID(ctx, ctx.ParamsInt64(":id"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if issues_model.IsErrLabelNotExist(err) {
|
if issues_model.IsErrLabelNotExist(err) {
|
||||||
|
@ -269,11 +279,16 @@ func ClearIssueLabels(ctx *context.APIContext) {
|
||||||
// type: integer
|
// type: integer
|
||||||
// format: int64
|
// format: int64
|
||||||
// required: true
|
// required: true
|
||||||
|
// - name: body
|
||||||
|
// in: body
|
||||||
|
// schema:
|
||||||
|
// "$ref": "#/definitions/DeleteLabelsOption"
|
||||||
// responses:
|
// responses:
|
||||||
// "204":
|
// "204":
|
||||||
// "$ref": "#/responses/empty"
|
// "$ref": "#/responses/empty"
|
||||||
// "403":
|
// "403":
|
||||||
// "$ref": "#/responses/forbidden"
|
// "$ref": "#/responses/forbidden"
|
||||||
|
form := web.GetForm(ctx).(*api.DeleteLabelsOption)
|
||||||
|
|
||||||
issue, err := issues_model.GetIssueByIndex(ctx, ctx.Repo.Repository.ID, ctx.ParamsInt64(":index"))
|
issue, err := issues_model.GetIssueByIndex(ctx, ctx.Repo.Repository.ID, ctx.ParamsInt64(":index"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -290,6 +305,11 @@ func ClearIssueLabels(ctx *context.APIContext) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err := issue_service.SetIssueUpdateDate(ctx, issue, form.Updated, ctx.Doer); err != nil {
|
||||||
|
ctx.Error(http.StatusForbidden, "SetIssueUpdateDate", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if err := issue_service.ClearLabels(issue, ctx.Doer); err != nil {
|
if err := issue_service.ClearLabels(issue, ctx.Doer); err != nil {
|
||||||
ctx.Error(http.StatusInternalServerError, "ClearLabels", err)
|
ctx.Error(http.StatusInternalServerError, "ClearLabels", err)
|
||||||
return
|
return
|
||||||
|
@ -320,5 +340,11 @@ func prepareForReplaceOrAdd(ctx *context.APIContext, form api.IssueLabelsOption)
|
||||||
return nil, nil, nil
|
return nil, nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
err = issue_service.SetIssueUpdateDate(ctx, issue, form.Updated, ctx.Doer)
|
||||||
|
if err != nil {
|
||||||
|
ctx.Error(http.StatusForbidden, "SetIssueUpdateDate", err)
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
return issue, labels, err
|
return issue, labels, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -47,6 +47,9 @@ type swaggerParameterBodies struct {
|
||||||
// in:body
|
// in:body
|
||||||
IssueLabelsOption api.IssueLabelsOption
|
IssueLabelsOption api.IssueLabelsOption
|
||||||
|
|
||||||
|
// in:body
|
||||||
|
DeleteLabelsOption api.DeleteLabelsOption
|
||||||
|
|
||||||
// in:body
|
// in:body
|
||||||
CreateKeyOption api.CreateKeyOption
|
CreateKeyOption api.CreateKeyOption
|
||||||
|
|
||||||
|
|
|
@ -32,7 +32,12 @@ func NewAttachment(attach *repo_model.Attachment, file io.Reader, size int64) (*
|
||||||
}
|
}
|
||||||
attach.Size = size
|
attach.Size = size
|
||||||
|
|
||||||
return db.Insert(ctx, attach)
|
eng := db.GetEngine(ctx)
|
||||||
|
if attach.NoAutoTime {
|
||||||
|
eng.NoAutoTime()
|
||||||
|
}
|
||||||
|
_, err = eng.Insert(attach)
|
||||||
|
return err
|
||||||
})
|
})
|
||||||
|
|
||||||
return attach, err
|
return attach, err
|
||||||
|
|
|
@ -89,7 +89,11 @@ func UpdateComment(ctx context.Context, c *issues_model.Comment, doer *user_mode
|
||||||
}
|
}
|
||||||
|
|
||||||
if needsContentHistory {
|
if needsContentHistory {
|
||||||
err := issues_model.SaveIssueContentHistory(ctx, doer.ID, c.IssueID, c.ID, timeutil.TimeStampNow(), c.Content, false)
|
historyDate := timeutil.TimeStampNow()
|
||||||
|
if c.Issue.NoAutoTime {
|
||||||
|
historyDate = c.Issue.UpdatedUnix
|
||||||
|
}
|
||||||
|
err := issues_model.SaveIssueContentHistory(ctx, doer.ID, c.IssueID, c.ID, historyDate, c.Content, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@ package issue
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
activities_model "code.gitea.io/gitea/models/activities"
|
activities_model "code.gitea.io/gitea/models/activities"
|
||||||
"code.gitea.io/gitea/models/db"
|
"code.gitea.io/gitea/models/db"
|
||||||
|
@ -18,6 +19,7 @@ import (
|
||||||
"code.gitea.io/gitea/modules/git"
|
"code.gitea.io/gitea/modules/git"
|
||||||
"code.gitea.io/gitea/modules/notification"
|
"code.gitea.io/gitea/modules/notification"
|
||||||
"code.gitea.io/gitea/modules/storage"
|
"code.gitea.io/gitea/modules/storage"
|
||||||
|
"code.gitea.io/gitea/modules/timeutil"
|
||||||
)
|
)
|
||||||
|
|
||||||
// NewIssue creates new issue with labels for repository.
|
// NewIssue creates new issue with labels for repository.
|
||||||
|
@ -304,3 +306,40 @@ func deleteIssue(ctx context.Context, issue *issues_model.Issue) error {
|
||||||
|
|
||||||
return committer.Commit()
|
return committer.Commit()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Set the UpdatedUnix date and the NoAutoTime field of an Issue if a non
|
||||||
|
// nil 'updated' time is provided
|
||||||
|
//
|
||||||
|
// In order to set a specific update time, the DB will be updated with
|
||||||
|
// NoAutoTime(). A 'NoAutoTime' boolean field in the Issue struct is used to
|
||||||
|
// propagate down to the DB update calls the will to apply autoupdate or not.
|
||||||
|
func SetIssueUpdateDate(ctx context.Context, issue *issues_model.Issue, updated *time.Time, doer *user_model.User) error {
|
||||||
|
issue.NoAutoTime = false
|
||||||
|
if updated == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := issue.LoadRepo(ctx); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the poster is allowed to set an update date
|
||||||
|
perm, err := access_model.GetUserRepoPermission(ctx, issue.Repo, doer)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !perm.IsAdmin() && !perm.IsOwner() {
|
||||||
|
return fmt.Errorf("user needs to have admin or owner right")
|
||||||
|
}
|
||||||
|
|
||||||
|
// A simple guard against potential inconsistent calls
|
||||||
|
updatedUnix := timeutil.TimeStamp(updated.Unix())
|
||||||
|
if updatedUnix < issue.CreatedUnix || updatedUnix > timeutil.TimeStampNow() {
|
||||||
|
return fmt.Errorf("unallowed update date")
|
||||||
|
}
|
||||||
|
|
||||||
|
issue.UpdatedUnix = updatedUnix
|
||||||
|
issue.NoAutoTime = true
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
|
@ -13,6 +13,32 @@ import (
|
||||||
"code.gitea.io/gitea/modules/notification"
|
"code.gitea.io/gitea/modules/notification"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func updateMilestoneCounters(ctx context.Context, issue *issues_model.Issue, id int64) error {
|
||||||
|
if issue.NoAutoTime {
|
||||||
|
// We set the milestone's update date to the max of the
|
||||||
|
// milestone and issue update dates.
|
||||||
|
// Note: we can not call UpdateMilestoneCounters() if the
|
||||||
|
// milestone's update date is to be kept, because that function
|
||||||
|
// auto-updates the dates.
|
||||||
|
milestone, err := issues_model.GetMilestoneByRepoID(ctx, issue.RepoID, id)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("GetMilestoneByRepoID: %w", err)
|
||||||
|
}
|
||||||
|
updatedUnix := milestone.UpdatedUnix
|
||||||
|
if issue.UpdatedUnix > updatedUnix {
|
||||||
|
updatedUnix = issue.UpdatedUnix
|
||||||
|
}
|
||||||
|
if err := issues_model.UpdateMilestoneCountersWithDate(ctx, id, updatedUnix); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if err := issues_model.UpdateMilestoneCounters(ctx, id); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func changeMilestoneAssign(ctx context.Context, doer *user_model.User, issue *issues_model.Issue, oldMilestoneID int64) error {
|
func changeMilestoneAssign(ctx context.Context, doer *user_model.User, issue *issues_model.Issue, oldMilestoneID int64) error {
|
||||||
// Only check if milestone exists if we don't remove it.
|
// Only check if milestone exists if we don't remove it.
|
||||||
if issue.MilestoneID > 0 {
|
if issue.MilestoneID > 0 {
|
||||||
|
@ -30,13 +56,13 @@ func changeMilestoneAssign(ctx context.Context, doer *user_model.User, issue *is
|
||||||
}
|
}
|
||||||
|
|
||||||
if oldMilestoneID > 0 {
|
if oldMilestoneID > 0 {
|
||||||
if err := issues_model.UpdateMilestoneCounters(ctx, oldMilestoneID); err != nil {
|
if err := updateMilestoneCounters(ctx, issue, oldMilestoneID); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if issue.MilestoneID > 0 {
|
if issue.MilestoneID > 0 {
|
||||||
if err := issues_model.UpdateMilestoneCounters(ctx, issue.MilestoneID); err != nil {
|
if err := updateMilestoneCounters(ctx, issue, issue.MilestoneID); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
60
templates/swagger/v1_json.tmpl
generated
60
templates/swagger/v1_json.tmpl
generated
|
@ -6120,6 +6120,13 @@
|
||||||
"name": "name",
|
"name": "name",
|
||||||
"in": "query"
|
"in": "query"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"format": "date-time",
|
||||||
|
"description": "time of the attachment's creation. This is a timestamp in RFC 3339 format",
|
||||||
|
"name": "updated_at",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "file",
|
"type": "file",
|
||||||
"description": "attachment to upload",
|
"description": "attachment to upload",
|
||||||
|
@ -6718,6 +6725,13 @@
|
||||||
"name": "name",
|
"name": "name",
|
||||||
"in": "query"
|
"in": "query"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"format": "date-time",
|
||||||
|
"description": "time of the attachment's creation. This is a timestamp in RFC 3339 format",
|
||||||
|
"name": "updated_at",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "file",
|
"type": "file",
|
||||||
"description": "attachment to upload",
|
"description": "attachment to upload",
|
||||||
|
@ -7651,6 +7665,13 @@
|
||||||
"name": "index",
|
"name": "index",
|
||||||
"in": "path",
|
"in": "path",
|
||||||
"required": true
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "body",
|
||||||
|
"in": "body",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/DeleteLabelsOption"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"responses": {
|
"responses": {
|
||||||
|
@ -7703,6 +7724,13 @@
|
||||||
"name": "id",
|
"name": "id",
|
||||||
"in": "path",
|
"in": "path",
|
||||||
"required": true
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "body",
|
||||||
|
"in": "body",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/DeleteLabelsOption"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"responses": {
|
"responses": {
|
||||||
|
@ -17026,6 +17054,11 @@
|
||||||
"body": {
|
"body": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"x-go-name": "Body"
|
"x-go-name": "Body"
|
||||||
|
},
|
||||||
|
"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"
|
||||||
|
@ -17784,6 +17817,18 @@
|
||||||
},
|
},
|
||||||
"x-go-package": "code.gitea.io/gitea/modules/structs"
|
"x-go-package": "code.gitea.io/gitea/modules/structs"
|
||||||
},
|
},
|
||||||
|
"DeleteLabelsOption": {
|
||||||
|
"description": "DeleteLabelOption options for deleting a label",
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"updated_at": {
|
||||||
|
"type": "string",
|
||||||
|
"format": "date-time",
|
||||||
|
"x-go-name": "Updated"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"x-go-package": "code.gitea.io/gitea/modules/structs"
|
||||||
|
},
|
||||||
"DeployKey": {
|
"DeployKey": {
|
||||||
"description": "DeployKey a deploy key",
|
"description": "DeployKey a deploy key",
|
||||||
"type": "object",
|
"type": "object",
|
||||||
|
@ -18037,6 +18082,11 @@
|
||||||
"body": {
|
"body": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"x-go-name": "Body"
|
"x-go-name": "Body"
|
||||||
|
},
|
||||||
|
"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"
|
||||||
|
@ -18086,6 +18136,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"
|
||||||
|
@ -19500,6 +19555,11 @@
|
||||||
"format": "int64"
|
"format": "int64"
|
||||||
},
|
},
|
||||||
"x-go-name": "Labels"
|
"x-go-name": "Labels"
|
||||||
|
},
|
||||||
|
"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"
|
||||||
|
|
|
@ -11,6 +11,7 @@ import (
|
||||||
"mime/multipart"
|
"mime/multipart"
|
||||||
"net/http"
|
"net/http"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
auth_model "code.gitea.io/gitea/models/auth"
|
auth_model "code.gitea.io/gitea/models/auth"
|
||||||
"code.gitea.io/gitea/models/db"
|
"code.gitea.io/gitea/models/db"
|
||||||
|
@ -111,6 +112,82 @@ func TestAPICreateCommentAttachment(t *testing.T) {
|
||||||
unittest.AssertExistsAndLoadBean(t, &repo_model.Attachment{ID: apiAttachment.ID, CommentID: comment.ID})
|
unittest.AssertExistsAndLoadBean(t, &repo_model.Attachment{ID: apiAttachment.ID, CommentID: comment.ID})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestAPICreateCommentAttachmentAutoDate(t *testing.T) {
|
||||||
|
defer tests.PrepareTestEnv(t)()
|
||||||
|
|
||||||
|
comment := unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{ID: 2})
|
||||||
|
issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: comment.IssueID})
|
||||||
|
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: issue.RepoID})
|
||||||
|
repoOwner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID})
|
||||||
|
|
||||||
|
session := loginUser(t, repoOwner.Name)
|
||||||
|
token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteIssue)
|
||||||
|
urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/issues/comments/%d/assets?token=%s",
|
||||||
|
repoOwner.Name, repo.Name, comment.ID, token)
|
||||||
|
|
||||||
|
filename := "image.png"
|
||||||
|
buff := generateImg()
|
||||||
|
body := &bytes.Buffer{}
|
||||||
|
|
||||||
|
t.Run("WithAutoDate", func(t *testing.T) {
|
||||||
|
defer tests.PrintCurrentTest(t)()
|
||||||
|
|
||||||
|
// Setup multi-part
|
||||||
|
writer := multipart.NewWriter(body)
|
||||||
|
part, err := writer.CreateFormFile("attachment", filename)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
_, err = io.Copy(part, &buff)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
err = writer.Close()
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
req := NewRequestWithBody(t, "POST", urlStr, body)
|
||||||
|
req.Header.Add("Content-Type", writer.FormDataContentType())
|
||||||
|
resp := session.MakeRequest(t, req, http.StatusCreated)
|
||||||
|
apiAttachment := new(api.Attachment)
|
||||||
|
DecodeJSON(t, resp, &apiAttachment)
|
||||||
|
|
||||||
|
unittest.AssertExistsAndLoadBean(t, &repo_model.Attachment{ID: apiAttachment.ID})
|
||||||
|
// the execution of the API call supposedly lasted less than one minute
|
||||||
|
updatedSince := time.Since(apiAttachment.Created)
|
||||||
|
assert.LessOrEqual(t, updatedSince, time.Minute)
|
||||||
|
|
||||||
|
commentAfter := unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{ID: comment.ID})
|
||||||
|
updatedSince = time.Since(commentAfter.UpdatedUnix.AsTime())
|
||||||
|
assert.LessOrEqual(t, updatedSince, time.Minute)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("WithUpdateDate", func(t *testing.T) {
|
||||||
|
defer tests.PrintCurrentTest(t)()
|
||||||
|
|
||||||
|
updatedAt := time.Now().Add(-time.Hour).Truncate(time.Second)
|
||||||
|
urlStr += fmt.Sprintf("&updated_at=%s", updatedAt.UTC().Format(time.RFC3339))
|
||||||
|
|
||||||
|
// Setup multi-part
|
||||||
|
writer := multipart.NewWriter(body)
|
||||||
|
part, err := writer.CreateFormFile("attachment", filename)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
_, err = io.Copy(part, &buff)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
err = writer.Close()
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
req := NewRequestWithBody(t, "POST", urlStr, body)
|
||||||
|
req.Header.Add("Content-Type", writer.FormDataContentType())
|
||||||
|
resp := session.MakeRequest(t, req, http.StatusCreated)
|
||||||
|
apiAttachment := new(api.Attachment)
|
||||||
|
DecodeJSON(t, resp, &apiAttachment)
|
||||||
|
|
||||||
|
// dates will be converted into the same tz, in order to compare them
|
||||||
|
utcTZ, _ := time.LoadLocation("UTC")
|
||||||
|
unittest.AssertExistsAndLoadBean(t, &repo_model.Attachment{ID: apiAttachment.ID})
|
||||||
|
assert.Equal(t, updatedAt.In(utcTZ), apiAttachment.Created.In(utcTZ))
|
||||||
|
|
||||||
|
commentAfter := unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{ID: comment.ID})
|
||||||
|
assert.Equal(t, updatedAt.In(utcTZ), commentAfter.UpdatedUnix.AsTime().In(utcTZ))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func TestAPIEditCommentAttachment(t *testing.T) {
|
func TestAPIEditCommentAttachment(t *testing.T) {
|
||||||
defer tests.PrepareTestEnv(t)()
|
defer tests.PrepareTestEnv(t)()
|
||||||
|
|
||||||
|
|
|
@ -8,6 +8,7 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
auth_model "code.gitea.io/gitea/models/auth"
|
auth_model "code.gitea.io/gitea/models/auth"
|
||||||
"code.gitea.io/gitea/models/db"
|
"code.gitea.io/gitea/models/db"
|
||||||
|
@ -110,6 +111,58 @@ func TestAPICreateComment(t *testing.T) {
|
||||||
unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{ID: updatedComment.ID, IssueID: issue.ID, Content: commentBody})
|
unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{ID: updatedComment.ID, IssueID: issue.ID, Content: commentBody})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestAPICreateCommentAutoDate(t *testing.T) {
|
||||||
|
defer tests.PrepareTestEnv(t)()
|
||||||
|
|
||||||
|
issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{})
|
||||||
|
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: issue.RepoID})
|
||||||
|
repoOwner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID})
|
||||||
|
token := getUserToken(t, repoOwner.Name, auth_model.AccessTokenScopeWriteIssue)
|
||||||
|
|
||||||
|
urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/issues/%d/comments?token=%s",
|
||||||
|
repoOwner.Name, repo.Name, issue.Index, token)
|
||||||
|
const commentBody = "Comment body"
|
||||||
|
|
||||||
|
t.Run("WithAutoDate", func(t *testing.T) {
|
||||||
|
defer tests.PrintCurrentTest(t)()
|
||||||
|
|
||||||
|
req := NewRequestWithValues(t, "POST", urlStr, map[string]string{
|
||||||
|
"body": commentBody,
|
||||||
|
})
|
||||||
|
resp := MakeRequest(t, req, http.StatusCreated)
|
||||||
|
var updatedComment api.Comment
|
||||||
|
DecodeJSON(t, resp, &updatedComment)
|
||||||
|
|
||||||
|
// the execution of the API call supposedly lasted less than one minute
|
||||||
|
updatedSince := time.Since(updatedComment.Updated)
|
||||||
|
assert.LessOrEqual(t, updatedSince, time.Minute)
|
||||||
|
|
||||||
|
commentAfter := unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{ID: updatedComment.ID, IssueID: issue.ID, Content: commentBody})
|
||||||
|
updatedSince = time.Since(commentAfter.UpdatedUnix.AsTime())
|
||||||
|
assert.LessOrEqual(t, updatedSince, time.Minute)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("WithUpdateDate", func(t *testing.T) {
|
||||||
|
defer tests.PrintCurrentTest(t)()
|
||||||
|
|
||||||
|
updatedAt := time.Now().Add(-time.Hour).Truncate(time.Second)
|
||||||
|
|
||||||
|
req := NewRequestWithJSON(t, "POST", urlStr, &api.CreateIssueCommentOption{
|
||||||
|
Body: commentBody,
|
||||||
|
Updated: &updatedAt,
|
||||||
|
})
|
||||||
|
resp := MakeRequest(t, req, http.StatusCreated)
|
||||||
|
var updatedComment api.Comment
|
||||||
|
DecodeJSON(t, resp, &updatedComment)
|
||||||
|
|
||||||
|
// dates will be converted into the same tz, in order to compare them
|
||||||
|
utcTZ, _ := time.LoadLocation("UTC")
|
||||||
|
assert.Equal(t, updatedAt.In(utcTZ), updatedComment.Updated.In(utcTZ))
|
||||||
|
commentAfter := unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{ID: updatedComment.ID, IssueID: issue.ID, Content: commentBody})
|
||||||
|
assert.Equal(t, updatedAt.In(utcTZ), commentAfter.UpdatedUnix.AsTime().In(utcTZ))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func TestAPIGetComment(t *testing.T) {
|
func TestAPIGetComment(t *testing.T) {
|
||||||
defer tests.PrepareTestEnv(t)()
|
defer tests.PrepareTestEnv(t)()
|
||||||
|
|
||||||
|
@ -161,6 +214,60 @@ func TestAPIEditComment(t *testing.T) {
|
||||||
unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{ID: comment.ID, IssueID: issue.ID, Content: newCommentBody})
|
unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{ID: comment.ID, IssueID: issue.ID, Content: newCommentBody})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestAPIEditCommentWithDate(t *testing.T) {
|
||||||
|
defer tests.PrepareTestEnv(t)()
|
||||||
|
|
||||||
|
comment := unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{},
|
||||||
|
unittest.Cond("type = ?", issues_model.CommentTypeComment))
|
||||||
|
issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: comment.IssueID})
|
||||||
|
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: issue.RepoID})
|
||||||
|
repoOwner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID})
|
||||||
|
token := getUserToken(t, repoOwner.Name, auth_model.AccessTokenScopeWriteIssue)
|
||||||
|
|
||||||
|
urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/issues/comments/%d?token=%s",
|
||||||
|
repoOwner.Name, repo.Name, comment.ID, token)
|
||||||
|
const newCommentBody = "This is the new comment body"
|
||||||
|
|
||||||
|
t.Run("WithAutoDate", func(t *testing.T) {
|
||||||
|
defer tests.PrintCurrentTest(t)()
|
||||||
|
|
||||||
|
req := NewRequestWithValues(t, "PATCH", urlStr, map[string]string{
|
||||||
|
"body": newCommentBody,
|
||||||
|
})
|
||||||
|
resp := MakeRequest(t, req, http.StatusOK)
|
||||||
|
var updatedComment api.Comment
|
||||||
|
DecodeJSON(t, resp, &updatedComment)
|
||||||
|
|
||||||
|
// the execution of the API call supposedly lasted less than one minute
|
||||||
|
updatedSince := time.Since(updatedComment.Updated)
|
||||||
|
assert.LessOrEqual(t, updatedSince, time.Minute)
|
||||||
|
|
||||||
|
commentAfter := unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{ID: comment.ID, IssueID: issue.ID, Content: newCommentBody})
|
||||||
|
updatedSince = time.Since(commentAfter.UpdatedUnix.AsTime())
|
||||||
|
assert.LessOrEqual(t, updatedSince, time.Minute)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("WithUpdateDate", func(t *testing.T) {
|
||||||
|
defer tests.PrintCurrentTest(t)()
|
||||||
|
|
||||||
|
updatedAt := time.Now().Add(-time.Hour).Truncate(time.Second)
|
||||||
|
|
||||||
|
req := NewRequestWithJSON(t, "PATCH", urlStr, &api.EditIssueCommentOption{
|
||||||
|
Body: newCommentBody,
|
||||||
|
Updated: &updatedAt,
|
||||||
|
})
|
||||||
|
resp := MakeRequest(t, req, http.StatusOK)
|
||||||
|
var updatedComment api.Comment
|
||||||
|
DecodeJSON(t, resp, &updatedComment)
|
||||||
|
|
||||||
|
// dates will be converted into the same tz, in order to compare them
|
||||||
|
utcTZ, _ := time.LoadLocation("UTC")
|
||||||
|
assert.Equal(t, updatedAt.In(utcTZ), updatedComment.Updated.In(utcTZ))
|
||||||
|
commentAfter := unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{ID: comment.ID, IssueID: issue.ID, Content: newCommentBody})
|
||||||
|
assert.Equal(t, updatedAt.In(utcTZ), commentAfter.UpdatedUnix.AsTime().In(utcTZ))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func TestAPIDeleteComment(t *testing.T) {
|
func TestAPIDeleteComment(t *testing.T) {
|
||||||
defer tests.PrepareTestEnv(t)()
|
defer tests.PrepareTestEnv(t)()
|
||||||
|
|
||||||
|
|
|
@ -11,6 +11,7 @@ import (
|
||||||
"mime/multipart"
|
"mime/multipart"
|
||||||
"net/http"
|
"net/http"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
auth_model "code.gitea.io/gitea/models/auth"
|
auth_model "code.gitea.io/gitea/models/auth"
|
||||||
issues_model "code.gitea.io/gitea/models/issues"
|
issues_model "code.gitea.io/gitea/models/issues"
|
||||||
|
@ -100,6 +101,82 @@ func TestAPICreateIssueAttachment(t *testing.T) {
|
||||||
unittest.AssertExistsAndLoadBean(t, &repo_model.Attachment{ID: apiAttachment.ID, IssueID: issue.ID})
|
unittest.AssertExistsAndLoadBean(t, &repo_model.Attachment{ID: apiAttachment.ID, IssueID: issue.ID})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestAPICreateIssueAttachmentAutoDate(t *testing.T) {
|
||||||
|
defer tests.PrepareTestEnv(t)()
|
||||||
|
|
||||||
|
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
|
||||||
|
issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{RepoID: repo.ID})
|
||||||
|
repoOwner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID})
|
||||||
|
|
||||||
|
session := loginUser(t, repoOwner.Name)
|
||||||
|
token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteIssue)
|
||||||
|
urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/issues/%d/assets?token=%s",
|
||||||
|
repoOwner.Name, repo.Name, issue.Index, token)
|
||||||
|
|
||||||
|
filename := "image.png"
|
||||||
|
buff := generateImg()
|
||||||
|
body := &bytes.Buffer{}
|
||||||
|
|
||||||
|
t.Run("WithAutoDate", func(t *testing.T) {
|
||||||
|
defer tests.PrintCurrentTest(t)()
|
||||||
|
|
||||||
|
// Setup multi-part
|
||||||
|
writer := multipart.NewWriter(body)
|
||||||
|
part, err := writer.CreateFormFile("attachment", filename)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
_, err = io.Copy(part, &buff)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
err = writer.Close()
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
req := NewRequestWithBody(t, "POST", urlStr, body)
|
||||||
|
req.Header.Add("Content-Type", writer.FormDataContentType())
|
||||||
|
resp := session.MakeRequest(t, req, http.StatusCreated)
|
||||||
|
|
||||||
|
apiAttachment := new(api.Attachment)
|
||||||
|
DecodeJSON(t, resp, &apiAttachment)
|
||||||
|
|
||||||
|
unittest.AssertExistsAndLoadBean(t, &repo_model.Attachment{ID: apiAttachment.ID, IssueID: issue.ID})
|
||||||
|
// the execution of the API call supposedly lasted less than one minute
|
||||||
|
updatedSince := time.Since(apiAttachment.Created)
|
||||||
|
assert.LessOrEqual(t, updatedSince, time.Minute)
|
||||||
|
|
||||||
|
issueAfter := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: issue.Index})
|
||||||
|
updatedSince = time.Since(issueAfter.UpdatedUnix.AsTime())
|
||||||
|
assert.LessOrEqual(t, updatedSince, time.Minute)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("WithUpdateDate", func(t *testing.T) {
|
||||||
|
defer tests.PrintCurrentTest(t)()
|
||||||
|
|
||||||
|
updatedAt := time.Now().Add(-time.Hour).Truncate(time.Second)
|
||||||
|
urlStr += fmt.Sprintf("&updated_at=%s", updatedAt.UTC().Format(time.RFC3339))
|
||||||
|
|
||||||
|
// Setup multi-part
|
||||||
|
writer := multipart.NewWriter(body)
|
||||||
|
part, err := writer.CreateFormFile("attachment", filename)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
_, err = io.Copy(part, &buff)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
err = writer.Close()
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
req := NewRequestWithBody(t, "POST", urlStr, body)
|
||||||
|
req.Header.Add("Content-Type", writer.FormDataContentType())
|
||||||
|
resp := session.MakeRequest(t, req, http.StatusCreated)
|
||||||
|
|
||||||
|
apiAttachment := new(api.Attachment)
|
||||||
|
DecodeJSON(t, resp, &apiAttachment)
|
||||||
|
|
||||||
|
// dates will be converted into the same tz, in order to compare them
|
||||||
|
utcTZ, _ := time.LoadLocation("UTC")
|
||||||
|
unittest.AssertExistsAndLoadBean(t, &repo_model.Attachment{ID: apiAttachment.ID, IssueID: issue.ID})
|
||||||
|
assert.Equal(t, updatedAt.In(utcTZ), apiAttachment.Created.In(utcTZ))
|
||||||
|
issueAfter := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: issue.ID})
|
||||||
|
assert.Equal(t, updatedAt.In(utcTZ), issueAfter.UpdatedUnix.AsTime().In(utcTZ))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func TestAPIEditIssueAttachment(t *testing.T) {
|
func TestAPIEditIssueAttachment(t *testing.T) {
|
||||||
defer tests.PrepareTestEnv(t)()
|
defer tests.PrepareTestEnv(t)()
|
||||||
|
|
||||||
|
|
|
@ -8,6 +8,7 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
auth_model "code.gitea.io/gitea/models/auth"
|
auth_model "code.gitea.io/gitea/models/auth"
|
||||||
issues_model "code.gitea.io/gitea/models/issues"
|
issues_model "code.gitea.io/gitea/models/issues"
|
||||||
|
@ -15,6 +16,7 @@ import (
|
||||||
"code.gitea.io/gitea/models/unittest"
|
"code.gitea.io/gitea/models/unittest"
|
||||||
user_model "code.gitea.io/gitea/models/user"
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
api "code.gitea.io/gitea/modules/structs"
|
api "code.gitea.io/gitea/modules/structs"
|
||||||
|
"code.gitea.io/gitea/tests"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
@ -111,6 +113,49 @@ func TestAPIAddIssueLabels(t *testing.T) {
|
||||||
unittest.AssertExistsAndLoadBean(t, &issues_model.IssueLabel{IssueID: issue.ID, LabelID: 2})
|
unittest.AssertExistsAndLoadBean(t, &issues_model.IssueLabel{IssueID: issue.ID, LabelID: 2})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestAPIAddIssueLabelsAutoDate(t *testing.T) {
|
||||||
|
defer tests.PrepareTestEnv(t)()
|
||||||
|
|
||||||
|
issueBefore := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 3})
|
||||||
|
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: issueBefore.RepoID})
|
||||||
|
owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID})
|
||||||
|
|
||||||
|
session := loginUser(t, owner.Name)
|
||||||
|
token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteIssue)
|
||||||
|
urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/issues/%d/labels?token=%s",
|
||||||
|
owner.Name, repo.Name, issueBefore.Index, token)
|
||||||
|
|
||||||
|
t.Run("WithAutoDate", func(t *testing.T) {
|
||||||
|
defer tests.PrintCurrentTest(t)()
|
||||||
|
|
||||||
|
req := NewRequestWithJSON(t, "POST", urlStr, &api.IssueLabelsOption{
|
||||||
|
Labels: []int64{1},
|
||||||
|
})
|
||||||
|
MakeRequest(t, req, http.StatusOK)
|
||||||
|
|
||||||
|
issueAfter := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: issueBefore.ID})
|
||||||
|
// the execution of the API call supposedly lasted less than one minute
|
||||||
|
updatedSince := time.Since(issueAfter.UpdatedUnix.AsTime())
|
||||||
|
assert.LessOrEqual(t, updatedSince, time.Minute)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("WithUpdatedDate", func(t *testing.T) {
|
||||||
|
defer tests.PrintCurrentTest(t)()
|
||||||
|
|
||||||
|
updatedAt := time.Now().Add(-time.Hour).Truncate(time.Second)
|
||||||
|
req := NewRequestWithJSON(t, "POST", urlStr, &api.IssueLabelsOption{
|
||||||
|
Labels: []int64{2},
|
||||||
|
Updated: &updatedAt,
|
||||||
|
})
|
||||||
|
MakeRequest(t, req, http.StatusOK)
|
||||||
|
|
||||||
|
// dates will be converted into the same tz, in order to compare them
|
||||||
|
utcTZ, _ := time.LoadLocation("UTC")
|
||||||
|
issueAfter := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: issueBefore.ID})
|
||||||
|
assert.Equal(t, updatedAt.In(utcTZ), issueAfter.UpdatedUnix.AsTime().In(utcTZ))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func TestAPIReplaceIssueLabels(t *testing.T) {
|
func TestAPIReplaceIssueLabels(t *testing.T) {
|
||||||
assert.NoError(t, unittest.LoadFixtures())
|
assert.NoError(t, unittest.LoadFixtures())
|
||||||
|
|
||||||
|
|
|
@ -213,6 +213,157 @@ func TestAPIEditIssue(t *testing.T) {
|
||||||
assert.Equal(t, title, issueAfter.Title)
|
assert.Equal(t, title, issueAfter.Title)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestAPIEditIssueAutoDate(t *testing.T) {
|
||||||
|
defer tests.PrepareTestEnv(t)()
|
||||||
|
|
||||||
|
issueBefore := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 13})
|
||||||
|
repoBefore := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: issueBefore.RepoID})
|
||||||
|
owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repoBefore.OwnerID})
|
||||||
|
assert.NoError(t, issueBefore.LoadAttributes(db.DefaultContext))
|
||||||
|
|
||||||
|
t.Run("WithAutoDate", func(t *testing.T) {
|
||||||
|
defer tests.PrintCurrentTest(t)()
|
||||||
|
|
||||||
|
// User2 is not owner, but can update the 'public' issue with auto date
|
||||||
|
session := loginUser(t, "user2")
|
||||||
|
token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteIssue)
|
||||||
|
urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/issues/%d?token=%s", owner.Name, repoBefore.Name, issueBefore.Index, token)
|
||||||
|
|
||||||
|
body := "new content!"
|
||||||
|
req := NewRequestWithJSON(t, "PATCH", urlStr, api.EditIssueOption{
|
||||||
|
Body: &body,
|
||||||
|
})
|
||||||
|
resp := MakeRequest(t, req, http.StatusCreated)
|
||||||
|
var apiIssue api.Issue
|
||||||
|
DecodeJSON(t, resp, &apiIssue)
|
||||||
|
|
||||||
|
// the execution of the API call supposedly lasted less than one minute
|
||||||
|
updatedSince := time.Since(apiIssue.Updated)
|
||||||
|
assert.LessOrEqual(t, updatedSince, time.Minute)
|
||||||
|
|
||||||
|
issueAfter := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: issueBefore.ID})
|
||||||
|
updatedSince = time.Since(issueAfter.UpdatedUnix.AsTime())
|
||||||
|
assert.LessOrEqual(t, updatedSince, time.Minute)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("WithUpdateDate", func(t *testing.T) {
|
||||||
|
defer tests.PrintCurrentTest(t)()
|
||||||
|
|
||||||
|
// User1 is admin, and so can update the issue without auto date
|
||||||
|
session := loginUser(t, "user1")
|
||||||
|
token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteIssue)
|
||||||
|
urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/issues/%d?token=%s", owner.Name, repoBefore.Name, issueBefore.Index, token)
|
||||||
|
|
||||||
|
body := "new content, with updated time"
|
||||||
|
updatedAt := time.Now().Add(-time.Hour).Truncate(time.Second)
|
||||||
|
req := NewRequestWithJSON(t, "PATCH", urlStr, api.EditIssueOption{
|
||||||
|
Body: &body,
|
||||||
|
Updated: &updatedAt,
|
||||||
|
})
|
||||||
|
resp := MakeRequest(t, req, http.StatusCreated)
|
||||||
|
var apiIssue api.Issue
|
||||||
|
DecodeJSON(t, resp, &apiIssue)
|
||||||
|
|
||||||
|
// dates are converted into the same tz, in order to compare them
|
||||||
|
utcTZ, _ := time.LoadLocation("UTC")
|
||||||
|
assert.Equal(t, updatedAt.In(utcTZ), apiIssue.Updated.In(utcTZ))
|
||||||
|
|
||||||
|
issueAfter := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: issueBefore.ID})
|
||||||
|
assert.Equal(t, updatedAt.In(utcTZ), issueAfter.UpdatedUnix.AsTime().In(utcTZ))
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("WithoutPermission", func(t *testing.T) {
|
||||||
|
defer tests.PrintCurrentTest(t)()
|
||||||
|
|
||||||
|
// User2 is not owner nor admin, and so can't update the issue without auto date
|
||||||
|
session := loginUser(t, "user2")
|
||||||
|
token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteIssue)
|
||||||
|
urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/issues/%d?token=%s", owner.Name, repoBefore.Name, issueBefore.Index, token)
|
||||||
|
|
||||||
|
body := "new content, with updated time"
|
||||||
|
updatedAt := time.Now().Add(-time.Hour).Truncate(time.Second)
|
||||||
|
req := NewRequestWithJSON(t, "PATCH", urlStr, api.EditIssueOption{
|
||||||
|
Body: &body,
|
||||||
|
Updated: &updatedAt,
|
||||||
|
})
|
||||||
|
resp := MakeRequest(t, req, http.StatusForbidden)
|
||||||
|
var apiError api.APIError
|
||||||
|
DecodeJSON(t, resp, &apiError)
|
||||||
|
|
||||||
|
assert.Equal(t, "user needs to have admin or owner right", apiError.Message)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAPIEditIssueMilestoneAutoDate(t *testing.T) {
|
||||||
|
defer tests.PrepareTestEnv(t)()
|
||||||
|
|
||||||
|
issueBefore := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 1})
|
||||||
|
repoBefore := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: issueBefore.RepoID})
|
||||||
|
|
||||||
|
owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repoBefore.OwnerID})
|
||||||
|
assert.NoError(t, issueBefore.LoadAttributes(db.DefaultContext))
|
||||||
|
|
||||||
|
session := loginUser(t, owner.Name)
|
||||||
|
token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteIssue)
|
||||||
|
urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/issues/%d?token=%s", owner.Name, repoBefore.Name, issueBefore.Index, token)
|
||||||
|
|
||||||
|
t.Run("WithAutoDate", func(t *testing.T) {
|
||||||
|
defer tests.PrintCurrentTest(t)()
|
||||||
|
|
||||||
|
milestone := int64(1)
|
||||||
|
req := NewRequestWithJSON(t, "PATCH", urlStr, api.EditIssueOption{
|
||||||
|
Milestone: &milestone,
|
||||||
|
})
|
||||||
|
MakeRequest(t, req, http.StatusCreated)
|
||||||
|
|
||||||
|
// the execution of the API call supposedly lasted less than one minute
|
||||||
|
milestoneAfter := unittest.AssertExistsAndLoadBean(t, &issues_model.Milestone{ID: milestone})
|
||||||
|
updatedSince := time.Since(milestoneAfter.UpdatedUnix.AsTime())
|
||||||
|
assert.LessOrEqual(t, updatedSince, time.Minute)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("WithPostUpdateDate", func(t *testing.T) {
|
||||||
|
defer tests.PrintCurrentTest(t)()
|
||||||
|
|
||||||
|
// Note: the updated_unix field of the test Milestones is set to NULL
|
||||||
|
// Hence, any date is higher than the Milestone's updated date
|
||||||
|
updatedAt := time.Now().Add(-time.Hour).Truncate(time.Second)
|
||||||
|
milestone := int64(2)
|
||||||
|
req := NewRequestWithJSON(t, "PATCH", urlStr, api.EditIssueOption{
|
||||||
|
Milestone: &milestone,
|
||||||
|
Updated: &updatedAt,
|
||||||
|
})
|
||||||
|
MakeRequest(t, req, http.StatusCreated)
|
||||||
|
|
||||||
|
// the milestone date should be set to 'updatedAt'
|
||||||
|
// dates are converted into the same tz, in order to compare them
|
||||||
|
utcTZ, _ := time.LoadLocation("UTC")
|
||||||
|
milestoneAfter := unittest.AssertExistsAndLoadBean(t, &issues_model.Milestone{ID: milestone})
|
||||||
|
assert.Equal(t, updatedAt.In(utcTZ), milestoneAfter.UpdatedUnix.AsTime().In(utcTZ))
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("WithPastUpdateDate", func(t *testing.T) {
|
||||||
|
defer tests.PrintCurrentTest(t)()
|
||||||
|
|
||||||
|
// Note: This Milestone's updated_unix has been set to Now() by the first subtest
|
||||||
|
milestone := int64(1)
|
||||||
|
milestoneBefore := unittest.AssertExistsAndLoadBean(t, &issues_model.Milestone{ID: milestone})
|
||||||
|
|
||||||
|
updatedAt := time.Now().Add(-time.Hour).Truncate(time.Second)
|
||||||
|
req := NewRequestWithJSON(t, "PATCH", urlStr, api.EditIssueOption{
|
||||||
|
Milestone: &milestone,
|
||||||
|
Updated: &updatedAt,
|
||||||
|
})
|
||||||
|
MakeRequest(t, req, http.StatusCreated)
|
||||||
|
|
||||||
|
// the milestone date should not change
|
||||||
|
// dates are converted into the same tz, in order to compare them
|
||||||
|
utcTZ, _ := time.LoadLocation("UTC")
|
||||||
|
milestoneAfter := unittest.AssertExistsAndLoadBean(t, &issues_model.Milestone{ID: milestone})
|
||||||
|
assert.Equal(t, milestoneAfter.UpdatedUnix.AsTime().In(utcTZ), milestoneBefore.UpdatedUnix.AsTime().In(utcTZ))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func TestAPISearchIssues(t *testing.T) {
|
func TestAPISearchIssues(t *testing.T) {
|
||||||
defer tests.PrepareTestEnv(t)()
|
defer tests.PrepareTestEnv(t)()
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue