| 1 | // SPDX-License-Identifier: AGPL-3.0-or-later |
| 2 | |
| 3 | package repo |
| 4 | |
| 5 | import ( |
| 6 | "context" |
| 7 | |
| 8 | "github.com/jackc/pgx/v5/pgtype" |
| 9 | |
| 10 | issuesdb "github.com/tenseleyFlow/shithub/internal/issues/sqlc" |
| 11 | "github.com/tenseleyFlow/shithub/internal/web/middleware" |
| 12 | ) |
| 13 | |
| 14 | // repoSubnavData is the shape the `repo-subnav` partial reads. Counts |
| 15 | // are best-effort: a query failure collapses to zero so the nav still |
| 16 | // renders. The `CanSettings` flag is a UX hint — the actual permission |
| 17 | // check happens inside the settings handler via policy.Can. |
| 18 | type repoSubnavData struct { |
| 19 | Issues int64 |
| 20 | Pulls int64 |
| 21 | Forks int64 |
| 22 | } |
| 23 | |
| 24 | // subnavCounts returns issue / PR / fork counts for the repo's |
| 25 | // header subnav. Counts are visibility-aware via the underlying |
| 26 | // queries (issues + pulls live in the same row table; the count |
| 27 | // query honors the issue_kind discriminator so closed counts and |
| 28 | // PR counts don't bleed into the issue badge). |
| 29 | func (h *Handlers) subnavCounts(ctx context.Context, repoID, forkCount int64) repoSubnavData { |
| 30 | out := repoSubnavData{Forks: forkCount} |
| 31 | openText := pgtype.Text{String: "open", Valid: true} |
| 32 | if n, err := h.iq.CountIssues(ctx, h.d.Pool, issuesdb.CountIssuesParams{ |
| 33 | RepoID: repoID, |
| 34 | StateFilter: openText, |
| 35 | Kind: issuesdb.NullIssueKind{IssueKind: issuesdb.IssueKindIssue, Valid: true}, |
| 36 | }); err == nil { |
| 37 | out.Issues = n |
| 38 | } |
| 39 | if n, err := h.iq.CountIssues(ctx, h.d.Pool, issuesdb.CountIssuesParams{ |
| 40 | RepoID: repoID, |
| 41 | StateFilter: openText, |
| 42 | Kind: issuesdb.NullIssueKind{IssueKind: issuesdb.IssueKindPr, Valid: true}, |
| 43 | }); err == nil { |
| 44 | out.Pulls = n |
| 45 | } |
| 46 | return out |
| 47 | } |
| 48 | |
| 49 | // canViewSettings reports whether the viewer should see the Settings |
| 50 | // tab. We surface it for any logged-in user who could plausibly have |
| 51 | // admin (the actual gate is server-side); for anonymous viewers we |
| 52 | // hide it. |
| 53 | func (h *Handlers) canViewSettings(viewer middleware.CurrentUser) bool { |
| 54 | return !viewer.IsAnonymous() |
| 55 | } |
| 56 |