LinkSyncServer: - Fix app.py imports, add CORS middleware, lifespan events - Create api/routes.py router aggregator - Create config/settings.py for centralized configuration - Rewrite models/base.py with proper relationships and serialization - Rewrite all API endpoints with real DB integration (auth, links, collections, sync, queries, tags) - Add admin endpoints (user management, stats, audit log) - Complete query parser with recursive descent and proper precedence - Complete query executor with set operations and field filters - Set up Alembic migrations with initial schema - Create web interface (templates, CSS, JS) - Add 42 passing tests (auth, links, collections, queries) - Add deploy.ps1 and deploy.sh scripts - Update README with deployment workflow LinkSyncExtension: - Create utils/api.js (REST client with retries, auth, error handling) - Create utils/sync.js (3 sync modes + conflict detection) - Create utils/collection.js (collection management) - Create utils/query-engine.js (client-side query parser) - Rewrite background.js (sync loop, bookmark events, message routing) - Rewrite popup.js (tabs, settings modal, notifications, CRUD) - Update popup.html (tabbed interface, query builder, modal) - Update popup.css (full redesign) - Create content/content.js (page metadata extraction) - Create options.html/js (dedicated settings page) - Generate icons (48x48, 96x96) - Update manifest.json (host permissions, content scripts, options) - Create AGENTS.md
72 lines
2.2 KiB
Python
72 lines
2.2 KiB
Python
"""
|
|
LinkSyncServer - Query Engine Endpoints
|
|
"""
|
|
|
|
import uuid
|
|
from typing import Any, Dict, List, Optional
|
|
|
|
from fastapi import APIRouter, HTTPException
|
|
|
|
from models.base import Bookmark, get_session
|
|
from queries.executor import execute_query
|
|
from queries.parser import QueryParser
|
|
|
|
router = APIRouter(prefix="/api/queries", tags=["Queries"])
|
|
|
|
|
|
@router.post("/parse", response_model=Dict[str, Any])
|
|
async def parse_expression(expression: str):
|
|
try:
|
|
parser = QueryParser()
|
|
parsed = parser.parse(expression)
|
|
return {
|
|
"expression": expression,
|
|
"parsed": parsed,
|
|
"valid": True,
|
|
}
|
|
except Exception as e:
|
|
return {
|
|
"expression": expression,
|
|
"parsed": None,
|
|
"valid": False,
|
|
"error": str(e),
|
|
}
|
|
|
|
|
|
@router.post("/execute", response_model=List[dict])
|
|
async def execute(expression: str, limit: int = 20, offset: int = 0):
|
|
db = get_session()
|
|
try:
|
|
parser = QueryParser()
|
|
parsed = parser.parse(expression)
|
|
if not parsed:
|
|
raise HTTPException(status_code=400, detail="Invalid query expression")
|
|
|
|
all_bookmarks = db.query(Bookmark).all()
|
|
results = execute_query(parsed, [b.to_dict() for b in all_bookmarks])
|
|
return results[offset : offset + limit]
|
|
finally:
|
|
db.close()
|
|
|
|
|
|
@router.get("/{query_id}", response_model=Dict[str, Any])
|
|
async def get_saved_query(query_id: str):
|
|
db = get_session()
|
|
try:
|
|
from models.base import Collection
|
|
collection = db.query(Collection).filter(Collection.id == query_id).first()
|
|
if not collection or collection.query_type != "dynamic":
|
|
raise HTTPException(status_code=404, detail="Saved query not found")
|
|
return {
|
|
"id": collection.id,
|
|
"name": collection.name,
|
|
"description": collection.description,
|
|
"expression": collection.query_expression,
|
|
"query_type": collection.query_type,
|
|
"is_public": collection.is_public,
|
|
"created_at": collection.created_at.isoformat() if collection.created_at else None,
|
|
"updated_at": collection.updated_at.isoformat() if collection.updated_at else None,
|
|
}
|
|
finally:
|
|
db.close()
|