From 2851773b395b602699def5d90aac68a697cff6db Mon Sep 17 00:00:00 2001 From: dusk Date: Tue, 12 Aug 2025 18:31:36 +0300 Subject: [PATCH] appview: repo: implement generating feed Change-Id: omomuqvrurzqrmpvuurnqlvkktnqqmkp Signed-off-by: dusk --- appview/repo/repo.go | 101 +++++++++++++++++++++++++++++++++++++++++ appview/repo/router.go | 1 + 2 files changed, 102 insertions(+) diff --git a/appview/repo/repo.go b/appview/repo/repo.go index e28cbc3..81ce425 100644 --- a/appview/repo/repo.go +++ b/appview/repo/repo.go @@ -37,6 +37,7 @@ import ( securejoin "github.com/cyphar/filepath-securejoin" "github.com/go-chi/chi/v5" "github.com/go-git/go-git/v5/plumbing" + "github.com/gorilla/feeds" comatproto "github.com/bluesky-social/indigo/api/atproto" "github.com/bluesky-social/indigo/atproto/syntax" @@ -288,6 +289,106 @@ func (rp *Repo) RepoDescription(w http.ResponseWriter, r *http.Request) { } } +func (rp *Repo) getRepoFeed(ctx context.Context, f *reporesolver.ResolvedRepo) (*feeds.Feed, error) { + const feedLimitPerType = 100 + + pulls, err := db.GetPullsWithLimit(rp.db, feedLimitPerType, db.FilterEq("repo_at", f.RepoAt())) + if err != nil { + return nil, err + } + + issues, err := db.GetIssuesWithLimit(rp.db, feedLimitPerType, db.FilterEq("repo_at", f.RepoAt())) + if err != nil { + return nil, err + } + + feed := &feeds.Feed{ + Title: fmt.Sprintf("activity feed for %s", f.OwnerSlashRepo()), + Link: &feeds.Link{Href: fmt.Sprintf("%s/%s", rp.config.Core.AppviewHost, f.OwnerSlashRepo()), Type: "text/html", Rel: "alternate"}, + Items: make([]*feeds.Item, 0), + Updated: time.UnixMilli(0), + } + + for _, pull := range pulls { + owner, err := rp.idResolver.ResolveIdent(ctx, pull.OwnerDid) + if err != nil { + return nil, err + } + var state string + if pull.State == db.PullOpen { + state = "opened" + } else { + state = pull.State.String() + } + item := &feeds.Item{ + Title: fmt.Sprintf("[PR] %s", pull.Title), + Description: fmt.Sprintf("@%s %s pull request on %s", owner.Handle, state, f.OwnerSlashRepo()), + Link: &feeds.Link{Href: fmt.Sprintf("%s/%s/pulls/%d", rp.config.Core.AppviewHost, f.OwnerSlashRepo(), pull.PullId)}, + Created: pull.Created, + Author: &feeds.Author{ + Name: fmt.Sprintf("@%s", owner.Handle), + }, + } + feed.Items = append(feed.Items, item) + } + + for _, issue := range issues { + owner, err := rp.idResolver.ResolveIdent(ctx, issue.OwnerDid) + if err != nil { + return nil, err + } + var state string + if issue.Open { + state = "opened" + } else { + state = "closed" + } + item := &feeds.Item{ + Title: fmt.Sprintf("[Issue] %s", issue.Title), + Description: fmt.Sprintf("@%s %s issue on %s", owner.Handle, state, f.OwnerSlashRepo()), + Link: &feeds.Link{Href: fmt.Sprintf("%s/%s/issues/%d", rp.config.Core.AppviewHost, f.OwnerSlashRepo(), issue.IssueId)}, + Created: issue.Created, + Author: &feeds.Author{ + Name: fmt.Sprintf("@%s", owner.Handle), + }, + } + feed.Items = append(feed.Items, item) + } + + slices.SortFunc(feed.Items, func(a *feeds.Item, b *feeds.Item) int { + return int(b.Created.UnixMilli()) - int(a.Created.UnixMilli()) + }) + if len(feed.Items) > 0 { + feed.Updated = feed.Items[0].Created + } + + return feed, nil +} + +func (rp *Repo) RepoAtomFeed(w http.ResponseWriter, r *http.Request) { + f, err := rp.repoResolver.Resolve(r) + if err != nil { + log.Println("failed to fully resolve repo:", err) + return + } + + feed, err := rp.getRepoFeed(r.Context(), f) + if err != nil { + log.Println("failed to get repo feed:", err) + rp.pages.Error500(w) + return + } + + atom, err := feed.ToAtom() + if err != nil { + rp.pages.Error500(w) + return + } + + w.Header().Set("content-type", "application/atom+xml") + w.Write([]byte(atom)) +} + func (rp *Repo) RepoCommit(w http.ResponseWriter, r *http.Request) { f, err := rp.repoResolver.Resolve(r) if err != nil { diff --git a/appview/repo/router.go b/appview/repo/router.go index 0a0f80d..bc01746 100644 --- a/appview/repo/router.go +++ b/appview/repo/router.go @@ -10,6 +10,7 @@ import ( func (rp *Repo) Router(mw *middleware.Middleware) http.Handler { r := chi.NewRouter() r.Get("/", rp.RepoIndex) + r.Get("/feed.atom", rp.RepoAtomFeed) r.Get("/commits/{ref}", rp.RepoLog) r.Route("/tree/{ref}", func(r chi.Router) { r.Get("/", rp.RepoIndex) -- 2.43.0