Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Project routes and Github Route #13

Merged
merged 10 commits into from
Aug 28, 2024
21 changes: 19 additions & 2 deletions .env.template
Original file line number Diff line number Diff line change
@@ -1,5 +1,22 @@
ENV= development
OPENAI_API_KEY=
OPENAI_MODEL_REASONING=
OPENAI_MODEL_REASONING=
# POSTGRES_SERVER=postgresql://postgres:mysecretpassword@host.docker.internal:5432/momentum Use this when using WSL
POSTGRES_SERVER=postgresql://postgres:mysecretpassword@localhost:5432/momentum
defaultUsername= defaultuser
MONGO_URI="mongodb://127.0.0.1:27017"
MONGODB_DB_NAME="momentum"
NEO4J_URI="bolt://127.0.0.1:7687"
NEO4J_USERNAME=neo4j
NEO4J_PASSWORD=mysecretpassword
REDISHOST=127.0.0.1
REDISPORT=6379
BROKER_URL="redis://127.0.0.1:6379/0"
WEAVIATE_ENDPOINT="http://127.0.0.1:8082"
WEAVIATE_API_KEY=
CELERY_QUEUE_NAME=dev
PORTKEY_API_KEY=
GITHUB_PRIVATE_KEY=
GITHUB_APP_ID=
GCP_PROJECT=
defaultUsername= defaultuser
FIREBASE_SERVICE_ACCOUNT=
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
*__pycache__
.venv
firebase_service_account.json
firebase_service_account.json:Zone.Identifier
cli/dist
cli/.venv
venv/
Expand Down
41 changes: 30 additions & 11 deletions app/core/mongo_manager.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import logging
import os
from typing import Optional

from mongoengine import connect, connection
import certifi
from pymongo import MongoClient
from pymongo.errors import ConnectionFailure, OperationFailure
Expand All @@ -23,19 +23,38 @@ def __init__(self):
raise RuntimeError("Use get_instance() to get the MongoManager instance")
self._connect()

def get_mongo_connection(self):
try:
db_name = os.getenv("MONGODB_DB_NAME", "test")
mongo_uri = os.getenv("MONGO_URI", "mongodb://localhost:27017")
env = os.getenv("ENV", "development") # Assume development if ENV is not set

if not mongo_uri:
raise ValueError("MONGO_URI environment variable is not set")

if not db_name:
raise ValueError("MONGODB_DB_NAME environment variable is not set")

# Establish the connection based on the environment
if env in ["production", "staging"]:
connect(db_name, host=mongo_uri, tlsCAFile=certifi.where())
else:
connect(db_name, host=mongo_uri)

# Return the established connection
db_connection = connection.get_connection()
db_connection.server_info() # This will raise an exception if the connection is not valid
return db_connection

except (ConnectionFailure, ValueError) as e:
logging.error(f"Failed to connect to MongoDB: {str(e)}")
raise

def _connect(self):
if self._client is None:
try:
mongodb_uri = os.environ.get("MONGO_URI")
if not mongodb_uri:
raise ValueError("MONGO_URI environment variable is not set")

self._client = MongoClient(
GodReaper marked this conversation as resolved.
Show resolved Hide resolved
mongodb_uri,
maxPoolSize=50,
waitQueueTimeoutMS=2500,
tlsCAFile=certifi.where(), # Use the certifi package to locate the CA bundle
)
# Establish connection using the utility function
self._client = self.get_mongo_connection()

db_name = os.environ.get("MONGODB_DB_NAME")
if not db_name:
Expand Down
5 changes: 5 additions & 0 deletions app/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
)
from app.modules.search.search_router import router as search_router
from app.modules.users.user_router import router as user_router
from app.modules.projects.projects_router import router as projects_router
from app.modules.github.github_router import router as github_router
from app.modules.utils.dummy_setup import DummyDataSetup
from app.modules.utils.firebase_setup import FirebaseSetup

Expand Down Expand Up @@ -89,7 +91,10 @@ def include_routers(self):
self.app.include_router(
secret_manager_router, prefix="/api/v1", tags=["Secret Manager"]
)

self.app.include_router(projects_router, prefix="/api/v1", tags=["Projects"])
self.app.include_router(search_router, prefix="/api/v1/search", tags=["search"])
self.app.include_router(github_router, prefix="/api/v1", tags=["Github"])

def add_health_check(self):
@self.app.get("/health", tags=["Health"])
Expand Down
52 changes: 52 additions & 0 deletions app/modules/github/github_router.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
from fastapi import Depends, HTTPException, Query
from app.modules.auth.auth_service import AuthService
from app.modules.github.github_service import GithubService
from app.modules.utils.APIRouter import APIRouter

router = APIRouter()

github_client = None

class GithubAPI:
@router.get("/github/user-repos")
def get_user_repos(user=Depends(AuthService.check_auth)):
try:
repos = GithubService.get_repos_for_user()
repo_list = [
{
"id": repo["id"],
"name": repo["name"],
"full_name": repo["full_name"],
"private": repo["private"],
"url": repo["html_url"],
"owner": repo["owner"]["login"],
}
for repo in repos
]
return {"repositories": repo_list}
except Exception as e:
raise HTTPException(
status_code=500,
detail=f"Failed to fetch repositories: {str(e)}"
)
@router.get("/github/get-branch-list")
def get_branch_list(
repo_name: str = Query(..., description="Repository name"),
user=Depends(AuthService.check_auth)
):
global github_client
if github_client is None:

