mirror of
https://github.com/pe1hvh/meshcore-gui.git
synced 2026-05-07 05:44:38 +02:00
8080bcc203
### FIXED - **Multibyte path hash support** (`ble/events.py`, `core/shared_data.py`): corrected docstrings in both `_resolve_path_names` methods that incorrectly described path hashes as "2-char hex strings". The actual contact lookup uses `startswith` matching, which is hash-size agnostic and correctly handles 1-byte (2 hex chars), 2-byte (4 hex chars) and 3-byte (6 hex chars) path hashes as introduced in MeshCore firmware v1.14.0. No functional code was changed — only the documentation was incorrect. - **MariaDB schema** (`meshcore_schema.sql`): `meshcore_messages.path_hashes` column widened from `VARCHAR(128)` to `VARCHAR(255)`. The old limit caused silent truncation for paths longer than ~40 hops in 1-byte mode or ~25 hops in 2-byte mode. Migration is backward-compatible; existing data is unchanged. ### CHANGED - `config.py`: version bump `1.17.0 → 1.17.1`. ### RATIONALE - MeshCore firmware v1.14.0 (2026-03-06) introduced configurable path hash sizes (1-, 2- or 3-byte per repeater). Verification confirmed that `meshcoredecoder 0.3.2` already returns correctly sized hex strings via `_decode_path_len_byte`. The GUI path-resolution logic was already forward-compatible; only the docstrings and the MariaDB column width required correction. ### IMPACT - No BLE handler, GUI panel, service or API endpoint modified. - `meshcoredecoder` library unchanged; no pip update required. - MariaDB migration: single `ALTER TABLE` statement, no downtime, no data loss.
127 lines
4.2 KiB
Python
127 lines
4.2 KiB
Python
"""
|
|
Public REST API route definitions for MeshCore GUI.
|
|
|
|
Registers four read-only GET endpoints under ``/api/v1/`` on the
|
|
NiceGUI/FastAPI application instance:
|
|
|
|
GET /api/v1/stats
|
|
GET /api/v1/nodes
|
|
GET /api/v1/messages
|
|
GET /api/v1/channels
|
|
|
|
Call :func:`register_routes` once from ``__main__.py`` after
|
|
:class:`~meshcore_gui.core.shared_data.SharedData` is constructed and
|
|
before ``ui.run()`` is called.
|
|
|
|
All routes are async and access shared data read-only. CORS is
|
|
handled via response headers on each endpoint to avoid conflicts with
|
|
NiceGUI's frozen middleware stack.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from typing import TYPE_CHECKING, Any, Dict, List
|
|
|
|
from fastapi import Query
|
|
from fastapi.responses import JSONResponse
|
|
from nicegui import app as _nicegui_app
|
|
|
|
import meshcore_gui.config as config
|
|
from meshcore_gui.services.public_api_service import (
|
|
get_channels_payload,
|
|
get_messages_payload,
|
|
get_nodes_payload,
|
|
get_stats_payload,
|
|
)
|
|
|
|
if TYPE_CHECKING:
|
|
from meshcore_gui.core.shared_data import SharedData
|
|
|
|
|
|
def _cors_response(data: Any) -> JSONResponse:
|
|
"""Wrap API data in a JSONResponse with CORS headers.
|
|
|
|
Using response-level CORS headers avoids touching the NiceGUI
|
|
middleware stack (which is frozen by the time routes are registered).
|
|
"""
|
|
origins = ", ".join(config.API_CORS_ORIGINS)
|
|
return JSONResponse(
|
|
content=data,
|
|
headers={
|
|
"Access-Control-Allow-Origin": origins,
|
|
"Access-Control-Allow-Methods": "GET",
|
|
},
|
|
)
|
|
|
|
|
|
def register_routes(shared: "SharedData") -> None:
|
|
"""Wire public API routes into the NiceGUI/FastAPI application.
|
|
|
|
Must be called after :class:`~meshcore_gui.core.shared_data.SharedData`
|
|
is constructed and **before** ``ui.run()`` so that FastAPI registers
|
|
the routes on startup.
|
|
|
|
CORS is handled via response headers on each endpoint rather than
|
|
middleware, which avoids conflicts with NiceGUI's frozen middleware stack.
|
|
|
|
Args:
|
|
shared: Application shared-data instance. Passed to service
|
|
functions as a read-only data source.
|
|
"""
|
|
# ── Routes ──────────────────────────────────────────────────────────
|
|
|
|
@_nicegui_app.get(
|
|
"/api/v1/stats",
|
|
tags=["MeshCore Public API"],
|
|
summary="Network statistics for the last 72 hours",
|
|
response_class=JSONResponse,
|
|
)
|
|
async def api_stats() -> JSONResponse:
|
|
"""Return aggregate statistics for the last 72 hours.
|
|
|
|
Only public (index 0) and hashtag channels are included in message
|
|
counts. Node counts reflect the live contact list.
|
|
"""
|
|
return _cors_response(get_stats_payload(shared))
|
|
|
|
@_nicegui_app.get(
|
|
"/api/v1/nodes",
|
|
tags=["MeshCore Public API"],
|
|
summary="All known mesh nodes",
|
|
response_class=JSONResponse,
|
|
)
|
|
async def api_nodes() -> JSONResponse:
|
|
"""Return all contacts from the live contact list."""
|
|
return _cors_response(get_nodes_payload(shared))
|
|
|
|
@_nicegui_app.get(
|
|
"/api/v1/messages",
|
|
tags=["MeshCore Public API"],
|
|
summary="Paginated public and hashtag channel messages",
|
|
response_class=JSONResponse,
|
|
)
|
|
async def api_messages(
|
|
limit: int = Query(default=100, ge=1, le=500, description="Maximum items to return"),
|
|
offset: int = Query(default=0, ge=0, description="Items to skip"),
|
|
) -> JSONResponse:
|
|
"""Return paginated messages from public and hashtag channels only.
|
|
|
|
Private channel messages are **never** returned.
|
|
"""
|
|
return _cors_response(get_messages_payload(shared, limit=limit, offset=offset))
|
|
|
|
@_nicegui_app.get(
|
|
"/api/v1/channels",
|
|
tags=["MeshCore Public API"],
|
|
summary="Channel list with privacy flag",
|
|
response_class=JSONResponse,
|
|
)
|
|
async def api_channels() -> JSONResponse:
|
|
"""Return all channels discovered from the device."""
|
|
return _cors_response(get_channels_payload(shared))
|
|
|
|
config.debug_print(
|
|
"Public API registered: /api/v1/stats, /api/v1/nodes, "
|
|
"/api/v1/messages, /api/v1/channels"
|
|
)
|