mirror of
https://codeberg.org/forgejo/forgejo.git
synced 2024-12-26 06:03:58 +01:00
parent
5fdd30423e
commit
cc98737ca8
5 changed files with 156 additions and 87 deletions
|
@ -22,6 +22,7 @@ import (
|
|||
"code.gitea.io/gitea/modules/git"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/structs"
|
||||
"code.gitea.io/gitea/modules/timeutil"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
|
||||
|
@ -315,19 +316,21 @@ func (a *Action) GetIssueContent() string {
|
|||
|
||||
// GetFeedsOptions options for retrieving feeds
|
||||
type GetFeedsOptions struct {
|
||||
RequestedUser *user_model.User // the user we want activity for
|
||||
RequestedTeam *Team // the team we want activity for
|
||||
Actor *user_model.User // the user viewing the activity
|
||||
IncludePrivate bool // include private actions
|
||||
OnlyPerformedBy bool // only actions performed by requested user
|
||||
IncludeDeleted bool // include deleted actions
|
||||
Date string // the day we want activity for: YYYY-MM-DD
|
||||
db.ListOptions
|
||||
RequestedUser *user_model.User // the user we want activity for
|
||||
RequestedTeam *Team // the team we want activity for
|
||||
RequestedRepo *repo_model.Repository // the repo we want activity for
|
||||
Actor *user_model.User // the user viewing the activity
|
||||
IncludePrivate bool // include private actions
|
||||
OnlyPerformedBy bool // only actions performed by requested user
|
||||
IncludeDeleted bool // include deleted actions
|
||||
Date string // the day we want activity for: YYYY-MM-DD
|
||||
}
|
||||
|
||||
// GetFeeds returns actions according to the provided options
|
||||
func GetFeeds(opts GetFeedsOptions) ([]*Action, error) {
|
||||
if !activityReadable(opts.RequestedUser, opts.Actor) {
|
||||
return make([]*Action, 0), nil
|
||||
if opts.RequestedUser == nil && opts.RequestedTeam == nil && opts.RequestedRepo == nil {
|
||||
return nil, fmt.Errorf("need at least one of these filters: RequestedUser, RequestedTeam, RequestedRepo")
|
||||
}
|
||||
|
||||
cond, err := activityQueryCondition(opts)
|
||||
|
@ -335,9 +338,14 @@ func GetFeeds(opts GetFeedsOptions) ([]*Action, error) {
|
|||
return nil, err
|
||||
}
|
||||
|
||||
actions := make([]*Action, 0, setting.UI.FeedPagingNum)
|
||||
sess := db.GetEngine(db.DefaultContext).Where(cond)
|
||||
|
||||
if err := db.GetEngine(db.DefaultContext).Limit(setting.UI.FeedPagingNum).Desc("created_unix").Where(cond).Find(&actions); err != nil {
|
||||
opts.SetDefaultValues()
|
||||
sess = db.SetSessionPagination(sess, &opts)
|
||||
|
||||
actions := make([]*Action, 0, opts.PageSize)
|
||||
|
||||
if err := sess.Desc("created_unix").Find(&actions); err != nil {
|
||||
return nil, fmt.Errorf("Find: %v", err)
|
||||
}
|
||||
|
||||
|
@ -349,41 +357,44 @@ func GetFeeds(opts GetFeedsOptions) ([]*Action, error) {
|
|||
}
|
||||
|
||||
func activityReadable(user, doer *user_model.User) bool {
|
||||
var doerID int64
|
||||
if doer != nil {
|
||||
doerID = doer.ID
|
||||
}
|
||||
if doer == nil || !doer.IsAdmin {
|
||||
if user.KeepActivityPrivate && doerID != user.ID {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
return !user.KeepActivityPrivate ||
|
||||
doer != nil && (doer.IsAdmin || user.ID == doer.ID)
|
||||
}
|
||||
|
||||
func activityQueryCondition(opts GetFeedsOptions) (builder.Cond, error) {
|
||||
cond := builder.NewCond()
|
||||
|
||||
var repoIDs []int64
|
||||
var actorID int64
|
||||
if opts.Actor != nil {
|
||||
actorID = opts.Actor.ID
|
||||
if opts.RequestedTeam != nil && opts.RequestedUser == nil {
|
||||
org, err := user_model.GetUserByID(opts.RequestedTeam.OrgID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
opts.RequestedUser = org
|
||||
}
|
||||
|
||||
// check activity visibility for actor ( similar to activityReadable() )
|
||||
if opts.Actor == nil {
|
||||
cond = cond.And(builder.In("act_user_id",
|
||||
builder.Select("`user`.id").Where(
|
||||
builder.Eq{"keep_activity_private": false, "visibility": structs.VisibleTypePublic},
|
||||
).From("`user`"),
|
||||
))
|
||||
} else if !opts.Actor.IsAdmin {
|
||||
cond = cond.And(builder.In("act_user_id",
|
||||
builder.Select("`user`.id").Where(
|
||||
builder.Eq{"keep_activity_private": false}.
|
||||
And(builder.In("visibility", structs.VisibleTypePublic, structs.VisibleTypeLimited))).
|
||||
Or(builder.Eq{"id": opts.Actor.ID}).From("`user`"),
|
||||
))
|
||||
}
|
||||
|
||||
// check readable repositories by doer/actor
|
||||
if opts.Actor == nil || !opts.Actor.IsAdmin {
|
||||
if opts.RequestedUser.IsOrganization() {
|
||||
env, err := OrgFromUser(opts.RequestedUser).AccessibleReposEnv(actorID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("AccessibleReposEnv: %v", err)
|
||||
}
|
||||
if repoIDs, err = env.RepoIDs(1, opts.RequestedUser.NumRepos); err != nil {
|
||||
return nil, fmt.Errorf("GetUserRepositories: %v", err)
|
||||
}
|
||||
cond = cond.And(builder.In("repo_id", repoIDs))
|
||||
} else {
|
||||
cond = cond.And(builder.In("repo_id", AccessibleRepoIDsQuery(opts.Actor)))
|
||||
}
|
||||
cond = cond.And(builder.In("repo_id", AccessibleRepoIDsQuery(opts.Actor)))
|
||||
}
|
||||
|
||||
if opts.RequestedRepo != nil {
|
||||
cond = cond.And(builder.Eq{"repo_id": opts.RequestedRepo.ID})
|
||||
}
|
||||
|
||||
if opts.RequestedTeam != nil {
|
||||
|
@ -395,11 +406,14 @@ func activityQueryCondition(opts GetFeedsOptions) (builder.Cond, error) {
|
|||
cond = cond.And(builder.In("repo_id", teamRepoIDs))
|
||||
}
|
||||
|
||||
cond = cond.And(builder.Eq{"user_id": opts.RequestedUser.ID})
|
||||
if opts.RequestedUser != nil {
|
||||
cond = cond.And(builder.Eq{"user_id": opts.RequestedUser.ID})
|
||||
|
||||
if opts.OnlyPerformedBy {
|
||||
cond = cond.And(builder.Eq{"act_user_id": opts.RequestedUser.ID})
|
||||
if opts.OnlyPerformedBy {
|
||||
cond = cond.And(builder.Eq{"act_user_id": opts.RequestedUser.ID})
|
||||
}
|
||||
}
|
||||
|
||||
if !opts.IncludePrivate {
|
||||
cond = cond.And(builder.Eq{"is_private": false})
|
||||
}
|
||||
|
|
|
@ -93,6 +93,46 @@ func TestGetFeeds2(t *testing.T) {
|
|||
assert.Len(t, actions, 0)
|
||||
}
|
||||
|
||||
func TestActivityReadable(t *testing.T) {
|
||||
tt := []struct {
|
||||
desc string
|
||||
user *user_model.User
|
||||
doer *user_model.User
|
||||
result bool
|
||||
}{{
|
||||
desc: "user should see own activity",
|
||||
user: &user_model.User{ID: 1},
|
||||
doer: &user_model.User{ID: 1},
|
||||
result: true,
|
||||
}, {
|
||||
desc: "anon should see activity if public",
|
||||
user: &user_model.User{ID: 1},
|
||||
result: true,
|
||||
}, {
|
||||
desc: "anon should NOT see activity",
|
||||
user: &user_model.User{ID: 1, KeepActivityPrivate: true},
|
||||
result: false,
|
||||
}, {
|
||||
desc: "user should see own activity if private too",
|
||||
user: &user_model.User{ID: 1, KeepActivityPrivate: true},
|
||||
doer: &user_model.User{ID: 1},
|
||||
result: true,
|
||||
}, {
|
||||
desc: "other user should NOT see activity",
|
||||
user: &user_model.User{ID: 1, KeepActivityPrivate: true},
|
||||
doer: &user_model.User{ID: 2},
|
||||
result: false,
|
||||
}, {
|
||||
desc: "admin should see activity",
|
||||
user: &user_model.User{ID: 1, KeepActivityPrivate: true},
|
||||
doer: &user_model.User{ID: 2, IsAdmin: true},
|
||||
result: true,
|
||||
}}
|
||||
for _, test := range tt {
|
||||
assert.Equal(t, test.result, activityReadable(test.user, test.doer), test.desc)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNotifyWatchers(t *testing.T) {
|
||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||
|
||||
|
|
|
@ -19,25 +19,40 @@ import (
|
|||
|
||||
func TestGetUserHeatmapDataByUser(t *testing.T) {
|
||||
testCases := []struct {
|
||||
desc string
|
||||
userID int64
|
||||
doerID int64
|
||||
CountResult int
|
||||
JSONResult string
|
||||
}{
|
||||
// self looks at action in private repo
|
||||
{2, 2, 1, `[{"timestamp":1603227600,"contributions":1}]`},
|
||||
// admin looks at action in private repo
|
||||
{2, 1, 1, `[{"timestamp":1603227600,"contributions":1}]`},
|
||||
// other user looks at action in private repo
|
||||
{2, 3, 0, `[]`},
|
||||
// nobody looks at action in private repo
|
||||
{2, 0, 0, `[]`},
|
||||
// collaborator looks at action in private repo
|
||||
{16, 15, 1, `[{"timestamp":1603267200,"contributions":1}]`},
|
||||
// no action action not performed by target user
|
||||
{3, 3, 0, `[]`},
|
||||
// multiple actions performed with two grouped together
|
||||
{10, 10, 3, `[{"timestamp":1603009800,"contributions":1},{"timestamp":1603010700,"contributions":2}]`},
|
||||
{
|
||||
"self looks at action in private repo",
|
||||
2, 2, 1, `[{"timestamp":1603227600,"contributions":1}]`,
|
||||
},
|
||||
{
|
||||
"admin looks at action in private repo",
|
||||
2, 1, 1, `[{"timestamp":1603227600,"contributions":1}]`,
|
||||
},
|
||||
{
|
||||
"other user looks at action in private repo",
|
||||
2, 3, 0, `[]`,
|
||||
},
|
||||
{
|
||||
"nobody looks at action in private repo",
|
||||
2, 0, 0, `[]`,
|
||||
},
|
||||
{
|
||||
"collaborator looks at action in private repo",
|
||||
16, 15, 1, `[{"timestamp":1603267200,"contributions":1}]`,
|
||||
},
|
||||
{
|
||||
"no action action not performed by target user",
|
||||
3, 3, 0, `[]`,
|
||||
},
|
||||
{
|
||||
"multiple actions performed with two grouped together",
|
||||
10, 10, 3, `[{"timestamp":1603009800,"contributions":1},{"timestamp":1603010700,"contributions":2}]`,
|
||||
},
|
||||
}
|
||||
// Prepare
|
||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||
|
@ -46,7 +61,7 @@ func TestGetUserHeatmapDataByUser(t *testing.T) {
|
|||
timeutil.Set(time.Date(2021, 1, 1, 0, 0, 0, 0, time.UTC))
|
||||
defer timeutil.Unset()
|
||||
|
||||
for i, tc := range testCases {
|
||||
for _, tc := range testCases {
|
||||
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: tc.userID}).(*user_model.User)
|
||||
|
||||
doer := &user_model.User{ID: tc.doerID}
|
||||
|
@ -74,7 +89,7 @@ func TestGetUserHeatmapDataByUser(t *testing.T) {
|
|||
}
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, actions, contributions, "invalid action count: did the test data became too old?")
|
||||
assert.Equal(t, tc.CountResult, contributions, fmt.Sprintf("testcase %d", i))
|
||||
assert.Equal(t, tc.CountResult, contributions, fmt.Sprintf("testcase '%s'", tc.desc))
|
||||
|
||||
// Test JSON rendering
|
||||
jsonData, err := json.Marshal(heatmap)
|
||||
|
|
|
@ -23,31 +23,34 @@ func RetrieveFeeds(ctx *context.Context, options models.GetFeedsOptions) []*mode
|
|||
return nil
|
||||
}
|
||||
|
||||
userCache := map[int64]*user_model.User{options.RequestedUser.ID: options.RequestedUser}
|
||||
if ctx.User != nil {
|
||||
userCache[ctx.User.ID] = ctx.User
|
||||
}
|
||||
for _, act := range actions {
|
||||
if act.ActUser != nil {
|
||||
userCache[act.ActUserID] = act.ActUser
|
||||
// TODO: move load repoOwner of act.Repo into models.GetFeeds->loadAttributes()
|
||||
{
|
||||
userCache := map[int64]*user_model.User{options.RequestedUser.ID: options.RequestedUser}
|
||||
if ctx.User != nil {
|
||||
userCache[ctx.User.ID] = ctx.User
|
||||
}
|
||||
for _, act := range actions {
|
||||
if act.ActUser != nil {
|
||||
userCache[act.ActUserID] = act.ActUser
|
||||
}
|
||||
}
|
||||
for _, act := range actions {
|
||||
repoOwner, ok := userCache[act.Repo.OwnerID]
|
||||
if !ok {
|
||||
repoOwner, err = user_model.GetUserByID(act.Repo.OwnerID)
|
||||
if err != nil {
|
||||
if user_model.IsErrUserNotExist(err) {
|
||||
continue
|
||||
}
|
||||
ctx.ServerError("GetUserByID", err)
|
||||
return nil
|
||||
}
|
||||
userCache[repoOwner.ID] = repoOwner
|
||||
}
|
||||
act.Repo.Owner = repoOwner
|
||||
}
|
||||
}
|
||||
|
||||
for _, act := range actions {
|
||||
repoOwner, ok := userCache[act.Repo.OwnerID]
|
||||
if !ok {
|
||||
repoOwner, err = user_model.GetUserByID(act.Repo.OwnerID)
|
||||
if err != nil {
|
||||
if user_model.IsErrUserNotExist(err) {
|
||||
continue
|
||||
}
|
||||
ctx.ServerError("GetUserByID", err)
|
||||
return nil
|
||||
}
|
||||
userCache[repoOwner.ID] = repoOwner
|
||||
}
|
||||
act.Repo.Owner = repoOwner
|
||||
}
|
||||
return actions
|
||||
}
|
||||
|
||||
|
@ -57,7 +60,7 @@ func ShowUserFeed(ctx *context.Context, ctxUser *user_model.User, formatType str
|
|||
RequestedUser: ctxUser,
|
||||
Actor: ctx.User,
|
||||
IncludePrivate: false,
|
||||
OnlyPerformedBy: true,
|
||||
OnlyPerformedBy: !ctxUser.IsOrganization(),
|
||||
IncludeDeleted: false,
|
||||
Date: ctx.FormString("date"),
|
||||
})
|
||||
|
|
|
@ -94,14 +94,11 @@ func Profile(ctx *context.Context) {
|
|||
}
|
||||
|
||||
if ctxUser.IsOrganization() {
|
||||
/*
|
||||
// TODO: enable after rss.RetrieveFeeds() do handle org correctly
|
||||
// Show Org RSS feed
|
||||
if len(showFeedType) != 0 {
|
||||
rss.ShowUserFeed(ctx, ctxUser, showFeedType)
|
||||
return
|
||||
}
|
||||
*/
|
||||
// Show Org RSS feed
|
||||
if len(showFeedType) != 0 {
|
||||
feed.ShowUserFeed(ctx, ctxUser, showFeedType)
|
||||
return
|
||||
}
|
||||
|
||||
org.Home(ctx)
|
||||
return
|
||||
|
|
Loading…
Reference in a new issue