Merge pull request #212 from ipnet-mesh/fix/recent-adverts-timestamps

fix: add public_key filter to advertisements API
This commit is contained in:
JingleManSweep
2026-05-15 19:20:52 +01:00
committed by GitHub
4 changed files with 147 additions and 0 deletions
@@ -0,0 +1,82 @@
# Plan: Fix Recent Advertisements Date Always Showing Today
**Date:** 2025-05-15
**Status:** Draft
**Scope:** API only (advertisements endpoint)
## Problem
The "Recent Advertisements" section on the Node detail page always shows today's date, while the time portion is accurate. The root cause is a missing `public_key` query parameter filter on the API endpoint.
## Root Cause Analysis
### Missing `public_key` filter in advertisements API
**Location:** `src/meshcore_hub/api/routes/advertisements.py:44`
The node detail page fetches advertisements with:
```js
// node-detail.js:86
apiGet('/api/v1/advertisements', { public_key: publicKey, limit: 10 })
```
But the `list_advertisements` endpoint does **not accept a `public_key` query parameter**. FastAPI silently ignores unknown query params, so the `public_key` filter is dropped. The API returns the 10 most recent advertisements across **all nodes** (sorted by `received_at DESC`), not filtered to the specific node.
Since results are the most recent network-wide ads, they cluster around the current time — making it appear that all dates show today.
### Sibling endpoints checked (no issues found)
- **Telemetry**: Already has `node_public_key` filter (`telemetry.py:23`) — the frontend passes `node_public_key` which the API correctly applies
- **Messages**: Not called from the node detail page — no impact
- **Nodes**: Uses path parameter (`/api/v1/nodes/{public_key}`) — no issue
## Plan of Changes
### Step 1: Add `public_key` filter to advertisements API route
**File:** `src/meshcore_hub/api/routes/advertisements.py`
- Add a `public_key: Optional[str] = Query(None, ...)` parameter to `list_advertisements`
- Add a filter clause: `if public_key: query = query.where(Advertisement.public_key == public_key)`
- This filters by the **source** node's public key (the node that sent the advertisement)
### Step 2: Update API tests
**File:** `tests/test_api/test_advertisements.py`
- Add a test for the `public_key` query parameter filtering
- Verify ads returned match the requested public key
- Verify ads with different public keys are excluded
### Step 3: Verify frontend formatting
No frontend changes needed. The `public_key` query param is already sent by `node-detail.js:86`. Once the API honors the filter, the node detail page will show only that node's most recent advertisements (which should span multiple dates, not just today).
## Files Changed
| File | Change |
|------|--------|
| `src/meshcore_hub/api/routes/advertisements.py` | Add `public_key` query parameter filter |
| `tests/test_api/test_advertisements.py` | Test `public_key` filter |
## Testing
```bash
source .venv/bin/activate
pytest tests/test_api/test_advertisements.py -v
pre-commit run --all-files
```
## Verification
After deploying the fix:
1. Navigate to a node detail page with historical advertisement data
2. Verify the "Recent Advertisements" table shows ads spanning multiple dates (not all today)
3. Verify the ad count is limited to 10 and only belongs to the viewed node
## Risk Assessment
- **Low risk:** Adding an optional query parameter is backward-compatible
- **No migration needed:** No schema changes to database tables
- **No frontend changes needed:** The frontend already sends the parameter
@@ -0,0 +1,13 @@
# Tasks: Fix Recent Advertisements Date Always Showing Today
## Implementation
- [ ] Add `public_key: Optional[str] = Query(None)` parameter to `list_advertisements` in `src/meshcore_hub/api/routes/advertisements.py`
- [ ] Add filter clause `if public_key: query = query.where(Advertisement.public_key == public_key)`
- [ ] Add test for `public_key` query parameter filtering in `tests/test_api/test_advertisements.py`
- [ ] Verify test covers: ads match requested key, ads with other keys excluded
## Validation
- [ ] `pytest tests/test_api/test_advertisements.py -v` passes
- [ ] `pre-commit run --all-files` passes
@@ -48,6 +48,9 @@ async def list_advertisements(
search: Optional[str] = Query(
None, description="Search in name tag, node name, or public key"
),
public_key: Optional[str] = Query(
None, description="Filter by source node public key"
),
observed_by: Optional[list[str]] = Query(
None, description="Filter by receiver node public keys"
),
@@ -97,6 +100,9 @@ async def list_advertisements(
)
)
if public_key:
query = query.where(Advertisement.public_key == public_key)
if observed_by:
query = query.where(ObserverNode.public_key.in_(observed_by))
+46
View File
@@ -293,6 +293,52 @@ class TestListAdvertisementsFilters:
data = response.json()
assert len(data["items"]) == 1
def test_filter_by_public_key(self, client_no_auth, api_db_session):
"""Test filtering advertisements by source public_key."""
now = datetime.now(timezone.utc)
ad_a = Advertisement(
public_key="pka" * 11,
name="AdAlpha",
adv_type="CLIENT",
received_at=now,
)
ad_b = Advertisement(
public_key="pkb" * 11,
name="AdBeta",
adv_type="CLIENT",
received_at=now,
)
api_db_session.add_all([ad_a, ad_b])
api_db_session.commit()
response = client_no_auth.get(
f"/api/v1/advertisements?public_key={ad_a.public_key}"
)
assert response.status_code == 200
data = response.json()
assert data["total"] == 1
assert data["items"][0]["public_key"] == ad_a.public_key
def test_filter_by_public_key_no_match(self, client_no_auth, api_db_session):
"""Test filtering by public_key with no matching ads returns empty."""
now = datetime.now(timezone.utc)
ad = Advertisement(
public_key="pkx" * 11,
name="AdX",
adv_type="CLIENT",
received_at=now,
)
api_db_session.add(ad)
api_db_session.commit()
response = client_no_auth.get(
"/api/v1/advertisements?public_key=nonexistent0000000000000000000"
)
assert response.status_code == 200
data = response.json()
assert data["total"] == 0
assert data["items"] == []
class TestAdvertisementSort:
"""Tests for advertisement list sort parameters."""