Skip to content

Commit cc98737

Browse files
authored
RSS/Atom support for Orgs (#17714)
part of #569
1 parent 5fdd304 commit cc98737

File tree

5 files changed

+154
-85
lines changed

5 files changed

+154
-85
lines changed

models/action.go

+54-40
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import (
2222
"code.gitea.io/gitea/modules/git"
2323
"code.gitea.io/gitea/modules/log"
2424
"code.gitea.io/gitea/modules/setting"
25+
"code.gitea.io/gitea/modules/structs"
2526
"code.gitea.io/gitea/modules/timeutil"
2627
"code.gitea.io/gitea/modules/util"
2728

@@ -315,29 +316,36 @@ func (a *Action) GetIssueContent() string {
315316

316317
// GetFeedsOptions options for retrieving feeds
317318
type GetFeedsOptions struct {
318-
RequestedUser *user_model.User // the user we want activity for
319-
RequestedTeam *Team // the team we want activity for
320-
Actor *user_model.User // the user viewing the activity
321-
IncludePrivate bool // include private actions
322-
OnlyPerformedBy bool // only actions performed by requested user
323-
IncludeDeleted bool // include deleted actions
324-
Date string // the day we want activity for: YYYY-MM-DD
319+
db.ListOptions
320+
RequestedUser *user_model.User // the user we want activity for
321+
RequestedTeam *Team // the team we want activity for
322+
RequestedRepo *repo_model.Repository // the repo we want activity for
323+
Actor *user_model.User // the user viewing the activity
324+
IncludePrivate bool // include private actions
325+
OnlyPerformedBy bool // only actions performed by requested user
326+
IncludeDeleted bool // include deleted actions
327+
Date string // the day we want activity for: YYYY-MM-DD
325328
}
326329

327330
// GetFeeds returns actions according to the provided options
328331
func GetFeeds(opts GetFeedsOptions) ([]*Action, error) {
329-
if !activityReadable(opts.RequestedUser, opts.Actor) {
330-
return make([]*Action, 0), nil
332+
if opts.RequestedUser == nil && opts.RequestedTeam == nil && opts.RequestedRepo == nil {
333+
return nil, fmt.Errorf("need at least one of these filters: RequestedUser, RequestedTeam, RequestedRepo")
331334
}
332335

333336
cond, err := activityQueryCondition(opts)
334337
if err != nil {
335338
return nil, err
336339
}
337340

338-
actions := make([]*Action, 0, setting.UI.FeedPagingNum)
341+
sess := db.GetEngine(db.DefaultContext).Where(cond)
339342

340-
if err := db.GetEngine(db.DefaultContext).Limit(setting.UI.FeedPagingNum).Desc("created_unix").Where(cond).Find(&actions); err != nil {
343+
opts.SetDefaultValues()
344+
sess = db.SetSessionPagination(sess, &opts)
345+
346+
actions := make([]*Action, 0, opts.PageSize)
347+
348+
if err := sess.Desc("created_unix").Find(&actions); err != nil {
341349
return nil, fmt.Errorf("Find: %v", err)
342350
}
343351

@@ -349,41 +357,44 @@ func GetFeeds(opts GetFeedsOptions) ([]*Action, error) {
349357
}
350358

351359
func activityReadable(user, doer *user_model.User) bool {
352-
var doerID int64
353-
if doer != nil {
354-
doerID = doer.ID
355-
}
356-
if doer == nil || !doer.IsAdmin {
357-
if user.KeepActivityPrivate && doerID != user.ID {
358-
return false
359-
}
360-
}
361-
return true
360+
return !user.KeepActivityPrivate ||
361+
doer != nil && (doer.IsAdmin || user.ID == doer.ID)
362362
}
363363

364364
func activityQueryCondition(opts GetFeedsOptions) (builder.Cond, error) {
365365
cond := builder.NewCond()
366366

367-
var repoIDs []int64
368-
var actorID int64
369-
if opts.Actor != nil {
370-
actorID = opts.Actor.ID
367+
if opts.RequestedTeam != nil && opts.RequestedUser == nil {
368+
org, err := user_model.GetUserByID(opts.RequestedTeam.OrgID)
369+
if err != nil {
370+
return nil, err
371+
}
372+
opts.RequestedUser = org
373+
}
374+
375+
// check activity visibility for actor ( similar to activityReadable() )
376+
if opts.Actor == nil {
377+
cond = cond.And(builder.In("act_user_id",
378+
builder.Select("`user`.id").Where(
379+
builder.Eq{"keep_activity_private": false, "visibility": structs.VisibleTypePublic},
380+
).From("`user`"),
381+
))
382+
} else if !opts.Actor.IsAdmin {
383+
cond = cond.And(builder.In("act_user_id",
384+
builder.Select("`user`.id").Where(
385+
builder.Eq{"keep_activity_private": false}.
386+
And(builder.In("visibility", structs.VisibleTypePublic, structs.VisibleTypeLimited))).
387+
Or(builder.Eq{"id": opts.Actor.ID}).From("`user`"),
388+
))
371389
}
372390

373391
// check readable repositories by doer/actor
374392
if opts.Actor == nil || !opts.Actor.IsAdmin {
375-
if opts.RequestedUser.IsOrganization() {
376-
env, err := OrgFromUser(opts.RequestedUser).AccessibleReposEnv(actorID)
377-
if err != nil {
378-
return nil, fmt.Errorf("AccessibleReposEnv: %v", err)
379-
}
380-
if repoIDs, err = env.RepoIDs(1, opts.RequestedUser.NumRepos); err != nil {
381-
return nil, fmt.Errorf("GetUserRepositories: %v", err)
382-
}
383-
cond = cond.And(builder.In("repo_id", repoIDs))
384-
} else {
385-
cond = cond.And(builder.In("repo_id", AccessibleRepoIDsQuery(opts.Actor)))
386-
}
393+
cond = cond.And(builder.In("repo_id", AccessibleRepoIDsQuery(opts.Actor)))
394+
}
395+
396+
if opts.RequestedRepo != nil {
397+
cond = cond.And(builder.Eq{"repo_id": opts.RequestedRepo.ID})
387398
}
388399

389400
if opts.RequestedTeam != nil {
@@ -395,11 +406,14 @@ func activityQueryCondition(opts GetFeedsOptions) (builder.Cond, error) {
395406
cond = cond.And(builder.In("repo_id", teamRepoIDs))
396407
}
397408

398-
cond = cond.And(builder.Eq{"user_id": opts.RequestedUser.ID})
409+
if opts.RequestedUser != nil {
410+
cond = cond.And(builder.Eq{"user_id": opts.RequestedUser.ID})
399411

400-
if opts.OnlyPerformedBy {
401-
cond = cond.And(builder.Eq{"act_user_id": opts.RequestedUser.ID})
412+
if opts.OnlyPerformedBy {
413+
cond = cond.And(builder.Eq{"act_user_id": opts.RequestedUser.ID})
414+
}
402415
}
416+
403417
if !opts.IncludePrivate {
404418
cond = cond.And(builder.Eq{"is_private": false})
405419
}

models/action_test.go

+40
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,46 @@ func TestGetFeeds2(t *testing.T) {
9393
assert.Len(t, actions, 0)
9494
}
9595

96+
func TestActivityReadable(t *testing.T) {
97+
tt := []struct {
98+
desc string
99+
user *user_model.User
100+
doer *user_model.User
101+
result bool
102+
}{{
103+
desc: "user should see own activity",
104+
user: &user_model.User{ID: 1},
105+
doer: &user_model.User{ID: 1},
106+
result: true,
107+
}, {
108+
desc: "anon should see activity if public",
109+
user: &user_model.User{ID: 1},
110+
result: true,
111+
}, {
112+
desc: "anon should NOT see activity",
113+
user: &user_model.User{ID: 1, KeepActivityPrivate: true},
114+
result: false,
115+
}, {
116+
desc: "user should see own activity if private too",
117+
user: &user_model.User{ID: 1, KeepActivityPrivate: true},
118+
doer: &user_model.User{ID: 1},
119+
result: true,
120+
}, {
121+
desc: "other user should NOT see activity",
122+
user: &user_model.User{ID: 1, KeepActivityPrivate: true},
123+
doer: &user_model.User{ID: 2},
124+
result: false,
125+
}, {
126+
desc: "admin should see activity",
127+
user: &user_model.User{ID: 1, KeepActivityPrivate: true},
128+
doer: &user_model.User{ID: 2, IsAdmin: true},
129+
result: true,
130+
}}
131+
for _, test := range tt {
132+
assert.Equal(t, test.result, activityReadable(test.user, test.doer), test.desc)
133+
}
134+
}
135+
96136
func TestNotifyWatchers(t *testing.T) {
97137
assert.NoError(t, unittest.PrepareTestDatabase())
98138

models/user_heatmap_test.go

+31-16
Original file line numberDiff line numberDiff line change
@@ -19,25 +19,40 @@ import (
1919

2020
func TestGetUserHeatmapDataByUser(t *testing.T) {
2121
testCases := []struct {
22+
desc string
2223
userID int64
2324
doerID int64
2425
CountResult int
2526
JSONResult string
2627
}{
27-
// self looks at action in private repo
28-
{2, 2, 1, `[{"timestamp":1603227600,"contributions":1}]`},
29-
// admin looks at action in private repo
30-
{2, 1, 1, `[{"timestamp":1603227600,"contributions":1}]`},
31-
// other user looks at action in private repo
32-
{2, 3, 0, `[]`},
33-
// nobody looks at action in private repo
34-
{2, 0, 0, `[]`},
35-
// collaborator looks at action in private repo
36-
{16, 15, 1, `[{"timestamp":1603267200,"contributions":1}]`},
37-
// no action action not performed by target user
38-
{3, 3, 0, `[]`},
39-
// multiple actions performed with two grouped together
40-
{10, 10, 3, `[{"timestamp":1603009800,"contributions":1},{"timestamp":1603010700,"contributions":2}]`},
28+
{
29+
"self looks at action in private repo",
30+
2, 2, 1, `[{"timestamp":1603227600,"contributions":1}]`,
31+
},
32+
{
33+
"admin looks at action in private repo",
34+
2, 1, 1, `[{"timestamp":1603227600,"contributions":1}]`,
35+
},
36+
{
37+
"other user looks at action in private repo",
38+
2, 3, 0, `[]`,
39+
},
40+
{
41+
"nobody looks at action in private repo",
42+
2, 0, 0, `[]`,
43+
},
44+
{
45+
"collaborator looks at action in private repo",
46+
16, 15, 1, `[{"timestamp":1603267200,"contributions":1}]`,
47+
},
48+
{
49+
"no action action not performed by target user",
50+
3, 3, 0, `[]`,
51+
},
52+
{
53+
"multiple actions performed with two grouped together",
54+
10, 10, 3, `[{"timestamp":1603009800,"contributions":1},{"timestamp":1603010700,"contributions":2}]`,
55+
},
4156
}
4257
// Prepare
4358
assert.NoError(t, unittest.PrepareTestDatabase())
@@ -46,7 +61,7 @@ func TestGetUserHeatmapDataByUser(t *testing.T) {
4661
timeutil.Set(time.Date(2021, 1, 1, 0, 0, 0, 0, time.UTC))
4762
defer timeutil.Unset()
4863

49-
for i, tc := range testCases {
64+
for _, tc := range testCases {
5065
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: tc.userID}).(*user_model.User)
5166

5267
doer := &user_model.User{ID: tc.doerID}
@@ -74,7 +89,7 @@ func TestGetUserHeatmapDataByUser(t *testing.T) {
7489
}
7590
assert.NoError(t, err)
7691
assert.Len(t, actions, contributions, "invalid action count: did the test data became too old?")
77-
assert.Equal(t, tc.CountResult, contributions, fmt.Sprintf("testcase %d", i))
92+
assert.Equal(t, tc.CountResult, contributions, fmt.Sprintf("testcase '%s'", tc.desc))
7893

7994
// Test JSON rendering
8095
jsonData, err := json.Marshal(heatmap)

routers/web/feed/profile.go

+24-21
Original file line numberDiff line numberDiff line change
@@ -23,31 +23,34 @@ func RetrieveFeeds(ctx *context.Context, options models.GetFeedsOptions) []*mode
2323
return nil
2424
}
2525

26-
userCache := map[int64]*user_model.User{options.RequestedUser.ID: options.RequestedUser}
27-
if ctx.User != nil {
28-
userCache[ctx.User.ID] = ctx.User
29-
}
30-
for _, act := range actions {
31-
if act.ActUser != nil {
32-
userCache[act.ActUserID] = act.ActUser
26+
// TODO: move load repoOwner of act.Repo into models.GetFeeds->loadAttributes()
27+
{
28+
userCache := map[int64]*user_model.User{options.RequestedUser.ID: options.RequestedUser}
29+
if ctx.User != nil {
30+
userCache[ctx.User.ID] = ctx.User
3331
}
34-
}
35-
36-
for _, act := range actions {
37-
repoOwner, ok := userCache[act.Repo.OwnerID]
38-
if !ok {
39-
repoOwner, err = user_model.GetUserByID(act.Repo.OwnerID)
40-
if err != nil {
41-
if user_model.IsErrUserNotExist(err) {
42-
continue
32+
for _, act := range actions {
33+
if act.ActUser != nil {
34+
userCache[act.ActUserID] = act.ActUser
35+
}
36+
}
37+
for _, act := range actions {
38+
repoOwner, ok := userCache[act.Repo.OwnerID]
39+
if !ok {
40+
repoOwner, err = user_model.GetUserByID(act.Repo.OwnerID)
41+
if err != nil {
42+
if user_model.IsErrUserNotExist(err) {
43+
continue
44+
}
45+
ctx.ServerError("GetUserByID", err)
46+
return nil
4347
}
44-
ctx.ServerError("GetUserByID", err)
45-
return nil
48+
userCache[repoOwner.ID] = repoOwner
4649
}
47-
userCache[repoOwner.ID] = repoOwner
50+
act.Repo.Owner = repoOwner
4851
}
49-
act.Repo.Owner = repoOwner
5052
}
53+
5154
return actions
5255
}
5356

@@ -57,7 +60,7 @@ func ShowUserFeed(ctx *context.Context, ctxUser *user_model.User, formatType str
5760
RequestedUser: ctxUser,
5861
Actor: ctx.User,
5962
IncludePrivate: false,
60-
OnlyPerformedBy: true,
63+
OnlyPerformedBy: !ctxUser.IsOrganization(),
6164
IncludeDeleted: false,
6265
Date: ctx.FormString("date"),
6366
})

routers/web/user/profile.go

+5-8
Original file line numberDiff line numberDiff line change
@@ -94,14 +94,11 @@ func Profile(ctx *context.Context) {
9494
}
9595

9696
if ctxUser.IsOrganization() {
97-
/*
98-
// TODO: enable after rss.RetrieveFeeds() do handle org correctly
99-
// Show Org RSS feed
100-
if len(showFeedType) != 0 {
101-
rss.ShowUserFeed(ctx, ctxUser, showFeedType)
102-
return
103-
}
104-
*/
97+
// Show Org RSS feed
98+
if len(showFeedType) != 0 {
99+
feed.ShowUserFeed(ctx, ctxUser, showFeedType)
100+
return
101+
}
105102

106103
org.Home(ctx)
107104
return

0 commit comments

Comments
 (0)