diff --git a/.gitignore b/.gitignore index c50e035..a6abbe3 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,3 @@ -/.idea /.venv __pycache__ *.pyc diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..26d3352 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,3 @@ +# Default ignored files +/shelf/ +/workspace.xml diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 0000000..146ab09 --- /dev/null +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,10 @@ + + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml new file mode 100644 index 0000000..105ce2d --- /dev/null +++ b/.idea/inspectionProfiles/profiles_settings.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/.idea/metaspot.iml b/.idea/metaspot.iml new file mode 100644 index 0000000..06bd9e9 --- /dev/null +++ b/.idea/metaspot.iml @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..4118577 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,7 @@ + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..b5318d1 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..35eb1dd --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/data/spot.py b/data/spot.py index 350cbcc..e28e0c4 100644 --- a/data/spot.py +++ b/data/spot.py @@ -17,7 +17,7 @@ from core.utils import infer_mode_type_from_mode, infer_band_from_freq, infer_co @dataclass class Spot: # Globally unique identifier for the spot - guid: str = str(uuid.uuid4()) + guid: str = None # Callsign of the operator that has been spotted dx_call: str = None # Callsign of the operator that has spotted them @@ -91,6 +91,9 @@ class Spot: # Infer missing parameters where possible def infer_missing(self): + # Always create a GUID + self.guid = str(uuid.uuid4()) + # Clean up DX call if it has an SSID or -# from RBN if self.dx_call and "-" in self.dx_call: self.dx_call = self.dx_call.split("-")[0] diff --git a/server/webserver.py b/server/webserver.py index 61ebc79..d6401f4 100644 --- a/server/webserver.py +++ b/server/webserver.py @@ -5,7 +5,7 @@ from threading import Thread import bottle import pytz -from bottle import run, response +from bottle import run, response, template from core.config import MAX_SPOT_AGE from core.constants import BANDS, ALL_MODES, MODE_TYPES, SIGS, SOURCES, CONTINENTS @@ -26,11 +26,15 @@ class WebServer: self.thread.daemon = True self.status = "Starting" - # Set up routing - bottle.get("/api/spots")(self.serve_api_spots) - bottle.get("/api/options")(self.serve_api_options) - bottle.get("/api/status")(self.serve_api_status) - bottle.get("/")(self.serve_index) + # Routes for API calls + bottle.get("/api/spots")(lambda: self.serve_api(self.get_spot_list_with_filters())) + bottle.get("/api/options")(lambda: self.serve_api(self.get_options())) + bottle.get("/api/status")(lambda: self.serve_api(self.status_data)) + # Routes for templated pages + bottle.get("/")(lambda: self.serve_template('webpage_home')) + bottle.get("/about")(lambda: self.serve_template('webpage_about')) + bottle.get("/apidocs")(lambda: self.serve_template('webpage_apidocs')) + # Default route to serve from "webassets" bottle.get("/")(self.serve_static_file) # Start the web server @@ -43,51 +47,29 @@ class WebServer: self.status = "Waiting" run(host='localhost', port=self.port) - # Main spots API - def serve_api_spots(self): + # Serve a JSON API endpoint + def serve_api(self, data): self.last_api_access_time = datetime.now(pytz.UTC) self.status = "OK" - spots_json = json.dumps(self.get_spot_list_with_filters(bottle.request.query), default=serialize_everything) response.content_type = 'application/json' - return spots_json + return json.dumps(data, default=serialize_everything) - # Options API - def serve_api_options(self): - self.last_api_access_time = datetime.now(pytz.UTC) - self.status = "OK" - status_json = json.dumps(self.get_options(), default=serialize_everything) - response.content_type = 'application/json' - return status_json - - # Server status API - def serve_api_status(self): - self.last_api_access_time = datetime.now(pytz.UTC) - self.status = "OK" - status_json = json.dumps(self.status_data, default=serialize_everything) - response.content_type = 'application/json' - return status_json - - # Serve the home page. This would be accessible as /index.html but we need this workaround to make it available as / - def serve_index(self): - return self.serve_static_file("") - - # Serve general static files from "webassets" directory, along with some extra workarounds to make URLs such as - # "/", "/about" and "/apidocs" work. - def serve_static_file(self, filepath): + # Serve a templated page + def serve_template(self, template_name): self.last_page_access_time = datetime.now(pytz.UTC) self.status = "OK" - if filepath == "": - return bottle.static_file("index.html", root="webassets") - elif filepath == "about": - return bottle.static_file("about.html", root="webassets") - elif filepath == "apidocs": - return bottle.static_file("index.html", root="webassets/apidocs") - else: - return bottle.static_file(filepath, root="webassets") + return template(template_name) + + # Serve general static files from "webassets" directory. + def serve_static_file(self, filepath): + return bottle.static_file(filepath, root="webassets") # Utility method to apply filters to the overall spot list and return only a subset. Enables query parameters in - # the main "spots" GET call. The "query" parameter should be the result of bottle's request.query, and is a MultiDict - def get_spot_list_with_filters(self, query): + # the main "spots" GET call. + def get_spot_list_with_filters(self): + # Get the query (and the right one, with Bottle magic. This is a MultiDict object + query = bottle.request.query + # Create a shallow copy of the spot list, ordered by spot time. We'll then filter it accordingly. # We can filter by spot time and received time with "since" and "received_since", which take a UNIX timestamp # in seconds UTC. diff --git a/views/webpage_about.tpl b/views/webpage_about.tpl new file mode 100644 index 0000000..1fc33e4 --- /dev/null +++ b/views/webpage_about.tpl @@ -0,0 +1,12 @@ +% rebase('webpage_base.tpl') + +
+

