The frontend AddSourceFolderDialog let users register source roots from inside the app, but with the bootstrap auto-creating one for the /photos mount on first boot, the dialog was redundant in the common case and confusing in every other (users had to know which container path corresponded to their host directory). Going config-driven matches Plex/Photoprism/Immich and matches the mental model "the docker mount IS the library". Frontend - Deleted components/dialogs/AddSourceFolderDialog.tsx entirely. - LeftSidebar drops the "+ Add Source Folder" button + bottom-bar layout, the addFolderMutation, the dead Plus action button on the (no-longer-existing) folders/heaps tree headers, and the Plus icon import. - api.ts: removed sourceFolders.add(), library.browse(), and the BrowseChild / BrowseResponse types. The remaining sourceFolders surface is read-only (list + manual scan). - LeftSidebar bottom strip is now just the "Scan all folders" button when there's at least one source root. Backend - Dropped POST /folders (no consumers) along with FolderCreate / FolderResponse pydantic models. The folders router header now documents the config-driven approach. - Dropped GET /library/browse (no consumers). Removed the unused os/HTTPException/SourceRoot imports it brought in. - cleanup_data_integrity now also walks the source roots and logs a warning for any whose path is missing on disk. Doesn't auto- delete (a missing path could be a temporarily unmounted drive) but surfaces enough hint to fix it. Returns the count in the summary dict alongside merged-duplicates. Docs - README "How libraries are managed" section rewritten to spell out that mounts ARE source roots, edit .env + restart, no UI for managing source roots. New "Changing or adding libraries" section walks through the typical edit-restart loop including the optional volume-nuke for a clean slate. - "Adding more libraries" subsection covers multi-mount via edited compose with a note that auto-registration is roadmap. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
55 lines
2.1 KiB
Python
55 lines
2.1 KiB
Python
"""
|
|
Folders API router. Source roots are config-driven (PHOTO_DIRS in .env →
|
|
backend bootstrap on startup); this router only exposes read access and a
|
|
manual rescan trigger. Adding/removing source roots happens by editing
|
|
docker-compose.yml + .env and restarting the stack.
|
|
"""
|
|
from fastapi import APIRouter, Depends, HTTPException
|
|
from sqlalchemy import select
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
import os
|
|
|
|
from app.database import get_db
|
|
from app.models import Folder, SourceRoot
|
|
|
|
router = APIRouter()
|
|
|
|
@router.get("")
|
|
async def get_folders(db: AsyncSession = Depends(get_db)):
|
|
"""Get all source folders"""
|
|
# Get source roots instead of regular folders
|
|
result = await db.execute(select(SourceRoot).where(SourceRoot.is_active == True))
|
|
source_roots = result.scalars().all()
|
|
|
|
folders_list = []
|
|
for root in source_roots:
|
|
# Get photo count for this source root
|
|
folder_result = await db.execute(
|
|
select(Folder).where(Folder.source_root_id == root.id)
|
|
)
|
|
folders = folder_result.scalars().all()
|
|
photo_count = sum(f.photo_count for f in folders)
|
|
|
|
folders_list.append({
|
|
"id": root.id,
|
|
"name": root.name or os.path.basename(root.path),
|
|
"path": root.path,
|
|
"photo_count": photo_count
|
|
})
|
|
|
|
return {"folders": folders_list}
|
|
|
|
@router.post("/{folder_id}/scan")
|
|
async def scan_folder(folder_id: str, db: AsyncSession = Depends(get_db)):
|
|
"""Trigger manual re-scan of source root folder"""
|
|
from app.tasks.celery import celery_app
|
|
|
|
result = await db.execute(select(SourceRoot).where(SourceRoot.id == folder_id))
|
|
source_root = result.scalar_one_or_none()
|
|
|
|
if not source_root:
|
|
raise HTTPException(status_code=404, detail="Source folder not found")
|
|
|
|
# Queue scan task using the task name defined in the decorator
|
|
task = celery_app.send_task('scan_folder', args=[source_root.path, source_root.id])
|
|
return {"status": "success", "message": f"Scan queued for {source_root.path}", "task_id": task.id} |