Files
Nexus-Guard-Suite/.sisyphus/plans/nxg-fix-peers-500-and-delete-device.md
T

155 lines
5.2 KiB
Markdown

# Fix Peers API 500 + Add Delete Device to Devices List
## TL;DR
> **Quick Summary**: Fix `HWID` unique constraint violation causing 500 on POST `/api/v1/peers` by making `HWID` nullable; add delete button to Devices.vue list view
>
> **Deliverables**:
> - Backend: HWID model change `string` → `*string`, fix CreatePeer/CreateDevice
> - Frontend: Delete button in Devices.vue table rows
> - Migration: SQL to convert existing empty HWIDs to NULL + container restart
>
> **Estimated Effort**: Short
> **Parallel Execution**: YES
## Context
### Root Cause
- `Device.HWID` is `string` with `uniqueIndex`. `CreatePeer` sets `HWID: ""` which collides with other devices having empty HWID → SQLSTATE 23505
- Fix: make HWID `*string` (nullable), PostgreSQL treats NULLs as distinct in unique constraints
### Delete Missing
- Backend: `DELETE /devices/:id` exists at `main.go:176`, `devicesHandler.Delete` in `devices.go:235`
- API: `deleteDevice(id)` exists in `devices.ts:50`
- DeviceDetail: has delete button with confirm dialog
- Devices.vue: Actions column only has View, Config, Firewall — no Delete
## TODOs
- [x] 1. Backend: Change HWID to `*string` in Device model
**What to do**:
- Edit `apps/server-core/internal/models/models.go` line 60:
- Change `HWID string \`gorm:"column:hwid;uniqueIndex;size:64"\``
- To `HWID *string \`gorm:"column:hwid;uniqueIndex;size:64"\``
**Must NOT do**:
- Don't change any other fields in the model
**Recommended Agent Profile**: `unspecified-high` (precise Go struct edit)
**QA Scenarios**:
```
Scenario: Verify build after model change
Tool: Bash
Steps:
1. cd apps/server-core && go build ./...
Expected Result: No compilation errors
Evidence: .sisyphus/evidence/task-1-build.txt
```
- [x] 2. Backend: Fix CreatePeer to not set empty HWID
**What to do**:
- Edit `apps/server-core/api/peers.go` — remove `HWID: ""` line (line 96)
- Since `HWID` is now `*string`, the zero value (nil) is correct — it won't violate unique constraint
- Keep `RegTokenHash: ""` as-is (no unique index on that)
**QA Scenarios**:
```
Scenario: Build passes after fix
Tool: Bash
Steps:
1. cd apps/server-core && go build ./...
Expected Result: Clean build
Evidence: .sisyphus/evidence/task-2-build.txt
```
- [x] 3. Backend: Fix CreateDevice to not set empty HWID
**What to do**:
- Edit `apps/server-core/api/devices.go` — verify `Create` function (line 84-89) doesn't set HWID
- It currently doesn't set HWID at all, so with `*string` it will default to nil — correct
- Verify `Delete` function (line 235-255) already works — it does, no changes needed
- [x] 4. Migration: Convert existing empty HWIDs to NULL on server
**What to do**:
- SSH into 172.20.8.191 and run SQL against the postgres container:
```bash
docker exec nexus-guard-suite-postgres-1 psql -U nexusguard -d nexusguard -c "UPDATE devices SET hwid = NULL WHERE hwid = '';"
```
- Then restart server-core container:
```bash
docker restart nexus-guard-suite-server-core-1
```
**QA Scenarios**:
```
Scenario: Verify empty HWIDs are now NULL
Tool: Bash
Steps:
1. docker exec nexus-guard-suite-postgres-1 psql -U nexusguard -d nexusguard -c "SELECT count(*) FROM devices WHERE hwid = '';"
Expected Result: count = 0
Evidence: .sisyphus/evidence/task-4-sql.txt
Scenario: Verify POST /api/v1/peers succeeds
Tool: Bash
Steps:
1. Obtain auth token via POST /api/v1/auth/login with admin credentials
2. POST /api/v1/peers with valid body (name, wg_server_id)
Expected Result: 201 Created (not 500)
Evidence: .sisyphus/evidence/task-4-peers-api.txt
```
- [x] 5. Frontend: Add delete button to Devices.vue list
**What to do**:
- Edit `apps/dashboard-ui/src/views/Devices.vue`
- Add delete button in the Actions column (line 34-39), after Firewall button:
```html
<button @click="handleDelete(device.ID, device.Name)" class="text-red-400 hover:text-red-300 font-bold">Delete</button>
```
- Import `deleteDevice` from `../api/devices`
- Add `handleDelete` function:
```typescript
const handleDelete = async (id: string, name: string) => {
if (!confirm(`Delete device "${name}"? This will destroy its tunnel and all firewall rules.`)) return
try {
await deleteDevice(id)
store.loadDevices()
} catch (err: any) {
alert(err.response?.data?.error || 'Failed to delete device')
}
}
```
**Must NOT do**:
- Don't remove the existing Config/Firewall buttons
**Recommended Agent Profile**: `visual-engineering` (Vue UI)
**QA Scenarios**:
```
Scenario: Build passes
Tool: Bash
Steps:
1. cd apps/dashboard-ui && npx vue-tsc --noEmit
Expected Result: No type errors
Evidence: .sisyphus/evidence/task-5-tsc.txt
```
- [x] 6. Commit and push changes
**What to do**:
- Commit server-core changes
- Commit dashboard-ui changes
- Push both submodules
- Commit/push root submodule pointer
## Success Criteria
- [x] `go build ./...` passes
- [x] `vue-tsc --noEmit` passes
- [x] POST `/api/v1/peers` returns 201, not 500
- [x] Devices.vue has a delete button per row that works
- [x] Existing empty HWIDs migrated to NULL