Add debug CLI design doc
This commit is contained in:
parent
81ff9d9e41
commit
01e928151a
1 changed files with 106 additions and 0 deletions
106
docs/plans/2026-02-22-debug-cli-design.md
Normal file
106
docs/plans/2026-02-22-debug-cli-design.md
Normal file
|
|
@ -0,0 +1,106 @@
|
||||||
|
# Debug CLI Design
|
||||||
|
|
||||||
|
**Date:** 2026-02-22
|
||||||
|
**Status:** Approved
|
||||||
|
|
||||||
|
## Goal
|
||||||
|
|
||||||
|
Add a debug CLI that mirrors all web UI interactions, with superuser access to impersonate any user. Purpose: debug flows without authorization overhead.
|
||||||
|
|
||||||
|
## Decisions
|
||||||
|
|
||||||
|
- **Scope:** All API endpoints (listings, decisions, POIs, tasks, districts)
|
||||||
|
- **Execution mode:** Switchable — direct service calls (default) or HTTP to running API (`--http`)
|
||||||
|
- **User identity:** `--user-email` flag to impersonate any user
|
||||||
|
- **Location:** New `cli/` package with submodules, registered under a `debug` group in `main.py`
|
||||||
|
- **Output:** Pretty-printed by default, `--json` flag for machine-readable output
|
||||||
|
|
||||||
|
## Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
cli/
|
||||||
|
__init__.py # Exports all groups for registration in main.py
|
||||||
|
_context.py # Shared context: user identity, HTTP vs direct, output formatting
|
||||||
|
listings.py # list, detail, stream, refresh
|
||||||
|
decisions.py # set, list, remove
|
||||||
|
pois.py # create, list, update, delete, calculate, distances
|
||||||
|
tasks.py # status, list, cancel, clear
|
||||||
|
districts.py # list
|
||||||
|
```
|
||||||
|
|
||||||
|
### Usage
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python main.py debug --user-email user@example.com listings list --type RENT --min-bedrooms 2
|
||||||
|
python main.py debug --user-email user@example.com --http decisions set 12345 liked --type RENT
|
||||||
|
python main.py debug --user-email user@example.com --json pois list
|
||||||
|
```
|
||||||
|
|
||||||
|
## Shared Context (`_context.py`)
|
||||||
|
|
||||||
|
- **`CliContext` dataclass:** Holds `user_email`, `use_http`, `json_output`, `api_base_url`
|
||||||
|
- **`get_user()`:** Creates `User(sub=email, email=email, name=email)` from provided email — no token validation
|
||||||
|
- **Direct mode:** Instantiates repositories and calls service functions, passing the fake `User`
|
||||||
|
- **HTTP mode:** Mints a self-signed JWT using `JWT_SECRET` from env (passkey-style token), makes requests via `httpx`
|
||||||
|
- **Output helpers:** `output(data, json_mode)` — prints JSON or pretty-formatted text
|
||||||
|
|
||||||
|
## Command Mapping
|
||||||
|
|
||||||
|
### `listings` group
|
||||||
|
|
||||||
|
| Command | Direct mode | HTTP mode |
|
||||||
|
|---------|------------|-----------|
|
||||||
|
| `list` | `listing_service.get_listings()` | `GET /api/listing` |
|
||||||
|
| `detail <id>` | Repository + poi_service | `GET /api/listing/{id}/detail` |
|
||||||
|
| `stream` | `export_service.export_to_geojson()` | `GET /api/listing_geojson/stream` |
|
||||||
|
| `refresh` | `listing_service.refresh_listings()` | `POST /api/refresh_listings` |
|
||||||
|
|
||||||
|
### `decisions` group
|
||||||
|
|
||||||
|
| Command | Direct mode | HTTP mode |
|
||||||
|
|---------|------------|-----------|
|
||||||
|
| `set <id> <liked\|disliked>` | `decision_service.set_decision()` | `PUT /api/decisions/{id}` |
|
||||||
|
| `list` | `decision_service.get_user_decisions()` | `GET /api/decisions` |
|
||||||
|
| `remove <id>` | `decision_service.remove_decision()` | `DELETE /api/decisions/{id}` |
|
||||||
|
|
||||||
|
### `pois` group
|
||||||
|
|
||||||
|
| Command | Direct mode | HTTP mode |
|
||||||
|
|---------|------------|-----------|
|
||||||
|
| `list` | `poi_service.get_user_pois()` | `GET /api/poi` |
|
||||||
|
| `create` | `poi_service.create_poi()` | `POST /api/poi` |
|
||||||
|
| `update <id>` | `poi_service.update_poi()` | `PUT /api/poi/{id}` |
|
||||||
|
| `delete <id>` | `poi_service.delete_poi()` | `DELETE /api/poi/{id}` |
|
||||||
|
| `calculate <id>` | `poi_service.trigger_calculation()` | `POST /api/poi/{id}/calculate` |
|
||||||
|
| `distances` | `poi_service.get_distances_for_listing()` | `GET /api/poi/distances` |
|
||||||
|
|
||||||
|
### `tasks` group
|
||||||
|
|
||||||
|
| Command | Direct mode | HTTP mode |
|
||||||
|
|---------|------------|-----------|
|
||||||
|
| `status <id>` | `task_service.get_task_status()` | `GET /api/task_status` |
|
||||||
|
| `list` | `task_service.get_user_tasks()` | `GET /api/tasks_for_user` |
|
||||||
|
| `cancel <id>` | `task_service.cancel_task()` | `POST /api/cancel_task` |
|
||||||
|
| `clear` | `task_service.clear_all_tasks()` | `POST /api/clear_all_tasks` |
|
||||||
|
|
||||||
|
### `districts` group
|
||||||
|
|
||||||
|
| Command | Direct mode | HTTP mode |
|
||||||
|
|---------|------------|-----------|
|
||||||
|
| `list` | `district_service.get_all_districts()` | `GET /api/get_districts` |
|
||||||
|
|
||||||
|
## Error Handling
|
||||||
|
|
||||||
|
- **Direct mode:** Catch service exceptions, print readable messages (or `{"error": "..."}` in JSON mode)
|
||||||
|
- **HTTP mode:** Print status code + response body on non-2xx
|
||||||
|
- **Missing services:** Fail fast with clear message about which service (DB, Redis) is unavailable
|
||||||
|
|
||||||
|
## Testing
|
||||||
|
|
||||||
|
- Unit tests for `_context.py` (JWT minting, user creation, output formatting)
|
||||||
|
- Integration tests for representative commands in direct mode (mocked repositories)
|
||||||
|
- No HTTP-mode tests (that's API testing, already covered)
|
||||||
|
|
||||||
|
## Dependencies
|
||||||
|
|
||||||
|
No new dependencies. Uses existing: `click`, `httpx`, `pyjwt`.
|
||||||
Loading…
Add table
Add a link
Reference in a new issue