Fix API documentation to match implementation
- REST API: Fix id type from integer to UUID, add device/equipment fields, correct rate limiting info (100 req/min, not "none"), add 429 status code - Mobile API: Fix search_callsign response field (lon not lng), add path to packet fields, correct rate limiting/timeout docs, clarify update_bounds behavior - Fix flaky pipeline tests (async race on global Application config)
This commit is contained in:
parent
33b4a35900
commit
f6730b282a
5 changed files with 49 additions and 24 deletions
|
|
@ -111,7 +111,7 @@ Subscribe to receive packets within specific geographic bounds. Upon subscriptio
|
|||
|
||||
### Updating Bounds
|
||||
|
||||
Update the geographic bounds (e.g., when user pans/zooms the map).
|
||||
Update the geographic bounds (e.g., when user pans/zooms the map). This updates the real-time streaming filter only; it does not re-send historical packets for the new bounds.
|
||||
|
||||
**Event:** `update_bounds`
|
||||
|
||||
|
|
@ -174,19 +174,21 @@ Once subscribed, you'll receive packets within your bounds. Historical packets a
|
|||
|
||||
| Field | Type | Description | Required |
|
||||
|-------|------|-------------|----------|
|
||||
| `id` | string | Unique packet identifier | Yes |
|
||||
| `callsign` | string | Station callsign | Yes |
|
||||
| `id` | string | Unique packet identifier (UUID) | Yes |
|
||||
| `callsign` | string | Station callsign (sender). For APRS objects/items, this is still the sender callsign, not the object name. | Yes |
|
||||
| `lat` | float | Latitude (-90 to 90) | Yes |
|
||||
| `lng` | float | Longitude (-180 to 180) | Yes |
|
||||
| `timestamp` | string | ISO 8601 timestamp | Yes |
|
||||
| `symbol_table_id` | string | APRS symbol table (/, \\) | Yes |
|
||||
| `symbol_code` | string | APRS symbol code | Yes |
|
||||
| `comment` | string | Station comment/status | Optional |
|
||||
| `altitude` | float | Altitude in meters | Optional |
|
||||
| `altitude` | float | Altitude in feet | Optional |
|
||||
| `speed` | float | Speed in knots | Optional |
|
||||
| `course` | integer | Course in degrees (0-359) | Optional |
|
||||
| `path` | string | APRS digipeater path | Optional |
|
||||
|
||||
**Note:** Optional fields are omitted from the payload entirely when their value is `nil`.
|
||||
|
||||
### Unsubscribing
|
||||
|
||||
Stop receiving packets.
|
||||
|
|
@ -245,7 +247,7 @@ Search for callsigns matching a pattern.
|
|||
"base_callsign": "W5ISP",
|
||||
"last_seen": "2025-10-25T17:30:00Z",
|
||||
"lat": 33.1225,
|
||||
"lng": -96.124
|
||||
"lon": -96.124
|
||||
}
|
||||
],
|
||||
"count": 1
|
||||
|
|
@ -461,11 +463,9 @@ struct APRSMapView: View {
|
|||
}
|
||||
```
|
||||
|
||||
## Rate Limiting
|
||||
## Connection Timeout
|
||||
|
||||
The API is rate-limited to prevent abuse:
|
||||
- 200 requests per minute per IP address
|
||||
- Connections are automatically closed if idle for >60 seconds
|
||||
WebSocket connections have a 60-second heartbeat timeout. If the server does not receive a heartbeat within 60 seconds, the connection is closed. Phoenix Channels clients send heartbeats automatically (every 30 seconds by default).
|
||||
|
||||
## Best Practices
|
||||
|
||||
|
|
|
|||
|
|
@ -44,7 +44,8 @@ defmodule AprsmeWeb.MobileChannel do
|
|||
comment: string (optional),
|
||||
altitude: float (optional),
|
||||
speed: float (optional),
|
||||
course: integer (optional)
|
||||
course: integer (optional),
|
||||
path: string (optional)
|
||||
}
|
||||
|
||||
- "subscription_confirmed" - Bounds subscription successful
|
||||
|
|
|
|||
|
|
@ -177,10 +177,22 @@ defmodule AprsmeWeb.ApiDocsLive do
|
|||
end
|
||||
|
||||
defp format_equipment(packet) do
|
||||
device =
|
||||
case packet.device_identifier do
|
||||
nil -> nil
|
||||
"" -> nil
|
||||
identifier -> Aprsme.DeviceCache.lookup_device(identifier)
|
||||
end
|
||||
|
||||
equipment =
|
||||
%{}
|
||||
|> maybe_add("device_identifier", packet.device_identifier)
|
||||
|> maybe_add("manufacturer", packet.manufacturer)
|
||||
|> maybe_add("equipment_type", packet.equipment_type)
|
||||
|> maybe_add("device_model", device && device.model)
|
||||
|> maybe_add("device_vendor", device && device.vendor)
|
||||
|> maybe_add("device_contact", device && device.contact)
|
||||
|> maybe_add("device_class", device && device.class)
|
||||
|
||||
if map_size(equipment) == 0, do: nil, else: equipment
|
||||
end
|
||||
|
|
@ -266,7 +278,7 @@ defmodule AprsmeWeb.ApiDocsLive do
|
|||
<div class="ml-3">
|
||||
<h3 class="text-sm font-bold text-yellow-700 dark:text-yellow-400">Rate Limiting</h3>
|
||||
<div class="text-sm text-yellow-700 dark:text-yellow-400">
|
||||
Currently no rate limiting is enforced, but please be respectful and avoid excessive requests. Rate limiting may be implemented in the future.
|
||||
API requests are rate limited to 100 requests per minute per IP address. Exceeding this limit will return a 429 Too Many Requests response.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -344,7 +356,7 @@ defmodule AprsmeWeb.ApiDocsLive do
|
|||
<div class="bg-gray-900 text-gray-100 dark:bg-gray-950 p-4 rounded-lg overflow-x-auto mb-4">
|
||||
<pre><code><%= raw ~s|{
|
||||
"data": {
|
||||
"id": 12345,
|
||||
"id": "d7249877-d4a6-45c2-b314-2a8a355d2566",
|
||||
"callsign": "N0CALL-9",
|
||||
"base_callsign": "N0CALL",
|
||||
"ssid": "9",
|
||||
|
|
@ -354,7 +366,7 @@ defmodule AprsmeWeb.ApiDocsLive do
|
|||
"data_type": "position",
|
||||
"information_field": "!4740.00N/12200.00W>Mobile Station",
|
||||
"raw_packet": "N0CALL-9>APRS,WIDE1-1,WIDE2-1:!4740.00N/12200.00W>Mobile Station",
|
||||
"received_at": "2024-01-15T10:30:45.123456Z",
|
||||
"received_at": "2024-01-15T10:30:45Z",
|
||||
"region": "US-West",
|
||||
"position": {
|
||||
"latitude": 47.666667,
|
||||
|
|
@ -372,13 +384,17 @@ defmodule AprsmeWeb.ApiDocsLive do
|
|||
"aprs_messaging": false,
|
||||
"weather": null,
|
||||
"equipment": {
|
||||
"device_identifier": "APK004",
|
||||
"manufacturer": "Kenwood",
|
||||
"equipment_type": "TM-D710"
|
||||
"equipment_type": "TM-D710",
|
||||
"device_model": "TM-D710G",
|
||||
"device_vendor": "Kenwood",
|
||||
"device_class": "rig"
|
||||
},
|
||||
"message": null,
|
||||
"has_position": true,
|
||||
"inserted_at": "2024-01-15T10:30:45.123456Z",
|
||||
"updated_at": "2024-01-15T10:30:45.123456Z"
|
||||
"inserted_at": "2024-01-15T10:30:45Z",
|
||||
"updated_at": "2024-01-15T10:30:45Z"
|
||||
}
|
||||
}| %></code></pre>
|
||||
</div>
|
||||
|
|
@ -420,7 +436,7 @@ defmodule AprsmeWeb.ApiDocsLive do
|
|||
<tbody class="divide-y divide-gray-200 dark:divide-gray-700">
|
||||
<tr>
|
||||
<td class="whitespace-nowrap px-3 py-4 text-sm text-gray-500 dark:text-gray-400 font-mono">id</td>
|
||||
<td class="whitespace-nowrap px-3 py-4 text-sm text-gray-500 dark:text-gray-400">integer</td>
|
||||
<td class="whitespace-nowrap px-3 py-4 text-sm text-gray-500 dark:text-gray-400">string (UUID)</td>
|
||||
<td class="px-3 py-4 text-sm text-gray-500 dark:text-gray-400">Unique packet identifier</td>
|
||||
</tr>
|
||||
<tr>
|
||||
|
|
@ -480,7 +496,7 @@ defmodule AprsmeWeb.ApiDocsLive do
|
|||
</td>
|
||||
<td class="whitespace-nowrap px-3 py-4 text-sm text-gray-500 dark:text-gray-400">object</td>
|
||||
<td class="px-3 py-4 text-sm text-gray-500 dark:text-gray-400">
|
||||
Equipment information (manufacturer, type)
|
||||
Equipment information (device_identifier, manufacturer, equipment_type, device_model, device_vendor, device_class, device_contact)
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
|
|
@ -544,6 +560,14 @@ defmodule AprsmeWeb.ApiDocsLive do
|
|||
</td>
|
||||
<td class="px-3 py-4 text-sm text-gray-500 dark:text-gray-400">Request took too long to process</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="whitespace-nowrap px-3 py-4 text-sm font-mono text-orange-600 dark:text-orange-400">
|
||||
429 Too Many Requests
|
||||
</td>
|
||||
<td class="px-3 py-4 text-sm text-gray-500 dark:text-gray-400">
|
||||
Rate limit exceeded (100 requests per minute per IP)
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="whitespace-nowrap px-3 py-4 text-sm font-mono text-red-600 dark:text-red-400">
|
||||
500 Internal Server Error
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
defmodule Aprsme.PacketConsumerPoolTest do
|
||||
use ExUnit.Case, async: true
|
||||
use ExUnit.Case, async: false
|
||||
|
||||
alias Aprsme.PacketConsumerPool
|
||||
|
||||
|
|
@ -13,15 +13,15 @@ defmodule Aprsme.PacketConsumerPoolTest do
|
|||
end
|
||||
|
||||
describe "init/1" do
|
||||
test "returns supervisor flags with one_for_one strategy and 3 children by default" do
|
||||
{:ok, {sup_flags, children}} = PacketConsumerPool.init([])
|
||||
test "returns supervisor flags with one_for_one strategy and 3 children" do
|
||||
{:ok, {sup_flags, children}} = PacketConsumerPool.init(num_consumers: 3)
|
||||
|
||||
assert sup_flags.strategy == :one_for_one
|
||||
assert length(children) == 3
|
||||
end
|
||||
|
||||
test "each child has a unique id of {Aprsme.PacketConsumer, index}" do
|
||||
{:ok, {_sup_flags, children}} = PacketConsumerPool.init([])
|
||||
{:ok, {_sup_flags, children}} = PacketConsumerPool.init(num_consumers: 3)
|
||||
|
||||
ids = Enum.map(children, & &1.id)
|
||||
|
||||
|
|
@ -72,7 +72,7 @@ defmodule Aprsme.PacketConsumerPoolTest do
|
|||
|
||||
Application.delete_env(:aprsme, :packet_pipeline)
|
||||
|
||||
{:ok, {_sup_flags, children}} = PacketConsumerPool.init([])
|
||||
{:ok, {_sup_flags, children}} = PacketConsumerPool.init(num_consumers: 3)
|
||||
|
||||
expected_demand = div(250, 3)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
defmodule Aprsme.PacketPipelineSupervisorTest do
|
||||
use ExUnit.Case, async: true
|
||||
use ExUnit.Case, async: false
|
||||
|
||||
alias Aprsme.PacketPipelineSupervisor
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue