150 lines
4.1 KiB
Python
150 lines
4.1 KiB
Python
"""
|
|
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"} |