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:
Ian Renton
2026-06-20 09:57:09 +01:00
parent 1e42c69b78
commit ae17839096
20 changed files with 132 additions and 82 deletions

View File

@@ -309,7 +309,7 @@ server {
} }
# SSE endpoints # SSE endpoints
location ~ ^/api/v1/(spots|alerts)/stream { location ~ ^/api/v2/(spots|alerts)/stream {
proxy_http_version 1.1; proxy_http_version 1.1;
proxy_set_header Connection ""; proxy_set_header Connection "";
proxy_pass http://127.0.0.1:8080; proxy_pass http://127.0.0.1:8080;

View File

@@ -154,7 +154,7 @@ alert-providers:
# Solar condition providers to use. These poll external APIs for solar propagation data (SFI, A/K indices, band # Solar condition providers to use. These poll external APIs for solar propagation data (SFI, A/K indices, band
# conditions, etc.) and make it available via the /api/v1/solar endpoint. # conditions, etc.) and make it available via the /api/v2/solar endpoint.
solar-condition-providers: solar-condition-providers:
- class: "HamQSL" - class: "HamQSL"
name: "HamQSL" name: "HamQSL"

View File

@@ -25,7 +25,7 @@ RECAPTCHA_VERIFY_URL = "https://www.google.com/recaptcha/api/siteverify"
class APISpotHandler(tornado.web.RequestHandler): 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): def __init__(self, application: "Application", request: httputil.HTTPServerRequest, **kwargs: Any):
self._spots = None self._spots = None
@@ -76,13 +76,15 @@ class APISpotHandler(tornado.web.RequestHandler):
# Read in the request body as JSON # Read in the request body as JSON
json_body = tornado.escape.json_decode(post_data) 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 # Extract the "spot" and "handling" sub-objects from the request body
# from the data so they don't accidentally end up in the spot object itself. spot_data = json_body.get("spot", {})
# todo: Better way of separating these out. Possible without API change or not? handling = json_body.get("handling", {})
submit_upstream = json_body.pop("submit_upstream", False)
upstream_provider_name = json_body.pop("upstream_provider", None) # Extract individual parameters that say how this spot should be handled by the server
upstream_credentials = json_body.pop("upstream_credentials", {}) submit_upstream = handling.get("submit_upstream", False)
captcha_token = json_body.pop("captcha_token", None) 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 # Verify CAPTCHA if required
if RECAPTCHA_SECRET_KEY: if RECAPTCHA_SECRET_KEY:
@@ -101,8 +103,8 @@ class APISpotHandler(tornado.web.RequestHandler):
self.set_header("Content-Type", "application/json") self.set_header("Content-Type", "application/json")
return return
# Convert remaining fields to a Spot object # Convert spot field to a Spot object
spot = Spot(**json_body) 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 # 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) # redo this in a functional style)

View File

@@ -20,7 +20,7 @@ SSE_HANDLER_QUEUE_CHECK_INTERVAL = 5000
class APIAlertsHandler(tornado.web.RequestHandler): 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): def __init__(self, application: "Application", request: httputil.HTTPServerRequest, **kwargs: Any):
self._alerts = None self._alerts = None
@@ -72,7 +72,7 @@ class APIAlertsHandler(tornado.web.RequestHandler):
class APIAlertsStreamHandler(tornado_eventsource.handler.EventSourceHandler): 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): def __init__(self, application, request, **kwargs: Any):
self._sse_alert_queues = None self._sse_alert_queues = None

View File

@@ -17,7 +17,7 @@ BANDS_SET = frozenset(BANDS)
class APIDxStatsHandler(tornado.web.RequestHandler): 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): def __init__(self, application: "Application", request: httputil.HTTPServerRequest, **kwargs: Any):
self._spots = None self._spots = None

View File

