5560ebbdfd
Первый Eloquent-слой над schema v8.6 + middleware для RLS-фильтрации HTTP-запросов. Pest 19/19 passed (1672 ms): 4 RLS smoke + 8 model smoke + 5 middleware + 2 default. app/app/Models/ (NEW Tenant.php, Project.php; MODIFIED User.php): - Tenant — saas-уровневая модель (БЕЗ RLS, тенант-родитель). Soft Deletes. hasMany Users, Projects. - User — переписан под нашу схему: password_hash вместо password, first_name/last_name вместо name, override getAuthPassword/Name для Laravel auth-интеграции. Soft Deletes. belongsTo Tenant. - Project — tenant-aware с RLS. belongsTo Tenant. app/database/factories/ (NEW TenantFactory, ProjectFactory; MODIFIED UserFactory): - TenantFactory: уникальный subdomain через Str::random + дефолты (timezone Europe/Moscow, locale ru, is_trial true, api_key_limit 5). - UserFactory: tenant_id через Tenant::factory() chain, email unique через faker, password_hash через Hash::make. - ProjectFactory: tenant_id через factory chain, дефолты под schema.sql (region_mask 255, delivery_days_mask 127, assignment_strategy manual). app/app/Http/Middleware/SetTenantContext.php (NEW, alias `tenant`): - Резолюция tenant_id (приоритет): auth()->user() → subdomain (3+ parts HTTP_HOST) → X-Tenant-Id header (только dev/testing). - Без контекста → 403 Forbidden с явным сообщением. - SET LOCAL app.current_tenant_id внутри транзакции (DB::beginTransaction + SET LOCAL + next() + commit/rollback). PgBouncer-safe (Прил. И Г.1 кейс 2). - Зарегистрирован в bootstrap/app.php через $middleware->alias(). app/tests/Feature/ (NEW TenantModelsTest, SetTenantContextTest): - TenantModelsTest (8 тестов): factories + связи (Tenant→users/projects, User→tenant, Project→tenant) + проверка User::getAuthPassword override. - SetTenantContextTest (5 тестов): 403 без контекста, X-Tenant-Id header, игнор не-числового header, subdomain резолюция через absolute URL, middleware устанавливает app.current_tenant_id для last query. Сопутствующие правки: - app/.gitignore: + _ide_helper_models.php (сгенерированный, как и _ide_helper.php — не в репо) - app/phpstan-baseline.neon: regenerated — Pest dynamic methods ($this->get(), withHeaders()) и factory-return-type mismatch (Larastan v3 не понимает array<string, mixed> vs array<model property of T>) + Request::user/header (Larastan тип hint мисс) + console.php $this в Closure — все в baseline до миграции на typed properties / pest extension - CLAUDE.md §6: Pest 6/6 → 19/19, добавлены модели + middleware - memory project_state.md, MEMORY.md: обновлены под новый этап Не сделано в этой сессии (отложено): - Deal model — composite primary key (id, received_at) + partitioned parent. Эта модель сложнее по архитектуре Eloquent. - ide-helper:models -W -M -N запускался — добавил @mixin IdeHelperX и сгенерил _ide_helper_models.php; но т.к. этот файл gitignored, @mixin строки удалены из моделей (PHPStan не нашёл бы класс на CI). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
140 lines
4.9 KiB
Plaintext
140 lines
4.9 KiB
Plaintext
parameters:
|
|
ignoreErrors:
|
|
-
|
|
message: '#^Access to an undefined property App\\Models\\User\:\:\$password_hash\.$#'
|
|
identifier: property.notFound
|
|
count: 1
|
|
path: app/Models/User.php
|
|
|
|
-
|
|
message: '#^Return type \(array\<string, mixed\>\) of method Database\\Factories\\ProjectFactory\:\:definition\(\) should be compatible with return type \(array\<model property of App\\Models\\Project, mixed\>\) of method Illuminate\\Database\\Eloquent\\Factories\\Factory\<App\\Models\\Project\>\:\:definition\(\)$#'
|
|
identifier: method.childReturnType
|
|
count: 1
|
|
path: database/factories/ProjectFactory.php
|
|
|
|
-
|
|
message: '#^Return type \(array\<string, mixed\>\) of method Database\\Factories\\TenantFactory\:\:definition\(\) should be compatible with return type \(array\<model property of App\\Models\\Tenant, mixed\>\) of method Illuminate\\Database\\Eloquent\\Factories\\Factory\<App\\Models\\Tenant\>\:\:definition\(\)$#'
|
|
identifier: method.childReturnType
|
|
count: 1
|
|
path: database/factories/TenantFactory.php
|
|
|
|
-
|
|
message: '#^Return type \(array\<string, mixed\>\) of method Database\\Factories\\UserFactory\:\:definition\(\) should be compatible with return type \(array\<model property of App\\Models\\User, mixed\>\) of method Illuminate\\Database\\Eloquent\\Factories\\Factory\<App\\Models\\User\>\:\:definition\(\)$#'
|
|
identifier: method.childReturnType
|
|
count: 1
|
|
path: database/factories/UserFactory.php
|
|
|
|
-
|
|
message: '#^Access to an undefined property Pest\\PendingCalls\\TestCall\:\:\$project1Id\.$#'
|
|
identifier: property.notFound
|
|
count: 1
|
|
path: tests/Feature/RlsSmokeTest.php
|
|
|
|
-
|
|
message: '#^Access to an undefined property Pest\\PendingCalls\\TestCall\:\:\$project2Id\.$#'
|
|
identifier: property.notFound
|
|
count: 1
|
|
path: tests/Feature/RlsSmokeTest.php
|
|
|
|
-
|
|
message: '#^Access to an undefined property Pest\\PendingCalls\\TestCall\:\:\$tenant1Id\.$#'
|
|
identifier: property.notFound
|
|
count: 5
|
|
path: tests/Feature/RlsSmokeTest.php
|
|
|
|
-
|
|
message: '#^Access to an undefined property Pest\\PendingCalls\\TestCall\:\:\$tenant2Id\.$#'
|
|
identifier: property.notFound
|
|
count: 5
|
|
path: tests/Feature/RlsSmokeTest.php
|
|
|
|
-
|
|
message: '#^Call to an undefined method Pest\\PendingCalls\\TestCall\:\:get\(\)\.$#'
|
|
identifier: method.notFound
|
|
count: 2
|
|
path: tests/Feature/SetTenantContextTest.php
|
|
|
|
-
|
|
message: '#^Call to an undefined method Pest\\PendingCalls\\TestCall\:\:withHeaders\(\)\.$#'
|
|
identifier: method.notFound
|
|
count: 3
|
|
path: tests/Feature/SetTenantContextTest.php
|
|
|
|
-
|
|
message: '#^Access to an undefined property App\\Models\\Project\:\:\$assignment_strategy\.$#'
|
|
identifier: property.notFound
|
|
count: 1
|
|
path: tests/Feature/TenantModelsTest.php
|
|
|
|
-
|
|
message: '#^Access to an undefined property App\\Models\\Project\:\:\$delivery_days_mask\.$#'
|
|
identifier: property.notFound
|
|
count: 1
|
|
path: tests/Feature/TenantModelsTest.php
|
|
|
|
-
|
|
message: '#^Access to an undefined property App\\Models\\Project\:\:\$region_mask\.$#'
|
|
identifier: property.notFound
|
|
count: 1
|
|
path: tests/Feature/TenantModelsTest.php
|
|
|
|
-
|
|
message: '#^Access to an undefined property App\\Models\\Project\:\:\$tenant_id\.$#'
|
|
identifier: property.notFound
|
|
count: 1
|
|
path: tests/Feature/TenantModelsTest.php
|
|
|
|
-
|
|
message: '#^Access to an undefined property App\\Models\\Project\:\:\$type\.$#'
|
|
identifier: property.notFound
|
|
count: 1
|
|
path: tests/Feature/TenantModelsTest.php
|
|
|
|
-
|
|
message: '#^Access to an undefined property App\\Models\\Tenant\:\:\$api_key_limit\.$#'
|
|
identifier: property.notFound
|
|
count: 1
|
|
path: tests/Feature/TenantModelsTest.php
|
|
|
|
-
|
|
message: '#^Access to an undefined property App\\Models\\Tenant\:\:\$is_trial\.$#'
|
|
identifier: property.notFound
|
|
count: 1
|
|
path: tests/Feature/TenantModelsTest.php
|
|
|
|
-
|
|
message: '#^Access to an undefined property App\\Models\\Tenant\:\:\$subdomain\.$#'
|
|
identifier: property.notFound
|
|
count: 1
|
|
path: tests/Feature/TenantModelsTest.php
|
|
|
|
-
|
|
message: '#^Access to an undefined property App\\Models\\User\:\:\$email\.$#'
|
|
identifier: property.notFound
|
|
count: 1
|
|
path: tests/Feature/TenantModelsTest.php
|
|
|
|
-
|
|
message: '#^Access to an undefined property App\\Models\\User\:\:\$is_active\.$#'
|
|
identifier: property.notFound
|
|
count: 1
|
|
path: tests/Feature/TenantModelsTest.php
|
|
|
|
-
|
|
message: '#^Access to an undefined property App\\Models\\User\:\:\$password_hash\.$#'
|
|
identifier: property.notFound
|
|
count: 2
|
|
path: tests/Feature/TenantModelsTest.php
|
|
|
|
-
|
|
message: '#^Access to an undefined property App\\Models\\User\:\:\$tenant_id\.$#'
|
|
identifier: property.notFound
|
|
count: 1
|
|
path: tests/Feature/TenantModelsTest.php
|
|
|
|
-
|
|
message: '#^Call to method PHPUnit\\Framework\\Assert\:\:assertTrue\(\) with true will always evaluate to true\.$#'
|
|
identifier: method.alreadyNarrowedType
|
|
count: 1
|
|
path: tests/Unit/ExampleTest.php
|