"""Database initialization: create all tables and seed defaults.""" from __future__ import annotations import asyncio import json from pathlib import Path from sqlalchemy import select, text from app.db import AsyncSessionLocal, Base, engine from app.models import GlossaryEntry, Preset, Setting, User from app.config import settings from app.logging_setup import get_logger, setup_logging from app.core.security import hash_password from app.prompts.fantasy_preset import FANTASY_PRESET_RU, FANTASY_PRESET_EN log = get_logger("migrations") DEFAULT_SETTINGS = [ ("llm.base_url", settings.default_llm_base_url, "OpenAI-compatible base URL"), ("llm.api_key", settings.default_llm_api_key, "API key for LLM endpoint"), ("llm.model", settings.default_llm_model, "Default model name"), ("llm.temperature", 0.7, "Temperature for orchestrator"), ("llm.step_temperature", 0.85, "Temperature for narrative step writer"), ("llm.summary_temperature", 0.3, "Temperature for summarizer"), ("llm.max_tokens", 1024, "Max tokens per LLM response"), ("llm.request_timeout", 120, "LLM request timeout, seconds"), ("llm.streaming", True, "Whether to use streaming responses"), ("context.recent_messages", settings.default_recent_messages, "Guaranteed recent messages in prompt"), ("context.compress_threshold", settings.default_compress_threshold, "Trigger compression at this count"), ("context.summary_messages", settings.default_summary_messages, "Number of messages per summary block"), ("context.max_tokens_total", 6000, "Soft token budget for context window (small models)"), ("triggers.enabled", True, "Enable deferred trigger processing"), ("triggers.check_interval", 30, "Trigger checker interval, seconds"), # Embeddings / RAG ("embedding.provider", settings.default_embedding_provider, "Embeddings provider: 'hash' (offline fallback) or 'openai' (real semantic embeddings)"), ("embedding.base_url", settings.default_embedding_base_url, "OpenAI-compatible embeddings base URL. Empty = reuse llm.base_url"), ("embedding.api_key", settings.default_embedding_api_key, "API key for embeddings endpoint. Empty = reuse llm.api_key"), ("embedding.model", settings.default_embedding_model, "Embedding model name (e.g. text-embedding-3-small, bge-m3, nomic-embed-text)"), ("embedding.dim", settings.default_embedding_dim, "Vector dimension. 0 = auto-probe from endpoint on first use"), ("embedding.request_timeout", settings.default_embedding_request_timeout, "Embeddings request timeout, seconds"), ] async def init_db() -> None: setup_logging() log.info("creating_tables") async with engine.begin() as conn: await conn.run_sync(Base.metadata.create_all) log.info("tables_ready") async with AsyncSessionLocal() as session: # Seed settings result = await session.execute(select(Setting).limit(1)) if result.scalars().first() is None: for key, value, desc in DEFAULT_SETTINGS: session.add(Setting(key=key, value=value, description=desc)) await session.commit() log.info("settings_seeded", count=len(DEFAULT_SETTINGS)) else: log.info("settings_already_exist") # Seed built-in Fantasy preset result = await session.execute(select(Preset).where(Preset.is_builtin.is_(True))) if result.scalars().first() is None: for preset_def in (FANTASY_PRESET_RU, FANTASY_PRESET_EN): session.add(Preset( slug=preset_def["slug"], title=preset_def["title"], description=preset_def["description"], language=preset_def["language"], is_public=True, is_builtin=True, payload=preset_def["payload"], )) await session.commit() log.info("builtin_presets_seeded") else: log.info("builtin_presets_already_exist") # Ensure admin_setup_token is set; if empty, generate and print token = settings.admin_setup_token.strip() if not token: import secrets as _s token = _s.token_urlsafe(24) async with AsyncSessionLocal() as session: existing = await session.execute(select(Setting).where(Setting.key == "admin.setup_token")) existing_obj = existing.scalars().first() if existing_obj is None: session.add(Setting(key="admin.setup_token", value=token, description="One-time token for /admin/setup")) await session.commit() print("=" * 60) print("ADMIN SETUP TOKEN (use at /admin/setup):") print(token) print("=" * 60) log.info("admin_setup_token_generated") else: log.info("admin_setup_token_already_set") if __name__ == "__main__": asyncio.run(init_db())