Skip to content

Add license info (spdxId + name) to RepoResponse#9

Merged
rainxchzed merged 2 commits intomainfrom
add-license-info
May 4, 2026
Merged

Add license info (spdxId + name) to RepoResponse#9
rainxchzed merged 2 commits intomainfrom
add-license-info

Conversation

@rainxchzed
Copy link
Copy Markdown
Member

@rainxchzed rainxchzed commented May 4, 2026

Summary

Surface a repo's license on the details screen + everywhere else RepoResponse is returned. Same pattern as openIssuesCount (#8) -- rides on the existing repo response shape, no new endpoint, no extra fetch.

Two new fields:

  • licenseSpdxId: String? -- short tag ("MIT", "GPL-3.0", "Apache-2.0")
  • licenseName: String? -- full name ("MIT License")

Both nullable. GitHub returns license: null for repos without a LICENSE file or unrecognised licenses.

Changes

Per CLAUDE.md's RepoResponse-fan-out checklist:

  • db/migration/V15__license_info.sql -- adds license_spdx_id TEXT + license_name TEXT, both nullable. Idempotent.
  • db/DatabaseFactory.kt -- registers V15.
  • db/Tables.kt -- two new Exposed columns on Repos.
  • model/RepoResponse.kt -- two new fields with null defaults.
  • db/RepoRepository.kt -- maps both columns.
  • db/MeilisearchClient.kt -- adds the fields to MeiliRepoHit.
  • ingest/GitHubSearchClient.kt -- new GitHubLicense DTO (only persists spdx_id + name); upsert + sync write both columns; RepoWithRelease.toRepoResponse carries them.
  • routes/RepoRoutes.kt -- toMetadataOnlyResponse() carries them.
  • routes/SearchRoutes.kt -- MeiliRepoHit.toRepoResponse() carries them.

Existing curated rows have null license fields until any write path fires (search ingest, refresh button, hourly worker, daily Python fetcher).

Why drop GitHub's key and url

key is spdx_id.lowercased() -- redundant. url points at GitHub's API, not a user-visible URL. Client links to https://github.com/{owner}/{name}/blob/HEAD/LICENSE which is always correct.

Client integration

docs/client/license-info.md -- standalone guide for the client agent. Covers display recommendations (chip with tooltip, no color-coding, hide cleanly when null), tap behaviour (open GitHub LICENSE file), back-compat, explicit non-goals.

Test plan

  • ./gradlew test -- all suites green.
  • After deploy, hit POST /v1/repo/sindresorhus/refined-github/refresh -- expect licenseSpdxId + licenseName populated.
  • Confirm a repo with no LICENSE returns null for both fields.
  • Update Python fetcher in a follow-up PR to write the values from the GitHub response.

Summary by CodeRabbit

Release Notes

  • New Features

    • Added open issues count to repository metadata across search, browse, and detail views.
    • Added license information (SPDX identifier and name) to repository metadata for display on detail screens.
    • Added admin endpoint for backfilling missing license metadata.
  • Documentation

    • Added client integration guides for displaying license information and open issues count.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 4, 2026

Warning

Rate limit exceeded

@rainxchzed has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 52 minutes and 23 seconds before requesting another review.

To keep reviews running without waiting, you can enable usage-based add-on for your organization. This allows additional reviews beyond the hourly cap. Account admins can enable it under billing.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: d53a6774-1761-4941-8f7b-27b05ead7eee

📥 Commits

Reviewing files that changed from the base of the PR and between 8904240 and 6d73c63.

📒 Files selected for processing (13)
  • CLAUDE.md
  • docs/client/license-info.md
  • src/main/kotlin/zed/rainxch/githubstore/db/DatabaseFactory.kt
  • src/main/kotlin/zed/rainxch/githubstore/db/MeilisearchClient.kt
  • src/main/kotlin/zed/rainxch/githubstore/db/RepoRepository.kt
  • src/main/kotlin/zed/rainxch/githubstore/db/Tables.kt
  • src/main/kotlin/zed/rainxch/githubstore/ingest/GitHubSearchClient.kt
  • src/main/kotlin/zed/rainxch/githubstore/model/RepoResponse.kt
  • src/main/kotlin/zed/rainxch/githubstore/routes/InternalRoutes.kt
  • src/main/kotlin/zed/rainxch/githubstore/routes/RepoRoutes.kt
  • src/main/kotlin/zed/rainxch/githubstore/routes/Routing.kt
  • src/main/kotlin/zed/rainxch/githubstore/routes/SearchRoutes.kt
  • src/main/resources/db/migration/V15__license_info.sql
