from fastapi import APIRouter, HTTPException from typing import Dict, Any from modules.database.tools import neo4j_driver_tools as driver_tools from modules.logger_tool import initialise_logger from neo4j.time import DateTime, Date import os from datetime import datetime logger = initialise_logger(__name__, os.getenv("LOG_LEVEL"), os.getenv("LOG_PATH"), 'default', True) router = APIRouter() def convert_neo4j_values(value: Any) -> Any: """Convert Neo4j types to JSON-serializable types.""" if isinstance(value, DateTime): return value.isoformat() # Convert to ISO format string elif isinstance(value, Date): return value.isoformat() # Convert Date to ISO format string elif isinstance(value, dict): return {k: convert_neo4j_values(v) for k, v in value.items()} elif isinstance(value, list): return [convert_neo4j_values(v) for v in value] return value def get_default_node_week(db_name: str) -> Dict[str, Any]: """Get the current week node.""" # Get today's date today = datetime.now() # Find the calendar week node that contains today's date query = """ MATCH (w:CalendarWeek) WHERE date(w.start_date) <= date($today) AND date($today) <= date(w.start_date) + duration('P7D') RETURN w """ with driver_tools.get_session(database=db_name) as session: result = session.run(query, today=today.strftime('%Y-%m-%d')) week_node = result.single() if not week_node: raise HTTPException(status_code=404, detail="No default node found for context: week") node = week_node["w"] node_data = dict(node) converted_data = convert_neo4j_values(node_data) return { "status": "success", "node": { "id": node["uuid_string"], "path": node["path"], "type": "CalendarWeek", "label": node.get("title", "Calendar Week"), "data": converted_data } } def get_default_node_month(db_name: str) -> Dict[str, Any]: """Get the current month node.""" # Get today's date today = datetime.now() # Find the calendar month node for the current month query = """ MATCH (m:CalendarMonth) WHERE m.year = $year AND m.month = $month RETURN m """ with driver_tools.get_session(database=db_name) as session: result = session.run(query, year=str(today.year), month=str(today.month)) month_node = result.single() if not month_node: raise HTTPException(status_code=404, detail="No default node found for context: month") node = month_node["m"] node_data = dict(node) converted_data = convert_neo4j_values(node_data) return { "status": "success", "node": { "id": node["uuid_string"], "path": node["path"], "type": "CalendarMonth", "label": node.get("title", "Calendar Month"), "data": converted_data } } @router.get("/debug-list-nodes") async def debug_list_nodes(db_name: str) -> Dict[str, Any]: """Debug endpoint to list all nodes in a database.""" try: with driver_tools.get_session(database=db_name) as session: query = """ MATCH (n) RETURN labels(n) as labels, n.uuid_string as uuid, n.user_name as name, n.cc_username as username LIMIT 20 """ result = session.run(query) nodes = [] for record in result: nodes.append({ "labels": list(record["labels"]), "uuid": record["uuid"], "name": record["name"], "username": record["username"] }) return { "status": "success", "db_name": db_name, "node_count": len(nodes), "nodes": nodes } except Exception as e: return { "status": "error", "db_name": db_name, "error": str(e) } @router.get("/get-default-node/{context}") async def get_default_node(context: str, db_name: str, base_context: str | None = None) -> Dict[str, Any]: """Get the default node for a given context.""" try: # Handle special cases for week and month if context == 'week': return get_default_node_week(db_name) elif context == 'month': return get_default_node_month(db_name) # Map contexts to their default node queries context_queries = { # Base Contexts 'profile': """ MATCH (n:User) RETURN n LIMIT 1 """, 'worker': """ MATCH (n) WHERE n:SchoolAdmin OR n:Teacher OR n:Student OR n:Developer OR n:SuperAdmin RETURN n LIMIT 1 """, 'calendar': """ MATCH (n:Calendar) RETURN n LIMIT 1 """, 'teaching': """ MATCH (n:Teacher) RETURN n LIMIT 1 """, 'school': """ MATCH (n:School) RETURN n LIMIT 1 """, 'department': """ MATCH (n:Department) RETURN n LIMIT 1 """, 'class': """ MATCH (n:Class) RETURN n LIMIT 1 """, # Extended Contexts - Overview queries for each base context 'overview': """ MATCH (n) WHERE CASE $base_context WHEN 'profile' THEN n:User WHEN 'calendar' THEN n:Calendar WHEN 'teaching' THEN n:Teacher WHEN 'school' THEN n:School WHEN 'department' THEN n:Department WHEN 'class' THEN n:Class ELSE false END RETURN n LIMIT 1 """, # Extended Contexts - User 'settings': """ MATCH (n:User) RETURN n LIMIT 1 """, 'history': """ MATCH (n:User) RETURN n LIMIT 1 """, 'journal': """ MATCH (n:Journal) RETURN n LIMIT 1 """, 'planner': """ MATCH (n:Planner) RETURN n LIMIT 1 """, # Extended Contexts - Calendar 'day': """ MATCH (n:CalendarDay) WHERE date(n.date) = date() RETURN n LIMIT 1 """, 'year': """ MATCH (n:CalendarYear) WHERE n.year = toString(date().year) RETURN n LIMIT 1 """, # Extended Contexts - Teaching 'timetable': """ MATCH (n:UserTeacherTimetable) RETURN n LIMIT 1 """, 'classes': """ MATCH (n:Class) RETURN n LIMIT 1 """, 'lessons': """ MATCH (n:TimetableLesson) RETURN n LIMIT 1 """, # Extended Contexts - School 'departments': """ MATCH (n:Department) RETURN n LIMIT 1 """, 'staff': """ MATCH (n:Teacher) RETURN n LIMIT 1 """, # Extended Contexts - Department 'teachers': """ MATCH (n:Teacher) RETURN n LIMIT 1 """, 'subjects': """ MATCH (n:Subject) RETURN n LIMIT 1 """, # Extended Contexts - Class 'students': """ MATCH (n:Student) RETURN n LIMIT 1 """ } if context not in context_queries: raise HTTPException(status_code=400, detail=f"Invalid context: {context}") query = context_queries[context] with driver_tools.get_session(database=db_name) as session: # For overview context, we need to pass the database name as a parameter params = {'db_name': db_name, 'base_context': base_context} if context == 'overview' else {} result = session.run(query, params) record = result.single() if not record: raise HTTPException( status_code=404, detail=f"No default node found for context: {context}" ) node = record["n"] node_data = dict(node) # Convert Neo4j types to JSON-serializable types converted_data = convert_neo4j_values(node_data) return { "status": "success", "node": { "id": node["uuid_string"], "path": node["path"], "node_storage_path": node.get("node_storage_path", node["path"]), "type": list(node.labels)[0], "label": node.get("title", ""), "data": converted_data } } except Exception as e: logger.error(f"Error getting default node: {str(e)}") raise HTTPException(status_code=500, detail=str(e))