from fastapi import APIRouter, Depends, Request from sqlalchemy.orm import Session from app.database import get_db from app.models import SyncSettings from app.schemas.sync import ( SyncConfigUpdate, SyncConfigResponse, SyncStatusResponse, SyncTestResponse, SyncOperationResponse, ) from app.utils.crypto import encrypt, decrypt from app.utils.webdav import WebDAVClient from app.utils.sync_service import push_to_remote, pull_from_remote, bidirectional_sync from app.utils.sync_lock import is_syncing router = APIRouter(prefix="/api/sync", tags=["同步"]) def _get_or_create_settings(db: Session) -> SyncSettings: settings = db.query(SyncSettings).filter(SyncSettings.id == 1).first() if not settings: settings = SyncSettings(id=1) db.add(settings) db.commit() db.refresh(settings) return settings @router.get("/config", response_model=SyncConfigResponse) def get_sync_config(request: Request, db: Session = Depends(get_db)): settings = _get_or_create_settings(db) response = SyncConfigResponse( webdav_url=settings.webdav_url, webdav_username=settings.webdav_username, webdav_password="***" if settings.webdav_password else None, webdav_path=settings.webdav_path or "/elysia-todo/", sync_enabled=settings.sync_enabled, auto_sync=settings.auto_sync, auto_sync_interval=settings.auto_sync_interval or 300, last_sync_at=settings.last_sync_at, last_sync_version=settings.last_sync_version or 0, ) return response @router.put("/config", response_model=SyncConfigResponse) def update_sync_config(config: SyncConfigUpdate, request: Request, db: Session = Depends(get_db)): settings = _get_or_create_settings(db) if config.webdav_url is not None: settings.webdav_url = config.webdav_url if config.webdav_username is not None: settings.webdav_username = config.webdav_username if config.webdav_password is not None and config.webdav_password != "***": settings.webdav_password = encrypt(config.webdav_password) if config.webdav_path is not None: settings.webdav_path = config.webdav_path if config.auto_sync is not None: settings.auto_sync = config.auto_sync if config.auto_sync_interval is not None: settings.auto_sync_interval = config.auto_sync_interval db.commit() db.refresh(settings) return SyncConfigResponse( webdav_url=settings.webdav_url, webdav_username=settings.webdav_username, webdav_password="***" if settings.webdav_password else None, webdav_path=settings.webdav_path or "/elysia-todo/", sync_enabled=settings.sync_enabled, auto_sync=settings.auto_sync, auto_sync_interval=settings.auto_sync_interval or 300, last_sync_at=settings.last_sync_at, last_sync_version=settings.last_sync_version or 0, ) @router.post("/test", response_model=SyncTestResponse) def test_connection(request: Request, db: Session = Depends(get_db)): settings = _get_or_create_settings(db) if not settings.webdav_url: return SyncTestResponse(success=False, message="未配置 WebDAV 地址") password = decrypt(settings.webdav_password) if settings.webdav_password else "" if settings.webdav_password and password is None: return SyncTestResponse(success=False, message="WebDAV 密码解密失败,请重新配置") client = WebDAVClient( url=settings.webdav_url, username=settings.webdav_username or "", password=password or "", path=settings.webdav_path or "/elysia-todo/", ) success, message = client.test_connection() return SyncTestResponse(success=success, message=message) @router.post("/push", response_model=SyncOperationResponse) def sync_push(request: Request, db: Session = Depends(get_db)): result = push_to_remote(db) return SyncOperationResponse(**result) @router.post("/pull", response_model=SyncOperationResponse) def sync_pull(request: Request, db: Session = Depends(get_db)): result = pull_from_remote(db) return SyncOperationResponse(**result) @router.post("/sync", response_model=SyncOperationResponse) def sync_bidirectional(request: Request, db: Session = Depends(get_db)): result = bidirectional_sync(db) return SyncOperationResponse(**result) @router.get("/status", response_model=SyncStatusResponse) def get_sync_status(request: Request, db: Session = Depends(get_db)): settings = _get_or_create_settings(db) return SyncStatusResponse( syncing=is_syncing(), last_sync_at=settings.last_sync_at, last_sync_version=settings.last_sync_version or 0, sync_enabled=settings.sync_enabled, ) @router.delete("/remote", response_model=SyncOperationResponse) def clear_remote(request: Request, db: Session = Depends(get_db)): from app.utils.webdav import WebDAVClient from app.utils.crypto import decrypt settings = _get_or_create_settings(db) if not settings.webdav_url: return SyncOperationResponse(success=False, message="未配置 WebDAV 地址") password = decrypt(settings.webdav_password) if settings.webdav_password else "" if settings.webdav_password and password is None: return SyncOperationResponse(success=False, message="WebDAV 密码解密失败,请重新配置") client = WebDAVClient( url=settings.webdav_url, username=settings.webdav_username or "", password=password or "", path=settings.webdav_path or "/elysia-todo/", ) success = client.clear_remote() if success: return SyncOperationResponse(success=True, message="远端数据已清空") return SyncOperationResponse(success=False, message="清空远端数据失败")