@@ -20,7 +20,7 @@ from data.spot import Spot
class APILookupCallHandler(tornado.web.RequestHandler): 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): def __init__(self, application: "Application", request: httputil.HTTPServerRequest, **kwargs: Any):
self._web_server_metrics = None self._web_server_metrics = None
@@ -85,7 +85,7 @@ class APILookupCallHandler(tornado.web.RequestHandler):
class APILookupSIGRefHandler(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): def __init__(self, application: "Application", request: httputil.HTTPServerRequest, **kwargs: Any):
self._web_server_metrics = None self._web_server_metrics = None
@@ -139,7 +139,7 @@ class APILookupSIGRefHandler(tornado.web.RequestHandler):
class APILookupGridHandler(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): def __init__(self, application: "Application", request: httputil.HTTPServerRequest, **kwargs: Any):
self._web_server_metrics = None self._web_server_metrics = None

View File

@@ -14,7 +14,7 @@ from core.utils import serialize_everything
class APIOptionsHandler(tornado.web.RequestHandler): 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): def __init__(self, application: "Application", request: httputil.HTTPServerRequest, **kwargs: Any):
self._status_data = None self._status_data = None

View File

@@ -10,7 +10,7 @@ from core.prometheus_metrics_handler import api_requests_counter
class APISolarConditionsHandler(tornado.web.RequestHandler): 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): def __init__(self, application: "Application", request: httputil.HTTPServerRequest, **kwargs: Any):
self._solar_conditions = None self._solar_conditions = None

View File

@@ -20,7 +20,7 @@ SSE_HANDLER_QUEUE_CHECK_INTERVAL = 5000
class APISpotsHandler(tornado.web.RequestHandler): 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): def __init__(self, application: "Application", request: httputil.HTTPServerRequest, **kwargs: Any):
self._spots = None self._spots = None
@@ -72,7 +72,7 @@ class APISpotsHandler(tornado.web.RequestHandler):
class APISpotsStreamHandler(tornado_eventsource.handler.EventSourceHandler): 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): def __init__(self, application, request, **kwargs: Any):
self._sse_spot_queues = None self._sse_spot_queues = None

View File

@@ -12,7 +12,7 @@ from core.utils import serialize_everything
class APIStatusHandler(tornado.web.RequestHandler): 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): def __init__(self, application: "Application", request: httputil.HTTPServerRequest, **kwargs: Any):
self._status_data = None self._status_data = None

View 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()

View File

