# REST API сервера — справочник

Веб-клиент общается с сервером по HTTP+JSON. Все API-запросы идут через `proxy.php` (CORS).

## Политика управления устройствами

Клиент **не управляет устройствами напрямую**. Вся логика — только через скрипты (`ControlScripts`):
- управление устройствами — через **action-скрипты** (`POST /api/v1/scripts/actions/run`)
- реакция на физические события — через **event-хендлеры** в скриптах
- периодические задачи — через **regular-скрипты** (cron)

Прямые вызовы устройств (`/api/v1/devices/action` и т.п.) — служебные endpoint'ы сервера для внутреннего использования, не предназначены для вызова из клиентского кода.

---

## Формат ответов

Все endpoint'ы используют единый формат из `Utils::response_success` / `Utils::response_error`.

**Успех:**
```json
{
  "status": true,
  "data": { ... }
}
```

**Ошибка:**
```json
{
  "status": false,
  "error_alias": "device_not_found",
  "failed_fields": ["device_id"],
  "msg": "Устройство не найдено"
}
```

> **Примечание.** Авторизация (`Authorization: Bearer <token>`) **пока не реализована** на уровне middleware — все endpoint'ы в данный момент открыты. Заголовок описан в спецификации, но проверка не подключена.

---

## Устройства `/api/v1/devices`

### `GET /api/v1/devices/list`
Список всех активных устройств.

**Ответ:**
```json
{
  "status": true,
  "data": {
    "devices": [
      {
        "id": 12,
        "alias": "kitchen_relay",
        "device_type": "relay",
        "device_hard_id": "ecf0a1b5c9d7...",
        "device_mac": "A4:CF:12:9B:3F:D2",
        "device_ip": "192.168.2.42",
        "firmware_version": "1.22 dev",
        "name": "Реле кухни",
        "status": "active",
        "connection_status": "active",
        "last_contact": "2026-04-22 18:35",
        "create_at": "2025-12-01 10:00"
      }
    ],
    "total": 1
  }
}
```

---

### `GET /api/v1/devices/id/{id}`
Данные одного устройства из БД (без запроса к самому устройству).

**Ответ:** `data.device` — поля устройства (те же что в списке выше).

---

### `GET /api/v1/devices/id/{id}/info`
Данные устройства + живой ответ `/about` с самого устройства.

**Ответ:**
```json
{
  "status": true,
  "data": {
    "device": {
      "id": 12,
      "alias": "...",
      "name": "...",
      "description": "...",
      "status": "active",
      "connection_status": "active",
      "last_contact": "...",
      "create_at": "...",
      "device": { /* ответ GET /about с устройства */ }
    }
  }
}
```

---

### `GET /api/v1/devices/id/{id}/status`
Живое состояние устройства — проксирует `GET /status` на само устройство.

**Ответ:**
```json
{
  "status": true,
  "data": {
    "device": {
      "id": 12,
      "alias": "...",
      "device_response": { /* ответ GET /status с устройства */ }
    }
  }
}
```

---

### `GET /api/v1/devices/scanning/setup`
Сканировать сеть и вернуть только устройства в режиме `setup` (ещё не добавленные).  
Параллельный curl_multi по диапазону IP из конфига (`192.168.2.2–192.168.2.254`).

**Ответ:** `data.devices` — массив ответов `/about` найденных устройств.

---

### `GET /api/v1/devices/scanning/all`
Сканировать сеть и вернуть все найденные устройства (любой статус).

---

### `POST /api/v1/devices/setup/new-device`
Добавить устройство. Сервер запрашивает `/about`, сохраняет в БД, генерирует токен, отправляет его устройству через `/set_token`, задаёт имя через `/set_device_name`.

**Тело:**
```json
{
  "device_ip": "192.168.2.42",
  "alias": "kitchen_relay",
  "name": "Реле кухни",
  "description": "..."
}
```

**Ответ (успех):** `data.device` — объект нового устройства.

**Ответ (ошибка):** `error_alias` — `invalid_ip` | `empty_field` | `alias_already_exists` | `device_not_found` | `device_mode_error` | `db_error`

---

### `POST /api/v1/devices/action`
Выполнить действие на устройстве — проксирует `POST /action` на само устройство.

**Тело:**
```json
{
  "device_id": 12,
  "action": "toggle_channel",
  "params": { "channel": 0 }
}
```

