diff --git a/server/webserver.py b/server/webserver.py
index e617255..3a9029f 100644
--- a/server/webserver.py
+++ b/server/webserver.py
@@ -1,5 +1,6 @@
import json
import logging
+import re
from datetime import datetime, timedelta
from threading import Thread
@@ -8,7 +9,8 @@ import pytz
from bottle import run, request, response, template
from core.config import MAX_SPOT_AGE, ALLOW_SPOTTING
-from core.constants import BANDS, ALL_MODES, MODE_TYPES, SIGS, CONTINENTS, SOFTWARE_VERSION
+from core.constants import BANDS, ALL_MODES, MODE_TYPES, SIGS, CONTINENTS, SOFTWARE_VERSION, UNKNOWN_BAND
+from core.lookup_helper import lookup_helper
from core.prometheus_metrics_handler import page_requests_counter, get_metrics, api_requests_counter
from data.spot import Spot
@@ -138,13 +140,31 @@ class WebServer:
json_spot = json.loads(post_data)
spot = Spot(**json_spot)
- # Reject if no timestamp or dx_call
- if not spot.time or not spot.dx_call:
+ # 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:
response.content_type = 'application/json'
response.status = 422
- return json.dumps("Error - 'time' and 'dx_call' must be provided as a minimum.",
+ return json.dumps("Error - 'time', 'dx_call', 'freq' and 'de_call' must be provided as a minimum.",
default=serialize_everything)
+ # Reject invalid-looking callsigns
+ if not re.match(r"^[A-Za-z0-9/\-]*$", spot.dx_call):
+ response.content_type = 'application/json'
+ response.status = 422
+ return json.dumps("Error - '" + spot.dx_call + "' does not look like a valid callsign.",
+ default=serialize_everything)
+ if not re.match(r"^[A-Za-z0-9/\-]*$", spot.de_call):
+ response.content_type = 'application/json'
+ response.status = 422
+ return json.dumps("Error - '" + spot.de_call + "' does not look like a valid callsign.",
+ default=serialize_everything)
+
+ # Reject if frequency not in a known band
+ if lookup_helper.infer_band_from_freq(spot.freq) == UNKNOWN_BAND:
+ response.content_type = 'application/json'
+ response.status = 422
+ return json.dumps("Error - Frequency of " + str(spot.freq / 1000.0) + "kHz is not in a known band.", default=serialize_everything)
+
# infer missing data, and add it to our database.
spot.source = "API"
spot.icon = "desktop"
diff --git a/views/webpage_add_spot.tpl b/views/webpage_add_spot.tpl
index a53a768..8793409 100644
--- a/views/webpage_add_spot.tpl
+++ b/views/webpage_add_spot.tpl
@@ -1,7 +1,10 @@
% rebase('webpage_base.tpl')
-
- Please note that spots added to Spothole are not currently sent "upstream" to DX clusters or xOTA spotting sites.
+
+
+ Adding spots to Spothole This page is implemented as a proof of concept for adding spots to the Spothole system. Currently, spots added in this way are only visible within Spothole and are not sent "upstream" to DX clusters or xOTA spotting sites. The functionality might be extended to include this in future if there is demand for it. If you'd like this to be added, please give a thumbs-up on issue #70 or get in touch via email.
+
+
What is Spothole? Spothole is an aggregator of amateur radio spots from DX clusters and outdoor activity programmes. It's free for anyone to use and includes an API that developers can build other applications on. For more information, check out the "About" page. If that sounds like nonsense to you, you can visit the FAQ section to learn more.
diff --git a/webassets/apidocs/openapi.yml b/webassets/apidocs/openapi.yml
index 44858ac..691672f 100644
--- a/webassets/apidocs/openapi.yml
+++ b/webassets/apidocs/openapi.yml
@@ -493,7 +493,7 @@ paths:
tags:
- spots
summary: Add a spot
- description: "Supply a new spot object, which will be added to the system. Currently, this will not be reported up the chain to a cluster, POTA, SOTA etc. This will be introduced in a future version. cURL example: `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 new spot object, which will be added to the system. Currently, this will not be reported up the chain to a cluster, POTA, SOTA etc. This may be introduced in a future version. cURL example: `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`"
operationId: spot
requestBody:
description: The JSON spot object
diff --git a/webassets/css/style.css b/webassets/css/style.css
index 6e3777e..f3ae3a9 100644
--- a/webassets/css/style.css
+++ b/webassets/css/style.css
@@ -7,7 +7,7 @@
/* INTRO/WARNING BOXES */
-#intro-box {
+.permanently-dismissible-box {
display: none;
}
diff --git a/webassets/js/add-spot.js b/webassets/js/add-spot.js
index e1ecc75..5d0c57a 100644
--- a/webassets/js/add-spot.js
+++ b/webassets/js/add-spot.js
@@ -48,7 +48,7 @@ function addSpot() {
}, 1000);
},
error: function (result) {
- showAddSpotError(result.responseText);
+ showAddSpotError(result.responseText.slice(1,-1));
}
});
} catch (error) {
@@ -73,8 +73,20 @@ $("#mode").change(function () {
$(this).val($(this).val().trim().toUpperCase());
});
+// Display the intro box, unless the user has already dismissed it once.
+function displayIntroBox() {
+ if (localStorage.getItem("add-spot-intro-box-dismissed") == null) {
+ $("#add-spot-intro-box").show();
+ }
+ $("#add-spot-intro-box-dismiss").click(function() {
+ localStorage.setItem("add-spot-intro-box-dismissed", true);
+ });
+}
+
// Startup
$(document).ready(function() {
// Load settings from settings storage
loadSettings();
+ // Display intro box
+ displayIntroBox();
});
\ No newline at end of file