Skip to content

Commit 183e5b3

Browse files
6543Stelios Malathouras
authored and
Stelios Malathouras
committed
RSS/Atom support for Repos (go-gitea#19055)
* support for repos * refactor * advertise the feeds via meta tags * allow feed suffix and feed header * optimize performance
1 parent b9645ee commit 183e5b3

File tree

14 files changed

+188
-110
lines changed

14 files changed

+188
-110
lines changed

models/action.go

+4-3
Original file line numberDiff line numberDiff line change
@@ -328,7 +328,7 @@ type GetFeedsOptions struct {
328328
}
329329

330330
// GetFeeds returns actions according to the provided options
331-
func GetFeeds(opts GetFeedsOptions) ([]*Action, error) {
331+
func GetFeeds(ctx context.Context, opts GetFeedsOptions) (ActionList, error) {
332332
if opts.RequestedUser == nil && opts.RequestedTeam == nil && opts.RequestedRepo == nil {
333333
return nil, fmt.Errorf("need at least one of these filters: RequestedUser, RequestedTeam, RequestedRepo")
334334
}
@@ -338,7 +338,8 @@ func GetFeeds(opts GetFeedsOptions) ([]*Action, error) {
338338
return nil, err
339339
}
340340

341-
sess := db.GetEngine(db.DefaultContext).Where(cond)
341+
e := db.GetEngine(ctx)
342+
sess := e.Where(cond)
342343

343344
opts.SetDefaultValues()
344345
sess = db.SetSessionPagination(sess, &opts)
@@ -349,7 +350,7 @@ func GetFeeds(opts GetFeedsOptions) ([]*Action, error) {
349350
return nil, fmt.Errorf("Find: %v", err)
350351
}
351352

352-
if err := ActionList(actions).LoadAttributes(); err != nil {
353+
if err := ActionList(actions).loadAttributes(e); err != nil {
353354
return nil, fmt.Errorf("LoadAttributes: %v", err)
354355
}
355356

models/action_list.go

+35-28
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ func (actions ActionList) getUserIDs() []int64 {
2525
return keysInt64(userIDs)
2626
}
2727

28-
func (actions ActionList) loadUsers(e db.Engine) ([]*user_model.User, error) {
28+
func (actions ActionList) loadUsers(e db.Engine) (map[int64]*user_model.User, error) {
2929
if len(actions) == 0 {
3030
return nil, nil
3131
}
@@ -42,12 +42,7 @@ func (actions ActionList) loadUsers(e db.Engine) ([]*user_model.User, error) {
4242
for _, action := range actions {
4343
action.ActUser = userMaps[action.ActUserID]
4444
}
45-
return valuesUser(userMaps), nil
46-
}
47-
48-
// LoadUsers loads actions' all users
49-
func (actions ActionList) LoadUsers() ([]*user_model.User, error) {
50-
return actions.loadUsers(db.GetEngine(db.DefaultContext))
45+
return userMaps, nil
5146
}
5247

5348
func (actions ActionList) getRepoIDs() []int64 {
@@ -60,45 +55,57 @@ func (actions ActionList) getRepoIDs() []int64 {
6055
return keysInt64(repoIDs)
6156
}
6257

63-
func (actions ActionList) loadRepositories(e db.Engine) ([]*repo_model.Repository, error) {
58+
func (actions ActionList) loadRepositories(e db.Engine) error {
6459
if len(actions) == 0 {
65-
return nil, nil
60+
return nil
6661
}
6762

6863
repoIDs := actions.getRepoIDs()
6964
repoMaps := make(map[int64]*repo_model.Repository, len(repoIDs))
70-
err := e.
71-
In("id", repoIDs).
72-
Find(&repoMaps)
65+
err := e.In("id", repoIDs).Find(&repoMaps)
7366
if err != nil {
74-
return nil, fmt.Errorf("find repository: %v", err)
67+
return fmt.Errorf("find repository: %v", err)
7568
}
7669

7770
for _, action := range actions {
7871
action.Repo = repoMaps[action.RepoID]
7972
}
80-
return valuesRepository(repoMaps), nil
81-
}
82-
83-
// LoadRepositories loads actions' all repositories
84-
func (actions ActionList) LoadRepositories() ([]*repo_model.Repository, error) {
85-
return actions.loadRepositories(db.GetEngine(db.DefaultContext))
73+
return nil
8674
}
8775

88-
// loadAttributes loads all attributes
89-
func (actions ActionList) loadAttributes(e db.Engine) (err error) {
90-
if _, err = actions.loadUsers(e); err != nil {
91-
return
76+
func (actions ActionList) loadRepoOwner(e db.Engine, userMap map[int64]*user_model.User) (err error) {
77+
if userMap == nil {
78+
userMap = make(map[int64]*user_model.User)
9279
}
9380

94-
if _, err = actions.loadRepositories(e); err != nil {
95-
return
81+
for _, action := range actions {
82+
repoOwner, ok := userMap[action.Repo.OwnerID]
83+
if !ok {
84+
repoOwner, err = user_model.GetUserByID(action.Repo.OwnerID)
85+
if err != nil {
86+
if user_model.IsErrUserNotExist(err) {
87+
continue
88+
}
89+
return err
90+
}
91+
userMap[repoOwner.ID] = repoOwner
92+
}
93+
action.Repo.Owner = repoOwner
9694
}
9795

9896
return nil
9997
}
10098

101-
// LoadAttributes loads attributes of the actions
102-
func (actions ActionList) LoadAttributes() error {
103-
return actions.loadAttributes(db.GetEngine(db.DefaultContext))
99+
// loadAttributes loads all attributes
100+
func (actions ActionList) loadAttributes(e db.Engine) error {
101+
userMap, err := actions.loadUsers(e)
102+
if err != nil {
103+
return err
104+
}
105+
106+
if err := actions.loadRepositories(e); err != nil {
107+
return err
108+
}
109+
110+
return actions.loadRepoOwner(e, userMap)
104111
}

models/action_test.go

+46-4
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"path"
99
"testing"
1010

11+
"code.gitea.io/gitea/models/db"
1112
repo_model "code.gitea.io/gitea/models/repo"
1213
"code.gitea.io/gitea/models/unittest"
1314
user_model "code.gitea.io/gitea/models/user"
@@ -39,7 +40,7 @@ func TestGetFeeds(t *testing.T) {
3940
assert.NoError(t, unittest.PrepareTestDatabase())
4041
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}).(*user_model.User)
4142

42-
actions, err := GetFeeds(GetFeedsOptions{
43+
actions, err := GetFeeds(db.DefaultContext, GetFeedsOptions{
4344
RequestedUser: user,
4445
Actor: user,
4546
IncludePrivate: true,
@@ -52,7 +53,7 @@ func TestGetFeeds(t *testing.T) {
5253
assert.EqualValues(t, user.ID, actions[0].UserID)
5354
}
5455

55-
actions, err = GetFeeds(GetFeedsOptions{
56+
actions, err = GetFeeds(db.DefaultContext, GetFeedsOptions{
5657
RequestedUser: user,
5758
Actor: user,
5859
IncludePrivate: false,
@@ -62,13 +63,54 @@ func TestGetFeeds(t *testing.T) {
6263
assert.Len(t, actions, 0)
6364
}
6465

66+
func TestGetFeedsForRepos(t *testing.T) {
67+
assert.NoError(t, unittest.PrepareTestDatabase())
68+
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}).(*user_model.User)
69+
privRepo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 2}).(*repo_model.Repository)
70+
pubRepo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 8}).(*repo_model.Repository)
71+
72+
// private repo & no login
73+
actions, err := GetFeeds(db.DefaultContext, GetFeedsOptions{
74+
RequestedRepo: privRepo,
75+
IncludePrivate: true,
76+
})
77+
assert.NoError(t, err)
78+
assert.Len(t, actions, 0)
79+
80+
// public repo & no login
81+
actions, err = GetFeeds(db.DefaultContext, GetFeedsOptions{
82+
RequestedRepo: pubRepo,
83+
IncludePrivate: true,
84+
})
85+
assert.NoError(t, err)
86+
assert.Len(t, actions, 1)
87+
88+
// private repo and login
89+
actions, err = GetFeeds(db.DefaultContext, GetFeedsOptions{
90+
RequestedRepo: privRepo,
91+
IncludePrivate: true,
92+
Actor: user,
93+
})
94+
assert.NoError(t, err)
95+
assert.Len(t, actions, 1)
96+
97+
// public repo & login
98+
actions, err = GetFeeds(db.DefaultContext, GetFeedsOptions{
99+
RequestedRepo: pubRepo,
100+
IncludePrivate: true,
101+
Actor: user,
102+
})
103+
assert.NoError(t, err)
104+
assert.Len(t, actions, 1)
105+
}
106+
65107
func TestGetFeeds2(t *testing.T) {
66108
// test with an organization user
67109
assert.NoError(t, unittest.PrepareTestDatabase())
68110
org := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 3}).(*user_model.User)
69111
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}).(*user_model.User)
70112

71-
actions, err := GetFeeds(GetFeedsOptions{
113+
actions, err := GetFeeds(db.DefaultContext, GetFeedsOptions{
72114
RequestedUser: org,
73115
Actor: user,
74116
IncludePrivate: true,
@@ -82,7 +124,7 @@ func TestGetFeeds2(t *testing.T) {
82124
assert.EqualValues(t, org.ID, actions[0].UserID)
83125
}
84126

85-
actions, err = GetFeeds(GetFeedsOptions{
127+
actions, err = GetFeeds(db.DefaultContext, GetFeedsOptions{
86128
RequestedUser: org,
87129
Actor: user,
88130
IncludePrivate: false,

models/fixtures/action.yml

+7-7
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
user_id: 2
44
op_type: 12 # close issue
55
act_user_id: 2
6-
repo_id: 2
6+
repo_id: 2 # private
77
is_private: true
88
created_unix: 1603228283
99

@@ -12,7 +12,7 @@
1212
user_id: 3
1313
op_type: 2 # rename repo
1414
act_user_id: 2
15-
repo_id: 3
15+
repo_id: 3 # private
1616
is_private: true
1717
content: oldRepoName
1818

@@ -21,38 +21,38 @@
2121
user_id: 11
2222
op_type: 1 # create repo
2323
act_user_id: 11
24-
repo_id: 9
24+
repo_id: 9 # public
2525
is_private: false
2626

2727
-
2828
id: 4
2929
user_id: 16
3030
op_type: 12 # close issue
3131
act_user_id: 16
32-
repo_id: 22
32+
repo_id: 22 # private
3333
is_private: true
3434
created_unix: 1603267920
3535

3636
- id: 5
3737
user_id: 10
3838
op_type: 1 # create repo
3939
act_user_id: 10
40-
repo_id: 6
40+
repo_id: 6 # private
4141
is_private: true
4242
created_unix: 1603010100
4343

4444
- id: 6
4545
user_id: 10
4646
op_type: 1 # create repo
4747
act_user_id: 10
48-
repo_id: 7
48+
repo_id: 7 # private
4949
is_private: true
5050
created_unix: 1603011300
5151

5252
- id: 7
5353
user_id: 10
5454
op_type: 1 # create repo
5555
act_user_id: 10
56-
repo_id: 8
56+
repo_id: 8 # public
5757
is_private: false
5858
created_unix: 1603011540 # grouped with id:7

models/user_heatmap_test.go

+2-1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"testing"
1010
"time"
1111

12+
"code.gitea.io/gitea/models/db"
1213
"code.gitea.io/gitea/models/unittest"
1314
user_model "code.gitea.io/gitea/models/user"
1415
"code.gitea.io/gitea/modules/json"
@@ -72,7 +73,7 @@ func TestGetUserHeatmapDataByUser(t *testing.T) {
7273
}
7374

7475
// get the action for comparison
75-
actions, err := GetFeeds(GetFeedsOptions{
76+
actions, err := GetFeeds(db.DefaultContext, GetFeedsOptions{
7677
RequestedUser: user,
7778
Actor: doer,
7879
IncludePrivate: true,

modules/auth/pam/pam.go

+3-3
Original file line numberDiff line numberDiff line change
@@ -34,10 +34,10 @@ func Auth(serviceName, userName, passwd string) (string, error) {
3434
if err = t.Authenticate(0); err != nil {
3535
return "", err
3636
}
37-
37+
3838
if err = t.AcctMgmt(0); err != nil {
39-
return "", err
40-
}
39+
return "", err
40+
}
4141

4242
// PAM login names might suffer transformations in the PAM stack.
4343
// We should take whatever the PAM stack returns for it.

modules/context/repo.go

+2
Original file line numberDiff line numberDiff line change
@@ -418,6 +418,8 @@ func RepoAssignment(ctx *Context) (cancel context.CancelFunc) {
418418
userName := ctx.Params(":username")
419419
repoName := ctx.Params(":reponame")
420420
repoName = strings.TrimSuffix(repoName, ".git")
421+
repoName = strings.TrimSuffix(repoName, ".rss")
422+
repoName = strings.TrimSuffix(repoName, ".atom")
421423

422424
// Check if the user is the same as the repository owner
423425
if ctx.IsSigned && ctx.User.LowerName == strings.ToLower(userName) {

routers/web/feed/convert.go

+17-1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ package feed
77
import (
88
"fmt"
99
"html"
10+
"net/http"
1011
"net/url"
1112
"strconv"
1213
"strings"
@@ -66,7 +67,7 @@ func renderMarkdown(ctx *context.Context, act *models.Action, content string) st
6667
}
6768

6869
// feedActionsToFeedItems convert gitea's Action feed to feeds Item
69-
func feedActionsToFeedItems(ctx *context.Context, actions []*models.Action) (items []*feeds.Item, err error) {
70+
func feedActionsToFeedItems(ctx *context.Context, actions models.ActionList) (items []*feeds.Item, err error) {
7071
for _, act := range actions {
7172
act.LoadActUser()
7273

@@ -247,3 +248,18 @@ func feedActionsToFeedItems(ctx *context.Context, actions []*models.Action) (ite
247248
}
248249
return
249250
}
251+
252+
// GetFeedType return if it is a feed request and altered name and feed type.
253+
func GetFeedType(name string, req *http.Request) (bool, string, string) {
254+
if strings.HasSuffix(name, ".rss") ||
255+
strings.Contains(req.Header.Get("Accept"), "application/rss+xml") {
256+
return true, strings.TrimSuffix(name, ".rss"), "rss"
257+
}
258+
259+
if strings.HasSuffix(name, ".atom") ||
260+
strings.Contains(req.Header.Get("Accept"), "application/atom+xml") {
261+
return true, strings.TrimSuffix(name, ".atom"), "atom"
262+
}
263+
264+
return false, name, ""
265+
}

0 commit comments

Comments
 (0)