diff --git a/MEMBER_EDITOR_PLAN.md b/MEMBER_EDITOR_PLAN.md new file mode 100644 index 0000000..51e5dd1 --- /dev/null +++ b/MEMBER_EDITOR_PLAN.md @@ -0,0 +1,548 @@ +# Member Editor Implementation Plan + +## Overview + +Create a Member Editor admin interface at `/a/members` following the proven pattern established by the Tag Editor. All backend API infrastructure already exists; this is purely a web UI implementation. + +## Current State + +### ✅ Already Implemented + +| Component | Status | Location | +|-----------|--------|----------| +| Member Model | ✅ Complete | `common/models/member.py` | +| API Schemas | ✅ Complete | `common/schemas/members.py` | +| API CRUD Endpoints | ✅ Complete | `api/routes/members.py` | +| YAML Import | ✅ Complete | `collector/member_import.py` | +| Public Members Page | ✅ Complete | `web/routes/members.py` | +| Admin Foundation | ✅ Complete | `web/routes/admin.py` | + +### ❌ Missing - To Be Implemented + +1. Admin web routes for Member CRUD at `/a/members` +2. Admin template `admin/members.html` +3. Navigation card in admin index + +## Architecture Reference + +The Member Editor will follow the **exact same pattern** as the Tag Editor: + +``` +User visits /a/members + ↓ +Displays members table with actions + ↓ +User clicks: Create | Edit | Delete + ↓ +Modal opens with form + ↓ +Form submits via POST to /a/members/{action} + ↓ +Backend calls API endpoint + ↓ +Redirects back to /a/members with flash message +``` + +## Implementation Tasks + +### Task 1: Add Admin Web Routes + +**File:** `src/meshcore_hub/web/routes/admin.py` + +Add the following routes following the Tag Editor pattern: + +#### 1.1 Main Members Page (GET) +```python +@router.get("/members", response_class=HTMLResponse) +async def admin_members( + request: Request, + message: Optional[str] = Query(None), + error: Optional[str] = Query(None) +) -> HTMLResponse +``` + +**Responsibilities:** +- Check admin enabled via `_check_admin_enabled(request)` +- Get auth context via `_get_auth_context(request)` +- Fetch all members from `/api/v1/members?limit=1000` +- Sort members by name +- Render `admin/members.html` template + +#### 1.2 Create Member (POST) +```python +@router.post("/members", response_class=RedirectResponse) +async def admin_create_member( + request: Request, + name: str = Form(...), + member_id: str = Form(...), + callsign: Optional[str] = Form(None), + role: Optional[str] = Form(None), + description: Optional[str] = Form(None), + contact: Optional[str] = Form(None) +) -> RedirectResponse +``` + +**Responsibilities:** +- Check admin enabled and require auth +- POST to `/api/v1/members` with form data +- Handle success (201) → redirect with success message +- Handle errors (409 duplicate, 400 validation) → redirect with error +- Use `_build_redirect_url()` helper + +#### 1.3 Update Member (POST) +```python +@router.post("/members/update", response_class=RedirectResponse) +async def admin_update_member( + request: Request, + id: str = Form(...), + name: Optional[str] = Form(None), + member_id: Optional[str] = Form(None), + callsign: Optional[str] = Form(None), + role: Optional[str] = Form(None), + description: Optional[str] = Form(None), + contact: Optional[str] = Form(None) +) -> RedirectResponse +``` + +**Responsibilities:** +- Check admin enabled and require auth +- Build update payload (only non-None fields) +- PUT to `/api/v1/members/{id}` with update data +- Handle success (200) → redirect with success message +- Handle errors (404, 409, 400) → redirect with error + +#### 1.4 Delete Member (POST) +```python +@router.post("/members/delete", response_class=RedirectResponse) +async def admin_delete_member( + request: Request, + id: str = Form(...) +) -> RedirectResponse +``` + +**Responsibilities:** +- Check admin enabled and require auth +- DELETE to `/api/v1/members/{id}` +- Handle success (204) → redirect with success message +- Handle errors (404) → redirect with error + +### Task 2: Create Admin Template + +**File:** `src/meshcore_hub/web/templates/admin/members.html` + +Structure based on `admin/node_tags.html`: + +#### 2.1 Page Layout +```html +{% extends "base.html" %} + +{% block title %}Members - Admin{% endblock %} + +{% block breadcrumb %} +
+{% endblock %} + +{% block content %} + + + + +{% endblock %} +``` + +#### 2.2 Flash Messages Section +```html +{% if message %} +| Member ID | +Name | +Callsign | +Role | +Contact | +Actions | +
|---|---|---|---|---|---|
{{ member.member_id }} |
+ {{ member.name }} | ++ {% if member.callsign %} + {{ member.callsign }} + {% endif %} + | +{{ member.role or '-' }} | +{{ member.contact or '-' }} | ++ + + | +
| + No members configured yet + | +|||||
Manage network members and operators
+Manage network members and operators.
+| Member ID | +Name | +Callsign | +Contact | +Actions | +
|---|---|---|---|---|
| {{ member.member_id }} | +{{ member.name }} | ++ {% if member.callsign %} + {{ member.callsign }} + {% else %} + - + {% endif %} + | +{{ member.contact or '-' }} | +
+
+
+
+
+ |
+
No members configured yet.
+Click "Add Member" to create the first member.
+