Adds read-only GET /user/orgs and GET /orgs/{org}/repos to
GiteaRepositoryBrowserApi (a third interface, OrganizationsRepository),
reusing existing repository mapping rather than duplicating it.
Repository list gains an organization filter chip row (all + one per
org), keeping the existing recent-push sort and paging. Organizations
are fetched once per session and cached; a failure to load them
degrades to an empty filter row instead of blocking the repository
list itself. Filtering only applies to the all-repositories mode,
since Gitea star is not organization-scoped.
Closes MVP outcome #2 in docs/product.md. No task file existed
beforehand; created it first per AGENTS.md.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
72 lines
6.2 KiB
Markdown
72 lines
6.2 KiB
Markdown
# TASK-014: Browse organizations and filter repositories by organization
|
|
|
|
Status: implemented; all required checks pass; pending Codex review
|
|
Owner: Claude (human product owner directed Claude to own all subsequent tasks, 2026-08-26)
|
|
Reviewer: Codex
|
|
|
|
## Outcome
|
|
|
|
A signed-in user can see the organizations their account belongs to and filter the repository list by one organization while still being able to return to "all repositories." This closes MVP outcome #2 in `docs/product.md` ("View the signed-in user and organizations").
|
|
|
|
## Scope
|
|
|
|
- Included:
|
|
- Read-only `GET /user/orgs` and `GET /orgs/{org}/repos`, added to `GiteaRepositoryBrowserApi` as a third small interface, reusing its existing repository-mapping code.
|
|
- An organization filter row on the repository list (ALL mode only — favorites are not org-scoped in Gitea), defaulting to "전체" (all), with the existing loading/empty/offline/auth-expired/server-error states applying to the filtered list the same way they already do for the unfiltered one.
|
|
- Excluded:
|
|
- Any organization administration or membership write (create/join/leave/manage team).
|
|
- Filtering the Favorites tab by organization — starred repos are not org-scoped in Gitea's own model.
|
|
|
|
## Writable paths
|
|
|
|
- `app/src/main/java/com/ubisam/ubgitea/domain/organizations/`
|
|
- `app/src/main/java/com/ubisam/ubgitea/data/repositories/GiteaRepositoryBrowserApi.kt` (additive: new interface implementation only)
|
|
- `app/src/test/java/com/ubisam/ubgitea/data/repositories/GiteaRepositoryBrowserApiTest.kt` (additive)
|
|
- `app/src/main/java/com/ubisam/ubgitea/feature/repositories/`
|
|
- `app/src/test/java/com/ubisam/ubgitea/feature/repositories/`
|
|
- `app/src/main/res/values/strings_repositories.xml`
|
|
- `tasks/014-organizations.md`
|
|
|
|
## Acceptance criteria
|
|
|
|
- [x] Organizations list and organization-scoped repository list are both paginated read-only calls; no membership/team-write endpoint is reachable — `GiteaRepositoryBrowserApi` only adds `get()` calls for these two new methods (grep-confirmed, same as every other read-only endpoint in that class).
|
|
- [x] The repository list can be filtered to one organization and back to "all repositories" without losing the recent-push sort or paging behavior already built (TASK-017) — `listOrganizationRepositories` requests the same `sort=updated&order=desc`, uses the same `REPOSITORY_PAGE_SIZE`/page-based navigation, and reuses `mapRepository` so field mapping cannot drift between the two code paths.
|
|
- [x] Loading, empty, offline, authentication-expired, and server-error states apply to the organization-filtered list the same way they already do for the unfiltered list — the org-scoped call goes through the exact same `loadRepositoryPage` try/catch and `RepositoryListUiState` cases as the unfiltered call; there is no separate, parallel state machine for it.
|
|
- [x] Tests cover organization list mapping, organization-repository mapping (reusing existing repository mapping, not duplicating it), and filter switching — 2 data-layer tests plus 3 ViewModel tests (see Evidence).
|
|
|
|
## Required checks
|
|
|
|
```text
|
|
./gradlew assembleDebug
|
|
./gradlew testDebugUnitTest
|
|
./gradlew lintDebug
|
|
```
|
|
|
|
## Evidence
|
|
|
|
Implemented on branch `claude/task-014-organizations` from commit `7756ec4`.
|
|
|
|
- Domain: `domain/organizations/OrganizationModels.kt` (`OrganizationSummary(name, displayName)`), `domain/organizations/OrganizationsRepository.kt`. Data: `GiteaRepositoryBrowserApi` gained a third interface implementation (`listOrganizations`, `listOrganizationRepositories`), reusing its existing `mapRepository` for the latter rather than duplicating repository-JSON mapping a third time.
|
|
- Verified against Gitea's actual `Organization` Go struct (`name`/`full_name` fields) before implementing, not guessed.
|
|
- `RepositoryListViewModel`: `Content`/`Empty` gained `organization: String?` (null = all) and `Content` gained `organizations: List<OrganizationSummary>` for the filter row. The organizations list is fetched once per session (`loadOrganizationsOnce`, cached in `cachedOrganizations`, cleared on logout) rather than on every page load — a failure to load it is treated as non-fatal (empty filter row) and does not block showing the repository list itself, since the list is the primary content and the filter is secondary. `selectOrganization(String?)` only applies in ALL mode (Gitea star is not organization-scoped, so Favorites ignores it, tested explicitly).
|
|
- UI: `RepositoryListScreen` gained a horizontally-scrollable `FilterChip` row (전체 + one chip per organization) shown only in ALL mode when at least one organization exists, using each organization's `displayName` when present and falling back to `name`.
|
|
|
|
Test coverage: 2 new data-layer tests (`organizations map name and full name`, `organization repositories reuse repository mapping and request recent-push order`) plus 3 new ViewModel tests (`selecting an organization filters the list and organizations are exposed for the picker`, `the organizations list is only fetched once and reused across page loads`, `organization filtering does not apply to the favorites tab`).
|
|
|
|
Checks run from the new worktree (`local.properties` created locally, not committed):
|
|
|
|
```text
|
|
./gradlew testDebugUnitTest --tests "com.ubisam.ubgitea.feature.repositories.RepositoryListViewModelTest" → BUILD SUCCESSFUL
|
|
./gradlew assembleDebug testDebugUnitTest lintDebug --rerun-tasks → BUILD SUCCESSFUL
|
|
- 169 unit tests across 28 suites, 0 failures/errors
|
|
- 0 lint errors
|
|
git diff --check → clean (CRLF notice only)
|
|
sensitive-pattern scan of changed files → no matches
|
|
```
|
|
|
|
Known limitations:
|
|
- No physical device was available to verify the filter chip row or an account with real multiple-organization membership interactively.
|
|
- Organization membership itself (which orgs the account belongs to) is read once per session and not refreshed mid-session if the user's org membership changes on the server; a manual logout/login or app restart picks up changes. This matches the app's existing no-background-refresh posture elsewhere.
|
|
|
|
No open P0-P3 findings from self-review. Requesting Codex read-only review.
|