This repository has been archived on 2026-10-28. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
karldns.de/router_dyndns/routers/api.py
T

109 lines
4.1 KiB
Python

from __future__ import annotations
from fastapi import APIRouter, HTTPException, Request, Response, status
from starlette.concurrency import run_in_threadpool
from ..ddns_dns import DnsProviderError
from ..ddns_service import BadIpError
from ..dependencies import ServiceDep, StoreDep
from ..schemas import (
ErrorResponse,
HostnameCredentials,
MagicHostnameRequest,
ManagedRecord,
RouterUpdateResponse,
_credentials_response,
_managed_record,
)
from .path_params import ManagementSlugPath, UpdateSlugPath
router = APIRouter(prefix="/api/v1")
@router.post(
"/hostnames/magic",
response_model=HostnameCredentials,
status_code=status.HTTP_201_CREATED,
summary="Create a KarlDNS hostname",
tags=["hostnames"],
responses={500: {"model": ErrorResponse}},
)
async def create_magic_hostname(payload: MagicHostnameRequest, service: ServiceDep) -> HostnameCredentials:
account = await run_in_threadpool(service.create_managed_account, payload.username)
return _credentials_response(service, account)
@router.get(
"/management/{management_slug}",
response_model=ManagedRecord,
summary="Read a hostname by private dashboard link",
description="Uses the private dashboard link secret. Dashboard passwords protect only the web dashboard, not this API endpoint.",
tags=["management"],
responses={404: {"model": ErrorResponse}},
)
async def read_managed_hostname(management_slug: ManagementSlugPath, store: StoreDep, service: ServiceDep) -> ManagedRecord:
account = await run_in_threadpool(store.get_account_by_management_slug, management_slug)
if not account or account["disabled"]:
raise HTTPException(status_code=404, detail="not found")
return _managed_record(service, account)
@router.delete(
"/management/{management_slug}",
status_code=status.HTTP_204_NO_CONTENT,
summary="Delete a hostname by private dashboard link",
description="Uses the private dashboard link secret. Dashboard passwords protect only the web dashboard, not this API endpoint.",
tags=["management"],
responses={404: {"model": ErrorResponse}, 500: {"model": ErrorResponse}},
)
async def delete_managed_hostname(management_slug: ManagementSlugPath, store: StoreDep, service: ServiceDep) -> Response:
account = await run_in_threadpool(store.get_account_by_management_slug, management_slug)
if not account or account["disabled"]:
raise HTTPException(status_code=404, detail="not found")
hostname = str(account["hostname"])
try:
await run_in_threadpool(service.delete_account, hostname, "api")
except DnsProviderError as exc:
raise HTTPException(status_code=500, detail="DNS delete failed. Hostname was not deleted.") from exc
return Response(status_code=status.HTTP_204_NO_CONTENT)
@router.get(
"/updates/{update_slug}",
response_model=RouterUpdateResponse,
summary="Router-compatible update endpoint",
tags=["updates"],
responses={404: {"model": ErrorResponse}, 500: {"model": ErrorResponse}},
)
async def update_hostname(
update_slug: UpdateSlugPath,
request: Request,
store: StoreDep,
service: ServiceDep,
myip: str | None = None,
ipaddr: str | None = None,
myipv6: str | None = None,
ip6addr: str | None = None,
) -> RouterUpdateResponse:
account = await run_in_threadpool(store.get_account_by_slug, update_slug)
if not account or account["disabled"]:
raise HTTPException(status_code=404, detail="not found")
hostname = str(account["hostname"])
try:
outcome = await run_in_threadpool(
service.apply_update,
request,
hostname,
myip,
ipaddr,
myipv6,
ip6addr,
)
except BadIpError as exc:
raise HTTPException(status_code=400, detail=str(exc)) from exc
except DnsProviderError as exc:
raise HTTPException(status_code=500, detail="DNS update failed") from exc
result = outcome.result
return RouterUpdateResponse(status="good" if result.changed else "nochg", hostname=hostname, ip=result.ipv4 or result.ipv6 or "")