package repo import ( "context" "fmt" "sort" "time" "polar/internal/git" ) type ActivityItem struct { Type string // "commit", "pull", "issue", "release" Title string Subtitle string AuthorUsername string AuthorAvatar string Link string Icon string Badge string CreatedAt time.Time } type ActivityStats struct { CommitCount int OpenIssues int ClosedIssues int OpenPRs int MergedPRs int ReleaseCount int } func (s *Service) GetRepoActivity(ctx context.Context, r *Repository, diskPath string) ([]ActivityItem, *ActivityStats, error) { var items []ActivityItem // 1. Recent commits from default branch commits, _ := git.ListCommits(ctx, diskPath, r.DefaultBranch, 25, 0) for _, c := range commits { items = append(items, ActivityItem{ Type: "commit", Title: c.Subject, Subtitle: c.ShortHash, AuthorUsername: c.AuthorName, AuthorAvatar: "", Link: fmt.Sprintf("/%s/%s/commit/%s", r.OwnerUsername, r.Name, c.Hash), Icon: "git-commit", Badge: "commit", CreatedAt: c.AuthoredAt, }) } // 2. Recent Pull Requests pulls, _ := s.ListPullRequests(ctx, r.ID, "all") var openPRs, mergedPRs int for _, pr := range pulls { switch pr.Status { case "open": openPRs++ case "merged": mergedPRs++ } badge := pr.Status icon := "git-pull-request" items = append(items, ActivityItem{ Type: "pull", Title: fmt.Sprintf("#%d %s", pr.Number, pr.Title), Subtitle: fmt.Sprintf("%s → %s", pr.HeadBranch, pr.BaseBranch), AuthorUsername: pr.AuthorUsername, AuthorAvatar: pr.AuthorAvatar, Link: fmt.Sprintf("/%s/%s/pulls/%d", r.OwnerUsername, r.Name, pr.Number), Icon: icon, Badge: badge, CreatedAt: pr.CreatedAt, }) } // 3. Recent Issues openIssList, _ := s.ListIssues(ctx, r.ID, false) closedIssList, _ := s.ListIssues(ctx, r.ID, true) openIssues := len(openIssList) closedIssues := len(closedIssList) allIssues := append(openIssList, closedIssList...) for _, iss := range allIssues { badge := "open" if iss.IsClosed { badge = "closed" } items = append(items, ActivityItem{ Type: "issue", Title: fmt.Sprintf("#%d %s", iss.Number, iss.Title), Subtitle: "", AuthorUsername: iss.AuthorUsername, AuthorAvatar: iss.AuthorAvatar, Link: fmt.Sprintf("/%s/%s/issues/%d", r.OwnerUsername, r.Name, iss.Number), Icon: "issue", Badge: badge, CreatedAt: iss.CreatedAt, }) } // 4. Recent Releases releases, _ := s.ListReleases(ctx, r.ID) for _, rel := range releases { items = append(items, ActivityItem{ Type: "release", Title: fmt.Sprintf("%s (%s)", rel.Title, rel.TagName), Subtitle: "", AuthorUsername: rel.AuthorUsername, AuthorAvatar: rel.AuthorAvatar, Link: fmt.Sprintf("/%s/%s/releases#%s", r.OwnerUsername, r.Name, rel.TagName), Icon: "tag", Badge: "release", CreatedAt: rel.PublishedAt, }) } // Sort combined timeline in reverse chronological order sort.Slice(items, func(i, j int) bool { return items[i].CreatedAt.After(items[j].CreatedAt) }) if len(items) > 50 { items = items[:50] } stats := &ActivityStats{ CommitCount: len(commits), OpenIssues: openIssues, ClosedIssues: closedIssues, OpenPRs: openPRs, MergedPRs: mergedPRs, ReleaseCount: len(releases), } return items, stats, nil }