latest
This commit is contained in:
@@ -0,0 +1,265 @@
|
||||
"""
|
||||
TLDraw Supabase Storage Router
|
||||
=============================
|
||||
|
||||
Handles TLDraw snapshot operations using Supabase Storage instead of local filesystem.
|
||||
This replaces the old filesystem-based tldraw_filesystem.py router.
|
||||
"""
|
||||
|
||||
from dotenv import load_dotenv, find_dotenv
|
||||
load_dotenv(find_dotenv())
|
||||
import os
|
||||
import json
|
||||
import logging
|
||||
from fastapi import APIRouter, HTTPException, Query
|
||||
from typing import Dict, Any
|
||||
|
||||
from modules.database.supabase.utils.storage import StorageAdmin
|
||||
from modules.logger_tool import initialise_logger
|
||||
|
||||
router = APIRouter()
|
||||
logger = initialise_logger(__name__, os.getenv("LOG_LEVEL"), os.getenv("LOG_PATH"), 'default', True)
|
||||
|
||||
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": []
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
||||
@router.get("/get_tldraw_node_file")
|
||||
async def read_tldraw_node_file_from_supabase(
|
||||
path: str = Query(..., description="Supabase Storage path (e.g., 'cc.public.snapshots/User/user_id')"),
|
||||
db_name: str = Query(..., description="Database name for context")
|
||||
):
|
||||
"""
|
||||
Load TLDraw snapshot from Supabase Storage.
|
||||
|
||||
Args:
|
||||
path: Supabase Storage path in format 'bucket/nodetype/node_id'
|
||||
db_name: Database name for context (used for logging)
|
||||
|
||||
Returns:
|
||||
TLDraw snapshot data
|
||||
"""
|
||||
logger.debug(f"Reading tldraw file from Supabase Storage for path: {path}")
|
||||
logger.debug(f"Database name: {db_name}")
|
||||
|
||||
if not path:
|
||||
raise HTTPException(status_code=400, detail="Path not provided")
|
||||
|
||||
try:
|
||||
# Initialize Supabase Storage
|
||||
storage = StorageAdmin()
|
||||
|
||||
# Parse the path to extract bucket and file path
|
||||
# Expected format: "cc.public.snapshots/User/user_id" or "cc.public.snapshots/Teacher/teacher_id"
|
||||
path_parts = path.split('/')
|
||||
if len(path_parts) < 3:
|
||||
raise HTTPException(status_code=400, detail="Invalid path format. Expected: bucket/nodetype/node_id")
|
||||
|
||||
bucket = path_parts[0] # e.g., "cc.public.snapshots"
|
||||
node_type = path_parts[1] # e.g., "User", "Teacher"
|
||||
node_id = path_parts[2] # e.g., "cbc309e5-4029-4c34-aab7-0aa33c563cd0"
|
||||
|
||||
# Construct the file path in Supabase Storage
|
||||
# Format: nodetype/node_id/tldraw_file.json
|
||||
file_path = f"{node_type}/{node_id}/tldraw_file.json"
|
||||
|
||||
logger.debug(f"Bucket: {bucket}")
|
||||
logger.debug(f"File path: {file_path}")
|
||||
|
||||
try:
|
||||
# Try to download the file from Supabase Storage
|
||||
file_data = storage.download_file(bucket, file_path)
|
||||
|
||||
# Parse JSON data
|
||||
try:
|
||||
snapshot_data = json.loads(file_data.decode('utf-8'))
|
||||
logger.info(f"Successfully loaded tldraw snapshot from Supabase Storage: {file_path}")
|
||||
|
||||
# Ensure the snapshot has the correct structure for TLDraw
|
||||
if isinstance(snapshot_data, dict) and 'document' in snapshot_data and 'session' in snapshot_data:
|
||||
# Check if it has the new format (schemaVersion in document.schema)
|
||||
if 'document' in snapshot_data and isinstance(snapshot_data['document'], dict) and 'schema' in snapshot_data['document']:
|
||||
return snapshot_data
|
||||
# Check if it has the old format (schemaVersion at root level)
|
||||
elif 'schemaVersion' in snapshot_data:
|
||||
return snapshot_data
|
||||
else:
|
||||
# Use default structure if schema is missing
|
||||
logger.warning(f"Snapshot data from {file_path_in_bucket} is missing schemaVersion. Using default structure.")
|
||||
return create_default_tldraw_content()
|
||||
else:
|
||||
# Use default structure if basic structure is missing
|
||||
logger.warning(f"Snapshot data from {file_path_in_bucket} is missing top-level TLDraw keys. Using default structure.")
|
||||
return create_default_tldraw_content()
|
||||
except json.JSONDecodeError as e:
|
||||
logger.error(f"Failed to parse JSON from Supabase Storage file: {e}")
|
||||
raise HTTPException(status_code=500, detail="Invalid JSON in file")
|
||||
|
||||
except Exception as e:
|
||||
# File doesn't exist, create default content
|
||||
logger.info(f"File not found in Supabase Storage, creating default tldraw content: {file_path}")
|
||||
|
||||
# Create default tldraw content
|
||||
default_content = create_default_tldraw_content()
|
||||
|
||||
try:
|
||||
# Upload default content to Supabase Storage
|
||||
json_data = json.dumps(default_content, indent=2).encode('utf-8')
|
||||
storage.upload_file(bucket, file_path, json_data, 'application/json', upsert=True)
|
||||
|
||||
logger.info(f"Default tldraw file created in Supabase Storage: {file_path}")
|
||||
return default_content
|
||||
|
||||
except Exception as upload_error:
|
||||
logger.error(f"Error creating default tldraw file in Supabase Storage: {upload_error}")
|
||||
raise HTTPException(status_code=500, detail="Error creating default tldraw file")
|
||||
|
||||
except HTTPException:
|
||||
# Re-raise HTTP exceptions
|
||||
raise
|
||||
except Exception as e:
|
||||
logger.error(f"Unexpected error loading tldraw file from Supabase Storage: {e}")
|
||||
raise HTTPException(status_code=500, detail=f"Error loading file: {str(e)}")
|
||||
|
||||
@router.post("/set_tldraw_node_file")
|
||||
async def set_tldraw_node_file_in_supabase(
|
||||
path: str = Query(..., description="Supabase Storage path (e.g., 'cc.public.snapshots/User/user_id')"),
|
||||
db_name: str = Query(..., description="Database name for context"),
|
||||
data: Dict[str, Any] = None
|
||||
):
|
||||
"""
|
||||
Save TLDraw snapshot to Supabase Storage.
|
||||
|
||||
Args:
|
||||
path: Supabase Storage path in format 'bucket/nodetype/node_id'
|
||||
db_name: Database name for context (used for logging)
|
||||
data: TLDraw snapshot data to save
|
||||
|
||||
Returns:
|
||||
Success status
|
||||
"""
|
||||
logger.debug(f"Saving tldraw file to Supabase Storage for path: {path}")
|
||||
logger.debug(f"Database name: {db_name}")
|
||||
|
||||
if not path:
|
||||
raise HTTPException(status_code=400, detail="Path not provided")
|
||||
|
||||
if not data:
|
||||
raise HTTPException(status_code=400, detail="Data not provided")
|
||||
|
||||
try:
|
||||
# Initialize Supabase Storage
|
||||
storage = StorageAdmin()
|
||||
|
||||
# Parse the path to extract bucket and file path
|
||||
path_parts = path.split('/')
|
||||
if len(path_parts) < 3:
|
||||
raise HTTPException(status_code=400, detail="Invalid path format. Expected: bucket/nodetype/node_id")
|
||||
|
||||
bucket = path_parts[0] # e.g., "cc.public.snapshots"
|
||||
node_type = path_parts[1] # e.g., "User", "Teacher"
|
||||
node_id = path_parts[2] # e.g., "cbc309e5-4029-4c34-aab7-0aa33c563cd0"
|
||||
|
||||
# Construct the file path in Supabase Storage
|
||||
file_path = f"{node_type}/{node_id}/tldraw_file.json"
|
||||
|
||||
logger.debug(f"Bucket: {bucket}")
|
||||
logger.debug(f"File path: {file_path}")
|
||||
|
||||
# Convert data to JSON
|
||||
try:
|
||||
json_data = json.dumps(data, indent=2).encode('utf-8')
|
||||
except (TypeError, ValueError) as e:
|
||||
logger.error(f"Failed to serialize data to JSON: {e}")
|
||||
raise HTTPException(status_code=400, detail="Invalid data format")
|
||||
|
||||
# Upload to Supabase Storage
|
||||
try:
|
||||
storage.upload_file(bucket, file_path, json_data, 'application/json', upsert=True)
|
||||
logger.info(f"Successfully saved tldraw snapshot to Supabase Storage: {file_path}")
|
||||
return {"status": "success", "message": "File saved successfully"}
|
||||
|
||||
except Exception as upload_error:
|
||||
logger.error(f"Error uploading file to Supabase Storage: {upload_error}")
|
||||
raise HTTPException(status_code=500, detail="Error saving file")
|
||||
|
||||
except HTTPException:
|
||||
# Re-raise HTTP exceptions
|
||||
raise
|
||||
except Exception as e:
|
||||
logger.error(f"Unexpected error saving tldraw file to Supabase Storage: {e}")
|
||||
raise HTTPException(status_code=500, detail=f"Error saving file: {str(e)}")
|
||||
Reference in New Issue
Block a user