131 lines
4.3 KiB
Python
131 lines
4.3 KiB
Python
|
|
from fastapi import APIRouter, Depends, HTTPException, status
|
||
|
|
from sqlalchemy import and_, select
|
||
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||
|
|
|
||
|
|
from app.database import get_db
|
||
|
|
from app.models.remote_mount import RemoteMount
|
||
|
|
from app.models.user import User
|
||
|
|
from app.routers.auth import get_current_user
|
||
|
|
from app.schemas.remote_mount import (
|
||
|
|
RemoteMountCreate,
|
||
|
|
RemoteMountOut,
|
||
|
|
RemoteMountTreeOut,
|
||
|
|
RemoteNodeOut,
|
||
|
|
RemoteSyncRequest,
|
||
|
|
RemoteSyncResultOut,
|
||
|
|
)
|
||
|
|
from app.services.remote_sync_service import RemoteSyncService
|
||
|
|
from app.services.secret_service import encrypt_secret
|
||
|
|
from app.services.webdav_service import WebDavNode, WebDavService
|
||
|
|
|
||
|
|
router = APIRouter(prefix="/api/remote-mounts", tags=["远程挂载"])
|
||
|
|
|
||
|
|
|
||
|
|
def _to_node_out(node: WebDavNode) -> RemoteNodeOut:
|
||
|
|
return RemoteNodeOut(
|
||
|
|
path=node.path,
|
||
|
|
name=node.name,
|
||
|
|
is_dir=node.is_dir,
|
||
|
|
size=node.size,
|
||
|
|
modified_at=node.modified_at,
|
||
|
|
etag=node.etag,
|
||
|
|
children=[_to_node_out(child) for child in node.children],
|
||
|
|
)
|
||
|
|
|
||
|
|
|
||
|
|
@router.get("", response_model=list[RemoteMountOut])
|
||
|
|
async def list_remote_mounts(
|
||
|
|
db: AsyncSession = Depends(get_db),
|
||
|
|
current_user: User = Depends(get_current_user),
|
||
|
|
):
|
||
|
|
result = await db.execute(
|
||
|
|
select(RemoteMount).where(RemoteMount.user_id == current_user.id).order_by(RemoteMount.created_at.desc())
|
||
|
|
)
|
||
|
|
return list(result.scalars().all())
|
||
|
|
|
||
|
|
|
||
|
|
@router.post("", response_model=RemoteMountOut, status_code=status.HTTP_201_CREATED)
|
||
|
|
async def create_remote_mount(
|
||
|
|
payload: RemoteMountCreate,
|
||
|
|
db: AsyncSession = Depends(get_db),
|
||
|
|
current_user: User = Depends(get_current_user),
|
||
|
|
):
|
||
|
|
existing = await db.execute(
|
||
|
|
select(RemoteMount).where(and_(RemoteMount.user_id == current_user.id, RemoteMount.name == payload.name))
|
||
|
|
)
|
||
|
|
if existing.scalar_one_or_none():
|
||
|
|
raise HTTPException(status_code=400, detail="同名远程挂载已存在")
|
||
|
|
|
||
|
|
mount = RemoteMount(
|
||
|
|
user_id=current_user.id,
|
||
|
|
name=payload.name,
|
||
|
|
mount_type="webdav",
|
||
|
|
base_url=str(payload.base_url),
|
||
|
|
username=payload.username,
|
||
|
|
password_encrypted=encrypt_secret(payload.password),
|
||
|
|
root_path=payload.root_path,
|
||
|
|
is_active=True,
|
||
|
|
)
|
||
|
|
try:
|
||
|
|
await WebDavService(mount).list_directory(payload.root_path)
|
||
|
|
except Exception as exc: # noqa: BLE001
|
||
|
|
raise HTTPException(status_code=400, detail=f"WebDAV 连接失败: {exc}") from exc
|
||
|
|
|
||
|
|
db.add(mount)
|
||
|
|
await db.commit()
|
||
|
|
await db.refresh(mount)
|
||
|
|
return mount
|
||
|
|
|
||
|
|
|
||
|
|
async def _get_user_mount(db: AsyncSession, user_id: str, mount_id: str) -> RemoteMount:
|
||
|
|
result = await db.execute(
|
||
|
|
select(RemoteMount).where(and_(RemoteMount.id == mount_id, RemoteMount.user_id == user_id))
|
||
|
|
)
|
||
|
|
mount = result.scalar_one_or_none()
|
||
|
|
if mount is None:
|
||
|
|
raise HTTPException(status_code=404, detail="远程挂载不存在")
|
||
|
|
return mount
|
||
|
|
|
||
|
|
|
||
|
|
@router.get("/{mount_id}/tree", response_model=RemoteMountTreeOut)
|
||
|
|
async def get_remote_tree(
|
||
|
|
mount_id: str,
|
||
|
|
path: str | None = None,
|
||
|
|
db: AsyncSession = Depends(get_db),
|
||
|
|
current_user: User = Depends(get_current_user),
|
||
|
|
):
|
||
|
|
mount = await _get_user_mount(db, current_user.id, mount_id)
|
||
|
|
try:
|
||
|
|
nodes = await WebDavService(mount).list_tree(path or mount.root_path)
|
||
|
|
except Exception as exc: # noqa: BLE001
|
||
|
|
raise HTTPException(status_code=400, detail=f"远程目录读取失败: {exc}") from exc
|
||
|
|
|
||
|
|
return RemoteMountTreeOut(
|
||
|
|
mount_id=mount.id,
|
||
|
|
root_path=path or mount.root_path,
|
||
|
|
nodes=[_to_node_out(node) for node in nodes],
|
||
|
|
)
|
||
|
|
|
||
|
|
|
||
|
|
@router.post("/{mount_id}/sync", response_model=RemoteSyncResultOut)
|
||
|
|
async def sync_remote_mount(
|
||
|
|
mount_id: str,
|
||
|
|
payload: RemoteSyncRequest,
|
||
|
|
db: AsyncSession = Depends(get_db),
|
||
|
|
current_user: User = Depends(get_current_user),
|
||
|
|
):
|
||
|
|
mount = await _get_user_mount(db, current_user.id, mount_id)
|
||
|
|
try:
|
||
|
|
result = await RemoteSyncService(db, current_user.id).sync_remote_path(
|
||
|
|
mount,
|
||
|
|
payload.remote_path,
|
||
|
|
payload.local_folder_id,
|
||
|
|
payload.mode,
|
||
|
|
)
|
||
|
|
except ValueError as exc:
|
||
|
|
raise HTTPException(status_code=400, detail=str(exc)) from exc
|
||
|
|
except Exception as exc: # noqa: BLE001
|
||
|
|
raise HTTPException(status_code=500, detail=f"远程同步失败: {exc}") from exc
|
||
|
|
|
||
|
|
return RemoteSyncResultOut(**result)
|