Files
ai-rpg/backend/app/api/misc.py
Mikan 32575e217e fix
2026-06-19 19:14:27 +03:00

91 lines
3.4 KiB
Python

"""Glossary + Triggers + public UI settings routes."""
from __future__ import annotations
from typing import Any, Dict, List
from uuid import UUID
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
from fastapi import APIRouter, Depends, HTTPException
from app.db import get_db_dep
from app.deps import get_current_user
from app.models import DeferredTrigger, GlossaryEntry, Session, Setting, User, World
from app.schemas import GlossaryEntryOut, TriggerOut
router = APIRouter(prefix="/api", tags=["misc"])
# Public, unauthenticated UI settings (logo URL etc.) — used by the frontend
# on the login/register/home pages BEFORE the user is authenticated, so the
# branding (logo, eventually theme) shows up everywhere.
#
# Only a curated subset of settings is exposed here. Anything sensitive (api
# keys, internal URLs, admin tokens) MUST stay behind /api/admin/settings.
PUBLIC_SETTING_KEYS = ("ui.logo_url",)
_PUBLIC_DEFAULTS: Dict[str, Any] = {"ui.logo_url": "/logo.png"}
@router.get("/settings/public")
async def get_public_settings(db: AsyncSession = Depends(get_db_dep)):
"""Return UI settings that are safe to expose without authentication.
Used by the frontend to render the logo (and other public branding) on
every page, including login/register. The response shape is a flat
`{key: value}` dict.
"""
out: Dict[str, Any] = dict(_PUBLIC_DEFAULTS)
rows = await db.execute(select(Setting).where(Setting.key.in_(PUBLIC_SETTING_KEYS)))
for row in rows.scalars().all():
out[row.key] = row.value
return out
@router.get("/worlds/{world_id}/glossary", response_model=List[GlossaryEntryOut])
async def list_glossary(
world_id: UUID,
kind: str | None = None,
db: AsyncSession = Depends(get_db_dep),
user: User = Depends(get_current_user),
):
w_result = await db.execute(select(World).where(World.id == world_id))
world = w_result.scalars().first()
if not world:
raise HTTPException(status_code=404, detail="world_not_found")
if world.owner_id != user.id and not user.is_admin:
raise HTTPException(status_code=403, detail="forbidden")
q = select(GlossaryEntry).where(GlossaryEntry.world_id == world_id)
if kind:
q = q.where(GlossaryEntry.kind == kind)
q = q.order_by(GlossaryEntry.created_at.desc())
result = await db.execute(q)
return result.scalars().all()
@router.get("/sessions/{session_id}/triggers", response_model=List[TriggerOut])
async def list_triggers(
session_id: UUID,
include_fired: bool = True,
db: AsyncSession = Depends(get_db_dep),
user: User = Depends(get_current_user),
):
s_result = await db.execute(
select(Session).join(World, Session.world_id == World.id).where(Session.id == session_id)
)
session = s_result.scalars().first()
if not session:
raise HTTPException(status_code=404, detail="session_not_found")
w_result = await db.execute(select(World).where(World.id == session.world_id))
world = w_result.scalars().first()
if not world or (world.owner_id != user.id and not user.is_admin):
raise HTTPException(status_code=403, detail="forbidden")
q = select(DeferredTrigger).where(DeferredTrigger.session_id == session_id)
if not include_fired:
q = q.where(DeferredTrigger.fired.is_(False))
q = q.order_by(DeferredTrigger.fire_at)
result = await db.execute(q)
return result.scalars().all()