📝 Walkthrough

Walkthrough

This PR extends the repository metadata model across all layers to support three new GitHub repo fields: openIssuesCount (count including open PRs), licenseSpdxId, and licenseName. Changes include database migrations, data model updates, ingest/mapping adjustments, and a new admin-only POST /internal/backfill-stale endpoint to backfill the license field on existing rows.

Changes

Repository Metadata Expansion

Layer / File(s) Summary
Database Schema
src/main/resources/db/migration/V14__open_issues_count.sql, src/main/resources/db/migration/V15__license_info.sql
Two new migrations add repos.open_issues (default 0) and repos.license_spdx_id, repos.license_name (nullable).
Domain Models & ORM Bindings
src/main/kotlin/zed/rainxch/githubstore/db/Tables.kt, src/main/kotlin/zed/rainxch/githubstore/model/RepoResponse.kt
Repos table schema extended with three new columns; RepoResponse DTO gains openIssuesCount: Int = 0 and nullable license fields.
Search Index Structure
src/main/kotlin/zed/rainxch/githubstore/db/MeilisearchClient.kt
MeiliRepoHit adds open_issues, license_spdx_id, license_name for search indexing.
GitHub API Ingest & Persistence
src/main/kotlin/zed/rainxch/githubstore/ingest/GitHubSearchClient.kt
New GitHubLicense DTO introduced; GitHubRepo parses open_issues_count and nested license object from API; Postgres upsert and Meilisearch document payload now persist all three new fields.
Response Mapping
src/main/kotlin/zed/rainxch/githubstore/db/RepoRepository.kt, src/main/kotlin/zed/rainxch/githubstore/routes/RepoRoutes.kt, src/main/kotlin/zed/rainxch/githubstore/routes/SearchRoutes.kt
Row-to-response and search-hit-to-response mappers populate the three new fields into RepoResponse payloads across all endpoints.
Admin Backfill Endpoint & Routing
src/main/kotlin/zed/rainxch/githubstore/routes/InternalRoutes.kt, src/main/kotlin/zed/rainxch/githubstore/routes/Routing.kt
New POST /internal/backfill-stale?limit=N endpoint (admin-only, single-flight via AtomicBoolean) refreshes stale rows where license_spdx_id IS NULL; enforces per-repo pacing and respects quiet window; wired into routing with GitHubSearchClient injection.
API & Client Documentation
CLAUDE.md, docs/client/open-issues-count.md, docs/client/license-info.md
API surface docs updated for new response fields and backfill endpoint; client integration guides specify UI display (chip, icon, compact format), tap behavior, and rollout/backwards-compatibility semantics.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~22 minutes

Possibly related PRs

  • PR #7: Both touch the same GitHubSearchClient.refreshRepo code path and update GitHubRepo.toMetadataOnlyResponse mapping; this PR extends that mapping to include the new fields and adds a background backfill job that depends on the same refresh infrastructure.

Poem

🐰 Three fields hop into the store so bright—
Issues counting, licenses shining light!
From GitHub's API to our DB deep,
Search indexes dance while background jobs keep
Stale rows refreshed through the quiet night. ✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title accurately summarizes the main change: adding license info (spdxId and name) to RepoResponse, which is the primary feature across code, database, and documentation.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch add-license-info

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
Review rate limit: 0/1 reviews remaining, refill in 52 minutes and 23 seconds.

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
src/main/resources/db/migration/V15__license_info.sql (1)

21-25: 💤 Low value

Consider consolidating into a single ALTER TABLE statement.

PostgreSQL supports multiple ADD COLUMN clauses in one ALTER TABLE, which is atomically cleaner and slightly more idiomatic than two separate statements.

