chore(demo): скрипт разбивки 5 демо-учёток на 5 изолированных тенантов

Каждый логин (admin/manager1-4) → своя компания/тенант.
Идемпотентный: firstOrCreate + reassign tenant_id.
Запуск: php artisan tinker storage/_demo_split_tenants.php

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Дмитрий
2026-05-20 16:08:08 +03:00
parent 9331465c26
commit c99362a3e5
2 changed files with 120 additions and 3 deletions
+117
View File
@@ -0,0 +1,117 @@
<?php
declare(strict_types=1);
/**
* ВРЕМЕННЫЙ demo-скрипт разбивает 5 тестовых пользователей на 5 отдельных тенантов.
* Каждый логин = своя компания, данные изолированы.
* Идемпотентный: повторный запуск не дублирует тенанты.
* Запуск: php artisan tinker storage/_demo_split_tenants.php
*/
use App\Models\Project;
use App\Models\Tenant;
use App\Models\User;
use Illuminate\Support\Str;
// -------------------------------------------------------------------
// 1. Проверяем исходное состояние
// -------------------------------------------------------------------
$totalBefore = User::count();
$tenantsBefore = Tenant::count();
echo "=== ДО: {$totalBefore} пользователей, {$tenantsBefore} тенант(ов) ===\n\n";
// -------------------------------------------------------------------
// 2. Описание каждого пользователя и его будущего тенанта
// -------------------------------------------------------------------
$accounts = [
[
'email' => 'admin@demo.local',
'tenant_subdomain' => 'demo', // оставляем существующий тенант
'org_name' => null, // null = взять из существующего
'create_new_tenant' => false,
],
[
'email' => 'manager1@demo.local',
'tenant_subdomain' => 'ivan-demo',
'org_name' => 'Компания Ивана',
'create_new_tenant' => true,
],
[
'email' => 'manager2@demo.local',
'tenant_subdomain' => 'anna-demo',
'org_name' => 'Компания Анны',
'create_new_tenant' => true,
],
[
'email' => 'manager3@demo.local',
'tenant_subdomain' => 'petr-demo',
'org_name' => 'Компания Петра',
'create_new_tenant' => true,
],
[
'email' => 'manager4@demo.local',
'tenant_subdomain' => 'mariya-demo',
'org_name' => 'Компания Марии',
'create_new_tenant' => true,
],
];
// -------------------------------------------------------------------
// 3. Создаём тенанты и переназначаем пользователей
// -------------------------------------------------------------------
foreach ($accounts as $a) {
$user = User::query()->where('email', $a['email'])->firstOrFail();
if (! $a['create_new_tenant']) {
// Demo Admin остаётся в tenant "demo"
$tenant = Tenant::query()->where('subdomain', $a['tenant_subdomain'])->firstOrFail();
echo "SKIP {$user->email} → тенант «{$tenant->organization_name}» (id={$tenant->id}) — без изменений\n";
continue;
}
// Создаём новый тенант, если ещё не существует
$tenant = Tenant::query()->firstOrCreate(
['subdomain' => $a['tenant_subdomain']],
[
'organization_name' => $a['org_name'],
'contact_email' => $user->email,
'webhook_token' => Str::random(64),
'timezone' => 'Europe/Moscow',
'locale' => 'ru',
'is_trial' => true,
'api_key_limit' => 5,
]
);
// Переназначаем пользователя в новый тенант
$user->tenant_id = $tenant->id;
$user->save();
echo "OK {$user->email} → новый тенант «{$tenant->organization_name}» (id={$tenant->id}, subdomain={$tenant->subdomain})\n";
}
// -------------------------------------------------------------------
// 4. Итоговый отчёт
// -------------------------------------------------------------------
echo "\n=== ИТОГО: изоляция тенантов ===\n";
$tenants = Tenant::query()
->whereIn('subdomain', ['demo', 'ivan-demo', 'anna-demo', 'petr-demo', 'mariya-demo'])
->orderBy('id')
->get();
foreach ($tenants as $t) {
$users = User::query()->where('tenant_id', $t->id)->pluck('email')->implode(', ');
$projects = Project::query()->where('tenant_id', $t->id)->count();
echo sprintf(
" Тенант %-12s (id=%-2d) — пользователи: %-40s | проектов: %d\n",
$t->subdomain,
$t->id,
$users ?: '(нет)',
$projects
);
}
echo "\nГотово. Каждый логин теперь в отдельной компании.\n";
echo "Пароль для всех: password\n";
+3 -3
View File
@@ -1,6 +1,6 @@
# Brain Status (auto-generated)
Last updated: 2026-05-20T11:34:26.960Z
Last updated: 2026-05-20T13:07:59.792Z
| Контролёр | Состояние | Детали |
|---|---|---|
@@ -8,11 +8,11 @@ Last updated: 2026-05-20T11:34:26.960Z
| C2 Cross-ref consistency | ✅ | [cross-ref-checker] OK — 0 drift in 4 files |
| C3 Observer-of-observer | ✅ | [observer-of-observer] OK — last read 0 week(s) ago |
| C4 Сигнальный статус | ✅ | This file (self-reference) |
| C5 Observer-coverage | ✅ | 16 episode(s) this month · Stop-hook + post-commit OK |
| C5 Observer-coverage | ✅ | 29 episode(s) this month · Stop-hook + post-commit OK |
## Метрики (информационные, не алерты)
- Observer evidence: 16 episodes this month, 0 observer_error markers, 0 PII matches before filter
- Observer evidence: 29 episodes this month, 0 observer_error markers, 26 PII matches before filter
- Legacy v1 episodes (not in factor analysis): 5
- Last /brain-retro: 1 day(s) ago
- Использование узлов: см. `/brain-retro` (раз в спринт). **Неиспользованные узлы — не проблема** (capability-readiness; см. memory `feedback_brain_unused_tools_not_problem` — outside-repo memory store).