78 lines
2.6 KiB
Python
78 lines
2.6 KiB
Python
from __future__ import annotations
|
|
|
|
from typing import Dict, Any
|
|
|
|
from sanic import response
|
|
from sqlalchemy import select
|
|
|
|
from app.core.logger import make_log
|
|
from app.core.models import NodeEvent, KnownNode
|
|
from app.core.network.nodesig import verify_request
|
|
from app.core.network.guard import check_rate_limit
|
|
from app.core._config import PROJECT_HOST
|
|
from app.core.events.service import LOCAL_PUBLIC_KEY
|
|
|
|
|
|
def _origin_host() -> str | None:
|
|
return PROJECT_HOST.rstrip('/') if PROJECT_HOST else None
|
|
|
|
|
|
async def s_api_v1_network_events(request):
|
|
remote_ip = (request.headers.get('X-Forwarded-For') or request.remote_addr or request.ip or '').split(',')[0].strip()
|
|
if not check_rate_limit(request.app.ctx.memory, remote_ip):
|
|
return response.json({"error": "RATE_LIMIT"}, status=429)
|
|
|
|
ok, node_id, reason = verify_request(request, request.app.ctx.memory)
|
|
if not ok:
|
|
return response.json({"error": reason or "UNAUTHORIZED"}, status=401)
|
|
|
|
session = request.ctx.db_session
|
|
trusted = (await session.execute(
|
|
select(KnownNode).where(KnownNode.public_key == node_id)
|
|
)).scalar_one_or_none()
|
|
role = (trusted.meta or {}).get('role') if trusted and trusted.meta else None
|
|
if role != 'trusted':
|
|
make_log("Events", f"Rejected events fetch from non-trusted node {node_id}", level="warning")
|
|
return response.json({"error": "FORBIDDEN"}, status=403)
|
|
|
|
try:
|
|
since = int(request.args.get('since') or 0)
|
|
except (TypeError, ValueError):
|
|
since = 0
|
|
since = max(since, 0)
|
|
|
|
try:
|
|
limit = int(request.args.get('limit') or 100)
|
|
except (TypeError, ValueError):
|
|
limit = 100
|
|
limit = max(1, min(limit, 200))
|
|
|
|
result = await session.execute(
|
|
select(NodeEvent)
|
|
.where(NodeEvent.origin_public_key == LOCAL_PUBLIC_KEY, NodeEvent.seq > since)
|
|
.order_by(NodeEvent.seq.asc())
|
|
.limit(limit)
|
|
)
|
|
rows = result.scalars().all()
|
|
|
|
events: list[Dict[str, Any]] = []
|
|
next_since = since
|
|
for row in rows:
|
|
next_since = max(next_since, int(row.seq))
|
|
events.append({
|
|
"origin_public_key": row.origin_public_key,
|
|
"origin_host": row.origin_host or _origin_host(),
|
|
"seq": int(row.seq),
|
|
"uid": row.uid,
|
|
"event_type": row.event_type,
|
|
"payload": row.payload,
|
|
"signature": row.signature,
|
|
"created_at": (row.created_at.isoformat() + 'Z') if row.created_at else None,
|
|
})
|
|
|
|
payload = {
|
|
"events": events,
|
|
"next_since": next_since,
|
|
}
|
|
return response.json(payload)
|