mirror of
https://git.ianrenton.com/ian/spothole.git
synced 2026-08-08 19:41:41 +00:00
@@ -12,12 +12,12 @@ from tornado.web import Application
|
||||
|
||||
from core.config import ALLOW_SPOTTING, ALLOW_UPSTREAM_SPOTTING, RECAPTCHA_SECRET_KEY
|
||||
from core.constants import UNKNOWN_BAND
|
||||
from core.utils import infer_band_from_freq
|
||||
from core.prometheus_metrics_handler import api_requests_counter
|
||||
from core.sig_utils import get_ref_regex_for_sig
|
||||
from core.utils import infer_band_from_freq
|
||||
from core.utils import safe_json_dumps
|
||||
from data.spot import Spot
|
||||
from spotproviders.spot_provider import SpotProvider
|
||||
from providers.spot.spot_provider import SpotProvider
|
||||
|
||||
RECAPTCHA_VERIFY_URL = "https://www.google.com/recaptcha/api/siteverify"
|
||||
|
||||
@@ -82,19 +82,24 @@ class APISpotHandler(tornado.web.RequestHandler):
|
||||
upstream_credentials = handling.get("upstream_credentials", {})
|
||||
captcha_token = handling.get("captcha_token", None)
|
||||
|
||||
|
||||
# Spothole v2.0 release only: deny upstream spotting. Spothole API breaking changes were in v2.0 but
|
||||
# functionality is not ready yet. TODO
|
||||
submit_upstream = False
|
||||
|
||||
|
||||
|
||||
# Verify CAPTCHA if required
|
||||
if RECAPTCHA_SECRET_KEY:
|
||||
if not captcha_token:
|
||||
self.set_status(422)
|
||||
self.write(json.dumps("Error - CAPTCHA token is required for spot submission.",
|
||||
default=serialize_everything))
|
||||
self.write(safe_json_dumps("Error - CAPTCHA token is required for spot submission."))
|
||||
self.set_header("Cache-Control", "no-store")
|
||||
self.set_header("Content-Type", "application/json")
|
||||
return
|
||||
if not self._verify_recaptcha(captcha_token):
|
||||
self.set_status(422)
|
||||
self.write(json.dumps("Error - CAPTCHA verification failed.",
|
||||
default=serialize_everything))
|
||||
self.write(safe_json_dumps("Error - CAPTCHA verification failed."))
|
||||
self.set_header("Cache-Control", "no-store")
|
||||
self.set_header("Content-Type", "application/json")
|
||||
return
|
||||
@@ -105,7 +110,8 @@ class APISpotHandler(tornado.web.RequestHandler):
|
||||
# Reject if no timestamp, frequency, dx_call or de_call
|
||||
if not spot.time or not spot.dx_call or not spot.freq or not spot.de_call:
|
||||
self.set_status(422)
|
||||
self.write(safe_json_dumps("Error - 'time', 'dx_call', 'freq' and 'de_call' must be provided as a minimum."))
|
||||
self.write(
|
||||
safe_json_dumps("Error - 'time', 'dx_call', 'freq' and 'de_call' must be provided as a minimum."))
|
||||
self.set_header("Cache-Control", "no-store")
|
||||
self.set_header("Content-Type", "application/json")
|
||||
return
|
||||
@@ -127,7 +133,8 @@ class APISpotHandler(tornado.web.RequestHandler):
|
||||
# Reject if frequency not in a known band
|
||||
if infer_band_from_freq(spot.freq) == UNKNOWN_BAND:
|
||||
self.set_status(422)
|
||||
self.write(safe_json_dumps("Error - Frequency of " + str(spot.freq / 1000.0) + "kHz is not in a known band."))
|
||||
self.write(
|
||||
safe_json_dumps("Error - Frequency of " + str(spot.freq / 1000.0) + "kHz is not in a known band."))
|
||||
self.set_header("Cache-Control", "no-store")
|
||||
self.set_header("Content-Type", "application/json")
|
||||
return
|
||||
@@ -137,7 +144,8 @@ class APISpotHandler(tornado.web.RequestHandler):
|
||||
r"^([A-R]{2}[0-9]{2}[A-X]{2}[0-9]{2}[A-X]{2}|[A-R]{2}[0-9]{2}[A-X]{2}[0-9]{2}|[A-R]{2}[0-9]{2}[A-X]{2}|[A-R]{2}[0-9]{2})$",
|
||||
spot.dx_grid.upper()):
|
||||
self.set_status(422)
|
||||
self.write(safe_json_dumps("Error - '" + spot.dx_grid + "' does not look like a valid Maidenhead grid."))
|
||||
self.write(
|
||||
safe_json_dumps("Error - '" + spot.dx_grid + "' does not look like a valid Maidenhead grid."))
|
||||
self.set_header("Cache-Control", "no-store")
|
||||
self.set_header("Content-Type", "application/json")
|
||||
return
|
||||
@@ -155,8 +163,7 @@ class APISpotHandler(tornado.web.RequestHandler):
|
||||
# Reject upstream submission if not permitted
|
||||
if submit_upstream and not ALLOW_UPSTREAM_SPOTTING:
|
||||
self.set_status(403)
|
||||
self.write(json.dumps("Error - this server does not allow upstream spot submission.",
|
||||
default=serialize_everything))
|
||||
self.write(safe_json_dumps("Error - this server does not allow upstream spot submission."))
|
||||
self.set_header("Cache-Control", "no-store")
|
||||
self.set_header("Content-Type", "application/json")
|
||||
return
|
||||
@@ -165,29 +172,26 @@ class APISpotHandler(tornado.web.RequestHandler):
|
||||
if submit_upstream and upstream_provider_name:
|
||||
if not spot.sig:
|
||||
self.set_status(422)
|
||||
self.write(json.dumps("Error - a SIG must be selected to submit upstream.",
|
||||
default=serialize_everything))
|
||||
self.write(safe_json_dumps("Error - a SIG must be selected to submit upstream."))
|
||||
self.set_header("Cache-Control", "no-store")
|
||||
self.set_header("Content-Type", "application/json")
|
||||
return
|
||||
if not spot.sig_refs and upstream_provider_name != "Tiles":
|
||||
self.set_status(422)
|
||||
self.write(json.dumps("Error - a SIG reference is required to submit upstream.",
|
||||
default=serialize_everything))
|
||||
self.write(safe_json_dumps("Error - a SIG reference is required to submit upstream."))
|
||||
self.set_header("Cache-Control", "no-store")
|
||||
self.set_header("Content-Type", "application/json")
|
||||
return
|
||||
if not spot.dx_grid and upstream_provider_name == "Tiles":
|
||||
self.set_status(422)
|
||||
self.write(json.dumps("Error - a grid reference is required to submit upstream to Tiles on the Air.",
|
||||
default=serialize_everything))
|
||||
self.write(
|
||||
safe_json_dumps("Error - a grid reference is required to submit upstream to Tiles on the Air."))
|
||||
self.set_header("Cache-Control", "no-store")
|
||||
self.set_header("Content-Type", "application/json")
|
||||
return
|
||||
if not spot.mode and upstream_provider_name == "Tiles":
|
||||
self.set_status(422)
|
||||
self.write(json.dumps("Error - a mode is required to submit upstream to Tiles on the Air.",
|
||||
default=serialize_everything))
|
||||
self.write(safe_json_dumps("Error - a mode is required to submit upstream to Tiles on the Air."))
|
||||
self.set_header("Cache-Control", "no-store")
|
||||
self.set_header("Content-Type", "application/json")
|
||||
return
|
||||
@@ -209,7 +213,8 @@ class APISpotHandler(tornado.web.RequestHandler):
|
||||
upstream_warning = "Spot was saved locally but upstream submission to " + upstream_provider_name + " failed: " + str(
|
||||
e)
|
||||
else:
|
||||
upstream_warning = "No enabled provider named '" + upstream_provider_name + "' supports upstream submission for " + spot.sig + " spots."
|
||||
upstream_warning = "No enabled provider named '" + upstream_provider_name + "' supports upstream submission for " + (
|
||||
spot.sig if spot.sig else "") + " spots."
|
||||
|
||||
# If we successfully submitted the spot upstream, don't add it direct to Spothole, otherwise it will be a
|
||||
# duplicate with what immediately comes back from the API. But if we weren't asked to send it upstream, or
|
||||
@@ -219,7 +224,7 @@ class APISpotHandler(tornado.web.RequestHandler):
|
||||
self._spots.set(spot.id, spot)
|
||||
|
||||
if upstream_warning:
|
||||
self.write(json.dumps("Warning - " + upstream_warning, default=serialize_everything))
|
||||
self.write(safe_json_dumps("Warning - " + upstream_warning))
|
||||
self.set_status(201)
|
||||
else:
|
||||
self.write(safe_json_dumps("OK"))
|
||||
|
||||
@@ -37,12 +37,14 @@ class APIOptionsHandler(tornado.web.RequestHandler):
|
||||
|
||||
# Build a map of SIG name -> list of provider names that can submit spots for that SIG
|
||||
spot_submit_providers = {}
|
||||
for provider in self._spot_providers:
|
||||
if not provider.enabled:
|
||||
continue
|
||||
for sig in SIGS:
|
||||
if provider.can_submit_spot(sig.name):
|
||||
spot_submit_providers.setdefault(sig.name, []).append(provider.name)
|
||||
|
||||
# Spothole v2.0 - disable this for now, API changes are in but this functionality is not ready yet. TODO
|
||||
# for provider in self._spot_providers:
|
||||
# if not provider.enabled:
|
||||
# continue
|
||||
# for sig in SIGS:
|
||||
# if provider.can_submit_spot(sig.name):
|
||||
# spot_submit_providers.setdefault(sig.name, []).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.
|
||||
|
||||
@@ -2,7 +2,7 @@ import json
|
||||
|
||||
import tornado
|
||||
|
||||
from core.utils import serialize_everything
|
||||
from core.utils import safe_json_dumps
|
||||
|
||||
|
||||
class V1GoneHandler(tornado.web.RequestHandler):
|
||||
@@ -11,10 +11,8 @@ class V1GoneHandler(tornado.web.RequestHandler):
|
||||
|
||||
def post(self):
|
||||
self.set_status(410)
|
||||
self.write(json.dumps(
|
||||
"This API endpoint has a breaking change or has been removed in the current version of the Spothole API. Please see /apidocs for details of the current API version and the endpoints available.",
|
||||
default=serialize_everything
|
||||
))
|
||||
self.write(safe_json_dumps(
|
||||
"This API endpoint has a breaking change or has been removed in the current version of the Spothole API. Please see /apidocs for details of the current API version and the endpoints available."))
|
||||
self.set_header("Cache-Control", "no-store")
|
||||
self.set_header("Content-Type", "application/json")
|
||||
|
||||
|
||||
+7
-5
@@ -1,15 +1,14 @@
|
||||
import asyncio
|
||||
import logging
|
||||
import os
|
||||
import threading
|
||||
|
||||
import tornado
|
||||
from tornado.web import StaticFileHandler
|
||||
|
||||
from core.config import ALLOW_SPOTTING, WEB_SERVER_PORT, API_ONLY_MODE, LOG_WEB_REQUESTS, BASE_URL
|
||||
from core.data_providers import DATA_PROVIDERS
|
||||
from core.data_store import DATA_STORE
|
||||
from server.handlers.api.addspot import APISpotHandler
|
||||
from server.handlers.api.v1_compatability import V1RedirectHandler, V1GoneHandler
|
||||
from server.handlers.api.alerts import APIAlertsHandler, APIAlertsStreamHandler
|
||||
from server.handlers.api.dxstats import APIDxStatsHandler
|
||||
from server.handlers.api.lookups import APILookupCallHandler, APILookupSIGRefHandler, APILookupGridHandler
|
||||
@@ -17,6 +16,7 @@ from server.handlers.api.options import APIOptionsHandler
|
||||
from server.handlers.api.solar_conditions import APISolarConditionsHandler
|
||||
from server.handlers.api.spots import APISpotsHandler, APISpotsStreamHandler
|
||||
from server.handlers.api.status import APIStatusHandler
|
||||
from server.handlers.api.v1_compatability import V1RedirectHandler, V1GoneHandler
|
||||
from server.handlers.manifesthandler import ManifestHandler
|
||||
from server.handlers.metrics import PrometheusMetricsHandler
|
||||
from server.handlers.pagetemplate import PageTemplateHandler
|
||||
@@ -32,6 +32,7 @@ class WebServer:
|
||||
"""Constructor"""
|
||||
|
||||
self._data_store = DATA_STORE
|
||||
self._data_providers = DATA_PROVIDERS
|
||||
self._spot_broadcaster = SSEBroadcaster()
|
||||
self._alert_broadcaster = SSEBroadcaster()
|
||||
self._port = WEB_SERVER_PORT
|
||||
@@ -87,11 +88,12 @@ class WebServer:
|
||||
(r"/api/v2/lookup/call", APILookupCallHandler, {**handler_opts}),
|
||||
(r"/api/v2/lookup/sigref", APILookupSIGRefHandler, {**handler_opts}),
|
||||
(r"/api/v2/lookup/grid", APILookupGridHandler, {**handler_opts}),
|
||||
(r"/api/v2/spot", APISpotHandler, {"spots": self._data_store.spots, **handler_opts}),
|
||||
(r"/api/v2/spot", APISpotHandler,
|
||||
{"spots": self._data_store.spots, "spot_providers": self._data_providers, **handler_opts}),
|
||||
]
|
||||
|
||||
# v1 API redirects. Most v1 enpoints are unchanged in v2, and get an HTTP 308 redirect to the v2 API. The ones
|
||||
# that have the actual breaking changes get a bespoke handler.
|
||||
# that have the major breaking changes get a bespoke handler.
|
||||
v1_compat_routes = [
|
||||
(r"/api/v1/spot", V1GoneHandler),
|
||||
(r"/api/v1/(.*)", V1RedirectHandler),
|
||||
@@ -162,4 +164,4 @@ def request_log(handler):
|
||||
|
||||
|
||||
# Global object
|
||||
WEB_SERVER = WebServer()
|
||||
WEB_SERVER = WebServer()
|
||||
|
||||
Reference in New Issue
Block a user