Skip to content

Commit de6ee5b

Browse files
Merge pull request #141 from smokeyScraper/frontend_revamp
feat: frontend revamps and connection establishment; enabling org owners to setup DevR AI to their respected platforms
2 parents a77f29b + cea55a8 commit de6ee5b

File tree

16 files changed

+6497
-5366
lines changed

16 files changed

+6497
-5366
lines changed

backend/app/api/router.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from fastapi import APIRouter
22
from .v1.auth import router as auth_router
33
from .v1.health import router as health_router
4+
from .v1.integrations import router as integrations_router
45

56
api_router = APIRouter()
67

@@ -16,4 +17,10 @@
1617
tags=["Health"]
1718
)
1819

20+
api_router.include_router(
21+
integrations_router,
22+
prefix="/v1/integrations",
23+
tags=["Integrations"]
24+
)
25+
1926
__all__ = ["api_router"]

backend/app/api/v1/integrations.py

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
from fastapi import APIRouter, HTTPException, Depends, status
2+
from uuid import UUID
3+
from app.models.integration import (
4+
IntegrationCreateRequest,
5+
IntegrationUpdateRequest,
6+
IntegrationResponse,
7+
IntegrationListResponse,
8+
IntegrationStatusResponse
9+
)
10+
from app.services.integration_service import integration_service, NotFoundError
11+
from app.core.dependencies import get_current_user
12+
13+
router = APIRouter()
14+
15+
16+
@router.post("/", response_model=IntegrationResponse, status_code=status.HTTP_201_CREATED)
17+
async def create_integration(
18+
request: IntegrationCreateRequest,
19+
user_id: UUID = Depends(get_current_user)
20+
):
21+
"""Create a new organization integration."""
22+
try:
23+
return await integration_service.create_integration(user_id, request)
24+
except ValueError as e:
25+
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(e))
26+
except Exception as e:
27+
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(e)) from e
28+
29+
30+
@router.get("/", response_model=IntegrationListResponse)
31+
async def list_integrations(user_id: UUID = Depends(get_current_user)):
32+
"""List all integrations for the current user."""
33+
try:
34+
integrations = await integration_service.get_integrations(user_id)
35+
return IntegrationListResponse(integrations=integrations, total=len(integrations))
36+
except Exception as e:
37+
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(e)) from e
38+
39+
40+
@router.get("/status/{platform}", response_model=IntegrationStatusResponse)
41+
async def get_integration_status(
42+
platform: str,
43+
user_id: UUID = Depends(get_current_user)
44+
):
45+
"""Get the status of a specific platform integration."""
46+
try:
47+
return await integration_service.get_integration_status(user_id, platform)
48+
except Exception as e:
49+
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(e)) from e
50+
51+
@router.get("/{integration_id}", response_model=IntegrationResponse)
52+
async def get_integration(
53+
integration_id: UUID,
54+
user_id: UUID = Depends(get_current_user)
55+
):
56+
"""Get a specific integration."""
57+
try:
58+
integration = await integration_service.get_integration(user_id, integration_id)
59+
60+
if not integration:
61+
raise HTTPException(
62+
status_code=status.HTTP_404_NOT_FOUND,
63+
detail="Integration not found"
64+
)
65+
66+
return integration
67+
except Exception as e:
68+
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(e)) from e
69+
70+
@router.put("/{integration_id}", response_model=IntegrationResponse)
71+
async def update_integration(
72+
integration_id: UUID,
73+
request: IntegrationUpdateRequest,
74+
user_id: UUID = Depends(get_current_user)
75+
):
76+
"""Update an existing integration."""
77+
try:
78+
return await integration_service.update_integration(user_id, integration_id, request)
79+
except NotFoundError as e:
80+
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(e)) from e
81+
except HTTPException:
82+
raise
83+
except Exception as e:
84+
raise HTTPException(
85+
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
86+
detail=f"Failed to update integration: {str(e)}"
87+
) from e
88+
89+
@router.delete("/{integration_id}", status_code=status.HTTP_204_NO_CONTENT)
90+
async def delete_integration(
91+
integration_id: UUID,
92+
user_id: UUID = Depends(get_current_user)
93+
):
94+
"""Delete an integration."""
95+
try:
96+
await integration_service.delete_integration(user_id, integration_id)
97+
except NotFoundError as e:
98+
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(e)) from e
99+
except HTTPException:
100+
raise
101+
except Exception as e:
102+
raise HTTPException(
103+
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
104+
detail=f"Failed to delete integration: {str(e)}"
105+
) from e

backend/app/core/dependencies.py

Lines changed: 62 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,69 @@
1-
from fastapi import Request
1+
from fastapi import Header, HTTPException, status, Request
2+
from uuid import UUID
3+
from app.database.supabase.client import get_supabase_client
4+
import logging
25
from typing import TYPE_CHECKING
36

47
if TYPE_CHECKING:
58
from main import DevRAIApplication
69

