Skip to content

Commit bda1756

Browse files
authored
Merge branch 'main' into feature/backend-fastapi-github-stats
2 parents 8a8eae6 + 6a4ab72 commit bda1756

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

68 files changed

+6851
-16705
lines changed
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
name: Auto-Update Project Structure
2+
on:
3+
pull_request:
4+
types: [closed]
5+
branches: [main]
6+
7+
jobs:
8+
update-structure:
9+
if: github.event.pull_request.merged == true
10+
runs-on: ubuntu-latest
11+
permissions:
12+
contents: write
13+
pull-requests: write
14+
steps:
15+
- name: Checkout code
16+
uses: actions/checkout@v4
17+
with:
18+
fetch-depth: 0
19+
20+
- name: Generate project structure file
21+
run: tree -a -I 'node_modules|.git' -f > project_structure.txt
22+
23+
24+
- name: Commit and push changes
25+
uses: stefanzweifel/git-auto-commit-action@v5
26+
with:
27+
commit_message: "Update project structure (auto)"
28+
branch: main
29+
commit_user_name: "CI Bot"
30+
commit_user_email: "[email protected]"
31+
commit_author: "CI Bot <[email protected]>"

backend/.env.example

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
SUPABASE_URL=
2-
SUPABASE_SERVICE_ROLE_KEY=
2+
SUPABASE_SERVICE_ROLE_KEY=
3+
DISCORD_BOT_TOKEN=

backend/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
.qodo

backend/app/core/events/base.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,16 +8,18 @@ class BaseEvent(BaseModel):
88
platform: str
99
event_type: str
1010
timestamp: datetime = Field(default_factory=datetime.now, description="Timestamp of the event")
11+
channel_id: str | None = None
1112
actor_id: str = Field(..., description="ID of the user who triggered the event")
1213
actor_name: Optional[str] = Field(None, description="Name of the user who triggered the event")
1314
raw_data: Dict[str, Any] = Field({}, description="Raw event data from the platform")
1415
metadata: Dict[str, Any] = Field({}, description="Additional metadata for the event")
15-
16+
content: Optional[str] = Field(None, description="Content of the event")
17+
1618
def to_dict(self) -> Dict[str, Any]:
1719
"""Convert event to dictionary for serialization"""
1820
return self.model_dump()
19-
21+
2022
@classmethod
2123
def from_dict(cls, data: Dict[str, Any]) -> "BaseEvent":
2224
"""Create event from dictionary"""
23-
return cls(**data)
25+
return cls(**data)

backend/app/core/events/discord_event.py

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,27 @@
66
class DiscordEvent(BaseEvent):
77
"""Discord specific event model"""
88
platform: PlatformType = PlatformType.DISCORD
9-
guild_id: str
10-
channel_id: str
9+
guild_id: Optional[str] = None
10+
channel_id: Optional[str] = None
1111
message_id: Optional[str] = None
1212
content: Optional[str] = None
1313
attachments: List[Dict[str, Any]] = Field(default_factory=list)
14-
mentions: List[str] = Field(default_factory=list)
14+
mentions: List[str] = Field(default_factory=list)
15+
16+
def __init__(self, **data):
17+
"""Ensure event data is properly initialized from raw_data"""
18+
raw_data = data.get("raw_data", {})
19+
20+
super().__init__(
21+
id=data.get("id"),
22+
event_type=data.get("event_type"),
23+
platform=PlatformType.DISCORD,
24+
actor_id=data.get("actor_id") or raw_data.get("actor_id"),
25+
actor_name=data.get("actor_name"),
26+
raw_data=raw_data,
27+
metadata=data.get("metadata", {}),
28+
guild_id=raw_data.get("guild_id"),
29+
message_id=raw_data.get("id"),
30+
content=raw_data.get("content"),
31+
channel_id=data.get("channel_id"),
32+
)

backend/app/core/events/enums.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,19 @@ class EventType(str, Enum):
1616
PR_UPDATED = "pr.updated"
1717
PR_COMMENTED = "pr.commented"
1818
PR_MERGED = "pr.merged"
19+
1920
PR_REVIEWED = "pr.reviewed"
2021

