refactor: complete Task 2/3 geo decouple + exceptions centralization; mark as done
This commit is contained in:
308
Docs/Tasks.md
308
Docs/Tasks.md
@@ -2,6 +2,314 @@
|
||||
|
||||
This document breaks the entire BanGUI project into development stages, ordered so that each stage builds on the previous one. Every task is described in prose with enough detail for a developer to begin work. References point to the relevant documentation.
|
||||
|
||||
Reference: `Docs/Refactoring.md` for full analysis of each issue.
|
||||
|
||||
---
|
||||
|
||||
## Open Issues
|
||||
|
||||
---
|
||||
|
||||
### Task 1 — Extract shared private functions to a utility module (✅ completed)
|
||||
|
||||
**Priority**: High
|
||||
**Refactoring ref**: Refactoring.md §1, §5
|
||||
**Affected files**:
|
||||
- `backend/app/services/ban_service.py` (defines `_get_fail2ban_db_path` ~L117, `_parse_data_json` ~L152, `_ts_to_iso` ~L105)
|
||||
- `backend/app/services/history_service.py` (imports these three private functions from `ban_service`)
|
||||
|
||||
**What to do**:
|
||||
1. Create a new file `backend/app/utils/fail2ban_db_utils.py`.
|
||||
2. Move the three functions `_get_fail2ban_db_path()`, `_parse_data_json()`, and `_ts_to_iso()` from `backend/app/services/ban_service.py` into the new utility file. Rename them to remove the leading underscore (they are now public utilities): `get_fail2ban_db_path()`, `parse_data_json()`, `ts_to_iso()`.
|
||||
3. In `backend/app/services/ban_service.py`, replace the function bodies with imports from the new utility: `from app.utils.fail2ban_db_utils import get_fail2ban_db_path, parse_data_json, ts_to_iso`. Update all internal call sites within `ban_service.py` that reference the old `_`-prefixed names.
|
||||
4. In `backend/app/services/history_service.py`, replace the import `from app.services.ban_service import _get_fail2ban_db_path, _parse_data_json, _ts_to_iso` with `from app.utils.fail2ban_db_utils import get_fail2ban_db_path, parse_data_json, ts_to_iso`. Update all call sites in `history_service.py`.
|
||||
5. Search the entire `backend/` tree for any other references to the old `_`-prefixed names and update them.
|
||||
6. Run existing tests: `cd backend && python -m pytest tests/` — all tests must pass.
|
||||
|
||||
**Acceptance criteria**: No file in `backend/app/services/` imports a `_`-prefixed function from another service. The three functions live in `backend/app/utils/fail2ban_db_utils.py` and are imported from there.
|
||||
|
||||
---
|
||||
|
||||
### Task 2 — Decouple geo-enrichment from services (✅ completed)
|
||||
|
||||
**Priority**: High
|
||||
**Refactoring ref**: Refactoring.md §1
|
||||
**Affected files**:
|
||||
- `backend/app/services/jail_service.py` (lazy imports `geo_service` at ~L860, ~L1047)
|
||||
- `backend/app/services/ban_service.py` (lazy imports `geo_service` at ~L251, ~L392)
|
||||
- `backend/app/services/blocklist_service.py` (lazy imports `geo_service` at ~L343)
|
||||
- `backend/app/services/history_service.py` (TYPE_CHECKING import of `geo_service` at ~L19)
|
||||
- `backend/app/services/geo_service.py` (the service being imported)
|
||||
- Router files that call these services: `backend/app/routers/jails.py`, `backend/app/routers/bans.py`, `backend/app/routers/dashboard.py`, `backend/app/routers/history.py`, `backend/app/routers/blocklist.py`
|
||||
|
||||
**What to do**:
|
||||
1. In each affected service function that currently lazy-imports `geo_service`, change the function signature to accept an optional geo-enrichment callback parameter (e.g., `enrich_geo: Callable | None = None`). The callback signature should match what `geo_service` provides (typically `async def enrich(ip: str) -> GeoInfo | None`).
|
||||
2. Remove all lazy imports of `geo_service` from `jail_service.py`, `ban_service.py`, `blocklist_service.py`, and `history_service.py`.
|
||||
3. In the corresponding router files, import `geo_service` and pass its enrichment function as the callback when calling the service functions. The router layer is where wiring belongs.
|
||||
4. Run existing tests: `cd backend && python -m pytest tests/` — all tests must pass. If tests mock `geo_service` inside a service, update mocks to inject the callback instead.
|
||||
|
||||
**Acceptance criteria**: No service file imports `geo_service` (directly or lazily). Geo-enrichment is injected from routers via callback parameters.
|
||||
|
||||
---
|
||||
|
||||
### Task 3 — Move shared domain exceptions to a central module (✅ completed)
|
||||
|
||||
**Priority**: High
|
||||
**Refactoring ref**: Refactoring.md §1
|
||||
**Affected files**:
|
||||
- `backend/app/services/config_file_service.py` (defines `JailNotFoundError` and other domain exceptions)
|
||||
- `backend/app/services/jail_service.py` (may define or re-export exceptions)
|
||||
- Any service or router that imports exceptions cross-service (e.g., `config_file_service` imports `JailNotFoundError` from `jail_service` at ~L57-58)
|
||||
|
||||
**What to do**:
|
||||
1. Create `backend/app/exceptions.py`.
|
||||
2. Grep the entire `backend/app/services/` directory for all custom exception class definitions (classes inheriting from `Exception` or `HTTPException`). Collect every exception that is imported by more than one module.
|
||||
3. Move those shared exception classes into `backend/app/exceptions.py`.
|
||||
4. Update all import statements across `backend/app/services/`, `backend/app/routers/`, and `backend/app/` to import from `backend/app/exceptions.py`.
|
||||
5. Exception classes used only within a single service may remain in that service file.
|
||||
6. Run existing tests: `cd backend && python -m pytest tests/` — all tests must pass.
|
||||
|
||||
**Acceptance criteria**: `backend/app/exceptions.py` exists and contains all cross-service exceptions. No service imports an exception class from another service module.
|
||||
|
||||
---
|
||||
|
||||
### Task 4 — Split `config_file_service.py` (god module)
|
||||
|
||||
**Priority**: High
|
||||
**Refactoring ref**: Refactoring.md §2
|
||||
**Affected files**:
|
||||
- `backend/app/services/config_file_service.py` (~2232 lines, ~73 functions)
|
||||
- `backend/app/routers/` files that import from `config_file_service`
|
||||
|
||||
**What to do**:
|
||||
1. Read `backend/app/services/config_file_service.py` and categorise every function into one of three domains:
|
||||
- **Jail config** — functions dealing with jail activation, deactivation, listing jail configs
|
||||
- **Filter config** — functions dealing with fail2ban filter files (reading, writing, listing filters)
|
||||
- **Action config** — functions dealing with fail2ban action files (reading, writing, listing actions)
|
||||
2. Create three new service files:
|
||||
- `backend/app/services/jail_config_service.py` — jail-related functions
|
||||
- `backend/app/services/filter_config_service.py` — filter-related functions
|
||||
- `backend/app/services/action_config_service.py` — action-related functions
|
||||
3. Move functions from `config_file_service.py` into the appropriate new file. Any truly shared helpers used across all three domains should remain in `config_file_service.py` (renamed to a shared helper) or move to `backend/app/utils/`.
|
||||
4. Delete `config_file_service.py` once empty (or keep it as a thin re-export layer for backward compatibility during transition).
|
||||
5. Update all imports in `backend/app/routers/` and `backend/app/services/` that referenced `config_file_service`.
|
||||
6. Run existing tests: `cd backend && python -m pytest tests/` — all tests must pass.
|
||||
|
||||
**Acceptance criteria**: No single service file exceeds ~800 lines. The three new files each handle one domain. All routers import from the correct new module.
|
||||
|
||||
---
|
||||
|
||||
### Task 5 — Extract log-preview / regex-test from `config_service.py`
|
||||
|
||||
**Priority**: Medium
|
||||
**Refactoring ref**: Refactoring.md §2
|
||||
**Affected files**:
|
||||
- `backend/app/services/config_service.py` (~1845 lines)
|
||||
- `backend/app/routers/config.py` (routes that call log-preview / regex-test functions)
|
||||
|
||||
**What to do**:
|
||||
1. Read `backend/app/services/config_service.py` and identify all functions related to log-preview and regex-testing (these are distinct from the core socket-based config reading/writing functions).
|
||||
2. Create `backend/app/services/log_service.py`.
|
||||
3. Move the log-preview and regex-test functions into `log_service.py`.
|
||||
4. Update imports in `backend/app/routers/config.py` (or create a new `backend/app/routers/log.py` if the endpoints are logically separate).
|
||||
5. Run existing tests: `cd backend && python -m pytest tests/` — all tests must pass.
|
||||
|
||||
**Acceptance criteria**: `config_service.py` no longer contains log-preview or regex-test logic. `log_service.py` exists and is used by the appropriate router.
|
||||
|
||||
---
|
||||
|
||||
### Task 6 — Rename confusing config service files
|
||||
|
||||
**Priority**: Medium
|
||||
**Refactoring ref**: Refactoring.md §3
|
||||
**Affected files**:
|
||||
- `backend/app/services/config_file_service.py` → rename to `jail_activation_service.py` (or the split modules from Task 4)
|
||||
- `backend/app/services/file_config_service.py` → rename to `raw_config_io_service.py`
|
||||
- All files importing from the old names
|
||||
|
||||
**Note**: This task depends on Task 4 being completed first. If Task 4 splits `config_file_service.py`, this task only needs to rename `file_config_service.py`.
|
||||
|
||||
**What to do**:
|
||||
1. Rename `backend/app/services/file_config_service.py` to `backend/app/services/raw_config_io_service.py`.
|
||||
2. Update all import statements across the codebase (`backend/app/services/`, `backend/app/routers/`, `backend/app/tasks/`, tests) that reference `file_config_service` to reference `raw_config_io_service`.
|
||||
3. Also rename the corresponding router if one exists: check `backend/app/routers/file_config.py` and rename accordingly.
|
||||
4. Run existing tests: `cd backend && python -m pytest tests/` — all tests must pass.
|
||||
|
||||
**Acceptance criteria**: No file named `file_config_service.py` exists. The new name `raw_config_io_service.py` is used everywhere.
|
||||
|
||||
---
|
||||
|
||||
### Task 7 — Remove remaining service-to-service coupling
|
||||
|
||||
**Priority**: Medium
|
||||
**Refactoring ref**: Refactoring.md §1
|
||||
**Affected files**:
|
||||
- `backend/app/services/auth_service.py` (imports `setup_service` at ~L23)
|
||||
- `backend/app/services/config_service.py` (imports `setup_service` at ~L47, lazy-imports `health_service` at ~L891)
|
||||
- `backend/app/services/blocklist_service.py` (lazy-imports `jail_service` at ~L299)
|
||||
|
||||
**What to do**:
|
||||
1. For each remaining service-to-service import, determine why the dependency exists (read the calling code).
|
||||
2. Refactor using one of these strategies:
|
||||
- **Dependency injection**: The router passes the needed data or function from service A when calling service B.
|
||||
- **Shared utility**: If the imported function is a pure utility, move it to `backend/app/utils/`.
|
||||
- **Event / callback**: The service accepts a callback parameter instead of importing another service directly.
|
||||
3. Remove all direct and lazy imports between service modules.
|
||||
4. Run existing tests: `cd backend && python -m pytest tests/` — all tests must pass.
|
||||
|
||||
**Acceptance criteria**: Running `grep -r "from app.services" backend/app/services/` returns zero results (no service imports another service). All wiring happens in the router or dependency-injection layer.
|
||||
|
||||
---
|
||||
|
||||
### Task 8 — Update Architecture documentation
|
||||
|
||||
**Priority**: Medium
|
||||
**Refactoring ref**: Refactoring.md §4
|
||||
**Affected files**:
|
||||
- `Docs/Architekture.md`
|
||||
|
||||
**What to do**:
|
||||
1. Read `Docs/Architekture.md` and the actual file listings below.
|
||||
2. Add the following missing items to the appropriate sections:
|
||||
- **Repositories**: Add `fail2ban_db_repo.py` and `geo_cache_repo.py` (in `backend/app/repositories/`)
|
||||
- **Utils**: Add `conffile_parser.py`, `config_parser.py`, `config_writer.py`, `jail_config.py` (in `backend/app/utils/`)
|
||||
- **Tasks**: Add `geo_re_resolve.py` (in `backend/app/tasks/`)
|
||||
- **Services**: Correct the entry that lists `conffile_parser` as a service — it is in `app/utils/`
|
||||
- **Routers**: Add `file_config.py` (in `backend/app/routers/`)
|
||||
3. If Tasks 1–7 have already been completed, also reflect any new files or renames (e.g., `fail2ban_db_utils.py`, `exceptions.py`, the split service files, renamed services).
|
||||
4. Verify no other files exist that are missing from the doc by comparing the doc's file lists against `ls backend/app/*/`.
|
||||
|
||||
**Acceptance criteria**: Every `.py` file under `backend/app/` (excluding `__init__.py` and `__pycache__`) is mentioned in the Architecture doc.
|
||||
|
||||
---
|
||||
|
||||
### Task 9 — Add React Error Boundary to the frontend
|
||||
|
||||
**Priority**: Medium
|
||||
**Refactoring ref**: Refactoring.md §6
|
||||
**Affected files**:
|
||||
- New file: `frontend/src/components/ErrorBoundary.tsx`
|
||||
- `frontend/src/App.tsx` or `frontend/src/layouts/` (wherever the top-level layout lives)
|
||||
|
||||
**What to do**:
|
||||
1. Create `frontend/src/components/ErrorBoundary.tsx` — a React class component implementing `componentDidCatch` and `getDerivedStateFromError`. It should:
|
||||
- Catch any rendering error in its children.
|
||||
- Display a user-friendly fallback UI (e.g., "Something went wrong" message with a "Reload" button that calls `window.location.reload()`).
|
||||
- Log the error (console.error is sufficient for now).
|
||||
2. Read `frontend/src/App.tsx` to find the main layout/route wrapper.
|
||||
3. Wrap the main content (inside `<App>` or `<MainLayout>`) with `<ErrorBoundary>` so that any component crash shows the fallback instead of a white screen.
|
||||
4. Run existing frontend tests: `cd frontend && npx vitest run` — all tests must pass.
|
||||
|
||||
**Acceptance criteria**: An `<ErrorBoundary>` component exists and wraps the application's main content. A component throwing during render shows a fallback UI instead of crashing the whole app.
|
||||
|
||||
---
|
||||
|
||||
### Task 10 — Consolidate duplicated formatting functions (frontend)
|
||||
|
||||
**Priority**: Low
|
||||
**Refactoring ref**: Refactoring.md §7
|
||||
**Affected files**:
|
||||
- `frontend/src/components/BanTable.tsx` (has `formatTimestamp()` ~L103)
|
||||
- `frontend/src/components/jail/BannedIpsSection.tsx` (has `fmtTime()` ~L139)
|
||||
- `frontend/src/pages/JailDetailPage.tsx` (has `fmtSeconds()` ~L152)
|
||||
- `frontend/src/pages/JailsPage.tsx` (has `fmtSeconds()` ~L147)
|
||||
|
||||
**What to do**:
|
||||
1. Create `frontend/src/utils/formatDate.ts`.
|
||||
2. Define three exported functions:
|
||||
- `formatTimestamp(ts: string): string` — consolidation of `formatTimestamp` and `fmtTime`
|
||||
- `formatSeconds(seconds: number): string` — consolidation of the two identical `fmtSeconds` functions
|
||||
3. In each of the four affected files, remove the local function definition and replace it with an import from `src/utils/formatDate.ts`. Adjust call sites if the function name changed.
|
||||
4. Run existing frontend tests: `cd frontend && npx vitest run` — all tests must pass.
|
||||
|
||||
**Acceptance criteria**: No formatting function for dates/times is defined locally in a component or page file. All import from `src/utils/formatDate.ts`.
|
||||
|
||||
---
|
||||
|
||||
### Task 11 — Create generic `useConfigItem<T>` hook (frontend)
|
||||
|
||||
**Priority**: Low
|
||||
**Refactoring ref**: Refactoring.md §8
|
||||
**Affected files**:
|
||||
- `frontend/src/hooks/useFilterConfig.ts` (~91 lines)
|
||||
- `frontend/src/hooks/useActionConfig.ts` (~88 lines)
|
||||
- `frontend/src/hooks/useJailFileConfig.ts` (~76 lines)
|
||||
|
||||
**What to do**:
|
||||
1. Read all three hook files. Identify the common pattern: load item via fetch → store in state → expose save function → handle abort controller cleanup.
|
||||
2. Create `frontend/src/hooks/useConfigItem.ts` with a generic hook:
|
||||
```ts
|
||||
function useConfigItem<T>(
|
||||
fetchFn: (signal: AbortSignal) => Promise<T>,
|
||||
saveFn: (data: T) => Promise<void>
|
||||
): { data: T | null; loading: boolean; error: string | null; save: (data: T) => Promise<void> }
|
||||
```
|
||||
3. Rewrite `useFilterConfig.ts`, `useActionConfig.ts`, and `useJailFileConfig.ts` to be thin wrappers around `useConfigItem<T>` — each file should be <20 lines, just providing the specific fetch/save functions.
|
||||
4. Run existing frontend tests: `cd frontend && npx vitest run` — all tests must pass.
|
||||
|
||||
**Acceptance criteria**: `useConfigItem.ts` exists. The three original hooks use it and each reduced to <20 lines of domain-specific glue.
|
||||
|
||||
---
|
||||
|
||||
### Task 12 — Standardise error handling in frontend hooks
|
||||
|
||||
**Priority**: Low
|
||||
**Refactoring ref**: Refactoring.md §9
|
||||
**Affected files**:
|
||||
- All hook files in `frontend/src/hooks/` that do fetch calls (at least: `useHistory.ts`, `useMapData.ts`, `useBans.ts`, `useBlocklist.ts`, and others)
|
||||
|
||||
**What to do**:
|
||||
1. Create a utility function in `frontend/src/utils/fetchError.ts`:
|
||||
```ts
|
||||
export function handleFetchError(err: unknown, setError: (msg: string | null) => void): void {
|
||||
if (err instanceof DOMException && err.name === "AbortError") return;
|
||||
setError(err instanceof Error ? err.message : "Unknown error");
|
||||
}
|
||||
```
|
||||
2. Grep all hook files for `catch` blocks. In every hook that catches fetch errors:
|
||||
- Replace the catch body with a call to `handleFetchError(err, setError)`.
|
||||
3. Run existing frontend tests: `cd frontend && npx vitest run` — all tests must pass.
|
||||
|
||||
**Acceptance criteria**: Every hook that fetches data uses `handleFetchError()` in its catch block. No hook surfaces `AbortError` to the UI.
|
||||
|
||||
---
|
||||
|
||||
### Task 13 — Extract sub-components from large frontend pages
|
||||
|
||||
**Priority**: Low
|
||||
**Refactoring ref**: Refactoring.md §11
|
||||
**Affected files**:
|
||||
- `frontend/src/pages/BlocklistsPage.tsx` (~968 lines)
|
||||
- `frontend/src/components/config/JailsTab.tsx` (~939 lines)
|
||||
- `frontend/src/pages/JailsPage.tsx` (~691 lines)
|
||||
- `frontend/src/pages/JailDetailPage.tsx` (~663 lines)
|
||||
|
||||
**What to do**:
|
||||
1. For each large file, identify logical UI sections that can be extracted into their own component files.
|
||||
2. Suggested splits (adjust after reading the actual code):
|
||||
- **BlocklistsPage.tsx**: Extract `<BlocklistSourceList>`, `<BlocklistAddEditDialog>`, `<BlocklistImportLog>`, `<BlocklistScheduleConfig>`.
|
||||
- **JailsTab.tsx**: Extract `<JailConfigEditor>`, `<JailRawConfig>`, `<JailValidation>`, `<JailActivateDialog>`.
|
||||
- **JailsPage.tsx**: Extract `<JailDetailDrawer>`, `<BanUnbanForm>`.
|
||||
- **JailDetailPage.tsx**: Extract logical sections (examine the JSX to identify).
|
||||
3. Place extracted components in appropriate directories (e.g., `frontend/src/components/blocklist/`, `frontend/src/components/jail/`).
|
||||
4. Each parent page should import and compose the new child components. Props should be passed down — avoid prop drilling deeper than 2 levels (use context if needed).
|
||||
5. Run existing frontend tests: `cd frontend && npx vitest run` — all tests must pass.
|
||||
|
||||
**Acceptance criteria**: No single page or component file exceeds ~400 lines. Each extracted component is in its own file.
|
||||
|
||||
---
|
||||
|
||||
### Task 14 — Consolidate duplicated section/card styles (frontend)
|
||||
|
||||
**Priority**: Low
|
||||
**Refactoring ref**: Refactoring.md §12
|
||||
**Affected files**:
|
||||
- 13+ files across `frontend/src/pages/` and `frontend/src/components/` that define identical card/section styles using `makeStyles` with `backgroundColor`, `borderRadius`, `border`, `padding` using Fluent UI tokens.
|
||||
|
||||
**What to do**:
|
||||
1. Grep the frontend codebase for `makeStyles` calls that contain `backgroundColor` and `borderRadius` together. Identify the common pattern.
|
||||
2. Create `frontend/src/theme/commonStyles.ts` with a shared `useCardStyles()` hook that exports the common section/card style class.
|
||||
3. In each of the 13+ files, remove the local `makeStyles` definition for the card/section style and import `useCardStyles` from `commonStyles.ts` instead. Keep any file-specific style overrides local.
|
||||
4. Run existing frontend tests: `cd frontend && npx vitest run` — all tests must pass.
|
||||
|
||||
**Acceptance criteria**: A shared `useCardStyles()` exists in `frontend/src/theme/commonStyles.ts`. At least 10 files import it instead of defining their own card styles.
|
||||
|
||||
Reference in New Issue
Block a user