lthn.io/app/Mod/Names/Actions/UpdateDnsRecords.php
Claude 3f294340b2
feat: UpdateDnsRecords Action, Prometheus metrics, JSON validation
- UpdateDnsRecords Action: controller method now one-liner, all DNS
  logic in Action with activity logging and edit lock.
- Prometheus metrics at /v1/metrics: chain_height, alias_count,
  claims_pending, dns_tickets, gateways_live. Grafana-ready.
- ValidateJsonRequest middleware: enforces application/json on POST,
  64KB body size limit. Applied to all /v1/* API routes.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 12:12:07 +01:00

91 lines
2.5 KiB
PHP

<?php
declare(strict_types=1);
namespace Mod\Names\Actions;
use Illuminate\Support\Facades\Cache;
use Illuminate\Validation\ValidationException;
use Mod\Chain\Services\DaemonRpc;
use Mod\Chain\Services\WalletRpc;
use Mod\Names\Models\DnsTicket;
use Mod\Names\Models\NameActivity;
/**
* Update DNS records for a .lthn name on the sidechain.
*
* $ticket = UpdateDnsRecords::run('mybrand', [
* ['type' => 'A', 'host' => '@', 'value' => '1.2.3.4'],
* ]);
*/
class UpdateDnsRecords
{
public function __construct(
private readonly DaemonRpc $rpc,
private readonly WalletRpc $wallet,
) {}
public function handle(string $name, array $records): DnsTicket
{
$name = strtolower(trim($name));
// Edit lock
$editLock = "dns_edit_lock:{$name}";
if (Cache::has($editLock)) {
throw ValidationException::withMessages([
'name' => 'A DNS update for this name is already pending.',
]);
}
$alias = $this->rpc->getAliasByName($name);
if (! $alias) {
throw ValidationException::withMessages([
'name' => 'Name not registered.',
]);
}
$address = $alias['address'] ?? '';
$comment = $this->buildComment($records);
$result = $this->wallet->updateAlias($name, $address, $comment);
if (isset($result['tx_id'])) {
Cache::put($editLock, true, 300);
$ticket = DnsTicket::open($name, $records, $result['tx_id'], $address, $comment);
} else {
$ticket = DnsTicket::open($name, $records, null, $address, $comment);
}
NameActivity::log($name, 'dns_updated', [
'ticket_id' => $ticket->ticket_id,
'records' => $records,
], request()?->ip());
return $ticket;
}
private function buildComment(array $records): string
{
$dnsEntries = [];
foreach ($records as $record) {
$type = $record['type'] ?? 'A';
$host = $record['host'] ?? '@';
$value = $record['value'] ?? '';
if ($value) {
$dnsEntries[] = "{$type}:{$host}:{$value}";
}
}
$comment = 'v=lthn1;type=user';
if ($dnsEntries) {
$comment .= ';dns=' . implode('|', $dnsEntries);
}
return $comment;
}
public static function run(string $name, array $records): DnsTicket
{
return app(static::class)->handle($name, $records);
}
}