forked from tangled.org/core
this repo has no description
at master 4.8 kB view raw
1package repo 2 3import ( 4 "context" 5 "fmt" 6 "log" 7 "net/http" 8 "slices" 9 "time" 10 11 "tangled.sh/tangled.sh/core/appview/db" 12 "tangled.sh/tangled.sh/core/appview/pagination" 13 "tangled.sh/tangled.sh/core/appview/reporesolver" 14 15 "github.com/bluesky-social/indigo/atproto/syntax" 16 "github.com/gorilla/feeds" 17) 18 19func (rp *Repo) getRepoFeed(ctx context.Context, f *reporesolver.ResolvedRepo) (*feeds.Feed, error) { 20 const feedLimitPerType = 100 21 22 pulls, err := db.GetPullsWithLimit(rp.db, feedLimitPerType, db.FilterEq("repo_at", f.RepoAt())) 23 if err != nil { 24 return nil, err 25 } 26 27 issues, err := db.GetIssuesPaginated( 28 rp.db, 29 pagination.Page{Limit: feedLimitPerType}, 30 db.FilterEq("repo_at", f.RepoAt()), 31 ) 32 if err != nil { 33 return nil, err 34 } 35 36 feed := &feeds.Feed{ 37 Title: fmt.Sprintf("activity feed for %s", f.OwnerSlashRepo()), 38 Link: &feeds.Link{Href: fmt.Sprintf("%s/%s", rp.config.Core.AppviewHost, f.OwnerSlashRepo()), Type: "text/html", Rel: "alternate"}, 39 Items: make([]*feeds.Item, 0), 40 Updated: time.UnixMilli(0), 41 } 42 43 for _, pull := range pulls { 44 items, err := rp.createPullItems(ctx, pull, f) 45 if err != nil { 46 return nil, err 47 } 48 feed.Items = append(feed.Items, items...) 49 } 50 51 for _, issue := range issues { 52 item, err := rp.createIssueItem(ctx, issue, f) 53 if err != nil { 54 return nil, err 55 } 56 feed.Items = append(feed.Items, item) 57 } 58 59 slices.SortFunc(feed.Items, func(a, b *feeds.Item) int { 60 if a.Created.After(b.Created) { 61 return -1 62 } 63 return 1 64 }) 65 66 if len(feed.Items) > 0 { 67 feed.Updated = feed.Items[0].Created 68 } 69 70 return feed, nil 71} 72 73func (rp *Repo) createPullItems(ctx context.Context, pull *db.Pull, f *reporesolver.ResolvedRepo) ([]*feeds.Item, error) { 74 owner, err := rp.idResolver.ResolveIdent(ctx, pull.OwnerDid) 75 if err != nil { 76 return nil, err 77 } 78 79 var items []*feeds.Item 80 81 state := rp.getPullState(pull) 82 description := rp.buildPullDescription(owner.Handle, state, pull, f.OwnerSlashRepo()) 83 84 mainItem := &feeds.Item{ 85 Title: fmt.Sprintf("[PR #%d] %s", pull.PullId, pull.Title), 86 Description: description, 87 Link: &feeds.Link{Href: fmt.Sprintf("%s/%s/pulls/%d", rp.config.Core.AppviewHost, f.OwnerSlashRepo(), pull.PullId)}, 88 Created: pull.Created, 89 Author: &feeds.Author{Name: fmt.Sprintf("@%s", owner.Handle)}, 90 } 91 items = append(items, mainItem) 92 93 for _, round := range pull.Submissions { 94 if round == nil || round.RoundNumber == 0 { 95 continue 96 } 97 98 roundItem := &feeds.Item{ 99 Title: fmt.Sprintf("[PR #%d] %s (round #%d)", pull.PullId, pull.Title, round.RoundNumber), 100 Description: fmt.Sprintf("@%s submitted changes (at round #%d) on PR #%d in %s", owner.Handle, round.RoundNumber, pull.PullId, f.OwnerSlashRepo()), 101 Link: &feeds.Link{Href: fmt.Sprintf("%s/%s/pulls/%d/round/%d/", rp.config.Core.AppviewHost, f.OwnerSlashRepo(), pull.PullId, round.RoundNumber)}, 102 Created: round.Created, 103 Author: &feeds.Author{Name: fmt.Sprintf("@%s", owner.Handle)}, 104 } 105 items = append(items, roundItem) 106 } 107 108 return items, nil 109} 110 111func (rp *Repo) createIssueItem(ctx context.Context, issue db.Issue, f *reporesolver.ResolvedRepo) (*feeds.Item, error) { 112 owner, err := rp.idResolver.ResolveIdent(ctx, issue.Did) 113 if err != nil { 114 return nil, err 115 } 116 117 state := "closed" 118 if issue.Open { 119 state = "opened" 120 } 121 122 return &feeds.Item{ 123 Title: fmt.Sprintf("[Issue #%d] %s", issue.IssueId, issue.Title), 124 Description: fmt.Sprintf("@%s %s issue #%d in %s", owner.Handle, state, issue.IssueId, f.OwnerSlashRepo()), 125 Link: &feeds.Link{Href: fmt.Sprintf("%s/%s/issues/%d", rp.config.Core.AppviewHost, f.OwnerSlashRepo(), issue.IssueId)}, 126 Created: issue.Created, 127 Author: &feeds.Author{Name: fmt.Sprintf("@%s", owner.Handle)}, 128 }, nil 129} 130 131func (rp *Repo) getPullState(pull *db.Pull) string { 132 if pull.State == db.PullOpen { 133 return "opened" 134 } 135 return pull.State.String() 136} 137 138func (rp *Repo) buildPullDescription(handle syntax.Handle, state string, pull *db.Pull, repoName string) string { 139 base := fmt.Sprintf("@%s %s pull request #%d", handle, state, pull.PullId) 140 141 if pull.State == db.PullMerged { 142 return fmt.Sprintf("%s (on round #%d) in %s", base, pull.LastRoundNumber(), repoName) 143 } 144 145 return fmt.Sprintf("%s in %s", base, repoName) 146} 147 148func (rp *Repo) RepoAtomFeed(w http.ResponseWriter, r *http.Request) { 149 f, err := rp.repoResolver.Resolve(r) 150 if err != nil { 151 log.Println("failed to fully resolve repo:", err) 152 return 153 } 154 155 feed, err := rp.getRepoFeed(r.Context(), f) 156 if err != nil { 157 log.Println("failed to get repo feed:", err) 158 rp.pages.Error500(w) 159 return 160 } 161 162 atom, err := feed.ToAtom() 163 if err != nil { 164 rp.pages.Error500(w) 165 return 166 } 167 168 w.Header().Set("content-type", "application/atom+xml") 169 w.Write([]byte(atom)) 170}