lthn.io/app/Mod/Names/Controllers/NamesController.php

194 lines
5.8 KiB
PHP
Raw Normal View History

<?php
declare(strict_types=1);
namespace Mod\Names\Controllers;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Routing\Controller;
use Mod\Chain\Services\DaemonRpc;
use Mod\Chain\Services\WalletRpc;
/**
* .lthn TLD registrar API.
*
* GET /v1/names/available/{name} check if name is available
* GET /v1/names/lookup/{name} look up a registered name
* GET /v1/names/search?q={query} search names
* POST /v1/names/register request name registration
* GET /v1/names/directory list all names grouped by type
*/
class NamesController extends Controller
{
public function __construct(
private readonly DaemonRpc $rpc,
private readonly WalletRpc $wallet,
) {}
/**
* GET /v1/names/available/myname
*/
public function available(string $name): JsonResponse
{
$name = strtolower(trim($name));
if (! $this->isValidName($name)) {
return response()->json([
'available' => false,
'reason' => 'Invalid name. Use 1-63 lowercase alphanumeric characters.',
]);
}
$alias = $this->rpc->getAliasByName($name);
return response()->json([
'name' => $name,
'available' => $alias === null,
'fqdn' => "{$name}.lthn",
]);
}
/**
* GET /v1/names/lookup/charon
*/
public function lookup(string $name): JsonResponse
{
$alias = $this->rpc->getAliasByName(strtolower(trim($name)));
if (! $alias) {
return response()->json(['error' => 'Name not registered'], 404);
}
return response()->json([
'name' => $name,
'fqdn' => "{$name}.lthn",
'address' => $alias['address'] ?? '',
'comment' => $alias['comment'] ?? '',
'registered' => true,
]);
}
/**
* GET /v1/names/search?q=gate
*/
public function search(Request $request): JsonResponse
{
$query = strtolower(trim((string) $request->get('q')));
$result = $this->rpc->getAllAliases();
$aliases = $result['aliases'] ?? [];
$matches = array_filter($aliases, function ($alias) use ($query) {
return str_contains($alias['alias'] ?? '', $query)
|| str_contains($alias['comment'] ?? '', $query);
});
return response()->json([
'query' => $query,
'results' => array_values($matches),
'count' => count($matches),
]);
}
/**
* GET /v1/names/directory
*/
public function directory(): JsonResponse
{
$result = $this->rpc->getAllAliases();
$aliases = $result['aliases'] ?? [];
// Group by type from comment metadata
$grouped = ['gateway' => [], 'service' => [], 'exit' => [], 'reserved' => [], 'user' => []];
foreach ($aliases as $alias) {
$comment = $alias['comment'] ?? '';
if (str_contains($comment, 'type=gateway')) {
$grouped['gateway'][] = $alias;
} elseif (str_contains($comment, 'type=exit')) {
$grouped['exit'][] = $alias;
} elseif (str_contains($comment, 'type=service')) {
$grouped['service'][] = $alias;
} elseif (str_contains($comment, 'type=reserved')) {
$grouped['reserved'][] = $alias;
} else {
$grouped['user'][] = $alias;
}
}
return response()->json([
'total' => count($aliases),
'directory' => $grouped,
]);
}
/**
* POST /v1/names/register {"name": "mysite", "address": "iTHN..."}
*/
public function register(Request $request): JsonResponse
{
$name = strtolower(trim((string) $request->input('name')));
$address = trim((string) $request->input('address'));
$comment = trim((string) $request->input('comment', 'v=lthn1;type=user'));
if (! $this->isValidName($name)) {
return response()->json([
'error' => 'Invalid name. Use 1-63 lowercase alphanumeric characters.',
], 422);
}
if (empty($address) || ! str_starts_with($address, 'iTHN')) {
return response()->json([
'error' => 'Invalid Lethean address.',
], 422);
}
// Check availability on chain
$existing = $this->rpc->getAliasByName($name);
if ($existing !== null) {
return response()->json([
'error' => 'Name already registered.',
'name' => $name,
], 409);
}
// Register via wallet RPC
$result = $this->wallet->registerAlias($name, $address, $comment);
if (isset($result['code']) || isset($result['error'])) {
$message = $result['message'] ?? ($result['error'] ?? 'Unknown error');
$code = 502;
if (str_contains($message, 'NOT_ENOUGH_MONEY')) {
$message = 'Registrar wallet has insufficient funds. Please try again later.';
$code = 503;
}
return response()->json([
'error' => $message,
'name' => $name,
], $code);
}
if (isset($result['tx_id'])) {
return response()->json([
'name' => $name,
'fqdn' => "{$name}.lthn",
'address' => $address,
'tx_id' => $result['tx_id'],
'status' => 'pending',
], 201);
}
return response()->json([
'error' => 'Unexpected response from chain',
'details' => $result,
], 500);
}
private function isValidName(string $name): bool
{
return (bool) preg_match('/^[a-z0-9][a-z0-9-]{0,62}$/', $name);
}
}