Files
portal/app/config/database.php
T
Дмитрий d5c972c3f2 feat(админка): connection pgsql_admin под ролью crm_admin_user (Путь А)
AdminTenantsController/AdminBillingController ходят под default-подключением;
новое pgsql_admin (crm_admin_user, srv_bypass) даст им cross-tenant доступ
через middleware-переключатель (следующий коммит). На dev fallback на
DB_USERNAME. Test: pgsql_admin делит базовый pgsql-конфиг, роль из DB_ADMIN_*.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-27 06:37:42 +03:00

246 lines
11 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
use Illuminate\Support\Str;
use Pdo\Mysql;
// pgsql base config — single source of truth для pgsql + pgsql_supplier (Plan 3 Task 3).
// config('database.connections.pgsql') внутри этого файла не работает: config Repository
// ещё не bootstrap'нут на момент return этого массива (chicken-and-egg). Используем
// локальную переменную: pgsql_supplier = $pgsqlConnection + override (username/password).
$pgsqlConnection = [
'driver' => 'pgsql',
'url' => env('DB_URL'),
'host' => env('DB_HOST', '127.0.0.1'),
'port' => env('DB_PORT', '5432'),
'database' => env('DB_DATABASE', 'laravel'),
'username' => env('DB_USERNAME', 'root'),
'password' => env('DB_PASSWORD', ''),
'charset' => env('DB_CHARSET', 'utf8'),
'prefix' => '',
'prefix_indexes' => true,
'search_path' => 'public',
'sslmode' => env('DB_SSLMODE', 'prefer'),
// Managed PG (Путь А, 26.06.2026): CA-файл для sslmode=verify-full. Если DB_SSLROOTCERT
// не задан (dev/локально) — env() вернёт null, Laravel-коннектор ключ пропустит (isset=false),
// поведение не меняется. На проде: DB_SSLMODE=verify-full + DB_SSLROOTCERT=<путь к CA>.
'sslrootcert' => env('DB_SSLROOTCERT'),
// PG session timezone = UTC. Без этого TIMESTAMPTZ возвращается с локальным offset
// (+03), а Carbon::parse теряет offset → password reset token expiry-check
// и аналогичные TZ-чувствительные сравнения ломаются.
'timezone' => env('DB_TIMEZONE', 'UTC'),
];
return [
/*
|--------------------------------------------------------------------------
| Default Database Connection Name
|--------------------------------------------------------------------------
|
| Here you may specify which of the database connections below you wish
| to use as your default connection for database operations. This is
| the connection which will be utilized unless another connection
| is explicitly specified when you execute a query / statement.
|
*/
'default' => env('DB_CONNECTION', 'sqlite'),
/*
|--------------------------------------------------------------------------
| Database Connections
|--------------------------------------------------------------------------
|
| Below are all of the database connections defined for your application.
| An example configuration is provided for each database system which
| is supported by Laravel. You're free to add / remove connections.
|
*/
'connections' => [
'sqlite' => [
'driver' => 'sqlite',
'url' => env('DB_URL'),
'database' => env('DB_DATABASE', database_path('database.sqlite')),
'prefix' => '',
'foreign_key_constraints' => env('DB_FOREIGN_KEYS', true),
'busy_timeout' => null,
'journal_mode' => null,
'synchronous' => null,
'transaction_mode' => 'DEFERRED',
],
'mysql' => [
'driver' => 'mysql',
'url' => env('DB_URL'),
'host' => env('DB_HOST', '127.0.0.1'),
'port' => env('DB_PORT', '3306'),
'database' => env('DB_DATABASE', 'laravel'),
'username' => env('DB_USERNAME', 'root'),
'password' => env('DB_PASSWORD', ''),
'unix_socket' => env('DB_SOCKET', ''),
'charset' => env('DB_CHARSET', 'utf8mb4'),
'collation' => env('DB_COLLATION', 'utf8mb4_unicode_ci'),
'prefix' => '',
'prefix_indexes' => true,
'strict' => true,
'engine' => null,
'options' => extension_loaded('pdo_mysql') ? array_filter([
(PHP_VERSION_ID >= 80500 ? Mysql::ATTR_SSL_CA : PDO::MYSQL_ATTR_SSL_CA) => env('MYSQL_ATTR_SSL_CA'),
]) : [],
],
'mariadb' => [
'driver' => 'mariadb',
'url' => env('DB_URL'),
'host' => env('DB_HOST', '127.0.0.1'),
'port' => env('DB_PORT', '3306'),
'database' => env('DB_DATABASE', 'laravel'),
'username' => env('DB_USERNAME', 'root'),
'password' => env('DB_PASSWORD', ''),
'unix_socket' => env('DB_SOCKET', ''),
'charset' => env('DB_CHARSET', 'utf8mb4'),
'collation' => env('DB_COLLATION', 'utf8mb4_unicode_ci'),
'prefix' => '',
'prefix_indexes' => true,
'strict' => true,
'engine' => null,
'options' => extension_loaded('pdo_mysql') ? array_filter([
(PHP_VERSION_ID >= 80500 ? Mysql::ATTR_SSL_CA : PDO::MYSQL_ATTR_SSL_CA) => env('MYSQL_ATTR_SSL_CA'),
]) : [],
],
'pgsql' => $pgsqlConnection,
// Plan 3 Task 3: dedicated PG connection для supplier-flow под BYPASSRLS-ролью
// crm_supplier_worker (создана Plan 2.6 #iv 7899071 в db/00_create_roles.sql v1.1).
// Закрывает 3 backlog-айтема одной правкой:
// - BLOCKER #6: INSERT в failed_webhook_jobs с tenant_id=NULL под BYPASSRLS
// проходит (политика tenant_isolation отвергает NULL под обычной ролью).
// - WARN #2: LeadRouter::matchEligibleProjects видит проекты ВСЕХ tenant'ов
// без SET LOCAL app.current_tenant_id (sharing-model §6 spec'а).
// - WARN #3: ResetDeliveredTodayCommand сбрасывает delivered_today по всем
// tenant'ам в одном UPDATE.
//
// На production env-keys DB_SUPPLIER_USERNAME=crm_supplier_worker + DB_SUPPLIER_PASSWORD
// указывают на BYPASSRLS-роль. На dev fallback на DB_USERNAME/DB_PASSWORD (postgres
// superuser — BYPASSRLS implicit), тесты работают без отдельной роли.
//
// WHERE(tenant_id=) фильтры в коде сохраняются как defense-in-depth — если в будущем
// роль сменится на не-BYPASSRLS, бизнес-логика останется корректной.
//
// Brainstorm decision: вариант C (BYPASSRLS-role) из 3 опций. См.
// docs/superpowers/specs/2026-05-11-plan3-supplier-sync-design.md §1.
'pgsql_supplier' => array_merge(
$pgsqlConnection,
[
// crm_supplier_worker (BYPASSRLS) for supplier-flow jobs.
// Plan 3 Task 3 — закрывает BLOCKER #6 + WARN #2/#3.
// На dev fallback на default DB_USERNAME/DB_PASSWORD; на prod
// ОБЯЗАТЕЛЬНО задать DB_SUPPLIER_USERNAME=crm_supplier_worker.
'username' => env('DB_SUPPLIER_USERNAME', env('DB_USERNAME', 'root')),
'password' => env('DB_SUPPLIER_PASSWORD', env('DB_PASSWORD', '')),
]
),
// Путь А (27.06.2026): dedicated PG connection для SaaS-admin зоны под
// ролью crm_admin_user (политика srv_bypass = видит все тенанты + GRANT на
// админ-таблицы). Используется через middleware UseAdminConnection (alias
// admin-db) на группе saas-admin: AdminTenantsController / AdminBillingController
// ходят под default → получают cross-tenant доступ. На dev fallback на
// DB_USERNAME/DB_PASSWORD (postgres superuser). На prod ОБЯЗАТЕЛЬНО задать
// DB_ADMIN_USERNAME=crm_admin_user + DB_ADMIN_PASSWORD.
// См. docs/superpowers/specs/2026-06-27-admin-db-connection-path-a-design.md
'pgsql_admin' => array_merge(
$pgsqlConnection,
[
'username' => env('DB_ADMIN_USERNAME', env('DB_USERNAME', 'root')),
'password' => env('DB_ADMIN_PASSWORD', env('DB_PASSWORD', '')),
]
),
'sqlsrv' => [
'driver' => 'sqlsrv',
'url' => env('DB_URL'),
'host' => env('DB_HOST', 'localhost'),
'port' => env('DB_PORT', '1433'),
'database' => env('DB_DATABASE', 'laravel'),
'username' => env('DB_USERNAME', 'root'),
'password' => env('DB_PASSWORD', ''),
'charset' => env('DB_CHARSET', 'utf8'),
'prefix' => '',
'prefix_indexes' => true,
// 'encrypt' => env('DB_ENCRYPT', 'yes'),
// 'trust_server_certificate' => env('DB_TRUST_SERVER_CERTIFICATE', 'false'),
],
],
/*
|--------------------------------------------------------------------------
| Migration Repository Table
|--------------------------------------------------------------------------
|
| This table keeps track of all the migrations that have already run for
| your application. Using this information, we can determine which of
| the migrations on disk haven't actually been run on the database.
|
*/
'migrations' => [
'table' => 'migrations',
'update_date_on_publish' => true,
],
/*
|--------------------------------------------------------------------------
| Redis Databases
|--------------------------------------------------------------------------
|
| Redis is an open source, fast, and advanced key-value store that also
| provides a richer body of commands than a typical key-value system
| such as Memcached. You may define your connection settings here.
|
*/
'redis' => [
'client' => env('REDIS_CLIENT', 'phpredis'),
'options' => [
'cluster' => env('REDIS_CLUSTER', 'redis'),
'prefix' => env('REDIS_PREFIX', Str::slug((string) env('APP_NAME', 'laravel')).'-database-'),
'persistent' => env('REDIS_PERSISTENT', false),
],
'default' => [
'url' => env('REDIS_URL'),
'host' => env('REDIS_HOST', '127.0.0.1'),
'username' => env('REDIS_USERNAME'),
'password' => env('REDIS_PASSWORD'),
'port' => env('REDIS_PORT', '6379'),
'database' => env('REDIS_DB', '0'),
'max_retries' => env('REDIS_MAX_RETRIES', 3),
'backoff_algorithm' => env('REDIS_BACKOFF_ALGORITHM', 'decorrelated_jitter'),
'backoff_base' => env('REDIS_BACKOFF_BASE', 100),
'backoff_cap' => env('REDIS_BACKOFF_CAP', 1000),
],
'cache' => [
'url' => env('REDIS_URL'),
'host' => env('REDIS_HOST', '127.0.0.1'),
'username' => env('REDIS_USERNAME'),
'password' => env('REDIS_PASSWORD'),
'port' => env('REDIS_PORT', '6379'),
'database' => env('REDIS_CACHE_DB', '1'),
'max_retries' => env('REDIS_MAX_RETRIES', 3),
'backoff_algorithm' => env('REDIS_BACKOFF_ALGORITHM', 'decorrelated_jitter'),
'backoff_base' => env('REDIS_BACKOFF_BASE', 100),
'backoff_cap' => env('REDIS_BACKOFF_CAP', 1000),
],
],
];