Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions appinfo/info.xml
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,9 @@ Vrij en open source onder de EUPL-licentie.
<job>OCA\OpenRegister\BackgroundJob\CronFileTextExtractionJob</job>
<job>OCA\OpenRegister\Cron\WebhookRetryJob</job>
<job>OCA\OpenRegister\BackgroundJob\BlobMigrationJob</job>
<job>OCA\OpenRegister\BackgroundJob\DestructionCheckJob</job>
<job>OCA\OpenRegister\Cron\TransferCheckJob</job>
<job>OCA\OpenRegister\BackgroundJob\TransferExecutionJob</job>
</background-jobs>

<navigations>
Expand Down
130 changes: 130 additions & 0 deletions lib/BackgroundJob/TransferExecutionJob.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
<?php

/**
* OpenRegister Transfer Execution Job
*
* Queued background job that executes e-Depot transfers for approved transfer lists.
*
* @category BackgroundJob
* @package OCA\OpenRegister\BackgroundJob
*
* @author Conduction Development Team <info@conduction.nl>
* @copyright 2024 Conduction B.V.
* @license EUPL-1.2 https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
*
* @version GIT: <git_id>
*
* @link https://www.OpenRegister.app
*/

declare(strict_types=1);

namespace OCA\OpenRegister\BackgroundJob;

use OCA\OpenRegister\Service\Edepot\EdepotTransferService;
use OCA\OpenRegister\Service\Edepot\Transport\OpenConnectorTransport;
use OCA\OpenRegister\Service\Edepot\Transport\RestApiTransport;
use OCA\OpenRegister\Service\Edepot\Transport\SftpTransport;
use OCA\OpenRegister\Service\Edepot\Transport\TransportInterface;
use OCP\AppFramework\Utility\ITimeFactory;
use OCP\BackgroundJob\QueuedJob;
use OCP\IAppConfig;
use Psr\Log\LoggerInterface;

/**
* Queued job for executing e-Depot transfers.
*
* Picks up approved transfer lists and runs the full transfer pipeline
* (SIP build, transport, status update).
*
* @psalm-suppress UnusedClass
*
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
*/
class TransferExecutionJob extends QueuedJob
{
/**
* Constructor.
*
* @param ITimeFactory $time The time factory.
* @param EdepotTransferService $transferService The transfer service.
* @param SftpTransport $sftpTransport SFTP transport.
* @param RestApiTransport $restTransport REST API transport.
* @param OpenConnectorTransport $ocTransport OpenConnector transport.
* @param IAppConfig $appConfig The app configuration.
* @param LoggerInterface $logger Logger.
*/
public function __construct(
ITimeFactory $time,
private readonly EdepotTransferService $transferService,
private readonly SftpTransport $sftpTransport,
private readonly RestApiTransport $restTransport,
private readonly OpenConnectorTransport $ocTransport,
private readonly IAppConfig $appConfig,
private readonly LoggerInterface $logger,
) {
parent::__construct(time: $time);
}//end __construct()

/**
* Execute the transfer job.
*
* @param mixed $argument Job arguments containing the transfer list data.
*
* @return void
*/
protected function run(mixed $argument): void
{
if (is_array($argument) === false || empty($argument['transferList']) === true) {
$this->logger->error(
message: '[TransferExecutionJob] Invalid job argument: missing transferList'
);
return;
}

$transferList = $argument['transferList'];

$this->logger->info(
message: '[TransferExecutionJob] Starting transfer execution',
context: ['transferUuid' => ($transferList['uuid'] ?? 'unknown')]
);

try {
$transport = $this->resolveTransport();
$this->transferService->executeTransfer($transferList, $transport);

$this->logger->info(
message: '[TransferExecutionJob] Transfer execution completed',
context: ['transferUuid' => ($transferList['uuid'] ?? 'unknown')]
);
} catch (\Exception $e) {
$this->logger->error(
message: '[TransferExecutionJob] Transfer execution failed',
context: [
'transferUuid' => ($transferList['uuid'] ?? 'unknown'),
'error' => $e->getMessage(),
]
);
}
}//end run()

/**
* Resolve the configured transport implementation.
*
* @return TransportInterface The transport to use.
*/
private function resolveTransport(): TransportInterface
{
$transportType = $this->appConfig->getValueString('openregister', 'edepot_transport', 'rest_api');

switch ($transportType) {
case 'sftp':
return $this->sftpTransport;
case 'openconnector':
return $this->ocTransport;
case 'rest_api':
default:
return $this->restTransport;
}
}//end resolveTransport()
}//end class
228 changes: 228 additions & 0 deletions lib/Controller/Settings/EdepotSettingsController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,228 @@
<?php

/**
* OpenRegister e-Depot Settings Controller
*
* Handles e-Depot endpoint configuration and connection testing.
*
* @category Controller
* @package OCA\OpenRegister\Controller\Settings
*
* @author Conduction Development Team <info@conduction.nl>
* @copyright 2024 Conduction B.V.
* @license EUPL-1.2 https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
*
* @version GIT: <git_id>
*
* @link https://www.OpenRegister.app
*/

declare(strict_types=1);

namespace OCA\OpenRegister\Controller\Settings;

use OCA\OpenRegister\Service\Edepot\EdepotTransferService;
use OCA\OpenRegister\Service\Edepot\Transport\OpenConnectorTransport;
use OCA\OpenRegister\Service\Edepot\Transport\RestApiTransport;
use OCA\OpenRegister\Service\Edepot\Transport\SftpTransport;
use OCA\OpenRegister\Service\Edepot\Transport\TransportInterface;
use OCP\AppFramework\Controller;
use OCP\AppFramework\Http\JSONResponse;
use OCP\IAppConfig;
use OCP\IRequest;
use Psr\Log\LoggerInterface;

