Description
EcosystemsPublicHandler.GetByID fetches an ecosystem's detail row, then runs a second aggregate query to compute project_count, contributors_count, open_issues_count, and open_prs_count for the response — but discards that second query's error entirely:
var projectCount int64
var contributorsCount int64
var openIssuesCount int64
var openPRsCount int64
_ = h.db.Pool.QueryRow(c.Context(), `
SELECT
(SELECT COUNT(*) FROM projects p WHERE p.ecosystem_id = $1 AND ...),
COALESCE((SELECT COUNT(DISTINCT a.author_login) FROM (...) a), 0),
COALESCE((SELECT COUNT(*) FROM github_issues gi INNER JOIN projects p ...), 0),
COALESCE((SELECT COUNT(*) FROM github_pull_requests gpr INNER JOIN projects p ...), 0)
`, ecoID, ecoID, ecoID, ecoID).Scan(&projectCount, &contributorsCount, &openIssuesCount, &openPRsCount)
out := fiber.Map{
...
"project_count": projectCount,
"contributors_count": contributorsCount,
"open_issues_count": openIssuesCount,
"open_prs_count": openPRsCount,
}
return c.Status(fiber.StatusOK).JSON(out)
If this query fails for any reason (statement timeout on the correlated-subquery-heavy aggregate, a dropped connection, a transient DB error), the _ = ...Scan(...) discards the error and projectCount/contributorsCount/openIssuesCount/openPRsCount all remain at their Go zero value (0). The handler then returns 200 OK with an ecosystem detail page that claims the ecosystem has zero projects, zero contributors, and zero open issues/PRs — a materially misleading response for what should be a simple transient-failure case, with no server-side log entry to explain why the numbers are wrong.
Requirements
- A failure of the aggregate stats query in
GetByID must not silently present fabricated all-zero counts as if they were real data.
- At minimum, the error must be logged so operators can distinguish "genuinely zero" from "query failed."
Suggested execution
- In
internal/handlers/ecosystems_public.go's GetByID, capture the error from the stats QueryRow(...).Scan(...) call instead of discarding it with _ =.
- On error, either (a) log it via
slog.Warn/slog.Error and omit the count fields (or mark them nullable/nil) from the response rather than defaulting to 0, or (b) fail the request with a 500 if this repo's convention treats stats as required — pick whichever matches how Get()'s own primary-row error is already handled just above this block in the same function.
- Add a test that simulates the stats query failing and asserts the response either surfaces an error or omits the counts, rather than silently returning zeros.
Acceptance criteria
Security notes
Data-integrity issue rather than a security vulnerability: presenting fabricated zero counts as legitimate data could mislead ecosystem maintainers or the public dashboard about real platform activity during a transient outage.
Guidelines
- Minimum 95% test coverage
- Timeframe: 96 hours
Description
EcosystemsPublicHandler.GetByIDfetches an ecosystem's detail row, then runs a second aggregate query to computeproject_count,contributors_count,open_issues_count, andopen_prs_countfor the response — but discards that second query's error entirely:If this query fails for any reason (statement timeout on the correlated-subquery-heavy aggregate, a dropped connection, a transient DB error), the
_ = ...Scan(...)discards the error andprojectCount/contributorsCount/openIssuesCount/openPRsCountall remain at their Go zero value (0). The handler then returns200 OKwith an ecosystem detail page that claims the ecosystem has zero projects, zero contributors, and zero open issues/PRs — a materially misleading response for what should be a simple transient-failure case, with no server-side log entry to explain why the numbers are wrong.Requirements
GetByIDmust not silently present fabricated all-zero counts as if they were real data.Suggested execution
internal/handlers/ecosystems_public.go'sGetByID, capture the error from the statsQueryRow(...).Scan(...)call instead of discarding it with_ =.slog.Warn/slog.Errorand omit the count fields (or mark them nullable/nil) from the response rather than defaulting to0, or (b) fail the request with a 500 if this repo's convention treats stats as required — pick whichever matches howGet()'s own primary-row error is already handled just above this block in the same function.Acceptance criteria
GetByIDno longer silently renders as all-zero counts.Security notes
Data-integrity issue rather than a security vulnerability: presenting fabricated zero counts as legitimate data could mislead ecosystem maintainers or the public dashboard about real platform activity during a transient outage.
Guidelines