| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259 |
- <?php
- declare(strict_types=1);
- namespace App\App;
- use App\Application\Admin\ConsumersController;
- use App\Application\Admin\MeController;
- use App\Application\Admin\ReportersController;
- use App\Application\Admin\TokensController;
- use App\Application\Auth\AuthController;
- use App\Application\Internal\JobsController;
- use App\Application\Jobs\CleanupAuditJob;
- use App\Application\Jobs\EnrichPendingJob;
- use App\Application\Jobs\RecomputeScoresJob;
- use App\Application\Jobs\TickJob;
- use App\Application\Public\ReportController;
- use App\Domain\Auth\Role;
- use App\Domain\Auth\TokenHasher;
- use App\Domain\Auth\TokenIssuer;
- use App\Domain\Reputation\PairScorer;
- use App\Domain\Time\Clock;
- use App\Domain\Time\SystemClock;
- use App\Infrastructure\Auth\RoleMappingRepository;
- use App\Infrastructure\Auth\ServiceTokenBootstrap;
- use App\Infrastructure\Auth\TokenRepository;
- use App\Infrastructure\Auth\UserRepository;
- use App\Infrastructure\Category\CategoryRepository;
- use App\Infrastructure\Consumer\ConsumerRepository;
- use App\Infrastructure\Db\ConnectionFactory;
- use App\Infrastructure\Http\JsonErrorHandler;
- use App\Infrastructure\Http\Middleware\ImpersonationMiddleware;
- use App\Infrastructure\Http\Middleware\InternalNetworkMiddleware;
- use App\Infrastructure\Http\Middleware\InternalTokenMiddleware;
- use App\Infrastructure\Http\Middleware\RateLimitMiddleware;
- use App\Infrastructure\Http\Middleware\TokenAuthenticationMiddleware;
- use App\Infrastructure\Http\RateLimiter;
- use App\Infrastructure\Jobs\JobLockRepository;
- use App\Infrastructure\Jobs\JobRegistry;
- use App\Infrastructure\Jobs\JobRunner;
- use App\Infrastructure\Jobs\JobRunRepository;
- use App\Infrastructure\Reporter\ReporterRepository;
- use App\Infrastructure\Reputation\IpScoreRepository;
- use App\Infrastructure\Reputation\ReportRepository;
- use function DI\autowire;
- use DI\ContainerBuilder;
- use function DI\factory;
- use Doctrine\DBAL\Connection;
- use Monolog\Formatter\JsonFormatter;
- use Monolog\Handler\StreamHandler;
- use Monolog\Logger;
- use Psr\Container\ContainerInterface;
- use Psr\Http\Message\ResponseFactoryInterface;
- use Psr\Log\LoggerInterface;
- use Slim\Psr7\Factory\ResponseFactory;
- /**
- * Builds the api's DI container.
- *
- * M04 additions: ReporterRepository / ConsumerRepository / CategoryRepository /
- * ReportRepository / IpScoreRepository, the PairScorer + Clock pair, and the
- * RateLimiter singleton plus its middleware. The rate-limit settings flow
- * through `settings` so tests can override capacity/refill cleanly.
- */
- final class Container
- {
- /**
- * @param array<string, mixed>|null $settings Optional override (tests pass in fixtures).
- */
- public static function build(?array $settings = null): ContainerInterface
- {
- $settings ??= require __DIR__ . '/../../config/settings.php';
- $builder = new ContainerBuilder();
- $builder->useAutowiring(true);
- $builder->addDefinitions([
- 'settings' => $settings,
- 'settings.db' => $settings['db'],
- 'settings.ui_service_token' => $settings['ui_service_token'] ?? '',
- 'settings.app_env' => $settings['app_env'] ?? 'production',
- 'settings.log_level' => $settings['log_level'] ?? \Monolog\Level::Info,
- 'settings.oidc_default_role' => $settings['oidc_default_role'] ?? Role::Viewer,
- 'settings.score_hard_cutoff_days' => (int) ($settings['score_hard_cutoff_days'] ?? 365),
- 'settings.rate_limit_per_second' => (int) ($settings['rate_limit_per_second'] ?? 60),
- 'settings.internal_job_token' => (string) ($settings['internal_job_token'] ?? ''),
- 'settings.job_recompute_max_runtime_seconds' => (int) ($settings['job_recompute_max_runtime_seconds'] ?? 240),
- 'settings.job_recompute_max_rows_per_tick' => (int) ($settings['job_recompute_max_rows_per_tick'] ?? 5000),
- 'settings.job_audit_retention_days' => (int) ($settings['job_audit_retention_days'] ?? 180),
- ConnectionFactory::class => factory(static function (ContainerInterface $c): ConnectionFactory {
- /** @var array{driver: string, sqlite_path: string, mysql_host: string, mysql_port: int, mysql_database: string, mysql_username: string, mysql_password: string} $db */
- $db = $c->get('settings.db');
- return new ConnectionFactory($db);
- }),
- Connection::class => factory(static function (ContainerInterface $c): Connection {
- /** @var ConnectionFactory $factory */
- $factory = $c->get(ConnectionFactory::class);
- return $factory->create();
- }),
- LoggerInterface::class => factory(static function (ContainerInterface $c): LoggerInterface {
- $logger = new Logger('api');
- /** @var \Monolog\Level $level */
- $level = $c->get('settings.log_level');
- $handler = new StreamHandler('php://stdout', $level);
- $handler->setFormatter(new JsonFormatter());
- $logger->pushHandler($handler);
- return $logger;
- }),
- ResponseFactoryInterface::class => autowire(ResponseFactory::class),
- Clock::class => autowire(SystemClock::class),
- TokenHasher::class => autowire(),
- TokenIssuer::class => autowire(),
- TokenRepository::class => autowire(),
- RoleMappingRepository::class => autowire(),
- UserRepository::class => autowire(),
- ReporterRepository::class => autowire(),
- ConsumerRepository::class => autowire(),
- CategoryRepository::class => autowire(),
- ReportRepository::class => autowire(),
- IpScoreRepository::class => autowire(),
- ServiceTokenBootstrap::class => autowire(),
- TokenAuthenticationMiddleware::class => autowire(),
- ImpersonationMiddleware::class => autowire(),
- PairScorer::class => factory(static function (ContainerInterface $c): PairScorer {
- /** @var ReportRepository $reports */
- $reports = $c->get(ReportRepository::class);
- /** @var CategoryRepository $categories */
- $categories = $c->get(CategoryRepository::class);
- /** @var Clock $clock */
- $clock = $c->get(Clock::class);
- /** @var int $cutoff */
- $cutoff = $c->get('settings.score_hard_cutoff_days');
- return new PairScorer($reports, $categories, $clock, $cutoff);
- }),
- RateLimiter::class => factory(static function (ContainerInterface $c): RateLimiter {
- /** @var Clock $clock */
- $clock = $c->get(Clock::class);
- /** @var int $perSecond */
- $perSecond = $c->get('settings.rate_limit_per_second');
- $perSecond = max(1, $perSecond);
- return new RateLimiter($clock, (float) $perSecond, (float) ($perSecond * 2));
- }),
- RateLimitMiddleware::class => autowire(),
- JobLockRepository::class => autowire(),
- JobRunRepository::class => autowire(),
- JobRunner::class => factory(static function (ContainerInterface $c): JobRunner {
- /** @var JobLockRepository $locks */
- $locks = $c->get(JobLockRepository::class);
- /** @var JobRunRepository $runs */
- $runs = $c->get(JobRunRepository::class);
- /** @var Clock $clock */
- $clock = $c->get(Clock::class);
- /** @var LoggerInterface $logger */
- $logger = $c->get(LoggerInterface::class);
- return new JobRunner($locks, $runs, $clock, $logger);
- }),
- RecomputeScoresJob::class => factory(static function (ContainerInterface $c): RecomputeScoresJob {
- /** @var ReportRepository $reports */
- $reports = $c->get(ReportRepository::class);
- /** @var IpScoreRepository $ipScores */
- $ipScores = $c->get(IpScoreRepository::class);
- /** @var PairScorer $scorer */
- $scorer = $c->get(PairScorer::class);
- /** @var int $maxRuntime */
- $maxRuntime = $c->get('settings.job_recompute_max_runtime_seconds');
- /** @var int $maxRows */
- $maxRows = $c->get('settings.job_recompute_max_rows_per_tick');
- return new RecomputeScoresJob($reports, $ipScores, $scorer, $maxRuntime, $maxRows);
- }),
- CleanupAuditJob::class => factory(static function (ContainerInterface $c): CleanupAuditJob {
- /** @var Connection $conn */
- $conn = $c->get(Connection::class);
- /** @var int $days */
- $days = $c->get('settings.job_audit_retention_days');
- return new CleanupAuditJob($conn, $days);
- }),
- EnrichPendingJob::class => autowire(),
- TickJob::class => factory(static function (ContainerInterface $c): TickJob {
- /** @var JobRunner $runner */
- $runner = $c->get(JobRunner::class);
- /** @var JobRunRepository $runs */
- $runs = $c->get(JobRunRepository::class);
- // Closure indirection: TickJob iterates JobRegistry at
- // run() time, after the registry is fully populated. A
- // direct JobRegistry dependency would create a build-time
- // cycle (registry → tick → registry).
- $resolver = static fn (): array => $c->get(JobRegistry::class)->all();
- return new TickJob($resolver, $runner, $runs);
- }),
- JobRegistry::class => factory(static function (ContainerInterface $c): JobRegistry {
- $registry = new JobRegistry();
- /** @var RecomputeScoresJob $recompute */
- $recompute = $c->get(RecomputeScoresJob::class);
- /** @var CleanupAuditJob $cleanup */
- $cleanup = $c->get(CleanupAuditJob::class);
- /** @var EnrichPendingJob $enrich */
- $enrich = $c->get(EnrichPendingJob::class);
- /** @var TickJob $tick */
- $tick = $c->get(TickJob::class);
- $registry->register($recompute);
- $registry->register($cleanup);
- $registry->register($enrich);
- $registry->register($tick);
- return $registry;
- }),
- JobsController::class => autowire(),
- InternalNetworkMiddleware::class => autowire(),
- InternalTokenMiddleware::class => factory(static function (ContainerInterface $c): InternalTokenMiddleware {
- /** @var ResponseFactoryInterface $rf */
- $rf = $c->get(ResponseFactoryInterface::class);
- /** @var string $token */
- $token = $c->get('settings.internal_job_token');
- return new InternalTokenMiddleware($rf, $token);
- }),
- JsonErrorHandler::class => factory(static function (ContainerInterface $c): JsonErrorHandler {
- /** @var ResponseFactoryInterface $factory */
- $factory = $c->get(ResponseFactoryInterface::class);
- /** @var LoggerInterface $logger */
- $logger = $c->get(LoggerInterface::class);
- return new JsonErrorHandler(
- $factory,
- $logger,
- $c->get('settings.app_env') === 'development',
- );
- }),
- AuthController::class => factory(static function (ContainerInterface $c): AuthController {
- /** @var Role|null $role */
- $role = $c->get('settings.oidc_default_role');
- /** @var UserRepository $users */
- $users = $c->get(UserRepository::class);
- return new AuthController($users, $role ?? Role::Viewer);
- }),
- MeController::class => autowire(),
- ReportersController::class => autowire(),
- ConsumersController::class => autowire(),
- TokensController::class => autowire(),
- ReportController::class => autowire(),
- ]);
- return $builder->build();
- }
- }
|