diff --git a/services/library-desk/src/core/dependencies.py b/services/library-desk/src/core/dependencies.py index 7a36f15..1aff0b5 100644 --- a/services/library-desk/src/core/dependencies.py +++ b/services/library-desk/src/core/dependencies.py @@ -73,12 +73,13 @@ def get_wikijs_client() -> WikiJSClient: Get Wiki.js client singleton. Returns: - Initialized Wiki.js GraphQL client + Initialized Wiki.js GraphQL client with username/password auth """ settings = get_settings() client = WikiJSClient( 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") return client @@ -255,9 +256,8 @@ async def check_service_health() -> dict: # SearXNG try: searxng = get_searxng_client() - # Try a simple search - await searxng.search_general("test", limit=1) - health["searxng"] = True + # Just check if service is up (no actual search) + health["searxng"] = await searxng.health_check() except Exception as e: logger.error(f"SearXNG health check failed: {e}") health["searxng"] = False @@ -274,6 +274,72 @@ async def check_service_health() -> dict: 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 def get_default_user() -> str: """ @@ -284,3 +350,35 @@ def get_default_user() -> str: """ from src.core.multi_tenancy import 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 diff --git a/services/library-desk/src/main.py b/services/library-desk/src/main.py index 7146b62..e1773e8 100644 --- a/services/library-desk/src/main.py +++ b/services/library-desk/src/main.py @@ -8,14 +8,16 @@ Following best practices: - OpenAPI documentation """ -from fastapi import FastAPI, HTTPException, Depends, Security -from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials +from fastapi import FastAPI, HTTPException, Depends from fastapi.middleware.cors import CORSMiddleware +from fastapi.staticfiles import StaticFiles from pydantic import BaseModel from typing import Dict, Any import logging +from pathlib import Path from src.config import Settings, get_settings +from src.core.dependencies import verify_api_key # Configure logging logging.basicConfig( @@ -24,9 +26,6 @@ logging.basicConfig( ) logger = logging.getLogger(__name__) -# Security -security = HTTPBearer() - # Initialize FastAPI app app = FastAPI( title="Library Desk API", @@ -45,22 +44,23 @@ app.add_middleware( allow_headers=["*"], ) +# Register routers +from src.routers import wiki, tools, graph, vector, hybrid_rag, consolidation, ingestion, entity_linking -# Dependencies -async def verify_api_key( - credentials: HTTPAuthorizationCredentials = Security(security), - settings: Settings = Depends(get_settings) -) -> str: - """ - Verify API key from Bearer token. - Following best practice: use dependencies for validation. - """ - if credentials.credentials != settings.library_api_key: - raise HTTPException( - status_code=403, - detail="Invalid API key" - ) - return credentials.credentials +app.include_router(wiki.router) +app.include_router(tools.router) +app.include_router(graph.router) +app.include_router(vector.router) +app.include_router(hybrid_rag.router) +app.include_router(consolidation.router) +app.include_router(ingestion.router) +app.include_router(entity_linking.router) + +# Mount static files directory for Wiki.js integration scripts +static_dir = Path(__file__).parent.parent / "static" +if static_dir.exists(): + app.mount("/static", StaticFiles(directory=str(static_dir)), name="static") + logger.info(f"Mounted static files from {static_dir}") # Response Models @@ -264,22 +264,7 @@ async def get_repo_status( # Query endpoints (stubs for future implementation) -@app.post("/query/hybrid", tags=["Query"]) -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 - } - +# NOTE: /query/hybrid is now implemented in routers/hybrid_rag.py @app.post("/query/semantic", tags=["Query"]) async def semantic_query(