diff --git a/backend/services/user_workspace_manager.py b/backend/services/user_workspace_manager.py index 51fde01d..155fb1a1 100644 --- a/backend/services/user_workspace_manager.py +++ b/backend/services/user_workspace_manager.py @@ -13,7 +13,7 @@ from loguru import logger from sqlalchemy.orm import Session from sqlalchemy import text -from services.database import init_user_database, ensure_user_workspace_db_directory +from services.database import WORKSPACE_DIR, init_user_database, ensure_user_workspace_db_directory from services.workspace_dirs import ensure_user_workspace_dirs from services.workspace_paths import get_workspace_root, get_user_workspace_dir @@ -39,60 +39,46 @@ class UserWorkspaceManager: """Create a complete user workspace with progressive setup.""" try: logger.info(f"Creating workspace for user {user_id}") - - # Sanitize user_id - safe_user_id = self._sanitize_user_id(user_id) - - # Check if we're in production and skip file operations if needed - if os.getenv("RENDER") or os.getenv("RAILWAY") or os.getenv("HEROKU"): - logger.info("Production environment detected - skipping file workspace creation") - return { - "user_id": user_id, - "workspace_path": "/tmp/alwrity_workspace/users/user_" + safe_user_id, - "config": self._create_user_config(user_id), - "created_at": datetime.utcnow().isoformat(), - "production_mode": True - } - - # Create user-specific directories - # Format: workspaces/workspace_{user_id} + + production_env = bool(os.getenv("RENDER") or os.getenv("RAILWAY") or os.getenv("HEROKU")) + filesystem_minimal_mode = bool(os.getenv("ALWRITY_FILESYSTEM_MINIMAL_MODE")) + mode = "filesystem_minimal" if filesystem_minimal_mode else ("production" if production_env else "development") + user_dir = get_user_workspace_dir(user_id) user_dir.mkdir(parents=True, exist_ok=True) - - # Ensure canonical DB directory and migrate legacy layout if needed self._ensure_workspace_db_directory(user_id) - - # Create user-specific directories lazily via centralized helper user_dir = ensure_user_workspace_dirs( user_id, capabilities={"core", "content", "research", "media", "assets"}, ) - - # Create user-specific configuration + config = self._create_user_config(user_id) config_file = user_dir / "config" / "user_config.json" with open(config_file, 'w') as f: json.dump(config, f, indent=2) - - # Create user-specific database tables - # Use database.py's init_user_database to ensure proper schema + try: init_user_database(user_id) except Exception as db_err: logger.error(f"Failed to initialize user database: {db_err}") - # We don't raise here to allow workspace creation to proceed, - # but it might be critical. Let's log and continue for now or raise? - # If DB init fails, the app might not work. raise db_err - - logger.info(f"✅ User workspace created: {user_dir}") + + dirs_created = ["db", "assets", "media", "content", "config/user_config.json"] + logger.info( + "User workspace created", + mode=mode, + workspace_path=str(user_dir), + dirs_created=dirs_created, + ) return { "user_id": user_id, "workspace_path": str(user_dir), "config": config, - "created_at": datetime.now().isoformat() + "created_at": datetime.now().isoformat(), + "mode": mode, + "dirs_created": dirs_created, } - + except Exception as e: logger.error(f"Error creating user workspace: {e}") raise diff --git a/backend/tests/test_user_workspace_manager.py b/backend/tests/test_user_workspace_manager.py new file mode 100644 index 00000000..8e6e493e --- /dev/null +++ b/backend/tests/test_user_workspace_manager.py @@ -0,0 +1,53 @@ +from pathlib import Path + +from services.user_workspace_manager import UserWorkspaceManager + + +def _configure_temp_workspace(monkeypatch, tmp_path): + workspace_root = tmp_path / "workspace" + monkeypatch.setattr("services.database.WORKSPACE_DIR", str(workspace_root)) + monkeypatch.setattr("services.workspace_dirs.WORKSPACE_DIR", str(workspace_root)) + monkeypatch.setattr("services.user_workspace_manager.WORKSPACE_DIR", str(workspace_root)) + monkeypatch.setattr("services.user_workspace_manager.init_user_database", lambda user_id: None) + return workspace_root + + +def _assert_required_contract(user_dir: Path): + assert user_dir.exists() + assert (user_dir / "db").exists() + assert (user_dir / "assets").exists() + assert (user_dir / "media").exists() + assert (user_dir / "content").exists() + assert (user_dir / "config" / "user_config.json").exists() + + +def test_create_user_workspace_development_contract(monkeypatch, tmp_path): + workspace_root = _configure_temp_workspace(monkeypatch, tmp_path) + monkeypatch.delenv("RENDER", raising=False) + monkeypatch.delenv("RAILWAY", raising=False) + monkeypatch.delenv("HEROKU", raising=False) + monkeypatch.delenv("ALWRITY_FILESYSTEM_MINIMAL_MODE", raising=False) + + manager = UserWorkspaceManager(db_session=None) + result = manager.create_user_workspace("dev-user") + + expected = workspace_root / "workspace_dev-user" + _assert_required_contract(expected) + assert result["workspace_path"] == str(expected) + assert result["mode"] == "development" + assert {"db", "assets", "media", "content", "config/user_config.json"}.issubset(set(result["dirs_created"])) + + +def test_create_user_workspace_production_filesystem_minimal_contract(monkeypatch, tmp_path): + workspace_root = _configure_temp_workspace(monkeypatch, tmp_path) + monkeypatch.setenv("RENDER", "1") + monkeypatch.setenv("ALWRITY_FILESYSTEM_MINIMAL_MODE", "1") + + manager = UserWorkspaceManager(db_session=None) + result = manager.create_user_workspace("prod-user") + + expected = workspace_root / "workspace_prod-user" + _assert_required_contract(expected) + assert result["workspace_path"] == str(expected) + assert result["mode"] == "filesystem_minimal" + assert {"db", "assets", "media", "content", "config/user_config.json"}.issubset(set(result["dirs_created"]))