diff --git a/backend/app/routes/folders.py b/backend/app/routes/folders.py index a66cca27c..59c306dae 100644 --- a/backend/app/routes/folders.py +++ b/backend/app/routes/folders.py @@ -450,6 +450,8 @@ def get_all_folders(): ai_tagging, tagging_completed, ) = folder_data + # Check if folder path still exists on filesystem + folder_exists = os.path.isdir(folder_path) folders.append( FolderDetails( folder_id=folder_id, @@ -458,6 +460,7 @@ def get_all_folders(): last_modified_time=last_modified_time, AI_Tagging=ai_tagging, taggingCompleted=tagging_completed, + exists=folder_exists, ) ) diff --git a/backend/app/schemas/folders.py b/backend/app/schemas/folders.py index 63045241b..0af0a9a71 100644 --- a/backend/app/schemas/folders.py +++ b/backend/app/schemas/folders.py @@ -30,6 +30,7 @@ class FolderDetails(BaseModel): last_modified_time: int AI_Tagging: bool taggingCompleted: Optional[bool] = None + exists: bool = True class GetAllFoldersData(BaseModel): diff --git a/backend/tests/test_folders.py b/backend/tests/test_folders.py index a0d26f0e5..4882fa925 100644 --- a/backend/tests/test_folders.py +++ b/backend/tests/test_folders.py @@ -586,11 +586,13 @@ def test_delete_folders_database_error(self, mock_delete_batch, client): # ============================================================================ @patch("app.routes.folders.db_get_all_folder_details") + @patch("app.routes.folders.os.path.isdir") def test_get_all_folders_success( - self, mock_get_all_folders, client, sample_folder_details + self, mock_isdir, mock_get_all_folders, client, sample_folder_details ): """Test successfully retrieving all folders.""" mock_get_all_folders.return_value = sample_folder_details + mock_isdir.return_value = True response = client.get("/folders/all-folders") @@ -608,8 +610,10 @@ def test_get_all_folders_success( assert first_folder["parent_folder_id"] is None assert first_folder["AI_Tagging"] is True assert first_folder["taggingCompleted"] is False + assert first_folder["exists"] is True mock_get_all_folders.assert_called_once() + assert mock_isdir.call_count == 2 @patch("app.routes.folders.db_get_all_folder_details") def test_get_all_folders_empty(self, mock_get_all_folders, client): diff --git a/docs/backend/backend_python/openapi.json b/docs/backend/backend_python/openapi.json index fbf40091b..ec0ee9daf 100644 --- a/docs/backend/backend_python/openapi.json +++ b/docs/backend/backend_python/openapi.json @@ -1686,6 +1686,11 @@ } ], "title": "Taggingcompleted" + }, + "exists": { + "type": "boolean", + "title": "Exists", + "default": true } }, "type": "object", diff --git a/frontend/src/hooks/useFolderOperations.tsx b/frontend/src/hooks/useFolderOperations.tsx index ff747ff0b..e40872bdf 100644 --- a/frontend/src/hooks/useFolderOperations.tsx +++ b/frontend/src/hooks/useFolderOperations.tsx @@ -25,6 +25,8 @@ export const useFolderOperations = () => { const foldersQuery = usePictoQuery({ queryKey: ['folders'], queryFn: getAllFolders, + // Ensure we re-check filesystem existence when navigating back to Settings + refetchOnMount: 'always', }); const taggingStatusQuery = usePictoQuery({ diff --git a/frontend/src/pages/SettingsPage/components/FolderManagementCard.tsx b/frontend/src/pages/SettingsPage/components/FolderManagementCard.tsx index db4b029fa..f374c72a8 100644 --- a/frontend/src/pages/SettingsPage/components/FolderManagementCard.tsx +++ b/frontend/src/pages/SettingsPage/components/FolderManagementCard.tsx @@ -37,11 +37,14 @@ const FolderManagementCard: React.FC = () => { > {folders.length > 0 ? (
- {folders.map((folder: FolderDetails, index: number) => ( -
+ {folders.map((folder: FolderDetails, index: number) => { + const isMissing = folder.exists === false; + + return ( +
@@ -49,6 +52,11 @@ const FolderManagementCard: React.FC = () => { {folder.folder_path} + {folder.exists === false && ( + + Missing + + )}
@@ -62,7 +70,9 @@ const FolderManagementCard: React.FC = () => { checked={folder.AI_Tagging} onCheckedChange={() => toggleAITagging(folder)} disabled={ - enableAITaggingPending || disableAITaggingPending + enableAITaggingPending || + disableAITaggingPending || + folder.exists === false } />
@@ -71,7 +81,11 @@ const FolderManagementCard: React.FC = () => { onClick={() => deleteFolder(folder.folder_id)} variant="outline" size="sm" - className="h-8 w-8 cursor-pointer text-gray-500 hover:border-red-300 hover:text-red-600 dark:text-gray-400 dark:hover:text-red-400" + className={ + isMissing + ? 'h-8 w-8 cursor-pointer border-red-300 bg-red-50 text-red-700 hover:border-red-400 hover:bg-red-100 hover:text-red-800 dark:border-red-900/50 dark:bg-red-900/20 dark:text-red-300 dark:hover:bg-red-900/30' + : 'h-8 w-8 cursor-pointer text-gray-500 hover:border-red-300 hover:text-red-600 dark:text-gray-400 dark:hover:text-red-400' + } disabled={deleteFolderPending} > @@ -113,8 +127,9 @@ const FolderManagementCard: React.FC = () => { />
)} -
- ))} +
+ ); + })} ) : (
diff --git a/frontend/src/types/Folder.ts b/frontend/src/types/Folder.ts index 34a9f9c58..f01565d9e 100644 --- a/frontend/src/types/Folder.ts +++ b/frontend/src/types/Folder.ts @@ -5,6 +5,7 @@ export interface FolderDetails { last_modified_time: number; AI_Tagging: boolean; taggingCompleted?: boolean; + exists?: boolean; } export interface GetAllFoldersData {