| @@ -3,6 +3,7 @@ | |||
| repo_id: 1 | |||
| index: 1 | |||
| poster_id: 1 | |||
| assignee_id: 1 | |||
| name: issue1 | |||
| content: content1 | |||
| 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 | |||
| } | |||
| // .___ ____ ___ | |||
| // | | ______ ________ __ ____ | | \______ ___________ | |||
| // | |/ ___// ___/ | \_/ __ \| | / ___// __ \_ __ \ | |||
| // | |\___ \ \___ \| | /\ ___/| | /\___ \\ ___/| | \/ | |||
| // |___/____ >____ >____/ \___ >______//____ >\___ >__| | |||
| // \/ \/ \/ \/ \/ | |||
| // 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 | |||
| // updates issue-user relations for them. | |||
| @@ -1400,67 +1336,6 @@ func UpdateIssue(issue *Issue) error { | |||
| 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 { | |||
| return err | |||
| } | |||
| fixtures, err = testfixtures.NewFolder(x.DB().DB, &testfixtures.SQLite{}, "fixtures/") | |||
| 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{} { | |||
| exists, err := loadBeanIfExists(bean, conditions...) | |||
| 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 | |||
| } | |||
| @@ -92,3 +94,15 @@ func AssertNotExistsBean(t *testing.T, bean interface{}, conditions ...interface | |||
| assert.NoError(t, err) | |||
| 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) | |||
| } | |||