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