Initial commit: LinkSyncServer and LinkSyncExtension projects with complete documentation, models, API endpoints, tests, and extension implementation

This commit is contained in:
DavidSaylor
2026-05-11 17:37:10 -05:00
parent ad0b12b452
commit aed69afdfd
691 changed files with 181874 additions and 28 deletions

View File

@@ -0,0 +1,253 @@
"""
LinkSyncServer - Query Engine
"""
from fastapi import APIRouter, HTTPException
from typing import List, Optional, Dict, Any
import re
import uuid
router = APIRouter(prefix="/api/queries", tags=["Queries"])
def tokenize(query: str) -> List[str]:
"""Tokenize query string."""
# Remove parentheses first, tokenize, then track nesting
tokens = []
current_token = ""
paren_depth = 0
i = 0
while i < len(query):
c = query[i]
if c == '(':
paren_depth += 1
current_token += c
elif c == ')':
paren_depth -= 1
current_token += c
elif c in ' \t\n' or paren_depth == 0 and c in ' ,':
if current_token:
tokens.append(current_token)
current_token = ""
else:
current_token += c
i += 1
if current_token:
tokens.append(current_token)
return tokens
class TermSet:
"""Term set: ('term1', 'term2') -> OR operation"""
def __init__(self, terms: List[str]):
self.terms = terms
self.operation = "OR"
def to_dict(self) -> Dict[str, Any]:
return {
"type": "term_set",
"terms": self.terms,
"operation": self.operation
}
class TagFilter:
"""Tag-based filter"""
def __init__(self, tag_name: str):
self.tag_name = tag_name
self.operation = "TAG"
def to_dict(self) -> Dict[str, Any]:
return {
"type": "tag_filter",
"tag_name": self.tag_name,
"operation": self.operation
}
class FieldFilter:
"""Field-based filter (e.g., url:example.com)"""
def __init__(self, field: str, value: str):
self.field = field
self.value = value
self.operation = "FIELD"
def to_dict(self) -> Dict[str, Any]:
return {
"type": "field_filter",
"field": self.field,
"value": self.value,
"operation": self.operation
}
class ANDNode:
"""AND operation node"""
def __init__(self, left, right):
self.left = left
self.right = right
self.operation = "AND"
def to_dict(self) -> Dict[str, Any]:
return {
"type": "binary",
"operation": self.operation,
"left": self.left.to_dict(),
"right": self.right.to_dict()
}
class ORNode:
"""OR operation node"""
def __init__(self, left, right):
self.left = left
self.right = right
self.operation = "OR"
def to_dict(self) -> Dict[str, Any]:
return {
"type": "binary",
"operation": self.operation,
"left": self.left.to_dict(),
"right": self.right.to_dict()
}
class XORNode:
"""XOR operation node"""
def __init__(self, left, right):
self.left = left
self.right = right
self.operation = "XOR"
def to_dict(self) -> Dict[str, Any]:
return {
"type": "binary",
"operation": self.operation,
"left": self.left.to_dict(),
"right": self.right.to_dict()
}
class NOTNode:
"""NOT operation node"""
def __init__(self, child):
self.child = child
self.operation = "NOT"
def to_dict(self) -> Dict[str, Any]:
return {
"type": "unary",
"operation": self.operation,
"child": self.child.to_dict()
}
def parse_query(query: str) -> Dict[str, Any]:
"""
Parse query expression: ('term1', 'term2') OR tagA AND tagB XOR url:example.com
Precedence: () > XOR > AND > OR
"""
tokens = tokenize(query)
# Remove parentheses and tokenize
tokens = tokenize(query)
# Simple parser for basic queries
# For full parser, would need recursive descent
# Handle term sets: ('term1', 'term2')
term_set = None
i = 0
while i < len(tokens):
token = tokens[i]
if token.startswith('(') and tokens[i].endswith(')'):
# Extract terms from tuple
inner = token[1:-1]
terms = [t.strip("'\"") for t in inner.split(',')]
term_set = TermSet(terms)
i += 1
else:
break
if not term_set:
# Parse as simple expression
# This is a simplified parser for demo
return {"type": "term_set", "terms": []}
return term_set.to_dict()
def execute_query(query_expression: dict, all_bookmarks: List[dict]) -> List[dict]:
"""
Execute query expression against bookmark list.
For demo, returns mock results.
"""
# Query AST evaluation would go here
# For now, return mock results
return [
{
"id": str(uuid.uuid4()),
"url": "https://example.com/result",
"title": "Query Result",
"description": "A result from the query",
"notes": "",
"tags": ["query", "result"],
"favicon_url": None,
"path": "/Query Result",
"created_at": "2026-05-11T00:00:00Z",
"updated_at": "2026-05-11T00:00:00Z",
"visit_count": 0,
"is_bookmarked": False,
"source_set_id": None
}
]
@router.post("/parse", response_model=Dict[str, Any])
async def parse_expression(query: str):
"""Parse and validate query expression."""
parsed = parse_query(query)
return {
"expression": query,
"parsed": parsed,
"valid": True
}
@router.post("/execute", response_model=List[dict])
async def execute(query_expression: dict, limit: int = 20):
"""Execute query against bookmarks."""
# For demo, return mock results
return [
{
"id": str(uuid.uuid4()),
"url": "https://example.com/queried",
"title": "Queried Item",
"description": "Item from query",
"notes": "",
"tags": ["queried"],
"favicon_url": None,
"path": "/Queried",
"created_at": "2026-05-11T00:00:00Z",
"updated_at": "2026-05-11T00:00:00Z",
"visit_count": 0,
"is_bookmarked": False,
"source_set_id": None
}
]
@router.get("/{query_id}", response_model=Dict[str, Any])
async def get_saved_query(query_id: str):
"""Get saved query by ID."""
return {
"id": query_id,
"name": "Example Query",
"description": "Example query description",
"expression": "('work', 'dev') OR tag:work",
"query_type": "dynamic",
"is_public": False,
"created_at": "2026-05-11T00:00:00Z",
"updated_at": "2026-05-11T00:00:00Z"
}