mirror of
https://git.ianrenton.com/ian/spothole.git
synced 2025-10-27 08:49:27 +00:00
POSTing a spot should use the request body not a URL param. #35
This commit is contained in:
@@ -1,7 +1,5 @@
|
|||||||
# 
|
# 
|
||||||
|
|
||||||
**Work in progress.**
|
|
||||||
|
|
||||||
Spothole 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.
|
Spothole 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.
|
||||||
|
|
||||||

|

|
||||||
|
|||||||
@@ -5,7 +5,7 @@ from threading import Thread
|
|||||||
|
|
||||||
import bottle
|
import bottle
|
||||||
import pytz
|
import pytz
|
||||||
from bottle import run, response, template
|
from bottle import run, request, response, template
|
||||||
|
|
||||||
from core.config import MAX_SPOT_AGE, ALLOW_SPOTTING
|
from core.config import MAX_SPOT_AGE, ALLOW_SPOTTING
|
||||||
from core.constants import BANDS, ALL_MODES, MODE_TYPES, SIGS, CONTINENTS
|
from core.constants import BANDS, ALL_MODES, MODE_TYPES, SIGS, CONTINENTS
|
||||||
@@ -74,14 +74,21 @@ class WebServer:
|
|||||||
return json.dumps("Error - this server does not allow new spots to be added via the API.",
|
return json.dumps("Error - this server does not allow new spots to be added via the API.",
|
||||||
default=serialize_everything)
|
default=serialize_everything)
|
||||||
|
|
||||||
# Reject if no spot
|
# Reject if format not json
|
||||||
if not bottle.request.query.spot:
|
if not request.get_header('Content-Type') or request.get_header('Content-Type') != "application/json":
|
||||||
|
response.content_type = 'application/json'
|
||||||
|
response.status = 415
|
||||||
|
return json.dumps("Error - request Content-Type must be application/json", default=serialize_everything)
|
||||||
|
|
||||||
|
# Reject if request body is empty
|
||||||
|
post_data = request.body.read()
|
||||||
|
if not post_data:
|
||||||
response.content_type = 'application/json'
|
response.content_type = 'application/json'
|
||||||
response.status = 422
|
response.status = 422
|
||||||
return json.dumps("Error - no 'spot' parameter provided", default=serialize_everything)
|
return json.dumps("Error - request body is empty", default=serialize_everything)
|
||||||
|
|
||||||
# Read in the spot as JSON then convert to a Spot object
|
# Read in the request body as JSON then convert to a Spot object
|
||||||
json_spot = json.loads(bottle.request.query.spot)
|
json_spot = json.loads(post_data)
|
||||||
spot = Spot(**json_spot)
|
spot = Spot(**json_spot)
|
||||||
|
|
||||||
# Reject if no timestamp or dx_call
|
# Reject if no timestamp or dx_call
|
||||||
@@ -96,6 +103,7 @@ class WebServer:
|
|||||||
spot.icon = "desktop"
|
spot.icon = "desktop"
|
||||||
spot.infer_missing()
|
spot.infer_missing()
|
||||||
self.spots.add(spot.id, spot, expire=MAX_SPOT_AGE)
|
self.spots.add(spot.id, spot, expire=MAX_SPOT_AGE)
|
||||||
|
print(spot)
|
||||||
|
|
||||||
response.content_type = 'application/json'
|
response.content_type = 'application/json'
|
||||||
response.set_header('Cache-Control', 'no-store')
|
response.set_header('Cache-Control', 'no-store')
|
||||||
@@ -103,7 +111,7 @@ class WebServer:
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.error(e)
|
logging.error(e)
|
||||||
response.content_type = 'application/json'
|
response.content_type = 'application/json'
|
||||||
response.status = 422
|
response.status = 500
|
||||||
return json.dumps("Error - " + str(e), default=serialize_everything)
|
return json.dumps("Error - " + str(e), default=serialize_everything)
|
||||||
|
|
||||||
# Serve a templated page
|
# Serve a templated page
|
||||||
@@ -220,7 +228,7 @@ class WebServer:
|
|||||||
# The idea is that this will include most of the things that can be provided as queries to the main spots call,
|
# The idea is that this will include most of the things that can be provided as queries to the main spots call,
|
||||||
# and thus a client can use this data to configure its filter controls.
|
# and thus a client can use this data to configure its filter controls.
|
||||||
def get_options(self):
|
def get_options(self):
|
||||||
return {"bands": BANDS,
|
options = {"bands": BANDS,
|
||||||
"modes": ALL_MODES,
|
"modes": ALL_MODES,
|
||||||
"mode_types": MODE_TYPES,
|
"mode_types": MODE_TYPES,
|
||||||
"sigs": SIGS,
|
"sigs": SIGS,
|
||||||
@@ -231,3 +239,9 @@ class WebServer:
|
|||||||
map(lambda p: p["name"], filter(lambda p: p["enabled"], self.status_data["alert_providers"]))),
|
map(lambda p: p["name"], filter(lambda p: p["enabled"], self.status_data["alert_providers"]))),
|
||||||
"continents": CONTINENTS,
|
"continents": CONTINENTS,
|
||||||
"max_spot_age": MAX_SPOT_AGE}
|
"max_spot_age": MAX_SPOT_AGE}
|
||||||
|
# If spotting to this server is enabled, "API" is another valid spot source even though it does not come from
|
||||||
|
# one of our proviers.
|
||||||
|
if ALLOW_SPOTTING:
|
||||||
|
options["spot_sources"].append("API")
|
||||||
|
|
||||||
|
return options
|
||||||
|
|||||||
@@ -263,7 +263,7 @@ paths:
|
|||||||
/status:
|
/status:
|
||||||
get:
|
get:
|
||||||
tags:
|
tags:
|
||||||
- status
|
- general
|
||||||
summary: Get server status
|
summary: Get server status
|
||||||
description: Query information about the server for use in a diagnostics display.
|
description: Query information about the server for use in a diagnostics display.
|
||||||
operationId: status
|
operationId: status
|
||||||
@@ -340,7 +340,7 @@ paths:
|
|||||||
/options:
|
/options:
|
||||||
get:
|
get:
|
||||||
tags:
|
tags:
|
||||||
- spots
|
- general
|
||||||
summary: Get enumeration options
|
summary: Get enumeration options
|
||||||
description: Retrieves the list of options for various enumerated types, which can be found in the spots and also provided back to the API as query parameters. While these enumerated options are defined in this spec anyway, providing them in an API call allows us to define extra parameters, like the colours associated with bands, and also allows clients to set up their filters and features without having to have internal knowledge about, for example, what bands the server knows about.
|
description: Retrieves the list of options for various enumerated types, which can be found in the spots and also provided back to the API as query parameters. While these enumerated options are defined in this spec anyway, providing them in an API call allows us to define extra parameters, like the colours associated with bands, and also allows clients to set up their filters and features without having to have internal knowledge about, for example, what bands the server knows about.
|
||||||
operationId: options
|
operationId: options
|
||||||
@@ -398,14 +398,14 @@ 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. Currently, this will not be reported up the chain to a cluster, POTA, SOTA etc. This will be introduced in a future version.
|
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/spot`"
|
||||||
operationId: spot
|
operationId: spot
|
||||||
parameters:
|
requestBody:
|
||||||
- name: spot
|
description: The JSON spot object
|
||||||
description: The spot data to post. The structure must contain at least "time" and "dx_call" to be accepted.
|
required: true
|
||||||
required: true
|
content:
|
||||||
schema:
|
application/json:
|
||||||
type:
|
schema:
|
||||||
$ref: '#/components/schemas/Spot'
|
$ref: '#/components/schemas/Spot'
|
||||||
responses:
|
responses:
|
||||||
'200':
|
'200':
|
||||||
@@ -415,6 +415,13 @@ paths:
|
|||||||
schema:
|
schema:
|
||||||
type: string
|
type: string
|
||||||
example: "OK"
|
example: "OK"
|
||||||
|
'415':
|
||||||
|
description: Incorrect Content-Type
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
example: "Failed"
|
||||||
'422':
|
'422':
|
||||||
description: Validation error
|
description: Validation error
|
||||||
content:
|
content:
|
||||||
@@ -422,6 +429,13 @@ paths:
|
|||||||
schema:
|
schema:
|
||||||
type: string
|
type: string
|
||||||
example: "Failed"
|
example: "Failed"
|
||||||
|
'500':
|
||||||
|
description: Internal server error
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
example: "Failed"
|
||||||
|
|
||||||
|
|
||||||
components:
|
components:
|
||||||
|
|||||||
Reference in New Issue
Block a user