Monorepo for Tangled tangled.org

appview: repo: implement generating feed

Signed-off-by: dusk <y.bera003.06@protonmail.com>

authored by ptr.pet and committed by Tangled 61c4ddbc f5917160

Changed files
+123
appview
+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" 40 41 41 42 comatproto "github.com/bluesky-social/indigo/api/atproto" 42 43 "github.com/bluesky-social/indigo/atproto/syntax" ··· 286 287 }) 287 288 return 288 289 } 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)) 289 411 } 290 412 291 413 func (rp *Repo) RepoCommit(w http.ResponseWriter, r *http.Request) {
+1
appview/repo/router.go
··· 10 10 func (rp *Repo) Router(mw *middleware.Middleware) http.Handler { 11 11 r := chi.NewRouter() 12 12 r.Get("/", rp.RepoIndex) 13 + r.Get("/feed.atom", rp.RepoAtomFeed) 13 14 r.Get("/commits/{ref}", rp.RepoLog) 14 15 r.Route("/tree/{ref}", func(r chi.Router) { 15 16 r.Get("/", rp.RepoIndex)