**Ответ (успех):**
```json
{
  "status": true,
  "data": {
    "device": {
      "id": 12,
      "alias": "...",
      "device_response": { /* ответ устройства */ }
    }
  }
}
```

---

### `POST /api/v1/devices/update-name`
Обновить имя устройства (в БД + на самом устройстве через `/set_device_name`).

**Тело:** `{ "device_id": 12, "name": "Новое имя" }`

---

### `POST /api/v1/devices/update-description`
Обновить описание устройства (только в БД).

**Тело:** `{ "device_id": 12, "description": "..." }`

---

### `POST /api/v1/devices/update-alias`
Обновить alias устройства (только в БД). **Осторожно:** сломает скрипты, использующие старый alias.

**Тело:** `{ "device_id": 12, "new_alias": "new_alias_name" }`

---

### `POST /api/v1/devices/place-in-area`
Поместить устройство в область.

**Тело:** `{ "target_id": 12, "place_in_area_id": 3 }`

---

### `GET /api/v1/devices/id/{id}/unassign-from-area`
Отвязать устройство от области (сделать «без области»).

---

### `POST /api/v1/devices/resetup`
Переустановить токен устройства (перепривязка).

**Тело:** `{ "device_id": 12 }`

---

### `POST /api/v1/devices/reset`
Сбросить устройство к заводским настройкам (вызывает `POST /reset` на устройстве).

**Тело:** `{ "device_id": 12 }`

---

### `GET /api/v1/devices/id/{id}/reboot`
Перезагрузить устройство.

---

### `GET /api/v1/devices/id/{id}/remove`
Удалить устройство: сбрасывает устройство (`POST /reset`), деактивирует токен, помечает запись как `removed` в БД.

---

## Области `/api/v1/areas`

Области — иерархическая структура физических пространств (комнаты, этажи, здания). Устройства и скрипты можно размещать в областях.

### `GET /api/v1/areas/list`
Список всех областей.

**Ответ:** `data.areas` — массив объектов AREA, `data.total`.

```json
{
  "id": 2,
  "type": "room",
  "alias": "kitchen",
  "display_name": "Кухня",
  "parent_area_id": 0
}
```

---

### `GET /api/v1/areas/id/{area_id}/list`
Список дочерних областей указанной области.

---

### `POST /api/v1/areas/new-area`
Создать новую область.

**Тело:** `{ "type": "room", "alias": "kitchen", "display_name": "Кухня" }`

**Ответ:** `data.alias`, `data.area`.

**Ошибки:** `alias_already_exists` | `empty_field` (поля `type`, `display_name`)

---

### `GET /api/v1/areas/id/{area_id}/remove`
Удалить область. Все устройства и дочерние области внутри — отвязываются (но не удаляются). Нельзя удалить area_id ≤ 1.

---

### `POST /api/v1/areas/place-in-area`
Вложить одну область в другую.

**Тело:** `{ "target_id": 5, "place_in_area_id": 2 }`

---

### `GET /api/v1/areas/id/{area_id}/unassign-from-area`
Отвязать область от родительской (поднять на верхний уровень).

---

### `POST /api/v1/areas/update-display-name`
Переименовать область.

**Тело:** `{ "area_id": 2, "display_name": "Кухня (1 этаж)" }`

---

### `POST /api/v1/areas/update-alias`
Изменить alias области. **Осторожно:** сломает скрипты, использующие старый alias.

**Тело:** `{ "area_id": 2, "new_alias": "kitchen_floor_1" }`

---

### `GET /api/v1/areas/id/{area_id}/devices`
Список устройств в области (включая вложенные области).

**Ответ:** `data.devices` — массив устройств, `data.total`.

---

### `GET /api/v1/areas/id/{area_id}/scripts`
Список скриптов, размещённых в области (action + regular).

**Ответ:** `data.scripts`, `data.total`.

---

### `GET /api/v1/areas/id/{area_id}/reboot_devices`
### `GET /api/v1/areas/reboot_devices`
Перезагрузить все устройства в области (или все устройства системы).

**Ответ:**
```json
{
  "status": true,
  "data": {
    "results": {
      "kitchen:relay_1": { /* ответ от устройства */ }
    },
    "total": 1
  }
}
```

---

### `GET /api/v1/areas/types/list`
Список всех существующих типов областей в БД (для подсказок при создании новой).

**Ответ:** `data.types` — массив строк, например `["room", "floor", "building"]`.

