Files
notify-bridge/packages/server/src/notify_bridge_server/api/command_configs.py
T
alexei.dolgolyov 1d445f3980 feat: entity relationship refactor — notification trackers, command system, chat actions
Rework entity schema: rename Tracker→NotificationTracker, add CommandConfig/
CommandTracker/CommandTrackerListener entities for decoupled command handling.
Commands now resolve through CommandTracker→CommandConfig instead of
TelegramBot.commands_config. Smart ref-counted bot polling based on active
listeners. Add chat_action to telegram targets. Full frontend CRUD pages
for command configs and command trackers. Idempotent SQLite migrations.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-21 01:27:20 +03:00

152 lines
4.5 KiB
Python

"""Command config management API routes."""
import logging
from typing import Any
from fastapi import APIRouter, Depends, HTTPException, status
from pydantic import BaseModel
from sqlmodel import select
from sqlmodel.ext.asyncio.session import AsyncSession
from ..auth.dependencies import get_current_user
from ..database.engine import get_session
from ..database.models import CommandConfig, CommandTracker, User
_LOGGER = logging.getLogger(__name__)
router = APIRouter(prefix="/api/command-configs", tags=["command-configs"])
class CommandConfigCreate(BaseModel):
provider_type: str
name: str
icon: str = ""
enabled_commands: list[str] = []
locale: str = "en"
response_mode: str = "media"
default_count: int = 5
rate_limits: dict[str, Any] = {}
class CommandConfigUpdate(BaseModel):
name: str | None = None
icon: str | None = None
enabled_commands: list[str] | None = None
locale: str | None = None
response_mode: str | None = None
default_count: int | None = None
rate_limits: dict[str, Any] | None = None
@router.get("")
async def list_command_configs(
user: User = Depends(get_current_user),
session: AsyncSession = Depends(get_session),
):
"""List all command configs for the current user."""
result = await session.exec(
select(CommandConfig).where(CommandConfig.user_id == user.id)
)
return [_config_response(c) for c in result.all()]
@router.post("", status_code=status.HTTP_201_CREATED)
async def create_command_config(
body: CommandConfigCreate,
user: User = Depends(get_current_user),
session: AsyncSession = Depends(get_session),
):
"""Create a new command config."""
# Validate provider_type
valid_types = ("immich",)
if body.provider_type not in valid_types:
raise HTTPException(
status_code=400,
detail=f"Invalid provider_type. Must be one of: {', '.join(valid_types)}",
)
config = CommandConfig(user_id=user.id, **body.model_dump())
session.add(config)
await session.commit()
await session.refresh(config)
return _config_response(config)
@router.get("/{config_id}")
async def get_command_config(
config_id: int,
user: User = Depends(get_current_user),
session: AsyncSession = Depends(get_session),
):
"""Get a single command config."""
config = await _get_user_config(session, config_id, user.id)
return _config_response(config)
@router.put("/{config_id}")
async def update_command_config(
config_id: int,
body: CommandConfigUpdate,
user: User = Depends(get_current_user),
session: AsyncSession = Depends(get_session),
):
"""Update a command config."""
config = await _get_user_config(session, config_id, user.id)
for field, value in body.model_dump(exclude_unset=True).items():
setattr(config, field, value)
session.add(config)
await session.commit()
await session.refresh(config)
return _config_response(config)
@router.delete("/{config_id}", status_code=status.HTTP_204_NO_CONTENT)
async def delete_command_config(
config_id: int,
user: User = Depends(get_current_user),
session: AsyncSession = Depends(get_session),
):
"""Delete a command config. Fails if in use by any command tracker."""
config = await _get_user_config(session, config_id, user.id)
# Check if any command tracker references this config
result = await session.exec(
select(CommandTracker).where(CommandTracker.command_config_id == config_id)
)
if result.first():
raise HTTPException(
status_code=status.HTTP_409_CONFLICT,
detail="Cannot delete: command config is in use by a command tracker",
)
await session.delete(config)
await session.commit()
# --- Helpers ---
def _config_response(c: CommandConfig) -> dict:
return {
"id": c.id,
"user_id": c.user_id,
"provider_type": c.provider_type,
"name": c.name,
"icon": c.icon,
"enabled_commands": c.enabled_commands or [],
"locale": c.locale,
"response_mode": c.response_mode,
"default_count": c.default_count,
"rate_limits": c.rate_limits or {},
"created_at": c.created_at.isoformat(),
}
async def _get_user_config(
session: AsyncSession, config_id: int, user_id: int
) -> CommandConfig:
config = await session.get(CommandConfig, config_id)
if not config or config.user_id != user_id:
raise HTTPException(status_code=404, detail="Command config not found")
return config