From 7dcb60175234f7ad96631faf0f3ff0bab1958738 Mon Sep 17 00:00:00 2001 From: 6543 <6543@obermui.de> Date: Thu, 10 Jun 2021 18:45:58 +0200 Subject: [PATCH 01/18] User/Org/Repo reserve "*.rss" pattern --- models/repo.go | 2 +- models/user.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/models/repo.go b/models/repo.go index 58a393ae708e7..f1c3262f82e1c 100644 --- a/models/repo.go +++ b/models/repo.go @@ -1024,7 +1024,7 @@ func GetRepoInitFile(tp, name string) ([]byte, error) { var ( reservedRepoNames = []string{".", ".."} - reservedRepoPatterns = []string{"*.git", "*.wiki"} + reservedRepoPatterns = []string{"*.git", "*.wiki", "*.rss"} ) // IsUsableRepoName returns true when repository is usable diff --git a/models/user.go b/models/user.go index 002c050651f17..3d45bbc06b309 100644 --- a/models/user.go +++ b/models/user.go @@ -811,7 +811,7 @@ var ( "user", } - reservedUserPatterns = []string{"*.keys", "*.gpg"} + reservedUserPatterns = []string{"*.keys", "*.gpg", "*.rss"} ) // isUsableName checks if name is reserved or pattern of name is not allowed From e8fba1f02d4e7e3be29062b503403a8a9089fb0d Mon Sep 17 00:00:00 2001 From: 6543 <6543@obermui.de> Date: Thu, 10 Jun 2021 18:57:12 +0200 Subject: [PATCH 02/18] refactor user.retriveFeeds --- routers/web/user/home.go | 13 +++++++------ routers/web/user/profile.go | 3 ++- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/routers/web/user/home.go b/routers/web/user/home.go index acf73f82fe362..0a4509aecc40e 100644 --- a/routers/web/user/home.go +++ b/routers/web/user/home.go @@ -59,11 +59,11 @@ func getDashboardContextUser(ctx *context.Context) *models.User { } // retrieveFeeds loads feeds for the specified user -func retrieveFeeds(ctx *context.Context, options models.GetFeedsOptions) { +func retrieveFeeds(ctx *context.Context, options models.GetFeedsOptions) []*models.Action { actions, err := models.GetFeeds(options) if err != nil { ctx.ServerError("GetFeeds", err) - return + return nil } userCache := map[int64]*models.User{options.RequestedUser.ID: options.RequestedUser} @@ -85,13 +85,13 @@ func retrieveFeeds(ctx *context.Context, options models.GetFeedsOptions) { continue } ctx.ServerError("GetUserByID", err) - return + return nil } userCache[repoOwner.ID] = repoOwner } act.Repo.Owner = repoOwner } - ctx.Data["Feeds"] = actions + return actions } // Dashboard render the dashboard page @@ -149,7 +149,7 @@ func Dashboard(ctx *context.Context) { ctx.Data["MirrorCount"] = len(mirrors) ctx.Data["Mirrors"] = mirrors - retrieveFeeds(ctx, models.GetFeedsOptions{ + actions := retrieveFeeds(ctx, models.GetFeedsOptions{ RequestedUser: ctxUser, RequestedTeam: ctx.Org.Team, Actor: ctx.User, @@ -158,10 +158,11 @@ func Dashboard(ctx *context.Context) { IncludeDeleted: false, Date: ctx.Query("date"), }) - if ctx.Written() { return } + ctx.Data["Feeds"] = actions + ctx.HTML(http.StatusOK, tplDashboard) } diff --git a/routers/web/user/profile.go b/routers/web/user/profile.go index e66820e1317bc..60013d6752129 100644 --- a/routers/web/user/profile.go +++ b/routers/web/user/profile.go @@ -208,7 +208,7 @@ func Profile(ctx *context.Context) { total = ctxUser.NumFollowing case "activity": - retrieveFeeds(ctx, models.GetFeedsOptions{RequestedUser: ctxUser, + actions := retrieveFeeds(ctx, models.GetFeedsOptions{RequestedUser: ctxUser, Actor: ctx.User, IncludePrivate: showPrivate, OnlyPerformedBy: true, @@ -218,6 +218,7 @@ func Profile(ctx *context.Context) { if ctx.Written() { return } + ctx.Data["Feeds"] = actions case "stars": ctx.Data["PageIsProfileStarList"] = true repos, count, err = models.SearchRepository(&models.SearchRepoOptions{ From b839c84e4802d24d25d48affa1ce69a07c1c4df1 Mon Sep 17 00:00:00 2001 From: 6543 <6543@obermui.de> Date: Thu, 10 Jun 2021 19:46:41 +0200 Subject: [PATCH 03/18] wip --- go.mod | 1 + go.sum | 2 + routers/web/user/profile.go | 62 ++++++ vendor/github.com/gorilla/feeds/.travis.yml | 16 ++ vendor/github.com/gorilla/feeds/AUTHORS | 29 +++ vendor/github.com/gorilla/feeds/LICENSE | 22 +++ vendor/github.com/gorilla/feeds/README.md | 185 ++++++++++++++++++ vendor/github.com/gorilla/feeds/atom.go | 169 ++++++++++++++++ vendor/github.com/gorilla/feeds/doc.go | 73 +++++++ vendor/github.com/gorilla/feeds/feed.go | 145 ++++++++++++++ vendor/github.com/gorilla/feeds/json.go | 183 +++++++++++++++++ vendor/github.com/gorilla/feeds/rss.go | 168 ++++++++++++++++ vendor/github.com/gorilla/feeds/test.atom | 92 +++++++++ vendor/github.com/gorilla/feeds/test.rss | 96 +++++++++ .../github.com/gorilla/feeds/to-implement.md | 20 ++ vendor/github.com/gorilla/feeds/uuid.go | 27 +++ vendor/modules.txt | 3 + 17 files changed, 1293 insertions(+) create mode 100644 vendor/github.com/gorilla/feeds/.travis.yml create mode 100644 vendor/github.com/gorilla/feeds/AUTHORS create mode 100644 vendor/github.com/gorilla/feeds/LICENSE create mode 100644 vendor/github.com/gorilla/feeds/README.md create mode 100644 vendor/github.com/gorilla/feeds/atom.go create mode 100644 vendor/github.com/gorilla/feeds/doc.go create mode 100644 vendor/github.com/gorilla/feeds/feed.go create mode 100644 vendor/github.com/gorilla/feeds/json.go create mode 100644 vendor/github.com/gorilla/feeds/rss.go create mode 100644 vendor/github.com/gorilla/feeds/test.atom create mode 100644 vendor/github.com/gorilla/feeds/test.rss create mode 100644 vendor/github.com/gorilla/feeds/to-implement.md create mode 100644 vendor/github.com/gorilla/feeds/uuid.go diff --git a/go.mod b/go.mod index aa18d52cd9034..45241e9f5e5a2 100644 --- a/go.mod +++ b/go.mod @@ -54,6 +54,7 @@ require ( github.com/google/go-querystring v1.1.0 // indirect github.com/google/uuid v1.2.0 github.com/gorilla/context v1.1.1 + github.com/gorilla/feeds v1.1.1 github.com/gorilla/mux v1.8.0 // indirect github.com/gorilla/sessions v1.2.1 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect diff --git a/go.sum b/go.sum index afe9b3e76f21d..2a3024a22984a 100644 --- a/go.sum +++ b/go.sum @@ -555,6 +555,8 @@ github.com/gorilla/context v1.1.1 h1:AWwleXJkX/nhcU9bZSnZoi3h/qGYqQAGhq6zZe/aQW8 github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= github.com/gorilla/css v1.0.0 h1:BQqNyPTi50JCFMTw/b67hByjMVXZRwGha6wxVGkeihY= github.com/gorilla/css v1.0.0/go.mod h1:Dn721qIggHpt4+EFCcTLTU/vk5ySda2ReITrtgBl60c= +github.com/gorilla/feeds v1.1.1 h1:HwKXxqzcRNg9to+BbvJog4+f3s/xzvtZXICcQGutYfY= +github.com/gorilla/feeds v1.1.1/go.mod h1:Nk0jZrvPFZX1OBe5NPiddPw7CfwF6Q9eqzaBbaightA= github.com/gorilla/handlers v1.5.1 h1:9lRY6j8DEeeBT10CvO9hGW0gmky0BprnvDI5vfhUHH4= github.com/gorilla/handlers v1.5.1/go.mod h1:t8XrUpc4KVXb7HGyJ4/cEnwQiaxrX/hz1Zv/4g96P1Q= github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= diff --git a/routers/web/user/profile.go b/routers/web/user/profile.go index 60013d6752129..51f11ec5f282c 100644 --- a/routers/web/user/profile.go +++ b/routers/web/user/profile.go @@ -10,6 +10,7 @@ import ( "net/http" "path" "strings" + "time" "code.gitea.io/gitea/models" "code.gitea.io/gitea/modules/context" @@ -18,6 +19,8 @@ import ( "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/routers/web/org" + + "github.com/gorilla/feeds" ) // GetUserByName get user by name @@ -70,6 +73,12 @@ func Profile(ctx *context.Context) { uname = strings.TrimSuffix(uname, ".gpg") } + isShowRSS := false + if strings.HasSuffix(uname, ".rss") { + isShowRSS = true + uname = strings.TrimSuffix(uname, ".rss") + } + ctxUser := GetUserByName(ctx, uname) if ctx.Written() { return @@ -87,6 +96,12 @@ func Profile(ctx *context.Context) { return } + // Show User RSS feed + if isShowRSS { + ShowRSS(ctx, ctxUser) + return + } + if ctxUser.IsOrganization() { org.Home(ctx) return @@ -306,6 +321,53 @@ func Profile(ctx *context.Context) { ctx.HTML(http.StatusOK, tplProfile) } +// ShowRSS show user activity as RSS feed +func ShowRSS(ctx *context.Context, ctxUser *models.User) { + actions := retrieveFeeds(ctx, models.GetFeedsOptions{RequestedUser: ctxUser, + Actor: ctx.User, + IncludePrivate: false, + OnlyPerformedBy: true, + IncludeDeleted: false, + Date: ctx.Query("date"), + }) + if ctx.Written() { + return + } + + now := time.Now() + feed := &feeds.Feed{ + Title: ctxUser.FullName, + Link: &feeds.Link{Href: ctxUser.HTMLURL()}, + Description: ctxUser.Description, + Created: now, + } + + feed.Items = feedActionsToFeedItems(actions) + + //atom, err := feed.ToAtom() + if rss, err := feed.ToRss(); err != nil { + ctx.ServerError("ToRss", err) + } else { + ctx.PlainText(http.StatusOK, []byte(rss)) + } + +} + +func feedActionsToFeedItems(actions []*models.Action) (items []*feeds.Item) { + for i := range actions { + actions[i].LoadActUser() + + items = append(items, &feeds.Item{ + Title: string(actions[i].GetOpType()), + Link: &feeds.Link{Href: actions[i].GetCommentLink(), Rel: actions[i].ActUser.AvatarLink()}, + Description: "A discussion on controlled parallelism in golang", + Author: &feeds.Author{Name: actions[i].ActUser.FullName, Email: actions[i].ActUser.GetEmail()}, + Created: actions[i].CreatedUnix.AsTime(), + }) + } + return +} + // Action response for follow/unfollow user request func Action(ctx *context.Context) { u := GetUserByParams(ctx) diff --git a/vendor/github.com/gorilla/feeds/.travis.yml b/vendor/github.com/gorilla/feeds/.travis.yml new file mode 100644 index 0000000000000..7939a2186638d --- /dev/null +++ b/vendor/github.com/gorilla/feeds/.travis.yml @@ -0,0 +1,16 @@ +language: go +sudo: false +matrix: + include: + - go: 1.8 + - go: 1.9 + - go: "1.10" + - go: 1.x + - go: tip + allow_failures: + - go: tip +script: + - go get -t -v ./... + - diff -u <(echo -n) <(gofmt -d -s .) + - go vet . + - go test -v -race ./... diff --git a/vendor/github.com/gorilla/feeds/AUTHORS b/vendor/github.com/gorilla/feeds/AUTHORS new file mode 100644 index 0000000000000..2c28cf943ae98 --- /dev/null +++ b/vendor/github.com/gorilla/feeds/AUTHORS @@ -0,0 +1,29 @@ +# This is the official list of gorilla/feeds authors for copyright purposes. +# Please keep the list sorted. + +Dmitry Chestnykh +Eddie Scholtz +Gabriel Simmer +Google LLC (https://opensource.google.com/) +honky +James Gregory +Jason Hall +Jason Moiron +Kamil Kisiel +Kevin Stock +Markus Zimmermann +Matt Silverlock +Matthew Dawson +Milan Aleksic +Milan Aleksić +nlimpid +Paul Petring +Sean Enck +Sue Spence +Supermighty +Toru Fukui +Vabd +Volker +ZhiFeng Hu +weberc2 + diff --git a/vendor/github.com/gorilla/feeds/LICENSE b/vendor/github.com/gorilla/feeds/LICENSE new file mode 100644 index 0000000000000..e24412d56162d --- /dev/null +++ b/vendor/github.com/gorilla/feeds/LICENSE @@ -0,0 +1,22 @@ +Copyright (c) 2013-2018 The Gorilla Feeds Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + + Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/github.com/gorilla/feeds/README.md b/vendor/github.com/gorilla/feeds/README.md new file mode 100644 index 0000000000000..4d733cf538982 --- /dev/null +++ b/vendor/github.com/gorilla/feeds/README.md @@ -0,0 +1,185 @@ +## gorilla/feeds +[![GoDoc](https://godoc.org/github.com/gorilla/feeds?status.svg)](https://godoc.org/github.com/gorilla/feeds) +[![Build Status](https://travis-ci.org/gorilla/feeds.svg?branch=master)](https://travis-ci.org/gorilla/feeds) + +feeds is a web feed generator library for generating RSS, Atom and JSON feeds from Go +applications. + +### Goals + + * Provide a simple interface to create both Atom & RSS 2.0 feeds + * Full support for [Atom][atom], [RSS 2.0][rss], and [JSON Feed Version 1][jsonfeed] spec elements + * Ability to modify particulars for each spec + +[atom]: https://tools.ietf.org/html/rfc4287 +[rss]: http://www.rssboard.org/rss-specification +[jsonfeed]: https://jsonfeed.org/version/1 + +### Usage + +```go +package main + +import ( + "fmt" + "log" + "time" + "github.com/gorilla/feeds" +) + +func main() { + now := time.Now() + feed := &feeds.Feed{ + Title: "jmoiron.net blog", + Link: &feeds.Link{Href: "http://jmoiron.net/blog"}, + Description: "discussion about tech, footie, photos", + Author: &feeds.Author{Name: "Jason Moiron", Email: "jmoiron@jmoiron.net"}, + Created: now, + } + + feed.Items = []*feeds.Item{ + &feeds.Item{ + Title: "Limiting Concurrency in Go", + Link: &feeds.Link{Href: "http://jmoiron.net/blog/limiting-concurrency-in-go/"}, + Description: "A discussion on controlled parallelism in golang", + Author: &feeds.Author{Name: "Jason Moiron", Email: "jmoiron@jmoiron.net"}, + Created: now, + }, + &feeds.Item{ + Title: "Logic-less Template Redux", + Link: &feeds.Link{Href: "http://jmoiron.net/blog/logicless-template-redux/"}, + Description: "More thoughts on logicless templates", + Created: now, + }, + &feeds.Item{ + Title: "Idiomatic Code Reuse in Go", + Link: &feeds.Link{Href: "http://jmoiron.net/blog/idiomatic-code-reuse-in-go/"}, + Description: "How to use interfaces effectively", + Created: now, + }, + } + + atom, err := feed.ToAtom() + if err != nil { + log.Fatal(err) + } + + rss, err := feed.ToRss() + if err != nil { + log.Fatal(err) + } + + json, err := feed.ToJSON() + if err != nil { + log.Fatal(err) + } + + fmt.Println(atom, "\n", rss, "\n", json) +} +``` + +Outputs: + +```xml + + + jmoiron.net blog + + http://jmoiron.net/blog + 2013-01-16T03:26:01-05:00 + discussion about tech, footie, photos + + Limiting Concurrency in Go + + 2013-01-16T03:26:01-05:00 + tag:jmoiron.net,2013-01-16:/blog/limiting-concurrency-in-go/ + A discussion on controlled parallelism in golang + + Jason Moiron + jmoiron@jmoiron.net + + + + Logic-less Template Redux + + 2013-01-16T03:26:01-05:00 + tag:jmoiron.net,2013-01-16:/blog/logicless-template-redux/ + More thoughts on logicless templates + + + + Idiomatic Code Reuse in Go + + 2013-01-16T03:26:01-05:00 + tag:jmoiron.net,2013-01-16:/blog/idiomatic-code-reuse-in-go/ + How to use interfaces <em>effectively</em> + + + + + + + + jmoiron.net blog + http://jmoiron.net/blog + discussion about tech, footie, photos + jmoiron@jmoiron.net (Jason Moiron) + 2013-01-16T03:22:24-05:00 + + Limiting Concurrency in Go + http://jmoiron.net/blog/limiting-concurrency-in-go/ + A discussion on controlled parallelism in golang + 2013-01-16T03:22:24-05:00 + + + Logic-less Template Redux + http://jmoiron.net/blog/logicless-template-redux/ + More thoughts on logicless templates + 2013-01-16T03:22:24-05:00 + + + Idiomatic Code Reuse in Go + http://jmoiron.net/blog/idiomatic-code-reuse-in-go/ + How to use interfaces <em>effectively</em> + 2013-01-16T03:22:24-05:00 + + + + +{ + "version": "https://jsonfeed.org/version/1", + "title": "jmoiron.net blog", + "home_page_url": "http://jmoiron.net/blog", + "description": "discussion about tech, footie, photos", + "author": { + "name": "Jason Moiron" + }, + "items": [ + { + "id": "", + "url": "http://jmoiron.net/blog/limiting-concurrency-in-go/", + "title": "Limiting Concurrency in Go", + "summary": "A discussion on controlled parallelism in golang", + "date_published": "2013-01-16T03:22:24.530817846-05:00", + "author": { + "name": "Jason Moiron" + } + }, + { + "id": "", + "url": "http://jmoiron.net/blog/logicless-template-redux/", + "title": "Logic-less Template Redux", + "summary": "More thoughts on logicless templates", + "date_published": "2013-01-16T03:22:24.530817846-05:00" + }, + { + "id": "", + "url": "http://jmoiron.net/blog/idiomatic-code-reuse-in-go/", + "title": "Idiomatic Code Reuse in Go", + "summary": "How to use interfaces \u003cem\u003eeffectively\u003c/em\u003e", + "date_published": "2013-01-16T03:22:24.530817846-05:00" + } + ] +} +``` + diff --git a/vendor/github.com/gorilla/feeds/atom.go b/vendor/github.com/gorilla/feeds/atom.go new file mode 100644 index 0000000000000..7196f4781e42f --- /dev/null +++ b/vendor/github.com/gorilla/feeds/atom.go @@ -0,0 +1,169 @@ +package feeds + +import ( + "encoding/xml" + "fmt" + "net/url" + "time" +) + +// Generates Atom feed as XML + +const ns = "http://www.w3.org/2005/Atom" + +type AtomPerson struct { + Name string `xml:"name,omitempty"` + Uri string `xml:"uri,omitempty"` + Email string `xml:"email,omitempty"` +} + +type AtomSummary struct { + XMLName xml.Name `xml:"summary"` + Content string `xml:",chardata"` + Type string `xml:"type,attr"` +} + +type AtomContent struct { + XMLName xml.Name `xml:"content"` + Content string `xml:",chardata"` + Type string `xml:"type,attr"` +} + +type AtomAuthor struct { + XMLName xml.Name `xml:"author"` + AtomPerson +} + +type AtomContributor struct { + XMLName xml.Name `xml:"contributor"` + AtomPerson +} + +type AtomEntry struct { + XMLName xml.Name `xml:"entry"` + Xmlns string `xml:"xmlns,attr,omitempty"` + Title string `xml:"title"` // required + Updated string `xml:"updated"` // required + Id string `xml:"id"` // required + Category string `xml:"category,omitempty"` + Content *AtomContent + Rights string `xml:"rights,omitempty"` + Source string `xml:"source,omitempty"` + Published string `xml:"published,omitempty"` + Contributor *AtomContributor + Links []AtomLink // required if no child 'content' elements + Summary *AtomSummary // required if content has src or content is base64 + Author *AtomAuthor // required if feed lacks an author +} + +// Multiple links with different rel can coexist +type AtomLink struct { + //Atom 1.0 + XMLName xml.Name `xml:"link"` + Href string `xml:"href,attr"` + Rel string `xml:"rel,attr,omitempty"` + Type string `xml:"type,attr,omitempty"` + Length string `xml:"length,attr,omitempty"` +} + +type AtomFeed struct { + XMLName xml.Name `xml:"feed"` + Xmlns string `xml:"xmlns,attr"` + Title string `xml:"title"` // required + Id string `xml:"id"` // required + Updated string `xml:"updated"` // required + Category string `xml:"category,omitempty"` + Icon string `xml:"icon,omitempty"` + Logo string `xml:"logo,omitempty"` + Rights string `xml:"rights,omitempty"` // copyright used + Subtitle string `xml:"subtitle,omitempty"` + Link *AtomLink + Author *AtomAuthor `xml:"author,omitempty"` + Contributor *AtomContributor + Entries []*AtomEntry `xml:"entry"` +} + +type Atom struct { + *Feed +} + +func newAtomEntry(i *Item) *AtomEntry { + id := i.Id + // assume the description is html + s := &AtomSummary{Content: i.Description, Type: "html"} + + if len(id) == 0 { + // if there's no id set, try to create one, either from data or just a uuid + if len(i.Link.Href) > 0 && (!i.Created.IsZero() || !i.Updated.IsZero()) { + dateStr := anyTimeFormat("2006-01-02", i.Updated, i.Created) + host, path := i.Link.Href, "/invalid.html" + if url, err := url.Parse(i.Link.Href); err == nil { + host, path = url.Host, url.Path + } + id = fmt.Sprintf("tag:%s,%s:%s", host, dateStr, path) + } else { + id = "urn:uuid:" + NewUUID().String() + } + } + var name, email string + if i.Author != nil { + name, email = i.Author.Name, i.Author.Email + } + + link_rel := i.Link.Rel + if link_rel == "" { + link_rel = "alternate" + } + x := &AtomEntry{ + Title: i.Title, + Links: []AtomLink{{Href: i.Link.Href, Rel: link_rel, Type: i.Link.Type}}, + Id: id, + Updated: anyTimeFormat(time.RFC3339, i.Updated, i.Created), + Summary: s, + } + + // if there's a content, assume it's html + if len(i.Content) > 0 { + x.Content = &AtomContent{Content: i.Content, Type: "html"} + } + + if i.Enclosure != nil && link_rel != "enclosure" { + x.Links = append(x.Links, AtomLink{Href: i.Enclosure.Url, Rel: "enclosure", Type: i.Enclosure.Type, Length: i.Enclosure.Length}) + } + + if len(name) > 0 || len(email) > 0 { + x.Author = &AtomAuthor{AtomPerson: AtomPerson{Name: name, Email: email}} + } + return x +} + +// create a new AtomFeed with a generic Feed struct's data +func (a *Atom) AtomFeed() *AtomFeed { + updated := anyTimeFormat(time.RFC3339, a.Updated, a.Created) + feed := &AtomFeed{ + Xmlns: ns, + Title: a.Title, + Link: &AtomLink{Href: a.Link.Href, Rel: a.Link.Rel}, + Subtitle: a.Description, + Id: a.Link.Href, + Updated: updated, + Rights: a.Copyright, + } + if a.Author != nil { + feed.Author = &AtomAuthor{AtomPerson: AtomPerson{Name: a.Author.Name, Email: a.Author.Email}} + } + for _, e := range a.Items { + feed.Entries = append(feed.Entries, newAtomEntry(e)) + } + return feed +} + +// FeedXml returns an XML-Ready object for an Atom object +func (a *Atom) FeedXml() interface{} { + return a.AtomFeed() +} + +// FeedXml returns an XML-ready object for an AtomFeed object +func (a *AtomFeed) FeedXml() interface{} { + return a +} diff --git a/vendor/github.com/gorilla/feeds/doc.go b/vendor/github.com/gorilla/feeds/doc.go new file mode 100644 index 0000000000000..4e0759ccccb52 --- /dev/null +++ b/vendor/github.com/gorilla/feeds/doc.go @@ -0,0 +1,73 @@ +/* +Syndication (feed) generator library for golang. + +Installing + + go get github.com/gorilla/feeds + +Feeds provides a simple, generic Feed interface with a generic Item object as well as RSS, Atom and JSON Feed specific RssFeed, AtomFeed and JSONFeed objects which allow access to all of each spec's defined elements. + +Examples + +Create a Feed and some Items in that feed using the generic interfaces: + + import ( + "time" + . "github.com/gorilla/feeds" + ) + + now = time.Now() + + feed := &Feed{ + Title: "jmoiron.net blog", + Link: &Link{Href: "http://jmoiron.net/blog"}, + Description: "discussion about tech, footie, photos", + Author: &Author{Name: "Jason Moiron", Email: "jmoiron@jmoiron.net"}, + Created: now, + Copyright: "This work is copyright © Benjamin Button", + } + + feed.Items = []*Item{ + &Item{ + Title: "Limiting Concurrency in Go", + Link: &Link{Href: "http://jmoiron.net/blog/limiting-concurrency-in-go/"}, + Description: "A discussion on controlled parallelism in golang", + Author: &Author{Name: "Jason Moiron", Email: "jmoiron@jmoiron.net"}, + Created: now, + }, + &Item{ + Title: "Logic-less Template Redux", + Link: &Link{Href: "http://jmoiron.net/blog/logicless-template-redux/"}, + Description: "More thoughts on logicless templates", + Created: now, + }, + &Item{ + Title: "Idiomatic Code Reuse in Go", + Link: &Link{Href: "http://jmoiron.net/blog/idiomatic-code-reuse-in-go/"}, + Description: "How to use interfaces effectively", + Created: now, + }, + } + +From here, you can output Atom, RSS, or JSON Feed versions of this feed easily + + atom, err := feed.ToAtom() + rss, err := feed.ToRss() + json, err := feed.ToJSON() + +You can also get access to the underlying objects that feeds uses to export its XML + + atomFeed := (&Atom{Feed: feed}).AtomFeed() + rssFeed := (&Rss{Feed: feed}).RssFeed() + jsonFeed := (&JSON{Feed: feed}).JSONFeed() + +From here, you can modify or add each syndication's specific fields before outputting + + atomFeed.Subtitle = "plays the blues" + atom, err := ToXML(atomFeed) + rssFeed.Generator = "gorilla/feeds v1.0 (github.com/gorilla/feeds)" + rss, err := ToXML(rssFeed) + jsonFeed.NextUrl = "https://www.example.com/feed.json?page=2" + json, err := jsonFeed.ToJSON() +*/ +package feeds diff --git a/vendor/github.com/gorilla/feeds/feed.go b/vendor/github.com/gorilla/feeds/feed.go new file mode 100644 index 0000000000000..790a1b6ce688c --- /dev/null +++ b/vendor/github.com/gorilla/feeds/feed.go @@ -0,0 +1,145 @@ +package feeds + +import ( + "encoding/json" + "encoding/xml" + "io" + "sort" + "time" +) + +type Link struct { + Href, Rel, Type, Length string +} + +type Author struct { + Name, Email string +} + +type Image struct { + Url, Title, Link string + Width, Height int +} + +type Enclosure struct { + Url, Length, Type string +} + +type Item struct { + Title string + Link *Link + Source *Link + Author *Author + Description string // used as description in rss, summary in atom + Id string // used as guid in rss, id in atom + Updated time.Time + Created time.Time + Enclosure *Enclosure + Content string +} + +type Feed struct { + Title string + Link *Link + Description string + Author *Author + Updated time.Time + Created time.Time + Id string + Subtitle string + Items []*Item + Copyright string + Image *Image +} + +// add a new Item to a Feed +func (f *Feed) Add(item *Item) { + f.Items = append(f.Items, item) +} + +// returns the first non-zero time formatted as a string or "" +func anyTimeFormat(format string, times ...time.Time) string { + for _, t := range times { + if !t.IsZero() { + return t.Format(format) + } + } + return "" +} + +// interface used by ToXML to get a object suitable for exporting XML. +type XmlFeed interface { + FeedXml() interface{} +} + +// turn a feed object (either a Feed, AtomFeed, or RssFeed) into xml +// returns an error if xml marshaling fails +func ToXML(feed XmlFeed) (string, error) { + x := feed.FeedXml() + data, err := xml.MarshalIndent(x, "", " ") + if err != nil { + return "", err + } + // strip empty line from default xml header + s := xml.Header[:len(xml.Header)-1] + string(data) + return s, nil +} + +// WriteXML writes a feed object (either a Feed, AtomFeed, or RssFeed) as XML into +// the writer. Returns an error if XML marshaling fails. +func WriteXML(feed XmlFeed, w io.Writer) error { + x := feed.FeedXml() + // write default xml header, without the newline + if _, err := w.Write([]byte(xml.Header[:len(xml.Header)-1])); err != nil { + return err + } + e := xml.NewEncoder(w) + e.Indent("", " ") + return e.Encode(x) +} + +// creates an Atom representation of this feed +func (f *Feed) ToAtom() (string, error) { + a := &Atom{f} + return ToXML(a) +} + +// WriteAtom writes an Atom representation of this feed to the writer. +func (f *Feed) WriteAtom(w io.Writer) error { + return WriteXML(&Atom{f}, w) +} + +// creates an Rss representation of this feed +func (f *Feed) ToRss() (string, error) { + r := &Rss{f} + return ToXML(r) +} + +// WriteRss writes an RSS representation of this feed to the writer. +func (f *Feed) WriteRss(w io.Writer) error { + return WriteXML(&Rss{f}, w) +} + +// ToJSON creates a JSON Feed representation of this feed +func (f *Feed) ToJSON() (string, error) { + j := &JSON{f} + return j.ToJSON() +} + +// WriteJSON writes an JSON representation of this feed to the writer. +func (f *Feed) WriteJSON(w io.Writer) error { + j := &JSON{f} + feed := j.JSONFeed() + + e := json.NewEncoder(w) + e.SetIndent("", " ") + return e.Encode(feed) +} + +// Sort sorts the Items in the feed with the given less function. +func (f *Feed) Sort(less func(a, b *Item) bool) { + lessFunc := func(i, j int) bool { + return less(f.Items[i], f.Items[j]) + } + sort.SliceStable(f.Items, lessFunc) +} diff --git a/vendor/github.com/gorilla/feeds/json.go b/vendor/github.com/gorilla/feeds/json.go new file mode 100644 index 0000000000000..75a82fd62a24d --- /dev/null +++ b/vendor/github.com/gorilla/feeds/json.go @@ -0,0 +1,183 @@ +package feeds + +import ( + "encoding/json" + "strings" + "time" +) + +const jsonFeedVersion = "https://jsonfeed.org/version/1" + +// JSONAuthor represents the author of the feed or of an individual item +// in the feed +type JSONAuthor struct { + Name string `json:"name,omitempty"` + Url string `json:"url,omitempty"` + Avatar string `json:"avatar,omitempty"` +} + +// JSONAttachment represents a related resource. Podcasts, for instance, would +// include an attachment that’s an audio or video file. +type JSONAttachment struct { + Url string `json:"url,omitempty"` + MIMEType string `json:"mime_type,omitempty"` + Title string `json:"title,omitempty"` + Size int32 `json:"size,omitempty"` + Duration time.Duration `json:"duration_in_seconds,omitempty"` +} + +// MarshalJSON implements the json.Marshaler interface. +// The Duration field is marshaled in seconds, all other fields are marshaled +// based upon the definitions in struct tags. +func (a *JSONAttachment) MarshalJSON() ([]byte, error) { + type EmbeddedJSONAttachment JSONAttachment + return json.Marshal(&struct { + Duration float64 `json:"duration_in_seconds,omitempty"` + *EmbeddedJSONAttachment + }{ + EmbeddedJSONAttachment: (*EmbeddedJSONAttachment)(a), + Duration: a.Duration.Seconds(), + }) +} + +// UnmarshalJSON implements the json.Unmarshaler interface. +// The Duration field is expected to be in seconds, all other field types +// match the struct definition. +func (a *JSONAttachment) UnmarshalJSON(data []byte) error { + type EmbeddedJSONAttachment JSONAttachment + var raw struct { + Duration float64 `json:"duration_in_seconds,omitempty"` + *EmbeddedJSONAttachment + } + raw.EmbeddedJSONAttachment = (*EmbeddedJSONAttachment)(a) + + err := json.Unmarshal(data, &raw) + if err != nil { + return err + } + + if raw.Duration > 0 { + nsec := int64(raw.Duration * float64(time.Second)) + raw.EmbeddedJSONAttachment.Duration = time.Duration(nsec) + } + + return nil +} + +// JSONItem represents a single entry/post for the feed. +type JSONItem struct { + Id string `json:"id"` + Url string `json:"url,omitempty"` + ExternalUrl string `json:"external_url,omitempty"` + Title string `json:"title,omitempty"` + ContentHTML string `json:"content_html,omitempty"` + ContentText string `json:"content_text,omitempty"` + Summary string `json:"summary,omitempty"` + Image string `json:"image,omitempty"` + BannerImage string `json:"banner_,omitempty"` + PublishedDate *time.Time `json:"date_published,omitempty"` + ModifiedDate *time.Time `json:"date_modified,omitempty"` + Author *JSONAuthor `json:"author,omitempty"` + Tags []string `json:"tags,omitempty"` + Attachments []JSONAttachment `json:"attachments,omitempty"` +} + +// JSONHub describes an endpoint that can be used to subscribe to real-time +// notifications from the publisher of this feed. +type JSONHub struct { + Type string `json:"type"` + Url string `json:"url"` +} + +// JSONFeed represents a syndication feed in the JSON Feed Version 1 format. +// Matching the specification found here: https://jsonfeed.org/version/1. +type JSONFeed struct { + Version string `json:"version"` + Title string `json:"title"` + HomePageUrl string `json:"home_page_url,omitempty"` + FeedUrl string `json:"feed_url,omitempty"` + Description string `json:"description,omitempty"` + UserComment string `json:"user_comment,omitempty"` + NextUrl string `json:"next_url,omitempty"` + Icon string `json:"icon,omitempty"` + Favicon string `json:"favicon,omitempty"` + Author *JSONAuthor `json:"author,omitempty"` + Expired *bool `json:"expired,omitempty"` + Hubs []*JSONItem `json:"hubs,omitempty"` + Items []*JSONItem `json:"items,omitempty"` +} + +// JSON is used to convert a generic Feed to a JSONFeed. +type JSON struct { + *Feed +} + +// ToJSON encodes f into a JSON string. Returns an error if marshalling fails. +func (f *JSON) ToJSON() (string, error) { + return f.JSONFeed().ToJSON() +} + +// ToJSON encodes f into a JSON string. Returns an error if marshalling fails. +func (f *JSONFeed) ToJSON() (string, error) { + data, err := json.MarshalIndent(f, "", " ") + if err != nil { + return "", err + } + + return string(data), nil +} + +// JSONFeed creates a new JSONFeed with a generic Feed struct's data. +func (f *JSON) JSONFeed() *JSONFeed { + feed := &JSONFeed{ + Version: jsonFeedVersion, + Title: f.Title, + Description: f.Description, + } + + if f.Link != nil { + feed.HomePageUrl = f.Link.Href + } + if f.Author != nil { + feed.Author = &JSONAuthor{ + Name: f.Author.Name, + } + } + for _, e := range f.Items { + feed.Items = append(feed.Items, newJSONItem(e)) + } + return feed +} + +func newJSONItem(i *Item) *JSONItem { + item := &JSONItem{ + Id: i.Id, + Title: i.Title, + Summary: i.Description, + + ContentHTML: i.Content, + } + + if i.Link != nil { + item.Url = i.Link.Href + } + if i.Source != nil { + item.ExternalUrl = i.Source.Href + } + if i.Author != nil { + item.Author = &JSONAuthor{ + Name: i.Author.Name, + } + } + if !i.Created.IsZero() { + item.PublishedDate = &i.Created + } + if !i.Updated.IsZero() { + item.ModifiedDate = &i.Updated + } + if i.Enclosure != nil && strings.HasPrefix(i.Enclosure.Type, "image/") { + item.Image = i.Enclosure.Url + } + + return item +} diff --git a/vendor/github.com/gorilla/feeds/rss.go b/vendor/github.com/gorilla/feeds/rss.go new file mode 100644 index 0000000000000..09179dfb2af75 --- /dev/null +++ b/vendor/github.com/gorilla/feeds/rss.go @@ -0,0 +1,168 @@ +package feeds + +// rss support +// validation done according to spec here: +// http://cyber.law.harvard.edu/rss/rss.html + +import ( + "encoding/xml" + "fmt" + "time" +) + +// private wrapper around the RssFeed which gives us the .. xml +type RssFeedXml struct { + XMLName xml.Name `xml:"rss"` + Version string `xml:"version,attr"` + ContentNamespace string `xml:"xmlns:content,attr"` + Channel *RssFeed +} + +type RssContent struct { + XMLName xml.Name `xml:"content:encoded"` + Content string `xml:",cdata"` +} + +type RssImage struct { + XMLName xml.Name `xml:"image"` + Url string `xml:"url"` + Title string `xml:"title"` + Link string `xml:"link"` + Width int `xml:"width,omitempty"` + Height int `xml:"height,omitempty"` +} + +type RssTextInput struct { + XMLName xml.Name `xml:"textInput"` + Title string `xml:"title"` + Description string `xml:"description"` + Name string `xml:"name"` + Link string `xml:"link"` +} + +type RssFeed struct { + XMLName xml.Name `xml:"channel"` + Title string `xml:"title"` // required + Link string `xml:"link"` // required + Description string `xml:"description"` // required + Language string `xml:"language,omitempty"` + Copyright string `xml:"copyright,omitempty"` + ManagingEditor string `xml:"managingEditor,omitempty"` // Author used + WebMaster string `xml:"webMaster,omitempty"` + PubDate string `xml:"pubDate,omitempty"` // created or updated + LastBuildDate string `xml:"lastBuildDate,omitempty"` // updated used + Category string `xml:"category,omitempty"` + Generator string `xml:"generator,omitempty"` + Docs string `xml:"docs,omitempty"` + Cloud string `xml:"cloud,omitempty"` + Ttl int `xml:"ttl,omitempty"` + Rating string `xml:"rating,omitempty"` + SkipHours string `xml:"skipHours,omitempty"` + SkipDays string `xml:"skipDays,omitempty"` + Image *RssImage + TextInput *RssTextInput + Items []*RssItem `xml:"item"` +} + +type RssItem struct { + XMLName xml.Name `xml:"item"` + Title string `xml:"title"` // required + Link string `xml:"link"` // required + Description string `xml:"description"` // required + Content *RssContent + Author string `xml:"author,omitempty"` + Category string `xml:"category,omitempty"` + Comments string `xml:"comments,omitempty"` + Enclosure *RssEnclosure + Guid string `xml:"guid,omitempty"` // Id used + PubDate string `xml:"pubDate,omitempty"` // created or updated + Source string `xml:"source,omitempty"` +} + +type RssEnclosure struct { + //RSS 2.0 + XMLName xml.Name `xml:"enclosure"` + Url string `xml:"url,attr"` + Length string `xml:"length,attr"` + Type string `xml:"type,attr"` +} + +type Rss struct { + *Feed +} + +// create a new RssItem with a generic Item struct's data +func newRssItem(i *Item) *RssItem { + item := &RssItem{ + Title: i.Title, + Link: i.Link.Href, + Description: i.Description, + Guid: i.Id, + PubDate: anyTimeFormat(time.RFC1123Z, i.Created, i.Updated), + } + if len(i.Content) > 0 { + item.Content = &RssContent{Content: i.Content} + } + if i.Source != nil { + item.Source = i.Source.Href + } + + // Define a closure + if i.Enclosure != nil && i.Enclosure.Type != "" && i.Enclosure.Length != "" { + item.Enclosure = &RssEnclosure{Url: i.Enclosure.Url, Type: i.Enclosure.Type, Length: i.Enclosure.Length} + } + + if i.Author != nil { + item.Author = i.Author.Name + } + return item +} + +// create a new RssFeed with a generic Feed struct's data +func (r *Rss) RssFeed() *RssFeed { + pub := anyTimeFormat(time.RFC1123Z, r.Created, r.Updated) + build := anyTimeFormat(time.RFC1123Z, r.Updated) + author := "" + if r.Author != nil { + author = r.Author.Email + if len(r.Author.Name) > 0 { + author = fmt.Sprintf("%s (%s)", r.Author.Email, r.Author.Name) + } + } + + var image *RssImage + if r.Image != nil { + image = &RssImage{Url: r.Image.Url, Title: r.Image.Title, Link: r.Image.Link, Width: r.Image.Width, Height: r.Image.Height} + } + + channel := &RssFeed{ + Title: r.Title, + Link: r.Link.Href, + Description: r.Description, + ManagingEditor: author, + PubDate: pub, + LastBuildDate: build, + Copyright: r.Copyright, + Image: image, + } + for _, i := range r.Items { + channel.Items = append(channel.Items, newRssItem(i)) + } + return channel +} + +// FeedXml returns an XML-Ready object for an Rss object +func (r *Rss) FeedXml() interface{} { + // only generate version 2.0 feeds for now + return r.RssFeed().FeedXml() + +} + +// FeedXml returns an XML-ready object for an RssFeed object +func (r *RssFeed) FeedXml() interface{} { + return &RssFeedXml{ + Version: "2.0", + Channel: r, + ContentNamespace: "http://purl.org/rss/1.0/modules/content/", + } +} diff --git a/vendor/github.com/gorilla/feeds/test.atom b/vendor/github.com/gorilla/feeds/test.atom new file mode 100644 index 0000000000000..aa152148155dd --- /dev/null +++ b/vendor/github.com/gorilla/feeds/test.atom @@ -0,0 +1,92 @@ + + + <![CDATA[Lorem ipsum feed for an interval of 1 minutes]]> + + http://example.com/ + RSS for Node + Tue, 30 Oct 2018 23:22:37 GMT + + Tue, 30 Oct 2018 23:22:00 GMT + + 60 + + <![CDATA[Lorem ipsum 2018-10-30T23:22:00+00:00]]> + + http://example.com/test/1540941720 + http://example.com/test/1540941720 + + Tue, 30 Oct 2018 23:22:00 GMT + + + <![CDATA[Lorem ipsum 2018-10-30T23:21:00+00:00]]> + + http://example.com/test/1540941660 + http://example.com/test/1540941660 + + Tue, 30 Oct 2018 23:21:00 GMT + + + <![CDATA[Lorem ipsum 2018-10-30T23:20:00+00:00]]> + + http://example.com/test/1540941600 + http://example.com/test/1540941600 + + Tue, 30 Oct 2018 23:20:00 GMT + + + <![CDATA[Lorem ipsum 2018-10-30T23:19:00+00:00]]> + + http://example.com/test/1540941540 + http://example.com/test/1540941540 + + Tue, 30 Oct 2018 23:19:00 GMT + + + <![CDATA[Lorem ipsum 2018-10-30T23:18:00+00:00]]> + + http://example.com/test/1540941480 + http://example.com/test/1540941480 + + Tue, 30 Oct 2018 23:18:00 GMT + + + <![CDATA[Lorem ipsum 2018-10-30T23:17:00+00:00]]> + + http://example.com/test/1540941420 + http://example.com/test/1540941420 + + Tue, 30 Oct 2018 23:17:00 GMT + + + <![CDATA[Lorem ipsum 2018-10-30T23:16:00+00:00]]> + + http://example.com/test/1540941360 + http://example.com/test/1540941360 + + Tue, 30 Oct 2018 23:16:00 GMT + + + <![CDATA[Lorem ipsum 2018-10-30T23:15:00+00:00]]> + + http://example.com/test/1540941300 + http://example.com/test/1540941300 + + Tue, 30 Oct 2018 23:15:00 GMT + + + <![CDATA[Lorem ipsum 2018-10-30T23:14:00+00:00]]> + + http://example.com/test/1540941240 + http://example.com/test/1540941240 + + Tue, 30 Oct 2018 23:14:00 GMT + + + <![CDATA[Lorem ipsum 2018-10-30T23:13:00+00:00]]> + + http://example.com/test/1540941180 + http://example.com/test/1540941180 + + Tue, 30 Oct 2018 23:13:00 GMT + + \ No newline at end of file diff --git a/vendor/github.com/gorilla/feeds/test.rss b/vendor/github.com/gorilla/feeds/test.rss new file mode 100644 index 0000000000000..8d912aba523aa --- /dev/null +++ b/vendor/github.com/gorilla/feeds/test.rss @@ -0,0 +1,96 @@ + + + + <![CDATA[Lorem ipsum feed for an interval of 1 minutes]]> + + http://example.com/ + RSS for Node + Tue, 30 Oct 2018 23:22:37 GMT + + Tue, 30 Oct 2018 23:22:00 GMT + + 60 + + <![CDATA[Lorem ipsum 2018-10-30T23:22:00+00:00]]> + + http://example.com/test/1540941720 + http://example.com/test/1540941720 + + Tue, 30 Oct 2018 23:22:00 GMT + + + <![CDATA[Lorem ipsum 2018-10-30T23:21:00+00:00]]> + + http://example.com/test/1540941660 + http://example.com/test/1540941660 + + Tue, 30 Oct 2018 23:21:00 GMT + + + <![CDATA[Lorem ipsum 2018-10-30T23:20:00+00:00]]> + + http://example.com/test/1540941600 + http://example.com/test/1540941600 + + Tue, 30 Oct 2018 23:20:00 GMT + + + <![CDATA[Lorem ipsum 2018-10-30T23:19:00+00:00]]> + + http://example.com/test/1540941540 + http://example.com/test/1540941540 + + Tue, 30 Oct 2018 23:19:00 GMT + + + <![CDATA[Lorem ipsum 2018-10-30T23:18:00+00:00]]> + + http://example.com/test/1540941480 + http://example.com/test/1540941480 + + Tue, 30 Oct 2018 23:18:00 GMT + + + <![CDATA[Lorem ipsum 2018-10-30T23:17:00+00:00]]> + + http://example.com/test/1540941420 + http://example.com/test/1540941420 + + Tue, 30 Oct 2018 23:17:00 GMT + + + <![CDATA[Lorem ipsum 2018-10-30T23:16:00+00:00]]> + + http://example.com/test/1540941360 + http://example.com/test/1540941360 + + Tue, 30 Oct 2018 23:16:00 GMT + + + <![CDATA[Lorem ipsum 2018-10-30T23:15:00+00:00]]> + + http://example.com/test/1540941300 + http://example.com/test/1540941300 + + Tue, 30 Oct 2018 23:15:00 GMT + + + <![CDATA[Lorem ipsum 2018-10-30T23:14:00+00:00]]> + + http://example.com/test/1540941240 + http://example.com/test/1540941240 + + Tue, 30 Oct 2018 23:14:00 GMT + + + <![CDATA[Lorem ipsum 2018-10-30T23:13:00+00:00]]> + + http://example.com/test/1540941180 + http://example.com/test/1540941180 + + Tue, 30 Oct 2018 23:13:00 GMT + + + \ No newline at end of file diff --git a/vendor/github.com/gorilla/feeds/to-implement.md b/vendor/github.com/gorilla/feeds/to-implement.md new file mode 100644 index 0000000000000..45fd1e75e2f04 --- /dev/null +++ b/vendor/github.com/gorilla/feeds/to-implement.md @@ -0,0 +1,20 @@ +[Full iTunes list](https://help.apple.com/itc/podcasts_connect/#/itcb54353390) + +[Example of ideal iTunes RSS feed](https://help.apple.com/itc/podcasts_connect/#/itcbaf351599) + +``` + + + + + + + + + + + + + + +``` \ No newline at end of file diff --git a/vendor/github.com/gorilla/feeds/uuid.go b/vendor/github.com/gorilla/feeds/uuid.go new file mode 100644 index 0000000000000..51bbafe13f6ad --- /dev/null +++ b/vendor/github.com/gorilla/feeds/uuid.go @@ -0,0 +1,27 @@ +package feeds + +// relevant bits from https://github.com/abneptis/GoUUID/blob/master/uuid.go + +import ( + "crypto/rand" + "fmt" +) + +type UUID [16]byte + +// create a new uuid v4 +func NewUUID() *UUID { + u := &UUID{} + _, err := rand.Read(u[:16]) + if err != nil { + panic(err) + } + + u[8] = (u[8] | 0x80) & 0xBf + u[6] = (u[6] | 0x40) & 0x4f + return u +} + +func (u *UUID) String() string { + return fmt.Sprintf("%x-%x-%x-%x-%x", u[:4], u[4:6], u[6:8], u[8:10], u[10:]) +} diff --git a/vendor/modules.txt b/vendor/modules.txt index 370d56a5fd878..d92e58cc2a734 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -448,6 +448,9 @@ github.com/google/uuid github.com/gorilla/context # github.com/gorilla/css v1.0.0 github.com/gorilla/css/scanner +# github.com/gorilla/feeds v1.1.1 +## explicit +github.com/gorilla/feeds # github.com/gorilla/handlers v1.5.1 github.com/gorilla/handlers # github.com/gorilla/mux v1.8.0 From 460e8ef2c6d6e0f579feaafe455695cf74c99c00 Mon Sep 17 00:00:00 2001 From: 6543 <6543@obermui.de> Date: Fri, 11 Jun 2021 02:03:20 +0200 Subject: [PATCH 04/18] wip --- options/locale/locale_en-US.ini | 1 + routers/web/rss/convert.go | 227 ++++++++++++++++++++++++++++++++ routers/web/user/profile.go | 20 +-- 3 files changed, 231 insertions(+), 17 deletions(-) create mode 100644 routers/web/rss/convert.go diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index 4df9965bcb07a..0851290f6ac9e 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -221,6 +221,7 @@ view_home = View %s search_repos = Find a repository… filter = Other Filters filter_by_team_repositories = Filter by team repositories +profile_of = Profile of %s show_archived = Archived show_both_archived_unarchived = Showing both archived and unarchived diff --git a/routers/web/rss/convert.go b/routers/web/rss/convert.go new file mode 100644 index 0000000000000..a9a88ada787dc --- /dev/null +++ b/routers/web/rss/convert.go @@ -0,0 +1,227 @@ +package rss + +import ( + "fmt" + "strings" + "time" + + "code.gitea.io/gitea/models" + "code.gitea.io/gitea/modules/context" + + "github.com/gorilla/feeds" +) + +func FeedActionsToFeedItems(ctx *context.Context, actions []*models.Action) (items []*feeds.Item) { + for _, act := range actions { + act.LoadActUser() + + content, desc, title := "", "", "" + + link := &feeds.Link{Href: act.GetCommentLink()} + + title = tmpTypeName(act.OpType) + + // title + { + switch act.OpType { + + case models.ActionCreateRepo: + title = ctx.Tr("action.create_repo", act.GetRepoLink(), act.ShortRepoPath()) + case models.ActionRenameRepo: + title = ctx.Tr("action.rename_repo", act.GetContent(), act.GetRepoLink(), act.ShortRepoPath()) + case models.ActionCommitRepo: + branchLink := act.GetBranch() + if len(act.Content) != 0 { + title = ctx.Tr("action.commit_repo", act.GetRepoLink(), branchLink, act.GetBranch(), act.ShortRepoPath()) + } else { + title = ctx.Tr("action.create_branch", act.GetRepoLink(), branchLink, act.GetBranch(), act.ShortRepoPath()) + } + case models.ActionCreateIssue: + title = ` {{ $index := index .GetIssueInfos 0}} + {{$.i18n.Tr "action.create_issue" .GetRepoLink $index .ShortRepoPath | Str2html}}` + case models.ActionCreatePullRequest: + title = `{{ $index := index .GetIssueInfos 0}} + {{$.i18n.Tr "action.create_pull_request" .GetRepoLink $index .ShortRepoPath | Str2html}}` + case models.ActionTransferRepo: + title = `{{$.i18n.Tr "action.transfer_repo" .GetContent .GetRepoLink .ShortRepoPath | Str2html}}` + case models.ActionPushTag: + title = `{{ $tagLink := .GetTag | EscapePound | Escape}} + {{$.i18n.Tr "action.push_tag" .GetRepoLink $tagLink .ShortRepoPath | Str2html}}` + case models.ActionCommentIssue: + title = `{{ $index := index .GetIssueInfos 0}} + {{$.i18n.Tr "action.comment_issue" .GetRepoLink $index .ShortRepoPath | Str2html}}` + case models.ActionMergePullRequest: + title = `{{ $index := index .GetIssueInfos 0}} + {{$.i18n.Tr "action.merge_pull_request" .GetRepoLink $index .ShortRepoPath | Str2html}}` + case models.ActionCloseIssue: + title = `{{ $index := index .GetIssueInfos 0}} + {{$.i18n.Tr "action.close_issue" .GetRepoLink $index .ShortRepoPath | Str2html}}` + case models.ActionReopenIssue: + title = `{{ $index := index .GetIssueInfos 0}} + {{$.i18n.Tr "action.reopen_issue" .GetRepoLink $index .ShortRepoPath | Str2html}}` + case models.ActionClosePullRequest: + title = `{{ $index := index .GetIssueInfos 0}} + {{$.i18n.Tr "action.close_pull_request" .GetRepoLink $index .ShortRepoPath | Str2html}}` + case models.ActionReopenPullRequest: + title = `{{ $index := index .GetIssueInfos 0}} + {{$.i18n.Tr "action.reopen_pull_request" .GetRepoLink $index .ShortRepoPath | Str2html}}` + case models.ActionDeleteTag: + title = `{{ $index := index .GetIssueInfos 0}} + {{$.i18n.Tr "action.delete_tag" .GetRepoLink (.GetTag|Escape) .ShortRepoPath | Str2html}}` + case models.ActionDeleteBranch: + title = `{{ $index := index .GetIssueInfos 0}} + {{$.i18n.Tr "action.delete_branch" .GetRepoLink (.GetBranch|Escape) .ShortRepoPath | Str2html}}` + case models.ActionMirrorSyncPush: + title = `{{ $branchLink := .GetBranch | EscapePound}} + {{$.i18n.Tr "action.mirror_sync_push" .GetRepoLink $branchLink (.GetBranch|Escape) .ShortRepoPath | Str2html}}` + case models.ActionMirrorSyncCreate: + title = `{{$.i18n.Tr "action.mirror_sync_create" .GetRepoLink (.GetBranch|Escape) .ShortRepoPath | Str2html}}` + case models.ActionMirrorSyncDelete: + title = `{{$.i18n.Tr "action.mirror_sync_delete" .GetRepoLink (.GetBranch|Escape) .ShortRepoPath | Str2html}}` + case models.ActionApprovePullRequest: + title = `{{ $index := index .GetIssueInfos 0}} + {{$.i18n.Tr "action.approve_pull_request" .GetRepoLink $index .ShortRepoPath | Str2html}}` + case models.ActionRejectPullRequest: + title = `{{ $index := index .GetIssueInfos 0}} + {{$.i18n.Tr "action.reject_pull_request" .GetRepoLink $index .ShortRepoPath | Str2html}}` + case models.ActionCommentPull: + title = `{{ $index := index .GetIssueInfos 0}} + {{$.i18n.Tr "action.comment_pull" .GetRepoLink $index .ShortRepoPath | Str2html}}` + case models.ActionPublishRelease: + title = `{{ $branchLink := .GetBranch | EscapePound | Escape}} + {{ $linkText := .Content | RenderEmoji }} + {{$.i18n.Tr "action.publish_release" .GetRepoLink $branchLink .ShortRepoPath $linkText | Str2html}}` + case models.ActionPullReviewDismissed: + title = `{{ $index := index .GetIssueInfos 0}} + {{ $reviewer := index .GetIssueInfos 1}} + {{$.i18n.Tr "action.review_dismissed" .GetRepoLink $index .ShortRepoPath $reviewer | Str2html}}` + } + } + + // description & content + { + switch act.OpType { + case models.ActionCommitRepo, models.ActionMirrorSyncPush: + desc = `
+
    + {{ $push := ActionContent2Commits .}} + {{ $repoLink := .GetRepoLink}} + {{if $push.Commits}} + {{range $push.Commits}} + {{ $commitLink := printf "%s/commit/%s" $repoLink .Sha1}} +
  • + {{avatarHTML ($push.AvatarLink .AuthorEmail) 16 "mr-2" .AuthorName}} + {{ShortSha .Sha1}} + + {{RenderCommitMessage .Message $repoLink $.ComposeMetas}} + +
  • + {{end}} + {{end}} + {{if and (gt $push.Len 1) $push.CompareURL}}
  • {{$.i18n.Tr "action.compare_commits" $push.Len}} »
  • {{end}} +
+
` + + case models.ActionCreateIssue, models.ActionCreatePullRequest: + desc = strings.Join(act.GetIssueInfos(), "#") // index .GetIssueInfos 1 | RenderEmoji + content = act.GetIssueContent() + case models.ActionCommentIssue, models.ActionApprovePullRequest, models.ActionRejectPullRequest, models.ActionCommentPull: + desc = act.GetIssueTitle() // class="text truncate issue title" | RenderEmoji + comment := act.GetIssueInfos()[1] // class="text light grey" | RenderEmoji + if len(comment) != 0 { + desc += "\n\n" + comment + } + case models.ActionMergePullRequest: + desc = act.GetIssueInfos()[1] // class="text light grey" + case models.ActionCloseIssue, models.ActionReopenIssue, models.ActionClosePullRequest, models.ActionReopenPullRequest: + desc = `{{.GetIssueTitle | RenderEmoji}}` + case models.ActionPullReviewDismissed: + desc = `

{{$.i18n.Tr "action.review_dismissed_reason"}}

+

{{index .GetIssueInfos 2 | RenderEmoji}}

` + } + } + if len(content) == 0 { + content = desc + } + + // img := templates.ActionIcon(act.OpType) + + items = append(items, &feeds.Item{ + Title: title, + Link: link, + Description: desc, + Author: feedsAuthor(act.ActUser), + Id: fmt.Sprint(act.ID), + Created: time.Now(), // Created: act.CreatedUnix.AsTime(), + Content: content, + }) + } + return +} + +func feedsAuthor(user *models.User) *feeds.Author { + return &feeds.Author{ + Name: user.DisplayName(), + Email: user.GetEmail(), + } +} + +func tmpTypeName(t models.ActionType) string { + switch t { + case models.ActionCreateRepo: + return "ActionCreateRepo" + case models.ActionRenameRepo: + return "ActionRenameRepo" + case models.ActionStarRepo: + return "ActionStarRepo" + case models.ActionWatchRepo: + return "ActionWatchRepo" + case models.ActionCommitRepo: + return "ActionCommitRepo" + case models.ActionCreateIssue: + return "ActionCreateIssue" + case models.ActionCreatePullRequest: + return "ActionCreatePullRequest" + case models.ActionTransferRepo: + return "ActionTransferRepo" + case models.ActionPushTag: + return "ActionPushTag" + case models.ActionCommentIssue: + return "ActionCommentIssue" + case models.ActionMergePullRequest: + return "ActionMergePullRequest" + case models.ActionCloseIssue: + return "ActionCloseIssue" + case models.ActionReopenIssue: + return "ActionReopenIssue" + case models.ActionClosePullRequest: + return "ActionClosePullRequest" + case models.ActionReopenPullRequest: + return "ActionReopenPullRequest" + case models.ActionDeleteTag: + return "ActionDeleteTag" + case models.ActionDeleteBranch: + return "ActionDeleteBranch" + case models.ActionMirrorSyncPush: + return "ActionMirrorSyncPush" + case models.ActionMirrorSyncCreate: + return "ActionMirrorSyncCreate" + case models.ActionMirrorSyncDelete: + return "ActionMirrorSyncDelete" + case models.ActionApprovePullRequest: + return "ActionApprovePullRequest" + case models.ActionRejectPullRequest: + return "ActionRejectPullRequest" + case models.ActionCommentPull: + return "ActionCommentPull" + case models.ActionPublishRelease: + return "ActionPublishRelease" + case models.ActionPullReviewDismissed: + return "ActionPullReviewDismissed" + } + return "" +} + +/* + + */ diff --git a/routers/web/user/profile.go b/routers/web/user/profile.go index 51f11ec5f282c..aebca5991f958 100644 --- a/routers/web/user/profile.go +++ b/routers/web/user/profile.go @@ -19,6 +19,7 @@ import ( "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/routers/web/org" + "code.gitea.io/gitea/routers/web/rss" "github.com/gorilla/feeds" ) @@ -336,13 +337,13 @@ func ShowRSS(ctx *context.Context, ctxUser *models.User) { now := time.Now() feed := &feeds.Feed{ - Title: ctxUser.FullName, + Title: ctx.Tr("home.profile_of", ctxUser.DisplayName()), Link: &feeds.Link{Href: ctxUser.HTMLURL()}, Description: ctxUser.Description, Created: now, } - feed.Items = feedActionsToFeedItems(actions) + feed.Items = rss.FeedActionsToFeedItems(ctx, actions) //atom, err := feed.ToAtom() if rss, err := feed.ToRss(); err != nil { @@ -353,21 +354,6 @@ func ShowRSS(ctx *context.Context, ctxUser *models.User) { } -func feedActionsToFeedItems(actions []*models.Action) (items []*feeds.Item) { - for i := range actions { - actions[i].LoadActUser() - - items = append(items, &feeds.Item{ - Title: string(actions[i].GetOpType()), - Link: &feeds.Link{Href: actions[i].GetCommentLink(), Rel: actions[i].ActUser.AvatarLink()}, - Description: "A discussion on controlled parallelism in golang", - Author: &feeds.Author{Name: actions[i].ActUser.FullName, Email: actions[i].ActUser.GetEmail()}, - Created: actions[i].CreatedUnix.AsTime(), - }) - } - return -} - // Action response for follow/unfollow user request func Action(ctx *context.Context) { u := GetUserByParams(ctx) From 83eda4da7030cc85b356d8a328abfa62cbe8d54a Mon Sep 17 00:00:00 2001 From: 6543 <6543@obermui.de> Date: Thu, 17 Jun 2021 02:41:41 +0200 Subject: [PATCH 05/18] just reserve for now --- models/repo.go | 2 +- models/user.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/models/repo.go b/models/repo.go index 6abeb9be9e4a4..c0d766deb751f 100644 --- a/models/repo.go +++ b/models/repo.go @@ -1036,7 +1036,7 @@ func GetRepoInitFile(tp, name string) ([]byte, error) { var ( reservedRepoNames = []string{".", ".."} - reservedRepoPatterns = []string{"*.git", "*.wiki", "*.rss"} + reservedRepoPatterns = []string{"*.git", "*.wiki", "*.rss", "*.atom"} ) // IsUsableRepoName returns true when repository is usable diff --git a/models/user.go b/models/user.go index 3d45bbc06b309..a240b46677003 100644 --- a/models/user.go +++ b/models/user.go @@ -811,7 +811,7 @@ var ( "user", } - reservedUserPatterns = []string{"*.keys", "*.gpg", "*.rss"} + reservedUserPatterns = []string{"*.keys", "*.gpg", "*.rss", "*.atom"} ) // isUsableName checks if name is reserved or pattern of name is not allowed From ed1522a6a197d4f5d14a1de7912080b33d288f36 Mon Sep 17 00:00:00 2001 From: 6543 <6543@obermui.de> Date: Sun, 5 Sep 2021 18:59:27 +0200 Subject: [PATCH 06/18] adopt --- routers/web/user/profile.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/routers/web/user/profile.go b/routers/web/user/profile.go index ae413140187bd..355f175e8ddca 100644 --- a/routers/web/user/profile.go +++ b/routers/web/user/profile.go @@ -337,7 +337,7 @@ func ShowRSS(ctx *context.Context, ctxUser *models.User) { IncludePrivate: false, OnlyPerformedBy: true, IncludeDeleted: false, - Date: ctx.Query("date"), + Date: ctx.FormString("date"), }) if ctx.Written() { return From 4bb0a4db3e64279a69e4d8e92b78f2d9259b0897 Mon Sep 17 00:00:00 2001 From: 6543 <6543@obermui.de> Date: Sun, 5 Sep 2021 19:11:14 +0200 Subject: [PATCH 07/18] use writer interface --- modules/context/context.go | 2 +- routers/web/user/profile.go | 29 +++++++++++++++++++---------- 2 files changed, 20 insertions(+), 11 deletions(-) diff --git a/modules/context/context.go b/modules/context/context.go index 3dbc2bb9dcd0f..47014e89d3b2b 100644 --- a/modules/context/context.go +++ b/modules/context/context.go @@ -319,7 +319,7 @@ func (ctx *Context) PlainText(status int, bs []byte) { ctx.Resp.WriteHeader(status) ctx.Resp.Header().Set("Content-Type", "text/plain;charset=utf-8") if _, err := ctx.Resp.Write(bs); err != nil { - ctx.ServerError("Render JSON failed", err) + ctx.ServerError("Write bytes failed", err) } } diff --git a/routers/web/user/profile.go b/routers/web/user/profile.go index 355f175e8ddca..08bfa145e3f86 100644 --- a/routers/web/user/profile.go +++ b/routers/web/user/profile.go @@ -74,11 +74,15 @@ func Profile(ctx *context.Context) { uname = strings.TrimSuffix(uname, ".gpg") } - isShowRSS := false + isShowRSS := "" if strings.HasSuffix(uname, ".rss") { - isShowRSS = true + isShowRSS = "rss" uname = strings.TrimSuffix(uname, ".rss") } + if strings.HasSuffix(uname, ".atom") { + isShowRSS = "atom" + uname = strings.TrimSuffix(uname, ".atom") + } ctxUser := GetUserByName(ctx, uname) if ctx.Written() { @@ -109,8 +113,8 @@ func Profile(ctx *context.Context) { } // Show User RSS feed - if isShowRSS { - ShowRSS(ctx, ctxUser) + if isShowRSS != "" { + ShowRSS(ctx, ctxUser, isShowRSS) return } @@ -331,7 +335,7 @@ func Profile(ctx *context.Context) { } // ShowRSS show user activity as RSS feed -func ShowRSS(ctx *context.Context, ctxUser *models.User) { +func ShowRSS(ctx *context.Context, ctxUser *models.User, formatType string) { actions := retrieveFeeds(ctx, models.GetFeedsOptions{RequestedUser: ctxUser, Actor: ctx.User, IncludePrivate: false, @@ -353,13 +357,18 @@ func ShowRSS(ctx *context.Context, ctxUser *models.User) { feed.Items = rss.FeedActionsToFeedItems(ctx, actions) - //atom, err := feed.ToAtom() - if rss, err := feed.ToRss(); err != nil { - ctx.ServerError("ToRss", err) + ctx.Resp.WriteHeader(http.StatusOK) + ctx.Resp.Header().Set("Content-Type", "text/plain;charset=utf-8") + + if formatType == "atom" { + if err := feed.WriteAtom(ctx.Resp); err != nil { + ctx.ServerError("Render Atom failed", err) + } } else { - ctx.PlainText(http.StatusOK, []byte(rss)) + if err := feed.WriteRss(ctx.Resp); err != nil { + ctx.ServerError("Render RSS failed", err) + } } - } // Action response for follow/unfollow user request From 8568cb0aae2cfb9651c5256f9dd35cde0301f0df Mon Sep 17 00:00:00 2001 From: 6543 <6543@obermui.de> Date: Sun, 5 Sep 2021 19:23:50 +0200 Subject: [PATCH 08/18] set correct Content-Type --- routers/web/user/profile.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/routers/web/user/profile.go b/routers/web/user/profile.go index 08bfa145e3f86..1e03635c5290f 100644 --- a/routers/web/user/profile.go +++ b/routers/web/user/profile.go @@ -358,13 +358,13 @@ func ShowRSS(ctx *context.Context, ctxUser *models.User, formatType string) { feed.Items = rss.FeedActionsToFeedItems(ctx, actions) ctx.Resp.WriteHeader(http.StatusOK) - ctx.Resp.Header().Set("Content-Type", "text/plain;charset=utf-8") - if formatType == "atom" { + ctx.Resp.Header().Set("Content-Type", "application/atom+xml;charset=utf-8") if err := feed.WriteAtom(ctx.Resp); err != nil { ctx.ServerError("Render Atom failed", err) } } else { + ctx.Resp.Header().Set("Content-Type", "application/rss+xml;charset=utf-8") if err := feed.WriteRss(ctx.Resp); err != nil { ctx.ServerError("Render RSS failed", err) } From 902be0109c227336517629da982f597feb3781b8 Mon Sep 17 00:00:00 2001 From: 6543 <6543@obermui.de> Date: Sun, 5 Sep 2021 20:12:06 +0200 Subject: [PATCH 09/18] wip next --- options/locale/locale_en-US.ini | 2 +- routers/web/rss/convert.go | 71 +++++++++++++-------------------- routers/web/user/profile.go | 6 ++- 3 files changed, 32 insertions(+), 47 deletions(-) diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index 68b7411319f99..15045f6d1c766 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -227,7 +227,7 @@ view_home = View %s search_repos = Find a repository… filter = Other Filters filter_by_team_repositories = Filter by team repositories -profile_of = Profile of %s +feed_of = Feed of "%s" show_archived = Archived show_both_archived_unarchived = Showing both archived and unarchived diff --git a/routers/web/rss/convert.go b/routers/web/rss/convert.go index a9a88ada787dc..30782d34a719d 100644 --- a/routers/web/rss/convert.go +++ b/routers/web/rss/convert.go @@ -2,6 +2,8 @@ package rss import ( "fmt" + "html" + "net/url" "strings" "time" @@ -22,79 +24,60 @@ func FeedActionsToFeedItems(ctx *context.Context, actions []*models.Action) (ite title = tmpTypeName(act.OpType) // title + title = act.ActUser.DisplayName() + " " { switch act.OpType { - case models.ActionCreateRepo: - title = ctx.Tr("action.create_repo", act.GetRepoLink(), act.ShortRepoPath()) + title += ctx.Tr("action.create_repo", act.GetRepoLink(), act.ShortRepoPath()) case models.ActionRenameRepo: - title = ctx.Tr("action.rename_repo", act.GetContent(), act.GetRepoLink(), act.ShortRepoPath()) + title += ctx.Tr("action.rename_repo", act.GetContent(), act.GetRepoLink(), act.ShortRepoPath()) case models.ActionCommitRepo: branchLink := act.GetBranch() if len(act.Content) != 0 { - title = ctx.Tr("action.commit_repo", act.GetRepoLink(), branchLink, act.GetBranch(), act.ShortRepoPath()) + title += ctx.Tr("action.commit_repo", act.GetRepoLink(), branchLink, act.GetBranch(), act.ShortRepoPath()) } else { - title = ctx.Tr("action.create_branch", act.GetRepoLink(), branchLink, act.GetBranch(), act.ShortRepoPath()) + title += ctx.Tr("action.create_branch", act.GetRepoLink(), branchLink, act.GetBranch(), act.ShortRepoPath()) } case models.ActionCreateIssue: - title = ` {{ $index := index .GetIssueInfos 0}} - {{$.i18n.Tr "action.create_issue" .GetRepoLink $index .ShortRepoPath | Str2html}}` + title += ctx.Tr("action.create_issue", act.GetRepoLink(), act.GetIssueInfos()[0], act.ShortRepoPath()) case models.ActionCreatePullRequest: - title = `{{ $index := index .GetIssueInfos 0}} - {{$.i18n.Tr "action.create_pull_request" .GetRepoLink $index .ShortRepoPath | Str2html}}` + title += ctx.Tr("action.create_pull_request", act.GetRepoLink(), act.GetIssueInfos()[0], act.ShortRepoPath()) case models.ActionTransferRepo: - title = `{{$.i18n.Tr "action.transfer_repo" .GetContent .GetRepoLink .ShortRepoPath | Str2html}}` + title += ctx.Tr("action.transfer_repo", act.GetContent(), act.GetRepoLink(), act.ShortRepoPath()) case models.ActionPushTag: - title = `{{ $tagLink := .GetTag | EscapePound | Escape}} - {{$.i18n.Tr "action.push_tag" .GetRepoLink $tagLink .ShortRepoPath | Str2html}}` + title += ctx.Tr("action.push_tag", act.GetRepoLink(), url.QueryEscape(act.GetTag()), act.ShortRepoPath()) case models.ActionCommentIssue: - title = `{{ $index := index .GetIssueInfos 0}} - {{$.i18n.Tr "action.comment_issue" .GetRepoLink $index .ShortRepoPath | Str2html}}` + title += ctx.Tr("action.comment_issue", act.GetRepoLink(), act.GetIssueInfos()[0], act.ShortRepoPath()) case models.ActionMergePullRequest: - title = `{{ $index := index .GetIssueInfos 0}} - {{$.i18n.Tr "action.merge_pull_request" .GetRepoLink $index .ShortRepoPath | Str2html}}` + title += ctx.Tr("action.merge_pull_request", act.GetRepoLink(), act.GetIssueInfos()[0], act.ShortRepoPath()) case models.ActionCloseIssue: - title = `{{ $index := index .GetIssueInfos 0}} - {{$.i18n.Tr "action.close_issue" .GetRepoLink $index .ShortRepoPath | Str2html}}` + title += ctx.Tr("action.close_issue", act.GetRepoLink(), act.GetIssueInfos()[0], act.ShortRepoPath()) case models.ActionReopenIssue: - title = `{{ $index := index .GetIssueInfos 0}} - {{$.i18n.Tr "action.reopen_issue" .GetRepoLink $index .ShortRepoPath | Str2html}}` + title += ctx.Tr("action.reopen_issue", act.GetRepoLink(), act.GetIssueInfos()[0], act.ShortRepoPath()) case models.ActionClosePullRequest: - title = `{{ $index := index .GetIssueInfos 0}} - {{$.i18n.Tr "action.close_pull_request" .GetRepoLink $index .ShortRepoPath | Str2html}}` + title += ctx.Tr("action.close_pull_request", act.GetRepoLink(), act.GetIssueInfos()[0], act.ShortRepoPath()) case models.ActionReopenPullRequest: - title = `{{ $index := index .GetIssueInfos 0}} - {{$.i18n.Tr "action.reopen_pull_request" .GetRepoLink $index .ShortRepoPath | Str2html}}` + title += ctx.Tr("action.reopen_pull_request", act.GetRepoLink(), act.GetIssueInfos()[0], act.ShortRepoPath) case models.ActionDeleteTag: - title = `{{ $index := index .GetIssueInfos 0}} - {{$.i18n.Tr "action.delete_tag" .GetRepoLink (.GetTag|Escape) .ShortRepoPath | Str2html}}` + title += ctx.Tr("action.delete_tag", act.GetRepoLink(), html.EscapeString(act.GetTag()), act.ShortRepoPath()) case models.ActionDeleteBranch: - title = `{{ $index := index .GetIssueInfos 0}} - {{$.i18n.Tr "action.delete_branch" .GetRepoLink (.GetBranch|Escape) .ShortRepoPath | Str2html}}` + title += ctx.Tr("action.delete_branch", act.GetRepoLink(), html.EscapeString(act.GetBranch()), act.ShortRepoPath()) case models.ActionMirrorSyncPush: - title = `{{ $branchLink := .GetBranch | EscapePound}} - {{$.i18n.Tr "action.mirror_sync_push" .GetRepoLink $branchLink (.GetBranch|Escape) .ShortRepoPath | Str2html}}` + title += ctx.Tr("action.mirror_sync_push", act.GetRepoLink(), url.QueryEscape(act.GetBranch()), html.EscapeString(act.GetBranch()), act.ShortRepoPath()) case models.ActionMirrorSyncCreate: - title = `{{$.i18n.Tr "action.mirror_sync_create" .GetRepoLink (.GetBranch|Escape) .ShortRepoPath | Str2html}}` + title += ctx.Tr("action.mirror_sync_create", act.GetRepoLink(), html.EscapeString(act.GetBranch()), act.ShortRepoPath()) case models.ActionMirrorSyncDelete: - title = `{{$.i18n.Tr "action.mirror_sync_delete" .GetRepoLink (.GetBranch|Escape) .ShortRepoPath | Str2html}}` + title += ctx.Tr("action.mirror_sync_delete", act.GetRepoLink(), html.EscapeString(act.GetBranch()), act.ShortRepoPath()) case models.ActionApprovePullRequest: - title = `{{ $index := index .GetIssueInfos 0}} - {{$.i18n.Tr "action.approve_pull_request" .GetRepoLink $index .ShortRepoPath | Str2html}}` + title += ctx.Tr("action.approve_pull_request", act.GetRepoLink(), act.GetIssueInfos()[0], act.ShortRepoPath()) case models.ActionRejectPullRequest: - title = `{{ $index := index .GetIssueInfos 0}} - {{$.i18n.Tr "action.reject_pull_request" .GetRepoLink $index .ShortRepoPath | Str2html}}` + title += ctx.Tr("action.reject_pull_request", act.GetRepoLink(), act.GetIssueInfos()[0], act.ShortRepoPath()) case models.ActionCommentPull: - title = `{{ $index := index .GetIssueInfos 0}} - {{$.i18n.Tr "action.comment_pull" .GetRepoLink $index .ShortRepoPath | Str2html}}` + title += ctx.Tr("action.comment_pull", act.GetRepoLink(), act.GetIssueInfos()[0], act.ShortRepoPath()) case models.ActionPublishRelease: - title = `{{ $branchLink := .GetBranch | EscapePound | Escape}} - {{ $linkText := .Content | RenderEmoji }} - {{$.i18n.Tr "action.publish_release" .GetRepoLink $branchLink .ShortRepoPath $linkText | Str2html}}` + title += ctx.Tr("action.publish_release", act.GetRepoLink(), html.EscapeString(act.GetBranch()), act.ShortRepoPath(), act.Content) case models.ActionPullReviewDismissed: - title = `{{ $index := index .GetIssueInfos 0}} - {{ $reviewer := index .GetIssueInfos 1}} - {{$.i18n.Tr "action.review_dismissed" .GetRepoLink $index .ShortRepoPath $reviewer | Str2html}}` + title += ctx.Tr("action.review_dismissed", act.GetRepoLink(), act.GetIssueInfos()[0], act.ShortRepoPath(), act.GetIssueInfos()[1]) } } diff --git a/routers/web/user/profile.go b/routers/web/user/profile.go index 1e03635c5290f..537a09c499405 100644 --- a/routers/web/user/profile.go +++ b/routers/web/user/profile.go @@ -336,7 +336,8 @@ func Profile(ctx *context.Context) { // ShowRSS show user activity as RSS feed func ShowRSS(ctx *context.Context, ctxUser *models.User, formatType string) { - actions := retrieveFeeds(ctx, models.GetFeedsOptions{RequestedUser: ctxUser, + actions := retrieveFeeds(ctx, models.GetFeedsOptions{ + RequestedUser: ctxUser, Actor: ctx.User, IncludePrivate: false, OnlyPerformedBy: true, @@ -347,9 +348,10 @@ func ShowRSS(ctx *context.Context, ctxUser *models.User, formatType string) { return } + // TODO: use time of newest action now := time.Now() feed := &feeds.Feed{ - Title: ctx.Tr("home.profile_of", ctxUser.DisplayName()), + Title: ctx.Tr("home.feed_of", ctxUser.DisplayName()), Link: &feeds.Link{Href: ctxUser.HTMLURL()}, Description: ctxUser.Description, Created: now, From be3d0a8e5660fbf70de228c0bbc9ae8f1add2028 Mon Sep 17 00:00:00 2001 From: 6543 <6543@obermui.de> Date: Thu, 9 Sep 2021 16:00:46 +0200 Subject: [PATCH 10/18] minify-diff --- routers/web/user/home.go | 4 ++-- routers/web/user/profile.go | 3 +-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/routers/web/user/home.go b/routers/web/user/home.go index e31ae9d725d88..3853db08dfeaa 100644 --- a/routers/web/user/home.go +++ b/routers/web/user/home.go @@ -150,7 +150,7 @@ func Dashboard(ctx *context.Context) { ctx.Data["MirrorCount"] = len(mirrors) ctx.Data["Mirrors"] = mirrors - actions := retrieveFeeds(ctx, models.GetFeedsOptions{ + ctx.Data["Feeds"] = retrieveFeeds(ctx, models.GetFeedsOptions{ RequestedUser: ctxUser, RequestedTeam: ctx.Org.Team, Actor: ctx.User, @@ -159,10 +159,10 @@ func Dashboard(ctx *context.Context) { IncludeDeleted: false, Date: ctx.FormString("date"), }) + if ctx.Written() { return } - ctx.Data["Feeds"] = actions ctx.HTML(http.StatusOK, tplDashboard) } diff --git a/routers/web/user/profile.go b/routers/web/user/profile.go index 537a09c499405..30aa4b98a95f2 100644 --- a/routers/web/user/profile.go +++ b/routers/web/user/profile.go @@ -236,7 +236,7 @@ func Profile(ctx *context.Context) { total = ctxUser.NumFollowing case "activity": - actions := retrieveFeeds(ctx, models.GetFeedsOptions{RequestedUser: ctxUser, + ctx.Data["Feeds"] = retrieveFeeds(ctx, models.GetFeedsOptions{RequestedUser: ctxUser, Actor: ctx.User, IncludePrivate: showPrivate, OnlyPerformedBy: true, @@ -246,7 +246,6 @@ func Profile(ctx *context.Context) { if ctx.Written() { return } - ctx.Data["Feeds"] = actions case "stars": ctx.Data["PageIsProfileStarList"] = true repos, count, err = models.SearchRepository(&models.SearchRepoOptions{ From f4aad737786ae239886d55ba9ef87d97a6b751e1 Mon Sep 17 00:00:00 2001 From: 6543 <6543@obermui.de> Date: Thu, 9 Sep 2021 16:11:49 +0200 Subject: [PATCH 11/18] ADD org rss as todo --- routers/web/user/profile.go | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/routers/web/user/profile.go b/routers/web/user/profile.go index 30aa4b98a95f2..4587975f49f63 100644 --- a/routers/web/user/profile.go +++ b/routers/web/user/profile.go @@ -90,6 +90,15 @@ func Profile(ctx *context.Context) { } if ctxUser.IsOrganization() { + /* + // TODO: enable after retrieveFeeds() do handle org correctly + // Show Org RSS feed + if isShowRSS != "" { + ShowRSS(ctx, ctxUser, isShowRSS) + return + } + */ + org.Home(ctx) return } From 9ddd72a6ef8bdf3ec101907021acd20ca10edf54 Mon Sep 17 00:00:00 2001 From: 6543 <6543@obermui.de> Date: Thu, 9 Sep 2021 16:17:40 +0200 Subject: [PATCH 12/18] refactor --- routers/web/rss/convert.go | 4 ++ routers/web/rss/profile.go | 88 +++++++++++++++++++++++++++++++++++++ routers/web/user/home.go | 39 +--------------- routers/web/user/profile.go | 54 +++-------------------- 4 files changed, 100 insertions(+), 85 deletions(-) create mode 100644 routers/web/rss/profile.go diff --git a/routers/web/rss/convert.go b/routers/web/rss/convert.go index 30782d34a719d..c02b55cac1966 100644 --- a/routers/web/rss/convert.go +++ b/routers/web/rss/convert.go @@ -1,3 +1,7 @@ +// Copyright 2021 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 rss import ( diff --git a/routers/web/rss/profile.go b/routers/web/rss/profile.go new file mode 100644 index 0000000000000..a6bffbecd841c --- /dev/null +++ b/routers/web/rss/profile.go @@ -0,0 +1,88 @@ +// Copyright 2021 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 rss + +import ( + "code.gitea.io/gitea/models" + "code.gitea.io/gitea/modules/context" + "github.com/gorilla/feeds" + "net/http" + "time" +) + +// 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 + } + + userCache := map[int64]*models.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 = models.GetUserByID(act.Repo.OwnerID) + if err != nil { + if models.IsErrUserNotExist(err) { + continue + } + ctx.ServerError("GetUserByID", err) + return nil + } + userCache[repoOwner.ID] = repoOwner + } + act.Repo.Owner = repoOwner + } + return actions +} + +// ShowRSS show user activity as RSS feed +func ShowRSS(ctx *context.Context, ctxUser *models.User, formatType string) { + actions := RetrieveFeeds(ctx, models.GetFeedsOptions{ + RequestedUser: ctxUser, + Actor: ctx.User, + IncludePrivate: false, + OnlyPerformedBy: true, + IncludeDeleted: false, + Date: ctx.FormString("date"), + }) + if ctx.Written() { + return + } + + // TODO: use time of newest action + now := time.Now() + feed := &feeds.Feed{ + Title: ctx.Tr("home.feed_of", ctxUser.DisplayName()), + Link: &feeds.Link{Href: ctxUser.HTMLURL()}, + Description: ctxUser.Description, + Created: now, + } + + feed.Items = FeedActionsToFeedItems(ctx, actions) + + ctx.Resp.WriteHeader(http.StatusOK) + if formatType == "atom" { + ctx.Resp.Header().Set("Content-Type", "application/atom+xml;charset=utf-8") + if err := feed.WriteAtom(ctx.Resp); err != nil { + ctx.ServerError("Render Atom failed", err) + } + } else { + ctx.Resp.Header().Set("Content-Type", "application/rss+xml;charset=utf-8") + if err := feed.WriteRss(ctx.Resp); err != nil { + ctx.ServerError("Render RSS failed", err) + } + } +} diff --git a/routers/web/user/home.go b/routers/web/user/home.go index 3853db08dfeaa..fdd906f965f3f 100644 --- a/routers/web/user/home.go +++ b/routers/web/user/home.go @@ -24,6 +24,7 @@ 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/rss" issue_service "code.gitea.io/gitea/services/issue" pull_service "code.gitea.io/gitea/services/pull" @@ -59,42 +60,6 @@ func getDashboardContextUser(ctx *context.Context) *models.User { return ctxUser } -// 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 - } - - userCache := map[int64]*models.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 = models.GetUserByID(act.Repo.OwnerID) - if err != nil { - if models.IsErrUserNotExist(err) { - continue - } - ctx.ServerError("GetUserByID", err) - return nil - } - userCache[repoOwner.ID] = repoOwner - } - act.Repo.Owner = repoOwner - } - return actions -} - // Dashboard render the dashboard page func Dashboard(ctx *context.Context) { ctxUser := getDashboardContextUser(ctx) @@ -150,7 +115,7 @@ func Dashboard(ctx *context.Context) { ctx.Data["MirrorCount"] = len(mirrors) ctx.Data["Mirrors"] = mirrors - ctx.Data["Feeds"] = retrieveFeeds(ctx, models.GetFeedsOptions{ + ctx.Data["Feeds"] = rss.RetrieveFeeds(ctx, models.GetFeedsOptions{ RequestedUser: ctxUser, RequestedTeam: ctx.Org.Team, Actor: ctx.User, diff --git a/routers/web/user/profile.go b/routers/web/user/profile.go index 4587975f49f63..55724e3153533 100644 --- a/routers/web/user/profile.go +++ b/routers/web/user/profile.go @@ -10,7 +10,6 @@ import ( "net/http" "path" "strings" - "time" "code.gitea.io/gitea/models" "code.gitea.io/gitea/modules/context" @@ -20,8 +19,6 @@ import ( "code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/routers/web/org" "code.gitea.io/gitea/routers/web/rss" - - "github.com/gorilla/feeds" ) // GetUserByName get user by name @@ -91,10 +88,10 @@ func Profile(ctx *context.Context) { if ctxUser.IsOrganization() { /* - // TODO: enable after retrieveFeeds() do handle org correctly + // TODO: enable after rss.RetrieveFeeds() do handle org correctly // Show Org RSS feed - if isShowRSS != "" { - ShowRSS(ctx, ctxUser, isShowRSS) + if len(isShowRSS) != 0 { + rss.ShowRSS(ctx, ctxUser, isShowRSS) return } */ @@ -122,8 +119,8 @@ func Profile(ctx *context.Context) { } // Show User RSS feed - if isShowRSS != "" { - ShowRSS(ctx, ctxUser, isShowRSS) + if len(isShowRSS) != 0 { + rss.ShowRSS(ctx, ctxUser, isShowRSS) return } @@ -245,7 +242,7 @@ func Profile(ctx *context.Context) { total = ctxUser.NumFollowing case "activity": - ctx.Data["Feeds"] = retrieveFeeds(ctx, models.GetFeedsOptions{RequestedUser: ctxUser, + ctx.Data["Feeds"] = rss.RetrieveFeeds(ctx, models.GetFeedsOptions{RequestedUser: ctxUser, Actor: ctx.User, IncludePrivate: showPrivate, OnlyPerformedBy: true, @@ -342,45 +339,6 @@ func Profile(ctx *context.Context) { ctx.HTML(http.StatusOK, tplProfile) } -// ShowRSS show user activity as RSS feed -func ShowRSS(ctx *context.Context, ctxUser *models.User, formatType string) { - actions := retrieveFeeds(ctx, models.GetFeedsOptions{ - RequestedUser: ctxUser, - Actor: ctx.User, - IncludePrivate: false, - OnlyPerformedBy: true, - IncludeDeleted: false, - Date: ctx.FormString("date"), - }) - if ctx.Written() { - return - } - - // TODO: use time of newest action - now := time.Now() - feed := &feeds.Feed{ - Title: ctx.Tr("home.feed_of", ctxUser.DisplayName()), - Link: &feeds.Link{Href: ctxUser.HTMLURL()}, - Description: ctxUser.Description, - Created: now, - } - - feed.Items = rss.FeedActionsToFeedItems(ctx, actions) - - ctx.Resp.WriteHeader(http.StatusOK) - if formatType == "atom" { - ctx.Resp.Header().Set("Content-Type", "application/atom+xml;charset=utf-8") - if err := feed.WriteAtom(ctx.Resp); err != nil { - ctx.ServerError("Render Atom failed", err) - } - } else { - ctx.Resp.Header().Set("Content-Type", "application/rss+xml;charset=utf-8") - if err := feed.WriteRss(ctx.Resp); err != nil { - ctx.ServerError("Render RSS failed", err) - } - } -} - // Action response for follow/unfollow user request func Action(ctx *context.Context) { u := GetUserByParams(ctx) From 96cdba98766fbd24002ff0034abe53f96efa3747 Mon Sep 17 00:00:00 2001 From: 6543 <6543@obermui.de> Date: Thu, 9 Sep 2021 17:32:52 +0200 Subject: [PATCH 13/18] wip --- routers/web/rss/convert.go | 249 ++++++++++++++----------------------- routers/web/rss/profile.go | 11 +- 2 files changed, 101 insertions(+), 159 deletions(-) diff --git a/routers/web/rss/convert.go b/routers/web/rss/convert.go index c02b55cac1966..cdf5cf8bc97fc 100644 --- a/routers/web/rss/convert.go +++ b/routers/web/rss/convert.go @@ -13,11 +13,15 @@ import ( "code.gitea.io/gitea/models" "code.gitea.io/gitea/modules/context" + "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/modules/templates" "github.com/gorilla/feeds" ) -func FeedActionsToFeedItems(ctx *context.Context, actions []*models.Action) (items []*feeds.Item) { +// feedActionsToFeedItems convert gitea's Action feed to feeds Item +func feedActionsToFeedItems(ctx *context.Context, actions []*models.Action) (items []*feeds.Item) { for _, act := range actions { act.LoadActUser() @@ -25,190 +29,125 @@ func FeedActionsToFeedItems(ctx *context.Context, actions []*models.Action) (ite link := &feeds.Link{Href: act.GetCommentLink()} - title = tmpTypeName(act.OpType) - // title title = act.ActUser.DisplayName() + " " - { - switch act.OpType { - case models.ActionCreateRepo: - title += ctx.Tr("action.create_repo", act.GetRepoLink(), act.ShortRepoPath()) - case models.ActionRenameRepo: - title += ctx.Tr("action.rename_repo", act.GetContent(), act.GetRepoLink(), act.ShortRepoPath()) - case models.ActionCommitRepo: - branchLink := act.GetBranch() - if len(act.Content) != 0 { - title += ctx.Tr("action.commit_repo", act.GetRepoLink(), branchLink, act.GetBranch(), act.ShortRepoPath()) - } else { - title += ctx.Tr("action.create_branch", act.GetRepoLink(), branchLink, act.GetBranch(), act.ShortRepoPath()) - } - case models.ActionCreateIssue: - title += ctx.Tr("action.create_issue", act.GetRepoLink(), act.GetIssueInfos()[0], act.ShortRepoPath()) - case models.ActionCreatePullRequest: - title += ctx.Tr("action.create_pull_request", act.GetRepoLink(), act.GetIssueInfos()[0], act.ShortRepoPath()) - case models.ActionTransferRepo: - title += ctx.Tr("action.transfer_repo", act.GetContent(), act.GetRepoLink(), act.ShortRepoPath()) - case models.ActionPushTag: - title += ctx.Tr("action.push_tag", act.GetRepoLink(), url.QueryEscape(act.GetTag()), act.ShortRepoPath()) - case models.ActionCommentIssue: - title += ctx.Tr("action.comment_issue", act.GetRepoLink(), act.GetIssueInfos()[0], act.ShortRepoPath()) - case models.ActionMergePullRequest: - title += ctx.Tr("action.merge_pull_request", act.GetRepoLink(), act.GetIssueInfos()[0], act.ShortRepoPath()) - case models.ActionCloseIssue: - title += ctx.Tr("action.close_issue", act.GetRepoLink(), act.GetIssueInfos()[0], act.ShortRepoPath()) - case models.ActionReopenIssue: - title += ctx.Tr("action.reopen_issue", act.GetRepoLink(), act.GetIssueInfos()[0], act.ShortRepoPath()) - case models.ActionClosePullRequest: - title += ctx.Tr("action.close_pull_request", act.GetRepoLink(), act.GetIssueInfos()[0], act.ShortRepoPath()) - case models.ActionReopenPullRequest: - title += ctx.Tr("action.reopen_pull_request", act.GetRepoLink(), act.GetIssueInfos()[0], act.ShortRepoPath) - case models.ActionDeleteTag: - title += ctx.Tr("action.delete_tag", act.GetRepoLink(), html.EscapeString(act.GetTag()), act.ShortRepoPath()) - case models.ActionDeleteBranch: - title += ctx.Tr("action.delete_branch", act.GetRepoLink(), html.EscapeString(act.GetBranch()), act.ShortRepoPath()) - case models.ActionMirrorSyncPush: - title += ctx.Tr("action.mirror_sync_push", act.GetRepoLink(), url.QueryEscape(act.GetBranch()), html.EscapeString(act.GetBranch()), act.ShortRepoPath()) - case models.ActionMirrorSyncCreate: - title += ctx.Tr("action.mirror_sync_create", act.GetRepoLink(), html.EscapeString(act.GetBranch()), act.ShortRepoPath()) - case models.ActionMirrorSyncDelete: - title += ctx.Tr("action.mirror_sync_delete", act.GetRepoLink(), html.EscapeString(act.GetBranch()), act.ShortRepoPath()) - case models.ActionApprovePullRequest: - title += ctx.Tr("action.approve_pull_request", act.GetRepoLink(), act.GetIssueInfos()[0], act.ShortRepoPath()) - case models.ActionRejectPullRequest: - title += ctx.Tr("action.reject_pull_request", act.GetRepoLink(), act.GetIssueInfos()[0], act.ShortRepoPath()) - case models.ActionCommentPull: - title += ctx.Tr("action.comment_pull", act.GetRepoLink(), act.GetIssueInfos()[0], act.ShortRepoPath()) - case models.ActionPublishRelease: - title += ctx.Tr("action.publish_release", act.GetRepoLink(), html.EscapeString(act.GetBranch()), act.ShortRepoPath(), act.Content) - case models.ActionPullReviewDismissed: - title += ctx.Tr("action.review_dismissed", act.GetRepoLink(), act.GetIssueInfos()[0], act.ShortRepoPath(), act.GetIssueInfos()[1]) + switch act.OpType { + case models.ActionCreateRepo: + title += ctx.Tr("action.create_repo", act.GetRepoLink(), act.ShortRepoPath()) + case models.ActionRenameRepo: + title += ctx.Tr("action.rename_repo", act.GetContent(), act.GetRepoLink(), act.ShortRepoPath()) + case models.ActionCommitRepo: + branchLink := act.GetBranch() + if len(act.Content) != 0 { + title += ctx.Tr("action.commit_repo", act.GetRepoLink(), branchLink, act.GetBranch(), act.ShortRepoPath()) + } else { + title += ctx.Tr("action.create_branch", act.GetRepoLink(), branchLink, act.GetBranch(), act.ShortRepoPath()) } + case models.ActionCreateIssue: + title += ctx.Tr("action.create_issue", act.GetRepoLink(), act.GetIssueInfos()[0], act.ShortRepoPath()) + case models.ActionCreatePullRequest: + title += ctx.Tr("action.create_pull_request", act.GetRepoLink(), act.GetIssueInfos()[0], act.ShortRepoPath()) + case models.ActionTransferRepo: + title += ctx.Tr("action.transfer_repo", act.GetContent(), act.GetRepoLink(), act.ShortRepoPath()) + case models.ActionPushTag: + title += ctx.Tr("action.push_tag", act.GetRepoLink(), url.QueryEscape(act.GetTag()), act.ShortRepoPath()) + case models.ActionCommentIssue: + title += ctx.Tr("action.comment_issue", act.GetRepoLink(), act.GetIssueInfos()[0], act.ShortRepoPath()) + case models.ActionMergePullRequest: + title += ctx.Tr("action.merge_pull_request", act.GetRepoLink(), act.GetIssueInfos()[0], act.ShortRepoPath()) + case models.ActionCloseIssue: + title += ctx.Tr("action.close_issue", act.GetRepoLink(), act.GetIssueInfos()[0], act.ShortRepoPath()) + case models.ActionReopenIssue: + title += ctx.Tr("action.reopen_issue", act.GetRepoLink(), act.GetIssueInfos()[0], act.ShortRepoPath()) + case models.ActionClosePullRequest: + title += ctx.Tr("action.close_pull_request", act.GetRepoLink(), act.GetIssueInfos()[0], act.ShortRepoPath()) + case models.ActionReopenPullRequest: + title += ctx.Tr("action.reopen_pull_request", act.GetRepoLink(), act.GetIssueInfos()[0], act.ShortRepoPath) + case models.ActionDeleteTag: + title += ctx.Tr("action.delete_tag", act.GetRepoLink(), html.EscapeString(act.GetTag()), act.ShortRepoPath()) + case models.ActionDeleteBranch: + title += ctx.Tr("action.delete_branch", act.GetRepoLink(), html.EscapeString(act.GetBranch()), act.ShortRepoPath()) + case models.ActionMirrorSyncPush: + title += ctx.Tr("action.mirror_sync_push", act.GetRepoLink(), url.QueryEscape(act.GetBranch()), html.EscapeString(act.GetBranch()), act.ShortRepoPath()) + case models.ActionMirrorSyncCreate: + title += ctx.Tr("action.mirror_sync_create", act.GetRepoLink(), html.EscapeString(act.GetBranch()), act.ShortRepoPath()) + case models.ActionMirrorSyncDelete: + title += ctx.Tr("action.mirror_sync_delete", act.GetRepoLink(), html.EscapeString(act.GetBranch()), act.ShortRepoPath()) + case models.ActionApprovePullRequest: + title += ctx.Tr("action.approve_pull_request", act.GetRepoLink(), act.GetIssueInfos()[0], act.ShortRepoPath()) + case models.ActionRejectPullRequest: + title += ctx.Tr("action.reject_pull_request", act.GetRepoLink(), act.GetIssueInfos()[0], act.ShortRepoPath()) + case models.ActionCommentPull: + title += ctx.Tr("action.comment_pull", act.GetRepoLink(), act.GetIssueInfos()[0], act.ShortRepoPath()) + case models.ActionPublishRelease: + title += ctx.Tr("action.publish_release", act.GetRepoLink(), html.EscapeString(act.GetBranch()), act.ShortRepoPath(), act.Content) + case models.ActionPullReviewDismissed: + title += ctx.Tr("action.review_dismissed", act.GetRepoLink(), act.GetIssueInfos()[0], act.ShortRepoPath(), act.GetIssueInfos()[1]) + case models.ActionStarRepo: + title += "ActionStarRepo" // TODO + case models.ActionWatchRepo: + title += "ActionWatchRepo" // TODO + default: + log.Error("unknown action type: %v", act.OpType) } // description & content { switch act.OpType { case models.ActionCommitRepo, models.ActionMirrorSyncPush: - desc = `
-
    - {{ $push := ActionContent2Commits .}} - {{ $repoLink := .GetRepoLink}} - {{if $push.Commits}} - {{range $push.Commits}} - {{ $commitLink := printf "%s/commit/%s" $repoLink .Sha1}} -
  • - {{avatarHTML ($push.AvatarLink .AuthorEmail) 16 "mr-2" .AuthorName}} - {{ShortSha .Sha1}} - - {{RenderCommitMessage .Message $repoLink $.ComposeMetas}} - -
  • - {{end}} - {{end}} - {{if and (gt $push.Len 1) $push.CompareURL}}
  • {{$.i18n.Tr "action.compare_commits" $push.Len}} »
  • {{end}} -
-
` + push := templates.ActionContent2Commits(act) + repoLink := act.GetRepoLink() + + for _, commit := range push.Commits { + if len(desc) != 0 { + desc += "\n\n" + } + desc += fmt.Sprintf("%s\n%s", + fmt.Sprintf("%s/commit/%s", act.GetRepoLink(), commit.Sha1), + commit.Sha1, + templates.RenderCommitMessage(commit.Message, repoLink, nil), + ) + } + + if push.Len > 1 { + link = &feeds.Link{Href: fmt.Sprintf("%s/%s", setting.AppSubURL, push.CompareURL)} + } else if push.Len == 1 { + link = &feeds.Link{Href: fmt.Sprintf("%s/commit/%s", act.GetRepoLink(), push.Commits[0].Sha1)} + } case models.ActionCreateIssue, models.ActionCreatePullRequest: - desc = strings.Join(act.GetIssueInfos(), "#") // index .GetIssueInfos 1 | RenderEmoji + desc = strings.Join(act.GetIssueInfos(), "#") content = act.GetIssueContent() case models.ActionCommentIssue, models.ActionApprovePullRequest, models.ActionRejectPullRequest, models.ActionCommentPull: - desc = act.GetIssueTitle() // class="text truncate issue title" | RenderEmoji - comment := act.GetIssueInfos()[1] // class="text light grey" | RenderEmoji + desc = act.GetIssueTitle() + comment := act.GetIssueInfos()[1] if len(comment) != 0 { desc += "\n\n" + comment } case models.ActionMergePullRequest: - desc = act.GetIssueInfos()[1] // class="text light grey" + desc = act.GetIssueInfos()[1] case models.ActionCloseIssue, models.ActionReopenIssue, models.ActionClosePullRequest, models.ActionReopenPullRequest: - desc = `{{.GetIssueTitle | RenderEmoji}}` + desc = act.GetIssueTitle() case models.ActionPullReviewDismissed: - desc = `

{{$.i18n.Tr "action.review_dismissed_reason"}}

-

{{index .GetIssueInfos 2 | RenderEmoji}}

` + desc = ctx.Tr("action.review_dismissed_reason") + "\n\n" + act.GetIssueInfos()[2] } } if len(content) == 0 { content = desc } - // img := templates.ActionIcon(act.OpType) - items = append(items, &feeds.Item{ Title: title, Link: link, Description: desc, - Author: feedsAuthor(act.ActUser), - Id: fmt.Sprint(act.ID), - Created: time.Now(), // Created: act.CreatedUnix.AsTime(), - Content: content, + Author: &feeds.Author{ + Name: act.ActUser.DisplayName(), + Email: act.ActUser.GetEmail(), + }, + Id: fmt.Sprint(act.ID), + Created: time.Now(), // TODO: Created: act.CreatedUnix.AsTime(), + Content: content, }) } return } - -func feedsAuthor(user *models.User) *feeds.Author { - return &feeds.Author{ - Name: user.DisplayName(), - Email: user.GetEmail(), - } -} - -func tmpTypeName(t models.ActionType) string { - switch t { - case models.ActionCreateRepo: - return "ActionCreateRepo" - case models.ActionRenameRepo: - return "ActionRenameRepo" - case models.ActionStarRepo: - return "ActionStarRepo" - case models.ActionWatchRepo: - return "ActionWatchRepo" - case models.ActionCommitRepo: - return "ActionCommitRepo" - case models.ActionCreateIssue: - return "ActionCreateIssue" - case models.ActionCreatePullRequest: - return "ActionCreatePullRequest" - case models.ActionTransferRepo: - return "ActionTransferRepo" - case models.ActionPushTag: - return "ActionPushTag" - case models.ActionCommentIssue: - return "ActionCommentIssue" - case models.ActionMergePullRequest: - return "ActionMergePullRequest" - case models.ActionCloseIssue: - return "ActionCloseIssue" - case models.ActionReopenIssue: - return "ActionReopenIssue" - case models.ActionClosePullRequest: - return "ActionClosePullRequest" - case models.ActionReopenPullRequest: - return "ActionReopenPullRequest" - case models.ActionDeleteTag: - return "ActionDeleteTag" - case models.ActionDeleteBranch: - return "ActionDeleteBranch" - case models.ActionMirrorSyncPush: - return "ActionMirrorSyncPush" - case models.ActionMirrorSyncCreate: - return "ActionMirrorSyncCreate" - case models.ActionMirrorSyncDelete: - return "ActionMirrorSyncDelete" - case models.ActionApprovePullRequest: - return "ActionApprovePullRequest" - case models.ActionRejectPullRequest: - return "ActionRejectPullRequest" - case models.ActionCommentPull: - return "ActionCommentPull" - case models.ActionPublishRelease: - return "ActionPublishRelease" - case models.ActionPullReviewDismissed: - return "ActionPullReviewDismissed" - } - return "" -} - -/* - - */ diff --git a/routers/web/rss/profile.go b/routers/web/rss/profile.go index a6bffbecd841c..4a8d4ac372a8a 100644 --- a/routers/web/rss/profile.go +++ b/routers/web/rss/profile.go @@ -62,17 +62,20 @@ func ShowRSS(ctx *context.Context, ctxUser *models.User, formatType string) { return } - // TODO: use time of newest action - now := time.Now() feed := &feeds.Feed{ Title: ctx.Tr("home.feed_of", ctxUser.DisplayName()), Link: &feeds.Link{Href: ctxUser.HTMLURL()}, Description: ctxUser.Description, - Created: now, + Created: time.Now(), } - feed.Items = FeedActionsToFeedItems(ctx, actions) + feed.Items = feedActionsToFeedItems(ctx, actions) + writeFeed(ctx, feed, formatType) +} + +// writeFeed write a feeds.Feed as atom or rss to ctx.Resp +func writeFeed(ctx *context.Context, feed *feeds.Feed, formatType string) { ctx.Resp.WriteHeader(http.StatusOK) if formatType == "atom" { ctx.Resp.Header().Set("Content-Type", "application/atom+xml;charset=utf-8") From 8205dc68b582fd92eaed07b8df40cb1f791ef52b Mon Sep 17 00:00:00 2001 From: 6543 <6543@obermui.de> Date: Thu, 9 Sep 2021 18:26:44 +0200 Subject: [PATCH 14/18] fix lint --- routers/web/rss/profile.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/routers/web/rss/profile.go b/routers/web/rss/profile.go index 4a8d4ac372a8a..f662144799e72 100644 --- a/routers/web/rss/profile.go +++ b/routers/web/rss/profile.go @@ -5,11 +5,13 @@ package rss import ( + "net/http" + "time" + "code.gitea.io/gitea/models" "code.gitea.io/gitea/modules/context" + "github.com/gorilla/feeds" - "net/http" - "time" ) // RetrieveFeeds loads feeds for the specified user From 41c9175ac1d2cf0fd2435a2910f167858bf5ef56 Mon Sep 17 00:00:00 2001 From: 6543 <6543@obermui.de> Date: Fri, 10 Sep 2021 18:35:55 +0200 Subject: [PATCH 15/18] resolve last todos --- options/locale/locale_en-US.ini | 2 ++ routers/web/rss/convert.go | 9 +++++---- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index 08658858e0e88..2fdafe259170f 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -2744,6 +2744,8 @@ publish_release = `released "%[4]s" at %[4]s for %[3]s#%[2]s` review_dismissed_reason = Reason: create_branch = created branch %[3]s in %[4]s +stared_repo = stared %[2]s +watched_repo = started watching %[2]s [tool] ago = %s ago diff --git a/routers/web/rss/convert.go b/routers/web/rss/convert.go index cdf5cf8bc97fc..50912483c7f9e 100644 --- a/routers/web/rss/convert.go +++ b/routers/web/rss/convert.go @@ -9,7 +9,6 @@ import ( "html" "net/url" "strings" - "time" "code.gitea.io/gitea/models" "code.gitea.io/gitea/modules/context" @@ -84,9 +83,11 @@ func feedActionsToFeedItems(ctx *context.Context, actions []*models.Action) (ite case models.ActionPullReviewDismissed: title += ctx.Tr("action.review_dismissed", act.GetRepoLink(), act.GetIssueInfos()[0], act.ShortRepoPath(), act.GetIssueInfos()[1]) case models.ActionStarRepo: - title += "ActionStarRepo" // TODO + title += ctx.Tr("action.stared_repo", act.GetRepoLink(), act.GetRepoPath()) + link = &feeds.Link{Href: act.GetRepoLink()} case models.ActionWatchRepo: - title += "ActionWatchRepo" // TODO + title += ctx.Tr("action.watched_repo", act.GetRepoLink(), act.GetRepoPath()) + link = &feeds.Link{Href: act.GetRepoLink()} default: log.Error("unknown action type: %v", act.OpType) } @@ -145,7 +146,7 @@ func feedActionsToFeedItems(ctx *context.Context, actions []*models.Action) (ite Email: act.ActUser.GetEmail(), }, Id: fmt.Sprint(act.ID), - Created: time.Now(), // TODO: Created: act.CreatedUnix.AsTime(), + Created: act.CreatedUnix.AsTime(), Content: content, }) } From af9d9e2ab63fc70c383d67ed873f6a49f7d0f207 Mon Sep 17 00:00:00 2001 From: 6543 <6543@obermui.de> Date: Mon, 20 Sep 2021 16:17:21 +0200 Subject: [PATCH 16/18] use accept header too --- routers/web/user/profile.go | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/routers/web/user/profile.go b/routers/web/user/profile.go index 55724e3153533..4dbfeebac6c61 100644 --- a/routers/web/user/profile.go +++ b/routers/web/user/profile.go @@ -71,14 +71,18 @@ func Profile(ctx *context.Context) { uname = strings.TrimSuffix(uname, ".gpg") } - isShowRSS := "" + isShowFeed := "" if strings.HasSuffix(uname, ".rss") { - isShowRSS = "rss" + isShowFeed = "rss" uname = strings.TrimSuffix(uname, ".rss") + } else if strings.Contains(ctx.Req.Header.Get("Accept"), "application/rss+xml") { + isShowFeed = "rss" } if strings.HasSuffix(uname, ".atom") { - isShowRSS = "atom" + isShowFeed = "atom" uname = strings.TrimSuffix(uname, ".atom") + } else if strings.Contains(ctx.Req.Header.Get("Accept"), "application/atom+xml") { + isShowFeed = "atom" } ctxUser := GetUserByName(ctx, uname) @@ -90,8 +94,8 @@ func Profile(ctx *context.Context) { /* // TODO: enable after rss.RetrieveFeeds() do handle org correctly // Show Org RSS feed - if len(isShowRSS) != 0 { - rss.ShowRSS(ctx, ctxUser, isShowRSS) + if len(isShowFeed) != 0 { + rss.ShowRSS(ctx, ctxUser, isShowFeed) return } */ @@ -119,8 +123,8 @@ func Profile(ctx *context.Context) { } // Show User RSS feed - if len(isShowRSS) != 0 { - rss.ShowRSS(ctx, ctxUser, isShowRSS) + if len(isShowFeed) != 0 { + rss.ShowRSS(ctx, ctxUser, isShowFeed) return } From 3cc6cbace0d7628fa115ba5f2e9800da7fa79991 Mon Sep 17 00:00:00 2001 From: 6543 <6543@obermui.de> Date: Wed, 22 Sep 2021 01:10:05 +0200 Subject: [PATCH 17/18] call it feed not rss --- routers/web/{rss => feed}/convert.go | 2 +- routers/web/{rss => feed}/profile.go | 6 +++--- routers/web/user/home.go | 4 ++-- routers/web/user/profile.go | 22 +++++++++++----------- 4 files changed, 17 insertions(+), 17 deletions(-) rename routers/web/{rss => feed}/convert.go (99%) rename routers/web/{rss => feed}/profile.go (93%) diff --git a/routers/web/rss/convert.go b/routers/web/feed/convert.go similarity index 99% rename from routers/web/rss/convert.go rename to routers/web/feed/convert.go index 50912483c7f9e..d891184b35ee8 100644 --- a/routers/web/rss/convert.go +++ b/routers/web/feed/convert.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a MIT-style // license that can be found in the LICENSE file. -package rss +package feed import ( "fmt" diff --git a/routers/web/rss/profile.go b/routers/web/feed/profile.go similarity index 93% rename from routers/web/rss/profile.go rename to routers/web/feed/profile.go index f662144799e72..7ec9a92a43987 100644 --- a/routers/web/rss/profile.go +++ b/routers/web/feed/profile.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a MIT-style // license that can be found in the LICENSE file. -package rss +package feed import ( "net/http" @@ -50,8 +50,8 @@ func RetrieveFeeds(ctx *context.Context, options models.GetFeedsOptions) []*mode return actions } -// ShowRSS show user activity as RSS feed -func ShowRSS(ctx *context.Context, ctxUser *models.User, formatType string) { +// ShowUserFeed show user activity as RSS / Atom feed +func ShowUserFeed(ctx *context.Context, ctxUser *models.User, formatType string) { actions := RetrieveFeeds(ctx, models.GetFeedsOptions{ RequestedUser: ctxUser, Actor: ctx.User, diff --git a/routers/web/user/home.go b/routers/web/user/home.go index fdd906f965f3f..3fe8105fb06eb 100644 --- a/routers/web/user/home.go +++ b/routers/web/user/home.go @@ -24,7 +24,7 @@ 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/rss" + "code.gitea.io/gitea/routers/web/feed" issue_service "code.gitea.io/gitea/services/issue" pull_service "code.gitea.io/gitea/services/pull" @@ -115,7 +115,7 @@ func Dashboard(ctx *context.Context) { ctx.Data["MirrorCount"] = len(mirrors) ctx.Data["Mirrors"] = mirrors - ctx.Data["Feeds"] = rss.RetrieveFeeds(ctx, models.GetFeedsOptions{ + ctx.Data["Feeds"] = feed.RetrieveFeeds(ctx, models.GetFeedsOptions{ RequestedUser: ctxUser, RequestedTeam: ctx.Org.Team, Actor: ctx.User, diff --git a/routers/web/user/profile.go b/routers/web/user/profile.go index 4dbfeebac6c61..afc3f92b179e7 100644 --- a/routers/web/user/profile.go +++ b/routers/web/user/profile.go @@ -17,8 +17,8 @@ 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" "code.gitea.io/gitea/routers/web/org" - "code.gitea.io/gitea/routers/web/rss" ) // GetUserByName get user by name @@ -71,18 +71,18 @@ func Profile(ctx *context.Context) { uname = strings.TrimSuffix(uname, ".gpg") } - isShowFeed := "" + showFeedType := "" if strings.HasSuffix(uname, ".rss") { - isShowFeed = "rss" + showFeedType = "rss" uname = strings.TrimSuffix(uname, ".rss") } else if strings.Contains(ctx.Req.Header.Get("Accept"), "application/rss+xml") { - isShowFeed = "rss" + showFeedType = "rss" } if strings.HasSuffix(uname, ".atom") { - isShowFeed = "atom" + showFeedType = "atom" uname = strings.TrimSuffix(uname, ".atom") } else if strings.Contains(ctx.Req.Header.Get("Accept"), "application/atom+xml") { - isShowFeed = "atom" + showFeedType = "atom" } ctxUser := GetUserByName(ctx, uname) @@ -94,8 +94,8 @@ func Profile(ctx *context.Context) { /* // TODO: enable after rss.RetrieveFeeds() do handle org correctly // Show Org RSS feed - if len(isShowFeed) != 0 { - rss.ShowRSS(ctx, ctxUser, isShowFeed) + if len(showFeedType) != 0 { + rss.ShowUserFeed(ctx, ctxUser, showFeedType) return } */ @@ -123,8 +123,8 @@ func Profile(ctx *context.Context) { } // Show User RSS feed - if len(isShowFeed) != 0 { - rss.ShowRSS(ctx, ctxUser, isShowFeed) + if len(showFeedType) != 0 { + feed.ShowUserFeed(ctx, ctxUser, showFeedType) return } @@ -246,7 +246,7 @@ func Profile(ctx *context.Context) { total = ctxUser.NumFollowing case "activity": - ctx.Data["Feeds"] = rss.RetrieveFeeds(ctx, models.GetFeedsOptions{RequestedUser: ctxUser, + ctx.Data["Feeds"] = feed.RetrieveFeeds(ctx, models.GetFeedsOptions{RequestedUser: ctxUser, Actor: ctx.User, IncludePrivate: showPrivate, OnlyPerformedBy: true, From 4e979ccba8a796b86683f0bec09bed1b6f7e2e67 Mon Sep 17 00:00:00 2001 From: 6543 <6543@obermui.de> Date: Fri, 15 Oct 2021 01:16:32 +0200 Subject: [PATCH 18/18] return error & use strconv --- routers/web/feed/convert.go | 8 ++++---- routers/web/feed/profile.go | 7 ++++++- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/routers/web/feed/convert.go b/routers/web/feed/convert.go index d891184b35ee8..8fd8a6c6b787a 100644 --- a/routers/web/feed/convert.go +++ b/routers/web/feed/convert.go @@ -8,11 +8,11 @@ import ( "fmt" "html" "net/url" + "strconv" "strings" "code.gitea.io/gitea/models" "code.gitea.io/gitea/modules/context" - "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/templates" @@ -20,7 +20,7 @@ import ( ) // feedActionsToFeedItems convert gitea's Action feed to feeds Item -func feedActionsToFeedItems(ctx *context.Context, actions []*models.Action) (items []*feeds.Item) { +func feedActionsToFeedItems(ctx *context.Context, actions []*models.Action) (items []*feeds.Item, err error) { for _, act := range actions { act.LoadActUser() @@ -89,7 +89,7 @@ func feedActionsToFeedItems(ctx *context.Context, actions []*models.Action) (ite title += ctx.Tr("action.watched_repo", act.GetRepoLink(), act.GetRepoPath()) link = &feeds.Link{Href: act.GetRepoLink()} default: - log.Error("unknown action type: %v", act.OpType) + return nil, fmt.Errorf("unknown action type: %v", act.OpType) } // description & content @@ -145,7 +145,7 @@ func feedActionsToFeedItems(ctx *context.Context, actions []*models.Action) (ite Name: act.ActUser.DisplayName(), Email: act.ActUser.GetEmail(), }, - Id: fmt.Sprint(act.ID), + Id: strconv.FormatInt(act.ID, 10), Created: act.CreatedUnix.AsTime(), Content: content, }) diff --git a/routers/web/feed/profile.go b/routers/web/feed/profile.go index 7ec9a92a43987..8bd0cb7c29c3f 100644 --- a/routers/web/feed/profile.go +++ b/routers/web/feed/profile.go @@ -71,7 +71,12 @@ func ShowUserFeed(ctx *context.Context, ctxUser *models.User, formatType string) Created: time.Now(), } - feed.Items = feedActionsToFeedItems(ctx, actions) + var err error + feed.Items, err = feedActionsToFeedItems(ctx, actions) + if err != nil { + ctx.ServerError("convert feed", err) + return + } writeFeed(ctx, feed, formatType) }