mirror of
https://git.ianrenton.com/ian/spothole.git
synced 2026-06-24 05:35:10 +00:00
Stop fudging the server-side handling instructions for "add spot" into the spot data structure itself, instead break them out into a new area. This is a breaking change to the API so all API endpoints have been bumped to v2.
This commit is contained in:
@@ -25,7 +25,7 @@ RECAPTCHA_VERIFY_URL = "https://www.google.com/recaptcha/api/siteverify"
|
||||
|
||||
|
||||
class APISpotHandler(tornado.web.RequestHandler):
|
||||
"""API request handler for /api/v1/spot (POST)"""
|
||||
"""API request handler for /api/v2/spot (POST)"""
|
||||
|
||||
def __init__(self, application: "Application", request: httputil.HTTPServerRequest, **kwargs: Any):
|
||||
self._spots = None
|
||||
@@ -76,13 +76,15 @@ class APISpotHandler(tornado.web.RequestHandler):
|
||||
# Read in the request body as JSON
|
||||
json_body = tornado.escape.json_decode(post_data)
|
||||
|
||||
# Extract fields relating to how we handle the spot, such as CAPTCHA and upstream submission. Remove these
|
||||
# from the data so they don't accidentally end up in the spot object itself.
|
||||
# todo: Better way of separating these out. Possible without API change or not?
|
||||
submit_upstream = json_body.pop("submit_upstream", False)
|
||||
upstream_provider_name = json_body.pop("upstream_provider", None)
|
||||
upstream_credentials = json_body.pop("upstream_credentials", {})
|
||||
captcha_token = json_body.pop("captcha_token", None)
|
||||
# Extract the "spot" and "handling" sub-objects from the request body
|
||||
spot_data = json_body.get("spot", {})
|
||||
handling = json_body.get("handling", {})
|
||||
|
||||
# Extract individual parameters that say how this spot should be handled by the server
|
||||
submit_upstream = handling.get("submit_upstream", False)
|
||||
upstream_provider_name = handling.get("upstream_provider", None)
|
||||
upstream_credentials = handling.get("upstream_credentials", {})
|
||||
captcha_token = handling.get("captcha_token", None)
|
||||
|
||||
# Verify CAPTCHA if required
|
||||
if RECAPTCHA_SECRET_KEY:
|
||||
@@ -101,8 +103,8 @@ class APISpotHandler(tornado.web.RequestHandler):
|
||||
self.set_header("Content-Type", "application/json")
|
||||
return
|
||||
|
||||
# Convert remaining fields to a Spot object
|
||||
spot = Spot(**json_body)
|
||||
# Convert spot field to a Spot object
|
||||
spot = Spot(**spot_data)
|
||||
|
||||
# Converting to a spot object this way won't have coped with sig_ref objects, so fix that. (Would be nice to
|
||||
# redo this in a functional style)
|
||||
|
||||
@@ -20,7 +20,7 @@ SSE_HANDLER_QUEUE_CHECK_INTERVAL = 5000
|
||||
|
||||
|
||||
class APIAlertsHandler(tornado.web.RequestHandler):
|
||||
"""API request handler for /api/v1/alerts"""
|
||||
"""API request handler for /api/v2/alerts"""
|
||||
|
||||
def __init__(self, application: "Application", request: httputil.HTTPServerRequest, **kwargs: Any):
|
||||
self._alerts = None
|
||||
@@ -72,7 +72,7 @@ class APIAlertsHandler(tornado.web.RequestHandler):
|
||||
|
||||
|
||||
class APIAlertsStreamHandler(tornado_eventsource.handler.EventSourceHandler):
|
||||
"""API request handler for /api/v1/alerts/stream"""
|
||||
"""API request handler for /api/v2/alerts/stream"""
|
||||
|
||||
def __init__(self, application, request, **kwargs: Any):
|
||||
self._sse_alert_queues = None
|
||||
|
||||
@@ -17,7 +17,7 @@ BANDS_SET = frozenset(BANDS)
|
||||
|
||||
|
||||
class APIDxStatsHandler(tornado.web.RequestHandler):
|
||||
"""API request handler for /api/v1/dxstats"""
|
||||
"""API request handler for /api/v2/dxstats"""
|
||||
|
||||
def __init__(self, application: "Application", request: httputil.HTTPServerRequest, **kwargs: Any):
|
||||
self._spots = None
|
||||
|
||||
@@ -20,7 +20,7 @@ from data.spot import Spot
|
||||
|
||||
|
||||
class APILookupCallHandler(tornado.web.RequestHandler):
|
||||
"""API request handler for /api/v1/lookup/call"""
|
||||
"""API request handler for /api/v2/lookup/call"""
|
||||
|
||||
def __init__(self, application: "Application", request: httputil.HTTPServerRequest, **kwargs: Any):
|
||||
self._web_server_metrics = None
|
||||
@@ -85,7 +85,7 @@ class APILookupCallHandler(tornado.web.RequestHandler):
|
||||
|
||||
|
||||
class APILookupSIGRefHandler(tornado.web.RequestHandler):
|
||||
"""API request handler for /api/v1/lookup/sigref"""
|
||||
"""API request handler for /api/v2/lookup/sigref"""
|
||||
|
||||
def __init__(self, application: "Application", request: httputil.HTTPServerRequest, **kwargs: Any):
|
||||
self._web_server_metrics = None
|
||||
@@ -139,7 +139,7 @@ class APILookupSIGRefHandler(tornado.web.RequestHandler):
|
||||
|
||||
|
||||
class APILookupGridHandler(tornado.web.RequestHandler):
|
||||
"""API request handler for /api/v1/lookup/grid"""
|
||||
"""API request handler for /api/v2/lookup/grid"""
|
||||
|
||||
def __init__(self, application: "Application", request: httputil.HTTPServerRequest, **kwargs: Any):
|
||||
self._web_server_metrics = None
|
||||
|
||||
@@ -14,7 +14,7 @@ from core.utils import serialize_everything
|
||||
|
||||
|
||||
class APIOptionsHandler(tornado.web.RequestHandler):
|
||||
"""API request handler for /api/v1/options"""
|
||||
"""API request handler for /api/v2/options"""
|
||||
|
||||
def __init__(self, application: "Application", request: httputil.HTTPServerRequest, **kwargs: Any):
|
||||
self._status_data = None
|
||||
|
||||
@@ -10,7 +10,7 @@ from core.prometheus_metrics_handler import api_requests_counter
|
||||
|
||||
|
||||
class APISolarConditionsHandler(tornado.web.RequestHandler):
|
||||
"""API request handler for /api/v1/solar"""
|
||||
"""API request handler for /api/v2/solar"""
|
||||
|
||||
def __init__(self, application: "Application", request: httputil.HTTPServerRequest, **kwargs: Any):
|
||||
self._solar_conditions = None
|
||||
|
||||
@@ -20,7 +20,7 @@ SSE_HANDLER_QUEUE_CHECK_INTERVAL = 5000
|
||||
|
||||
|
||||
class APISpotsHandler(tornado.web.RequestHandler):
|
||||
"""API request handler for /api/v1/spots"""
|
||||
"""API request handler for /api/v2/spots"""
|
||||
|
||||
def __init__(self, application: "Application", request: httputil.HTTPServerRequest, **kwargs: Any):
|
||||
self._spots = None
|
||||
@@ -72,7 +72,7 @@ class APISpotsHandler(tornado.web.RequestHandler):
|
||||
|
||||
|
||||
class APISpotsStreamHandler(tornado_eventsource.handler.EventSourceHandler):
|
||||
"""API request handler for /api/v1/spots/stream"""
|
||||
"""API request handler for /api/v2/spots/stream"""
|
||||
|
||||
def __init__(self, application, request, **kwargs: Any):
|
||||
self._sse_spot_queues = None
|
||||
|
||||
@@ -12,7 +12,7 @@ from core.utils import serialize_everything
|
||||
|
||||
|
||||
class APIStatusHandler(tornado.web.RequestHandler):
|
||||
"""API request handler for /api/v1/status"""
|
||||
"""API request handler for /api/v2/status"""
|
||||
|
||||
def __init__(self, application: "Application", request: httputil.HTTPServerRequest, **kwargs: Any):
|
||||
self._status_data = None
|
||||
|
||||
31
server/handlers/api/v1_compatability.py
Normal file
31
server/handlers/api/v1_compatability.py
Normal file
@@ -0,0 +1,31 @@
|
||||
import json
|
||||
|
||||
import tornado
|
||||
|
||||
from core.utils import serialize_everything
|
||||
|
||||
|
||||
class V1GoneHandler(tornado.web.RequestHandler):
|
||||
"""Returns 410 Gone with a message for any endpoints in the old API that have breaking changes in the new one or
|
||||
have been retired."""
|
||||
|
||||
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.set_header("Cache-Control", "no-store")
|
||||
self.set_header("Content-Type", "application/json")
|
||||
|
||||
|
||||
class V1RedirectHandler(tornado.web.RequestHandler):
|
||||
"""Returns 308 Permanent Redirect from any path in the old API to the new one, where there were no breaking changes."""
|
||||
|
||||
def get(self, path):
|
||||
new_url = "/api/v2/" + path
|
||||
if self.request.query:
|
||||
new_url += "?" + self.request.query
|
||||
self.set_status(308)
|
||||
self.set_header("Location", new_url)
|
||||
self.finish()
|
||||
@@ -8,6 +8,7 @@ from tornado.web import StaticFileHandler
|
||||
from core.config import ALLOW_SPOTTING, WEB_SERVER_PORT, API_ONLY_MODE
|
||||
from core.utils import empty_queue
|
||||
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
|
||||
@@ -64,24 +65,31 @@ class WebServer:
|
||||
|
||||
# API endpoints are always enabled
|
||||
api_routes = [
|
||||
(r"/api/v1/spots", APISpotsHandler, {"spots": self._spots, **handler_opts}),
|
||||
(r"/api/v1/alerts", APIAlertsHandler, {"alerts": self._alerts, **handler_opts}),
|
||||
(r"/api/v1/spots/stream", APISpotsStreamHandler,
|
||||
(r"/api/v2/spots", APISpotsHandler, {"spots": self._spots, **handler_opts}),
|
||||
(r"/api/v2/alerts", APIAlertsHandler, {"alerts": self._alerts, **handler_opts}),
|
||||
(r"/api/v2/spots/stream", APISpotsStreamHandler,
|
||||
{"sse_spot_queues": self._sse_spot_queues, **handler_opts}),
|
||||
(r"/api/v1/alerts/stream", APIAlertsStreamHandler,
|
||||
(r"/api/v2/alerts/stream", APIAlertsStreamHandler,
|
||||
{"sse_alert_queues": self._sse_alert_queues, **handler_opts}),
|
||||
(r"/api/v1/solar", APISolarConditionsHandler, {"solar_conditions": self._solar_conditions, **handler_opts}),
|
||||
(r"/api/v1/dxstats", APIDxStatsHandler, {"spots": self._spots, **handler_opts}),
|
||||
(r"/api/v1/options", APIOptionsHandler,
|
||||
(r"/api/v2/solar", APISolarConditionsHandler, {"solar_conditions": self._solar_conditions, **handler_opts}),
|
||||
(r"/api/v2/dxstats", APIDxStatsHandler, {"spots": self._spots, **handler_opts}),
|
||||
(r"/api/v2/options", APIOptionsHandler,
|
||||
{"status_data": self._status_data, "spot_providers": self._spot_providers, **handler_opts}),
|
||||
(r"/api/v1/status", APIStatusHandler, {"status_data": self._status_data, **handler_opts}),
|
||||
(r"/api/v1/lookup/call", APILookupCallHandler, {**handler_opts}),
|
||||
(r"/api/v1/lookup/sigref", APILookupSIGRefHandler, {**handler_opts}),
|
||||
(r"/api/v1/lookup/grid", APILookupGridHandler, {**handler_opts}),
|
||||
(r"/api/v1/spot", APISpotHandler,
|
||||
(r"/api/v2/status", APIStatusHandler, {"status_data": self._status_data, **handler_opts}),
|
||||
(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._spots, "spot_providers": self._spot_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.
|
||||
v1_compat_routes = [
|
||||
(r"/api/v1/spot", V1GoneHandler),
|
||||
(r"/api/v1/(.*)", V1RedirectHandler),
|
||||
]
|
||||
|
||||
# If in API-only mode, serve a basic homepage; in normal mode, serve the usual UI routes
|
||||
if self._api_only_mode:
|
||||
logging.info("API-only mode is enabled. Web UI will not be served.")
|
||||
@@ -109,7 +117,7 @@ class WebServer:
|
||||
(r"/(.*)", StaticFileHandler, {"path": os.path.join(_HERE, "../webassets")})
|
||||
]
|
||||
|
||||
app = tornado.web.Application(api_routes + ui_routes + misc_routes,
|
||||
app = tornado.web.Application(api_routes + v1_compat_routes + ui_routes + misc_routes,
|
||||
template_path=os.path.join(_HERE, "../templates"),
|
||||
debug=False)
|
||||
app.listen(self._port)
|
||||
|
||||
Reference in New Issue
Block a user