feat(127): erli integration foundation

Phase 127 complete:

- add global Erli settings schema and encrypted API key repository

- add real read-only Erli API connection test and settings UI

- expose Erli in integrations hub and update PAUL/docs state
This commit is contained in:
2026-05-15 23:26:44 +02:00
parent afdbc67887
commit d6b18a6438
18 changed files with 1281 additions and 28 deletions

View File

@@ -0,0 +1,113 @@
<?php
declare(strict_types=1);
namespace App\Modules\Settings;
use App\Core\Exceptions\IntegrationConfigException;
use App\Core\Http\RedirectPathResolver;
use App\Core\Http\Request;
use App\Core\Http\Response;
use App\Core\I18n\Translator;
use App\Core\Security\Csrf;
use App\Core\Support\Flash;
use App\Core\View\Template;
use App\Modules\Auth\AuthService;
use Throwable;
final class ErliIntegrationController
{
public function __construct(
private readonly Template $template,
private readonly Translator $translator,
private readonly AuthService $auth,
private readonly ErliIntegrationRepository $repository,
private readonly ErliApiClient $apiClient,
private readonly IntegrationsRepository $integrations
) {
}
public function index(Request $request): Response
{
$html = $this->template->render('settings/erli', [
'title' => $this->translator->get('settings.erli.title'),
'activeMenu' => 'settings',
'activeSettings' => 'integrations',
'user' => $this->auth->user(),
'csrfToken' => Csrf::token(),
'settings' => $this->repository->getSettings(),
'errorMessage' => (string) Flash::get('settings_error', ''),
'successMessage' => (string) Flash::get('settings_success', ''),
'testMessage' => (string) Flash::get('erli_test', ''),
], 'layouts/app');
return Response::html($html);
}
public function save(Request $request): Response
{
$redirectTo = $this->resolveRedirect($request);
if (!Csrf::validate((string) $request->input('_token', ''))) {
Flash::set('settings_error', $this->translator->get('auth.errors.csrf_expired'));
return Response::redirect($redirectTo);
}
try {
$this->repository->saveSettings([
'account_label' => (string) $request->input('account_label', ''),
'api_key' => (string) $request->input('api_key', ''),
'is_active' => $request->input('is_active', ''),
]);
Flash::set('settings_success', $this->translator->get('settings.erli.flash.saved'));
} catch (Throwable $exception) {
Flash::set(
'settings_error',
$this->translator->get('settings.erli.flash.save_failed') . ' ' . $exception->getMessage()
);
}
return Response::redirect($redirectTo);
}
public function test(Request $request): Response
{
$redirectTo = $this->resolveRedirect($request);
if (!Csrf::validate((string) $request->input('_token', ''))) {
Flash::set('settings_error', $this->translator->get('auth.errors.csrf_expired'));
return Response::redirect($redirectTo);
}
try {
$credentials = $this->repository->getCredentials();
if ($credentials === null) {
throw new IntegrationConfigException('Najpierw zapisz kompletna i aktywna konfiguracje Erli.');
}
$result = $this->apiClient->testConnection($credentials);
$this->integrations->updateTestResult(
$credentials['integration_id'],
$result['ok'] ? 'ok' : 'fail',
(int) $result['http_code'],
(string) $result['message']
);
if ($result['ok']) {
Flash::set('erli_test', $this->translator->get('settings.erli.flash.test_success'));
} else {
Flash::set('settings_error', $this->translator->get('settings.erli.flash.test_failed') . ' ' . $result['message']);
}
} catch (Throwable $exception) {
Flash::set('settings_error', $this->translator->get('settings.erli.flash.test_failed') . ' ' . $exception->getMessage());
}
return Response::redirect($redirectTo);
}
private function resolveRedirect(Request $request): string
{
return RedirectPathResolver::resolve(
(string) $request->input('return_to', '/settings/integrations/erli'),
['/settings/integrations'],
'/settings/integrations/erli'
);
}
}