/**
* Controller for e-Depot settings management.
*
* Provides endpoints for configuring the e-Depot endpoint, transport protocol,
* authentication, and SIP profile. Also supports connection testing.
*
* @psalm-suppress UnusedClass
*
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
*/
class EdepotSettingsController extends Controller
{
/**
* Constructor.
*
* @param string $appName The app name.
* @param IRequest $request The request.
* @param IAppConfig $appConfig The app configuration.
* @param EdepotTransferService $transferService The transfer service.
* @param SftpTransport $sftpTransport SFTP transport.
* @param RestApiTransport $restTransport REST API transport.
* @param OpenConnectorTransport $ocTransport OpenConnector transport.
* @param LoggerInterface $logger Logger.
*/
public function __construct(
$appName,
IRequest $request,
private readonly IAppConfig $appConfig,
private readonly EdepotTransferService $transferService,
private readonly SftpTransport $sftpTransport,
private readonly RestApiTransport $restTransport,
private readonly OpenConnectorTransport $ocTransport,
private readonly LoggerInterface $logger,
) {
parent::__construct(appName: $appName, request: $request);
}//end __construct()

/**
* Get e-Depot settings.
*
* @NoCSRFRequired
*
* @return JSONResponse The current e-Depot configuration.
*/
public function getEdepotSettings(): JSONResponse
{
try {
$config = $this->transferService->getTransportConfig();

// Mask sensitive values.
if (empty($config['apiKey']) === false) {
$config['apiKey'] = '***';
}

if (empty($config['bearerToken']) === false) {
$config['bearerToken'] = '***';
}

if (empty($config['password']) === false) {
$config['password'] = '***';
}

$config['availableProfiles'] = $this->transferService->getAvailableProfiles();

return new JSONResponse(data: $config);
} catch (\Exception $e) {
return new JSONResponse(data: ['error' => $e->getMessage()], statusCode: 500);
}//end try
}//end getEdepotSettings()

/**
* Update e-Depot settings.
*
* @NoCSRFRequired
*
* @return JSONResponse The update result.
*/
public function updateEdepotSettings(): JSONResponse
{
try {
$params = $this->request->getParams();

// Validate SIP profile.
$sipProfile = ($params['sipProfile'] ?? 'default');
if ($this->transferService->isValidProfile($sipProfile) === false) {
$available = implode(', ', array_keys($this->transferService->getAvailableProfiles()));
return new JSONResponse(
data: ['error' => "Invalid SIP profile '{$sipProfile}'. Available: {$available}"],
statusCode: 400
);
}

// Store configuration values.
$configMap = [
'endpointUrl' => 'edepot_endpoint_url',
'authenticationType' => 'edepot_auth_type',
'apiKey' => 'edepot_api_key',
'bearerToken' => 'edepot_bearer_token',
'targetArchive' => 'edepot_target_archive',
'sipProfile' => 'edepot_sip_profile',
'transport' => 'edepot_transport',
'host' => 'edepot_sftp_host',
'port' => 'edepot_sftp_port',
'username' => 'edepot_sftp_username',
'password' => 'edepot_sftp_password',
'keyPath' => 'edepot_sftp_key_path',
'remotePath' => 'edepot_sftp_remote_path',
'sourceId' => 'edepot_openconnector_source_id',
'baseUrl' => 'edepot_openconnector_base_url',
];

foreach ($configMap as $paramKey => $configKey) {
if (isset($params[$paramKey]) === true) {
$value = (string) $params[$paramKey];
// Skip masked values (don't overwrite secrets with '***').
if ($value === '***') {
continue;
}

$this->appConfig->setValueString('openregister', $configKey, $value);
}
}

// Test connection if requested.
$testResult = null;
if (isset($params['testConnection']) === true && $params['testConnection'] === true) {
$transport = $this->resolveTransport(type: ($params['transport'] ?? 'rest_api'));
$config = $this->transferService->getTransportConfig();
$testResult = $transport->testConnection($config);
}

$response = ['success' => true];
if ($testResult !== null) {
$response['connectionTest'] = $testResult;
}

return new JSONResponse(data: $response);
} catch (\Exception $e) {
return new JSONResponse(data: ['error' => $e->getMessage()], statusCode: 500);
}//end try
}//end updateEdepotSettings()

/**
* Test e-Depot connection.
*
* @NoCSRFRequired
*
* @return JSONResponse The connection test result.
*/
public function testEdepotConnection(): JSONResponse
{
try {
$config = $this->transferService->getTransportConfig();
$transport = $this->resolveTransport(type: ($config['transport'] ?? 'rest_api'));
$result = $transport->testConnection($config);

return new JSONResponse(
data: [
'success' => $result,
'transport' => $transport->getName(),
'message' => ($result === true) ? 'Connection successful' : 'Connection failed',
]
);
} catch (\Exception $e) {
return new JSONResponse(
data: [
'success' => false,
'error' => $e->getMessage(),
],
statusCode: 500
);
}//end try
}//end testEdepotConnection()

/**
* Resolve transport implementation by type name.
*
* @param string $type The transport type.
*
* @return TransportInterface The transport.
*/
private function resolveTransport(string $type): TransportInterface
{
switch ($type) {
case 'sftp':
return $this->sftpTransport;
case 'openconnector':
return $this->ocTransport;
case 'rest_api':
default:
return $this->restTransport;
}
}//end resolveTransport()
}//end class
Loading
Loading