Skip to content

Commit ace1d74

Browse files
committed
Adding Adming route
1 parent 6d78041 commit ace1d74

3 files changed

Lines changed: 165 additions & 0 deletions

File tree

hypertrade/daemon.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
from .middleware.rate_limit import RateLimitMiddleware
1919
from .routes.health import router as health_router
2020
from .routes.webhooks import router as webhooks_router, history_router
21+
from .routes.admin import router as admin_router
2122
from .notify import send_telegram_message
2223
from .exception_handlers import register_exception_handlers
2324
from .database import OrderDatabase
@@ -178,6 +179,7 @@ def _telegram_notify(text: str, _token=token, _chat_id=chat_id):
178179
app.include_router(health_router)
179180
app.include_router(webhooks_router)
180181
app.include_router(history_router)
182+
app.include_router(admin_router)
181183

182184
# Log endpoints after routes are registered
183185
log_endpoints(app)

hypertrade/routes/admin.py

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
"""Admin endpoints for managing application settings."""
2+
3+
import logging
4+
import hmac
5+
6+
from fastapi import APIRouter, HTTPException, Request
7+
from pydantic import BaseModel, Field
8+
from typing import Optional
9+
10+
from ..config import get_settings
11+
from ..notify import send_telegram_message
12+
13+
log = logging.getLogger("uvicorn.error")
14+
15+
router = APIRouter(tags=["admin"], prefix="/admin")
16+
17+
18+
class TelegramSettingsUpdate(BaseModel):
19+
"""Request schema for updating Telegram settings."""
20+
21+
enabled: Optional[bool] = Field(None, description="Enable or disable Telegram notifications")
22+
bot_token: Optional[str] = Field(None, description="Telegram bot token (required if enabling)")
23+
chat_id: Optional[str] = Field(None, description="Telegram chat ID (required if enabling)")
24+
25+
26+
class TelegramSettingsResponse(BaseModel):
27+
"""Response schema for Telegram settings."""
28+
29+
status: str = Field(description="Operation status")
30+
telegram_enabled: bool = Field(description="Whether Telegram notifications are enabled")
31+
telegram_bot_token: Optional[str] = Field(None, description="Current bot token (masked)")
32+
telegram_chat_id: Optional[str] = Field(None, description="Current chat ID")
33+
message: Optional[str] = Field(None, description="Operation message")
34+
35+
36+
def _validate_webhook_secret(request: Request) -> None:
37+
"""Validate webhook secret from Authorization header.
38+
39+
Expects: Authorization: Bearer <secret>
40+
"""
41+
settings = request.app.state.settings
42+
env_secret = getattr(settings, "webhook_secret", None)
43+
44+
if not env_secret:
45+
raise HTTPException(status_code=403, detail="Forbidden: webhook secret not configured")
46+
47+
auth_header = request.headers.get("Authorization", "")
48+
if not auth_header.startswith("Bearer "):
49+
raise HTTPException(status_code=401, detail="Unauthorized: missing Bearer token")
50+
51+
provided_secret = auth_header[7:] # Remove "Bearer " prefix
52+
expected_secret = env_secret.get_secret_value()
53+
54+
if not hmac.compare_digest(provided_secret, expected_secret):
55+
raise HTTPException(status_code=401, detail="Unauthorized: invalid secret")
56+
57+
58+
def _mask_secret(secret: str, show_chars: int = 4) -> str:
59+
"""Mask a secret string, showing only the last few characters."""
60+
if not secret or len(secret) <= show_chars:
61+
return "***"
62+
return "*" * (len(secret) - show_chars) + secret[-show_chars:]
63+
64+
65+
@router.post(
66+
"/telegram",
67+
response_model=TelegramSettingsResponse,
68+
summary="Manage Telegram notification settings",
69+
)
70+
async def manage_telegram_settings(
71+
request: Request,
72+
settings_update: TelegramSettingsUpdate,
73+
) -> TelegramSettingsResponse:
74+
"""Update Telegram notification settings.
75+
76+
Requires authentication via webhook secret in Authorization header:
77+
Authorization: Bearer <webhook_secret>
78+
79+
Args:
80+
enabled: Enable/disable Telegram notifications
81+
bot_token: Telegram bot token (required if enabling)
82+
chat_id: Telegram chat ID (required if enabling)
83+
84+
Returns:
85+
Updated Telegram settings status
86+
"""
87+
# Validate secret
88+
_validate_webhook_secret(request)
89+
90+
app_settings = request.app.state.settings
91+
92+
# Handle enable/disable
93+
if settings_update.enabled is not None:
94+
if settings_update.enabled and (not settings_update.bot_token or not settings_update.chat_id):
95+
raise HTTPException(
96+
status_code=400,
97+
detail="bot_token and chat_id are required when enabling Telegram"
98+
)
99+
100+
app_settings.telegram_enabled = settings_update.enabled
101+
102+
if settings_update.enabled:
103+
app_settings.telegram_bot_token = settings_update.bot_token
104+
app_settings.telegram_chat_id = settings_update.chat_id
105+
106+
# Update the telegram_notify function in app state
107+
def _telegram_notify(text: str, _token=settings_update.bot_token, _chat_id=settings_update.chat_id):
108+
return send_telegram_message(_token, _chat_id, text)
109+
110+
request.app.state.telegram_notify = _telegram_notify
111+
log.info("Telegram notifications enabled via admin endpoint")
112+
else:
113+
request.app.state.telegram_notify = None
114+
log.info("Telegram notifications disabled via admin endpoint")
115+
116+
# Handle token/chat_id updates when already enabled
117+
elif app_settings.telegram_enabled:
118+
if settings_update.bot_token:
119+
app_settings.telegram_bot_token = settings_update.bot_token
120+
121+
if settings_update.chat_id:
122+
app_settings.telegram_chat_id = settings_update.chat_id
123+
124+
# Re-create the notify function if either was updated
125+
if settings_update.bot_token or settings_update.chat_id:
126+
token = app_settings.telegram_bot_token
127+
chat_id = app_settings.telegram_chat_id
128+
129+
def _telegram_notify(text: str, _token=token, _chat_id=chat_id):
130+
return send_telegram_message(_token, _chat_id, text)
131+
132+
request.app.state.telegram_notify = _telegram_notify
133+
log.info("Telegram notification settings updated via admin endpoint")
134+
135+
return TelegramSettingsResponse(
136+
status="ok",
137+
telegram_enabled=app_settings.telegram_enabled,
138+
telegram_bot_token=_mask_secret(app_settings.telegram_bot_token) if app_settings.telegram_bot_token else None,
139+
telegram_chat_id=app_settings.telegram_chat_id,
140+
message="Telegram settings updated successfully"
141+
)

hypertrade/schemas/telegram.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
"""Telegram settings management schemas."""
2+
3+
from typing import Optional
4+
from pydantic import BaseModel, Field
5+
6+
7+
class TelegramSettingsUpdate(BaseModel):
8+
"""Request schema for updating Telegram settings."""
9+
10+
enabled: Optional[bool] = Field(None, description="Enable or disable Telegram notifications")
11+
bot_token: Optional[str] = Field(None, description="Telegram bot token (required if enabling)")
12+
chat_id: Optional[str] = Field(None, description="Telegram chat ID (required if enabling)")
13+
14+
15+
class TelegramSettingsResponse(BaseModel):
16+
"""Response schema for Telegram settings."""
17+
18+
status: str = Field(description="Operation status")
19+
telegram_enabled: bool = Field(description="Whether Telegram notifications are enabled")
20+
telegram_bot_token: Optional[str] = Field(None, description="Current bot token (masked)")
21+
telegram_chat_id: Optional[str] = Field(None, description="Current chat ID")
22+
message: Optional[str] = Field(None, description="Operation message")

0 commit comments

Comments
 (0)