7-
async def get_app_instance(request: Request) -> "DevRAIApplication":
10+
logger = logging.getLogger(__name__)
11+
12+
13+
def get_app_instance(request: Request) -> "DevRAIApplication":
14+
"""Get the application instance from FastAPI app state."""
15+
return request.app.state.app_instance
16+
17+
18+
async def get_current_user(authorization: str = Header(None)) -> UUID:
819
"""
9-
Dependency to get the application instance from FastAPI's state.
10-
This avoids circular imports by using dependency injection.
20+
Get the current authenticated user from the Supabase JWT token.
21+
22+
Args:
23+
authorization: The Authorization header containing the Bearer token
24+
25+
Returns:
26+
UUID: The user's ID
27+
28+
Raises:
29+
HTTPException: If authentication fails
1130
"""
12-
return request.app.state.app_instance
31+
if not authorization:
32+
raise HTTPException(
33+
status_code=status.HTTP_401_UNAUTHORIZED,
34+
detail="Missing authorization header",
35+
headers={"WWW-Authenticate": "Bearer"},
36+
)
37+
38+
if not authorization.startswith("Bearer "):
39+
raise HTTPException(
40+
status_code=status.HTTP_401_UNAUTHORIZED,
41+
detail="Invalid authorization header format. Expected 'Bearer <token>'",
42+
headers={"WWW-Authenticate": "Bearer"},
43+
)
44+
45+
token = authorization.replace("Bearer ", "")
46+
47+
try:
48+
supabase = get_supabase_client()
49+
# Verify the token and get user
50+
user_response = supabase.auth.get_user(token)
51+
52+
if not user_response or not user_response.user:
53+
raise HTTPException(
54+
status_code=status.HTTP_401_UNAUTHORIZED,
55+
detail="Invalid or expired token",
56+
headers={"WWW-Authenticate": "Bearer"},
57+
)
58+
59+
return UUID(user_response.user.id)
60+
61+
except HTTPException:
62+
raise
63+
except Exception as e:
64+
logger.exception("Authentication error")
65+
raise HTTPException(
66+
status_code=status.HTTP_401_UNAUTHORIZED,
67+
detail="Authentication failed",
68+
headers={"WWW-Authenticate": "Bearer"},
69+
) from e

backend/app/models/database/supabase.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,29 @@ class ConversationContext(BaseModel):
187187

188188
created_at: datetime = Field(default_factory=datetime.now)
189189

190+
class OrganizationIntegration(BaseModel):
191+
"""
192+
Represents a registered organization (just metadata, no credentials).
193+
194+
Attributes:
195+
id (UUID): Unique identifier for the integration.
196+
user_id (UUID): User/Owner who registered this organization.
197+
platform (str): Platform name (github, discord, slack, discourse).
198+
organization_name (str): Name of the organization.
199+
is_active (bool): Whether the integration is active.
200+
created_at (datetime): Timestamp when registered.
201+
updated_at (datetime): Timestamp when last updated.
202+
config (dict): Platform-specific data (org link, guild_id, etc.).
203+
"""
204+
id: UUID
205+
user_id: UUID
206+
platform: str
207+
organization_name: str
208+
is_active: bool = True
209+
created_at: datetime
210+
updated_at: datetime
211+
config: Optional[dict] = None
212+
190213
class IndexedRepository(BaseModel):
191214
"""Model for FalkorDB indexed repositories"""
192215
id: UUID

backend/app/models/integration.py

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
from pydantic import BaseModel, Field
2+
from typing import Optional, Literal
3+
from datetime import datetime
4+
from uuid import UUID
5+
6+
7+
# Request Models
8+
class IntegrationCreateRequest(BaseModel):
9+
"""Request model for registering an organization."""
10+
platform: Literal["github", "discord", "slack", "discourse"]
11+
organization_name: str
12+
organization_link: Optional[str] = None # GitHub org URL, Discord server ID, etc.
13+
config: Optional[dict] = None # Platform-specific data (discord_guild_id, etc.)
14+
15+
16+
class IntegrationUpdateRequest(BaseModel):
17+
"""Request model for updating an integration."""
18+
organization_name: Optional[str] = None
19+
organization_link: Optional[str] = None
20+
is_active: Optional[bool] = None
21+
config: Optional[dict] = None
22+
23+
24+
# Response Models
25+
class IntegrationResponse(BaseModel):
26+
"""Response model for integration data."""
27+
id: UUID
28+
user_id: UUID
29+
platform: str
30+
organization_name: str
31+
is_active: bool
32+
created_at: datetime
33+
updated_at: datetime
34+
config: Optional[dict] = None
35+
# Note: We never return the actual token in responses
36+
37+
38+
class IntegrationListResponse(BaseModel):
39+
"""Response model for listing integrations."""
40+
integrations: list[IntegrationResponse]
41+
total: int
42+
43+
44+
class IntegrationStatusResponse(BaseModel):
45+
"""Response model for checking integration status."""
46+
platform: str
47+
is_connected: bool
48+
organization_name: Optional[str] = None
49+
last_updated: Optional[datetime] = None

0 commit comments

Comments
 (0)