this repo has no description

appview: move feed generation into separate file

Signed-off-by: oppiliappan <me@oppi.li>

Changed files
+165 -122
appview
+165
appview/repo/feed.go
··· 1 + package repo 2 + 3 + import ( 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/reporesolver" 13 + 14 + "github.com/bluesky-social/indigo/atproto/syntax" 15 + "github.com/gorilla/feeds" 16 + ) 17 + 18 + func (rp *Repo) getRepoFeed(ctx context.Context, f *reporesolver.ResolvedRepo) (*feeds.Feed, error) { 19 + const feedLimitPerType = 100 20 + 21 + pulls, err := db.GetPullsWithLimit(rp.db, feedLimitPerType, db.FilterEq("repo_at", f.RepoAt())) 22 + if err != nil { 23 + return nil, err 24 + } 25 + 26 + issues, err := db.GetIssuesWithLimit(rp.db, feedLimitPerType, db.FilterEq("repo_at", f.RepoAt())) 27 + if err != nil { 28 + return nil, err 29 + } 30 + 31 + feed := &feeds.Feed{ 32 + Title: fmt.Sprintf("activity feed for %s", f.OwnerSlashRepo()), 33 + Link: &feeds.Link{Href: fmt.Sprintf("%s/%s", rp.config.Core.AppviewHost, f.OwnerSlashRepo()), Type: "text/html", Rel: "alternate"}, 34 + Items: make([]*feeds.Item, 0), 35 + Updated: time.UnixMilli(0), 36 + } 37 + 38 + for _, pull := range pulls { 39 + items, err := rp.createPullItems(ctx, pull, f) 40 + if err != nil { 41 + return nil, err 42 + } 43 + feed.Items = append(feed.Items, items...) 44 + } 45 + 46 + for _, issue := range issues { 47 + item, err := rp.createIssueItem(ctx, issue, f) 48 + if err != nil { 49 + return nil, err 50 + } 51 + feed.Items = append(feed.Items, item) 52 + } 53 + 54 + slices.SortFunc(feed.Items, func(a, b *feeds.Item) int { 55 + if a.Created.After(b.Created) { 56 + return -1 57 + } 58 + return 1 59 + }) 60 + 61 + if len(feed.Items) > 0 { 62 + feed.Updated = feed.Items[0].Created 63 + } 64 + 65 + return feed, nil 66 + } 67 + 68 + func (rp *Repo) createPullItems(ctx context.Context, pull *db.Pull, f *reporesolver.ResolvedRepo) ([]*feeds.Item, error) { 69 + owner, err := rp.idResolver.ResolveIdent(ctx, pull.OwnerDid) 70 + if err != nil { 71 + return nil, err 72 + } 73 + 74 + var items []*feeds.Item 75 + 76 + state := rp.getPullState(pull) 77 + description := rp.buildPullDescription(owner.Handle, state, pull, f.OwnerSlashRepo()) 78 + 79 + mainItem := &feeds.Item{ 80 + Title: fmt.Sprintf("[PR #%d] %s", pull.PullId, pull.Title), 81 + Description: description, 82 + Link: &feeds.Link{Href: fmt.Sprintf("%s/%s/pulls/%d", rp.config.Core.AppviewHost, f.OwnerSlashRepo(), pull.PullId)}, 83 + Created: pull.Created, 84 + Author: &feeds.Author{Name: fmt.Sprintf("@%s", owner.Handle)}, 85 + } 86 + items = append(items, mainItem) 87 + 88 + for _, round := range pull.Submissions { 89 + if round == nil || round.RoundNumber == 0 { 90 + continue 91 + } 92 + 93 + roundItem := &feeds.Item{ 94 + Title: fmt.Sprintf("[PR #%d] %s (round #%d)", pull.PullId, pull.Title, round.RoundNumber), 95 + Description: fmt.Sprintf("@%s submitted changes (at round #%d) on PR #%d in %s", owner.Handle, round.RoundNumber, pull.PullId, f.OwnerSlashRepo()), 96 + Link: &feeds.Link{Href: fmt.Sprintf("%s/%s/pulls/%d/round/%d/", rp.config.Core.AppviewHost, f.OwnerSlashRepo(), pull.PullId, round.RoundNumber)}, 97 + Created: round.Created, 98 + Author: &feeds.Author{Name: fmt.Sprintf("@%s", owner.Handle)}, 99 + } 100 + items = append(items, roundItem) 101 + } 102 + 103 + return items, nil 104 + } 105 + 106 + func (rp *Repo) createIssueItem(ctx context.Context, issue db.Issue, f *reporesolver.ResolvedRepo) (*feeds.Item, error) { 107 + owner, err := rp.idResolver.ResolveIdent(ctx, issue.OwnerDid) 108 + if err != nil { 109 + return nil, err 110 + } 111 + 112 + state := "closed" 113 + if issue.Open { 114 + state = "opened" 115 + } 116 + 117 + return &feeds.Item{ 118 + Title: fmt.Sprintf("[Issue #%d] %s", issue.IssueId, issue.Title), 119 + Description: fmt.Sprintf("@%s %s issue #%d in %s", owner.Handle, state, issue.IssueId, f.OwnerSlashRepo()), 120 + Link: &feeds.Link{Href: fmt.Sprintf("%s/%s/issues/%d", rp.config.Core.AppviewHost, f.OwnerSlashRepo(), issue.IssueId)}, 121 + Created: issue.Created, 122 + Author: &feeds.Author{Name: fmt.Sprintf("@%s", owner.Handle)}, 123 + }, nil 124 + } 125 + 126 + func (rp *Repo) getPullState(pull *db.Pull) string { 127 + if pull.State == db.PullOpen { 128 + return "opened" 129 + } 130 + return pull.State.String() 131 + } 132 + 133 + func (rp *Repo) buildPullDescription(handle syntax.Handle, state string, pull *db.Pull, repoName string) string { 134 + base := fmt.Sprintf("@%s %s pull request #%d", handle, state, pull.PullId) 135 + 136 + if pull.State == db.PullMerged { 137 + return fmt.Sprintf("%s (on round #%d) in %s", base, pull.LastRoundNumber(), repoName) 138 + } 139 + 140 + return fmt.Sprintf("%s in %s", base, repoName) 141 + } 142 + 143 + func (rp *Repo) RepoAtomFeed(w http.ResponseWriter, r *http.Request) { 144 + f, err := rp.repoResolver.Resolve(r) 145 + if err != nil { 146 + log.Println("failed to fully resolve repo:", err) 147 + return 148 + } 149 + 150 + feed, err := rp.getRepoFeed(r.Context(), f) 151 + if err != nil { 152 + log.Println("failed to get repo feed:", err) 153 + rp.pages.Error500(w) 154 + return 155 + } 156 + 157 + atom, err := feed.ToAtom() 158 + if err != nil { 159 + rp.pages.Error500(w) 160 + return 161 + } 162 + 163 + w.Header().Set("content-type", "application/atom+xml") 164 + w.Write([]byte(atom)) 165 + }
-122
appview/repo/repo.go
··· 37 37 securejoin "github.com/cyphar/filepath-securejoin" 38 38 "github.com/go-chi/chi/v5" 39 39 "github.com/go-git/go-git/v5/plumbing" 40 - "github.com/gorilla/feeds" 41 40 42 41 comatproto "github.com/bluesky-social/indigo/api/atproto" 43 42 "github.com/bluesky-social/indigo/atproto/syntax" ··· 287 286 }) 288 287 return 289 288 } 290 - } 291 - 292 - func (rp *Repo) getRepoFeed(ctx context.Context, f *reporesolver.ResolvedRepo) (*feeds.Feed, error) { 293 - const feedLimitPerType = 100 294 - 295 - pulls, err := db.GetPullsWithLimit(rp.db, feedLimitPerType, db.FilterEq("repo_at", f.RepoAt())) 296 - if err != nil { 297 - return nil, err 298 - } 299 - 300 - issues, err := db.GetIssuesWithLimit(rp.db, feedLimitPerType, db.FilterEq("repo_at", f.RepoAt())) 301 - if err != nil { 302 - return nil, err 303 - } 304 - 305 - feed := &feeds.Feed{ 306 - Title: fmt.Sprintf("activity feed for %s", f.OwnerSlashRepo()), 307 - Link: &feeds.Link{Href: fmt.Sprintf("%s/%s", rp.config.Core.AppviewHost, f.OwnerSlashRepo()), Type: "text/html", Rel: "alternate"}, 308 - Items: make([]*feeds.Item, 0), 309 - Updated: time.UnixMilli(0), 310 - } 311 - 312 - for _, pull := range pulls { 313 - owner, err := rp.idResolver.ResolveIdent(ctx, pull.OwnerDid) 314 - if err != nil { 315 - return nil, err 316 - } 317 - 318 - var state string 319 - if pull.State == db.PullOpen { 320 - state = "opened" 321 - } else { 322 - state = pull.State.String() 323 - } 324 - mergedAtRounds := "" 325 - if pull.State == db.PullMerged { 326 - mergedAtRounds = fmt.Sprintf(" (on round #%d)", pull.LastRoundNumber()) 327 - } 328 - item := &feeds.Item{ 329 - Title: fmt.Sprintf("[PR #%d] %s", pull.PullId, pull.Title), 330 - Description: fmt.Sprintf("@%s %s pull request #%d%s in %s", owner.Handle, state, pull.PullId, mergedAtRounds, f.OwnerSlashRepo()), 331 - Link: &feeds.Link{Href: fmt.Sprintf("%s/%s/pulls/%d", rp.config.Core.AppviewHost, f.OwnerSlashRepo(), pull.PullId)}, 332 - Created: pull.Created, 333 - Author: &feeds.Author{ 334 - Name: fmt.Sprintf("@%s", owner.Handle), 335 - }, 336 - } 337 - feed.Items = append(feed.Items, item) 338 - 339 - for _, round := range pull.Submissions { 340 - if round == nil || round.RoundNumber == 0 { 341 - continue 342 - } 343 - item := &feeds.Item{ 344 - Title: fmt.Sprintf("[PR #%d] %s (round #%d)", pull.PullId, pull.Title, round.RoundNumber), 345 - Description: fmt.Sprintf("@%s submitted changes (at round #%d) on PR #%d in %s", owner.Handle, round.RoundNumber, pull.PullId, f.OwnerSlashRepo()), 346 - Link: &feeds.Link{Href: fmt.Sprintf("%s/%s/pulls/%d/round/%d/", rp.config.Core.AppviewHost, f.OwnerSlashRepo(), pull.PullId, round.RoundNumber)}, 347 - Created: round.Created, 348 - Author: &feeds.Author{ 349 - Name: fmt.Sprintf("@%s", owner.Handle), 350 - }, 351 - } 352 - feed.Items = append(feed.Items, item) 353 - } 354 - } 355 - 356 - for _, issue := range issues { 357 - owner, err := rp.idResolver.ResolveIdent(ctx, issue.OwnerDid) 358 - if err != nil { 359 - return nil, err 360 - } 361 - var state string 362 - if issue.Open { 363 - state = "opened" 364 - } else { 365 - state = "closed" 366 - } 367 - item := &feeds.Item{ 368 - Title: fmt.Sprintf("[Issue #%d] %s", issue.IssueId, issue.Title), 369 - Description: fmt.Sprintf("@%s %s issue #%d in %s", owner.Handle, state, issue.IssueId, f.OwnerSlashRepo()), 370 - Link: &feeds.Link{Href: fmt.Sprintf("%s/%s/issues/%d", rp.config.Core.AppviewHost, f.OwnerSlashRepo(), issue.IssueId)}, 371 - Created: issue.Created, 372 - Author: &feeds.Author{ 373 - Name: fmt.Sprintf("@%s", owner.Handle), 374 - }, 375 - } 376 - feed.Items = append(feed.Items, item) 377 - } 378 - 379 - slices.SortFunc(feed.Items, func(a *feeds.Item, b *feeds.Item) int { 380 - return int(b.Created.UnixMilli()) - int(a.Created.UnixMilli()) 381 - }) 382 - if len(feed.Items) > 0 { 383 - feed.Updated = feed.Items[0].Created 384 - } 385 - 386 - return feed, nil 387 - } 388 - 389 - func (rp *Repo) RepoAtomFeed(w http.ResponseWriter, r *http.Request) { 390 - f, err := rp.repoResolver.Resolve(r) 391 - if err != nil { 392 - log.Println("failed to fully resolve repo:", err) 393 - return 394 - } 395 - 396 - feed, err := rp.getRepoFeed(r.Context(), f) 397 - if err != nil { 398 - log.Println("failed to get repo feed:", err) 399 - rp.pages.Error500(w) 400 - return 401 - } 402 - 403 - atom, err := feed.ToAtom() 404 - if err != nil { 405 - rp.pages.Error500(w) 406 - return 407 - } 408 - 409 - w.Header().Set("content-type", "application/atom+xml") 410 - w.Write([]byte(atom)) 411 289 } 412 290 413 291 func (rp *Repo) RepoCommit(w http.ResponseWriter, r *http.Request) {