@@ -8,6 +8,7 @@ from tornado.web import StaticFileHandler
from core.config import ALLOW_SPOTTING, WEB_SERVER_PORT, API_ONLY_MODE from core.config import ALLOW_SPOTTING, WEB_SERVER_PORT, API_ONLY_MODE
from core.utils import empty_queue from core.utils import empty_queue
from server.handlers.api.addspot import APISpotHandler 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.alerts import APIAlertsHandler, APIAlertsStreamHandler
from server.handlers.api.dxstats import APIDxStatsHandler from server.handlers.api.dxstats import APIDxStatsHandler
from server.handlers.api.lookups import APILookupCallHandler, APILookupSIGRefHandler, APILookupGridHandler from server.handlers.api.lookups import APILookupCallHandler, APILookupSIGRefHandler, APILookupGridHandler
@@ -64,24 +65,31 @@ class WebServer:
# API endpoints are always enabled # API endpoints are always enabled
api_routes = [ api_routes = [
(r"/api/v1/spots", APISpotsHandler, {"spots": self._spots, **handler_opts}), (r"/api/v2/spots", APISpotsHandler, {"spots": self._spots, **handler_opts}),
(r"/api/v1/alerts", APIAlertsHandler, {"alerts": self._alerts, **handler_opts}), (r"/api/v2/alerts", APIAlertsHandler, {"alerts": self._alerts, **handler_opts}),
(r"/api/v1/spots/stream", APISpotsStreamHandler, (r"/api/v2/spots/stream", APISpotsStreamHandler,
{"sse_spot_queues": self._sse_spot_queues, **handler_opts}), {"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}), {"sse_alert_queues": self._sse_alert_queues, **handler_opts}),
(r"/api/v1/solar", APISolarConditionsHandler, {"solar_conditions": self._solar_conditions, **handler_opts}), (r"/api/v2/solar", APISolarConditionsHandler, {"solar_conditions": self._solar_conditions, **handler_opts}),
(r"/api/v1/dxstats", APIDxStatsHandler, {"spots": self._spots, **handler_opts}), (r"/api/v2/dxstats", APIDxStatsHandler, {"spots": self._spots, **handler_opts}),
(r"/api/v1/options", APIOptionsHandler, (r"/api/v2/options", APIOptionsHandler,
{"status_data": self._status_data, "spot_providers": self._spot_providers, **handler_opts}), {"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/v2/status", APIStatusHandler, {"status_data": self._status_data, **handler_opts}),
(r"/api/v1/lookup/call", APILookupCallHandler, {**handler_opts}), (r"/api/v2/lookup/call", APILookupCallHandler, {**handler_opts}),
(r"/api/v1/lookup/sigref", APILookupSIGRefHandler, {**handler_opts}), (r"/api/v2/lookup/sigref", APILookupSIGRefHandler, {**handler_opts}),
(r"/api/v1/lookup/grid", APILookupGridHandler, {**handler_opts}), (r"/api/v2/lookup/grid", APILookupGridHandler, {**handler_opts}),
(r"/api/v1/spot", APISpotHandler, (r"/api/v2/spot", APISpotHandler,
{"spots": self._spots, "spot_providers": self._spot_providers, **handler_opts}), {"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 in API-only mode, serve a basic homepage; in normal mode, serve the usual UI routes
if self._api_only_mode: if self._api_only_mode:
logging.info("API-only mode is enabled. Web UI will not be served.") 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")}) (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"), template_path=os.path.join(_HERE, "../templates"),
debug=False) debug=False)
app.listen(self._port) app.listen(self._port)

View File

@@ -15,11 +15,11 @@ info:
## Changelog ## Changelog
### 1.4 ### 2.0
* POST `/spot` now supports upstream submission to external providers such as POTA and SOTA via new `submit_upstream`, `upstream_provider`, and `upstream_credentials` request body fields. * POST `/spot` now supports upstream submission to external providers such as POTA and SOTA. The "add spot" API has a **breaking change** to enable this: instead of just posting the spot object itself as the JSON content of the POST, this has moved into a `spot` object within the structure. A new `handling` object alongside it contains the `submit_upstream`, `upstream_provider`, `upstream_credentials`, and `captcha_token` fields which control the server handling of the spot.
* POST `/spot` now supports Google reCaptcha and (if the site owner has set it up) now requires `captcha_token` in order to successfully submit. (This is used to lock down the submit function and prevent submission via Spothole by bots or third-party clients.) * POST `/spot` now supports Google reCaptcha and (if the site owner has set it up) now requires `captcha_token` in order to successfully submit. (This is used to lock down the submit function and prevent submission via Spothole by bots or third-party clients.)
* GET `/options` now returns `spot_submit_providers`, a map of SIG names to the names of providers that support upstream spot submission for that SIG. * GET `/options` now returns `spot_submit_providers`, a map of SIG names to the names of providers that support upstream spot submission for that SIG. (This allows clients to present the user with options of where a new spot can be sent to.)
### 1.3 ### 1.3
@@ -42,10 +42,10 @@ info:
license: license:
name: The Unlicense name: The Unlicense
url: https://unlicense.org/#the-unlicense url: https://unlicense.org/#the-unlicense
version: 1.4 version: 2.0
servers: servers:
- url: https://spothole.app/api/v1 - url: https://spothole.app/api/v2
tags: tags:
- name: Spots - name: Spots
@@ -356,10 +356,10 @@ paths:
tags: tags:
- Spots - Spots
summary: Add a spot summary: Add a spot
description: "Supply a new spot object, which will be added to the system. Optionally, set `submit_upstream` to true to forward the spot to an external provider such as POTA or SOTA. Check `spot_submit_providers` in the `/options` response to see which SIGs and providers support this. cURL example (local-only): `curl --request POST --header \"Content-Type: application/json\" --data '{\"dx_call\":\"M0TRT\",\"time\":1760019539, \"freq\":14200000, \"comment\":\"Test spot please ignore\", \"de_call\":\"M0TRT\"}' https://spothole.app/api/v1/spot`" description: "Supply a spot submission object containing a `spot` sub-object (the spot data) and an optional `handling` sub-object (server-side instructions such as upstream submission). Check `spot_submit_providers` in the `/options` response to see which SIGs and providers support upstream submission. cURL example: `curl --request POST --header \"Content-Type: application/json\" --data '{\"spot\":{\"dx_call\":\"M0TRT\",\"time\":1760019539,\"freq\":14200000,\"comment\":\"Test spot please ignore\",\"de_call\":\"M0TRT\"}}' https://spothole.app/api/v2/spot`"
operationId: spot operationId: spot
requestBody: requestBody:
description: The JSON spot object, plus optional upstream submission control fields description: Object containing a "spot" sub-object with the spot data, and an optional "handling" sub-object with server-side instructions of what to do with it.
required: true required: true
content: content:
application/json: application/json:
@@ -997,11 +997,18 @@ components:
SpotSubmission: SpotSubmission:
description: > description: >
Request body for POST /spot. Contains all the fields of a Spot, plus optional Request body for POST /spot. Contains a "spot" sub-object with the spot data, and an optional
upstream submission control fields that are consumed by the server and never stored in the spot. "handling" sub-object with server-side instructions consumed by Spothole.
allOf: type: object
- $ref: '#/components/schemas/Spot' required:
- type: object - spot
properties:
spot:
$ref: '#/components/schemas/Spot'
handling:
type: object
description: >
Optional server-side instructions for how to process this spot submission.
properties: properties:
submit_upstream: submit_upstream:
type: boolean type: boolean
@@ -1021,7 +1028,7 @@ components:
type: object type: object
description: > description: >
Provider-specific credentials required to authenticate the upstream submission. Provider-specific credentials required to authenticate the upstream submission.
The required keys depend on the provider . Credentials are used only for the upstream The required keys depend on the provider. Credentials are used only for the upstream
call and are never stored by Spothole. call and are never stored by Spothole.
additionalProperties: additionalProperties:
type: string type: string
@@ -1032,8 +1039,7 @@ components:
type: string type: string
description: > description: >
A Google reCAPTCHA v2 response token. Required when submitting upstream if the A Google reCAPTCHA v2 response token. Required when submitting upstream if the
server has reCAPTCHA configured (i.e. `submit_upstream` is true and the server server has reCAPTCHA configured. Obtain the token by completing the reCAPTCHA
operator has set up reCAPTCHA keys). Obtain the token by completing the reCAPTCHA
widget rendered on the Add Spot page. widget rendered on the Add Spot page.
example: "03AFY_a8Xq..." example: "03AFY_a8Xq..."
@@ -1678,7 +1684,7 @@ components:
example: "EU" example: "EU"
max_spot_age: max_spot_age:
type: integer type: integer
description: The maximum age, in seconds, of any spot before it will be deleted by the system. When querying the /api/v1/spots endpoint and providing a "max_age" or "since" parameter, there is no point providing a number larger than this, because the system drops all spots older than this. description: The maximum age, in seconds, of any spot before it will be deleted by the system. When querying the /api/v2/spots endpoint and providing a "max_age" or "since" parameter, there is no point providing a number larger than this, because the system drops all spots older than this.
example: 3600 example: 3600
spot_allowed: spot_allowed:
type: boolean type: boolean

View File

@@ -29,7 +29,7 @@ const PROVIDER_CREDENTIAL_SCHEMAS = {
// Load server options. Once a successful callback is made from this, we can populate the choice boxes in the form and load // Load server options. Once a successful callback is made from this, we can populate the choice boxes in the form and load
// any saved values from local storage. // any saved values from local storage.
function loadOptions() { function loadOptions() {
$.getJSON('/api/v1/options', function (jsonData) { $.getJSON('/api/v2/options', function (jsonData) {
// Store options // Store options
options = jsonData; options = jsonData;
@@ -203,6 +203,7 @@ function addSpot() {
const comment = $("#comment").val(); const comment = $("#comment").val();
const de = $("#de-call").val().toUpperCase(); const de = $("#de-call").val().toUpperCase();
// Prepare the spot object for the server
const spot = {}; const spot = {};
if (dx !== "") { if (dx !== "") {
spot["dx_call"] = dx; spot["dx_call"] = dx;
@@ -240,6 +241,19 @@ function addSpot() {
} }
spot["time"] = moment.utc().valueOf() / 1000.0; spot["time"] = moment.utc().valueOf() / 1000.0;
// Prepare "handling" structure to tell the server what to do with this spot
const handling = {};
// Add CAPTCHA token if reCAPTCHA is loaded
if (window._recaptchaWidgetId !== undefined) {
const token = grecaptcha.getResponse(window._recaptchaWidgetId);
if (!token) {
showAddSpotError("Please complete the CAPTCHA to submit upstream.");
return;
}
handling["captcha_token"] = token;
}
// Upstream submission // Upstream submission
const submitUpstream = $("#submit-upstream").is(":checked"); const submitUpstream = $("#submit-upstream").is(":checked");
const upstreamProviderName = getSelectedUpstreamProvider(); const upstreamProviderName = getSelectedUpstreamProvider();
@@ -261,24 +275,13 @@ function addSpot() {
return; return;
} }
const creds = loadCredentials(upstreamProviderName); handling["submit_upstream"] = true;
spot["submit_upstream"] = true; handling["upstream_provider"] = upstreamProviderName;
spot["upstream_provider"] = upstreamProviderName; handling["upstream_credentials"] = loadCredentials(upstreamProviderName);
spot["upstream_credentials"] = creds;
// Add CAPTCHA token if reCAPTCHA is loaded
if (window._recaptchaWidgetId !== undefined) {
const token = grecaptcha.getResponse(window._recaptchaWidgetId);
if (!token) {
showAddSpotError("Please complete the CAPTCHA to submit upstream.");
return;
}
spot["captcha_token"] = token;
}
} }
$.ajax("/api/v1/spot", { $.ajax("/api/v2/spot", {
data: JSON.stringify(spot), data: JSON.stringify({spot, handling}),
contentType: 'application/json', contentType: 'application/json',
type: 'POST', type: 'POST',
timeout: 10000, timeout: 10000,

View File

@@ -6,7 +6,7 @@ let alerts = [];
// Load alerts and populate the table. // Load alerts and populate the table.
function loadAlerts() { function loadAlerts() {
$.getJSON('/api/v1/alerts' + buildQueryString(false), function (jsonData) { $.getJSON('/api/v2/alerts' + buildQueryString(false), function (jsonData) {
// Store last updated time // Store last updated time
lastUpdateTime = moment.utc(); lastUpdateTime = moment.utc();
updateRefreshDisplay(); updateRefreshDisplay();
@@ -280,7 +280,7 @@ function addAlertRowsToTable(tbody, alerts) {
// Load server options. Once a successful callback is made from this, we then query alerts. // Load server options. Once a successful callback is made from this, we then query alerts.
function loadOptions() { function loadOptions() {
$.getJSON('/api/v1/options', function (jsonData) { $.getJSON('/api/v2/options', function (jsonData) {
// Store options // Store options
options = jsonData; options = jsonData;

View File

@@ -12,7 +12,7 @@ BAND_COLUMN_SPOT_DIV_HEIGHT_PX = BAND_COLUMN_FONT_SIZE * 1.6;
// Load spots and populate the bands display. // Load spots and populate the bands display.
function loadSpots() { function loadSpots() {
$.getJSON('/api/v1/spots' + buildQueryString(false), function (jsonData) { $.getJSON('/api/v2/spots' + buildQueryString(false), function (jsonData) {
// Store last updated time // Store last updated time
lastUpdateTime = moment.utc(); lastUpdateTime = moment.utc();
updateRefreshDisplay(); updateRefreshDisplay();
@@ -229,7 +229,7 @@ function removeDuplicatesForBandPanel(spotList) {
// Load server options. Once a successful callback is made from this, we then query spots and set up the timer to query // Load server options. Once a successful callback is made from this, we then query spots and set up the timer to query
// spots repeatedly. // spots repeatedly.
function loadOptions() { function loadOptions() {
$.getJSON('/api/v1/options', function (jsonData) { $.getJSON('/api/v2/options', function (jsonData) {
// Store options // Store options
options = jsonData; options = jsonData;

View File

@@ -10,7 +10,7 @@ let ionosondeChart = null;
// Load solar conditions // Load solar conditions
function loadSolarConditions() { function loadSolarConditions() {
$.getJSON('/api/v1/solar', function (jsonData) { $.getJSON('/api/v2/solar', function (jsonData) {
// HF // HF
@@ -660,7 +660,7 @@ function dxStatsContientChanged() {
// Fetch DX stats from the API and render // Fetch DX stats from the API and render
function loadDxStats() { function loadDxStats() {
$.getJSON('/api/v1/dxstats', function (jsonData) { $.getJSON('/api/v2/dxstats', function (jsonData) {
dxStatsData = jsonData; dxStatsData = jsonData;
renderDxStats(); renderDxStats();
}); });

View File

@@ -28,7 +28,7 @@ let firstLoad = true;
// Load spots and populate the map. // Load spots and populate the map.
function loadSpots() { function loadSpots() {
$.getJSON('/api/v1/spots' + buildQueryString(true), function (jsonData) { $.getJSON('/api/v2/spots' + buildQueryString(true), function (jsonData) {
// Store data // Store data
spots = jsonData; spots = jsonData;
// Update map // Update map
@@ -194,7 +194,7 @@ function getTooltipText(s) {
// Load server options. Once a successful callback is made from this, we then query spots and set up the timer to query // Load server options. Once a successful callback is made from this, we then query spots and set up the timer to query
// spots repeatedly. // spots repeatedly.
function loadOptions() { function loadOptions() {
$.getJSON('/api/v1/options', function (jsonData) { $.getJSON('/api/v2/options', function (jsonData) {
// Store options // Store options
options = jsonData; options = jsonData;

View File

@@ -20,7 +20,7 @@ function loadSpots() {
} }
// Make the new query // Make the new query
$.getJSON('/api/v1/spots' + buildQueryString(false), function (jsonData) { $.getJSON('/api/v2/spots' + buildQueryString(false), function (jsonData) {
// Store data // Store data
spots = jsonData; spots = jsonData;
// Update table // Update table
@@ -39,7 +39,7 @@ function startSSEConnection() {
if (evtSource != null) { if (evtSource != null) {
evtSource.close(); evtSource.close();
} }
evtSource = new EventSource('/api/v1/spots/stream' + buildQueryString(true)); evtSource = new EventSource('/api/v2/spots/stream' + buildQueryString(true));
evtSource.onmessage = function (event) { evtSource.onmessage = function (event) {
// Get the new spot // Get the new spot
@@ -418,7 +418,7 @@ function createNewTableRowsForSpot(s, highlightNew) {
// Load server options. Once a successful callback is made from this, we then query spots and set up the timer to query // Load server options. Once a successful callback is made from this, we then query spots and set up the timer to query
// spots repeatedly. // spots repeatedly.
function loadOptions() { function loadOptions() {
$.getJSON('/api/v1/options', function (jsonData) { $.getJSON('/api/v2/options', function (jsonData) {
// Store options // Store options
options = jsonData; options = jsonData;

View File

@@ -1,6 +1,6 @@
// Load server status // Load server status
function loadStatus() { function loadStatus() {
$.getJSON('/api/v1/status', function (jsonData) { $.getJSON('/api/v2/status', function (jsonData) {
$("#software-version").text(jsonData["software-version"]); $("#software-version").text(jsonData["software-version"]);
$("#server-owner-callsign").text(jsonData["server-owner-callsign"]); $("#server-owner-callsign").text(jsonData["server-owner-callsign"]);
$("#up-since").text(moment().subtract(jsonData["uptime"], 'seconds').fromNow()); $("#up-since").text(moment().subtract(jsonData["uptime"], 'seconds').fromNow());