Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
Expand Down
4 changes: 4 additions & 0 deletions backend/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
SUPABASE_URL=
SUPABASE_SERVICE_ROLE_KEY=
CORS_ORIGINS=http://localhost:3000
GITHUB_TOKEN=
59 changes: 59 additions & 0 deletions backend/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# Setting Up the .env File for FastAPI

This guide provides step-by-step instructions to configure your `.env` file and securely use a GitHub Personal Access Token in your FastAPI project.

## 1. Create the `.env` File

1. Navigate to the `backend` directory where your project is set up.
2. Create a new file named `.env` (if it doesn’t already exist).

## 2. Add the GitHub Access Token

1. Open the `.env` file and add the following line:

```text
GITHUB_TOKEN=your_personal_access_token_here
```

2. Replace `your_personal_access_token_here` with your actual GitHub Personal Access Token.

## 3. Generate a GitHub Personal Access Token

1. Go to [GitHub Developer Settings](https://github.com/settings/tokens).
2. Click on **Tokens (classic)** and then **Generate new token** and then generate the classic token.
3. Select the required scopes for your application:
- **For public repositories**: Check `repo`.
- **For additional access**: Check `read:org`, `read:user`, etc., as needed.
4. Copy the generated token (it will only be displayed once).


## 5. Load Environment Variables in FastAPI

Ensure you have these things installed in your project:

```bash
pip install python-dotenv requests fastapi uvicorn

```

### Test API Requests
Run your FastAPI server:

```bash
uvicorn backend.main:app --reload
```

Use `curl` or **Postman** to test the `/api/repo-stats` endpoint:

```bash
curl -X POST http://localhost:8000/api/repo-stats \
-H "Content-Type: application/json" \
-d '{"repo_url": "https://github.com/AOSSIE-Org/Devr.AI/"}'
```

If all the things correctly got setup then you will see the JSON repsponse

---


Now, your FastAPI application is securely set up to use GitHub API credentials from a `.env` file!
Empty file added backend/app/core/__init__.py
Empty file.
16 changes: 16 additions & 0 deletions backend/app/core/events/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
from .base import BaseEvent
from .enums import PlatformType, EventType
from .github_event import GitHubEvent
from .discord_event import DiscordEvent
from .slack_event import SlackEvent
from .event_bus import EventBus

__all__ = [
"BaseEvent",
"PlatformType",
"EventType",
"GitHubEvent",
"DiscordEvent",
"SlackEvent",
"EventBus",
]
23 changes: 23 additions & 0 deletions backend/app/core/events/base.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
from datetime import datetime
from typing import Dict, Any, Optional
from pydantic import BaseModel, Field

class BaseEvent(BaseModel):
"""Base event model for all platform events"""
id: str = Field(..., description="Unique identifier for the event")
platform: str
event_type: str
timestamp: datetime = Field(default_factory=datetime.now, description="Timestamp of the event")
actor_id: str = Field(..., description="ID of the user who triggered the event")
actor_name: Optional[str] = Field(None, description="Name of the user who triggered the event")
raw_data: Dict[str, Any] = Field({}, description="Raw event data from the platform")
metadata: Dict[str, Any] = Field({}, description="Additional metadata for the event")

def to_dict(self) -> Dict[str, Any]:
"""Convert event to dictionary for serialization"""
return self.model_dump()

@classmethod
def from_dict(cls, data: Dict[str, Any]) -> "BaseEvent":
"""Create event from dictionary"""
return cls(**data)
14 changes: 14 additions & 0 deletions backend/app/core/events/discord_event.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
from typing import List, Dict, Optional, Any
from pydantic import Field
from .base import BaseEvent
from .enums import PlatformType

class DiscordEvent(BaseEvent):
"""Discord specific event model"""
platform: PlatformType = PlatformType.DISCORD
guild_id: str
channel_id: str
message_id: Optional[str] = None
content: Optional[str] = None
attachments: List[Dict[str, Any]] = Field(default_factory=list)
mentions: List[str] = Field(default_factory=list)
30 changes: 30 additions & 0 deletions backend/app/core/events/enums.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
from enum import Enum

class PlatformType(str, Enum):
GITHUB = "github"
DISCORD = "discord"
SLACK = "slack"
DISCOURSE = "discourse"
SYSTEM = "system"

class EventType(str, Enum):
ISSUE_CREATED = "issue.created"
ISSUE_CLOSED = "issue.closed"
ISSUE_UPDATED = "issue.updated"
ISSUE_COMMENTED = "issue.commented"
PR_CREATED = "pr.created"
PR_UPDATED = "pr.updated"
PR_COMMENTED = "pr.commented"
PR_MERGED = "pr.merged"
PR_REVIEWED = "pr.reviewed"

MESSAGE_CREATED = "message.created"
MESSAGE_UPDATED = "message.updated"
REACTION_ADDED = "reaction.added"
USER_JOINED = "user.joined"

ONBOARDING_STARTED = "onboarding.started"
ONBOARDING_COMPLETED = "onboarding.completed"
FAQ_REQUESTED = "faq.requested"
KNOWLEDGE_UPDATED = "knowledge.updated"
ANALYTICS_COLLECTED = "analytics.collected"
56 changes: 56 additions & 0 deletions backend/app/core/events/event_bus.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import asyncio
import logging
from typing import Dict, List, Union, Optional
from .base import BaseEvent
from .enums import EventType, PlatformType
from ..handler.handler_registry import HandlerRegistry

logger = logging.getLogger(__name__)

class EventBus:
"""Central event bus for dispatching events to registered handlers"""

def __init__(self, handler_registry: HandlerRegistry):
self.handler_registry = handler_registry
self.handlers: Dict[EventType, List[callable]] = {}
self.global_handlers: List[callable] = []

def register_handler(self, event_type: Union[EventType, List[EventType]], handler_func, platform: Optional[PlatformType] = None):
"""Register a handler function for a specific event type and optionally platform"""
if isinstance(event_type, list):
for et in event_type:
self._add_handler(et, handler_func)
else:
self._add_handler(event_type, handler_func)
logger.info(f"Handler {handler_func.__name__} registered for event type {event_type}")
pass

def _add_handler(self, event_type: EventType, handler_func: callable):
if event_type not in self.handlers:
self.handlers[event_type] = []

self.handlers[event_type].append(handler_func)
pass

def register_global_handler(self, handler_func):
"""Register a handler that will receive all events"""
self.global_handlers.append(handler_func)
logger.info(f"Global handler {handler_func._name_} registered")
pass

async def dispatch(self, event: BaseEvent):
"""Dispatch an event to all registered handlers"""
logger.info(f"Dispatching event {event.id} of type {event.event_type}")

# Call global handlers first
for handler in self.global_handlers:
asyncio.create_task(handler(event))

# Call event-specific handlers
if event.event_type in self.handlers:
for handler in self.handlers[event.event_type]:
asyncio.create_task(handler(event))

else:
logger.info(f"No handlers registered for event type {event.event_type}")
pass
16 changes: 16 additions & 0 deletions backend/app/core/events/github_event.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
from typing import List, Optional
from pydantic import Field
from .base import BaseEvent
from .enums import PlatformType

class GitHubEvent(BaseEvent):
"""GitHub specific event model"""
platform: PlatformType = PlatformType.GITHUB
repository: str
organization: Optional[str] = None
issue_number: Optional[int] = None
pr_number: Optional[int] = None
labels: List[str] = Field(default_factory=list)
title: Optional[str] = None
body: Optional[str] = None
url: Optional[str] = None
14 changes: 14 additions & 0 deletions backend/app/core/events/slack_event.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
from typing import List, Dict, Optional, Any
from pydantic import Field
from .base import BaseEvent
from .enums import PlatformType

class SlackEvent(BaseEvent):
"""Slack specific event model"""
platform: PlatformType = PlatformType.SLACK
team_id: str
channel_id: str
message_ts: Optional[str] = None
text: Optional[str] = None
thread_ts: Optional[str] = None
blocks: List[Dict[str, Any]] = Field(default_factory=list)
Empty file.
39 changes: 39 additions & 0 deletions backend/app/core/handler/base.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
from abc import ABC, abstractmethod
from typing import Dict, Any, List, Type, Optional
import logging
from ..events.base import BaseEvent
from ..events.enums import EventType, PlatformType

logger = logging.getLogger(__name__)


class BaseHandler(ABC):
"""Base class for all event handlers"""

def __init__(self):
self.name = self.__class__.__name__

async def pre_handle(self, event: BaseEvent) -> BaseEvent:
"""Pre-processing for an event before handling"""
logger.debug(f"Pre-handling event {event.id} with {self.name}")
return event

@abstractmethod
async def handle(self, event: BaseEvent) -> Dict[str, Any]:
"""Handle the event"""
pass

async def post_handle(self, event: BaseEvent, result: Dict[str, Any]) -> Dict[str, Any]:
"""Post-processing after handling an event"""
logger.debug(f"Post-handling event {event.id} with {self.name}")
return result

async def process(self, event: BaseEvent) -> Dict[str, Any]:
"""Process the event through the complete pipeline"""
try:
processed_event = await self.pre_handle(event)
result = await self.handle(processed_event)
return await self.post_handle(processed_event, result)
except Exception as e:
logger.error(f"Error processing event {event.id} with handler {self.name}: {str(e)}")
return {"success": False, "error": str(e)}
28 changes: 28 additions & 0 deletions backend/app/core/handler/faq_handler.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import logging
from typing import Dict, Any
from ..events.base import BaseEvent, EventType
from .base import BaseHandler

logger = logging.getLogger(__name__)

class FAQHandler(BaseHandler):
"""Handler for FAQ and knowledge base queries"""

async def handle(self, event: BaseEvent) -> Dict[str, Any]:
logger.info(f"Handling FAQ request event: {event.event_type}")

if event.event_type == EventType.FAQ_REQUESTED:
return await self._handle_faq_request(event)
elif event.event_type == EventType.KNOWLEDGE_UPDATED:
return await self._handle_knowledge_update(event)
else:
logger.warning(f"Unsupported FAQ event type: {event.event_type}")
return {"success": False, "reason": "Unsupported event type"}

async def _handle_faq_request(self, event: BaseEvent) -> Dict[str, Any]:
# Implementation for FAQ request
return {"success": True, "action": "faq_processed"}

async def _handle_knowledge_update(self, event: BaseEvent) -> Dict[str, Any]:
# Implementation for updating knowledge base
return {"success": True, "action": "knowledge_updated"}
38 changes: 38 additions & 0 deletions backend/app/core/handler/handler_registry.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
from typing import Dict, Type, List, Optional
from ..events.base import BaseEvent
from ..events.enums import EventType, PlatformType
from .base import BaseHandler

class HandlerRegistry:
"""Registry for event handlers"""

def __init__(self):
self.handlers: Dict[str, Type[BaseHandler]] = {}
self.instances: Dict[str, BaseHandler] = {}

def register(self, event_types: List[EventType], handler_class: Type[BaseHandler],
platform: Optional[PlatformType] = None):
"""Register a handler class for specific event types"""
for event_type in event_types:
key = f"{platform.value}:{event_type.value}" if platform else event_type.value
self.handlers[key] = handler_class

def get_handler(self, event: BaseEvent) -> BaseHandler:
"""Get handler instance for an event"""
# Try platform-specific handler first
key = f"{event.platform.value}:{event.event_type.value}"
handler_class = self.handlers.get(key)

# Fall back to generic event type handler
if not handler_class:
key = event.event_type.value
handler_class = self.handlers.get(key)

if not handler_class:
raise ValueError(f"No handler registered for event type {event.event_type} on platform {event.platform}")

# Create instance if not cached
if key not in self.instances:
self.instances[key] = handler_class()

return self.instances[key]
Loading