"""Helpers for building vessel device rows from catalog selections.""" from __future__ import annotations from app.core.crypto import decrypt_secret, encrypt_secret from app.agent.asterisk_profiles import ASTERISK_CATALOG_KEYS from app.inventory_catalog import CatalogEntry, catalog_by_key from app.models.inventory import Device, Vessel from app.schemas import DeviceOut, VesselDeviceSelection from app.services.device_defaults import merge_selection_with_defaults def device_to_out(d: Device) -> DeviceOut: out = DeviceOut.model_validate(d) out.has_secret = bool(d.secret_enc) return out def _resolve_address(vessel: Vessel, sel: VesselDeviceSelection) -> str: if sel.address: return sel.address if vessel.public_ip: return vessel.public_ip raise ValueError(f"Device '{sel.catalog_key}' needs an address or vessel public IP") def build_device( vessel: Vessel, sel: VesselDeviceSelection, entry: CatalogEntry, *, existing_secret_plain: str | None = None, ) -> Device: port, username, secret = merge_selection_with_defaults( entry.key, entry, port=sel.port, username=sel.username, secret=sel.secret, existing_secret=existing_secret_plain, ) secret_enc = encrypt_secret(secret) if secret else None return Device( vessel_id=vessel.id, catalog_key=entry.key, name=f"{vessel.name} — {entry.label}", device_type=entry.device_type, address=_resolve_address(vessel, sel), port=port, mcp_server=entry.mcp_server, username=username, secret_enc=secret_enc, notes=sel.notes, ) def validate_selections(selections: list[VesselDeviceSelection]) -> list[tuple[VesselDeviceSelection, CatalogEntry]]: catalog = catalog_by_key() enabled = [s for s in selections if s.enabled] if not enabled: raise ValueError("Select at least one device for this vessel") out: list[tuple[VesselDeviceSelection, CatalogEntry]] = [] seen: set[str] = set() asterisk_picked = False for sel in enabled: entry = catalog.get(sel.catalog_key) if not entry: raise ValueError(f"Unknown catalog key: {sel.catalog_key}") if sel.catalog_key in seen: raise ValueError(f"Duplicate device slot: {sel.catalog_key}") if sel.catalog_key in ASTERISK_CATALOG_KEYS: if asterisk_picked: raise ValueError( "Only one Asterisk slot per vessel — pick GeneseasX (docker voip) OR TMGeneseas/satbox (native)" ) asterisk_picked = True seen.add(sel.catalog_key) out.append((sel, entry)) return out