---

## Скрипты `/api/v1/scripts`

Управление тремя типами скриптов: **action** (ручной запуск), **regular** (cron), **scope** (PHP-класс, контейнер для action + regular).

### `GET /api/v1/scripts/actions/list`
Список всех зарегистрированных action-скриптов с их состоянием (enabled/disabled).

**Ответ:**
```json
{
  "status": true,
  "data": {
    "scripts": [
      {
        "alias": "kitchen_light_toggle",
        "name": "Свет на кухне",
        "icon": "<i class=\"ph ph-lightbulb\"></i>",
        "description": "...",
        "author": "Eugene Sukhodolskiy",
        "state": "enabled",
        "filename": "LightHubScope.php",
        "path": "/srv/http/smart-home-serv.local/server/ControlScripts"
      }
    ],
    "total": 3
  }
}
```

---

### `GET /api/v1/scripts/regular/list`
Список всех зарегистрированных regular-скриптов.

---

### `GET /api/v1/scripts/scopes/list`
Список всех Scope-классов с их состоянием.

**Ответ:** `data.scopes` — массив `{ name, filename, state, path }`, `data.total`.

---

### `GET /api/v1/scripts/scopes/name/{name}`
Получить исходный код PHP-файла Scope. Возвращает raw PHP-код (не JSON).

---

### `POST /api/v1/scripts/actions/run`
Запустить action-скрипт вручную.

**Тело:**
```json
{
  "alias": "kitchen_light_toggle",
  "params": {}
}
```

**Ответ:**
```json
{
  "status": true,
  "data": {
    "return": {
      "result": { /* возвращаемое скриптом */ },
      "exec_time": "0.042 seconds"
    }
  }
}
```

**Ошибка:** `action_script_not_found` (если alias не существует или скрипт disabled)

---

### `GET /api/v1/scripts/actions/alias/{alias}/enable`
### `GET /api/v1/scripts/actions/alias/{alias}/disable`
Включить / выключить action-скрипт (записывает состояние в БД).

---

### `GET /api/v1/scripts/regular/alias/{alias}/enable`
### `GET /api/v1/scripts/regular/alias/{alias}/disable`
Включить / выключить regular-скрипт.

---

### `GET /api/v1/scripts/actions/scope/{name}/enable`
### `GET /api/v1/scripts/actions/scope/{name}/disable`
Включить / выключить весь Scope (все его скрипты перестают регистрироваться при следующем старте сервера).

---

### `POST /api/v1/scripts/scopes/update`
Перезаписать содержимое PHP-файла Scope.

**Тело:** `{ "name": "LightHubScope", "path": "/srv/.../ControlScripts", "file": "<?php ..." }`

---

### `POST /api/v1/scripts/place-in-area`
Поместить скрипт в область.

**Тело:** `{ "target_id": 5, "place_in_area_id": 2 }`

---

### `GET /api/v1/scripts/id/{id}/unassign-from-area`
Отвязать скрипт от области.

---

## Общее

### `GET /api/v1/status`
Статус сервера.

```json
{
  "status": "active",
  "docs": "https://git.gnexus.space/root/smart-home-server/tree/master/docs"
}
```

---

## События от устройств

### `POST /events/new`
Endpoint для устройств (не для клиента). Принимает событие, немедленно отвечает `200 OK`, затем асинхронно запускает обработчики из Control Scripts.

**Тело (от устройства):**
```json
{
  "device_id": "ecf0a1b5c9d74f9a8e294c1f67b0a8b9",
  "event_name": "press",
  "data": { "channel": 0 }
}
```

Требует `Authorization: Bearer <device_token>`.

---

## Cron-маршруты

Не для клиента — вызываются планировщиком на сервере.

| Endpoint | Действие |
|----------|---------|
| `GET /cron/regular-scripts` | Запустить все enabled regular-скрипты |
| `GET /cron/status-update-scanning` | Сканировать сеть, обновить `connection_status` и `device_ip` в БД |

---

## Запланировано, не реализовано

Следующие разделы описаны в спецификациях (`docs/server-api-v1/`), но ещё не реализованы:

| Раздел | Файл спеки |
|--------|-----------|
| Авторизация | `auth.md` |
| Пользователи | `users.md` |
| Группы пользователей и права | `groups.md` |
| Логи | `logs.md` |
| Уведомления | `notifications.md` |
