1
1
mirror of https://github.com/go-gitea/gitea synced 2025-07-22 18:28:37 +00:00

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:
6543
2022-03-13 17:40:47 +01:00
committed by GitHub
parent 780cf76f6e
commit bc0d2c8ada
14 changed files with 188 additions and 110 deletions

View File

@@ -7,6 +7,7 @@ package feed
import (
"fmt"
"html"
"net/http"
"net/url"
"strconv"
"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
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 {
act.LoadActUser()
@@ -247,3 +248,18 @@ func feedActionsToFeedItems(ctx *context.Context, actions []*models.Action) (ite
}
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, ""
}

View File

@@ -15,48 +15,9 @@ import (
"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
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,
Actor: ctx.User,
IncludePrivate: false,
@@ -64,7 +25,8 @@ func ShowUserFeed(ctx *context.Context, ctxUser *user_model.User, formatType str
IncludeDeleted: false,
Date: ctx.FormString("date"),
})
if ctx.Written() {
if err != nil {
ctx.ServerError("GetFeeds", err)
return
}
@@ -75,7 +37,6 @@ func ShowUserFeed(ctx *context.Context, ctxUser *user_model.User, formatType str
Created: time.Now(),
}
var err error
feed.Items, err = feedActionsToFeedItems(ctx, actions)
if err != nil {
ctx.ServerError("convert feed", err)

44
routers/web/feed/repo.go Normal file
View 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)
}

View File

@@ -38,6 +38,7 @@ import (
"code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/typesniffer"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/routers/web/feed"
)
const (
@@ -691,6 +692,14 @@ func checkHomeCodeViewable(ctx *context.Context) {
// Home render repository home page
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)
if ctx.Written() {
return

View File

@@ -29,7 +29,6 @@ import (
"code.gitea.io/gitea/modules/markup/markdown"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/routers/web/feed"
issue_service "code.gitea.io/gitea/services/issue"
pull_service "code.gitea.io/gitea/services/pull"
@@ -131,7 +130,7 @@ func Dashboard(ctx *context.Context) {
ctx.Data["MirrorCount"] = len(mirrors)
ctx.Data["Mirrors"] = mirrors
ctx.Data["Feeds"] = feed.RetrieveFeeds(ctx, models.GetFeedsOptions{
ctx.Data["Feeds"], err = models.GetFeeds(ctx, models.GetFeedsOptions{
RequestedUser: ctxUser,
RequestedTeam: ctx.Org.Team,
Actor: ctx.User,
@@ -140,8 +139,8 @@ func Dashboard(ctx *context.Context) {
IncludeDeleted: false,
Date: ctx.FormString("date"),
})
if ctx.Written() {
if err != nil {
ctx.ServerError("GetFeeds", err)
return
}

View File

@@ -74,19 +74,7 @@ func Profile(ctx *context.Context) {
uname = strings.TrimSuffix(uname, ".gpg")
}
showFeedType := ""
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"
}
isShowFeed, uname, showFeedType := feed.GetFeedType(uname, ctx.Req)
ctxUser := GetUserByName(ctx, uname)
if ctx.Written() {
@@ -95,7 +83,7 @@ func Profile(ctx *context.Context) {
if ctxUser.IsOrganization() {
// Show Org RSS feed
if len(showFeedType) != 0 {
if isShowFeed {
feed.ShowUserFeed(ctx, ctxUser, showFeedType)
return
}
@@ -123,11 +111,14 @@ func Profile(ctx *context.Context) {
}
// Show User RSS feed
if len(showFeedType) != 0 {
if isShowFeed {
feed.ShowUserFeed(ctx, ctxUser, showFeedType)
return
}
// advertise feed via meta tag
ctx.Data["FeedURL"] = ctxUser.HTMLURL()
// Show OpenID URIs
openIDs, err := user_model.GetUserOpenIDs(ctxUser.ID)
if err != nil {
@@ -259,7 +250,7 @@ func Profile(ctx *context.Context) {
total = ctxUser.NumFollowing
case "activity":
ctx.Data["Feeds"] = feed.RetrieveFeeds(ctx, models.GetFeedsOptions{
ctx.Data["Feeds"], err = models.GetFeeds(ctx, models.GetFeedsOptions{
RequestedUser: ctxUser,
Actor: ctx.User,
IncludePrivate: showPrivate,
@@ -267,7 +258,8 @@ func Profile(ctx *context.Context) {
IncludeDeleted: false,
Date: ctx.FormString("date"),
})
if ctx.Written() {
if err != nil {
ctx.ServerError("GetFeeds", err)
return
}
case "stars":