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¶
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¶
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¶
Body:
Delete Device (Soft Delete)¶
Sets deletedAt timestamp for soft delete.
Response:
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