Files
myworkspace/LinkSyncServer/api/v1/sync.py

152 lines
4.3 KiB
Python

"""
LinkSyncServer - Sync Endpoint for Browser Extension
"""
from fastapi import APIRouter, HTTPException, status
from typing import List, Dict
import jwt
import logging
from datetime import datetime
import json
from models.base import Bookmark, Collection, get_engine
from api.parsers.bookmarks import BookmarkParser
from api.parsers.sync import SyncParser
import os
router = APIRouter(prefix="/api/v1/sync", tags=["Sync"])
logger = logging.getLogger(__name__)
# Get database and secrets
DATABASE_URL = os.environ.get("DATABASE_URL", "sqlite:///links.db")
SECRET_KEY = os.environ.get("SECRET_KEY", "fallback-for-dev")
# Initialize parser
bookmark_parser = BookmarkParser()
sync_parser = SyncParser()
def get_db_session():
"""Get database session."""
from sqlalchemy.pool import StaticPool
from sqlalchemy.orm import Session
from sqlalchemy import create_engine
engine = create_engine(
DATABASE_URL,
connect_args={'check_same_thread': False}
)
return Session(engine)
def validate_request_token(request_token: str) -> Dict:
"""
Validate sync request token.
Accepts:
- Token header from extension
- No auth for demo/maintenance
"""
if not request_token:
# Allow anonymous for demo
return {"type": "anonymous", "permissions": {}}
try:
# Try to decode as JWT
payload = jwt.decode(request_token, SECRET_KEY, algorithms=["HS256"])
# Check permissions
permissions = {
"collections": payload.get("permissions", {}).get("collections", []),
"bookmarks": payload.get("permissions", {}).get("bookmarks", [])
}
return {
"type": "authorized",
"permissions": permissions
}
except Exception:
# Token invalid, fall back to anonymous
return {"type": "anonymous", "permissions": {}}
def sync_with_github(account_id: str, collection_id: str, request_token: str) -> Dict:
"""
Sync bookmarks from GitHub to local collection.
Args:
account_id: GitHub account ID
collection_id: LinkSync collection ID
request_token: Token from extension request
Returns:
Sync response (JSON payload for extension)
"""
# Validate token
token_info = validate_request_token(request_token)
if token_info["type"] != "authorized":
raise HTTPException(status_code=403, detail="Unauthorized access")
# Get collection
db = get_db_session()
collection = db.query(Collection).filter(Collection.id == collection_id).first()
if not collection:
raise HTTPException(status_code=404, detail="Collection not found")
# Make request to GitHub API (using library or requests)
try:
# GitHub API v3
# GET /users/{user_id}/starred
# Response: list of starred repositories and Gists (links)
github_api_base = "https://api.github.com"
starred_response = requests.get(
f"{github_api_base}/users/{account_id}/starred",
headers={
"Accept": "application/vnd.github.v3+json"
}
)
if starred_response.status_code != 200:
raise HTTPException(status_code=502, detail="Failed to fetch GitHub data")
github_links = starred_response.json()
# Parse GitHub data
github_bookmarks = sync_parser.parse_github_links(github_links)
# Create/update/delete based on sync
changes = bookmark_parser.parse_sync(
github_bookmarks, collection_id
)
# Commit changes
db.commit()
# Build response
sync_response = {
"_links": {
"sync": {
"_links": {
"self": {}
}
}
},
"meta": {
"account_id": account_id,
"collections": [collection_id],
"changes": changes,
"total_synced": len(github_links)
}
}
return sync_response
except Exception as e:
logger.error(f"Sync error: {e}")
raise HTTPException(status_code=500, detail=str(e))