Skip to content

Admin API

Admin-only endpoints under /api/v1/admin.

Access Control

Admin endpoints require: 1. Valid JWT token 2. Admin or owner role in the organization

// Role check in useDevices hook
const { isAdmin } = useRole();
if (!isAdmin) {
  throw new Error("Admin access required");
}

Devices

List Devices

GET /api/v1/admin/devices

Response:

{
  "devices": [
    {
      "id": "device-123",
      "name": "Main Camera",
      "type": "camera",
      "organizationId": "org-456",
      "teamId": "team-789",
      "status": "active",
      "metadata": {
        "resolution": "1920x1080",
        "fps": 30
      },
      "createdAt": "2024-01-15T10:00:00Z",
      "updatedAt": "2024-01-15T10:00:00Z",
      "deletedAt": null
    }
  ]
}

Register Device

POST /api/v1/admin/devices/register

Body:

{
  "name": "Main Camera",
  "type": "camera",
  "organizationId": "org-456",
  "teamId": "team-789",
  "metadata": {
    "resolution": "1920x1080",
    "fps": 30
  }
}

Response:

{
  "device": {
    "id": "device-123",
    "name": "Main Camera",
    "type": "camera",
    "status": "active",
    ...
  }
}

Update Device

PATCH /api/v1/admin/devices/{deviceId}

Body:

{
  "name": "Updated Camera Name",
  "metadata": {
    "resolution": "3840x2160",
    "fps": 60
  }
}

Delete Device (Soft Delete)

PATCH /api/v1/admin/devices/{deviceId}/delete

Sets deletedAt timestamp for soft delete.

Response:

{
  "device": {
    "id": "device-123",
    "deletedAt": "2024-01-15T12:00:00Z",
    ...
  }
}

useDevices Hook

hooks/use-devices.ts

const {
  devices,         // Device[]
  isLoading,
  registerDevice,  // (data) => Promise<Device>
  updateDevice,    // (id, data) => Promise<void>
  deleteDevice     // (id) => Promise<void>
} = useDevices();

Implementation

export function useDevices() {
  const { token } = useAuth();
  const queryClient = useQueryClient();

  const basePath = `${API_BASE_URL}/api/v1/admin/devices`;

  // List devices
  const { data: devices, isLoading } = useQuery({
    queryKey: ["devices"],
    queryFn: async () => {
      const response = await fetch(basePath, {
        headers: { Authorization: `Bearer ${token}` }
      });
      return response.json();
    },
    enabled: Boolean(token)
  });

  // Register device
  const registerMutation = useMutation({
    mutationFn: async (data: RegisterDeviceData) => {
      logger.info("Registering device", { data });
      const response = await fetch(`${basePath}/register`, {
        method: "POST",
        headers: {
          Authorization: `Bearer ${token}`,
          "Content-Type": "application/json"
        },
        body: JSON.stringify(data)
      });
      return response.json();
    },
    onSuccess: (device) => {
      logger.info("Device registered", { deviceId: device.id });
      toast.success("Device registered");
      queryClient.invalidateQueries(["devices"]);
    },
    onError: (error) => {
      logger.error("Device registration failed", { error });
      toast.error("Failed to register device");
    }
  });

  // Update device
  const updateMutation = useMutation({
    mutationFn: async ({ id, data }: UpdateDeviceParams) => {
      logger.info("Updating device", { id, data });
      const response = await fetch(`${basePath}/${id}`, {
        method: "PATCH",
        headers: {
          Authorization: `Bearer ${token}`,
          "Content-Type": "application/json"
        },
        body: JSON.stringify(data)
      });
      return response.json();
    },
    onSuccess: () => {
      logger.info("Device updated");
      toast.success("Device updated");
      queryClient.invalidateQueries(["devices"]);
    },
    onError: (error) => {
      logger.error("Device update failed", { error });
      toast.error("Failed to update device");
    }
  });

  // Delete device
  const deleteMutation = useMutation({
    mutationFn: async (id: string) => {
      logger.info("Deleting device", { id });
      const response = await fetch(`${basePath}/${id}/delete`, {
        method: "PATCH",
        headers: { Authorization: `Bearer ${token}` }
      });
      return response.json();
    },
    onSuccess: () => {
      logger.info("Device deleted");
      toast.success("Device deleted");
      queryClient.invalidateQueries(["devices"]);
    },
    onError: (error) => {
      logger.error("Device deletion failed", { error });
      toast.error("Failed to delete device");
    }
  });

  return {
    devices: devices?.devices || [],
    isLoading,
    registerDevice: registerMutation.mutateAsync,
    updateDevice: (id: string, data: UpdateDeviceData) => 
      updateMutation.mutateAsync({ id, data }),
    deleteDevice: deleteMutation.mutateAsync
  };
}

Logging

All admin operations are logged for audit:

import { logger } from "@/lib/logger-client";

// On success
logger.info("Device registered", { 
  deviceId: device.id,
  deviceType: device.type 
});

// On error
logger.error("Device registration failed", { 
  error: error.message,
  attemptedData: data 
});

UI Components

TeamDevicesTable

components/tables/teams/team-devices-table.tsx

Displays devices for a team with actions: - View details - Edit device - Delete device

RegisterDeviceForm

components/forms/admin/register-device-form.tsx

Form for registering new devices: - Name - Type (camera, sensor, etc.) - Team assignment - Metadata fields