236 lines
7.8 KiB
PHP
236 lines
7.8 KiB
PHP
<?php
|
|
|
|
namespace App\Vito\Plugins\LiittleCookie\VitoTechnitiumDns\Actions;
|
|
|
|
use App\Vito\Plugins\LiittleCookie\VitoTechnitiumDns\Services\RecordMapper;
|
|
use App\Vito\Plugins\LiittleCookie\VitoTechnitiumDns\Services\TechnitiumClient;
|
|
use Illuminate\Http\Client\Response;
|
|
use Illuminate\Support\Facades\Log;
|
|
use Illuminate\Validation\ValidationException;
|
|
use Throwable;
|
|
|
|
class ManageRecords
|
|
{
|
|
private const int DEFAULT_TTL = 3600;
|
|
|
|
public function __construct(private readonly TechnitiumClient $client) {}
|
|
|
|
/**
|
|
* List all records for a zone.
|
|
*
|
|
* @return array<int, array<string, mixed>>
|
|
*
|
|
* @throws \RuntimeException
|
|
*/
|
|
public function list(string $domainId): array
|
|
{
|
|
$response = $this->client->get('zones/records/get', [
|
|
'domain' => $domainId,
|
|
'listZone' => 'true',
|
|
]);
|
|
|
|
if (! $this->client->isSuccessful($response)) {
|
|
Log::error('Failed to fetch Technitium DNS records', [
|
|
'domainId' => $domainId,
|
|
'response' => $response->json(),
|
|
]);
|
|
|
|
throw new \RuntimeException(
|
|
'Failed to fetch DNS records: '
|
|
.($response->json('errorMessage') ?? 'Unknown error')
|
|
);
|
|
}
|
|
|
|
$records = $this->client->responseData($response, 'records') ?? [];
|
|
|
|
return collect($records)
|
|
// Skip SOA records — they are managed by Technitium itself
|
|
->reject(fn (array $r) => strtoupper($r['type'] ?? '') === 'SOA')
|
|
->map(function (array $record) {
|
|
$type = $record['type'] ?? '';
|
|
$rData = $record['rData'] ?? [];
|
|
$domain = $record['name'] ?? '';
|
|
|
|
return [
|
|
'id' => RecordMapper::encodeRecordId($domain, $type, $rData),
|
|
'type' => $type,
|
|
'name' => $domain,
|
|
'content' => RecordMapper::rDataToContent($type, $rData),
|
|
'ttl' => $record['ttl'] ?? self::DEFAULT_TTL,
|
|
'proxied' => false,
|
|
'created_on' => null,
|
|
'modified_on' => null,
|
|
];
|
|
})
|
|
->values()
|
|
->toArray();
|
|
}
|
|
|
|
/**
|
|
* Create a new DNS record.
|
|
*
|
|
* @return array<string, mixed>
|
|
*
|
|
* @throws ValidationException
|
|
*/
|
|
public function create(string $domainId, array $input): array
|
|
{
|
|
try {
|
|
$type = $input['type'];
|
|
$name = $input['name'];
|
|
$content = $input['content'];
|
|
$ttl = $input['ttl'] ?? self::DEFAULT_TTL;
|
|
$priority = $input['prio'] ?? null;
|
|
|
|
$params = array_merge(
|
|
[
|
|
'domain' => $name,
|
|
'zone' => $domainId,
|
|
'type' => $type,
|
|
'ttl' => $ttl,
|
|
],
|
|
RecordMapper::contentToApiParams($type, $content, $priority),
|
|
);
|
|
|
|
$response = $this->client->get('zones/records/add', $params);
|
|
|
|
$this->ensureSuccessful($response, 'create', [
|
|
'domainId' => $domainId,
|
|
'input' => $input,
|
|
]);
|
|
|
|
// Build the rData from the content to create a valid record ID
|
|
$rData = RecordMapper::contentToApiParams($type, $content, $priority);
|
|
|
|
return [
|
|
'id' => RecordMapper::encodeRecordId($name, $type, $rData),
|
|
'type' => $type,
|
|
'name' => $name,
|
|
'content' => $content,
|
|
'ttl' => $ttl,
|
|
'proxied' => false,
|
|
];
|
|
} catch (ValidationException $e) {
|
|
throw $e;
|
|
} catch (Throwable $e) {
|
|
Log::error('Technitium createRecord exception', ['error' => $e->getMessage()]);
|
|
throw ValidationException::withMessages(['record' => 'Failed to create DNS record: '.$e->getMessage()]);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Update an existing DNS record.
|
|
*
|
|
* Technitium requires specifying the OLD record values to identify the record,
|
|
* plus the NEW values. The composite record ID contains the old values.
|
|
*
|
|
* @return array<string, mixed>
|
|
*
|
|
* @throws ValidationException
|
|
*/
|
|
public function update(string $domainId, string $recordId, array $input): array
|
|
{
|
|
try {
|
|
$old = RecordMapper::decodeRecordId($recordId);
|
|
|
|
$type = $input['type'];
|
|
$name = $input['name'];
|
|
$content = $input['content'];
|
|
$ttl = $input['ttl'] ?? self::DEFAULT_TTL;
|
|
$priority = $input['prio'] ?? null;
|
|
|
|
$params = array_merge(
|
|
[
|
|
'domain' => $old['domain'],
|
|
'zone' => $domainId,
|
|
'type' => $old['type'],
|
|
'ttl' => $ttl,
|
|
// New domain name if it changed
|
|
'newDomain' => $name,
|
|
],
|
|
// Old record values for identification
|
|
RecordMapper::oldRecordParams($old['type'], $old['rData']),
|
|
// New record values
|
|
RecordMapper::contentToApiParams($type, $content, $priority),
|
|
);
|
|
|
|
$response = $this->client->get('zones/records/update', $params);
|
|
|
|
$this->ensureSuccessful($response, 'update', [
|
|
'domainId' => $domainId,
|
|
'recordId' => $recordId,
|
|
'input' => $input,
|
|
]);
|
|
|
|
$newRData = RecordMapper::contentToApiParams($type, $content, $priority);
|
|
|
|
return [
|
|
'id' => RecordMapper::encodeRecordId($name, $type, $newRData),
|
|
'type' => $type,
|
|
'name' => $name,
|
|
'content' => $content,
|
|
'ttl' => $ttl,
|
|
'proxied' => false,
|
|
];
|
|
} catch (ValidationException $e) {
|
|
throw $e;
|
|
} catch (Throwable $e) {
|
|
Log::error('Technitium updateRecord exception', ['error' => $e->getMessage()]);
|
|
throw ValidationException::withMessages(['record' => 'Failed to update DNS record: '.$e->getMessage()]);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Delete a DNS record.
|
|
*/
|
|
public function delete(string $domainId, string $recordId): bool
|
|
{
|
|
try {
|
|
$old = RecordMapper::decodeRecordId($recordId);
|
|
|
|
$params = array_merge(
|
|
[
|
|
'domain' => $old['domain'],
|
|
'zone' => $domainId,
|
|
'type' => $old['type'],
|
|
],
|
|
RecordMapper::deleteParams($old['type'], $old['rData']),
|
|
);
|
|
|
|
$response = $this->client->get('zones/records/delete', $params);
|
|
|
|
if (! $this->client->isSuccessful($response)) {
|
|
Log::error('Failed to delete Technitium DNS record', [
|
|
'domainId' => $domainId,
|
|
'recordId' => $recordId,
|
|
'response' => $response->json(),
|
|
]);
|
|
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
} catch (Throwable $e) {
|
|
Log::error('Technitium deleteRecord exception', ['error' => $e->getMessage()]);
|
|
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @throws ValidationException
|
|
*/
|
|
private function ensureSuccessful(Response $response, string $operation, array $context): void
|
|
{
|
|
if (! $this->client->isSuccessful($response)) {
|
|
Log::error("Failed to {$operation} Technitium DNS record", array_merge($context, [
|
|
'response' => $response->json(),
|
|
]));
|
|
|
|
throw ValidationException::withMessages([
|
|
'record' => "Failed to {$operation} DNS record: ".($response->json('errorMessage') ?? 'Unknown error'),
|
|
]);
|
|
}
|
|
}
|
|
}
|