from dotenv import load_dotenv, find_dotenv load_dotenv(find_dotenv()) import os import modules.logger_tool as logger log_name = 'api_routers_database_tools_tldraw_filesystem' log_dir = os.getenv("LOG_PATH", "/logs") # Default path as fallback logging = logger.get_logger( name=log_name, log_level=os.getenv("LOG_LEVEL", "DEBUG"), log_path=log_dir, log_file=log_name, runtime=True, log_format='default' ) from fastapi import APIRouter, HTTPException, Query from typing import Dict import json from modules.database.tools.filesystem_tools import ClassroomCopilotFilesystem from modules.database.schemas.nodes.users import UserNode from modules.database.tools.neo4j_db_formatter import format_user_email_for_neo_db router = APIRouter() @router.post("/get_tldraw_user_node_file") async def read_tldraw_user_node_file(user_node: UserNode): logging.debug(f"Reading tldraw file for user node: {user_node.user_email}") # Format the database name using the email formatted_email = format_user_email_for_neo_db(user_node.user_email) db_name = f"cc.users.{formatted_email}" fs = ClassroomCopilotFilesystem(db_name=db_name, init_run_type="user") logging.debug(f"Filesystem root path: {fs.root_path}") # Use the path directly as provided - it represents the structure from root if not user_node.node_storage_path: raise HTTPException(status_code=400, detail="Node path not found") # The path might already contain parts of the filesystem structure # We need to construct the full path carefully if user_node.node_storage_path.startswith("users/"): # If path starts with users/, remove it since filesystem already has users/ structure base_path = user_node.node_storage_path[6:] # Remove "users/" prefix logging.debug(f"Removed 'users/' prefix, base_path is now: {base_path}") else: base_path = user_node.node_storage_path logging.debug(f"No 'users/' prefix found, using path as-is: {base_path}") base_path = os.path.normpath(base_path) logging.debug(f"Using base path: {base_path}") # Construct final path including tldraw file file_path = os.path.join(base_path, "tldraw_file.json") logging.debug(f"File path: {file_path}") file_location = os.path.normpath(os.path.join(fs.root_path, file_path)) logging.debug(f"File location: {file_location}") logging.debug(f"Attempting to read file at: {file_location}") if os.path.exists(file_location): logging.debug(f"File exists: {file_location}") try: with open(file_location, "r") as file: data = json.load(file) return data except json.JSONDecodeError as e: logging.error(f"Failed to parse JSON from file: {e}") raise HTTPException(status_code=500, detail="Invalid JSON in file") except Exception as e: logging.error(f"Error reading file: {e}") raise HTTPException(status_code=500, detail="Error reading file") else: # Check if directory exists directory_location = os.path.dirname(file_location) if os.path.exists(directory_location): logging.debug(f"Directory exists but file doesn't, creating default tldraw file at: {file_location}") try: # Create default tldraw content default_tldraw_content = create_default_tldraw_content() # Ensure directory exists (should already exist, but just in case) os.makedirs(directory_location, exist_ok=True) # Write the default file with open(file_location, "w") as file: json.dump(default_tldraw_content, file, indent=4) logging.info(f"Default tldraw file created at: {file_location}") return default_tldraw_content except Exception as e: logging.error(f"Error creating default tldraw file: {e}") raise HTTPException(status_code=500, detail="Error creating default tldraw file") else: logging.debug(f"Neither directory nor file exists: {directory_location}") raise HTTPException(status_code=404, detail="Directory not found") @router.post("/set_tldraw_user_node_file") async def set_tldraw_user_node_file(user_node: UserNode, data: Dict): logging.debug(f"Setting tldraw file for user node: {user_node.user_email}") # Format the database name using the email formatted_email = format_user_email_for_neo_db(user_node.user_email) db_name = f"cc.users.{formatted_email}" fs = ClassroomCopilotFilesystem(db_name=db_name, init_run_type="user") # Use the path directly as provided - it represents the structure from root if not user_node.node_storage_path: raise HTTPException(status_code=400, detail="Node path not found") # The path might already contain parts of the filesystem structure # We need to construct the full path carefully if user_node.node_storage_path.startswith("users/"): # If path starts with users/, remove it since filesystem already has users/ structure base_path = user_node.node_storage_path[6:] # Remove "users/" prefix logging.debug(f"Removed 'users/' prefix, base_path is now: {base_path}") else: base_path = user_node.node_storage_path logging.debug(f"No 'users/' prefix found, using path as-is: {base_path}") base_path = os.path.normpath(base_path) logging.debug(f"Using base path: {base_path}") # Construct final path including tldraw file file_path = os.path.join(base_path, "tldraw_file.json") file_location = os.path.normpath(os.path.join(fs.root_path, file_path)) logging.debug(f"Attempting to write file at: {file_location}") try: # Ensure directory exists directory_location = os.path.dirname(file_location) os.makedirs(directory_location, exist_ok=True) logging.debug(f"Ensured directory exists: {directory_location}") # Write the file with open(file_location, "w") as file: json.dump(data, file, indent=4) logging.info(f"tldraw file successfully written to: {file_location}") return {"status": "success"} except Exception as e: logging.error(f"Error writing file: {e}") raise HTTPException(status_code=500, detail="Error writing file") @router.get("/get_tldraw_node_file") async def read_tldraw_node_file(path: str, db_name: str): logging.debug(f"Reading tldraw file for path: {path}") logging.debug(f"Database name: {db_name}") fs = ClassroomCopilotFilesystem(db_name=db_name, init_run_type="user") logging.debug(f"Filesystem root path: {fs.root_path}") # Use the path directly as provided - it represents the structure from root if not path: raise HTTPException(status_code=400, detail="Path not provided") # The path might already contain parts of the filesystem structure # We need to construct the full path carefully if path.startswith("users/"): # If path starts with users/, remove it since filesystem already has users/ structure base_path = path[6:] # Remove "users/" prefix logging.debug(f"Removed 'users/' prefix, base_path is now: {base_path}") else: base_path = path logging.debug(f"No 'users/' prefix found, using path as-is: {base_path}") base_path = os.path.normpath(base_path) logging.debug(f"Using base path: {base_path}") # Construct final path including tldraw file file_path = os.path.join(base_path, "tldraw_file.json") logging.debug(f"File path: {file_path}") file_location = os.path.normpath(os.path.join(fs.root_path, file_path)) logging.debug(f"Final file location: {file_location}") # Debug: Check what directories exist logging.debug(f"Checking if root path exists: {fs.root_path} - {os.path.exists(fs.root_path)}") logging.debug(f"Checking if base path exists: {os.path.join(fs.root_path, base_path)} - {os.path.exists(os.path.join(fs.root_path, base_path))}") logging.debug(f"Attempting to read file at: {file_location}") if os.path.exists(file_location): logging.debug(f"File exists: {file_location}") try: with open(file_location, "r") as file: data = json.load(file) return data except json.JSONDecodeError as e: logging.error(f"Failed to parse JSON from file: {e}") raise HTTPException(status_code=500, detail="Invalid JSON in file") except Exception as e: logging.error(f"Error reading file: {e}") raise HTTPException(status_code=500, detail="Error reading file") else: # Check if directory exists directory_location = os.path.dirname(file_location) logging.debug(f"Checking if directory exists: {directory_location} - {os.path.exists(directory_location)}") if os.path.exists(directory_location): logging.debug(f"Directory exists but file doesn't, creating default tldraw file at: {file_location}") try: # Create default tldraw content default_tldraw_content = create_default_tldraw_content() # Ensure directory exists (should already exist, but just in case) os.makedirs(directory_location, exist_ok=True) # Write the default file with open(file_location, "w") as file: json.dump(default_tldraw_content, file, indent=4) logging.info(f"Default tldraw file created at: {file_location}") return default_tldraw_content except Exception as e: logging.error(f"Error creating default tldraw file: {e}") raise HTTPException(status_code=500, detail="Error creating default tldraw file") else: logging.debug(f"Neither directory nor file exists: {directory_location}") # List contents of parent directories to help debug parent_dir = os.path.dirname(directory_location) if os.path.exists(parent_dir): logging.debug(f"Parent directory exists: {parent_dir}") try: contents = os.listdir(parent_dir) logging.debug(f"Parent directory contents: {contents}") except Exception as e: logging.debug(f"Could not list parent directory contents: {e}") else: logging.debug(f"Parent directory does not exist: {parent_dir}") raise HTTPException(status_code=404, detail="Directory not found") @router.post("/set_tldraw_node_file") async def set_tldraw_node_file(path: str, db_name: str, data: Dict): logging.debug(f"Setting tldraw file for path: {path}") fs = ClassroomCopilotFilesystem(db_name=db_name, init_run_type="user") logging.debug(f"Filesystem root path: {fs.root_path}") # Use the path directly as provided - it represents the structure from root if not path: raise HTTPException(status_code=400, detail="Path not provided") # The path might already contain parts of the filesystem structure # We need to construct the full path carefully if path.startswith("users/"): # If path starts with users/, remove it since filesystem already has users/ structure base_path = path[6:] # Remove "users/" prefix logging.debug(f"Removed 'users/' prefix, base_path is now: {base_path}") else: base_path = path logging.debug(f"No 'users/' prefix found, using path as-is: {base_path}") base_path = os.path.normpath(base_path) logging.debug(f"Using base path: {base_path}") # Construct final path including tldraw file file_path = os.path.join(base_path, "tldraw_file.json") file_location = os.path.normpath(os.path.join(fs.root_path, file_path)) logging.debug(f"File location: {file_location}") logging.debug(f"Attempting to set file at: {file_location}") try: # Ensure directory exists directory_location = os.path.dirname(file_location) os.makedirs(directory_location, exist_ok=True) logging.debug(f"Ensured directory exists: {directory_location}") # Write the file with open(file_location, "w") as file: json.dump(data, file, indent=4) logging.info(f"tldraw file successfully written to: {file_location}") return {"status": "success"} except Exception as e: logging.error(f"Error writing file: {e}") raise HTTPException(status_code=500, detail="Error writing file") def create_default_tldraw_content(): """Create default tldraw content structure.""" return { "document": { "store": { "document:document": { "gridSize": 10, "name": "", "meta": {}, "id": "document:document", "typeName": "document" }, "page:page": { "meta": {}, "id": "page:page", "name": "Page 1", "index": "a1", "typeName": "page" } }, "schema": { "schemaVersion": 2, "sequences": { "com.tldraw.store": 4, "com.tldraw.asset": 1, "com.tldraw.camera": 1, "com.tldraw.document": 2, "com.tldraw.instance": 25, "com.tldraw.instance_page_state": 5, "com.tldraw.page": 1, "com.tldraw.instance_presence": 5, "com.tldraw.pointer": 1, "com.tldraw.shape": 4, "com.tldraw.asset.bookmark": 2, "com.tldraw.asset.image": 5, "com.tldraw.asset.video": 5, "com.tldraw.shape.arrow": 5, "com.tldraw.shape.bookmark": 2, "com.tldraw.shape.draw": 2, "com.tldraw.shape.embed": 4, "com.tldraw.shape.frame": 0, "com.tldraw.shape.geo": 9, "com.tldraw.shape.group": 0, "com.tldraw.shape.highlight": 1, "com.tldraw.shape.image": 4, "com.tldraw.shape.line": 5, "com.tldraw.shape.note": 8, "com.tldraw.shape.text": 2, "com.tldraw.shape.video": 2, "com.tldraw.binding.arrow": 0 } }, "recordVersions": { "asset": {"version": 1, "subTypeKey": "type", "subTypeVersions": {}}, "camera": {"version": 1}, "document": {"version": 2}, "instance": {"version": 21}, "instance_page_state": {"version": 5}, "page": {"version": 1}, "shape": {"version": 3, "subTypeKey": "type", "subTypeVersions": {}}, "instance_presence": {"version": 5}, "pointer": {"version": 1} }, "rootShapeIds": [], "bindings": [], "assets": [] }, "session": { "version": 0, "currentPageId": "page:page", "pageStates": [{ "pageId": "page:page", "camera": {"x": 0, "y": 0, "z": 1}, "selectedShapeIds": [] }] } }