2122
MESSAGE_CREATED = "message.created"
2223
MESSAGE_UPDATED = "message.updated"
24+
MESSAGE_DELETED = "message.deleted"
2325
REACTION_ADDED = "reaction.added"
2426
USER_JOINED = "user.joined"
2527

2628
ONBOARDING_STARTED = "onboarding.started"
2729
ONBOARDING_COMPLETED = "onboarding.completed"
2830
FAQ_REQUESTED = "faq.requested"
2931
KNOWLEDGE_UPDATED = "knowledge.updated"
32+
3033
ANALYTICS_COLLECTED = "analytics.collected"
34+

backend/app/core/events/event_bus.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@ def register_handler(self, event_type: Union[EventType, List[EventType]], handle
2222
self._add_handler(et, handler_func)
2323
else:
2424
self._add_handler(event_type, handler_func)
25-
logger.info(f"Handler {handler_func.__name__} registered for event type {event_type}")
2625
pass
2726

2827
def _add_handler(self, event_type: EventType, handler_func: callable):
@@ -35,22 +34,21 @@ def _add_handler(self, event_type: EventType, handler_func: callable):
3534
def register_global_handler(self, handler_func):
3635
"""Register a handler that will receive all events"""
3736
self.global_handlers.append(handler_func)
38-
logger.info(f"Global handler {handler_func._name_} registered")
3937
pass
4038

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

4542
# Call global handlers first
4643
for handler in self.global_handlers:
44+
logger.info(f"Calling global handler: {handler.__name__}")
4745
asyncio.create_task(handler(event))
4846

4947
# Call event-specific handlers
5048
if event.event_type in self.handlers:
5149
for handler in self.handlers[event.event_type]:
50+
logger.info(f"Calling handler: {handler.__name__} for event type: {event.event_type}")
5251
asyncio.create_task(handler(event))
53-
5452
else:
5553
logger.info(f"No handlers registered for event type {event.event_type}")
5654
pass
Lines changed: 45 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,63 @@
11
import logging
22
from typing import Dict, Any
3-
from ..events.base import BaseEvent, EventType
4-
from .base import BaseHandler
3+
from app.core.events.base import BaseEvent
4+
from app.core.events.enums import EventType, PlatformType
5+
from app.core.handler.base import BaseHandler
56

67
logger = logging.getLogger(__name__)
78

89
class FAQHandler(BaseHandler):
910
"""Handler for FAQ and knowledge base queries"""
10-
11+
12+
def __init__(self, bot=None):
13+
self.bot = bot
14+
self.faq_responses = {
15+
"what is devr.ai?": "Devr.AI is an AI-powered Developer Relations assistant that helps open-source communities by automating engagement and issue tracking.",
16+
"how do i contribute?": "You can contribute by visiting our GitHub repo, checking open issues, and submitting pull requests.",
17+
"what platforms does devr.ai support?": "Devr.AI integrates with Discord, Slack, GitHub, and Discourse to assist developers and maintainers.",
18+
"who maintains devr.ai?": "Devr.AI is maintained by an open-source community of developers.",
19+
"how do i report a bug?": "You can report a bug by opening an issue on our GitHub repository.",
20+
}
21+
22+
async def is_faq(self, message: str):
23+
"""Check if the question is in the FAQ and return the response"""
24+
for key in self.faq_responses:
25+
if message.lower() == key.lower():
26+
return True, self.faq_responses[key]
27+
return False, None
28+
1129
async def handle(self, event: BaseEvent) -> Dict[str, Any]:
1230
logger.info(f"Handling FAQ request event: {event.event_type}")
13-
31+
1432
if event.event_type == EventType.FAQ_REQUESTED:
1533
return await self._handle_faq_request(event)
1634
elif event.event_type == EventType.KNOWLEDGE_UPDATED:
1735
return await self._handle_knowledge_update(event)
1836
else:
1937
logger.warning(f"Unsupported FAQ event type: {event.event_type}")
2038
return {"success": False, "reason": "Unsupported event type"}
21-
39+
2240
async def _handle_faq_request(self, event: BaseEvent) -> Dict[str, Any]:
23-
# Implementation for FAQ request
24-
return {"success": True, "action": "faq_processed"}
25-
41+
question = (event.content or "").strip().lower()
42+
response = self.get_faq_response(question)
43+
44+
logger.info("Question: " + question + ", Response: " + response)
45+
46+
await self._send_discord_response(event.channel_id, response)
47+
return {"success": True, "action": "faq_response_sent"}
48+
49+
def get_faq_response(self, question: str) -> str:
50+
return self.faq_responses.get(question.lower(), "I'm not sure about that, but I can find out!")
51+
2652
async def _handle_knowledge_update(self, event: BaseEvent) -> Dict[str, Any]:
27-
# Implementation for updating knowledge base
28-
return {"success": True, "action": "knowledge_updated"}
53+
"""Handles knowledge base updates."""
54+
return {"success": True, "action": "knowledge_updated"}
55+
56+
async def _send_discord_response(self, channel_id: str, response: str):
57+
"""Sends a response message to the specified Discord channel."""
58+
if self.bot:
59+
channel = self.bot.get_channel(int(channel_id))
60+
if channel:
61+
await channel.send(response)
62+
else:
63+
logger.error(f"Could not find Discord channel with ID {channel_id}")

backend/app/core/handler/issue_handler.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import logging
22
from typing import Dict, Any
3-
from ..events.base import BaseEvent, EventType
3+
from ..events.base import BaseEvent
4+
from ..events.enums import EventType
45
from .base import BaseHandler
56

67
logger = logging.getLogger(__name__)
Lines changed: 29 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,54 @@
11
import logging
22
from typing import Dict, Any
3-
from ..events.base import BaseEvent, EventType
3+
from ..events.base import BaseEvent
4+
from ..events.enums import EventType
45
from .base import BaseHandler
5-
6+
from .faq_handler import FAQHandler
67
logger = logging.getLogger(__name__)
78

89
class MessageHandler(BaseHandler):
910
"""Handler for Discord/Slack message events"""
10-
11+
12+
def __init__(self, bot=None):
13+
self.bot = bot
14+
self.faq_handler = FAQHandler()
15+
1116
async def handle(self, event: BaseEvent) -> Dict[str, Any]:
1217
logger.info(f"Handling message event from {event.platform}: {event.event_type}")
13-
1418
if event.event_type == EventType.MESSAGE_CREATED:
1519
return await self._handle_message_created(event)
1620
elif event.event_type == EventType.MESSAGE_UPDATED:
1721
return await self._handle_message_updated(event)
1822
else:
1923
logger.warning(f"Unsupported message event type: {event.event_type}")
2024
return {"success": False, "reason": "Unsupported event type"}
21-
25+
2226
async def _handle_message_created(self, event: BaseEvent) -> Dict[str, Any]:
27+
28+
user_message = (event.content or "").strip().lower()
29+
30+
if not user_message:
31+
logger.error(f"Received empty message event: {event}")
32+
return {"success": False, "reason": "Empty message"}
33+
34+
# Check if it's a FAQ request
35+
if await self.faq_handler.is_faq(user_message):
36+
faq_event = BaseEvent(
37+
id=event.id,
38+
event_type=EventType.FAQ_REQUESTED,
39+
platform=event.platform,
40+
channel_id=event.raw_data.get("channel_id"),
41+
actor_id=event.actor_id,
42+
data={"content": user_message},
43+
)
44+
return await self.faq_handler.handle(faq_event)
45+
2346
# Implementation for new message creation
2447
# - Check if it's a command
2548
# - Check if it's a question
2649
# - Process natural language
2750
return {"success": True, "action": "message_processed"}
28-
51+
2952
async def _handle_message_updated(self, event: BaseEvent) -> Dict[str, Any]:
3053
# Implementation for message updates
3154
return {"success": True, "action": "message_updated"}

0 commit comments

Comments
 (0)