from datetime import datetime from sqlalchemy import func, select from sqlalchemy.orm import Session from app.db import ( AuditLogORM, AutomationORM, AuthorizedUserORM, DEFAULT_TOOLS, MemoryItemORM, SecretORM, SettingORM, TelegramUserProfileORM, ToolStateORM, list_recent_logs, ) from app.config import get_settings from app.models import AutomationRecord, DashboardSnapshot, MemoryRecord, RuntimeSettings, TelegramStatus, ToolToggle, UserProfileRecord, 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)) } tool_records = { tool.name: tool.enabled for tool in self.session.scalars(select(ToolStateORM).order_by(ToolStateORM.name.asc())) } return RuntimeSettings( terminal_mode=int(settings["terminal_mode"]), search_provider=settings["search_provider"], model_provider=settings["model_provider"], local_base_url=settings["local_base_url"], local_model=settings["local_model"], zai_model=settings["zai_model"], anythingllm_base_url=settings["anythingllm_base_url"], anythingllm_workspace_slug=settings["anythingllm_workspace_slug"], tools=[ToolToggle(name=name, enabled=tool_records.get(name, enabled)) for name, enabled in DEFAULT_TOOLS.items()], ) 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("model_provider", payload.model_provider) self._save_setting("local_base_url", payload.local_base_url) self._save_setting("local_model", payload.local_model) self._save_setting("zai_model", payload.zai_model) self._save_setting("anythingllm_base_url", payload.anythingllm_base_url) self._save_setting("anythingllm_workspace_slug", payload.anythingllm_workspace_slug) 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_user_profiles(self) -> list[UserProfileRecord]: stmt = select(TelegramUserProfileORM).order_by(TelegramUserProfileORM.updated_at.desc()) profiles: list[UserProfileRecord] = [] for item in self.session.scalars(stmt): profiles.append( UserProfileRecord( telegram_user_id=item.telegram_user_id, display_name=item.display_name, bio=item.bio, occupation=item.occupation, primary_use_cases=self._decode_list(item.primary_use_cases), answer_priorities=self._decode_list(item.answer_priorities), tone_preference=item.tone_preference, response_length=item.response_length, language_preference=item.language_preference, workflow_preference=item.workflow_preference, interests=self._decode_list(item.interests), approval_preferences=self._decode_list(item.approval_preferences), avoid_preferences=item.avoid_preferences, onboarding_completed=item.onboarding_completed, last_onboarding_step=item.last_onboarding_step, ) ) return profiles def list_automations(self) -> list[AutomationRecord]: stmt = select(AutomationORM).order_by(AutomationORM.created_at.desc(), AutomationORM.id.desc()) records: list[AutomationRecord] = [] for item in self.session.scalars(stmt): records.append( AutomationRecord( id=item.id, telegram_user_id=item.telegram_user_id, name=item.name, prompt=item.prompt, schedule_type=item.schedule_type, # type: ignore[arg-type] interval_hours=item.interval_hours, time_of_day=item.time_of_day, days_of_week=self._decode_list(item.days_of_week), status=item.status, # type: ignore[arg-type] last_run_at=item.last_run_at, next_run_at=item.next_run_at, last_result=item.last_result, created_at=item.created_at, updated_at=item.updated_at, ) ) return records 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) if record is not None: value = record.value else: settings = get_settings() value = str(getattr(settings, key, "") or "") 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.", ) def _decode_list(self, value: str) -> list[str]: import json try: payload = json.loads(value) except json.JSONDecodeError: return [] if not isinstance(payload, list): return [] return [str(item).strip() for item in payload if str(item).strip()]