# LEARNINGS.md - Neo's Insights

## 2026-05-19 - Issue 5-8 Implementation

### Issue 5: Backend Connection/Repository Validation API
- Created `/backend/app/schemas/forgejo_validation.py` with validation response schemas
- Added POST `/api/v1/forgejo/connections/{connection_id}/validate` endpoint
- Added POST `/api/v1/forgejo/repositories/{repository_id}/validate` endpoint
- Validation tests authentication with Forgejo API
- Errors are safe for display (no tokens exposed)

### Issue 6: Cached Issue Database Model
- Created `/backend/app/models/forgejo_issues.py` with ForgejoIssue model
- Created `/backend/app/schemas/forgejo_issues.py` with read/create/list schemas
- Created migration `/backend/migrations/versions/a1b2c3d4e5f7_add_forgejo_issues.py`
- Model includes JSON fields for labels and assignees using `sa_column=Column(JSON)`
- Unique constraint on (repository_id, forgejo_issue_number)

### Issue 7: Issue Sync Service and Manual Sync API
- Created `/backend/app/services/forgejo_issue_sync.py` with IssueSyncService
- Service fetches issues via ForgejoAPIClient with pagination
- Handles upsert of issues into database
- Excludes pull requests from sync (type=issues filter)
- Updates repository.last_sync_at on success
- Updates repository.last_sync_error on failure
- Admin-only endpoint at POST `/api/v1/forgejo/repositories/{repository_id}/sync`

### Issue 8: Human Issue List and Read APIs
- Created `/backend/app/api/forgejo_issues.py` with issue endpoints
- GET `/api/v1/forgejo/issues` - paginated list with filters:
  - repository_id, state, label, assignee, search text
- GET `/api/v1/forgejo/issues/{issue_id}` - single issue read
- Cross-organization access returns 404
- Pull requests excluded from responses (sync service filters them)

### Errors Encountered
- Import path truncated in forgejo_connections.py - fixed by proper import ordering
- Schema file had duplicate content - rewritten cleanly
- API file corruption during incremental edits - rewritten completely for forgejo_issues.py
- JSON field type error: `TypeError: issubclass() arg 1 must be a class` - caused by using `sa_column=JSON` instead of `sa_column=Column(JSON)`. Fixed by following existing pattern in other models (approvals.py, agents.py, board_onboarding.py)

### Notes
- All endpoints follow existing patterns in forgejo_connections.py and forgejo_repositories.py
- Auth context via require_org_admin decorator ensures organization isolation
- SQLModel relationships cannot be set directly - use fetch + assign pattern
- JSON fields use `sa_column=Column(JSON)` for SQLAlchemy JSON type
- Issue sync excludes pull requests with `if issue_data.get("pull_request") is not None: continue`
