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).
71 lines
2.2 KiB
PHP
71 lines
2.2 KiB
PHP
<?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,
|
|
]
|
|
);
|
|
}
|
|
}
|