RSS/Atom support for Repos (#19055)
* support for repos * refactor * advertise the feeds via meta tags * allow feed suffix and feed header * optimize performance
This commit is contained in:
parent
780cf76f6e
commit
bc0d2c8ada
14 changed files with 188 additions and 110 deletions
|
@ -328,7 +328,7 @@ type GetFeedsOptions struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetFeeds returns actions according to the provided options
|
// GetFeeds returns actions according to the provided options
|
||||||
func GetFeeds(opts GetFeedsOptions) ([]*Action, error) {
|
func GetFeeds(ctx context.Context, opts GetFeedsOptions) (ActionList, error) {
|
||||||
if opts.RequestedUser == nil && opts.RequestedTeam == nil && opts.RequestedRepo == 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")
|
return nil, fmt.Errorf("need at least one of these filters: RequestedUser, RequestedTeam, RequestedRepo")
|
||||||
}
|
}
|
||||||
|
@ -338,7 +338,8 @@ func GetFeeds(opts GetFeedsOptions) ([]*Action, error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
sess := db.GetEngine(db.DefaultContext).Where(cond)
|
e := db.GetEngine(ctx)
|
||||||
|
sess := e.Where(cond)
|
||||||
|
|
||||||
opts.SetDefaultValues()
|
opts.SetDefaultValues()
|
||||||
sess = db.SetSessionPagination(sess, &opts)
|
sess = db.SetSessionPagination(sess, &opts)
|
||||||
|
@ -349,7 +350,7 @@ func GetFeeds(opts GetFeedsOptions) ([]*Action, error) {
|
||||||
return nil, fmt.Errorf("Find: %v", err)
|
return nil, fmt.Errorf("Find: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := ActionList(actions).LoadAttributes(); err != nil {
|
if err := ActionList(actions).loadAttributes(e); err != nil {
|
||||||
return nil, fmt.Errorf("LoadAttributes: %v", err)
|
return nil, fmt.Errorf("LoadAttributes: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -25,7 +25,7 @@ func (actions ActionList) getUserIDs() []int64 {
|
||||||
return keysInt64(userIDs)
|
return keysInt64(userIDs)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (actions ActionList) loadUsers(e db.Engine) ([]*user_model.User, error) {
|
func (actions ActionList) loadUsers(e db.Engine) (map[int64]*user_model.User, error) {
|
||||||
if len(actions) == 0 {
|
if len(actions) == 0 {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
@ -42,12 +42,7 @@ func (actions ActionList) loadUsers(e db.Engine) ([]*user_model.User, error) {
|
||||||
for _, action := range actions {
|
for _, action := range actions {
|
||||||
action.ActUser = userMaps[action.ActUserID]
|
action.ActUser = userMaps[action.ActUserID]
|
||||||
}
|
}
|
||||||
return valuesUser(userMaps), nil
|
return userMaps, nil
|
||||||
}
|
|
||||||
|
|
||||||
// LoadUsers loads actions' all users
|
|
||||||
func (actions ActionList) LoadUsers() ([]*user_model.User, error) {
|
|
||||||
return actions.loadUsers(db.GetEngine(db.DefaultContext))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (actions ActionList) getRepoIDs() []int64 {
|
func (actions ActionList) getRepoIDs() []int64 {
|
||||||
|
@ -60,45 +55,57 @@ func (actions ActionList) getRepoIDs() []int64 {
|
||||||
return keysInt64(repoIDs)
|
return keysInt64(repoIDs)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (actions ActionList) loadRepositories(e db.Engine) ([]*repo_model.Repository, error) {
|
func (actions ActionList) loadRepositories(e db.Engine) error {
|
||||||
if len(actions) == 0 {
|
if len(actions) == 0 {
|
||||||
return nil, nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
repoIDs := actions.getRepoIDs()
|
repoIDs := actions.getRepoIDs()
|
||||||
repoMaps := make(map[int64]*repo_model.Repository, len(repoIDs))
|
repoMaps := make(map[int64]*repo_model.Repository, len(repoIDs))
|
||||||
err := e.
|
err := e.In("id", repoIDs).Find(&repoMaps)
|
||||||
In("id", repoIDs).
|
|
||||||
Find(&repoMaps)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("find repository: %v", err)
|
return fmt.Errorf("find repository: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, action := range actions {
|
for _, action := range actions {
|
||||||
action.Repo = repoMaps[action.RepoID]
|
action.Repo = repoMaps[action.RepoID]
|
||||||
}
|
}
|
||||||
return valuesRepository(repoMaps), nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// LoadRepositories loads actions' all repositories
|
func (actions ActionList) loadRepoOwner(e db.Engine, userMap map[int64]*user_model.User) (err error) {
|
||||||
func (actions ActionList) LoadRepositories() ([]*repo_model.Repository, error) {
|
if userMap == nil {
|
||||||
return actions.loadRepositories(db.GetEngine(db.DefaultContext))
|
userMap = make(map[int64]*user_model.User)
|
||||||
}
|
|
||||||
|
|
||||||
// loadAttributes loads all attributes
|
|
||||||
func (actions ActionList) loadAttributes(e db.Engine) (err error) {
|
|
||||||
if _, err = actions.loadUsers(e); err != nil {
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err = actions.loadRepositories(e); err != nil {
|
for _, action := range actions {
|
||||||
return
|
repoOwner, ok := userMap[action.Repo.OwnerID]
|
||||||
|
if !ok {
|
||||||
|
repoOwner, err = user_model.GetUserByID(action.Repo.OwnerID)
|
||||||
|
if err != nil {
|
||||||
|
if user_model.IsErrUserNotExist(err) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
userMap[repoOwner.ID] = repoOwner
|
||||||
|
}
|
||||||
|
action.Repo.Owner = repoOwner
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// LoadAttributes loads attributes of the actions
|
// loadAttributes loads all attributes
|
||||||
func (actions ActionList) LoadAttributes() error {
|
func (actions ActionList) loadAttributes(e db.Engine) error {
|
||||||
return actions.loadAttributes(db.GetEngine(db.DefaultContext))
|
userMap, err := actions.loadUsers(e)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := actions.loadRepositories(e); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return actions.loadRepoOwner(e, userMap)
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,7 @@ import (
|
||||||
"path"
|
"path"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"code.gitea.io/gitea/models/db"
|
||||||
repo_model "code.gitea.io/gitea/models/repo"
|
repo_model "code.gitea.io/gitea/models/repo"
|
||||||
"code.gitea.io/gitea/models/unittest"
|
"code.gitea.io/gitea/models/unittest"
|
||||||
user_model "code.gitea.io/gitea/models/user"
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
|
@ -39,7 +40,7 @@ func TestGetFeeds(t *testing.T) {
|
||||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||||
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}).(*user_model.User)
|
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}).(*user_model.User)
|
||||||
|
|
||||||
actions, err := GetFeeds(GetFeedsOptions{
|
actions, err := GetFeeds(db.DefaultContext, GetFeedsOptions{
|
||||||
RequestedUser: user,
|
RequestedUser: user,
|
||||||
Actor: user,
|
Actor: user,
|
||||||
IncludePrivate: true,
|
IncludePrivate: true,
|
||||||
|
@ -52,7 +53,7 @@ func TestGetFeeds(t *testing.T) {
|
||||||
assert.EqualValues(t, user.ID, actions[0].UserID)
|
assert.EqualValues(t, user.ID, actions[0].UserID)
|
||||||
}
|
}
|
||||||
|
|
||||||
actions, err = GetFeeds(GetFeedsOptions{
|
actions, err = GetFeeds(db.DefaultContext, GetFeedsOptions{
|
||||||
RequestedUser: user,
|
RequestedUser: user,
|
||||||
Actor: user,
|
Actor: user,
|
||||||
IncludePrivate: false,
|
IncludePrivate: false,
|
||||||
|
@ -62,13 +63,54 @@ func TestGetFeeds(t *testing.T) {
|
||||||
assert.Len(t, actions, 0)
|
assert.Len(t, actions, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestGetFeedsForRepos(t *testing.T) {
|
||||||
|
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||||
|
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}).(*user_model.User)
|
||||||
|
privRepo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 2}).(*repo_model.Repository)
|
||||||
|
pubRepo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 8}).(*repo_model.Repository)
|
||||||
|
|
||||||
|
// private repo & no login
|
||||||
|
actions, err := GetFeeds(db.DefaultContext, GetFeedsOptions{
|
||||||
|
RequestedRepo: privRepo,
|
||||||
|
IncludePrivate: true,
|
||||||
|
})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Len(t, actions, 0)
|
||||||
|
|
||||||
|
// public repo & no login
|
||||||
|
actions, err = GetFeeds(db.DefaultContext, GetFeedsOptions{
|
||||||
|
RequestedRepo: pubRepo,
|
||||||
|
IncludePrivate: true,
|
||||||
|
})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Len(t, actions, 1)
|
||||||
|
|
||||||
|
// private repo and login
|
||||||
|
actions, err = GetFeeds(db.DefaultContext, GetFeedsOptions{
|
||||||
|
RequestedRepo: privRepo,
|
||||||
|
IncludePrivate: true,
|
||||||
|
Actor: user,
|
||||||
|
})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Len(t, actions, 1)
|
||||||
|
|
||||||
|
// public repo & login
|
||||||
|
actions, err = GetFeeds(db.DefaultContext, GetFeedsOptions{
|
||||||
|
RequestedRepo: pubRepo,
|
||||||
|
IncludePrivate: true,
|
||||||
|
Actor: user,
|
||||||
|
})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Len(t, actions, 1)
|
||||||
|
}
|
||||||
|
|
||||||
func TestGetFeeds2(t *testing.T) {
|
func TestGetFeeds2(t *testing.T) {
|
||||||
// test with an organization user
|
// test with an organization user
|
||||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||||
org := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 3}).(*user_model.User)
|
org := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 3}).(*user_model.User)
|
||||||
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}).(*user_model.User)
|
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}).(*user_model.User)
|
||||||
|
|
||||||
actions, err := GetFeeds(GetFeedsOptions{
|
actions, err := GetFeeds(db.DefaultContext, GetFeedsOptions{
|
||||||
RequestedUser: org,
|
RequestedUser: org,
|
||||||
Actor: user,
|
Actor: user,
|
||||||
IncludePrivate: true,
|
IncludePrivate: true,
|
||||||
|
@ -82,7 +124,7 @@ func TestGetFeeds2(t *testing.T) {
|
||||||
assert.EqualValues(t, org.ID, actions[0].UserID)
|
assert.EqualValues(t, org.ID, actions[0].UserID)
|
||||||
}
|
}
|
||||||
|
|
||||||
actions, err = GetFeeds(GetFeedsOptions{
|
actions, err = GetFeeds(db.DefaultContext, GetFeedsOptions{
|
||||||
RequestedUser: org,
|
RequestedUser: org,
|
||||||
Actor: user,
|
Actor: user,
|
||||||
IncludePrivate: false,
|
IncludePrivate: false,
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
user_id: 2
|
user_id: 2
|
||||||
op_type: 12 # close issue
|
op_type: 12 # close issue
|
||||||
act_user_id: 2
|
act_user_id: 2
|
||||||
repo_id: 2
|
repo_id: 2 # private
|
||||||
is_private: true
|
is_private: true
|
||||||
created_unix: 1603228283
|
created_unix: 1603228283
|
||||||
|
|
||||||
|
@ -12,7 +12,7 @@
|
||||||
user_id: 3
|
user_id: 3
|
||||||
op_type: 2 # rename repo
|
op_type: 2 # rename repo
|
||||||
act_user_id: 2
|
act_user_id: 2
|
||||||
repo_id: 3
|
repo_id: 3 # private
|
||||||
is_private: true
|
is_private: true
|
||||||
content: oldRepoName
|
content: oldRepoName
|
||||||
|
|
||||||
|
@ -21,7 +21,7 @@
|
||||||
user_id: 11
|
user_id: 11
|
||||||
op_type: 1 # create repo
|
op_type: 1 # create repo
|
||||||
act_user_id: 11
|
act_user_id: 11
|
||||||
repo_id: 9
|
repo_id: 9 # public
|
||||||
is_private: false
|
is_private: false
|
||||||
|
|
||||||
-
|
-
|
||||||
|
@ -29,7 +29,7 @@
|
||||||
user_id: 16
|
user_id: 16
|
||||||
op_type: 12 # close issue
|
op_type: 12 # close issue
|
||||||
act_user_id: 16
|
act_user_id: 16
|
||||||
repo_id: 22
|
repo_id: 22 # private
|
||||||
is_private: true
|
is_private: true
|
||||||
created_unix: 1603267920
|
created_unix: 1603267920
|
||||||
|
|
||||||
|
@ -37,7 +37,7 @@
|
||||||
user_id: 10
|
user_id: 10
|
||||||
op_type: 1 # create repo
|
op_type: 1 # create repo
|
||||||
act_user_id: 10
|
act_user_id: 10
|
||||||
repo_id: 6
|
repo_id: 6 # private
|
||||||
is_private: true
|
is_private: true
|
||||||
created_unix: 1603010100
|
created_unix: 1603010100
|
||||||
|
|
||||||
|
@ -45,7 +45,7 @@
|
||||||
user_id: 10
|
user_id: 10
|
||||||
op_type: 1 # create repo
|
op_type: 1 # create repo
|
||||||
act_user_id: 10
|
act_user_id: 10
|
||||||
repo_id: 7
|
repo_id: 7 # private
|
||||||
is_private: true
|
is_private: true
|
||||||
created_unix: 1603011300
|
created_unix: 1603011300
|
||||||
|
|
||||||
|
@ -53,6 +53,6 @@
|
||||||
user_id: 10
|
user_id: 10
|
||||||
op_type: 1 # create repo
|
op_type: 1 # create repo
|
||||||
act_user_id: 10
|
act_user_id: 10
|
||||||
repo_id: 8
|
repo_id: 8 # public
|
||||||
is_private: false
|
is_private: false
|
||||||
created_unix: 1603011540 # grouped with id:7
|
created_unix: 1603011540 # grouped with id:7
|
||||||
|
|
|
@ -9,6 +9,7 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"code.gitea.io/gitea/models/db"
|
||||||
"code.gitea.io/gitea/models/unittest"
|
"code.gitea.io/gitea/models/unittest"
|
||||||
user_model "code.gitea.io/gitea/models/user"
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
"code.gitea.io/gitea/modules/json"
|
"code.gitea.io/gitea/modules/json"
|
||||||
|
@ -72,7 +73,7 @@ func TestGetUserHeatmapDataByUser(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// get the action for comparison
|
// get the action for comparison
|
||||||
actions, err := GetFeeds(GetFeedsOptions{
|
actions, err := GetFeeds(db.DefaultContext, GetFeedsOptions{
|
||||||
RequestedUser: user,
|
RequestedUser: user,
|
||||||
Actor: doer,
|
Actor: doer,
|
||||||
IncludePrivate: true,
|
IncludePrivate: true,
|
||||||
|
|
|
@ -36,8 +36,8 @@ func Auth(serviceName, userName, passwd string) (string, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = t.AcctMgmt(0); err != nil {
|
if err = t.AcctMgmt(0); err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
// PAM login names might suffer transformations in the PAM stack.
|
// PAM login names might suffer transformations in the PAM stack.
|
||||||
// We should take whatever the PAM stack returns for it.
|
// We should take whatever the PAM stack returns for it.
|
||||||
|
|
|
@ -418,6 +418,8 @@ func RepoAssignment(ctx *Context) (cancel context.CancelFunc) {
|
||||||
userName := ctx.Params(":username")
|
userName := ctx.Params(":username")
|
||||||
repoName := ctx.Params(":reponame")
|
repoName := ctx.Params(":reponame")
|
||||||
repoName = strings.TrimSuffix(repoName, ".git")
|
repoName = strings.TrimSuffix(repoName, ".git")
|
||||||
|
repoName = strings.TrimSuffix(repoName, ".rss")
|
||||||
|
repoName = strings.TrimSuffix(repoName, ".atom")
|
||||||
|
|
||||||
// Check if the user is the same as the repository owner
|
// Check if the user is the same as the repository owner
|
||||||
if ctx.IsSigned && ctx.User.LowerName == strings.ToLower(userName) {
|
if ctx.IsSigned && ctx.User.LowerName == strings.ToLower(userName) {
|
||||||
|
|
|
@ -7,6 +7,7 @@ package feed
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"html"
|
"html"
|
||||||
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
@ -66,7 +67,7 @@ func renderMarkdown(ctx *context.Context, act *models.Action, content string) st
|
||||||
}
|
}
|
||||||
|
|
||||||
// feedActionsToFeedItems convert gitea's Action feed to feeds Item
|
// feedActionsToFeedItems convert gitea's Action feed to feeds Item
|
||||||
func feedActionsToFeedItems(ctx *context.Context, actions []*models.Action) (items []*feeds.Item, err error) {
|
func feedActionsToFeedItems(ctx *context.Context, actions models.ActionList) (items []*feeds.Item, err error) {
|
||||||
for _, act := range actions {
|
for _, act := range actions {
|
||||||
act.LoadActUser()
|
act.LoadActUser()
|
||||||
|
|
||||||
|
@ -247,3 +248,18 @@ func feedActionsToFeedItems(ctx *context.Context, actions []*models.Action) (ite
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetFeedType return if it is a feed request and altered name and feed type.
|
||||||
|
func GetFeedType(name string, req *http.Request) (bool, string, string) {
|
||||||
|
if strings.HasSuffix(name, ".rss") ||
|
||||||
|
strings.Contains(req.Header.Get("Accept"), "application/rss+xml") {
|
||||||
|
return true, strings.TrimSuffix(name, ".rss"), "rss"
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.HasSuffix(name, ".atom") ||
|
||||||
|
strings.Contains(req.Header.Get("Accept"), "application/atom+xml") {
|
||||||
|
return true, strings.TrimSuffix(name, ".atom"), "atom"
|
||||||
|
}
|
||||||
|
|
||||||
|
return false, name, ""
|
||||||
|
}
|
||||||
|
|
|
@ -15,48 +15,9 @@ import (
|
||||||
"github.com/gorilla/feeds"
|
"github.com/gorilla/feeds"
|
||||||
)
|
)
|
||||||
|
|
||||||
// RetrieveFeeds loads feeds for the specified user
|
|
||||||
func RetrieveFeeds(ctx *context.Context, options models.GetFeedsOptions) []*models.Action {
|
|
||||||
actions, err := models.GetFeeds(options)
|
|
||||||
if err != nil {
|
|
||||||
ctx.ServerError("GetFeeds", err)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return actions
|
|
||||||
}
|
|
||||||
|
|
||||||
// ShowUserFeed show user activity as RSS / Atom feed
|
// ShowUserFeed show user activity as RSS / Atom feed
|
||||||
func ShowUserFeed(ctx *context.Context, ctxUser *user_model.User, formatType string) {
|
func ShowUserFeed(ctx *context.Context, ctxUser *user_model.User, formatType string) {
|
||||||
actions := RetrieveFeeds(ctx, models.GetFeedsOptions{
|
actions, err := models.GetFeeds(ctx, models.GetFeedsOptions{
|
||||||
RequestedUser: ctxUser,
|
RequestedUser: ctxUser,
|
||||||
Actor: ctx.User,
|
Actor: ctx.User,
|
||||||
IncludePrivate: false,
|
IncludePrivate: false,
|
||||||
|
@ -64,7 +25,8 @@ func ShowUserFeed(ctx *context.Context, ctxUser *user_model.User, formatType str
|
||||||
IncludeDeleted: false,
|
IncludeDeleted: false,
|
||||||
Date: ctx.FormString("date"),
|
Date: ctx.FormString("date"),
|
||||||
})
|
})
|
||||||
if ctx.Written() {
|
if err != nil {
|
||||||
|
ctx.ServerError("GetFeeds", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -75,7 +37,6 @@ func ShowUserFeed(ctx *context.Context, ctxUser *user_model.User, formatType str
|
||||||
Created: time.Now(),
|
Created: time.Now(),
|
||||||
}
|
}
|
||||||
|
|
||||||
var err error
|
|
||||||
feed.Items, err = feedActionsToFeedItems(ctx, actions)
|
feed.Items, err = feedActionsToFeedItems(ctx, actions)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.ServerError("convert feed", err)
|
ctx.ServerError("convert feed", err)
|
||||||
|
|
44
routers/web/feed/repo.go
Normal file
44
routers/web/feed/repo.go
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
// Copyright 2022 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 feed
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"code.gitea.io/gitea/models"
|
||||||
|
repo_model "code.gitea.io/gitea/models/repo"
|
||||||
|
"code.gitea.io/gitea/modules/context"
|
||||||
|
|
||||||
|
"github.com/gorilla/feeds"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ShowRepoFeed shows user activity on the repo as RSS / Atom feed
|
||||||
|
func ShowRepoFeed(ctx *context.Context, repo *repo_model.Repository, formatType string) {
|
||||||
|
actions, err := models.GetFeeds(ctx, models.GetFeedsOptions{
|
||||||
|
RequestedRepo: repo,
|
||||||
|
Actor: ctx.User,
|
||||||
|
IncludePrivate: true,
|
||||||
|
Date: ctx.FormString("date"),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
ctx.ServerError("GetFeeds", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
feed := &feeds.Feed{
|
||||||
|
Title: ctx.Tr("home.feed_of", repo.FullName()),
|
||||||
|
Link: &feeds.Link{Href: repo.HTMLURL()},
|
||||||
|
Description: repo.Description,
|
||||||
|
Created: time.Now(),
|
||||||
|
}
|
||||||
|
|
||||||
|
feed.Items, err = feedActionsToFeedItems(ctx, actions)
|
||||||
|
if err != nil {
|
||||||
|
ctx.ServerError("convert feed", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
writeFeed(ctx, feed, formatType)
|
||||||
|
}
|
|
@ -38,6 +38,7 @@ import (
|
||||||
"code.gitea.io/gitea/modules/structs"
|
"code.gitea.io/gitea/modules/structs"
|
||||||
"code.gitea.io/gitea/modules/typesniffer"
|
"code.gitea.io/gitea/modules/typesniffer"
|
||||||
"code.gitea.io/gitea/modules/util"
|
"code.gitea.io/gitea/modules/util"
|
||||||
|
"code.gitea.io/gitea/routers/web/feed"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -691,6 +692,14 @@ func checkHomeCodeViewable(ctx *context.Context) {
|
||||||
|
|
||||||
// Home render repository home page
|
// Home render repository home page
|
||||||
func Home(ctx *context.Context) {
|
func Home(ctx *context.Context) {
|
||||||
|
isFeed, _, showFeedType := feed.GetFeedType(ctx.Params(":reponame"), ctx.Req)
|
||||||
|
if isFeed {
|
||||||
|
feed.ShowRepoFeed(ctx, ctx.Repo.Repository, showFeedType)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.Data["FeedURL"] = ctx.Repo.Repository.HTMLURL()
|
||||||
|
|
||||||
checkHomeCodeViewable(ctx)
|
checkHomeCodeViewable(ctx)
|
||||||
if ctx.Written() {
|
if ctx.Written() {
|
||||||
return
|
return
|
||||||
|
|
|
@ -29,7 +29,6 @@ import (
|
||||||
"code.gitea.io/gitea/modules/markup/markdown"
|
"code.gitea.io/gitea/modules/markup/markdown"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
"code.gitea.io/gitea/modules/util"
|
"code.gitea.io/gitea/modules/util"
|
||||||
"code.gitea.io/gitea/routers/web/feed"
|
|
||||||
issue_service "code.gitea.io/gitea/services/issue"
|
issue_service "code.gitea.io/gitea/services/issue"
|
||||||
pull_service "code.gitea.io/gitea/services/pull"
|
pull_service "code.gitea.io/gitea/services/pull"
|
||||||
|
|
||||||
|
@ -131,7 +130,7 @@ func Dashboard(ctx *context.Context) {
|
||||||
ctx.Data["MirrorCount"] = len(mirrors)
|
ctx.Data["MirrorCount"] = len(mirrors)
|
||||||
ctx.Data["Mirrors"] = mirrors
|
ctx.Data["Mirrors"] = mirrors
|
||||||
|
|
||||||
ctx.Data["Feeds"] = feed.RetrieveFeeds(ctx, models.GetFeedsOptions{
|
ctx.Data["Feeds"], err = models.GetFeeds(ctx, models.GetFeedsOptions{
|
||||||
RequestedUser: ctxUser,
|
RequestedUser: ctxUser,
|
||||||
RequestedTeam: ctx.Org.Team,
|
RequestedTeam: ctx.Org.Team,
|
||||||
Actor: ctx.User,
|
Actor: ctx.User,
|
||||||
|
@ -140,8 +139,8 @@ func Dashboard(ctx *context.Context) {
|
||||||
IncludeDeleted: false,
|
IncludeDeleted: false,
|
||||||
Date: ctx.FormString("date"),
|
Date: ctx.FormString("date"),
|
||||||
})
|
})
|
||||||
|
if err != nil {
|
||||||
if ctx.Written() {
|
ctx.ServerError("GetFeeds", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -74,19 +74,7 @@ func Profile(ctx *context.Context) {
|
||||||
uname = strings.TrimSuffix(uname, ".gpg")
|
uname = strings.TrimSuffix(uname, ".gpg")
|
||||||
}
|
}
|
||||||
|
|
||||||
showFeedType := ""
|
isShowFeed, uname, showFeedType := feed.GetFeedType(uname, ctx.Req)
|
||||||
if strings.HasSuffix(uname, ".rss") {
|
|
||||||
showFeedType = "rss"
|
|
||||||
uname = strings.TrimSuffix(uname, ".rss")
|
|
||||||
} else if strings.Contains(ctx.Req.Header.Get("Accept"), "application/rss+xml") {
|
|
||||||
showFeedType = "rss"
|
|
||||||
}
|
|
||||||
if strings.HasSuffix(uname, ".atom") {
|
|
||||||
showFeedType = "atom"
|
|
||||||
uname = strings.TrimSuffix(uname, ".atom")
|
|
||||||
} else if strings.Contains(ctx.Req.Header.Get("Accept"), "application/atom+xml") {
|
|
||||||
showFeedType = "atom"
|
|
||||||
}
|
|
||||||
|
|
||||||
ctxUser := GetUserByName(ctx, uname)
|
ctxUser := GetUserByName(ctx, uname)
|
||||||
if ctx.Written() {
|
if ctx.Written() {
|
||||||
|
@ -95,7 +83,7 @@ func Profile(ctx *context.Context) {
|
||||||
|
|
||||||
if ctxUser.IsOrganization() {
|
if ctxUser.IsOrganization() {
|
||||||
// Show Org RSS feed
|
// Show Org RSS feed
|
||||||
if len(showFeedType) != 0 {
|
if isShowFeed {
|
||||||
feed.ShowUserFeed(ctx, ctxUser, showFeedType)
|
feed.ShowUserFeed(ctx, ctxUser, showFeedType)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -123,11 +111,14 @@ func Profile(ctx *context.Context) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Show User RSS feed
|
// Show User RSS feed
|
||||||
if len(showFeedType) != 0 {
|
if isShowFeed {
|
||||||
feed.ShowUserFeed(ctx, ctxUser, showFeedType)
|
feed.ShowUserFeed(ctx, ctxUser, showFeedType)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// advertise feed via meta tag
|
||||||
|
ctx.Data["FeedURL"] = ctxUser.HTMLURL()
|
||||||
|
|
||||||
// Show OpenID URIs
|
// Show OpenID URIs
|
||||||
openIDs, err := user_model.GetUserOpenIDs(ctxUser.ID)
|
openIDs, err := user_model.GetUserOpenIDs(ctxUser.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -259,7 +250,7 @@ func Profile(ctx *context.Context) {
|
||||||
|
|
||||||
total = ctxUser.NumFollowing
|
total = ctxUser.NumFollowing
|
||||||
case "activity":
|
case "activity":
|
||||||
ctx.Data["Feeds"] = feed.RetrieveFeeds(ctx, models.GetFeedsOptions{
|
ctx.Data["Feeds"], err = models.GetFeeds(ctx, models.GetFeedsOptions{
|
||||||
RequestedUser: ctxUser,
|
RequestedUser: ctxUser,
|
||||||
Actor: ctx.User,
|
Actor: ctx.User,
|
||||||
IncludePrivate: showPrivate,
|
IncludePrivate: showPrivate,
|
||||||
|
@ -267,7 +258,8 @@ func Profile(ctx *context.Context) {
|
||||||
IncludeDeleted: false,
|
IncludeDeleted: false,
|
||||||
Date: ctx.FormString("date"),
|
Date: ctx.FormString("date"),
|
||||||
})
|
})
|
||||||
if ctx.Written() {
|
if err != nil {
|
||||||
|
ctx.ServerError("GetFeeds", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
case "stars":
|
case "stars":
|
||||||
|
|
|
@ -14,6 +14,10 @@
|
||||||
{{if .GoGetImport}}
|
{{if .GoGetImport}}
|
||||||
<meta name="go-import" content="{{.GoGetImport}} git {{.CloneLink.HTTPS}}">
|
<meta name="go-import" content="{{.GoGetImport}} git {{.CloneLink.HTTPS}}">
|
||||||
<meta name="go-source" content="{{.GoGetImport}} _ {{.GoDocDirectory}} {{.GoDocFile}}">
|
<meta name="go-source" content="{{.GoGetImport}} _ {{.GoDocDirectory}} {{.GoDocFile}}">
|
||||||
|
{{end}}
|
||||||
|
{{if .FeedURL}}
|
||||||
|
<link rel="alternate" type="application/atom+xml" title="" href="{{.FeedURL}}.atom">
|
||||||
|
<link rel="alternate" type="application/rss+xml" title="" href="{{.FeedURL}}.rss">
|
||||||
{{end}}
|
{{end}}
|
||||||
<script>
|
<script>
|
||||||
<!-- /* eslint-disable */ -->
|
<!-- /* eslint-disable */ -->
|
||||||
|
|
Reference in a new issue