api/routers/database/tools/tldraw_filesystem.py
2025-11-14 14:47:19 +00:00

364 lines
16 KiB
Python

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": []
}]
}
}