feat: backend servis iskeletini ve yönetim uçlarını ekle

This commit is contained in:
2026-03-21 11:53:04 +03:00
parent df1924b772
commit 62add37d9d
29 changed files with 953 additions and 0 deletions

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1,78 @@
from fastapi import APIRouter, Depends
from pydantic import BaseModel
from sqlalchemy.orm import Session
from app.admin.services import AdminService
from app.db import get_session
from app.llm.ollama_client import OllamaClient
from app.models import MemoryRecord, OllamaStatus, RuntimeSettings, TelegramStatus, UserRecord
router = APIRouter(prefix="/admin", tags=["admin"])
class SecretPayload(BaseModel):
key: str
value: str
def get_admin_service(session: Session = Depends(get_session)) -> AdminService:
return AdminService(session)
@router.get("/dashboard")
def get_dashboard(service: AdminService = Depends(get_admin_service)):
return service.dashboard()
@router.get("/settings", response_model=RuntimeSettings)
def get_settings(service: AdminService = Depends(get_admin_service)):
return service.get_runtime_settings()
@router.put("/settings", response_model=RuntimeSettings)
def put_settings(payload: RuntimeSettings, service: AdminService = Depends(get_admin_service)):
return service.update_runtime_settings(payload)
@router.get("/users", response_model=list[UserRecord])
def get_users(service: AdminService = Depends(get_admin_service)):
return service.list_users()
@router.post("/users", response_model=UserRecord)
def post_user(payload: UserRecord, service: AdminService = Depends(get_admin_service)):
return service.save_user(payload)
@router.get("/memory", response_model=list[MemoryRecord])
def get_memory(service: AdminService = Depends(get_admin_service)):
return service.list_memory()
@router.delete("/memory")
def delete_memory(service: AdminService = Depends(get_admin_service)):
service.clear_memory()
return {"status": "ok"}
@router.get("/secrets/{key}")
def get_secret(key: str, service: AdminService = Depends(get_admin_service)):
return {"key": key, "masked": service.get_secret_mask(key)}
@router.post("/secrets")
def post_secret(payload: SecretPayload, service: AdminService = Depends(get_admin_service)):
service.save_secret(payload.key, payload.value)
return {"status": "ok"}
@router.get("/integrations/ollama", response_model=OllamaStatus)
async def get_ollama_status(service: AdminService = Depends(get_admin_service)):
runtime = service.get_runtime_settings()
client = OllamaClient(runtime.ollama_base_url)
return await client.status(runtime.default_model)
@router.get("/integrations/telegram", response_model=TelegramStatus)
def get_telegram_status(service: AdminService = Depends(get_admin_service)):
return service.telegram_status()

View File

@@ -0,0 +1,142 @@
from datetime import datetime
from sqlalchemy import func, select
from sqlalchemy.orm import Session
from app.db import (
AuditLogORM,
AuthorizedUserORM,
MemoryItemORM,
SecretORM,
SettingORM,
ToolStateORM,
list_recent_logs,
)
from app.config import get_settings
from app.models import DashboardSnapshot, MemoryRecord, RuntimeSettings, TelegramStatus, ToolToggle, UserRecord
class AdminService:
def __init__(self, session: Session) -> None:
self.session = session
def get_runtime_settings(self) -> RuntimeSettings:
settings = {
item.key: item.value for item in self.session.scalars(select(SettingORM))
}
tools = list(self.session.scalars(select(ToolStateORM).order_by(ToolStateORM.name.asc())))
return RuntimeSettings(
terminal_mode=int(settings["terminal_mode"]),
search_provider=settings["search_provider"],
ollama_base_url=settings["ollama_base_url"],
default_model=settings["default_model"],
tools=[ToolToggle(name=tool.name, enabled=tool.enabled) for tool in tools],
)
def update_runtime_settings(self, payload: RuntimeSettings) -> RuntimeSettings:
self._save_setting("terminal_mode", str(payload.terminal_mode))
self._save_setting("search_provider", payload.search_provider)
self._save_setting("ollama_base_url", payload.ollama_base_url)
self._save_setting("default_model", payload.default_model)
for tool in payload.tools:
record = self.session.get(ToolStateORM, tool.name)
if record is None:
self.session.add(ToolStateORM(name=tool.name, enabled=tool.enabled, updated_at=datetime.utcnow()))
else:
record.enabled = tool.enabled
record.updated_at = datetime.utcnow()
self.session.add(AuditLogORM(category="settings", message="settings:runtime-updated"))
self.session.commit()
return self.get_runtime_settings()
def dashboard(self) -> DashboardSnapshot:
return DashboardSnapshot(
settings=self.get_runtime_settings(),
whitelist_count=self.session.scalar(select(func.count()).select_from(AuthorizedUserORM)) or 0,
memory_items=self.session.scalar(select(func.count()).select_from(MemoryItemORM)) or 0,
recent_logs=list_recent_logs(self.session, limit=10),
)
def list_users(self) -> list[UserRecord]:
stmt = select(AuthorizedUserORM).order_by(AuthorizedUserORM.created_at.desc())
return [
UserRecord(
telegram_user_id=user.telegram_user_id,
username=user.username,
display_name=user.display_name,
is_active=user.is_active,
)
for user in self.session.scalars(stmt)
]
def save_user(self, user: UserRecord) -> UserRecord:
record = self.session.get(AuthorizedUserORM, user.telegram_user_id)
if record is None:
record = AuthorizedUserORM(
telegram_user_id=user.telegram_user_id,
username=user.username,
display_name=user.display_name,
is_active=user.is_active,
created_at=datetime.utcnow(),
updated_at=datetime.utcnow(),
)
self.session.add(record)
else:
record.username = user.username
record.display_name = user.display_name
record.is_active = user.is_active
record.updated_at = datetime.utcnow()
self.session.add(AuditLogORM(category="users", message=f"users:upsert:{user.telegram_user_id}"))
self.session.commit()
return user
def list_memory(self) -> list[MemoryRecord]:
stmt = select(MemoryItemORM).order_by(MemoryItemORM.created_at.desc(), MemoryItemORM.id.desc()).limit(50)
return [
MemoryRecord(id=item.id, content=item.content, kind=item.kind, created_at=item.created_at)
for item in self.session.scalars(stmt)
]
def clear_memory(self) -> None:
for item in self.session.scalars(select(MemoryItemORM)):
self.session.delete(item)
self.session.add(AuditLogORM(category="memory", message="memory:cleared"))
self.session.commit()
def get_secret_mask(self, key: str) -> str:
record = self.session.get(SecretORM, key)
value = record.value if record else ""
if len(value) < 4:
return ""
return f"{value[:2]}***{value[-2:]}"
def save_secret(self, key: str, value: str) -> None:
record = self.session.get(SecretORM, key)
if record is None:
self.session.add(SecretORM(key=key, value=value, updated_at=datetime.utcnow()))
else:
record.value = value
record.updated_at = datetime.utcnow()
self.session.add(AuditLogORM(category="secrets", message=f"secrets:updated:{key}"))
self.session.commit()
def _save_setting(self, key: str, value: str) -> None:
record = self.session.get(SettingORM, key)
if record is None:
self.session.add(SettingORM(key=key, value=value, updated_at=datetime.utcnow()))
else:
record.value = value
record.updated_at = datetime.utcnow()
def telegram_status(self) -> TelegramStatus:
settings = get_settings()
configured = bool(settings.telegram_bot_token)
return TelegramStatus(
configured=configured,
polling_active=False,
message="Telegram token is configured. Polling starts when the backend boots."
if configured
else "Telegram token is not configured.",
)