05/18/2026 Catchup - linksync project work and TicTacToe evaluations on different coding LLMs with OpenCode.
This commit is contained in:
151
LinkSyncServer/api/v1/sync.py
Normal file
151
LinkSyncServer/api/v1/sync.py
Normal file
@@ -0,0 +1,151 @@
|
||||
"""
|
||||
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))
|
||||
Reference in New Issue
Block a user