mirror of
https://github.com/bytedance/deer-flow.git
synced 2026-05-21 15:36:48 +00:00
refactor(auth): remove SQL orphan migration (unused in supported scenarios)
The _migrate_orphan_sql_tables helper existed to bind NULL owner_id
rows in threads_meta, runs, run_events, and feedback to the admin on
first boot. But in every supported upgrade path, it's a no-op:
1. Fresh install: create_all builds fresh tables, no legacy rows
2. No-auth → with-auth (no existing persistence DB): persistence
tables are created fresh by create_all, no legacy rows
3. No-auth → with-auth (has existing persistence DB from #1930):
NOT a supported upgrade path — "有 DB 到有 DB" schema evolution
is out of scope; users wipe DB or run manual ALTER
So the SQL orphan migration never has anything to do in the
supported matrix. Delete the function, simplify _ensure_admin_user
from a 3-step pipeline to a 2-step one (admin creation + LangGraph
store orphan migration only).
LangGraph store orphan migration stays: it serves the real
"no-auth → with-auth" upgrade path where a user's existing LangGraph
thread metadata has no owner_id field and needs to be stamped with
the newly-created admin's id.
Tests: 284 passed (auth + persistence + isolation)
Lint: clean
This commit is contained in:
+13
-54
@@ -43,19 +43,21 @@ logger = logging.getLogger(__name__)
|
|||||||
async def _ensure_admin_user(app: FastAPI) -> None:
|
async def _ensure_admin_user(app: FastAPI) -> None:
|
||||||
"""Auto-create the admin user on first boot if no users exist.
|
"""Auto-create the admin user on first boot if no users exist.
|
||||||
|
|
||||||
After admin creation (or on every boot), run a three-step orphan
|
After admin creation, migrate orphan threads from the LangGraph
|
||||||
migration pipeline:
|
store (metadata.owner_id unset) to the admin account. This is the
|
||||||
|
"no-auth → with-auth" upgrade path: users who ran DeerFlow without
|
||||||
|
authentication have existing LangGraph thread data that needs an
|
||||||
|
owner assigned.
|
||||||
|
|
||||||
1. Fatal: admin creation (can't proceed without an admin user)
|
No SQL persistence migration is needed: the four owner_id columns
|
||||||
2. Non-fatal: LangGraph store orphan threads (cursor-paginated)
|
(threads_meta, runs, run_events, feedback) only come into existence
|
||||||
3. Non-fatal: SQL persistence tables (threads_meta, runs, run_events,
|
alongside the auth module via create_all, so freshly created tables
|
||||||
feedback) — every row with owner_id IS NULL gets bound to admin
|
never contain NULL-owner rows. "Existing persistence DB + new auth"
|
||||||
|
is not a supported upgrade path — fresh install or wipe-and-retry.
|
||||||
|
|
||||||
Multi-worker safe: relies on SQLite UNIQUE constraint to resolve
|
Multi-worker safe: relies on SQLite UNIQUE constraint to resolve
|
||||||
races during admin creation. Only the worker that successfully
|
races during admin creation. Only the worker that successfully
|
||||||
creates/updates the admin prints the password; losers silently skip.
|
creates/updates the admin prints the password; losers silently skip.
|
||||||
The orphan migration steps are idempotent — a second call finds
|
|
||||||
nothing to migrate and returns 0.
|
|
||||||
"""
|
"""
|
||||||
import secrets
|
import secrets
|
||||||
|
|
||||||
@@ -104,7 +106,9 @@ async def _ensure_admin_user(app: FastAPI) -> None:
|
|||||||
|
|
||||||
admin_id = str(admin.id)
|
admin_id = str(admin.id)
|
||||||
|
|
||||||
# Step 2: LangGraph store orphan migration — non-fatal
|
# LangGraph store orphan migration — non-fatal.
|
||||||
|
# This covers the "no-auth → with-auth" upgrade path for users
|
||||||
|
# whose existing LangGraph thread metadata has no owner_id set.
|
||||||
store = getattr(app.state, "store", None)
|
store = getattr(app.state, "store", None)
|
||||||
if store is not None:
|
if store is not None:
|
||||||
try:
|
try:
|
||||||
@@ -114,12 +118,6 @@ async def _ensure_admin_user(app: FastAPI) -> None:
|
|||||||
except Exception:
|
except Exception:
|
||||||
logger.exception("LangGraph thread migration failed (non-fatal)")
|
logger.exception("LangGraph thread migration failed (non-fatal)")
|
||||||
|
|
||||||
# Step 3: SQL persistence tables — non-fatal
|
|
||||||
try:
|
|
||||||
await _migrate_orphan_sql_tables(admin_id)
|
|
||||||
except Exception:
|
|
||||||
logger.exception("SQL persistence migration failed (non-fatal)")
|
|
||||||
|
|
||||||
if fresh_admin_created:
|
if fresh_admin_created:
|
||||||
logger.info("=" * 60)
|
logger.info("=" * 60)
|
||||||
logger.info(" Admin account created on first boot")
|
logger.info(" Admin account created on first boot")
|
||||||
@@ -166,45 +164,6 @@ async def _migrate_orphaned_threads(store, admin_user_id: str) -> int:
|
|||||||
return migrated
|
return migrated
|
||||||
|
|
||||||
|
|
||||||
async def _migrate_orphan_sql_tables(admin_user_id: str) -> None:
|
|
||||||
"""Bind NULL owner_id rows in the 4 SQL persistence tables to admin.
|
|
||||||
|
|
||||||
Runs in a single transaction per table via the shared async session
|
|
||||||
factory. Each UPDATE is idempotent — a second call finds nothing to
|
|
||||||
migrate (rowcount=0).
|
|
||||||
"""
|
|
||||||
from sqlalchemy import update
|
|
||||||
|
|
||||||
from deerflow.persistence.engine import get_session_factory
|
|
||||||
from deerflow.persistence.feedback.model import FeedbackRow
|
|
||||||
from deerflow.persistence.models.run_event import RunEventRow
|
|
||||||
from deerflow.persistence.run.model import RunRow
|
|
||||||
from deerflow.persistence.thread_meta.model import ThreadMetaRow
|
|
||||||
|
|
||||||
sf = get_session_factory()
|
|
||||||
if sf is None:
|
|
||||||
# In-memory / no persistence backend — nothing to migrate.
|
|
||||||
return
|
|
||||||
|
|
||||||
tables = [
|
|
||||||
(ThreadMetaRow, "threads_meta"),
|
|
||||||
(RunRow, "runs"),
|
|
||||||
(RunEventRow, "run_events"),
|
|
||||||
(FeedbackRow, "feedback"),
|
|
||||||
]
|
|
||||||
|
|
||||||
async with sf() as session:
|
|
||||||
for model, label in tables:
|
|
||||||
stmt = update(model).where(model.owner_id.is_(None)).values(owner_id=admin_user_id)
|
|
||||||
result = await session.execute(stmt)
|
|
||||||
count = result.rowcount or 0
|
|
||||||
if count > 0:
|
|
||||||
logger.info("Migrated %d orphan %s row(s) to admin", count, label)
|
|
||||||
else:
|
|
||||||
logger.debug("No orphan %s rows to migrate", label)
|
|
||||||
await session.commit()
|
|
||||||
|
|
||||||
|
|
||||||
@asynccontextmanager
|
@asynccontextmanager
|
||||||
async def lifespan(app: FastAPI) -> AsyncGenerator[None, None]:
|
async def lifespan(app: FastAPI) -> AsyncGenerator[None, None]:
|
||||||
"""Application lifespan handler."""
|
"""Application lifespan handler."""
|
||||||
|
|||||||
Reference in New Issue
Block a user