About (S)pothole

+

(S)pothole is a utility to aggregate "spots" from amateur radio DX clusters and xOTA spotting sites, and provide an open JSON API as well as a website to browse the data.

+

While there are several other web-based interfaces to DX clusters, and sites that aggregate spots from various outfoor activity programmes for amateur radio, (S)pothole differentiates itself by supporting a large number of data sources, and by being "API first" rather than just providing a web front-end. This allows other software to be built on top of it.

+

The API is deliberately well-defined with an OpenAPI specification and auto-generated API documentation. The API delivers spots in a consistent format regardless of the data source, freeing developers from needing to know how each individual data source presents its data.

+

(S)pothole itself is also open source, Public Domain licenced code that anyone can take and modify. The source code is here.

+

Supported data sources include DX Clusters, the Reverse Beacon Network (RBN), the APRS Internet Service (APRS-IS), POTA, SOTA, WWFF, GMA, WWBOTA, HEMA, and Parks 'n' Peaks.

+

The software was written by Ian Renton, MØTRT.

+

« Back home

+
\ No newline at end of file diff --git a/views/webpage_apidocs.tpl b/views/webpage_apidocs.tpl new file mode 100644 index 0000000..f9e7f14 --- /dev/null +++ b/views/webpage_apidocs.tpl @@ -0,0 +1,4 @@ +% rebase('webpage_base.tpl') + + + \ No newline at end of file diff --git a/webassets/index.html b/views/webpage_base.tpl similarity index 86% rename from webassets/index.html rename to views/webpage_base.tpl index da1e9fd..c36ad07 100644 --- a/webassets/index.html +++ b/views/webpage_base.tpl @@ -21,12 +21,12 @@ - +
-
diff --git a/views/webpage_home.tpl b/views/webpage_home.tpl new file mode 100644 index 0000000..2d608dd --- /dev/null +++ b/views/webpage_home.tpl @@ -0,0 +1,6 @@ +% rebase('webpage_base.tpl') + +
+

Latest spots as of XXXX. Updating in XXX seconds...

+
+
\ No newline at end of file diff --git a/webassets/about.html b/webassets/about.html deleted file mode 100644 index db99a02..0000000 --- a/webassets/about.html +++ /dev/null @@ -1,46 +0,0 @@ - - - - - - - (S)pothole - - - - - - - - - - - -
- - -
-
-

About (S)pothole

-

(S)pothole is a utility to aggregate "spots" from amateur radio DX clusters and xOTA spotting sites, and provide an open JSON API as well as a website to browse the data.

-

While there are several other web-based interfaces to DX clusters, and sites that aggregate spots from various outfoor activity programmes for amateur radio, (S)pothole differentiates itself by supporting a large number of data sources, and by being "API first" rather than just providing a web front-end. This allows other software to be built on top of it.

-

The API is deliberately well-defined with an OpenAPI specification and auto-generated API documentation. The API delivers spots in a consistent format regardless of the data source, freeing developers from needing to know how each individual data source presents its data.

-

(S)pothole itself is also open source, Public Domain licenced code that anyone can take and modify. The source code is here.

-

Supported data sources include DX Clusters, the Reverse Beacon Network (RBN), the APRS Internet Service (APRS-IS), POTA, SOTA, WWFF, GMA, WWBOTA, HEMA, and Parks 'n' Peaks.

-

The software was written by Ian Renton, MØTRT.

-

« Back home

-
-
-
- - - diff --git a/webassets/apidocs/index.html b/webassets/apidocs/index.html deleted file mode 100644 index 1679373..0000000 --- a/webassets/apidocs/index.html +++ /dev/null @@ -1,19 +0,0 @@ - - - - - API Documentation - - - - - - \ No newline at end of file diff --git a/webassets/js/code.js b/webassets/js/code.js index f514e8e..8d91a4c 100644 --- a/webassets/js/code.js +++ b/webassets/js/code.js @@ -17,7 +17,7 @@ $.getJSON('/api/spots', function(jsonData) { table.find('tbody').append($tr); }); - $('#table-container-inner').html(table); + $('#table-container').html(table); }); function escapeHtml(str) {