feat(library-desk): integrate entity linking into main app

Main App:
- Mount /static directory for serving Wiki.js integration scripts
- Register entity_linking router
- Refactor API key verification to dependencies module

Dependencies:
- Add service factory functions for all services
- Add get_wiki_service() for wiki operations
- Add get_graph_service() for entity operations
- Add get_ingestion_service() for auto entity linking
- Improve health check for SearXNG
This commit is contained in:
2025-12-10 01:18:40 +01:00
parent d80f063186
commit 7354a66a3d
2 changed files with 124 additions and 41 deletions
+103 -5
View File
@@ -73,12 +73,13 @@ def get_wikijs_client() -> WikiJSClient:
Get Wiki.js client singleton. Get Wiki.js client singleton.
Returns: Returns:
Initialized Wiki.js GraphQL client Initialized Wiki.js GraphQL client with username/password auth
""" """
settings = get_settings() settings = get_settings()
client = WikiJSClient( client = WikiJSClient(
base_url=settings.wikijs_url, base_url=settings.wikijs_url,
api_key=settings.wikijs_api_key username=settings.wikijs_username,
password=settings.wikijs_password
) )
logger.debug("Created Wiki.js client instance") logger.debug("Created Wiki.js client instance")
return client return client
@@ -255,9 +256,8 @@ async def check_service_health() -> dict:
# SearXNG # SearXNG
try: try:
searxng = get_searxng_client() searxng = get_searxng_client()
# Try a simple search # Just check if service is up (no actual search)
await searxng.search_general("test", limit=1) health["searxng"] = await searxng.health_check()
health["searxng"] = True
except Exception as e: except Exception as e:
logger.error(f"SearXNG health check failed: {e}") logger.error(f"SearXNG health check failed: {e}")
health["searxng"] = False health["searxng"] = False
@@ -274,6 +274,72 @@ async def check_service_health() -> dict:
return health return health
# Service factory functions
@lru_cache
def get_vector_service() -> "VectorService":
"""Get VectorService singleton."""
from src.services.vector_service import VectorService
return VectorService(
qdrant_client=get_qdrant_client(),
wikijs_client=get_wikijs_client(),
ollama_client=get_ollama_client()
)
@lru_cache
def get_graph_service() -> "GraphService":
"""Get GraphService singleton."""
from src.services.graph_service import GraphService
return GraphService(
neo4j_client=get_neo4j_client(),
wikijs_client=get_wikijs_client()
)
@lru_cache
def get_wiki_service() -> "WikiService":
"""Get WikiService singleton."""
from src.services.wiki_service import WikiService
return WikiService(wiki_client=get_wikijs_client())
@lru_cache
def get_consolidation_service() -> "ConsolidationService":
"""Get ConsolidationService singleton."""
from src.services.consolidation_service import ConsolidationService
return ConsolidationService(
neo4j=get_neo4j_client(),
ollama=get_ollama_client(),
wiki=get_wikijs_client(),
settings=get_settings(),
ingestion_service=get_ingestion_service()
)
@lru_cache
def get_ingestion_service() -> "IngestionService":
"""Get IngestionService singleton."""
from src.services.ingestion_service import IngestionService
return IngestionService(
vector_service=get_vector_service(),
graph_service=get_graph_service(),
wiki_client=get_wikijs_client()
)
@lru_cache
def get_hybrid_rag_service() -> "HybridRAGService":
"""Get HybridRAGService singleton."""
from src.services.hybrid_rag_service import HybridRAGService
return HybridRAGService(
vector_service=get_vector_service(),
graph_service=get_graph_service(),
searxng_client=get_searxng_client(),
ollama_client=get_ollama_client(),
settings=get_settings()
)
# Utility: Get default user from settings or multi_tenancy # Utility: Get default user from settings or multi_tenancy
def get_default_user() -> str: def get_default_user() -> str:
""" """
@@ -284,3 +350,35 @@ def get_default_user() -> str:
""" """
from src.core.multi_tenancy import DEFAULT_USER from src.core.multi_tenancy import DEFAULT_USER
return DEFAULT_USER return DEFAULT_USER
# Authentication
from fastapi import Security, HTTPException
from fastapi.security import HTTPBearer
security = HTTPBearer()
async def verify_api_key(
credentials: Annotated[HTTPBearer, Security(security)],
settings: SettingsDep
) -> str:
"""
Verify API key from Bearer token.
Args:
credentials: HTTP Bearer credentials
settings: Application settings
Returns:
API key if valid
Raises:
HTTPException: If API key is invalid
"""
if credentials.credentials != settings.library_api_key:
raise HTTPException(
status_code=403,
detail="Invalid API key"
)
return credentials.credentials
+21 -36
View File
@@ -8,14 +8,16 @@ Following best practices:
- OpenAPI documentation - OpenAPI documentation
""" """
from fastapi import FastAPI, HTTPException, Depends, Security from fastapi import FastAPI, HTTPException, Depends
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
from fastapi.middleware.cors import CORSMiddleware from fastapi.middleware.cors import CORSMiddleware
from fastapi.staticfiles import StaticFiles
from pydantic import BaseModel from pydantic import BaseModel
from typing import Dict, Any from typing import Dict, Any
import logging import logging
from pathlib import Path
from src.config import Settings, get_settings from src.config import Settings, get_settings
from src.core.dependencies import verify_api_key
# Configure logging # Configure logging
logging.basicConfig( logging.basicConfig(
@@ -24,9 +26,6 @@ logging.basicConfig(
) )
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
# Security
security = HTTPBearer()
# Initialize FastAPI app # Initialize FastAPI app
app = FastAPI( app = FastAPI(
title="Library Desk API", title="Library Desk API",
@@ -45,22 +44,23 @@ app.add_middleware(
allow_headers=["*"], allow_headers=["*"],
) )
# Register routers
from src.routers import wiki, tools, graph, vector, hybrid_rag, consolidation, ingestion, entity_linking
# Dependencies app.include_router(wiki.router)
async def verify_api_key( app.include_router(tools.router)
credentials: HTTPAuthorizationCredentials = Security(security), app.include_router(graph.router)
settings: Settings = Depends(get_settings) app.include_router(vector.router)
) -> str: app.include_router(hybrid_rag.router)
""" app.include_router(consolidation.router)
Verify API key from Bearer token. app.include_router(ingestion.router)
Following best practice: use dependencies for validation. app.include_router(entity_linking.router)
"""
if credentials.credentials != settings.library_api_key: # Mount static files directory for Wiki.js integration scripts
raise HTTPException( static_dir = Path(__file__).parent.parent / "static"
status_code=403, if static_dir.exists():
detail="Invalid API key" app.mount("/static", StaticFiles(directory=str(static_dir)), name="static")
) logger.info(f"Mounted static files from {static_dir}")
return credentials.credentials
# Response Models # Response Models
@@ -264,22 +264,7 @@ async def get_repo_status(
# Query endpoints (stubs for future implementation) # Query endpoints (stubs for future implementation)
@app.post("/query/hybrid", tags=["Query"]) # NOTE: /query/hybrid is now implemented in routers/hybrid_rag.py
async def hybrid_query(
query: Dict[str, Any],
api_key: str = Depends(verify_api_key)
) -> Dict[str, Any]:
"""
HybridRAG query endpoint.
Combines Neo4j (structure) + Qdrant (semantics) + SearXNG (web).
TODO: Implement HybridRAG logic
"""
return {
"message": "HybridRAG not yet implemented",
"query": query
}
@app.post("/query/semantic", tags=["Query"]) @app.post("/query/semantic", tags=["Query"])
async def semantic_query( async def semantic_query(