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:
@@ -14,12 +14,12 @@ info:
|
||||
Spothole's source code is located at https://git.ianrenton.com/ian/spothole and the README there provides setup instructions if you would like to run your own copy. A demonstration server of Spothole is located at https://spothole.app.
|
||||
|
||||
## Changelog
|
||||
|
||||
### 1.4
|
||||
|
||||
* 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.
|
||||
### 2.0
|
||||
|
||||
* 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.)
|
||||
* 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
|
||||
|
||||
@@ -42,10 +42,10 @@ info:
|
||||
license:
|
||||
name: The Unlicense
|
||||
url: https://unlicense.org/#the-unlicense
|
||||
version: 1.4
|
||||
version: 2.0
|
||||
|
||||
servers:
|
||||
- url: https://spothole.app/api/v1
|
||||
- url: https://spothole.app/api/v2
|
||||
|
||||
tags:
|
||||
- name: Spots
|
||||
@@ -356,10 +356,10 @@ paths:
|
||||
tags:
|
||||
- Spots
|
||||
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
|
||||
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
|
||||
content:
|
||||
application/json:
|
||||
@@ -997,11 +997,18 @@ components:
|
||||
|
||||
SpotSubmission:
|
||||
description: >
|
||||
Request body for POST /spot. Contains all the fields of a Spot, plus optional
|
||||
upstream submission control fields that are consumed by the server and never stored in the spot.
|
||||
allOf:
|
||||
- $ref: '#/components/schemas/Spot'
|
||||
- type: object
|
||||
Request body for POST /spot. Contains a "spot" sub-object with the spot data, and an optional
|
||||
"handling" sub-object with server-side instructions consumed by Spothole.
|
||||
type: object
|
||||
required:
|
||||
- spot
|
||||
properties:
|
||||
spot:
|
||||
$ref: '#/components/schemas/Spot'
|
||||
handling:
|
||||
type: object
|
||||
description: >
|
||||
Optional server-side instructions for how to process this spot submission.
|
||||
properties:
|
||||
submit_upstream:
|
||||
type: boolean
|
||||
@@ -1021,7 +1028,7 @@ components:
|
||||
type: object
|
||||
description: >
|
||||
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.
|
||||
additionalProperties:
|
||||
type: string
|
||||
@@ -1032,8 +1039,7 @@ components:
|
||||
type: string
|
||||
description: >
|
||||
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
|
||||
operator has set up reCAPTCHA keys). Obtain the token by completing the reCAPTCHA
|
||||
server has reCAPTCHA configured. Obtain the token by completing the reCAPTCHA
|
||||
widget rendered on the Add Spot page.
|
||||
example: "03AFY_a8Xq..."
|
||||
|
||||
@@ -1678,7 +1684,7 @@ components:
|
||||
example: "EU"
|
||||
max_spot_age:
|
||||
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
|
||||
spot_allowed:
|
||||
type: boolean
|
||||
|
||||
@@ -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
|
||||
// any saved values from local storage.
|
||||
function loadOptions() {
|
||||
$.getJSON('/api/v1/options', function (jsonData) {
|
||||
$.getJSON('/api/v2/options', function (jsonData) {
|
||||
// Store options
|
||||
options = jsonData;
|
||||
|
||||
@@ -203,6 +203,7 @@ function addSpot() {
|
||||
const comment = $("#comment").val();
|
||||
const de = $("#de-call").val().toUpperCase();
|
||||
|
||||
// Prepare the spot object for the server
|
||||
const spot = {};
|
||||
if (dx !== "") {
|
||||
spot["dx_call"] = dx;
|
||||
@@ -240,6 +241,19 @@ function addSpot() {
|
||||
}
|
||||
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
|
||||
const submitUpstream = $("#submit-upstream").is(":checked");
|
||||
const upstreamProviderName = getSelectedUpstreamProvider();
|
||||
@@ -261,24 +275,13 @@ function addSpot() {
|
||||
return;
|
||||
}
|
||||
|
||||
const creds = loadCredentials(upstreamProviderName);
|
||||
spot["submit_upstream"] = true;
|
||||
spot["upstream_provider"] = 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;
|
||||
}
|
||||
handling["submit_upstream"] = true;
|
||||
handling["upstream_provider"] = upstreamProviderName;
|
||||
handling["upstream_credentials"] = loadCredentials(upstreamProviderName);
|
||||
}
|
||||
|
||||
$.ajax("/api/v1/spot", {
|
||||
data: JSON.stringify(spot),
|
||||
$.ajax("/api/v2/spot", {
|
||||
data: JSON.stringify({spot, handling}),
|
||||
contentType: 'application/json',
|
||||
type: 'POST',
|
||||
timeout: 10000,
|
||||
|
||||
@@ -6,7 +6,7 @@ let alerts = [];
|
||||
|
||||
// Load alerts and populate the table.
|
||||
function loadAlerts() {
|
||||
$.getJSON('/api/v1/alerts' + buildQueryString(false), function (jsonData) {
|
||||
$.getJSON('/api/v2/alerts' + buildQueryString(false), function (jsonData) {
|
||||
// Store last updated time
|
||||
lastUpdateTime = moment.utc();
|
||||
updateRefreshDisplay();
|
||||
@@ -280,7 +280,7 @@ function addAlertRowsToTable(tbody, alerts) {
|
||||
|
||||
// Load server options. Once a successful callback is made from this, we then query alerts.
|
||||
function loadOptions() {
|
||||
$.getJSON('/api/v1/options', function (jsonData) {
|
||||
$.getJSON('/api/v2/options', function (jsonData) {
|
||||
// Store options
|
||||
options = jsonData;
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ BAND_COLUMN_SPOT_DIV_HEIGHT_PX = BAND_COLUMN_FONT_SIZE * 1.6;
|
||||
|
||||
// Load spots and populate the bands display.
|
||||
function loadSpots() {
|
||||
$.getJSON('/api/v1/spots' + buildQueryString(false), function (jsonData) {
|
||||
$.getJSON('/api/v2/spots' + buildQueryString(false), function (jsonData) {
|
||||
// Store last updated time
|
||||
lastUpdateTime = moment.utc();
|
||||
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
|
||||
// spots repeatedly.
|
||||
function loadOptions() {
|
||||
$.getJSON('/api/v1/options', function (jsonData) {
|
||||
$.getJSON('/api/v2/options', function (jsonData) {
|
||||
// Store options
|
||||
options = jsonData;
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ let ionosondeChart = null;
|
||||
|
||||
// Load solar conditions
|
||||
function loadSolarConditions() {
|
||||
$.getJSON('/api/v1/solar', function (jsonData) {
|
||||
$.getJSON('/api/v2/solar', function (jsonData) {
|
||||
|
||||
// HF
|
||||
|
||||
@@ -660,7 +660,7 @@ function dxStatsContientChanged() {
|
||||
|
||||
// Fetch DX stats from the API and render
|
||||
function loadDxStats() {
|
||||
$.getJSON('/api/v1/dxstats', function (jsonData) {
|
||||
$.getJSON('/api/v2/dxstats', function (jsonData) {
|
||||
dxStatsData = jsonData;
|
||||
renderDxStats();
|
||||
});
|
||||
|
||||
@@ -28,7 +28,7 @@ let firstLoad = true;
|
||||
|
||||
// Load spots and populate the map.
|
||||
function loadSpots() {
|
||||
$.getJSON('/api/v1/spots' + buildQueryString(true), function (jsonData) {
|
||||
$.getJSON('/api/v2/spots' + buildQueryString(true), function (jsonData) {
|
||||
// Store data
|
||||
spots = jsonData;
|
||||
// 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
|
||||
// spots repeatedly.
|
||||
function loadOptions() {
|
||||
$.getJSON('/api/v1/options', function (jsonData) {
|
||||
$.getJSON('/api/v2/options', function (jsonData) {
|
||||
// Store options
|
||||
options = jsonData;
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ function loadSpots() {
|
||||
}
|
||||
|
||||
// Make the new query
|
||||
$.getJSON('/api/v1/spots' + buildQueryString(false), function (jsonData) {
|
||||
$.getJSON('/api/v2/spots' + buildQueryString(false), function (jsonData) {
|
||||
// Store data
|
||||
spots = jsonData;
|
||||
// Update table
|
||||
@@ -39,7 +39,7 @@ function startSSEConnection() {
|
||||
if (evtSource != null) {
|
||||
evtSource.close();
|
||||
}
|
||||
evtSource = new EventSource('/api/v1/spots/stream' + buildQueryString(true));
|
||||
evtSource = new EventSource('/api/v2/spots/stream' + buildQueryString(true));
|
||||
|
||||
evtSource.onmessage = function (event) {
|
||||
// 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
|
||||
// spots repeatedly.
|
||||
function loadOptions() {
|
||||
$.getJSON('/api/v1/options', function (jsonData) {
|
||||
$.getJSON('/api/v2/options', function (jsonData) {
|
||||
// Store options
|
||||
options = jsonData;
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// Load server status
|
||||
function loadStatus() {
|
||||
$.getJSON('/api/v1/status', function (jsonData) {
|
||||
$.getJSON('/api/v2/status', function (jsonData) {
|
||||
$("#software-version").text(jsonData["software-version"]);
|
||||
$("#server-owner-callsign").text(jsonData["server-owner-callsign"]);
|
||||
$("#up-since").text(moment().subtract(jsonData["uptime"], 'seconds').fromNow());
|
||||
|
||||
Reference in New Issue
Block a user