220 lines
9.2 KiB
Python
220 lines
9.2 KiB
Python
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()]
|