| @@ -33,6 +33,7 @@ | |||||
| num_closed_issues: 0 | num_closed_issues: 0 | ||||
| num_pulls: 0 | num_pulls: 0 | ||||
| num_closed_pulls: 0 | num_closed_pulls: 0 | ||||
| num_watches: 0 | |||||
| - | - | ||||
| id: 4 | id: 4 | ||||
| @@ -2256,109 +2256,6 @@ func (repos MirrorRepositoryList) LoadAttributes() error { | |||||
| return repos.loadAttributes(x) | return repos.loadAttributes(x) | ||||
| } | } | ||||
| // __ __ __ .__ | |||||
| // / \ / \_____ _/ |_ ____ | |__ | |||||
| // \ \/\/ /\__ \\ __\/ ___\| | \ | |||||
| // \ / / __ \| | \ \___| Y \ | |||||
| // \__/\ / (____ /__| \___ >___| / | |||||
| // \/ \/ \/ \/ | |||||
| // Watch is connection request for receiving repository notification. | |||||
| type Watch struct { | |||||
| ID int64 `xorm:"pk autoincr"` | |||||
| UserID int64 `xorm:"UNIQUE(watch)"` | |||||
| RepoID int64 `xorm:"UNIQUE(watch)"` | |||||
| } | |||||
| func isWatching(e Engine, userID, repoID int64) bool { | |||||
| has, _ := e.Get(&Watch{0, userID, repoID}) | |||||
| return has | |||||
| } | |||||
| // IsWatching checks if user has watched given repository. | |||||
| func IsWatching(userID, repoID int64) bool { | |||||
| return isWatching(x, userID, repoID) | |||||
| } | |||||
| func watchRepo(e Engine, userID, repoID int64, watch bool) (err error) { | |||||
| if watch { | |||||
| if isWatching(e, userID, repoID) { | |||||
| return nil | |||||
| } | |||||
| if _, err = e.Insert(&Watch{RepoID: repoID, UserID: userID}); err != nil { | |||||
| return err | |||||
| } | |||||
| _, err = e.Exec("UPDATE `repository` SET num_watches = num_watches + 1 WHERE id = ?", repoID) | |||||
| } else { | |||||
| if !isWatching(e, userID, repoID) { | |||||
| return nil | |||||
| } | |||||
| if _, err = e.Delete(&Watch{0, userID, repoID}); err != nil { | |||||
| return err | |||||
| } | |||||
| _, err = e.Exec("UPDATE `repository` SET num_watches = num_watches - 1 WHERE id = ?", repoID) | |||||
| } | |||||
| return err | |||||
| } | |||||
| // WatchRepo watch or unwatch repository. | |||||
| func WatchRepo(userID, repoID int64, watch bool) (err error) { | |||||
| return watchRepo(x, userID, repoID, watch) | |||||
| } | |||||
| func getWatchers(e Engine, repoID int64) ([]*Watch, error) { | |||||
| watches := make([]*Watch, 0, 10) | |||||
| return watches, e.Find(&watches, &Watch{RepoID: repoID}) | |||||
| } | |||||
| // GetWatchers returns all watchers of given repository. | |||||
| func GetWatchers(repoID int64) ([]*Watch, error) { | |||||
| return getWatchers(x, repoID) | |||||
| } | |||||
| // GetWatchers returns range of users watching given repository. | |||||
| func (repo *Repository) GetWatchers(page int) ([]*User, error) { | |||||
| users := make([]*User, 0, ItemsPerPage) | |||||
| sess := x.Where("watch.repo_id=?", repo.ID). | |||||
| Join("LEFT", "watch", "`user`.id=`watch`.user_id") | |||||
| if page > 0 { | |||||
| sess = sess.Limit(ItemsPerPage, (page-1)*ItemsPerPage) | |||||
| } | |||||
| return users, sess.Find(&users) | |||||
| } | |||||
| func notifyWatchers(e Engine, act *Action) error { | |||||
| // Add feeds for user self and all watchers. | |||||
| watches, err := getWatchers(e, act.RepoID) | |||||
| if err != nil { | |||||
| return fmt.Errorf("get watchers: %v", err) | |||||
| } | |||||
| // Add feed for actioner. | |||||
| act.UserID = act.ActUserID | |||||
| if _, err = e.InsertOne(act); err != nil { | |||||
| return fmt.Errorf("insert new actioner: %v", err) | |||||
| } | |||||
| for i := range watches { | |||||
| if act.ActUserID == watches[i].UserID { | |||||
| continue | |||||
| } | |||||
| act.ID = 0 | |||||
| act.UserID = watches[i].UserID | |||||
| if _, err = e.InsertOne(act); err != nil { | |||||
| return fmt.Errorf("insert new action: %v", err) | |||||
| } | |||||
| } | |||||
| return nil | |||||
| } | |||||
| // NotifyWatchers creates batch of actions for every watcher. | |||||
| func NotifyWatchers(act *Action) error { | |||||
| return notifyWatchers(x, act) | |||||
| } | |||||
| // ___________ __ | // ___________ __ | ||||
| // \_ _____/__________| | __ | // \_ _____/__________| | __ | ||||
| // | __)/ _ \_ __ \ |/ / | // | __)/ _ \_ __ \ |/ / | ||||
| @@ -0,0 +1,103 @@ | |||||
| // Copyright 2017 The Gitea 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" | |||||
| // Watch is connection request for receiving repository notification. | |||||
| type Watch struct { | |||||
| ID int64 `xorm:"pk autoincr"` | |||||
| UserID int64 `xorm:"UNIQUE(watch)"` | |||||
| RepoID int64 `xorm:"UNIQUE(watch)"` | |||||
| } | |||||
| func isWatching(e Engine, userID, repoID int64) bool { | |||||
| has, _ := e.Get(&Watch{UserID: userID, RepoID: repoID}) | |||||
| return has | |||||
| } | |||||
| // IsWatching checks if user has watched given repository. | |||||
| func IsWatching(userID, repoID int64) bool { | |||||
| return isWatching(x, userID, repoID) | |||||
| } | |||||
| func watchRepo(e Engine, userID, repoID int64, watch bool) (err error) { | |||||
| if watch { | |||||
| if isWatching(e, userID, repoID) { | |||||
| return nil | |||||
| } | |||||
| if _, err = e.Insert(&Watch{RepoID: repoID, UserID: userID}); err != nil { | |||||
| return err | |||||
| } | |||||
| _, err = e.Exec("UPDATE `repository` SET num_watches = num_watches + 1 WHERE id = ?", repoID) | |||||
| } else { | |||||
| if !isWatching(e, userID, repoID) { | |||||
| return nil | |||||
| } | |||||
| if _, err = e.Delete(&Watch{0, userID, repoID}); err != nil { | |||||
| return err | |||||
| } | |||||
| _, err = e.Exec("UPDATE `repository` SET num_watches = num_watches - 1 WHERE id = ?", repoID) | |||||
| } | |||||
| return err | |||||
| } | |||||
| // WatchRepo watch or unwatch repository. | |||||
| func WatchRepo(userID, repoID int64, watch bool) (err error) { | |||||
| return watchRepo(x, userID, repoID, watch) | |||||
| } | |||||
| func getWatchers(e Engine, repoID int64) ([]*Watch, error) { | |||||
| watches := make([]*Watch, 0, 10) | |||||
| return watches, e.Find(&watches, &Watch{RepoID: repoID}) | |||||
| } | |||||
| // GetWatchers returns all watchers of given repository. | |||||
| func GetWatchers(repoID int64) ([]*Watch, error) { | |||||
| return getWatchers(x, repoID) | |||||
| } | |||||
| // GetWatchers returns range of users watching given repository. | |||||
| func (repo *Repository) GetWatchers(page int) ([]*User, error) { | |||||
| users := make([]*User, 0, ItemsPerPage) | |||||
| sess := x.Where("watch.repo_id=?", repo.ID). | |||||
| Join("LEFT", "watch", "`user`.id=`watch`.user_id") | |||||
| if page > 0 { | |||||
| sess = sess.Limit(ItemsPerPage, (page-1)*ItemsPerPage) | |||||
| } | |||||
| return users, sess.Find(&users) | |||||
| } | |||||
| func notifyWatchers(e Engine, act *Action) error { | |||||
| // Add feeds for user self and all watchers. | |||||
| watches, err := getWatchers(e, act.RepoID) | |||||
| if err != nil { | |||||
| return fmt.Errorf("get watchers: %v", err) | |||||
| } | |||||
| // Add feed for actioner. | |||||
| act.UserID = act.ActUserID | |||||
| if _, err = e.InsertOne(act); err != nil { | |||||
| return fmt.Errorf("insert new actioner: %v", err) | |||||
| } | |||||
| for i := range watches { | |||||
| if act.ActUserID == watches[i].UserID { | |||||
| continue | |||||
| } | |||||
| act.ID = 0 | |||||
| act.UserID = watches[i].UserID | |||||
| if _, err = e.InsertOne(act); err != nil { | |||||
| return fmt.Errorf("insert new action: %v", err) | |||||
| } | |||||
| } | |||||
| return nil | |||||
| } | |||||
| // NotifyWatchers creates batch of actions for every watcher. | |||||
| func NotifyWatchers(act *Action) error { | |||||
| return notifyWatchers(x, act) | |||||
| } | |||||
| @@ -0,0 +1,98 @@ | |||||
| // Copyright 2017 The Gitea 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 TestIsWatching(t *testing.T) { | |||||
| assert.NoError(t, PrepareTestDatabase()) | |||||
| assert.True(t, IsWatching(1, 1)) | |||||
| assert.True(t, IsWatching(4, 1)) | |||||
| assert.False(t, IsWatching(1, 5)) | |||||
| assert.False(t, IsWatching(NonexistentID, NonexistentID)) | |||||
| } | |||||
| func TestWatchRepo(t *testing.T) { | |||||
| assert.NoError(t, PrepareTestDatabase()) | |||||
| const repoID = 3 | |||||
| const userID = 2 | |||||
| assert.NoError(t, WatchRepo(userID, repoID, true)) | |||||
| AssertExistsAndLoadBean(t, &Watch{RepoID: repoID, UserID: userID}) | |||||
| CheckConsistencyFor(t, &Repository{ID: repoID}) | |||||
| assert.NoError(t, WatchRepo(userID, repoID, false)) | |||||
| AssertNotExistsBean(t, &Watch{RepoID: repoID, UserID: userID}) | |||||
| CheckConsistencyFor(t, &Repository{ID: repoID}) | |||||
| } | |||||
| func TestGetWatchers(t *testing.T) { | |||||
| assert.NoError(t, PrepareTestDatabase()) | |||||
| repo := AssertExistsAndLoadBean(t, &Repository{ID: 1}).(*Repository) | |||||
| watches, err := GetWatchers(repo.ID) | |||||
| assert.NoError(t, err) | |||||
| assert.Len(t, watches, repo.NumWatches) | |||||
| for _, watch := range watches { | |||||
| assert.EqualValues(t, repo.ID, watch.RepoID) | |||||
| } | |||||
| watches, err = GetWatchers(NonexistentID) | |||||
| assert.NoError(t, err) | |||||
| assert.Len(t, watches, 0) | |||||
| } | |||||
| func TestRepository_GetWatchers(t *testing.T) { | |||||
| assert.NoError(t, PrepareTestDatabase()) | |||||
| repo := AssertExistsAndLoadBean(t, &Repository{ID: 1}).(*Repository) | |||||
| watchers, err := repo.GetWatchers(1) | |||||
| assert.NoError(t, err) | |||||
| assert.Len(t, watchers, repo.NumWatches) | |||||
| for _, watcher := range watchers { | |||||
| AssertExistsAndLoadBean(t, &Watch{UserID: watcher.ID, RepoID: repo.ID}) | |||||
| } | |||||
| repo = AssertExistsAndLoadBean(t, &Repository{ID: 10}).(*Repository) | |||||
| watchers, err = repo.GetWatchers(1) | |||||
| assert.NoError(t, err) | |||||
| assert.Len(t, watchers, 0) | |||||
| } | |||||
| func TestNotifyWatchers(t *testing.T) { | |||||
| assert.NoError(t, PrepareTestDatabase()) | |||||
| action := &Action{ | |||||
| ActUserID: 8, | |||||
| RepoID: 1, | |||||
| OpType: ActionStarRepo, | |||||
| } | |||||
| assert.NoError(t, NotifyWatchers(action)) | |||||
| AssertExistsAndLoadBean(t, &Action{ | |||||
| ActUserID: action.ActUserID, | |||||
| UserID: 1, | |||||
| RepoID: action.RepoID, | |||||
| OpType: action.OpType, | |||||
| }) | |||||
| AssertExistsAndLoadBean(t, &Action{ | |||||
| ActUserID: action.ActUserID, | |||||
| UserID: 4, | |||||
| RepoID: action.RepoID, | |||||
| OpType: action.OpType, | |||||
| }) | |||||
| AssertExistsAndLoadBean(t, &Action{ | |||||
| ActUserID: action.ActUserID, | |||||
| UserID: 8, | |||||
| RepoID: action.RepoID, | |||||
| OpType: action.OpType, | |||||
| }) | |||||
| } | |||||