""" LinkSyncServer - Sync Endpoint for Browser Extension """ from fastapi import APIRouter, Depends, HTTPException, status from typing import List, Dict, Any import uuid router = APIRouter(prefix="/api/sync", tags=["Sync"]) class SyncConfig(BaseModel): mode: str # "bi-directional", "browser-authoritative", "server-authoritative" deletions_enabled: bool = False class BookmarkData(BaseModel): id: str url: str title: str description: str notes: str tags: List[str] favicon_url: str path: str visit_count: int is_bookmarked: bool class SyncResponse(BaseModel): actions: List[Dict[str, Any]] synced_count: int def mock_apply_sync(sync_config: SyncConfig, browser_bookmarks: List[Dict]) -> SyncResponse: """ Apply sync based on mode. For demo, return mock actions. """ actions = [] for bookmark in browser_bookmarks: if sync_config.mode == "bi-directional": actions.append({ "type": "create" if not bookmark.get("from_server", False) else "update", "link_id": bookmark["id"], "message": "Synced from browser" }) elif sync_config.mode == "browser-authoritative": actions.append({ "type": "update", "link_id": bookmark["id"], "message": "Overwritten from browser" }) elif sync_config.mode == "server-authoritative": actions.append({ "type": "download", "link_id": bookmark["id"], "message": "Downloaded from server" }) # If deletions enabled, would remove stale bookmarks here return SyncResponse( actions=actions, synced_count=len(actions) ) def mock_get_server_bookmarks() -> List[Dict]: """Get bookmarks from server (mock).""" return [ { "id": str(uuid.uuid4()), "url": "https://example.com/example", "title": "Example", "description": "An example", "notes": "", "tags": ["example"], "favicon_url": None, "path": "/Example", "visit_count": 0, "is_bookmarked": False } ] @router.post("/", response_model=SyncResponse) async def sync( config: SyncConfig, browser_bookmarks: List[BookmarkData], server_bookmarks: List[Dict] = Depends(mock_get_server_bookmarks) ): """ Sync bookmarks between browser and server. Mode options: - bi-directional: Push both ways - browser-authoritative: Browser overwrites server - server-authoritative: Download from server only """ response = mock_apply_sync(config, [b.model_dump() for b in browser_bookmarks]) return response @router.get("/collections") async def list_collections(): """List user's collections.""" return [ { "id": str(uuid.uuid4()), "name": "Work Links", "description": "Work-related links", "query_type": "dynamic", "query_expression": {"operation": "OR", "operands": [{"operation": "TERM", "value": "work"}]}, "is_public": False } ] @router.get("/collections/{collection_id}") async def get_collection(collection_id: str): """Get collection details.""" return { "id": collection_id, "name": "Work Links", "description": "Work-related links", "query_type": "dynamic", "query_expression": {"operation": "OR", "operands": [{"operation": "TERM", "value": "work"}]}, "is_public": False } @router.post("/collections/{collection_id}/add-links") async def add_links_to_collection( collection_id: str, bookmark_ids: List[str] ): """Add links to static collection.""" return { "collection_id": collection_id, "added_count": len(bookmark_ids), "message": "Links added successfully" } @router.delete("/collections/{collection_id}") async def delete_collection(collection_id: str): """Delete collection.""" return {"message": "Collection deleted successfully"}