import json import logging import re from datetime import datetime import pytz import tornado from core.constants import SIGS from core.prometheus_metrics_handler import api_requests_counter from core.sig_utils import get_ref_regex_for_sig, populate_sig_ref_info from core.utils import serialize_everything from data.sig_ref import SIGRef from data.spot import Spot # API request handler for /api/v1/lookup/call class APILookupCallHandler(tornado.web.RequestHandler): def initialize(self, web_server_metrics): self.web_server_metrics = web_server_metrics def get(self): try: # Metrics self.web_server_metrics["last_api_access_time"] = datetime.now(pytz.UTC) self.web_server_metrics["api_access_counter"] += 1 self.web_server_metrics["status"] = "OK" api_requests_counter.inc() # request.arguments contains lists for each param key because technically the client can supply multiple, # reduce that to just the first entry, and convert bytes to string query_params = {k: v[0].decode("utf-8") for k, v in self.request.arguments.items()} # The "call" query param must exist and look like a callsign if "call" in query_params.keys(): call = query_params.get("call").upper() if re.match(r"^[A-Z0-9/\-]*$", call): # Take the callsign, make a "fake spot" so we can run infer_missing() on it, then repack the # resulting data in the correct way for the API response. fake_spot = Spot(dx_call=call) fake_spot.infer_missing() data = { "call": call, "name": fake_spot.dx_name, "qth": fake_spot.dx_qth, "country": fake_spot.dx_country, "flag": fake_spot.dx_flag, "continent": fake_spot.dx_continent, "dxcc_id": fake_spot.dx_dxcc_id, "cq_zone": fake_spot.dx_cq_zone, "itu_zone": fake_spot.dx_itu_zone, "grid": fake_spot.dx_grid, "latitude": fake_spot.dx_latitude, "longitude": fake_spot.dx_longitude, "location_source": fake_spot.dx_location_source } self.write(json.dumps(data, default=serialize_everything)) else: self.write(json.dumps("Error - '" + call + "' does not look like a valid callsign.", default=serialize_everything)) self.set_status(422) else: self.write(json.dumps("Error - call must be provided", default=serialize_everything)) self.set_status(422) except Exception as e: logging.error(e) self.write(json.dumps("Error - " + str(e), default=serialize_everything)) self.set_status(500) self.set_header("Cache-Control", "no-store") self.set_header("Content-Type", "application/json") # API request handler for /api/v1/lookup/sigref class APILookupSIGRefHandler(tornado.web.RequestHandler): def initialize(self, web_server_metrics): self.web_server_metrics = web_server_metrics def get(self): try: # Metrics self.web_server_metrics["last_api_access_time"] = datetime.now(pytz.UTC) self.web_server_metrics["api_access_counter"] += 1 self.web_server_metrics["status"] = "OK" api_requests_counter.inc() # request.arguments contains lists for each param key because technically the client can supply multiple, # reduce that to just the first entry, and convert bytes to string query_params = {k: v[0].decode("utf-8") for k, v in self.request.arguments.items()} # "sig" and "id" query params must exist, SIG must be known, and if we have a reference regex for that SIG, # the provided id must match it. if "sig" in query_params.keys() and "id" in query_params.keys(): sig = query_params.get("sig").upper() id = query_params.get("id").upper() if sig in list(map(lambda p: p.name, SIGS)): if not get_ref_regex_for_sig(sig) or re.match(get_ref_regex_for_sig(sig), id): data = populate_sig_ref_info(SIGRef(id=id, sig=sig)) self.write(json.dumps(data, default=serialize_everything)) else: self.write( json.dumps("Error - '" + id + "' does not look like a valid reference ID for " + sig + ".", default=serialize_everything)) self.set_status(422) else: self.write(json.dumps("Error - sig '" + sig + "' is not known.", default=serialize_everything)) self.set_status(422) else: self.write(json.dumps("Error - sig and id must be provided", default=serialize_everything)) self.set_status(422) except Exception as e: logging.error(e) self.write(json.dumps("Error - " + str(e), default=serialize_everything)) self.set_status(500) self.set_header("Cache-Control", "no-store") self.set_header("Content-Type", "application/json")