mirror of
https://github.com/jkingsman/Remote-Terminal-for-MeshCore.git
synced 2026-07-06 09:52:06 +02:00
Add docs for new non-WS message fetching and remove dead funcs
This commit is contained in:
@@ -228,6 +228,7 @@ All endpoints are prefixed with `/api` (e.g., `/api/health`).
|
|||||||
| POST | `/api/packets/maintenance` | Delete old packets (cleanup) |
|
| POST | `/api/packets/maintenance` | Delete old packets (cleanup) |
|
||||||
| POST | `/api/contacts/{key}/mark-read` | Mark contact conversation as read |
|
| POST | `/api/contacts/{key}/mark-read` | Mark contact conversation as read |
|
||||||
| POST | `/api/channels/{key}/mark-read` | Mark channel as read |
|
| POST | `/api/channels/{key}/mark-read` | Mark channel as read |
|
||||||
|
| GET | `/api/read-state/unreads` | Server-computed unread counts, mentions, last message times |
|
||||||
| POST | `/api/read-state/mark-all-read` | Mark all conversations as read |
|
| POST | `/api/read-state/mark-all-read` | Mark all conversations as read |
|
||||||
| GET | `/api/settings` | Get app settings |
|
| GET | `/api/settings` | Get app settings |
|
||||||
| PATCH | `/api/settings` | Update app settings |
|
| PATCH | `/api/settings` | Update app settings |
|
||||||
@@ -266,7 +267,7 @@ Read state (`last_read_at`) is tracked **server-side** for consistency across de
|
|||||||
- Stored as Unix timestamp in `contacts.last_read_at` and `channels.last_read_at`
|
- Stored as Unix timestamp in `contacts.last_read_at` and `channels.last_read_at`
|
||||||
- Updated via `POST /api/contacts/{key}/mark-read` and `POST /api/channels/{key}/mark-read`
|
- Updated via `POST /api/contacts/{key}/mark-read` and `POST /api/channels/{key}/mark-read`
|
||||||
- Bulk update via `POST /api/read-state/mark-all-read`
|
- Bulk update via `POST /api/read-state/mark-all-read`
|
||||||
- Frontend compares `last_read_at` with message `received_at` to count unreads
|
- Aggregated counts via `GET /api/read-state/unreads` (server-side computation)
|
||||||
|
|
||||||
**State Tracking Keys (Frontend)**: Generated by `getStateKey()` for message times (sidebar sorting):
|
**State Tracking Keys (Frontend)**: Generated by `getStateKey()` for message times (sidebar sorting):
|
||||||
- Channels: `channel-{channel_key}`
|
- Channels: `channel-{channel_key}`
|
||||||
|
|||||||
+6
-1
@@ -35,7 +35,7 @@ app/
|
|||||||
├── channels.py # Channel CRUD, radio sync, mark-read
|
├── channels.py # Channel CRUD, radio sync, mark-read
|
||||||
├── messages.py # Message list and send (direct/channel)
|
├── messages.py # Message list and send (direct/channel)
|
||||||
├── packets.py # Raw packet endpoints, historical decryption
|
├── packets.py # Raw packet endpoints, historical decryption
|
||||||
├── read_state.py # Bulk read state operations (mark-all-read)
|
├── read_state.py # Read state: unread counts, mark-all-read
|
||||||
├── settings.py # App settings (max_radio_contacts)
|
├── settings.py # App settings (max_radio_contacts)
|
||||||
└── ws.py # WebSocket endpoint at /api/ws
|
└── ws.py # WebSocket endpoint at /api/ws
|
||||||
```
|
```
|
||||||
@@ -104,6 +104,10 @@ await ws_manager.broadcast("message", {"id": 1, "text": "Hello"})
|
|||||||
|
|
||||||
Event types: `health`, `contacts`, `channels`, `message`, `contact`, `raw_packet`, `message_acked`, `error`
|
Event types: `health`, `contacts`, `channels`, `message`, `contact`, `raw_packet`, `message_acked`, `error`
|
||||||
|
|
||||||
|
**Note:** The WebSocket initial connect only sends `health`. Contacts and channels are fetched
|
||||||
|
via REST (`GET /api/contacts`, `GET /api/channels`) for faster parallel loading. The WS still
|
||||||
|
broadcasts real-time `contacts`/`channels` updates when data changes.
|
||||||
|
|
||||||
Helper functions for common broadcasts:
|
Helper functions for common broadcasts:
|
||||||
|
|
||||||
```python
|
```python
|
||||||
@@ -490,6 +494,7 @@ All endpoints are prefixed with `/api`.
|
|||||||
- `DELETE /api/channels/{key}` - Delete channel
|
- `DELETE /api/channels/{key}` - Delete channel
|
||||||
|
|
||||||
### Read State
|
### Read State
|
||||||
|
- `GET /api/read-state/unreads?name=X` - Server-computed unread counts, mention flags, and last message times
|
||||||
- `POST /api/read-state/mark-all-read` - Mark all contacts and channels as read
|
- `POST /api/read-state/mark-all-read` - Mark all contacts and channels as read
|
||||||
|
|
||||||
### Messages
|
### Messages
|
||||||
|
|||||||
@@ -497,59 +497,6 @@ class MessageRepository:
|
|||||||
acked=row["acked"],
|
acked=row["acked"],
|
||||||
)
|
)
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
async def get_bulk(
|
|
||||||
conversations: list[dict],
|
|
||||||
limit_per_conversation: int = 100,
|
|
||||||
) -> dict[str, list["Message"]]:
|
|
||||||
"""Fetch messages for multiple conversations in one query per conversation.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
conversations: List of {type: 'PRIV'|'CHAN', conversation_key: string}
|
|
||||||
limit_per_conversation: Max messages to return per conversation
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Dict mapping 'type:conversation_key' to list of messages
|
|
||||||
"""
|
|
||||||
result: dict[str, list[Message]] = {}
|
|
||||||
|
|
||||||
for conv in conversations:
|
|
||||||
msg_type = conv.get("type")
|
|
||||||
conv_key = conv.get("conversation_key")
|
|
||||||
if not msg_type or not conv_key:
|
|
||||||
continue
|
|
||||||
|
|
||||||
key = f"{msg_type}:{conv_key}"
|
|
||||||
|
|
||||||
cursor = await db.conn.execute(
|
|
||||||
"""
|
|
||||||
SELECT * FROM messages
|
|
||||||
WHERE type = ? AND conversation_key LIKE ?
|
|
||||||
ORDER BY received_at DESC
|
|
||||||
LIMIT ?
|
|
||||||
""",
|
|
||||||
(msg_type, f"{conv_key}%", limit_per_conversation),
|
|
||||||
)
|
|
||||||
rows = await cursor.fetchall()
|
|
||||||
result[key] = [
|
|
||||||
Message(
|
|
||||||
id=row["id"],
|
|
||||||
type=row["type"],
|
|
||||||
conversation_key=row["conversation_key"],
|
|
||||||
text=row["text"],
|
|
||||||
sender_timestamp=row["sender_timestamp"],
|
|
||||||
received_at=row["received_at"],
|
|
||||||
paths=MessageRepository._parse_paths(row["paths"]),
|
|
||||||
txt_type=row["txt_type"],
|
|
||||||
signature=row["signature"],
|
|
||||||
outgoing=bool(row["outgoing"]),
|
|
||||||
acked=row["acked"],
|
|
||||||
)
|
|
||||||
for row in rows
|
|
||||||
]
|
|
||||||
|
|
||||||
return result
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
async def get_unread_counts(name: str | None = None) -> dict:
|
async def get_unread_counts(name: str | None = None) -> dict:
|
||||||
"""Get unread message counts, mention flags, and last message times for all conversations.
|
"""Get unread message counts, mention flags, and last message times for all conversations.
|
||||||
|
|||||||
@@ -38,19 +38,6 @@ async def list_messages(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@router.post("/bulk", response_model=dict[str, list[Message]])
|
|
||||||
async def get_messages_bulk(
|
|
||||||
conversations: list[dict],
|
|
||||||
limit_per_conversation: int = Query(default=100, ge=1, le=1000),
|
|
||||||
) -> dict[str, list[Message]]:
|
|
||||||
"""Fetch messages for multiple conversations in one request.
|
|
||||||
|
|
||||||
Body should be a list of {type: 'PRIV'|'CHAN', conversation_key: string}.
|
|
||||||
Returns a dict mapping 'type:conversation_key' to list of messages.
|
|
||||||
"""
|
|
||||||
return await MessageRepository.get_bulk(conversations, limit_per_conversation)
|
|
||||||
|
|
||||||
|
|
||||||
@router.post("/direct", response_model=Message)
|
@router.post("/direct", response_model=Message)
|
||||||
async def send_direct_message(request: SendDirectMessageRequest) -> Message:
|
async def send_direct_message(request: SendDirectMessageRequest) -> Message:
|
||||||
"""Send a direct message to a contact."""
|
"""Send a direct message to a contact."""
|
||||||
|
|||||||
+12
-10
@@ -90,10 +90,13 @@ The `preferences_migrated` flag prevents duplicate migrations.
|
|||||||
|
|
||||||
### State Flow
|
### State Flow
|
||||||
|
|
||||||
1. **WebSocket** pushes real-time updates (health, contacts, channels, messages)
|
1. **REST API** fetches initial data on mount in parallel (config, settings, channels, contacts, unreads)
|
||||||
2. **REST API** fetches initial data and handles user actions
|
2. **WebSocket** pushes real-time updates (health, messages, contact changes, raw packets)
|
||||||
3. **Components** receive state as props, call handlers to trigger changes
|
3. **Components** receive state as props, call handlers to trigger changes
|
||||||
|
|
||||||
|
**Note:** Contacts and channels are loaded via REST on mount (not from WebSocket initial push).
|
||||||
|
The WebSocket only sends health on initial connect, then broadcasts real-time updates.
|
||||||
|
|
||||||
### Conversation Header
|
### Conversation Header
|
||||||
|
|
||||||
For contacts, the header shows path information alongside "Last heard":
|
For contacts, the header shows path information alongside "Last heard":
|
||||||
@@ -229,8 +232,8 @@ interface Message {
|
|||||||
}
|
}
|
||||||
|
|
||||||
interface Conversation {
|
interface Conversation {
|
||||||
type: 'contact' | 'channel' | 'raw' | 'map';
|
type: 'contact' | 'channel' | 'raw' | 'map' | 'visualizer';
|
||||||
id: string; // PublicKey for contacts, ChannelKey for channels, 'raw'/'map' for special views
|
id: string; // PublicKey for contacts, ChannelKey for channels, 'raw'/'map'/'visualizer' for special views
|
||||||
name: string;
|
name: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -397,11 +400,8 @@ for local state tracking, while `conversation_key` is the raw database field.
|
|||||||
Unread tracking uses server-side `last_read_at` timestamps for cross-device consistency:
|
Unread tracking uses server-side `last_read_at` timestamps for cross-device consistency:
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
// Contacts and channels include last_read_at from server
|
// Fetch aggregated unread counts from server (replaces bulk message fetch + client-side counting)
|
||||||
interface Contact {
|
await api.getUnreads(myName); // Returns { counts, mentions, last_message_times }
|
||||||
// ...
|
|
||||||
last_read_at: number | null; // Unix timestamp when conversation was last read
|
|
||||||
}
|
|
||||||
|
|
||||||
// Mark as read via API (called automatically when viewing conversation)
|
// Mark as read via API (called automatically when viewing conversation)
|
||||||
await api.markContactRead(publicKey);
|
await api.markContactRead(publicKey);
|
||||||
@@ -409,7 +409,9 @@ await api.markChannelRead(channelKey);
|
|||||||
await api.markAllRead(); // Bulk mark all as read
|
await api.markAllRead(); // Bulk mark all as read
|
||||||
```
|
```
|
||||||
|
|
||||||
Unread count = messages where `received_at > last_read_at`.
|
The `useUnreadCounts` hook fetches counts from `GET /api/read-state/unreads` on mount and
|
||||||
|
when channels/contacts change. Real-time increments are still tracked client-side via WebSocket
|
||||||
|
message events. The server computes unread counts using `last_read_at` vs `received_at`.
|
||||||
|
|
||||||
## Utility Functions
|
## Utility Functions
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user