# 03 — Admin User Management

## Overview

CRUD interface for managing the **ops team** — the rows in the `admins` table. Used by superadmins to add new RMs, coordinators, customer-care reps, etc.

**Status:** Live

---

## User Stories

| ID | As a | I want to | So that |
|----|------|-----------|---------|
| ADM-01 | Superadmin | See a paginated list of all admins with role + status | I know who's on the team |
| ADM-02 | Superadmin | Add a new admin (name, email, phone, password, role, superadmin flag) | I can onboard new ops members |
| ADM-03 | Superadmin | Edit an existing admin's details + reset password | I can update access |
| ADM-04 | Superadmin | Delete an admin | I can offboard team members |
| ADM-05 | Superadmin | Visually distinguish superadmins from role-based admins | I don't accidentally remove privileged access |

---

## Screens & Flows

```
┌─────────────────────┐    ┌───────────────────────────┐
│ /user/adminlist     │    │ /user/ajaxAdminList       │
│ adminlist.blade.php │───▶│  (DataTables source)      │
└──────────┬──────────┘    └───────────────────────────┘
           │
           ├── New Admin ──▶ GET/POST /user/admin
           │                          │
           │                          ▼
           │                ┌────────────────────┐
           │                │ addAdmin.blade.php │
           │                │ (validate + save)  │
           │                └────────────────────┘
           │
           ├── Edit ───────▶ GET/POST /user/admin/{id}
           │                          │
           │                          ▼
           │                ┌────────────────────┐
           │                │ addAdmin.blade.php │ (re-used)
           │                │ (prefilled, save)  │
           │                └────────────────────┘
           │
           └── Delete ─────▶ GET /user/deleteAdmin/{id}
                                      │
                                      ▼
                            ┌────────────────────────┐
                            │ DB::delete WHERE id=$id│
                            │ (raw query)            │
                            └────────────────────────┘
```

### Routes & Actions

| Route | Method | Handler | Description |
|-------|--------|---------|-------------|
| `/user/adminlist` | GET | `UsersController::adminlist()` | List admins (Blade) |
| `/user/ajaxAdminList` | GET | `UsersController::ajaxAdminList()` | DataTable JSON |
| `/user/admin` | GET/POST | `UsersController::addAdmin()` | New admin form |
| `/user/admin/{id}` | GET/POST | `UsersController::editAdmin()` | Edit admin form |
| `/user/deleteAdmin/{id}` | GET | `UsersController::deleteAdmin()` | Delete row |

---

## Data Model

```php
// Eloquent: App\Models\Admin (skeleton — see App\Models\User for the full mapping)
{
  id:         int,
  uuid:       string,                // generated via Str::uuid()
  name:       string,                // required, max 255
  email:      string,                // required, unique on admins.email
  phone:      string|null,           // optional, max 15
  password:   bcrypt-hash,           // Hash::make(plain)
  role_id:    int,                   // 1..8
  superadmin: 0|1|2,                 // 0=normal, 1=superadmin, 2=coordinator
  status:     'active'|'inactive',
  created_at: datetime,
  updated_at: datetime,
}
```

---

## Validations & Business Rules

| Rule | Detail |
|------|--------|
| Email uniqueness | `unique:admins` on create, `unique:admins,email,$adminId` on edit |
| Password required on create | `required \| min:8` |
| Password optional on edit | Only updated if `!empty($request->password)` |
| Phone optional | `nullable \| string \| max:15` |
| Role required | `role_id` — no in-validator check that it's in 1..8 |
| superadmin required | Field is in validation but freely accepts 0/1/2 |
| Default status on create | Hardcoded to `'active'` |
| Delete uses raw SQL with route param | `DB::delete("DELETE FROM admins WHERE id=".$id)` — see "Known Issues" |
| UUID is generated server-side | `Str::uuid()` on creation |

---

## API Endpoints

| Method | Path | Auth | Request | Response | Consumer |
|--------|------|------|---------|----------|----------|
| GET | `/user/adminlist` | session + permission 5 | — | HTML | Browser |
| GET | `/user/ajaxAdminList` | session (AJAX whitelisted) | `draw, start, length` | DataTable JSON | jQuery DataTable on adminlist page |
| GET | `/user/admin` | session + permission 5 | — | HTML | Browser |
| POST | `/user/admin` | session + permission 5 | `name, email, phone?, password, role_id, superadmin` | Redirect to `/user/adminlist` with flash | Form |
| GET | `/user/admin/{id}` | session + permission 5 | path id | HTML | Browser |
| POST | `/user/admin/{id}` | session + permission 5 | `name, email, phone?, password?, role_id, superadmin` | Redirect to `/user/adminlist` with flash | Form |
| GET | `/user/deleteAdmin/{id}` | session + permission 5 | path id | Redirect | Browser link |

---

## Upstream Impact

- **`roles()` helper** — populates the role dropdown in the add/edit form.
- **No upstream data feeds** — admins are typed in manually.

---

## Downstream Impact

- **Feature 01 (Authentication)** — every row here is a login credential.
- **Feature 02 (RBAC)** — `role_id` here points at `role_permissions.role_id` lookup.
- **Feature 16 (Notifications)** — `Notification.created_by` references admin id.
- **Feature 20 (User Activity Log)** — `viso_user_activity_log.user_id` references admin id.
- **Feature 09 (RM Create Event)** — `RmController` filters by `auth()->user()->country_code`, set on this admin row.

---

## Impact of Changes

| If you change... | Risk to... | Level | Type |
|-----------------|------------|-------|------|
| `admins` table column names (`name`, `email`, `phone`, `role_id`, `superadmin`, `status`, `password`) | Auth + all admin pages | Critical | Data |
| Removing the `'active'` default in `addAdmin` | New admins might be blocked from login if your guard checks status | High | Data |
| Making `superadmin` a stricter enum / boolean | Coordinator (`= 2`) and full superadmin (`= 1`) collapse | Critical | Data |
| Renaming `addAdmin.blade.php` | Both add + edit flows break (shared template) | High | UI |
| Switching `deleteAdmin` to soft-delete | All other features still see deleted admin in joins (e.g. activity log) | Medium | Data |
| Allowing self-registration via `/register` | Anyone can create an admin row — see [01 Authentication](../01-admin-authentication/spec.md) Known Issues | Critical | Guard |

---

## Known Issues

- **SQL injection vector**: `deleteAdmin` does `DB::delete("DELETE FROM admins WHERE id=".$id)`. `$id` arrives from `$request->id`, which is the URL `{id}` placeholder bound to `$request->id` because Laravel populates Request inputs from the route params. A crafted URL like `/user/deleteAdmin/1 OR 1=1` would execute. **Fix owed**.
- **No "force change password" flow**. Newly created admins know their plaintext password (the one entered in the form). Until they change it manually, it persists.
- **Email is the login key, but** delete cascades nothing — orphan references to `viso_user_activity_log.user_id`, `notifications.created_by` will persist.
- **The route `/user/admin` is dual-purpose**: GET and POST both routed at the same place. Mixing intent is brittle — modal-based UX could regress this.
