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

5.2 KiB

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

  • 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
    
  • 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
    
  • 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
  • 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:
    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:
    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
    
  • 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:
      <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:
      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
    
  • 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

  • go build ./... passes
  • vue-tsc --noEmit passes
  • POST /api/v1/peers returns 201, not 500
  • Devices.vue has a delete button per row that works
  • Existing empty HWIDs migrated to NULL