@@ -33,6 +33,7 @@ | |||
num_closed_issues: 0 | |||
num_pulls: 0 | |||
num_closed_pulls: 0 | |||
num_watches: 0 | |||
- | |||
id: 4 |
@@ -2256,109 +2256,6 @@ func (repos MirrorRepositoryList) LoadAttributes() error { | |||
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, | |||
}) | |||
} |