CRUD зон/устройств/правил автоматизации + RBAC owner/viewer
Policies (ZonePolicy/DevicePolicy/AutomationRulePolicy): не multi-tenant изоляция по user_id, а household-wide RBAC — все авторизованные видят одни и те же данные, только owner может создавать/менять/удалять. user_id на записи — это created_by, а не граница видимости. Form Requests с authorize() через политики; DeviceRequest учитывает unique external_id с ignore на update. AutomationRuleRequest: action_type остаётся строкой (не enum), т.к. это открытый список из device_types.capabilities; для действия set_level в форме есть отдельное поле "level" (Alpine.js переключает его видимость), которое контроллер мапит в action_params — без сырого JSON-редактора для MVP. Контроллеры без show (index+create/edit достаточно), Blade-формы на базе Breeze-компонентов. Навигация и дашборд (счётчики зон/устройств/активных правил) обновлены. Добавлен DemoGrowboxSeeder (демо-зона с датчиком, вентилятором и правилом) и viewer-пользователь в сидер. Восстановлен трейт AuthorizesRequests в базовом Controller (Laravel 11+ убрал его по умолчанию) — нужен для $this->authorize(). Проверено: 33/33 теста (включая новые ZoneControllerTest, AutomationRuleControllerTest — маппинг level→action_params, 403 для viewer), полный CRUD-цикл вживую через браузер под owner (создание зоны, правила с set_level, Alpine-переключение поля "Уровень"), доступ viewer подтверждён как read-only (нет кнопок изменения, прямой заход на /zones/create отдаёт 403).
This commit is contained in:
@@ -0,0 +1,70 @@
|
||||
<?php
|
||||
|
||||
namespace Database\Seeders;
|
||||
|
||||
use App\Models\AutomationRule;
|
||||
use App\Models\Device;
|
||||
use App\Models\DeviceType;
|
||||
use App\Models\User;
|
||||
use App\Models\Zone;
|
||||
use Illuminate\Database\Seeder;
|
||||
|
||||
/**
|
||||
* Demo data for the growbox zone — the first zone implemented on the
|
||||
* platform. Purely example content so the CRUD screens aren't empty on
|
||||
* first run; nothing here is special-cased in application code.
|
||||
*/
|
||||
class DemoGrowboxSeeder extends Seeder
|
||||
{
|
||||
public function run(): void
|
||||
{
|
||||
$owner = User::where('email', 'owner@example.com')->firstOrFail();
|
||||
|
||||
$zone = Zone::query()->firstOrCreate(
|
||||
['name' => 'Гроубокс'],
|
||||
['user_id' => $owner->id, 'description' => 'Демо-зона для проверки платформы']
|
||||
);
|
||||
|
||||
$sensorType = DeviceType::where('code', 'sensor_temp_humidity')->firstOrFail();
|
||||
$fanType = DeviceType::where('code', 'fan')->firstOrFail();
|
||||
|
||||
$sensor = Device::query()->firstOrCreate(
|
||||
['external_id' => 'sensor-1'],
|
||||
[
|
||||
'user_id' => $owner->id,
|
||||
'zone_id' => $zone->id,
|
||||
'device_type_id' => $sensorType->id,
|
||||
'name' => 'Датчик температуры',
|
||||
'protocol' => 'mqtt',
|
||||
]
|
||||
);
|
||||
|
||||
$fan = Device::query()->firstOrCreate(
|
||||
['external_id' => 'fan-1'],
|
||||
[
|
||||
'user_id' => $owner->id,
|
||||
'zone_id' => $zone->id,
|
||||
'device_type_id' => $fanType->id,
|
||||
'name' => 'Вентилятор',
|
||||
'protocol' => 'mqtt',
|
||||
]
|
||||
);
|
||||
|
||||
AutomationRule::query()->firstOrCreate(
|
||||
[
|
||||
'condition_source_device_id' => $sensor->id,
|
||||
'target_device_id' => $fan->id,
|
||||
],
|
||||
[
|
||||
'user_id' => $owner->id,
|
||||
'zone_id' => $zone->id,
|
||||
'condition_sensor_type' => 'temperature',
|
||||
'condition_operator' => '>',
|
||||
'condition_value' => 28,
|
||||
'action_type' => 'turn_on',
|
||||
'action_params' => [],
|
||||
'is_active' => true,
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user