Files
portal/app/config/database.php
T
Дмитрий 662ebd6e8b
Accessibility (Pa11y live) / a11y (push) Has been cancelled
SAST — Semgrep / Semgrep SAST scan (push) Has been cancelled
feat/db-path-a: прод переключён на Managed PG + verify-full SSL + хвосты закрыты
- config/database.php: добавлен sslrootcert (env DB_SSLROOTCERT) для sslmode=verify-full
- ПИЛОТ.md §3: боевая БД = Yandex Managed PG; старая локальная БД = откат >=7 дней
- etap3-prod-cutover-DONE: отчёт переезда (деньги ДО==ПОСЛЕ, HTTP200, изоляция, откат)
- cspell-words: +рус. жаргон из снимков

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 12:22:06 +03:00

230 lines
10 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', '')),
]
),
'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),
],
],
];