initial
This commit is contained in:
103
backend/app/migrations/init_db.py
Normal file
103
backend/app/migrations/init_db.py
Normal file
@@ -0,0 +1,103 @@
|
||||
"""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())
|
||||
Reference in New Issue
Block a user