diff --git a/backend/check_cols.py b/backend/check_cols.py index a8d09f5f..04bc39e1 100644 --- a/backend/check_cols.py +++ b/backend/check_cols.py @@ -1,7 +1,10 @@ -import sqlite3 import os +import sqlite3 -db_path = r'workspace/workspace_user_33Gz1FPI86VDXhRY8QN4ragRFGN/db/alwrity_user_33Gz1FPI86VDXhRY8QN4ragRFGN.db' +from services.database import get_user_db_path + +USER_ID = "user_33Gz1FPI86VDXhRY8QN4ragRFGN" +db_path = get_user_db_path(USER_ID) if os.path.exists(db_path): conn = sqlite3.connect(db_path) diff --git a/backend/scripts/debug_usage_v2.py b/backend/scripts/debug_usage_v2.py index acd5050d..962f084c 100644 --- a/backend/scripts/debug_usage_v2.py +++ b/backend/scripts/debug_usage_v2.py @@ -19,18 +19,8 @@ from services.subscription import UsageTrackingService, PricingService USER_ID = "user_33Gz1FPI86VDXhRY8QN4ragRFGN" def get_db_path(user_id): - # Logic from database.py to resolve path - base_path = os.getcwd() - # Sanitize user_id - safe_user_id = "".join(c for c in user_id if c.isalnum() or c in ('-', '_')) - user_workspace = os.path.join(base_path, "workspace", f"workspace_{safe_user_id}") - # Try both naming conventions - db_path_v1 = os.path.join(user_workspace, "db", "alwrity.db") - db_path_v2 = os.path.join(user_workspace, "db", f"alwrity_{safe_user_id}.db") - - if os.path.exists(db_path_v2): - return db_path_v2 - return db_path_v1 + from services.database import get_user_db_path + return get_user_db_path(user_id) def check_user_data(): db_path = get_db_path(USER_ID) diff --git a/backend/scripts/inspect_user_db.py b/backend/scripts/inspect_user_db.py index 38606b74..fef7279c 100644 --- a/backend/scripts/inspect_user_db.py +++ b/backend/scripts/inspect_user_db.py @@ -1,13 +1,14 @@ - import os import sys import sqlite3 +# Add backend to path +sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) + +from services.database import get_user_db_path + user_id = "user_33Gz1FPI86VDXhRY8QN4ragRFGN" -base_path = os.getcwd() -safe_user_id = "".join(c for c in user_id if c.isalnum() or c in ('-', '_')) -user_workspace = os.path.join(base_path, "workspace", f"workspace_{safe_user_id}") -db_path = os.path.join(user_workspace, "db", f"alwrity_{safe_user_id}.db") +db_path = get_user_db_path(user_id) print(f"Reading from: {db_path}") diff --git a/backend/scripts/verify_db_path.py b/backend/scripts/verify_db_path.py index b94299b6..402a2d53 100644 --- a/backend/scripts/verify_db_path.py +++ b/backend/scripts/verify_db_path.py @@ -9,16 +9,10 @@ sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) from services.database import get_user_db_path user_id = "user_33Gz1FPI86VDXhRY8QN4ragRFGN" -base_path = os.getcwd() -safe_user_id = "".join(c for c in user_id if c.isalnum() or c in ('-', '_')) -user_workspace = os.path.join(base_path, "workspace", f"workspace_{safe_user_id}") - -path1 = os.path.join(user_workspace, "db", "alwrity.db") -path2 = os.path.join(user_workspace, "db", f"alwrity_{safe_user_id}.db") - -print(f"Checking paths for user {user_id}:") -print(f"Legacy: {path1}") -print(f"Specific: {path2}") +resolved = get_user_db_path(user_id) +legacy_filename = "alwrity.db" +print(f"Resolved active DB path for user {user_id}: {resolved}") +print(f"Legacy filename still supported in db/: {legacy_filename}") def check_db(path): if not os.path.exists(path): @@ -35,9 +29,7 @@ def check_db(path): except Exception as e: print(f" [EXISTS] {path} - Error reading: {e}") -check_db(path1) -check_db(path2) +check_db(resolved) print("-" * 30) -resolved = get_user_db_path(user_id) print(f"Application resolves to: {resolved}") diff --git a/backend/services/database.py b/backend/services/database.py index a8ee31ee..f5ee6031 100644 --- a/backend/services/database.py +++ b/backend/services/database.py @@ -86,16 +86,56 @@ def _ensure_daily_workflow_schema(engine, user_id: str) -> None: except Exception as e: logger.error(f"Failed daily_workflow_plans schema compatibility check for user {user_id}: {e}") +def _sanitize_user_id(user_id: str) -> str: + """Sanitize user_id to be safe for filesystem.""" + return "".join(c for c in user_id if c.isalnum() or c in ('-', '_')) + + +def ensure_user_workspace_db_directory(user_id: str) -> str: + """Ensure modern `db/` directory exists, migrating legacy `database/` when safe.""" + safe_user_id = _sanitize_user_id(user_id) + user_workspace = os.path.join(WORKSPACE_DIR, f"workspace_{safe_user_id}") + db_dir = os.path.join(user_workspace, 'db') + legacy_db_dir = os.path.join(user_workspace, 'database') + + if os.path.isdir(legacy_db_dir) and not os.path.exists(db_dir): + try: + os.rename(legacy_db_dir, db_dir) + logger.info(f"Migrated legacy database directory to db/: {user_workspace}") + except OSError as rename_error: + logger.warning( + f"Could not rename legacy database directory for {user_workspace}: {rename_error}" + ) + os.makedirs(db_dir, exist_ok=True) + for filename in os.listdir(legacy_db_dir): + src = os.path.join(legacy_db_dir, filename) + dst = os.path.join(db_dir, filename) + if os.path.isfile(src) and not os.path.exists(dst): + try: + os.link(src, dst) + except OSError: + # Fall back to copy when hard-linking is not possible. + import shutil + shutil.copy2(src, dst) + else: + os.makedirs(db_dir, exist_ok=True) + + return db_dir + def get_user_db_path(user_id: str) -> str: """Get the database path for a specific user.""" - # Sanitize user_id to be safe for filesystem - safe_user_id = "".join(c for c in user_id if c.isalnum() or c in ('-', '_')) + safe_user_id = _sanitize_user_id(user_id) user_workspace = os.path.join(WORKSPACE_DIR, f"workspace_{safe_user_id}") + db_dir = ensure_user_workspace_db_directory(user_id) # Check for legacy naming convention first (to support existing data) # Some older workspaces might have 'alwrity.db' instead of 'alwrity_{user_id}.db' - legacy_db_path = os.path.join(user_workspace, 'db', 'alwrity.db') - specific_db_path = os.path.join(user_workspace, 'db', f'alwrity_{safe_user_id}.db') + legacy_db_path = os.path.join(db_dir, 'alwrity.db') + specific_db_path = os.path.join(db_dir, f'alwrity_{safe_user_id}.db') + + # Backward compatibility when filesystem migration couldn't run yet. + legacy_dir_path = os.path.join(user_workspace, 'database', f'alwrity_{safe_user_id}.db') + legacy_dir_default = os.path.join(user_workspace, 'database', 'alwrity.db') # If the specific one exists, use it (preferred) if os.path.exists(specific_db_path): @@ -104,6 +144,12 @@ def get_user_db_path(user_id: str) -> str: # If legacy exists and specific doesn't, use legacy if os.path.exists(legacy_db_path): return legacy_db_path + + if os.path.exists(legacy_dir_path): + return legacy_dir_path + + if os.path.exists(legacy_dir_default): + return legacy_dir_default # Default to specific for new databases return specific_db_path diff --git a/backend/services/research/deep_crawl_service.py b/backend/services/research/deep_crawl_service.py index 4d281e70..a230902d 100644 --- a/backend/services/research/deep_crawl_service.py +++ b/backend/services/research/deep_crawl_service.py @@ -100,7 +100,7 @@ class DeepCrawlService: # Note: Path logic should be consistent with project structure # Assuming workspace path is available via env or config, or constructing it. # Using relative path for now, adjusted to project root. - # The memory says: workspace/workspace_{user_id}/db/alwrity.db + # Database files are stored under workspace/workspace_{user_id}/db/. # So workspace root is workspace/workspace_{user_id}/ workspace_dir = f"workspace/workspace_{user_id}/crawled_content" os.makedirs(workspace_dir, exist_ok=True) diff --git a/backend/services/user_workspace_manager.py b/backend/services/user_workspace_manager.py index 6c404db3..7924b1b8 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 +from services.database import init_user_database, ensure_user_workspace_db_directory class UserWorkspaceManager: """Manages user-specific workspaces and progressive setup.""" @@ -36,6 +36,11 @@ class UserWorkspaceManager: """Sanitize user_id to be safe for filesystem (matches database.py logic).""" return "".join(c for c in user_id if c.isalnum() or c in ('-', '_')) + def _ensure_workspace_db_directory(self, user_id: str) -> None: + """Ensure workspace uses canonical `db/` layout via database service authority.""" + ensure_user_workspace_db_directory(user_id) + + def create_user_workspace(self, user_id: str) -> Dict[str, Any]: """Create a complete user workspace with progressive setup.""" try: @@ -59,6 +64,9 @@ class UserWorkspaceManager: # Format: workspaces/workspace_{user_id} user_dir = self.user_workspaces_dir / f"workspace_{safe_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 subdirectories subdirs = [ @@ -74,8 +82,7 @@ class UserWorkspaceManager: "cache", "exports", "templates", - "database", - "db", # Requested 'db' folder + "db", # Official database folder "media", # Requested 'media' folder "data" # User specific data folder ] @@ -358,8 +365,8 @@ class UserWorkspaceManager: if user_dir.exists(): shutil.rmtree(user_dir) - # Note: We do not drop tables here because each user has their own DB file - # (alwrity.db) inside their workspace. Deleting the workspace folder + # Note: We do not drop tables here because each user has their own DB file + # inside workspace/workspace_{id}/db/. Deleting the workspace folder # deletes the DB file as well. logger.info(f"✅ User workspace cleaned up for user {user_id}") diff --git a/backend/verify_schema.py b/backend/verify_schema.py index ea778409..69542eb7 100644 --- a/backend/verify_schema.py +++ b/backend/verify_schema.py @@ -1,6 +1,9 @@ import sqlite3 -db_path = r'c:\Users\diksha rawat\Desktop\ALwrity\workspace\workspace_alwrity\db\alwrity_alwrity.db' +from services.database import get_user_db_path + +USER_ID = "alwrity" +db_path = get_user_db_path(USER_ID) conn = sqlite3.connect(db_path) cursor = conn.cursor() @@ -15,7 +18,7 @@ if tables: cols = cursor.fetchall() col_names = [c[1] for c in cols] print(f"\nColumns in daily_workflow_plans: {col_names}") - + required = ['generation_mode', 'committee_agent_count', 'fallback_used'] for col in required: if col in col_names: