import os from typing import Dict, Any from fastapi import APIRouter, Depends from modules.logger_tool import initialise_logger from modules.auth.supabase_bearer import SupabaseBearer from modules.database.services.provisioning_service import ProvisioningService import modules.database.tools.neo4j_driver_tools as driver_tools import modules.database.schemas.nodes.users as user_nodes import modules.database.tools.neontology_tools as neon logger = initialise_logger(__name__, os.getenv("LOG_LEVEL"), os.getenv("LOG_PATH"), 'default', True) router = APIRouter() def _teacher_db(user_id: str) -> str: return f"cc.users.teacher.{user_id.replace('-', '')}" def _ensure_journal_planner(user_id: str, teacher_db: str) -> None: neon.init_neontology_connection() try: with driver_tools.get_session(database=teacher_db) as session: has_journal = session.run("MATCH (j:Journal) RETURN count(j) AS n").single()["n"] > 0 has_planner = session.run("MATCH (p:Planner) RETURN count(p) AS n").single()["n"] > 0 if not has_journal: journal = user_nodes.JournalNode( uuid_string=f"{user_id}_journal", node_storage_path=f"users/{user_id}/nodes/journal", user_id=user_id, ) neon.create_or_merge_neontology_node(journal, database=teacher_db, operation='merge') logger.info(f"Created Journal node for {user_id}") if not has_planner: planner = user_nodes.PlannerNode( uuid_string=f"{user_id}_planner", node_storage_path=f"users/{user_id}/nodes/planner", user_id=user_id, ) neon.create_or_merge_neontology_node(planner, database=teacher_db, operation='merge') logger.info(f"Created Planner node for {user_id}") finally: neon.close_neontology_connection() @router.post("/init") async def init_user(credentials: dict = Depends(SupabaseBearer())) -> Dict[str, Any]: user_id = credentials.get("sub", "") if not user_id: return {"status": "error", "message": "No user ID in token"} db = _teacher_db(user_id) # Fast path: check if already fully provisioned try: with driver_tools.get_session(database=db) as session: r = session.run( "MATCH (u:User) " "OPTIONAL MATCH (j:Journal) " "OPTIONAL MATCH (p:Planner) " "RETURN count(u) AS u, count(j) AS j, count(p) AS p" ).single() if r and r["u"] > 0 and r["j"] > 0 and r["p"] > 0: logger.debug(f"User {user_id} already initialized — fast path") return {"status": "ok", "initialized": True, "teacher_db": db} except Exception: pass # DB doesn't exist yet — fall through to full provisioning # Full provisioning try: logger.info(f"Provisioning user {user_id}...") service = ProvisioningService() result = service.ensure_user(user_id) user_db = result.get("user_db_name") or db _ensure_journal_planner(user_id, user_db) logger.info(f"User {user_id} provisioned successfully: {user_db}") return {"status": "ok", "initialized": True, "teacher_db": user_db} except Exception as e: logger.error(f"User init failed for {user_id}: {e}") return {"status": "error", "message": str(e)}