✨ Suggested consolidation
-ALTER TABLE repos
-    ADD COLUMN IF NOT EXISTS license_spdx_id TEXT;
-
-ALTER TABLE repos
-    ADD COLUMN IF NOT EXISTS license_name TEXT;
+ALTER TABLE repos
+    ADD COLUMN IF NOT EXISTS license_spdx_id TEXT,
+    ADD COLUMN IF NOT EXISTS license_name TEXT;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/resources/db/migration/V15__license_info.sql` around lines 21 - 25,
The migration creates two columns on the same table using two ALTER TABLE
statements; consolidate into a single ALTER TABLE on the repos table that
includes both ADD COLUMN IF NOT EXISTS clauses for license_spdx_id and
license_name to make the change atomic and cleaner (update V15__license_info.sql
to use one ALTER TABLE repos ... ADD COLUMN IF NOT EXISTS license_spdx_id ...,
ADD COLUMN IF NOT EXISTS license_name ...).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@docs/client/license-info.md`:
- Around line 95-103: The docs overstate that license filtering is available:
update the text in docs/client/license-info.md to remove the implication that
calling /v1/search will accept arbitrary Meilisearch filter expressions and
instead note that the backend currently only accepts q, platform, sort, limit,
and offset (SearchRoutes.kt's /search handler) and that enabling filters
requires two backend changes — (1) extend the /search handler (SearchRoutes.kt)
to accept a filter parameter and safely passthrough or translate it to the
Meilisearch client, and (2) ensure the search index settings include
license_spdx_id in filterableAttributes so filter expressions work; reword the
section to mention these prerequisites and that license_spdx_id is indexed but
not yet filterable until those changes are made.

---

Nitpick comments:
In `@src/main/resources/db/migration/V15__license_info.sql`:
- Around line 21-25: The migration creates two columns on the same table using
two ALTER TABLE statements; consolidate into a single ALTER TABLE on the repos
table that includes both ADD COLUMN IF NOT EXISTS clauses for license_spdx_id
and license_name to make the change atomic and cleaner (update
V15__license_info.sql to use one ALTER TABLE repos ... ADD COLUMN IF NOT EXISTS
license_spdx_id ..., ADD COLUMN IF NOT EXISTS license_name ...).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 69707fe1-c826-4123-b60c-c035e02c0a1b

📥 Commits

Reviewing files that changed from the base of the PR and between 3d426de and 8904240.

📒 Files selected for processing (15)
  • CLAUDE.md
  • docs/client/license-info.md
  • docs/client/open-issues-count.md
  • src/main/kotlin/zed/rainxch/githubstore/db/DatabaseFactory.kt
  • src/main/kotlin/zed/rainxch/githubstore/db/MeilisearchClient.kt
  • src/main/kotlin/zed/rainxch/githubstore/db/RepoRepository.kt
  • src/main/kotlin/zed/rainxch/githubstore/db/Tables.kt
  • src/main/kotlin/zed/rainxch/githubstore/ingest/GitHubSearchClient.kt
  • src/main/kotlin/zed/rainxch/githubstore/model/RepoResponse.kt
  • src/main/kotlin/zed/rainxch/githubstore/routes/InternalRoutes.kt
  • src/main/kotlin/zed/rainxch/githubstore/routes/RepoRoutes.kt
  • src/main/kotlin/zed/rainxch/githubstore/routes/Routing.kt
  • src/main/kotlin/zed/rainxch/githubstore/routes/SearchRoutes.kt
  • src/main/resources/db/migration/V14__open_issues_count.sql
  • src/main/resources/db/migration/V15__license_info.sql

Comment on lines +95 to +103
## 6. Filter / search use cases (out of scope for this PR but FYI)

`licenseSpdxId` is now indexed in Meilisearch via the `license_spdx_id` field on the search document. If you want to add "filter by license" to the search screen later, it's already there — call `/v1/search` with a Meilisearch filter expression. Not implementing that here; just noting the data is available.

Common useful filter sets:
- "Permissive only": `MIT`, `Apache-2.0`, `BSD-2-Clause`, `BSD-3-Clause`, `MPL-2.0`, `Unlicense`, `0BSD`, `ISC`.
- "Copyleft only": `GPL-2.0`, `GPL-3.0`, `AGPL-3.0`, `LGPL-2.1`, `LGPL-3.0`.
- "Permissive or copyleft (anything but proprietary)": null exclusion + filter list.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Section 6 overstates how ready license filtering is.

"call //v1/search with a Meilisearch filter expression" implies the backend already exposes that capability. It doesn't — SearchRoutes.kt's /search handler only accepts q, platform, sort, limit, and offset; there is no path to pass arbitrary Meilisearch filter strings through to the index.

Additionally, storing license_spdx_id in documents doesn't automatically make it filterable in Meilisearch — it also needs to be added to the index's filterableAttributes settings before filter expressions work.

Suggest rewording to prevent a future developer from wiring a broken client filter without the necessary backend changes:

📝 Suggested rewording
-`licenseSpdxId` is now indexed in Meilisearch via the `license_spdx_id` field on the search document. If you want to add "filter by license" to the search screen later, it's already there — call `/v1/search` with a Meilisearch filter expression. Not implementing that here; just noting the data is available.
+`licenseSpdxId` is stored in every Meilisearch document via the `license_spdx_id` field, so the raw data is available for future filtering. To actually enable "filter by license" you would need two backend changes first:
+1. Add `license_spdx_id` to the index's `filterableAttributes` in Meilisearch settings.
+2. Expose a `license` (or similar) query parameter in `GET /v1/search` wired into `MeilisearchClient.search()`.
+Neither is in scope here; just noting the data is in the index.
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
## 6. Filter / search use cases (out of scope for this PR but FYI)
`licenseSpdxId` is now indexed in Meilisearch via the `license_spdx_id` field on the search document. If you want to add "filter by license" to the search screen later, it's already there — call `/v1/search` with a Meilisearch filter expression. Not implementing that here; just noting the data is available.
Common useful filter sets:
- "Permissive only": `MIT`, `Apache-2.0`, `BSD-2-Clause`, `BSD-3-Clause`, `MPL-2.0`, `Unlicense`, `0BSD`, `ISC`.
- "Copyleft only": `GPL-2.0`, `GPL-3.0`, `AGPL-3.0`, `LGPL-2.1`, `LGPL-3.0`.
- "Permissive or copyleft (anything but proprietary)": null exclusion + filter list.
## 6. Filter / search use cases (out of scope for this PR but FYI)
`licenseSpdxId` is stored in every Meilisearch document via the `license_spdx_id` field, so the raw data is available for future filtering. To actually enable "filter by license" you would need two backend changes first:
1. Add `license_spdx_id` to the index's `filterableAttributes` in Meilisearch settings.
2. Expose a `license` (or similar) query parameter in `GET /v1/search` wired into `MeilisearchClient.search()`.
Neither is in scope here; just noting the data is in the index.
Common useful filter sets:
- "Permissive only": `MIT`, `Apache-2.0`, `BSD-2-Clause`, `BSD-3-Clause`, `MPL-2.0`, `Unlicense`, `0BSD`, `ISC`.
- "Copyleft only": `GPL-2.0`, `GPL-3.0`, `AGPL-3.0`, `LGPL-2.1`, `LGPL-3.0`.
- "Permissive or copyleft (anything but proprietary)": null exclusion + filter list.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/client/license-info.md` around lines 95 - 103, The docs overstate that
license filtering is available: update the text in docs/client/license-info.md
to remove the implication that calling /v1/search will accept arbitrary
Meilisearch filter expressions and instead note that the backend currently only
accepts q, platform, sort, limit, and offset (SearchRoutes.kt's /search handler)
and that enabling filters requires two backend changes — (1) extend the /search
handler (SearchRoutes.kt) to accept a filter parameter and safely passthrough or
translate it to the Meilisearch client, and (2) ensure the search index settings
include license_spdx_id in filterableAttributes so filter expressions work;
reword the section to mention these prerequisites and that license_spdx_id is
indexed but not yet filterable until those changes are made.

@rainxchzed rainxchzed merged commit 8e24fa6 into main May 4, 2026
2 checks passed
@rainxchzed rainxchzed deleted the add-license-info branch May 4, 2026 17:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant