from bson import ObjectId from app.db import get_db from datetime import datetime class Folder: @staticmethod def create(folder_data, user_id): """Create a new folder.""" db = get_db() # Add metadata folder_data["created_at"] = datetime.utcnow() folder_data["created_by"] = user_id # Note: No longer storing persona_ids in folders - using persona-centric storage result = db.folders.insert_one(folder_data) return str(result.inserted_id) @staticmethod def find_by_id(folder_id): """Find a folder by its ID.""" db = get_db() try: folder = db.folders.find_one({"_id": ObjectId(folder_id)}) if folder: folder["_id"] = str(folder["_id"]) return folder except Exception as e: print(f"Error in find_by_id: {e}") return None @staticmethod def find_by_user(user_id, limit=100): """Find all folders created by a specific user.""" db = get_db() folders = db.folders.find({"created_by": user_id}).sort("created_at", -1).limit(limit) result = [] for folder in folders: folder["_id"] = str(folder["_id"]) result.append(folder) return result @staticmethod def get_all(limit=100): """Get all folders (for debugging/admin purposes).""" try: db = get_db() folders = list(db.folders.find().sort("created_at", -1).limit(limit)) result = [] for folder in folders: folder["_id"] = str(folder["_id"]) result.append(folder) return result except Exception as e: print(f"Error in Folder.get_all: {e}") return [] @staticmethod def update(folder_id, data): """Update a folder.""" db = get_db() # Create a copy of the data to avoid modifying the original filtered_data = data.copy() # Remove fields that shouldn't be updated if '_id' in filtered_data: del filtered_data['_id'] if 'id' in filtered_data: del filtered_data['id'] if 'created_at' in filtered_data: del filtered_data['created_at'] if 'created_by' in filtered_data: del filtered_data['created_by'] # Set the updated timestamp filtered_data["updated_at"] = datetime.utcnow() result = db.folders.update_one( {"_id": ObjectId(folder_id)}, {"$set": filtered_data} ) return result.modified_count > 0 @staticmethod def delete(folder_id): """Delete a folder.""" db = get_db() try: result = db.folders.delete_one({"_id": ObjectId(folder_id)}) return result.deleted_count > 0 except Exception as e: print(f"Error in delete: {e}") return False @staticmethod def add_persona(folder_id, persona_id): """Add a persona to a folder (persona-centric storage).""" db = get_db() try: print(f"🔧 FOLDER ADD_PERSONA: folder_id={folder_id}, persona_id={persona_id}") # Check if persona exists persona = db.personas.find_one({"_id": ObjectId(persona_id)}) if not persona: print(f"❌ FOLDER ADD_PERSONA: Persona {persona_id} not found") return False print(f"✅ FOLDER ADD_PERSONA: Found persona {persona.get('name', 'Unknown')} ({persona_id})") print(f"📋 FOLDER ADD_PERSONA: Current folder_ids: {persona.get('folder_ids', 'None')}") # Only update the persona's folder_ids - single source of truth persona_result = db.personas.update_one( {"_id": ObjectId(persona_id)}, {"$addToSet": {"folder_ids": folder_id}, "$set": {"updated_at": datetime.utcnow()}} ) print(f"📝 FOLDER ADD_PERSONA: Update result - modified_count: {persona_result.modified_count}, matched_count: {persona_result.matched_count}") # Verify the update updated_persona = db.personas.find_one({"_id": ObjectId(persona_id)}) print(f"✅ FOLDER ADD_PERSONA: Updated folder_ids: {updated_persona.get('folder_ids', 'None')}") # Update folder's updated_at timestamp db.folders.update_one( {"_id": ObjectId(folder_id)}, {"$set": {"updated_at": datetime.utcnow()}} ) return persona_result.modified_count > 0 except Exception as e: print(f"❌ FOLDER ADD_PERSONA ERROR: {e}") import traceback print(f"❌ FOLDER ADD_PERSONA TRACEBACK: {traceback.format_exc()}") return False @staticmethod def remove_persona(folder_id, persona_id): """Remove a persona from a folder (persona-centric storage).""" db = get_db() try: print(f"🔧 FOLDER REMOVE_PERSONA: folder_id={folder_id}, persona_id={persona_id}") # Check if persona exists persona = db.personas.find_one({"_id": ObjectId(persona_id)}) if not persona: print(f"❌ FOLDER REMOVE_PERSONA: Persona {persona_id} not found") return False print(f"✅ FOLDER REMOVE_PERSONA: Found persona {persona.get('name', 'Unknown')} ({persona_id})") print(f"📋 FOLDER REMOVE_PERSONA: Current folder_ids: {persona.get('folder_ids', 'None')}") # Only update the persona's folder_ids - single source of truth persona_result = db.personas.update_one( {"_id": ObjectId(persona_id)}, {"$pull": {"folder_ids": folder_id}, "$set": {"updated_at": datetime.utcnow()}} ) print(f"📝 FOLDER REMOVE_PERSONA: Update result - modified_count: {persona_result.modified_count}, matched_count: {persona_result.matched_count}") # Verify the update updated_persona = db.personas.find_one({"_id": ObjectId(persona_id)}) print(f"✅ FOLDER REMOVE_PERSONA: Updated folder_ids: {updated_persona.get('folder_ids', 'None')}") # Update folder's updated_at timestamp db.folders.update_one( {"_id": ObjectId(folder_id)}, {"$set": {"updated_at": datetime.utcnow()}} ) return persona_result.modified_count > 0 except Exception as e: print(f"❌ FOLDER REMOVE_PERSONA ERROR: {e}") import traceback print(f"❌ FOLDER REMOVE_PERSONA TRACEBACK: {traceback.format_exc()}") return False @staticmethod def add_personas_batch(folder_id, persona_ids): """Add multiple personas to a folder (persona-centric storage).""" db = get_db() try: print(f"🔧 FOLDER ADD_PERSONAS_BATCH: folder_id={folder_id}, persona_ids={persona_ids}") # Add folder to each persona's folder_ids - single source of truth persona_results = [] for persona_id in persona_ids: try: print(f"🔧 FOLDER BATCH: Processing persona {persona_id}") # Check if persona exists persona = db.personas.find_one({"_id": ObjectId(persona_id)}) if not persona: print(f"❌ FOLDER BATCH: Persona {persona_id} not found") persona_results.append(False) continue print(f"✅ FOLDER BATCH: Found persona {persona.get('name', 'Unknown')}") print(f"📋 FOLDER BATCH: Current folder_ids: {persona.get('folder_ids', 'None')}") result = db.personas.update_one( {"_id": ObjectId(persona_id)}, {"$addToSet": {"folder_ids": folder_id}, "$set": {"updated_at": datetime.utcnow()}} ) print(f"📝 FOLDER BATCH: Update result for {persona_id} - modified: {result.modified_count}") persona_results.append(result.modified_count > 0) except Exception as e: print(f"❌ FOLDER BATCH ERROR for persona {persona_id}: {e}") persona_results.append(False) # Update folder's updated_at timestamp db.folders.update_one( {"_id": ObjectId(folder_id)}, {"$set": {"updated_at": datetime.utcnow()}} ) success_count = sum(1 for r in persona_results if r) print(f"✅ FOLDER ADD_PERSONAS_BATCH: {success_count}/{len(persona_ids)} personas updated successfully") return any(persona_results) except Exception as e: print(f"❌ FOLDER ADD_PERSONAS_BATCH ERROR: {e}") import traceback print(f"❌ FOLDER ADD_PERSONAS_BATCH TRACEBACK: {traceback.format_exc()}") return False @staticmethod def remove_personas_batch(folder_id, persona_ids): """Remove multiple personas from a folder (persona-centric storage).""" db = get_db() try: print(f"🔧 FOLDER REMOVE_PERSONAS_BATCH: folder_id={folder_id}, persona_ids={persona_ids}") # Remove folder from each persona's folder_ids - single source of truth persona_results = [] for persona_id in persona_ids: try: print(f"🔧 FOLDER REMOVE_BATCH: Processing persona {persona_id}") # Check if persona exists persona = db.personas.find_one({"_id": ObjectId(persona_id)}) if not persona: print(f"❌ FOLDER REMOVE_BATCH: Persona {persona_id} not found") persona_results.append(False) continue print(f"✅ FOLDER REMOVE_BATCH: Found persona {persona.get('name', 'Unknown')}") print(f"📋 FOLDER REMOVE_BATCH: Current folder_ids: {persona.get('folder_ids', 'None')}") result = db.personas.update_one( {"_id": ObjectId(persona_id)}, {"$pull": {"folder_ids": folder_id}, "$set": {"updated_at": datetime.utcnow()}} ) print(f"📝 FOLDER REMOVE_BATCH: Update result for {persona_id} - modified: {result.modified_count}") persona_results.append(result.modified_count > 0) except Exception as e: print(f"❌ FOLDER REMOVE_BATCH ERROR for persona {persona_id}: {e}") persona_results.append(False) # Update folder's updated_at timestamp db.folders.update_one( {"_id": ObjectId(folder_id)}, {"$set": {"updated_at": datetime.utcnow()}} ) success_count = sum(1 for r in persona_results if r) print(f"✅ FOLDER REMOVE_PERSONAS_BATCH: {success_count}/{len(persona_ids)} personas updated successfully") return any(persona_results) except Exception as e: print(f"❌ FOLDER REMOVE_PERSONAS_BATCH ERROR: {e}") import traceback print(f"❌ FOLDER REMOVE_PERSONAS_BATCH TRACEBACK: {traceback.format_exc()}") return False @staticmethod def get_folders_containing_persona(persona_id, user_id=None): """Find all folders that contain a specific persona (persona-centric storage).""" db = get_db() try: # Get the persona to see which folders it belongs to persona = db.personas.find_one({"_id": ObjectId(persona_id)}) if not persona or not persona.get("folder_ids"): return [] # Get folders by their IDs folder_ids = [ObjectId(fid) for fid in persona["folder_ids"]] query = {"_id": {"$in": folder_ids}} # Optionally filter by user if user_id: query["created_by"] = user_id folders = list(db.folders.find(query)) result = [] for folder in folders: folder["_id"] = str(folder["_id"]) result.append(folder) return result except Exception as e: print(f"Error getting folders for persona {persona_id}: {e}") return []