import functools import logging from typing import Any import tornado from tornado import httputil from tornado.web import Application from core.config import ALLOW_SPOTTING, MAX_SPOT_AGE from core.constants import BANDS, PROPAGATION_MODES from core.data_providers import DATA_PROVIDERS from core.enums import Continent, Mode, ModeType from core.utils import safe_json_dumps from data.activities import ACTIVITIES logger = logging.getLogger(__name__) @functools.cache def build_options(): """Build the options data that is returned by the /options API and used to populate the web UI templates. This doesn't change at runtime, so it is built once on first use and then cached.""" # Build a map of activity name -> list of provider names that can submit spots for that activity. Spots with no # activity use the empty string as the key as that's effectively what is in the "blank" entry in the drop-down # on the Add Spot page spot_submit_providers = {} for provider in DATA_PROVIDERS.spot_providers: if not provider.enabled: continue for activity_name in [None, *(activity.name for activity in ACTIVITIES.values())]: if provider.can_submit_spot(activity_name): names = spot_submit_providers.setdefault(activity_name or "", []) if provider.name not in names: names.append(provider.name) # Spot/alert sources are filtered for only ones that are enabled in config, no point letting the user toggle # things that aren't even available. spot_providers = [p.name for p in DATA_PROVIDERS.spot_providers if p.enabled] alert_providers = [p.name for p in DATA_PROVIDERS.alert_providers if p.enabled] callsign_data_providers = [p.name for p in DATA_PROVIDERS.callsign_data_providers if p.enabled] spot_providers_enabled_by_default = [ p.name for p in DATA_PROVIDERS.spot_providers if p.enabled and p.enabled_by_default_in_web_ui ] # If spotting to this server is enabled, "API" is another valid spot source even though it does not come from # one of our providers. if ALLOW_SPOTTING: spot_providers.append("API") spot_providers_enabled_by_default.append("API") return { "bands": BANDS, "modes": [m.value for m in Mode], "mode_types": [t.value for t in ModeType], "activities": list(ACTIVITIES.values()), "spot_providers": spot_providers, "spot_providers_enabled_by_default": spot_providers_enabled_by_default, "alert_providers": alert_providers, "callsign_data_providers": callsign_data_providers, "continents": [c.value for c in Continent], "propagation_modes": list(PROPAGATION_MODES.values()), "max_spot_age": MAX_SPOT_AGE, "spot_allowed": ALLOW_SPOTTING, "spot_submit_providers": spot_submit_providers, } class APIOptionsHandler(tornado.web.RequestHandler): """API request handler for /api/v3/options""" def __init__( self, application: "Application", request: httputil.HTTPServerRequest, **kwargs: Any, ): super().__init__(application, request, **kwargs) def get(self): try: self.write(safe_json_dumps(build_options())) self.set_status(200) self.set_header("Cache-Control", "max-age=3600") self.set_header("Content-Type", "application/json") except Exception: logger.exception("Exception when handling client request to options API") self.write(safe_json_dumps("Error - an internal server error occurred.")) self.set_status(500)