mirror of
https://github.com/ipnet-mesh/meshcore-hub.git
synced 2026-08-07 01:13:11 +02:00
Merge pull request #212 from ipnet-mesh/fix/recent-adverts-timestamps
fix: add public_key filter to advertisements API
This commit is contained in:
@@ -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))
|
||||
|
||||
|
||||
@@ -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."""
|
||||
|
||||
Reference in New Issue
Block a user