| @@ -3,6 +3,7 @@ | |||||
| repo_id: 1 | repo_id: 1 | ||||
| index: 1 | index: 1 | ||||
| poster_id: 1 | poster_id: 1 | ||||
| assignee_id: 1 | |||||
| name: issue1 | name: issue1 | ||||
| content: content1 | content: content1 | ||||
| is_closed: false | is_closed: false | ||||
| @@ -0,0 +1,23 @@ | |||||
| - | |||||
| id: 1 | |||||
| uid: 1 | |||||
| issue_id: 1 | |||||
| is_read: true | |||||
| is_assigned: true | |||||
| is_mentioned: false | |||||
| - | |||||
| id: 2 | |||||
| uid: 2 | |||||
| issue_id: 1 | |||||
| is_read: true | |||||
| is_assigned: false | |||||
| is_mentioned: false | |||||
| - | |||||
| id: 3 | |||||
| uid: 4 | |||||
| issue_id: 1 | |||||
| is_read: false | |||||
| is_assigned: false | |||||
| is_mentioned: false | |||||
| @@ -1106,70 +1106,6 @@ func Issues(opts *IssuesOptions) ([]*Issue, error) { | |||||
| return issues, nil | return issues, nil | ||||
| } | } | ||||
| // .___ ____ ___ | |||||
| // | | ______ ________ __ ____ | | \______ ___________ | |||||
| // | |/ ___// ___/ | \_/ __ \| | / ___// __ \_ __ \ | |||||
| // | |\___ \ \___ \| | /\ ___/| | /\___ \\ ___/| | \/ | |||||
| // |___/____ >____ >____/ \___ >______//____ >\___ >__| | |||||
| // \/ \/ \/ \/ \/ | |||||
| // IssueUser represents an issue-user relation. | |||||
| type IssueUser struct { | |||||
| ID int64 `xorm:"pk autoincr"` | |||||
| UID int64 `xorm:"INDEX"` // User ID. | |||||
| IssueID int64 | |||||
| IsRead bool | |||||
| IsAssigned bool | |||||
| IsMentioned bool | |||||
| } | |||||
| func newIssueUsers(e *xorm.Session, repo *Repository, issue *Issue) error { | |||||
| assignees, err := repo.getAssignees(e) | |||||
| if err != nil { | |||||
| return fmt.Errorf("getAssignees: %v", err) | |||||
| } | |||||
| // Poster can be anyone, append later if not one of assignees. | |||||
| isPosterAssignee := false | |||||
| // Leave a seat for poster itself to append later, but if poster is one of assignee | |||||
| // and just waste 1 unit is cheaper than re-allocate memory once. | |||||
| issueUsers := make([]*IssueUser, 0, len(assignees)+1) | |||||
| for _, assignee := range assignees { | |||||
| issueUsers = append(issueUsers, &IssueUser{ | |||||
| IssueID: issue.ID, | |||||
| UID: assignee.ID, | |||||
| IsAssigned: assignee.ID == issue.AssigneeID, | |||||
| }) | |||||
| isPosterAssignee = isPosterAssignee || assignee.ID == issue.PosterID | |||||
| } | |||||
| if !isPosterAssignee { | |||||
| issueUsers = append(issueUsers, &IssueUser{ | |||||
| IssueID: issue.ID, | |||||
| UID: issue.PosterID, | |||||
| }) | |||||
| } | |||||
| if _, err = e.Insert(issueUsers); err != nil { | |||||
| return err | |||||
| } | |||||
| return nil | |||||
| } | |||||
| // NewIssueUsers adds new issue-user relations for new issue of repository. | |||||
| func NewIssueUsers(repo *Repository, issue *Issue) (err error) { | |||||
| sess := x.NewSession() | |||||
| defer sessionRelease(sess) | |||||
| if err = sess.Begin(); err != nil { | |||||
| return err | |||||
| } | |||||
| if err = newIssueUsers(sess, repo, issue); err != nil { | |||||
| return err | |||||
| } | |||||
| return sess.Commit() | |||||
| } | |||||
| // UpdateIssueMentions extracts mentioned people from content and | // UpdateIssueMentions extracts mentioned people from content and | ||||
| // updates issue-user relations for them. | // updates issue-user relations for them. | ||||
| @@ -1400,67 +1336,6 @@ func UpdateIssue(issue *Issue) error { | |||||
| return updateIssue(x, issue) | return updateIssue(x, issue) | ||||
| } | } | ||||
| func updateIssueUserByAssignee(e *xorm.Session, issue *Issue) (err error) { | |||||
| if _, err = e.Exec("UPDATE `issue_user` SET is_assigned = ? WHERE issue_id = ?", false, issue.ID); err != nil { | |||||
| return err | |||||
| } | |||||
| // Assignee ID equals to 0 means clear assignee. | |||||
| if issue.AssigneeID > 0 { | |||||
| if _, err = e.Exec("UPDATE `issue_user` SET is_assigned = ? WHERE uid = ? AND issue_id = ?", true, issue.AssigneeID, issue.ID); err != nil { | |||||
| return err | |||||
| } | |||||
| } | |||||
| return updateIssue(e, issue) | |||||
| } | |||||
| // UpdateIssueUserByAssignee updates issue-user relation for assignee. | |||||
| func UpdateIssueUserByAssignee(issue *Issue) (err error) { | |||||
| sess := x.NewSession() | |||||
| defer sessionRelease(sess) | |||||
| if err = sess.Begin(); err != nil { | |||||
| return err | |||||
| } | |||||
| if err = updateIssueUserByAssignee(sess, issue); err != nil { | |||||
| return err | |||||
| } | |||||
| return sess.Commit() | |||||
| } | |||||
| // UpdateIssueUserByRead updates issue-user relation for reading. | |||||
| func UpdateIssueUserByRead(uid, issueID int64) error { | |||||
| _, err := x.Exec("UPDATE `issue_user` SET is_read=? WHERE uid=? AND issue_id=?", true, uid, issueID) | |||||
| return err | |||||
| } | |||||
| // UpdateIssueUsersByMentions updates issue-user pairs by mentioning. | |||||
| func UpdateIssueUsersByMentions(e Engine, issueID int64, uids []int64) error { | |||||
| for _, uid := range uids { | |||||
| iu := &IssueUser{ | |||||
| UID: uid, | |||||
| IssueID: issueID, | |||||
| } | |||||
| has, err := e.Get(iu) | |||||
| if err != nil { | |||||
| return err | |||||
| } | |||||
| iu.IsMentioned = true | |||||
| if has { | |||||
| _, err = e.Id(iu.ID).AllCols().Update(iu) | |||||
| } else { | |||||
| _, err = e.Insert(iu) | |||||
| } | |||||
| if err != nil { | |||||
| return err | |||||
| } | |||||
| } | |||||
| return nil | |||||
| } | |||||
| // _____ .__.__ __ | // _____ .__.__ __ | ||||
| // / \ |__| | ____ _______/ |_ ____ ____ ____ | // / \ |__| | ____ _______/ |_ ____ ____ ____ | ||||
| // / \ / \| | | _/ __ \ / ___/\ __\/ _ \ / \_/ __ \ | // / \ / \| | | _/ __ \ / ___/\ __\/ _ \ / \_/ __ \ | ||||
| @@ -0,0 +1,113 @@ | |||||
| // Copyright 2017 The Gogs Authors. All rights reserved. | |||||
| // Use of this source code is governed by a MIT-style | |||||
| // license that can be found in the LICENSE file. | |||||
| package models | |||||
| import ( | |||||
| "fmt" | |||||
| ) | |||||
| // IssueUser represents an issue-user relation. | |||||
| type IssueUser struct { | |||||
| ID int64 `xorm:"pk autoincr"` | |||||
| UID int64 `xorm:"INDEX"` // User ID. | |||||
| IssueID int64 | |||||
| IsRead bool | |||||
| IsAssigned bool | |||||
| IsMentioned bool | |||||
| } | |||||
| func newIssueUsers(e Engine, repo *Repository, issue *Issue) error { | |||||
| assignees, err := repo.getAssignees(e) | |||||
| if err != nil { | |||||
| return fmt.Errorf("getAssignees: %v", err) | |||||
| } | |||||
| // Poster can be anyone, append later if not one of assignees. | |||||
| isPosterAssignee := false | |||||
| // Leave a seat for poster itself to append later, but if poster is one of assignee | |||||
| // and just waste 1 unit is cheaper than re-allocate memory once. | |||||
| issueUsers := make([]*IssueUser, 0, len(assignees)+1) | |||||
| for _, assignee := range assignees { | |||||
| issueUsers = append(issueUsers, &IssueUser{ | |||||
| IssueID: issue.ID, | |||||
| UID: assignee.ID, | |||||
| IsAssigned: assignee.ID == issue.AssigneeID, | |||||
| }) | |||||
| isPosterAssignee = isPosterAssignee || assignee.ID == issue.PosterID | |||||
| } | |||||
| if !isPosterAssignee { | |||||
| issueUsers = append(issueUsers, &IssueUser{ | |||||
| IssueID: issue.ID, | |||||
| UID: issue.PosterID, | |||||
| }) | |||||
| } | |||||
| if _, err = e.Insert(issueUsers); err != nil { | |||||
| return err | |||||
| } | |||||
| return nil | |||||
| } | |||||
| func updateIssueUserByAssignee(e Engine, issue *Issue) (err error) { | |||||
| if _, err = e.Exec("UPDATE `issue_user` SET is_assigned = ? WHERE issue_id = ?", false, issue.ID); err != nil { | |||||
| return err | |||||
| } | |||||
| // Assignee ID equals to 0 means clear assignee. | |||||
| if issue.AssigneeID > 0 { | |||||
| if _, err = e.Exec("UPDATE `issue_user` SET is_assigned = ? WHERE uid = ? AND issue_id = ?", true, issue.AssigneeID, issue.ID); err != nil { | |||||
| return err | |||||
| } | |||||
| } | |||||
| return updateIssue(e, issue) | |||||
| } | |||||
| // UpdateIssueUserByAssignee updates issue-user relation for assignee. | |||||
| func UpdateIssueUserByAssignee(issue *Issue) (err error) { | |||||
| sess := x.NewSession() | |||||
| defer sessionRelease(sess) | |||||
| if err = sess.Begin(); err != nil { | |||||
| return err | |||||
| } | |||||
| if err = updateIssueUserByAssignee(sess, issue); err != nil { | |||||
| return err | |||||
| } | |||||
| return sess.Commit() | |||||
| } | |||||
| // UpdateIssueUserByRead updates issue-user relation for reading. | |||||
| func UpdateIssueUserByRead(uid, issueID int64) error { | |||||
| _, err := x.Exec("UPDATE `issue_user` SET is_read=? WHERE uid=? AND issue_id=?", true, uid, issueID) | |||||
| return err | |||||
| } | |||||
| // UpdateIssueUsersByMentions updates issue-user pairs by mentioning. | |||||
| func UpdateIssueUsersByMentions(e Engine, issueID int64, uids []int64) error { | |||||
| for _, uid := range uids { | |||||
| iu := &IssueUser{ | |||||
| UID: uid, | |||||
| IssueID: issueID, | |||||
| } | |||||
| has, err := e.Get(iu) | |||||
| if err != nil { | |||||
| return err | |||||
| } | |||||
| iu.IsMentioned = true | |||||
| if has { | |||||
| _, err = e.Id(iu.ID).AllCols().Update(iu) | |||||
| } else { | |||||
| _, err = e.Insert(iu) | |||||
| } | |||||
| if err != nil { | |||||
| return err | |||||
| } | |||||
| } | |||||
| return nil | |||||
| } | |||||
| @@ -0,0 +1,74 @@ | |||||
| // Copyright 2017 The Gogs Authors. All rights reserved. | |||||
| // Use of this source code is governed by a MIT-style | |||||
| // license that can be found in the LICENSE file. | |||||
| package models | |||||
| import ( | |||||
| "testing" | |||||
| "github.com/stretchr/testify/assert" | |||||
| ) | |||||
| func Test_newIssueUsers(t *testing.T) { | |||||
| assert.NoError(t, PrepareTestDatabase()) | |||||
| repo := AssertExistsAndLoadBean(t, &Repository{ID: 1}).(*Repository) | |||||
| newIssue := &Issue{ | |||||
| RepoID: repo.ID, | |||||
| PosterID: 4, | |||||
| Index: 5, | |||||
| Title: "newTestIssueTitle", | |||||
| Content: "newTestIssueContent", | |||||
| } | |||||
| // artificially insert new issue | |||||
| AssertSuccessfulInsert(t, newIssue) | |||||
| assert.NoError(t, newIssueUsers(x, repo, newIssue)) | |||||
| // issue_user table should now have entries for new issue | |||||
| AssertExistsAndLoadBean(t, &IssueUser{IssueID: newIssue.ID, UID: newIssue.PosterID}) | |||||
| AssertExistsAndLoadBean(t, &IssueUser{IssueID: newIssue.ID, UID: repo.OwnerID}) | |||||
| } | |||||
| func TestUpdateIssueUserByAssignee(t *testing.T) { | |||||
| assert.NoError(t, PrepareTestDatabase()) | |||||
| issue := AssertExistsAndLoadBean(t, &Issue{ID: 1}).(*Issue) | |||||
| // artificially change assignee in issue_user table | |||||
| AssertSuccessfulInsert(t, &IssueUser{IssueID: issue.ID, UID: 5, IsAssigned: true}) | |||||
| _, err := x.Cols("is_assigned"). | |||||
| Update(&IssueUser{IsAssigned: false}, &IssueUser{IssueID: issue.ID, UID: issue.AssigneeID}) | |||||
| assert.NoError(t, err) | |||||
| assert.NoError(t, UpdateIssueUserByAssignee(issue)) | |||||
| // issue_user table should now be correct again | |||||
| AssertExistsAndLoadBean(t, &IssueUser{IssueID: issue.ID, UID: issue.AssigneeID}, "is_assigned=1") | |||||
| AssertExistsAndLoadBean(t, &IssueUser{IssueID: issue.ID, UID: 5}, "is_assigned=0") | |||||
| } | |||||
| func TestUpdateIssueUserByRead(t *testing.T) { | |||||
| assert.NoError(t, PrepareTestDatabase()) | |||||
| issue := AssertExistsAndLoadBean(t, &Issue{ID: 1}).(*Issue) | |||||
| assert.NoError(t, UpdateIssueUserByRead(4, issue.ID)) | |||||
| AssertExistsAndLoadBean(t, &IssueUser{IssueID: issue.ID, UID: 4}, "is_read=1") | |||||
| assert.NoError(t, UpdateIssueUserByRead(4, issue.ID)) | |||||
| AssertExistsAndLoadBean(t, &IssueUser{IssueID: issue.ID, UID: 4}, "is_read=1") | |||||
| assert.NoError(t, UpdateIssueUserByRead(NonexistentID, NonexistentID)) | |||||
| } | |||||
| func TestUpdateIssueUsersByMentions(t *testing.T) { | |||||
| assert.NoError(t, PrepareTestDatabase()) | |||||
| issue := AssertExistsAndLoadBean(t, &Issue{ID: 1}).(*Issue) | |||||
| uids := []int64{2, 5} | |||||
| assert.NoError(t, UpdateIssueUsersByMentions(x, issue.ID, uids)) | |||||
| for _, uid := range uids { | |||||
| AssertExistsAndLoadBean(t, &IssueUser{IssueID: issue.ID, UID: uid}, "is_mentioned=1") | |||||
| } | |||||
| } | |||||
| @@ -51,6 +51,7 @@ func CreateTestEngine() error { | |||||
| if err = x.StoreEngine("InnoDB").Sync2(tables...); err != nil { | if err = x.StoreEngine("InnoDB").Sync2(tables...); err != nil { | ||||
| return err | return err | ||||
| } | } | ||||
| fixtures, err = testfixtures.NewFolder(x.DB().DB, &testfixtures.SQLite{}, "fixtures/") | fixtures, err = testfixtures.NewFolder(x.DB().DB, &testfixtures.SQLite{}, "fixtures/") | ||||
| return err | return err | ||||
| } | } | ||||
| @@ -82,7 +83,8 @@ func BeanExists(t *testing.T, bean interface{}, conditions ...interface{}) bool | |||||
| func AssertExistsAndLoadBean(t *testing.T, bean interface{}, conditions ...interface{}) interface{} { | func AssertExistsAndLoadBean(t *testing.T, bean interface{}, conditions ...interface{}) interface{} { | ||||
| exists, err := loadBeanIfExists(bean, conditions...) | exists, err := loadBeanIfExists(bean, conditions...) | ||||
| assert.NoError(t, err) | assert.NoError(t, err) | ||||
| assert.True(t, exists) | |||||
| assert.True(t, exists, | |||||
| "Expected to find %+v (with conditions %+v), but did not", bean, conditions) | |||||
| return bean | return bean | ||||
| } | } | ||||
| @@ -92,3 +94,15 @@ func AssertNotExistsBean(t *testing.T, bean interface{}, conditions ...interface | |||||
| assert.NoError(t, err) | assert.NoError(t, err) | ||||
| assert.False(t, exists) | assert.False(t, exists) | ||||
| } | } | ||||
| // AssertSuccessfulInsert assert that beans is successfully inserted | |||||
| func AssertSuccessfulInsert(t *testing.T, beans ...interface{}) { | |||||
| _, err := x.Insert(beans...) | |||||
| assert.NoError(t, err) | |||||
| } | |||||
| // AssertSuccessfulUpdate assert that bean is successfully updated | |||||
| func AssertSuccessfulUpdate(t *testing.T, bean interface{}, conditions ...interface{}) { | |||||
| _, err := x.Update(bean, conditions...) | |||||
| assert.NoError(t, err) | |||||
| } | |||||