Enforce runtime-only workspace directory creation policy
This commit is contained in:
@@ -14,6 +14,7 @@ from sqlalchemy.orm import Session
|
||||
from sqlalchemy import text
|
||||
|
||||
from services.database import init_user_database
|
||||
from services.workspace_dirs import ensure_user_workspace_dirs
|
||||
|
||||
class UserWorkspaceManager:
|
||||
"""Manages user-specific workspaces and progressive setup."""
|
||||
@@ -55,33 +56,11 @@ class UserWorkspaceManager:
|
||||
"production_mode": True
|
||||
}
|
||||
|
||||
# Create user-specific directories
|
||||
# Format: workspaces/workspace_{user_id}
|
||||
user_dir = self.user_workspaces_dir / f"workspace_{safe_user_id}"
|
||||
user_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
# Create subdirectories
|
||||
subdirs = [
|
||||
"content",
|
||||
"content/images",
|
||||
"content/videos",
|
||||
"content/audio",
|
||||
"content/text",
|
||||
"content/youtube", # Consolidated
|
||||
"content/story", # Consolidated
|
||||
"research",
|
||||
"config",
|
||||
"cache",
|
||||
"exports",
|
||||
"templates",
|
||||
"database",
|
||||
"db", # Requested 'db' folder
|
||||
"media", # Requested 'media' folder
|
||||
"data" # User specific data folder
|
||||
]
|
||||
|
||||
for subdir in subdirs:
|
||||
(user_dir / subdir).mkdir(parents=True, exist_ok=True)
|
||||
# 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)
|
||||
@@ -273,9 +252,8 @@ class UserWorkspaceManager:
|
||||
|
||||
def _setup_ai_services(self, user_id: str):
|
||||
"""Set up AI services for the user."""
|
||||
safe_user_id = self._sanitize_user_id(user_id)
|
||||
# Create user-specific AI service configuration
|
||||
user_dir = self.user_workspaces_dir / f"workspace_{safe_user_id}"
|
||||
user_dir = ensure_user_workspace_dirs(user_id, capabilities={"ai_services"})
|
||||
ai_config = user_dir / "config" / "ai_services.json"
|
||||
|
||||
ai_services = {
|
||||
@@ -284,9 +262,6 @@ class UserWorkspaceManager:
|
||||
"copilotkit": {"enabled": True, "assistant_type": "content"}
|
||||
}
|
||||
|
||||
# Ensure config directory exists
|
||||
ai_config.parent.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
with open(ai_config, 'w') as f:
|
||||
json.dump(ai_services, f, indent=2)
|
||||
|
||||
@@ -307,9 +282,8 @@ class UserWorkspaceManager:
|
||||
|
||||
def _setup_integrations(self, user_id: str):
|
||||
"""Set up external integrations."""
|
||||
safe_user_id = self._sanitize_user_id(user_id)
|
||||
# Create integrations configuration
|
||||
user_dir = self.user_workspaces_dir / f"workspace_{safe_user_id}"
|
||||
user_dir = ensure_user_workspace_dirs(user_id, capabilities={"integrations"})
|
||||
integrations_config = user_dir / "config" / "integrations.json"
|
||||
|
||||
integrations = {
|
||||
@@ -318,28 +292,18 @@ class UserWorkspaceManager:
|
||||
"wordpress": {"enabled": False, "connected": False}
|
||||
}
|
||||
|
||||
# Ensure config directory exists
|
||||
integrations_config.parent.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
with open(integrations_config, 'w') as f:
|
||||
json.dump(integrations, f, indent=2)
|
||||
|
||||
def _setup_complete_features(self, user_id: str):
|
||||
"""Set up complete feature set."""
|
||||
safe_user_id = self._sanitize_user_id(user_id)
|
||||
# Create comprehensive workspace
|
||||
user_dir = self.user_workspaces_dir / f"workspace_{safe_user_id}"
|
||||
|
||||
user_dir = ensure_user_workspace_dirs(user_id, capabilities={"core", "content", "research", "media", "assets"})
|
||||
|
||||
# Create additional directories for complete setup
|
||||
complete_dirs = [
|
||||
"ai_models",
|
||||
"content_templates",
|
||||
"export_templates",
|
||||
"backup"
|
||||
]
|
||||
|
||||
complete_dirs = ["ai_models", "content_templates", "export_templates", "backup"]
|
||||
for dir_name in complete_dirs:
|
||||
(user_dir / dir_name).mkdir(exist_ok=True)
|
||||
(user_dir / dir_name).mkdir(parents=True, exist_ok=True)
|
||||
|
||||
# Create final configuration
|
||||
final_config = {
|
||||
|
||||
80
backend/services/workspace_dirs.py
Normal file
80
backend/services/workspace_dirs.py
Normal file
@@ -0,0 +1,80 @@
|
||||
"""Workspace directory helpers.
|
||||
|
||||
Centralizes directory creation so API/service imports stay side-effect free.
|
||||
"""
|
||||
|
||||
from pathlib import Path
|
||||
from typing import Iterable, Optional, Set
|
||||
|
||||
from services.database import WORKSPACE_DIR
|
||||
|
||||
|
||||
GLOBAL_OPERATIONAL_DIRS = {
|
||||
"logs": Path("logs"),
|
||||
"temp": Path("temp"),
|
||||
}
|
||||
|
||||
|
||||
USER_CAPABILITY_DIRS = {
|
||||
"core": {
|
||||
"config",
|
||||
"cache",
|
||||
"exports",
|
||||
"templates",
|
||||
"database",
|
||||
"db",
|
||||
"data",
|
||||
},
|
||||
"content": {
|
||||
"content",
|
||||
"content/images",
|
||||
"content/videos",
|
||||
"content/audio",
|
||||
"content/text",
|
||||
"content/youtube",
|
||||
"content/story",
|
||||
},
|
||||
"research": {"research"},
|
||||
"media": {"media"},
|
||||
"assets": {"assets", "assets/avatars", "assets/voice_samples"},
|
||||
"integrations": {"integrations"},
|
||||
"ai_services": {"config"},
|
||||
}
|
||||
|
||||
|
||||
def _sanitize_user_id(user_id: str) -> str:
|
||||
return "".join(c for c in user_id if c.isalnum() or c in ("-", "_"))
|
||||
|
||||
|
||||
def ensure_global_operational_dirs(dir_names: Optional[Iterable[str]] = None) -> None:
|
||||
"""Create only operational global directories (logs/temp), on demand."""
|
||||
targets = set(dir_names or GLOBAL_OPERATIONAL_DIRS.keys())
|
||||
for name in targets:
|
||||
directory = GLOBAL_OPERATIONAL_DIRS.get(name)
|
||||
if directory:
|
||||
directory.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
|
||||
def ensure_user_workspace_dirs(user_id: str, capabilities: Optional[Iterable[str]] = None) -> Path:
|
||||
"""Ensure user workspace directories required by capabilities.
|
||||
|
||||
Args:
|
||||
user_id: tenant/user identifier.
|
||||
capabilities: iterable of capability keys from USER_CAPABILITY_DIRS.
|
||||
"""
|
||||
safe_user_id = _sanitize_user_id(user_id)
|
||||
user_dir = Path(WORKSPACE_DIR) / f"workspace_{safe_user_id}"
|
||||
|
||||
requested = set(capabilities or {"core"})
|
||||
requested.add("core")
|
||||
|
||||
subdirs: Set[str] = set()
|
||||
for capability in requested:
|
||||
subdirs.update(USER_CAPABILITY_DIRS.get(capability, set()))
|
||||
|
||||
user_dir.mkdir(parents=True, exist_ok=True)
|
||||
for subdir in sorted(subdirs):
|
||||
(user_dir / subdir).mkdir(parents=True, exist_ok=True)
|
||||
|
||||
return user_dir
|
||||
|
||||
Reference in New Issue
Block a user