diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 00000000..fedcf2ee --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,93 @@ +name: CI + +on: + push: + branches: [ main, develop ] + pull_request: + branches: [ main, develop ] + +jobs: + test: + runs-on: ubuntu-latest + strategy: + matrix: + python-version: [3.9, 3.10, 3.11] + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v4 + with: + python-version: ${{ matrix.python-version }} + + - name: Cache pip dependencies + uses: actions/cache@v3 + with: + path: ~/.cache/pip + key: ${{ runner.os }}-pip-${{ hashFiles('backend/requirements.txt') }} + restore-keys: | + ${{ runner.os }}-pip- + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + cd backend + pip install -r requirements.txt + + - name: Smoke test - Verify module imports + run: | + export PYTHONPATH=backend + python - <<'PY' + # Test imports for relocated modules + try: + from app.agents.state import AgentState + print("AgentState import successful") + except ImportError as e: + print(f"AgentState import failed: {e}") + exit(1) + + try: + from app.agents.devrel.nodes.summarization import store_summary_to_database + print("store_summary_to_database import successful") + except ImportError as e: + print(f"store_summary_to_database import failed: {e}") + exit(1) + + print("🎉 All smoke test imports successful!") + PY + + - name: Run tests + run: | + export PYTHONPATH=backend + cd backend + # Add actual test commands here when tests are available + # python -m pytest tests/ -v + echo "Test placeholder - add actual test commands when available" + + lint: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: 3.11 + + - name: Install linting dependencies + run: | + python -m pip install --upgrade pip + pip install flake8 black isort + + - name: Run linting checks + run: | + cd backend + # Check code formatting + black --check --diff . + # Check import sorting + isort --check-only --diff . + # Run flake8 linting + flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics \ No newline at end of file diff --git a/backend/app/agents/__init__.py b/backend/app/agents/__init__.py index f846d24e..a48cce4b 100644 --- a/backend/app/agents/__init__.py +++ b/backend/app/agents/__init__.py @@ -1,6 +1,6 @@ from .devrel.agent import DevRelAgent -from .shared.base_agent import BaseAgent, AgentState -from .shared.classification_router import ClassificationRouter +from .base_agent import BaseAgent, AgentState +from .classification_router import ClassificationRouter __all__ = [ "DevRelAgent", diff --git a/backend/app/agents/shared/base_agent.py b/backend/app/agents/base_agent.py similarity index 100% rename from backend/app/agents/shared/base_agent.py rename to backend/app/agents/base_agent.py diff --git a/backend/app/agents/shared/classification_router.py b/backend/app/agents/classification_router.py similarity index 100% rename from backend/app/agents/shared/classification_router.py rename to backend/app/agents/classification_router.py diff --git a/backend/app/agents/devrel/__init__.py b/backend/app/agents/devrel/__init__.py index e69de29b..8b137891 100644 --- a/backend/app/agents/devrel/__init__.py +++ b/backend/app/agents/devrel/__init__.py @@ -0,0 +1 @@ + diff --git a/backend/app/agents/devrel/agent.py b/backend/app/agents/devrel/agent.py index c94fe211..d3733db3 100644 --- a/backend/app/agents/devrel/agent.py +++ b/backend/app/agents/devrel/agent.py @@ -4,18 +4,18 @@ from langgraph.graph import StateGraph, END from langchain_google_genai import ChatGoogleGenerativeAI from langgraph.checkpoint.memory import InMemorySaver -from ..shared.base_agent import BaseAgent, AgentState -from ..shared.classification_router import MessageCategory +from ..base_agent import BaseAgent, AgentState +from ..classification_router import MessageCategory from .tools.search_tool import TavilySearchTool from .tools.faq_tool import FAQTool from app.core.config import settings -from .nodes.gather_context_node import gather_context_node -from .nodes.handle_faq_node import handle_faq_node -from .nodes.handle_web_search_node import handle_web_search_node -from .nodes.handle_technical_support_node import handle_technical_support_node -from .nodes.handle_onboarding_node import handle_onboarding_node -from .nodes.generate_response_node import generate_response_node -from .nodes.summarization_node import check_summarization_needed, summarize_conversation_node, store_summary_to_database +from .nodes.gather_context import gather_context_node +from .nodes.handlers.faq import handle_faq_node +from .nodes.handlers.web_search import handle_web_search_node +from .nodes.handlers.technical_support import handle_technical_support_node +from .nodes.handlers.onboarding import handle_onboarding_node +from .generate_response_node import generate_response_node +from .nodes.summarization import check_summarization_needed, summarize_conversation_node, store_summary_to_database logger = logging.getLogger(__name__) diff --git a/backend/app/agents/devrel/nodes/generate_response_node.py b/backend/app/agents/devrel/generate_response_node.py similarity index 75% rename from backend/app/agents/devrel/nodes/generate_response_node.py rename to backend/app/agents/devrel/generate_response_node.py index 503ebb95..a83bcb5f 100644 --- a/backend/app/agents/devrel/nodes/generate_response_node.py +++ b/backend/app/agents/devrel/generate_response_node.py @@ -1,28 +1,12 @@ import logging from typing import Dict, Any -from app.agents.shared.state import AgentState +from app.agents.state import AgentState from langchain_core.messages import HumanMessage -from ..prompts.base_prompt import GENERAL_LLM_RESPONSE_PROMPT +from .prompts.base_prompt import GENERAL_LLM_RESPONSE_PROMPT +from .nodes.handlers.web_search import create_search_response logger = logging.getLogger(__name__) -async def _create_search_response(task_result: Dict[str, Any]) -> str: - """Create a response string from search results.""" - query = task_result.get("query") - results = task_result.get("results", []) - if not results: - return f"I couldn't find any information for '{query}'. You might want to try rephrasing your search." - - response_parts = [f"Here's what I found for '{query}':"] - for i, result in enumerate(results[:3]): - title = result.get('title', 'N/A') - snippet = result.get('snippet', 'N/A') - url = result.get('url', '#') - result_line = f"{i+1}. {title}: {snippet}" - response_parts.append(result_line) - response_parts.append(f" (Source: {url})") - response_parts.append("You can ask me to search again with a different query if these aren't helpful.") - return "\n".join(response_parts) async def _create_llm_response(state: AgentState, task_result: Dict[str, Any], llm) -> str: """Generate a response using the LLM based on the current state and task result.""" @@ -89,7 +73,7 @@ async def generate_response_node(state: AgentState, llm) -> dict: if task_result.get("type") == "faq": final_response = task_result.get("response", "I don't have a specific answer for that question.") elif task_result.get("type") == "web_search": - final_response = await _create_search_response(task_result) + final_response = create_search_response(task_result) else: final_response = await _create_llm_response(state, task_result, llm) diff --git a/backend/app/agents/devrel/nodes/gather_context_node.py b/backend/app/agents/devrel/nodes/gather_context.py similarity index 90% rename from backend/app/agents/devrel/nodes/gather_context_node.py rename to backend/app/agents/devrel/nodes/gather_context.py index f7fbc8e3..0a33de68 100644 --- a/backend/app/agents/devrel/nodes/gather_context_node.py +++ b/backend/app/agents/devrel/nodes/gather_context.py @@ -1,7 +1,6 @@ import logging from datetime import datetime -from app.agents.shared.state import AgentState -from app.agents.shared.classification_router import MessageCategory +from app.agents.state import AgentState logger = logging.getLogger(__name__) diff --git a/backend/app/agents/shared/__init__.py b/backend/app/agents/devrel/nodes/handlers/__init__.py similarity index 100% rename from backend/app/agents/shared/__init__.py rename to backend/app/agents/devrel/nodes/handlers/__init__.py diff --git a/backend/app/agents/devrel/nodes/handle_faq_node.py b/backend/app/agents/devrel/nodes/handlers/faq.py similarity index 94% rename from backend/app/agents/devrel/nodes/handle_faq_node.py rename to backend/app/agents/devrel/nodes/handlers/faq.py index e6b2aaec..8855c323 100644 --- a/backend/app/agents/devrel/nodes/handle_faq_node.py +++ b/backend/app/agents/devrel/nodes/handlers/faq.py @@ -1,5 +1,5 @@ import logging -from app.agents.shared.state import AgentState +from app.agents.state import AgentState logger = logging.getLogger(__name__) diff --git a/backend/app/agents/devrel/nodes/handle_onboarding_node.py b/backend/app/agents/devrel/nodes/handlers/onboarding.py similarity index 91% rename from backend/app/agents/devrel/nodes/handle_onboarding_node.py rename to backend/app/agents/devrel/nodes/handlers/onboarding.py index 3f63d65b..86bba563 100644 --- a/backend/app/agents/devrel/nodes/handle_onboarding_node.py +++ b/backend/app/agents/devrel/nodes/handlers/onboarding.py @@ -1,5 +1,5 @@ import logging -from app.agents.shared.state import AgentState +from app.agents.state import AgentState logger = logging.getLogger(__name__) diff --git a/backend/app/agents/devrel/nodes/handle_technical_support_node.py b/backend/app/agents/devrel/nodes/handlers/technical_support.py similarity index 91% rename from backend/app/agents/devrel/nodes/handle_technical_support_node.py rename to backend/app/agents/devrel/nodes/handlers/technical_support.py index edb672c8..2d4414e8 100644 --- a/backend/app/agents/devrel/nodes/handle_technical_support_node.py +++ b/backend/app/agents/devrel/nodes/handlers/technical_support.py @@ -1,5 +1,5 @@ import logging -from app.agents.shared.state import AgentState +from app.agents.state import AgentState logger = logging.getLogger(__name__) diff --git a/backend/app/agents/devrel/nodes/user_support.py b/backend/app/agents/devrel/nodes/handlers/user_support.py similarity index 100% rename from backend/app/agents/devrel/nodes/user_support.py rename to backend/app/agents/devrel/nodes/handlers/user_support.py diff --git a/backend/app/agents/devrel/nodes/handle_web_search_node.py b/backend/app/agents/devrel/nodes/handlers/web_search.py similarity index 54% rename from backend/app/agents/devrel/nodes/handle_web_search_node.py rename to backend/app/agents/devrel/nodes/handlers/web_search.py index 8fbd8c1f..96fac811 100644 --- a/backend/app/agents/devrel/nodes/handle_web_search_node.py +++ b/backend/app/agents/devrel/nodes/handlers/web_search.py @@ -1,12 +1,16 @@ import logging -from app.agents.shared.state import AgentState +from typing import Dict, Any +from app.agents.state import AgentState from langchain_core.messages import HumanMessage -from ..prompts.search_prompt import EXTRACT_SEARCH_QUERY_PROMPT +from app.agents.devrel.prompts.search_prompt import EXTRACT_SEARCH_QUERY_PROMPT + logger = logging.getLogger(__name__) async def _extract_search_query(message: str, llm) -> str: - """Extract a concise search query from the user's message.""" + """ + Extract a concise search query from the user's message by invoking the LLM. + """ logger.info(f"Extracting search query from: {message[:100]}") try: prompt = EXTRACT_SEARCH_QUERY_PROMPT.format(message=message) @@ -19,7 +23,9 @@ async def _extract_search_query(message: str, llm) -> str: return search_query async def handle_web_search_node(state: AgentState, search_tool, llm) -> dict: - """Handle web search requests""" + """ + Handle web search requests + """ logger.info(f"Handling web search for session {state.session_id}") latest_message = "" @@ -41,3 +47,25 @@ async def handle_web_search_node(state: AgentState, search_tool, llm) -> dict: "tools_used": ["tavily_search"], "current_task": "web_search_handled" } + +def create_search_response(task_result: Dict[str, Any]) -> str: + """ + Create a user-friendly response string from search results. + """ + query = task_result.get("query") + results = task_result.get("results", []) + + if not results: + return f"I couldn't find any information for '{query}'. You might want to try rephrasing your search." + + response_parts = [f"Here's what I found for '{query}':"] + for i, result in enumerate(results[:5]): + title = result.get('title', 'N/A') + snippet = result.get('snippet', 'N/A') + url = result.get('url', '#') + result_line = f"{i+1}. {title}: {snippet}" + response_parts.append(result_line) + response_parts.append(f" (Source: {url})") + + response_parts.append("You can ask me to search again with a different query if these aren't helpful.") + return "\n".join(response_parts) diff --git a/backend/app/agents/devrel/nodes/summarization_node.py b/backend/app/agents/devrel/nodes/summarization.py similarity index 93% rename from backend/app/agents/devrel/nodes/summarization_node.py rename to backend/app/agents/devrel/nodes/summarization.py index 0219fc50..10281a2f 100644 --- a/backend/app/agents/devrel/nodes/summarization_node.py +++ b/backend/app/agents/devrel/nodes/summarization.py @@ -1,7 +1,7 @@ import logging from datetime import datetime, timedelta from typing import Dict, Any -from app.agents.shared.state import AgentState +from app.agents.state import AgentState from langchain_core.messages import HumanMessage from app.agents.devrel.prompts.summarization_prompt import CONVERSATION_SUMMARY_PROMPT @@ -12,7 +12,9 @@ THREAD_TIMEOUT_HOURS = 1 async def check_summarization_needed(state: AgentState) -> Dict[str, Any]: - """Check if summarization is needed and update interaction count""" + """ + Check if summarization is needed and update interaction count + """ current_count = getattr(state, 'interaction_count', 0) new_count = current_count + 1 @@ -46,14 +48,15 @@ async def check_summarization_needed(state: AgentState) -> Dict[str, Any]: return updates async def summarize_conversation_node(state: AgentState, llm) -> Dict[str, Any]: - """Summarize the conversation and update the state""" + """ + Summarize the conversation and update the state + """ logger.info(f"Summarizing conversation for session {state.session_id}") try: current_count = state.interaction_count logger.info(f"Summarizing at interaction count: {current_count}") - # Get the recent messages all_messages = state.messages if not all_messages: @@ -66,7 +69,6 @@ async def summarize_conversation_node(state: AgentState, llm) -> Dict[str, Any]: for msg in all_messages ]) - # Create prompt existing_summary = state.conversation_summary if not existing_summary or existing_summary == "This is the beginning of our conversation.": existing_summary = "No previous summary - this is the start of our conversation tracking." @@ -85,11 +87,9 @@ async def summarize_conversation_node(state: AgentState, llm) -> Dict[str, Any]: logger.info(f"Generating summary with {len(all_messages)} messages, " f"conversation text length: {len(conversation_text)}") - # Generate summary response = await llm.ainvoke([HumanMessage(content=prompt)]) new_summary = response.content.strip() - # Extract key topics from summary new_topics = await _extract_key_topics(new_summary, llm) logger.info(f"Conversation summarized successfully for session {state.session_id}") @@ -121,7 +121,6 @@ async def _extract_key_topics(summary: str, llm) -> list[str]: response = await llm.ainvoke([HumanMessage(content=topic_prompt)]) topics_text = response.content.strip() - # Parse topics from response topics = [topic.strip() for topic in topics_text.split(',') if topic.strip()] return topics[:5] # Limiting to 5 topics diff --git a/backend/app/agents/shared/state.py b/backend/app/agents/state.py similarity index 100% rename from backend/app/agents/shared/state.py rename to backend/app/agents/state.py diff --git a/backend/app/api/__init__.py b/backend/app/api/__init__.py index e69de29b..75059e88 100644 --- a/backend/app/api/__init__.py +++ b/backend/app/api/__init__.py @@ -0,0 +1,11 @@ +""" +API package for the Devr.AI backend. + +This package contains all API-related components: +- router: Main API router with all endpoints +- v1: Version 1 API endpoints +""" + +from .router import api_router + +__all__ = ["api_router"] diff --git a/backend/app/api/router.py b/backend/app/api/router.py new file mode 100644 index 00000000..93664dc8 --- /dev/null +++ b/backend/app/api/router.py @@ -0,0 +1,19 @@ +from fastapi import APIRouter +from .v1.auth import router as auth_router +from .v1.health import router as health_router + +api_router = APIRouter() + +api_router.include_router( + auth_router, + prefix="/v1/auth", + tags=["Authentication"] +) + +api_router.include_router( + health_router, + prefix="/v1", + tags=["Health"] +) + +__all__ = ["api_router"] diff --git a/backend/app/api/v1/__init__.py b/backend/app/api/v1/__init__.py new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/backend/app/api/v1/__init__.py @@ -0,0 +1 @@ + diff --git a/backend/app/api/v1/auth.py b/backend/app/api/v1/auth.py index 2a20e1c7..e70c5dc3 100644 --- a/backend/app/api/v1/auth.py +++ b/backend/app/api/v1/auth.py @@ -1,8 +1,8 @@ from fastapi import APIRouter, Request, HTTPException, Query from fastapi.responses import HTMLResponse -from app.db.supabase.supabase_client import get_supabase_client -from app.db.supabase.users_service import find_user_by_session_and_verify, get_verification_session_info -from app.db.weaviate.user_profiling import profile_user_from_github +from app.database.supabase.client import get_supabase_client +from app.services.auth.verification import find_user_by_session_and_verify, get_verification_session_info +from app.services.user.profiling import profile_user_from_github from typing import Optional import logging import asyncio diff --git a/backend/app/api/v1/health.py b/backend/app/api/v1/health.py new file mode 100644 index 00000000..a60ef0a5 --- /dev/null +++ b/backend/app/api/v1/health.py @@ -0,0 +1,86 @@ +import logging +from fastapi import APIRouter, HTTPException, Depends +from app.database.weaviate.client import get_weaviate_client +from app.core.dependencies import get_app_instance +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from main import DevRAIApplication + +router = APIRouter() +logger = logging.getLogger(__name__) + + +@router.get("/health") +async def health_check(app_instance: "DevRAIApplication" = Depends(get_app_instance)): + """ + General health check endpoint to verify services are running. + + Returns: + dict: Status of the application and its services + """ + try: + async with get_weaviate_client() as client: + weaviate_ready = await client.is_ready() + + return { + "status": "healthy", + "services": { + "weaviate": "ready" if weaviate_ready else "not_ready", + "discord_bot": "running" if app_instance.discord_bot and not app_instance.discord_bot.is_closed() else "stopped" + } + } + except Exception as e: + logger.error(f"Health check failed: {e}") + raise HTTPException( + status_code=503, + detail={ + "status": "unhealthy", + "error": str(e) + } + ) from e + + +@router.get("/health/weaviate") +async def weaviate_health(): + """Check specifically Weaviate service health.""" + try: + async with get_weaviate_client() as client: + is_ready = await client.is_ready() + + return { + "service": "weaviate", + "status": "ready" if is_ready else "not_ready" + } + except Exception as e: + logger.error(f"Weaviate health check failed: {e}") + raise HTTPException( + status_code=503, + detail={ + "service": "weaviate", + "status": "unhealthy", + "error": str(e) + } + ) from e + + +@router.get("/health/discord") +async def discord_health(app_instance: "DevRAIApplication" = Depends(get_app_instance)): + """Check specifically Discord bot health.""" + try: + bot_status = "running" if app_instance.discord_bot and not app_instance.discord_bot.is_closed() else "stopped" + + return { + "service": "discord_bot", + "status": bot_status + } + except Exception as e: + logger.error(f"Discord bot health check failed: {e}") + raise HTTPException( + status_code=503, + detail={ + "service": "discord_bot", + "status": "unhealthy", + "error": str(e) + } + ) from e diff --git a/backend/app/core/config/__init__.py b/backend/app/core/config/__init__.py new file mode 100644 index 00000000..84a6cc56 --- /dev/null +++ b/backend/app/core/config/__init__.py @@ -0,0 +1,3 @@ +from .settings import settings + +__all__ = ["settings"] diff --git a/backend/app/core/config.py b/backend/app/core/config/settings.py similarity index 100% rename from backend/app/core/config.py rename to backend/app/core/config/settings.py diff --git a/backend/app/core/dependencies.py b/backend/app/core/dependencies.py new file mode 100644 index 00000000..10175385 --- /dev/null +++ b/backend/app/core/dependencies.py @@ -0,0 +1,12 @@ +from fastapi import Request +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from main import DevRAIApplication + +async def get_app_instance(request: Request) -> "DevRAIApplication": + """ + Dependency to get the application instance from FastAPI's state. + This avoids circular imports by using dependency injection. + """ + return request.app.state.app_instance diff --git a/backend/app/core/orchestration/agent_coordinator.py b/backend/app/core/orchestration/agent_coordinator.py index 25ed96ad..b80ab979 100644 --- a/backend/app/core/orchestration/agent_coordinator.py +++ b/backend/app/core/orchestration/agent_coordinator.py @@ -5,9 +5,9 @@ from app.agents.devrel.agent import DevRelAgent # TODO: Implement GitHub agent # from app.agents.github.agent import GitHubAgent -from app.agents.shared.state import AgentState +from app.agents.state import AgentState from app.core.orchestration.queue_manager import AsyncQueueManager -from app.agents.devrel.nodes.summarization_node import store_summary_to_database +from app.agents.devrel.nodes.summarization import store_summary_to_database from langsmith import traceable logger = logging.getLogger(__name__) diff --git a/backend/app/model/__init__.py b/backend/app/database/__init__.py similarity index 100% rename from backend/app/model/__init__.py rename to backend/app/database/__init__.py diff --git a/backend/app/model/supabase/__init__.py b/backend/app/database/supabase/__init__.py similarity index 100% rename from backend/app/model/supabase/__init__.py rename to backend/app/database/supabase/__init__.py diff --git a/backend/app/db/supabase/supabase_client.py b/backend/app/database/supabase/client.py similarity index 100% rename from backend/app/db/supabase/supabase_client.py rename to backend/app/database/supabase/client.py diff --git a/backend/app/scripts/supabase/create_db.sql b/backend/app/database/supabase/scripts/create_db.sql similarity index 100% rename from backend/app/scripts/supabase/create_db.sql rename to backend/app/database/supabase/scripts/create_db.sql diff --git a/backend/app/scripts/supabase/populate_db.sql b/backend/app/database/supabase/scripts/populate_db.sql similarity index 100% rename from backend/app/scripts/supabase/populate_db.sql rename to backend/app/database/supabase/scripts/populate_db.sql diff --git a/backend/app/scripts/weaviate/__init__.py b/backend/app/database/weaviate/__init__.py similarity index 100% rename from backend/app/scripts/weaviate/__init__.py rename to backend/app/database/weaviate/__init__.py diff --git a/backend/app/db/weaviate/weaviate_client.py b/backend/app/database/weaviate/client.py similarity index 100% rename from backend/app/db/weaviate/weaviate_client.py rename to backend/app/database/weaviate/client.py diff --git a/backend/app/db/weaviate/weaviate_operations.py b/backend/app/database/weaviate/operations.py similarity index 97% rename from backend/app/db/weaviate/weaviate_operations.py rename to backend/app/database/weaviate/operations.py index a86adcf7..600b52c0 100644 --- a/backend/app/db/weaviate/weaviate_operations.py +++ b/backend/app/database/weaviate/operations.py @@ -2,8 +2,8 @@ import json from typing import Optional, Dict, Any from datetime import datetime, timezone -from app.model.weaviate.models import WeaviateUserProfile -from app.db.weaviate.weaviate_client import get_weaviate_client +from app.models.database.weaviate import WeaviateUserProfile +from app.database.weaviate.client import get_weaviate_client import weaviate.exceptions as weaviate_exceptions import weaviate.classes as wvc diff --git a/backend/bots/__init__.py b/backend/app/database/weaviate/scripts/__init__.py similarity index 100% rename from backend/bots/__init__.py rename to backend/app/database/weaviate/scripts/__init__.py diff --git a/backend/app/scripts/weaviate/create_schemas.py b/backend/app/database/weaviate/scripts/create_schemas.py similarity index 97% rename from backend/app/scripts/weaviate/create_schemas.py rename to backend/app/database/weaviate/scripts/create_schemas.py index 6d6fedcd..351c47a8 100644 --- a/backend/app/scripts/weaviate/create_schemas.py +++ b/backend/app/database/weaviate/scripts/create_schemas.py @@ -1,5 +1,5 @@ import asyncio -from app.db.weaviate.weaviate_client import get_client +from app.database.weaviate.client import get_client import weaviate.classes.config as wc async def create_schema(client, name, properties): diff --git a/backend/app/scripts/weaviate/populate_db.py b/backend/app/database/weaviate/scripts/populate_db.py similarity index 99% rename from backend/app/scripts/weaviate/populate_db.py rename to backend/app/database/weaviate/scripts/populate_db.py index 0137570c..12bb9ebc 100644 --- a/backend/app/scripts/weaviate/populate_db.py +++ b/backend/app/database/weaviate/scripts/populate_db.py @@ -1,7 +1,7 @@ import json import asyncio from datetime import datetime -from app.db.weaviate.weaviate_client import get_weaviate_client +from app.database.weaviate.client import get_weaviate_client async def populate_weaviate_user_profile(client): """ diff --git a/backend/app/models.py b/backend/app/models.py deleted file mode 100644 index 0c5196ff..00000000 --- a/backend/app/models.py +++ /dev/null @@ -1,4 +0,0 @@ -from pydantic import BaseModel - -class RepoRequest(BaseModel): - repo_url: str diff --git a/backend/app/models/__init__.py b/backend/app/models/__init__.py new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/backend/app/models/__init__.py @@ -0,0 +1 @@ + diff --git a/backend/app/model/supabase/models.py b/backend/app/models/database/supabase.py similarity index 100% rename from backend/app/model/supabase/models.py rename to backend/app/models/database/supabase.py diff --git a/backend/app/model/weaviate/models.py b/backend/app/models/database/weaviate.py similarity index 100% rename from backend/app/model/weaviate/models.py rename to backend/app/models/database/weaviate.py diff --git a/backend/app/routes.py b/backend/app/routes.py deleted file mode 100644 index 8b0b277f..00000000 --- a/backend/app/routes.py +++ /dev/null @@ -1,12 +0,0 @@ -from fastapi import APIRouter, HTTPException -from app.models import RepoRequest -from app.utils.github_api import get_repo_stats - -router = APIRouter() - -@router.post("/repo-stats") -async def repo_stats_endpoint(repo: RepoRequest): - try: - return await get_repo_stats(repo.repo_url) - except Exception as e: - raise HTTPException(status_code=500, detail=str(e)) diff --git a/backend/bots/discord/__init__.py b/backend/app/services/auth/__init__.py similarity index 100% rename from backend/bots/discord/__init__.py rename to backend/app/services/auth/__init__.py diff --git a/backend/app/services/auth/management.py b/backend/app/services/auth/management.py new file mode 100644 index 00000000..4d0f35d8 --- /dev/null +++ b/backend/app/services/auth/management.py @@ -0,0 +1,93 @@ +import uuid +from datetime import datetime +from typing import Optional +from app.database.supabase.client import get_supabase_client +from app.models.database.supabase import User +import logging + +logger = logging.getLogger(__name__) + +async def get_or_create_user_by_discord( + discord_id: str, display_name: str, discord_username: str, avatar_url: Optional[str] +) -> User: + """ + Get or create a user by Discord ID. + """ + supabase = get_supabase_client() + existing_user_res = await supabase.table("users").select("*").eq("discord_id", discord_id).limit(1).execute() + + if existing_user_res.data: + logger.info(f"Found existing user for Discord ID: {discord_id}") + return User(**existing_user_res.data[0]) + + # Create new user if not found + logger.info(f"No user found for Discord ID: {discord_id}. Creating new user.") + new_user_data = { + "id": str(uuid.uuid4()), + "discord_id": discord_id, + "display_name": display_name, + "discord_username": discord_username, + "avatar_url": avatar_url, + "preferred_languages": [], + "created_at": datetime.now().isoformat(), + "updated_at": datetime.now().isoformat() + } + + insert_res = await supabase.table("users").insert(new_user_data).execute() + if not insert_res.data: + raise Exception("Failed to create new user in database.") + + logger.info(f"Successfully created new user with ID: {insert_res.data[0]['id']}") + return User(**insert_res.data[0]) + +async def get_user_by_id(user_id: str) -> Optional[User]: + """ + Get user by their ID. + """ + supabase = get_supabase_client() + + try: + user_res = await supabase.table("users").select("*").eq("id", user_id).limit(1).execute() + + if user_res.data: + return User(**user_res.data[0]) + return None + except Exception as e: + logger.error(f"Error getting user by ID {user_id}: {e}") + return None + +async def get_user_by_github_id(github_id: str) -> Optional[User]: + """ + Get user by their GitHub ID. + """ + supabase = get_supabase_client() + + try: + user_res = await supabase.table("users").select("*").eq("github_id", github_id).limit(1).execute() + + if user_res.data: + return User(**user_res.data[0]) + return None + except Exception as e: + logger.error(f"Error getting user by GitHub ID {github_id}: {e}") + return None + +async def update_user_profile(user_id: str, **updates) -> Optional[User]: + """ + Update user profile data. + """ + supabase = get_supabase_client() + + try: + # Add updated_at timestamp + updates["updated_at"] = datetime.now().isoformat() + + update_res = await supabase.table("users").update(updates).eq("id", user_id).execute() + + if update_res.data: + logger.info(f"Successfully updated user {user_id}") + return User(**update_res.data[0]) + return None + except Exception as e: + logger.error(f"Error updating user {user_id}: {e}") + return None diff --git a/backend/app/db/supabase/auth.py b/backend/app/services/auth/supabase.py similarity index 96% rename from backend/app/db/supabase/auth.py rename to backend/app/services/auth/supabase.py index 6e31f807..6ee0d58e 100644 --- a/backend/app/db/supabase/auth.py +++ b/backend/app/services/auth/supabase.py @@ -1,5 +1,5 @@ from typing import Optional -from app.db.supabase.supabase_client import get_supabase_client +from app.database.supabase.client import get_supabase_client import logging logger = logging.getLogger(__name__) diff --git a/backend/app/db/supabase/users_service.py b/backend/app/services/auth/verification.py similarity index 83% rename from backend/app/db/supabase/users_service.py rename to backend/app/services/auth/verification.py index 7ef98a56..cbfa156c 100644 --- a/backend/app/db/supabase/users_service.py +++ b/backend/app/services/auth/verification.py @@ -1,8 +1,8 @@ import uuid from datetime import datetime, timedelta from typing import Optional, Dict, Tuple -from app.db.supabase.supabase_client import get_supabase_client -from app.model.supabase.models import User +from app.database.supabase.client import get_supabase_client +from app.models.database.supabase import User import logging logger = logging.getLogger(__name__) @@ -12,34 +12,6 @@ SESSION_EXPIRY_MINUTES = 5 -async def get_or_create_user_by_discord( - discord_id: str, display_name: str, discord_username: str, avatar_url: Optional[str] -) -> User: - """ - Get or create a user by Discord ID. - """ - supabase = get_supabase_client() - existing_user_res = await supabase.table("users").select("*").eq("discord_id", discord_id).limit(1).execute() - - if existing_user_res.data: - logger.info(f"Found existing user for Discord ID: {discord_id}") - return User(**existing_user_res.data[0]) - logger.info(f"No user found for Discord ID: {discord_id}. Creating new user.") - new_user_data = { - "id": str(uuid.uuid4()), - "discord_id": discord_id, - "display_name": display_name, - "discord_username": discord_username, - "avatar_url": avatar_url, - "preferred_languages": [], - "created_at": datetime.now().isoformat(), - "updated_at": datetime.now().isoformat() - } - insert_res = await supabase.table("users").insert(new_user_data).execute() - if not insert_res.data: - raise Exception("Failed to create new user in database.") - return User(**insert_res.data[0]) - def _cleanup_expired_sessions(): """ Remove expired verification sessions. @@ -93,6 +65,7 @@ async def find_user_by_session_and_verify( ) -> Optional[User]: """ Find and verify user using session ID with expiry validation. + Links GitHub account to Discord user. """ supabase = get_supabase_client() diff --git a/backend/bots/github_bot/__init__.py b/backend/app/services/user/__init__.py similarity index 100% rename from backend/bots/github_bot/__init__.py rename to backend/app/services/user/__init__.py diff --git a/backend/app/db/weaviate/user_profiling.py b/backend/app/services/user/profiling.py similarity index 98% rename from backend/app/db/weaviate/user_profiling.py rename to backend/app/services/user/profiling.py index 8d77255d..223f3abd 100644 --- a/backend/app/db/weaviate/user_profiling.py +++ b/backend/app/services/user/profiling.py @@ -4,8 +4,8 @@ from typing import List, Optional, Dict from datetime import datetime from collections import Counter -from app.model.weaviate.models import WeaviateUserProfile, WeaviateRepository, WeaviatePullRequest -from app.db.weaviate.weaviate_operations import store_user_profile +from app.models.database.weaviate import WeaviateUserProfile, WeaviateRepository, WeaviatePullRequest +from app.database.weaviate.operations import store_user_profile from app.core.config import settings logger = logging.getLogger(__name__) diff --git a/backend/app/services/vector_db/sql.txt b/backend/app/services/vector_db/sql.txt deleted file mode 100644 index 766b07cb..00000000 --- a/backend/app/services/vector_db/sql.txt +++ /dev/null @@ -1,133 +0,0 @@ --- Ensure vector extension is enabled -CREATE EXTENSION IF NOT EXISTS vector; - --- Ensure `authenticator` has proper schema permissions -REVOKE ALL ON SCHEMA public FROM PUBLIC; -GRANT ALL ON SCHEMA public TO postgres; -GRANT USAGE, CREATE ON SCHEMA public TO authenticator; -ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON TABLES TO authenticator; - --- Drop existing functions -DROP FUNCTION IF EXISTS get_embedding; -DROP FUNCTION IF EXISTS delete_embedding; -DROP FUNCTION IF EXISTS add_embedding; -DROP FUNCTION IF EXISTS update_embedding; -DROP FUNCTION IF EXISTS add_mutiple_embedding; -DROP FUNCTION IF EXISTS search_embeddings; -DROP FUNCTION IF EXISTS create_embeddings_table; - --- Step 3: Ensure the vector extension exists before using it -CREATE OR REPLACE FUNCTION create_embeddings_table() RETURNS VOID AS $$ -BEGIN - -- Create table only if it doesn't exist - DROP TABLE IF EXISTS embeddings CASCADE; - - CREATE TABLE embeddings ( - id TEXT, - collection TEXT NOT NULL, - content TEXT NOT NULL, - metadata JSONB, - embedding VECTOR(100), - CONSTRAINT embeddings_pkey PRIMARY KEY (collection, id) - ); - - -- Create vector index (ensure this is compatible with your vector extension) - CREATE INDEX IF NOT EXISTS embeddings_embedding_idx - ON embeddings USING ivfflat (embedding vector_cosine_ops) - WITH (lists = 100); - - -- Index for efficient collection queries - CREATE INDEX IF NOT EXISTS embeddings_collection_idx - ON embeddings (collection); -END; -$$ LANGUAGE plpgsql SECURITY DEFINER; - --- Function to add an embedding (handle potential constraint issues) --- Explicit vector(100) in all functions -CREATE OR REPLACE FUNCTION add_embedding( - p_id TEXT, - p_collection TEXT, - p_content TEXT, - p_metadata JSONB, - p_embedding VECTOR(100) -- Fixed here -) RETURNS VOID AS $$ -BEGIN - INSERT INTO embeddings (id, collection, content, metadata, embedding) - VALUES (p_id, p_collection, p_content, p_metadata, p_embedding) - ON CONFLICT (collection, id) DO UPDATE - SET content = EXCLUDED.content, metadata = EXCLUDED.metadata, embedding = EXCLUDED.embedding; -END; -$$ LANGUAGE plpgsql; - - --- Function to bulk insert embeddings -CREATE OR REPLACE FUNCTION add_multiple_embeddings(data JSONB) RETURNS VOID AS $$ -BEGIN - INSERT INTO embeddings (id, collection, content, metadata, embedding) - SELECT - item->>'p_id', - item->>'p_collection', - item->>'p_content', - item->'p_metadata', - (item->>'p_embedding')::vector(100) -- Correct casting to vector - FROM jsonb_array_elements(data) - ON CONFLICT (collection, id) DO UPDATE - SET content = EXCLUDED.content, metadata = EXCLUDED.metadata, embedding = EXCLUDED.embedding; -END; -$$ LANGUAGE plpgsql; - --- Function to search for embeddings -CREATE OR REPLACE FUNCTION search_embeddings( - p_query_embedding VECTOR(100), - p_collection TEXT, - p_limit INT, - p_threshold FLOAT -) -RETURNS TABLE(id TEXT, collection TEXT, content TEXT, metadata JSONB, embedding VECTOR(100)) AS $$ -SELECT * FROM embeddings -WHERE collection = p_collection -ORDER BY embedding <-> p_query_embedding -LIMIT p_limit; -$$ LANGUAGE sql; - --- Function to retrieve an embedding by ID -CREATE OR REPLACE FUNCTION get_embedding(p_id TEXT, p_collection TEXT) -RETURNS TABLE(id TEXT, collection TEXT, content TEXT, metadata JSONB, embedding VECTOR(100)) AS $$ -SELECT * FROM embeddings WHERE id = p_id AND collection = p_collection; -$$ LANGUAGE sql; - --- Function to delete an embedding -CREATE OR REPLACE FUNCTION delete_embedding(p_id TEXT, p_collection TEXT) -RETURNS VOID AS $$ -DELETE FROM embeddings WHERE id = p_id AND collection = p_collection; -$$ LANGUAGE sql; - --- Function to update an embedding -CREATE OR REPLACE FUNCTION update_embedding( - p_id TEXT, p_collection TEXT, p_content TEXT, p_metadata JSONB, p_embedding VECTOR(100) -) RETURNS VOID AS $$ -UPDATE embeddings -SET content = p_content, metadata = p_metadata, embedding = p_embedding -WHERE id = p_id AND collection = p_collection; -$$ LANGUAGE sql; - --- Function to list all collections -CREATE OR REPLACE FUNCTION list_collections() RETURNS TABLE(collection TEXT) AS $$ -SELECT DISTINCT collection FROM embeddings; -$$ LANGUAGE sql; - --- Function to check database connection -CREATE OR REPLACE FUNCTION check_embeddings_connection() RETURNS BOOLEAN AS $$ -SELECT EXISTS(SELECT 1 FROM embeddings LIMIT 1); -$$ LANGUAGE sql; - --- Ensure `authenticator` can execute functions -GRANT EXECUTE ON FUNCTION create_embeddings_table() TO authenticator; -GRANT EXECUTE ON FUNCTION add_embedding(TEXT, TEXT, TEXT, JSONB, VECTOR(100)) TO authenticator; -GRANT EXECUTE ON FUNCTION add_multiple_embeddings(JSONB) TO authenticator; -GRANT EXECUTE ON FUNCTION search_embeddings(VECTOR(100), TEXT, INT, FLOAT) TO authenticator; -GRANT EXECUTE ON FUNCTION get_embedding(TEXT, TEXT) TO authenticator; -GRANT EXECUTE ON FUNCTION delete_embedding(TEXT, TEXT) TO authenticator; -GRANT EXECUTE ON FUNCTION update_embedding(TEXT, TEXT, TEXT, JSONB, VECTOR(100)) TO authenticator; -GRANT EXECUTE ON FUNCTION list_collections() TO authenticator; -GRANT EXECUTE ON FUNCTION check_embeddings_connection() TO authenticator; \ No newline at end of file diff --git a/backend/app/utils/github_api.py b/backend/app/utils/github_api.py deleted file mode 100644 index 23ac0f80..00000000 --- a/backend/app/utils/github_api.py +++ /dev/null @@ -1,144 +0,0 @@ -from fastapi import FastAPI, HTTPException -from pydantic import BaseModel -import requests -import os -from urllib.parse import urlparse -from dotenv import load_dotenv - -load_dotenv() # Load environment variables - -app = FastAPI() - -class RepoRequest(BaseModel): - repo_url: str - -def parse_github_url(url: str) -> tuple: - """Extract owner/repo from GitHub URL""" - parsed = urlparse(url) - path = parsed.path.strip('/').split('/') - if len(path) < 2: - raise ValueError("Invalid GitHub URL") - return path[0], path[1] - -def github_api_request(endpoint: str) -> dict: - """Make authenticated GitHub API request""" - headers = { - "Authorization": f"token {os.getenv('GITHUB_TOKEN')}", - "Accept": "application/vnd.github.v3+json" - } - response = requests.get(f"https://api.github.com{endpoint}", headers=headers) - response.raise_for_status() - return response.json() - - -@app.post("/repo-stats") -async def get_repo_stats(repo_url: str): - try: - owner, repo_name = parse_github_url(repo_url) - # Rest of your function... - # Get basic repo info - repo_info = github_api_request(f"/repos/{owner}/{repo_name}") - - # Get contributors - contributors = github_api_request(f"/repos/{owner}/{repo_name}/contributors") - - # Get pull requests - prs = github_api_request(f"/repos/{owner}/{repo_name}/pulls?state=all") - - # Get issues - issues = github_api_request(f"/repos/{owner}/{repo_name}/issues?state=all") - - community_profile = github_api_request(f"/repos/{owner}/{repo_name}/community/profile") - - # Recent commits (last 5) - commits = github_api_request(f"/repos/{owner}/{repo_name}/commits?per_page=5") - - code_frequency = github_api_request(f"/repos/{owner}/{repo_name}/stats/code_frequency") - - pull_requests_by_state = { - "open": sum(1 for pr in prs if pr["state"] == "open"), - "closed": sum(1 for pr in prs if pr["state"] == "closed"), - "draft": sum(1 for pr in prs if pr.get("draft", False)), - "merged": sum(1 for pr in prs if pr.get("merged_at")) - } - pr_details = [{ - "title": pr["title"], - "number": pr["number"], - "state": pr["state"], - "url": pr["html_url"], - "author": { - "login": pr["user"]["login"], - "avatar_url": pr["user"]["avatar_url"], - "profile_url": pr["user"]["html_url"] - }, - } for pr in prs] - - - return { - "name": repo_info["full_name"], - "stars": repo_info["stargazers_count"], - "forks": repo_info["forks_count"], - "watchers": repo_info["subscribers_count"], - "created_at": repo_info["created_at"], - "updated_at": repo_info["updated_at"], - # Licensing and topics - # "license": repo_info.get("license", {}).get("spdx_id", "No License"), - - "topics": repo_info.get("topics", []), - - "contributors": [{ - "login": c["login"], - "contributions": c["contributions"], - "avatar_url": c["avatar_url"] - } for c in contributors], - "recent_commits": [{ - "sha": commit["sha"][:7], - "author": commit["commit"]["author"]["name"], - "message": commit["commit"]["message"], - "date": commit["commit"]["author"]["date"] - } for commit in commits], - - - "community": { - "health_percentage": community_profile["health_percentage"], - "code_of_conduct": community_profile.get("files", {}).get("code_of_conduct") is not None, - "license": community_profile.get("files", {}).get("license") is not None, - "readme": community_profile.get("files", {}).get("readme") is not None - }, - # Issues - "issues": { - "total": len(issues), - "open": sum(1 for issue in issues if issue["state"] == "open"), - "closed": sum(1 for issue in issues if issue["state"] == "closed"), - "labels": list({label["name"] for issue in issues for label in issue["labels"]}) - }, - - # Code statistics - "code_activity": { - "weekly_commits": len(code_frequency) if isinstance(code_frequency, list) else 0, - "total_additions": sum(week[1] for week in code_frequency) if isinstance(code_frequency, list) else 0, - "total_deletions": sum(abs(week[2]) for week in code_frequency) if isinstance(code_frequency, list) else 0 - }, - - # Pull Requests - "pull_requests": { - **pull_requests_by_state, - "total": len(prs), - "details": pr_details - "total": len(prs), - "merged": sum(1 for pr in prs if pr["merged_at"]), - "draft": sum(1 for pr in prs if pr["draft"]), - "by_state": { - "open": sum(1 for pr in prs if pr["state"] == "open"), - "closed": sum(1 for pr in prs if pr["state"] == "closed") - } - - }, - } - - except requests.HTTPError as e: - raise HTTPException(status_code=e.response.status_code, - detail="GitHub API error") - except ValueError: - raise HTTPException(status_code=400, - detail="Invalid GitHub URL format") diff --git a/backend/bots/slack_bot/__init__.py b/backend/integrations/__init__.py similarity index 100% rename from backend/bots/slack_bot/__init__.py rename to backend/integrations/__init__.py diff --git a/backend/app/agents/devrel/nodes/human_in_loop.py b/backend/integrations/discord/__init__.py similarity index 100% rename from backend/app/agents/devrel/nodes/human_in_loop.py rename to backend/integrations/discord/__init__.py diff --git a/backend/bots/discord/discord_bot.py b/backend/integrations/discord/bot.py similarity index 98% rename from backend/bots/discord/discord_bot.py rename to backend/integrations/discord/bot.py index 59ebab0c..d17d5346 100644 --- a/backend/bots/discord/discord_bot.py +++ b/backend/integrations/discord/bot.py @@ -3,7 +3,7 @@ import logging from typing import Dict, Any, Optional from app.core.orchestration.queue_manager import AsyncQueueManager, QueuePriority -from app.agents.shared.classification_router import ClassificationRouter +from app.agents.classification_router import ClassificationRouter logger = logging.getLogger(__name__) diff --git a/backend/bots/discord/discord_cogs.py b/backend/integrations/discord/cogs.py similarity index 97% rename from backend/bots/discord/discord_cogs.py rename to backend/integrations/discord/cogs.py index 0c78549b..1f892953 100644 --- a/backend/bots/discord/discord_cogs.py +++ b/backend/integrations/discord/cogs.py @@ -2,14 +2,11 @@ from discord.ext import commands, tasks import logging from app.core.orchestration.queue_manager import AsyncQueueManager, QueuePriority -from app.db.supabase.auth import login_with_github -from app.db.supabase.users_service import ( - get_or_create_user_by_discord, - create_verification_session, - cleanup_expired_tokens -) -from bots.discord.discord_bot import DiscordBot -from bots.discord.discord_views import OAuthView +from app.services.auth.supabase import login_with_github +from app.services.auth.management import get_or_create_user_by_discord +from app.services.auth.verification import create_verification_session, cleanup_expired_tokens +from integrations.discord.bot import DiscordBot +from integrations.discord.views import OAuthView from app.core.config import settings logger = logging.getLogger(__name__) diff --git a/backend/bots/discord/discord_views.py b/backend/integrations/discord/views.py similarity index 100% rename from backend/bots/discord/discord_views.py rename to backend/integrations/discord/views.py diff --git a/backend/app/agents/shared/response_coordinator.py b/backend/integrations/github/__init__.py similarity index 100% rename from backend/app/agents/shared/response_coordinator.py rename to backend/integrations/github/__init__.py diff --git a/backend/app/utils/helpers.py b/backend/integrations/slack/__init__.py similarity index 100% rename from backend/app/utils/helpers.py rename to backend/integrations/slack/__init__.py diff --git a/backend/main.py b/backend/main.py index 77a69440..912f67b9 100644 --- a/backend/main.py +++ b/backend/main.py @@ -6,13 +6,13 @@ import uvicorn from fastapi import FastAPI, Response -from app.api.v1.auth import router as auth_router +from app.api.router import api_router from app.core.config import settings from app.core.orchestration.agent_coordinator import AgentCoordinator from app.core.orchestration.queue_manager import AsyncQueueManager -from app.db.weaviate.weaviate_client import get_weaviate_client -from bots.discord.discord_bot import DiscordBot -from bots.discord.discord_cogs import DevRelCommands +from app.database.weaviate.client import get_weaviate_client +from integrations.discord.bot import DiscordBot +from integrations.discord.cogs import DevRelCommands logging.basicConfig( level=logging.INFO, @@ -91,6 +91,7 @@ async def lifespan(app: FastAPI): """ Lifespan manager for the FastAPI application. Handles startup and shutdown events. """ + app.state.app_instance = app_instance await app_instance.start_background_tasks() yield await app_instance.stop_background_tasks() @@ -103,28 +104,8 @@ async def favicon(): """Return empty favicon to prevent 404 logs""" return Response(status_code=204) -@api.get("/health") -async def health_check(): - """Health check endpoint to verify services are running""" - try: - async with get_weaviate_client() as client: - weaviate_ready = await client.is_ready() - - return { - "status": "healthy", - "services": { - "weaviate": "ready" if weaviate_ready else "not_ready", - "discord_bot": "running" if app_instance.discord_bot and not app_instance.discord_bot.is_closed() else "stopped" - } - } - except Exception as e: - logger.error(f"Health check failed: {e}") - return { - "status": "unhealthy", - "error": str(e) - } - -api.include_router(auth_router, prefix="/v1/auth", tags=["Authentication"]) + +api.include_router(api_router) if __name__ == "__main__": diff --git a/backend/routes.py b/backend/routes.py index 1bf8bed0..7dbd6463 100644 --- a/backend/routes.py +++ b/backend/routes.py @@ -13,6 +13,7 @@ class RepoRequest(BaseModel): repo_url: str + logging.basicConfig(level=logging.INFO) handler_registry = HandlerRegistry() event_bus = EventBus(handler_registry) @@ -71,7 +72,7 @@ async def github_webhook(request: Request): event_type = EventType.PR_MERGED else: logging.info("Pull request closed without merge; no event dispatched.") - + # Handle pull request comment events elif event_header in ["pull_request_review_comment", "pull_request_comment"]: action = payload.get("action") diff --git a/tests/test_supabase.py b/tests/test_supabase.py index 2cf7f1b3..3fdcef41 100644 --- a/tests/test_supabase.py +++ b/tests/test_supabase.py @@ -1,6 +1,6 @@ -from ..backend.app.model.supabase.models import User, Interaction, CodeChunk, Repository +from ..backend.app.models.database.supabase import User, Interaction, CodeChunk, Repository from uuid import uuid4 -from ..backend.app.db.supabase.supabase_client import get_supabase_client +from ..backend.app.database.supabase.client import get_supabase_client from datetime import datetime # Your User model import client = get_supabase_client()