feat(integration) : migrate Gitea/BookStack/Zimbra/Share into module (back)

LST-68 (2.6) backend. Behaviour-preserving move of the external integrations
into src/Module/Integration/. All 26 routes and securities unchanged.

- 5 entities (4 *Configuration singletons + TaskBookStackLink) + 5 repositories
  (Domain interfaces + Doctrine impls, bound). TaskBookStackLink.task now
  references TaskInterface (contract).
- Domain (FileSource interface, SharePathResolver, share DTOs + exceptions);
  Infrastructure (GiteaApiService, BookStackApiService, SmbFileSource, 15
  ApiResources, 21 State, 4 Share controllers).
- Cross-module couplings via abstractions: CalDavService (PM) injects
  ZimbraConfigurationRepositoryInterface; PM TaskDocument consumers repointed
  to the module's FileSource/SharePathResolver; Gitea/BookStack State load
  tasks via TaskRepositoryInterface (concrete Project read for integration
  fields — documented). ZimbraTestConnection keeps CalDavService (no build
  cycle). TokenEncryptor stays shared.
- IntegrationModule registered; doctrine mapping added.
- #[Auditable] + Timestampable on the 4 Configuration entities (additive
  migration on the 4 *_configuration tables).

163 tests green, container compiles (no cycle), no route regression, cs-fixer clean.
This commit is contained in:
Matthieu
2026-06-20 20:16:20 +02:00
parent bb7d7e7953
commit 90682e809c
79 changed files with 589 additions and 284 deletions
@@ -0,0 +1,80 @@
<?php
declare(strict_types=1);
namespace App\Module\Integration\Infrastructure\ApiPlatform\State;
use ApiPlatform\Metadata\Delete;
use ApiPlatform\Metadata\Operation;
use ApiPlatform\State\ProcessorInterface;
use App\Module\Integration\Domain\Entity\TaskBookStackLink;
use App\Module\Integration\Domain\Repository\TaskBookStackLinkRepositoryInterface;
use App\Module\Integration\Infrastructure\ApiPlatform\Resource\BookStackLink;
use App\Module\ProjectManagement\Domain\Repository\TaskRepositoryInterface;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
final readonly class BookStackLinkProcessor implements ProcessorInterface
{
public function __construct(
private EntityManagerInterface $em,
private TaskBookStackLinkRepositoryInterface $linkRepository,
private TaskRepositoryInterface $taskRepository,
) {}
public function process(mixed $data, Operation $operation, array $uriVariables = [], array $context = []): ?BookStackLink
{
if ($operation instanceof Delete) {
return $this->handleDelete($uriVariables);
}
return $this->handleCreate($data, $uriVariables);
}
private function handleCreate(mixed $data, array $uriVariables): BookStackLink
{
assert($data instanceof BookStackLink);
$taskId = $uriVariables['taskId'] ?? 0;
$task = $this->taskRepository->findById((int) $taskId);
if (null === $task) {
throw new NotFoundHttpException('Task not found.');
}
$link = new TaskBookStackLink();
$link->setTask($task);
$link->setBookstackId($data->bookstackId);
$link->setBookstackType($data->bookstackType);
$link->setTitle($data->title);
$link->setUrl($data->url);
$this->em->persist($link);
$this->em->flush();
$result = new BookStackLink();
$result->id = $link->getId();
$result->bookstackId = $link->getBookstackId();
$result->bookstackType = $link->getBookstackType();
$result->title = $link->getTitle();
$result->url = $link->getUrl();
$result->createdAt = $link->getCreatedAt()->format('c');
return $result;
}
private function handleDelete(array $uriVariables): null
{
$linkId = $uriVariables['id'] ?? 0;
$link = $this->linkRepository->findById((int) $linkId);
if (null === $link) {
throw new NotFoundHttpException('Link not found.');
}
$this->em->remove($link);
$this->em->flush();
return null;
}
}
@@ -0,0 +1,54 @@
<?php
declare(strict_types=1);
namespace App\Module\Integration\Infrastructure\ApiPlatform\State;
use ApiPlatform\Metadata\Delete;
use ApiPlatform\Metadata\Operation;
use ApiPlatform\Metadata\Post;
use ApiPlatform\State\ProviderInterface;
use App\Module\Integration\Domain\Entity\TaskBookStackLink;
use App\Module\Integration\Domain\Repository\TaskBookStackLinkRepositoryInterface;
use App\Module\Integration\Infrastructure\ApiPlatform\Resource\BookStackLink;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
final readonly class BookStackLinkProvider implements ProviderInterface
{
public function __construct(
private TaskBookStackLinkRepositoryInterface $linkRepository,
) {}
public function provide(Operation $operation, array $uriVariables = [], array $context = []): array|BookStackLink
{
if ($operation instanceof Post) {
return new BookStackLink();
}
if ($operation instanceof Delete) {
$link = $this->linkRepository->find($uriVariables['id'] ?? 0);
if (null === $link) {
throw new NotFoundHttpException('Link not found.');
}
$dto = new BookStackLink();
$dto->id = $link->getId();
return $dto;
}
$taskId = $uriVariables['taskId'] ?? 0;
$links = $this->linkRepository->findByTaskId($taskId);
return array_map(static function (TaskBookStackLink $link): BookStackLink {
$dto = new BookStackLink();
$dto->id = $link->getId();
$dto->bookstackId = $link->getBookstackId();
$dto->bookstackType = $link->getBookstackType();
$dto->title = $link->getTitle();
$dto->url = $link->getUrl();
$dto->createdAt = $link->getCreatedAt()->format('c');
return $dto;
}, $links);
}
}
@@ -0,0 +1,61 @@
<?php
declare(strict_types=1);
namespace App\Module\Integration\Infrastructure\ApiPlatform\State;
use ApiPlatform\Metadata\Operation;
use ApiPlatform\State\ProviderInterface;
use App\Module\Integration\Domain\Exception\BookStackApiException;
use App\Module\Integration\Infrastructure\ApiPlatform\Resource\BookStackSearchResult;
use App\Module\Integration\Infrastructure\Service\BookStackApiService;
use App\Module\ProjectManagement\Domain\Repository\TaskRepositoryInterface;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
final readonly class BookStackSearchResultProvider implements ProviderInterface
{
public function __construct(
private BookStackApiService $bookStackApiService,
private TaskRepositoryInterface $taskRepository,
private RequestStack $requestStack,
) {}
public function provide(Operation $operation, array $uriVariables = [], array $context = []): array
{
$taskId = $uriVariables['taskId'] ?? 0;
$task = $this->taskRepository->findById((int) $taskId);
if (null === $task || null === $task->getProject()) {
return [];
}
$shelfId = $task->getProject()->getBookstackShelfId();
if (null === $shelfId) {
return [];
}
$request = $this->requestStack->getCurrentRequest();
$query = $request?->query->get('q', '') ?? '';
if ('' === trim($query)) {
return [];
}
try {
$results = $this->bookStackApiService->searchInShelf($shelfId, $query);
} catch (BookStackApiException $e) {
throw new BadRequestHttpException($e->getMessage(), $e);
}
return array_map(static function (array $item): BookStackSearchResult {
$dto = new BookStackSearchResult();
$dto->id = $item['id'];
$dto->type = $item['type'];
$dto->name = $item['name'];
$dto->url = $item['url'];
return $dto;
}, $results);
}
}
@@ -0,0 +1,49 @@
<?php
declare(strict_types=1);
namespace App\Module\Integration\Infrastructure\ApiPlatform\State;
use ApiPlatform\Metadata\Operation;
use ApiPlatform\State\ProcessorInterface;
use App\Module\Integration\Domain\Entity\BookStackConfiguration;
use App\Module\Integration\Domain\Repository\BookStackConfigurationRepositoryInterface;
use App\Module\Integration\Infrastructure\ApiPlatform\Resource\BookStackSettings;
use App\Service\TokenEncryptor;
use Doctrine\ORM\EntityManagerInterface;
final readonly class BookStackSettingsProcessor implements ProcessorInterface
{
public function __construct(
private EntityManagerInterface $em,
private BookStackConfigurationRepositoryInterface $configRepository,
private TokenEncryptor $tokenEncryptor,
) {}
public function process(mixed $data, Operation $operation, array $uriVariables = [], array $context = []): BookStackSettings
{
assert($data instanceof BookStackSettings);
$config = $this->configRepository->findSingleton();
if (null === $config) {
$config = new BookStackConfiguration();
}
$config->setUrl($data->url);
if (null !== $data->tokenId && '' !== $data->tokenId
&& null !== $data->tokenSecret && '' !== $data->tokenSecret) {
$config->setEncryptedTokenId($this->tokenEncryptor->encrypt($data->tokenId));
$config->setEncryptedTokenSecret($this->tokenEncryptor->encrypt($data->tokenSecret));
}
$this->em->persist($config);
$this->em->flush();
$result = new BookStackSettings();
$result->url = $config->getUrl();
$result->hasToken = $config->hasToken();
return $result;
}
}
@@ -0,0 +1,30 @@
<?php
declare(strict_types=1);
namespace App\Module\Integration\Infrastructure\ApiPlatform\State;
use ApiPlatform\Metadata\Operation;
use ApiPlatform\State\ProviderInterface;
use App\Module\Integration\Domain\Repository\BookStackConfigurationRepositoryInterface;
use App\Module\Integration\Infrastructure\ApiPlatform\Resource\BookStackSettings;
final readonly class BookStackSettingsProvider implements ProviderInterface
{
public function __construct(
private BookStackConfigurationRepositoryInterface $configRepository,
) {}
public function provide(Operation $operation, array $uriVariables = [], array $context = []): BookStackSettings
{
$config = $this->configRepository->findSingleton();
$dto = new BookStackSettings();
if (null !== $config) {
$dto->url = $config->getUrl();
$dto->hasToken = $config->hasToken();
}
return $dto;
}
}
@@ -0,0 +1,36 @@
<?php
declare(strict_types=1);
namespace App\Module\Integration\Infrastructure\ApiPlatform\State;
use ApiPlatform\Metadata\Operation;
use ApiPlatform\State\ProviderInterface;
use App\Module\Integration\Domain\Exception\BookStackApiException;
use App\Module\Integration\Infrastructure\ApiPlatform\Resource\BookStackShelf;
use App\Module\Integration\Infrastructure\Service\BookStackApiService;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
final readonly class BookStackShelfProvider implements ProviderInterface
{
public function __construct(
private BookStackApiService $bookStackApiService,
) {}
public function provide(Operation $operation, array $uriVariables = [], array $context = []): array
{
try {
$shelves = $this->bookStackApiService->listShelves();
} catch (BookStackApiException $e) {
throw new BadRequestHttpException($e->getMessage(), $e);
}
return array_map(static function (array $shelf): BookStackShelf {
$dto = new BookStackShelf();
$dto->id = $shelf['id'] ?? 0;
$dto->name = $shelf['name'] ?? '';
return $dto;
}, $shelves);
}
}
@@ -0,0 +1,31 @@
<?php
declare(strict_types=1);
namespace App\Module\Integration\Infrastructure\ApiPlatform\State;
use ApiPlatform\Metadata\Operation;
use ApiPlatform\State\ProcessorInterface;
use ApiPlatform\State\ProviderInterface;
use App\Module\Integration\Infrastructure\ApiPlatform\Resource\BookStackTestConnection;
use App\Module\Integration\Infrastructure\Service\BookStackApiService;
final readonly class BookStackTestConnectionProvider implements ProviderInterface, ProcessorInterface
{
public function __construct(
private BookStackApiService $bookStackApiService,
) {}
public function provide(Operation $operation, array $uriVariables = [], array $context = []): BookStackTestConnection
{
return new BookStackTestConnection();
}
public function process(mixed $data, Operation $operation, array $uriVariables = [], array $context = []): BookStackTestConnection
{
$result = new BookStackTestConnection();
$result->success = $this->bookStackApiService->testConnection();
return $result;
}
}
@@ -0,0 +1,42 @@
<?php
declare(strict_types=1);
namespace App\Module\Integration\Infrastructure\ApiPlatform\State;
use ApiPlatform\Metadata\Operation;
use ApiPlatform\State\ProviderInterface;
use App\Module\Integration\Infrastructure\ApiPlatform\Resource\GiteaBranchName;
use App\Module\Integration\Infrastructure\Service\GiteaApiService;
use App\Module\ProjectManagement\Domain\Repository\TaskRepositoryInterface;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
final readonly class GiteaBranchNameProvider implements ProviderInterface
{
/** @see GiteaBranchProcessor::ALLOWED_TYPES */
private const array ALLOWED_TYPES = ['feature', 'fix', 'refactor', 'hotfix', 'chore'];
public function __construct(
private GiteaApiService $giteaApiService,
private TaskRepositoryInterface $taskRepository,
) {}
public function provide(Operation $operation, array $uriVariables = [], array $context = []): GiteaBranchName
{
$task = $this->taskRepository->findById((int) ($uriVariables['taskId'] ?? 0));
if (null === $task) {
throw new NotFoundHttpException('Task not found.');
}
$type = $uriVariables['type'] ?? 'feature';
if (!in_array($type, self::ALLOWED_TYPES, true)) {
throw new BadRequestHttpException('Invalid branch type.');
}
$dto = new GiteaBranchName();
$dto->name = $this->giteaApiService->generateBranchName($task, $type);
return $dto;
}
}
@@ -0,0 +1,54 @@
<?php
declare(strict_types=1);
namespace App\Module\Integration\Infrastructure\ApiPlatform\State;
use ApiPlatform\Metadata\Operation;
use ApiPlatform\State\ProcessorInterface;
use App\Module\Integration\Domain\Exception\GiteaApiException;
use App\Module\Integration\Infrastructure\ApiPlatform\Resource\GiteaBranch;
use App\Module\Integration\Infrastructure\Service\GiteaApiService;
use App\Module\ProjectManagement\Domain\Repository\TaskRepositoryInterface;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
final readonly class GiteaBranchProcessor implements ProcessorInterface
{
private const array ALLOWED_TYPES = ['feature', 'fix', 'refactor', 'hotfix', 'chore'];
public function __construct(
private GiteaApiService $giteaApiService,
private TaskRepositoryInterface $taskRepository,
) {}
public function process(mixed $data, Operation $operation, array $uriVariables = [], array $context = []): GiteaBranch
{
assert($data instanceof GiteaBranch);
$task = $this->taskRepository->findById((int) ($uriVariables['taskId'] ?? 0));
if (null === $task || null === $task->getProject()) {
throw new NotFoundHttpException('Task not found.');
}
$project = $task->getProject();
if (!$project->hasGiteaRepo()) {
throw new BadRequestHttpException('Project has no Gitea repository.');
}
if (!in_array($data->type, self::ALLOWED_TYPES, true)) {
throw new BadRequestHttpException('Invalid branch type.');
}
try {
$branchName = $this->giteaApiService->createBranch($project, $task, $data->type, $data->baseBranch);
} catch (GiteaApiException $e) {
throw new BadRequestHttpException($e->getMessage());
}
$result = new GiteaBranch();
$result->name = $branchName;
return $result;
}
}
@@ -0,0 +1,70 @@
<?php
declare(strict_types=1);
namespace App\Module\Integration\Infrastructure\ApiPlatform\State;
use ApiPlatform\Metadata\Operation;
use ApiPlatform\Metadata\Post;
use ApiPlatform\State\ProviderInterface;
use App\Module\Integration\Domain\Exception\GiteaApiException;
use App\Module\Integration\Infrastructure\ApiPlatform\Resource\GiteaBranch;
use App\Module\Integration\Infrastructure\Service\GiteaApiService;
use App\Module\ProjectManagement\Domain\Repository\TaskRepositoryInterface;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
final readonly class GiteaBranchProvider implements ProviderInterface
{
public function __construct(
private GiteaApiService $giteaApiService,
private TaskRepositoryInterface $taskRepository,
) {}
public function provide(Operation $operation, array $uriVariables = [], array $context = []): array|GiteaBranch
{
if ($operation instanceof Post) {
return new GiteaBranch();
}
$task = $this->taskRepository->findById((int) ($uriVariables['taskId'] ?? 0));
if (null === $task || null === $task->getProject()) {
return [];
}
$project = $task->getProject();
if (!$project->hasGiteaRepo()) {
return [];
}
$taskCode = $project->getCode().'-'.$task->getNumber();
try {
$branches = $this->giteaApiService->listBranches($project, $taskCode);
} catch (GiteaApiException $e) {
throw new BadRequestHttpException($e->getMessage(), $e);
}
$result = [];
foreach ($branches as $branch) {
$dto = new GiteaBranch();
$dto->name = $branch['name'];
try {
$commits = $this->giteaApiService->listBranchCommits($project, $branch['name']);
$dto->commits = array_map(static fn (array $c): array => [
'sha' => substr($c['sha'] ?? '', 0, 7),
'message' => $c['commit']['message'] ?? '',
'author' => $c['commit']['author']['name'] ?? '',
'date' => $c['commit']['author']['date'] ?? $c['created'] ?? '',
], $commits);
} catch (GiteaApiException) {
// Commits fetch failure should not block branch listing
$dto->commits = [];
}
$result[] = $dto;
}
return $result;
}
}
@@ -0,0 +1,60 @@
<?php
declare(strict_types=1);
namespace App\Module\Integration\Infrastructure\ApiPlatform\State;
use ApiPlatform\Metadata\Operation;
use ApiPlatform\State\ProviderInterface;
use App\Module\Integration\Domain\Exception\GiteaApiException;
use App\Module\Integration\Infrastructure\ApiPlatform\Resource\GiteaPullRequest;
use App\Module\Integration\Infrastructure\Service\GiteaApiService;
use App\Module\ProjectManagement\Domain\Repository\TaskRepositoryInterface;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
final readonly class GiteaPullRequestProvider implements ProviderInterface
{
public function __construct(
private GiteaApiService $giteaApiService,
private TaskRepositoryInterface $taskRepository,
) {}
public function provide(Operation $operation, array $uriVariables = [], array $context = []): array
{
$task = $this->taskRepository->findById((int) ($uriVariables['taskId'] ?? 0));
if (null === $task || null === $task->getProject()) {
return [];
}
$project = $task->getProject();
if (!$project->hasGiteaRepo()) {
return [];
}
$taskCode = $project->getCode().'-'.$task->getNumber();
try {
$prs = $this->giteaApiService->listPullRequests($project, $taskCode);
} catch (GiteaApiException $e) {
throw new BadRequestHttpException($e->getMessage(), $e);
}
return array_map(static function (array $pr): GiteaPullRequest {
$dto = new GiteaPullRequest();
$dto->number = $pr['number'] ?? 0;
$dto->title = $pr['title'] ?? '';
$dto->state = $pr['state'] ?? '';
$dto->merged = $pr['merged'] ?? false;
$dto->headBranch = $pr['head']['ref'] ?? '';
$dto->author = $pr['user']['login'] ?? '';
$dto->url = $pr['html_url'] ?? '';
$dto->ciStatuses = array_map(static fn (array $s): array => [
'context' => $s['context'] ?? '',
'status' => $s['status'] ?? '',
'target_url' => $s['target_url'] ?? '',
], $pr['ci_statuses'] ?? []);
return $dto;
}, $prs);
}
}
@@ -0,0 +1,37 @@
<?php
declare(strict_types=1);
namespace App\Module\Integration\Infrastructure\ApiPlatform\State;
use ApiPlatform\Metadata\Operation;
use ApiPlatform\State\ProviderInterface;
use App\Module\Integration\Domain\Exception\GiteaApiException;
use App\Module\Integration\Infrastructure\ApiPlatform\Resource\GiteaRepository;
use App\Module\Integration\Infrastructure\Service\GiteaApiService;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
final readonly class GiteaRepositoryProvider implements ProviderInterface
{
public function __construct(
private GiteaApiService $giteaApiService,
) {}
public function provide(Operation $operation, array $uriVariables = [], array $context = []): array
{
try {
$repos = $this->giteaApiService->listRepositories();
} catch (GiteaApiException $e) {
throw new BadRequestHttpException($e->getMessage(), $e);
}
return array_map(static function (array $repo): GiteaRepository {
$dto = new GiteaRepository();
$dto->fullName = $repo['full_name'] ?? '';
$dto->name = $repo['name'] ?? '';
$dto->owner = $repo['owner']['login'] ?? '';
return $dto;
}, $repos);
}
}
@@ -0,0 +1,47 @@
<?php
declare(strict_types=1);
namespace App\Module\Integration\Infrastructure\ApiPlatform\State;
use ApiPlatform\Metadata\Operation;
use ApiPlatform\State\ProcessorInterface;
use App\Module\Integration\Domain\Entity\GiteaConfiguration;
use App\Module\Integration\Domain\Repository\GiteaConfigurationRepositoryInterface;
use App\Module\Integration\Infrastructure\ApiPlatform\Resource\GiteaSettings;
use App\Service\TokenEncryptor;
use Doctrine\ORM\EntityManagerInterface;
final readonly class GiteaSettingsProcessor implements ProcessorInterface
{
public function __construct(
private EntityManagerInterface $em,
private GiteaConfigurationRepositoryInterface $configRepository,
private TokenEncryptor $tokenEncryptor,
) {}
public function process(mixed $data, Operation $operation, array $uriVariables = [], array $context = []): GiteaSettings
{
assert($data instanceof GiteaSettings);
$config = $this->configRepository->findSingleton();
if (null === $config) {
$config = new GiteaConfiguration();
}
$config->setUrl($data->url);
if (null !== $data->token && '' !== $data->token) {
$config->setEncryptedToken($this->tokenEncryptor->encrypt($data->token));
}
$this->em->persist($config);
$this->em->flush();
$result = new GiteaSettings();
$result->url = $config->getUrl();
$result->hasToken = $config->hasToken();
return $result;
}
}
@@ -0,0 +1,30 @@
<?php
declare(strict_types=1);
namespace App\Module\Integration\Infrastructure\ApiPlatform\State;
use ApiPlatform\Metadata\Operation;
use ApiPlatform\State\ProviderInterface;
use App\Module\Integration\Domain\Repository\GiteaConfigurationRepositoryInterface;
use App\Module\Integration\Infrastructure\ApiPlatform\Resource\GiteaSettings;
final readonly class GiteaSettingsProvider implements ProviderInterface
{
public function __construct(
private GiteaConfigurationRepositoryInterface $configRepository,
) {}
public function provide(Operation $operation, array $uriVariables = [], array $context = []): GiteaSettings
{
$config = $this->configRepository->findSingleton();
$dto = new GiteaSettings();
if (null !== $config) {
$dto->url = $config->getUrl();
$dto->hasToken = $config->hasToken();
}
return $dto;
}
}
@@ -0,0 +1,31 @@
<?php
declare(strict_types=1);
namespace App\Module\Integration\Infrastructure\ApiPlatform\State;
use ApiPlatform\Metadata\Operation;
use ApiPlatform\State\ProcessorInterface;
use ApiPlatform\State\ProviderInterface;
use App\Module\Integration\Infrastructure\ApiPlatform\Resource\GiteaTestConnection;
use App\Module\Integration\Infrastructure\Service\GiteaApiService;
final readonly class GiteaTestConnectionProvider implements ProviderInterface, ProcessorInterface
{
public function __construct(
private GiteaApiService $giteaApiService,
) {}
public function provide(Operation $operation, array $uriVariables = [], array $context = []): GiteaTestConnection
{
return new GiteaTestConnection();
}
public function process(mixed $data, Operation $operation, array $uriVariables = [], array $context = []): GiteaTestConnection
{
$result = new GiteaTestConnection();
$result->success = $this->giteaApiService->testConnection();
return $result;
}
}
@@ -0,0 +1,54 @@
<?php
declare(strict_types=1);
namespace App\Module\Integration\Infrastructure\ApiPlatform\State;
use ApiPlatform\Metadata\Operation;
use ApiPlatform\State\ProcessorInterface;
use App\Module\Integration\Domain\Entity\ShareConfiguration;
use App\Module\Integration\Domain\Repository\ShareConfigurationRepositoryInterface;
use App\Module\Integration\Infrastructure\ApiPlatform\Resource\ShareSettings;
use App\Service\TokenEncryptor;
use Doctrine\ORM\EntityManagerInterface;
final readonly class ShareSettingsProcessor implements ProcessorInterface
{
public function __construct(
private EntityManagerInterface $em,
private ShareConfigurationRepositoryInterface $configRepository,
private TokenEncryptor $tokenEncryptor,
) {}
public function process(mixed $data, Operation $operation, array $uriVariables = [], array $context = []): ShareSettings
{
assert($data instanceof ShareSettings);
$config = $this->configRepository->findSingleton() ?? new ShareConfiguration();
$config->setHost($data->host);
$config->setShareName($data->shareName);
$config->setBasePath($data->basePath);
$config->setDomain($data->domain);
$config->setUsername($data->username);
$config->setEnabled($data->enabled);
if (null !== $data->password && '' !== $data->password) {
$config->setEncryptedPassword($this->tokenEncryptor->encrypt($data->password));
}
$this->em->persist($config);
$this->em->flush();
$result = new ShareSettings();
$result->host = $config->getHost();
$result->shareName = $config->getShareName();
$result->basePath = $config->getBasePath();
$result->domain = $config->getDomain();
$result->username = $config->getUsername();
$result->enabled = $config->isEnabled();
$result->hasPassword = $config->hasPassword();
return $result;
}
}
@@ -0,0 +1,35 @@
<?php
declare(strict_types=1);
namespace App\Module\Integration\Infrastructure\ApiPlatform\State;
use ApiPlatform\Metadata\Operation;
use ApiPlatform\State\ProviderInterface;
use App\Module\Integration\Domain\Repository\ShareConfigurationRepositoryInterface;
use App\Module\Integration\Infrastructure\ApiPlatform\Resource\ShareSettings;
final readonly class ShareSettingsProvider implements ProviderInterface
{
public function __construct(
private ShareConfigurationRepositoryInterface $configRepository,
) {}
public function provide(Operation $operation, array $uriVariables = [], array $context = []): ShareSettings
{
$config = $this->configRepository->findSingleton();
$dto = new ShareSettings();
if (null !== $config) {
$dto->host = $config->getHost();
$dto->shareName = $config->getShareName();
$dto->basePath = $config->getBasePath();
$dto->domain = $config->getDomain();
$dto->username = $config->getUsername();
$dto->enabled = $config->isEnabled();
$dto->hasPassword = $config->hasPassword();
}
return $dto;
}
}
@@ -0,0 +1,34 @@
<?php
declare(strict_types=1);
namespace App\Module\Integration\Infrastructure\ApiPlatform\State;
use ApiPlatform\Metadata\Operation;
use ApiPlatform\State\ProcessorInterface;
use ApiPlatform\State\ProviderInterface;
use App\Module\Integration\Domain\Service\FileSource;
use App\Module\Integration\Infrastructure\ApiPlatform\Resource\ShareTestConnection;
final readonly class ShareTestConnectionProvider implements ProviderInterface, ProcessorInterface
{
public function __construct(
private FileSource $fileSource,
) {}
public function provide(Operation $operation, array $uriVariables = [], array $context = []): ShareTestConnection
{
return new ShareTestConnection();
}
public function process(mixed $data, Operation $operation, array $uriVariables = [], array $context = []): ShareTestConnection
{
$result = $this->fileSource->test();
$dto = new ShareTestConnection();
$dto->success = $result->success;
$dto->message = $result->message;
return $dto;
}
}
@@ -0,0 +1,53 @@
<?php
declare(strict_types=1);
namespace App\Module\Integration\Infrastructure\ApiPlatform\State;
use ApiPlatform\Metadata\Operation;
use ApiPlatform\State\ProcessorInterface;
use App\Module\Integration\Domain\Entity\ZimbraConfiguration;
use App\Module\Integration\Domain\Repository\ZimbraConfigurationRepositoryInterface;
use App\Module\Integration\Infrastructure\ApiPlatform\Resource\ZimbraSettings;
use App\Service\TokenEncryptor;
use Doctrine\ORM\EntityManagerInterface;
final readonly class ZimbraSettingsProcessor implements ProcessorInterface
{
public function __construct(
private EntityManagerInterface $em,
private ZimbraConfigurationRepositoryInterface $configRepository,
private TokenEncryptor $tokenEncryptor,
) {}
public function process(mixed $data, Operation $operation, array $uriVariables = [], array $context = []): ZimbraSettings
{
assert($data instanceof ZimbraSettings);
$config = $this->configRepository->findSingleton();
if (null === $config) {
$config = new ZimbraConfiguration();
}
$config->setServerUrl($data->serverUrl);
$config->setUsername($data->username);
$config->setCalendarPath($data->calendarPath);
$config->setEnabled($data->enabled);
if (null !== $data->password && '' !== $data->password) {
$config->setEncryptedPassword($this->tokenEncryptor->encrypt($data->password));
}
$this->em->persist($config);
$this->em->flush();
$result = new ZimbraSettings();
$result->serverUrl = $config->getServerUrl();
$result->username = $config->getUsername();
$result->calendarPath = $config->getCalendarPath();
$result->enabled = $config->isEnabled();
$result->hasPassword = $config->hasPassword();
return $result;
}
}
@@ -0,0 +1,33 @@
<?php
declare(strict_types=1);
namespace App\Module\Integration\Infrastructure\ApiPlatform\State;
use ApiPlatform\Metadata\Operation;
use ApiPlatform\State\ProviderInterface;
use App\Module\Integration\Domain\Repository\ZimbraConfigurationRepositoryInterface;
use App\Module\Integration\Infrastructure\ApiPlatform\Resource\ZimbraSettings;
final readonly class ZimbraSettingsProvider implements ProviderInterface
{
public function __construct(
private ZimbraConfigurationRepositoryInterface $configRepository,
) {}
public function provide(Operation $operation, array $uriVariables = [], array $context = []): ZimbraSettings
{
$config = $this->configRepository->findSingleton();
$dto = new ZimbraSettings();
if (null !== $config) {
$dto->serverUrl = $config->getServerUrl();
$dto->username = $config->getUsername();
$dto->calendarPath = $config->getCalendarPath();
$dto->enabled = $config->isEnabled();
$dto->hasPassword = $config->hasPassword();
}
return $dto;
}
}
@@ -0,0 +1,37 @@
<?php
declare(strict_types=1);
namespace App\Module\Integration\Infrastructure\ApiPlatform\State;
use ApiPlatform\Metadata\Operation;
use ApiPlatform\State\ProcessorInterface;
use ApiPlatform\State\ProviderInterface;
use App\Module\Integration\Infrastructure\ApiPlatform\Resource\ZimbraTestConnection;
use App\Module\ProjectManagement\Infrastructure\Service\CalDavService;
use Throwable;
final readonly class ZimbraTestConnectionProvider implements ProviderInterface, ProcessorInterface
{
public function __construct(
private CalDavService $calDavService,
) {}
public function provide(Operation $operation, array $uriVariables = [], array $context = []): ZimbraTestConnection
{
return new ZimbraTestConnection();
}
public function process(mixed $data, Operation $operation, array $uriVariables = [], array $context = []): ZimbraTestConnection
{
$result = new ZimbraTestConnection();
try {
$result->success = $this->calDavService->testConnection();
} catch (Throwable) {
$result->success = false;
}
return $result;
}
}