github_client, _, _, _ = GithubService.get_github_repo_details(repo_name)
try:
repo = github_client.get_repo(repo_name)
branches = repo.get_branches()
branch_list = [branch.name for branch in branches]
except Exception as e:
raise HTTPException(
status_code=404,
detail=(
f"Repository not found or error fetching branches: {str(e)}"
),
)
return {"branches": branch_list}
46 changes: 44 additions & 2 deletions app/modules/github/github_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ def __init__(self, db: Session):
self.project_manager = ProjectService(db)

@staticmethod
def get_github_repo_details(repo_name):
def get_github_repo_details(repo_name: str):
private_key = (
"-----BEGIN RSA PRIVATE KEY-----\n"
+ config_provider.get_github_key()
Expand All @@ -38,8 +38,14 @@ def get_github_repo_details(repo_name):
"Authorization": f"Bearer {jwt}",
"X-GitHub-Api-Version": "2022-11-28",
}
response = requests.get(url, headers=headers)
if response.status_code != 200:
raise HTTPException(status_code=400, detail="Failed to get installation ID")
app_auth = auth.get_installation_auth(response.json()["id"])

github = Github(auth=app_auth)

return requests.get(url, headers=headers), auth, owner
return github, response, auth, owner

@staticmethod
def check_is_commit_added(repo_details, project_details, branch_name):
Expand Down Expand Up @@ -92,3 +98,39 @@ def fetch_method_from_repo(node, db):
logger.error(f"An error occurred: {e}", exc_info=True)

return method_content

@staticmethod
def get_repos_for_user():
private_key = (
"-----BEGIN RSA PRIVATE KEY-----\n"
+ config_provider.get_github_key()
+ "\n-----END RSA PRIVATE KEY-----\n"
)
app_id = os.environ["GITHUB_APP_ID"]
auth = AppAuth(app_id=app_id, private_key=private_key)
jwt = auth.create_jwt()

url = f"https://api.github.com/app/installations"
headers = {
"Accept": "application/vnd.github+json",
"Authorization": f"Bearer {jwt}",
"X-GitHub-Api-Version": "2022-11-28",
}
response = requests.get(url, headers=headers)
if response.status_code != 200:
raise HTTPException(status_code=400, detail="Failed to get installations")

installations = response.json()
repos = []

for installation in installations:
app_auth = auth.get_installation_auth(installation["id"])
github = Github(auth=app_auth)
repos_url = installation["repositories_url"]
repos_response = requests.get(repos_url, headers={"Authorization": f"Bearer {app_auth.token}"})
if repos_response.status_code == 200:
repos.extend(repos_response.json().get('repositories', []))
else:
logger.error(f"Failed to fetch repositories for installation ID {installation['id']}")

return repos
49 changes: 49 additions & 0 deletions app/modules/projects/projects_router.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
from fastapi import Depends, HTTPException
from starlette.responses import JSONResponse
from app.core.database import get_db
from app.modules.auth.auth_service import AuthService
from app.modules.projects.projects_service import ProjectService
from app.modules.utils.APIRouter import APIRouter

router = APIRouter()

class ProjectAPI:
@router.get("/projects/list")
async def get_project_list(
user=Depends(AuthService.check_auth), db=Depends(get_db)
):
user_id = user["user_id"]
project_service = ProjectService(db)
try:
branch_list = []
project_details = await project_service.get_parsed_project_branches( user_id )
branch_list.extend(
{
"project_id": branch[0],
"branch_name": branch[1],
"repo_name": branch[2],
"last_updated_at": branch[3],
"is_default": branch[4],
"project_status": branch[5],
}
for branch in project_details
)
return branch_list
except Exception as e:
raise HTTPException(status_code=400, detail=f"{str(e)}")

@router.delete("/projects")
def delete_project(project_id: int, user=Depends(AuthService.check_auth), db=Depends(get_db)):

project_service = ProjectService(db)
try:
project_service.delete_project(project_id)
return JSONResponse(
status_code=200,
content={
"message": "Project deleted successfully.",
"id": project_id
}
)
except Exception as e:
raise HTTPException(status_code=500, detail=f"{str(e)}")
7 changes: 6 additions & 1 deletion app/modules/projects/projects_schema.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from enum import Enum

from typing import Optional
from pydantic import BaseModel, Field

class ProjectStatusEnum(str, Enum):
SUBMITTED = "submitted"
Expand All @@ -8,3 +9,7 @@ class ProjectStatusEnum(str, Enum):
PROCESSING = "processing"
READY = "ready"
ERROR = "error"


class RepoDetails(BaseModel):
repo_name: str
11 changes: 8 additions & 3 deletions app/modules/projects/projects_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from sqlalchemy import and_
from sqlalchemy.exc import SQLAlchemyError
from sqlalchemy.orm import Session
from sqlalchemy import cast, String

from app.modules.projects.projects_model import Project
from app.modules.projects.projects_schema import ProjectStatusEnum
Expand Down Expand Up @@ -312,9 +313,13 @@ def update_project(db: Session, project_id: int, **kwargs):

return None

def delete_project(db: Session, project_id: int):
db.query(Project).filter(Project.id == project_id).delete()
db.commit()
def delete_project(self, project_id: int):
project = self.db.query(Project).filter(cast(Project.id, String) == str(project_id)).first()
if not project:
raise HTTPException(status_code=404, detail="Project not found.")
self.db.delete(project)
self.db.commit()


def get_projects_by_repo_name(
db: Session, repo_name: str, user_id: str, is_deleted: bool = False
Expand Down
11 changes: 11 additions & 0 deletions docker-compose.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,17 @@ services:
networks:
- app-network

mongodb:
image: mongo:latest
container_name: mongodb
restart: always
environment:
- MONGO_INITDB_DATABASE=momentum
ports:
- "27017:27017"
networks:
- app-network

redis:
image: redis:latest
container_name: redis_broker
Expand Down