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

refactor(install): Refactor GitHub App Installation Logic #581

Merged
merged 3 commits into from
Dec 10, 2024
Merged
Show file tree
Hide file tree
Changes from all 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
154 changes: 104 additions & 50 deletions server/core/dao/repositoryConfigDAO.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
from typing import List
from typing import Counter, List
from core.dao.BaseDAO import BaseDAO
from core.models.bot import RepoBindBotConfigVO
from core.models.repository import RepositoryConfig
from supabase.client import Client

from petercat_utils.db.client.supabase import get_client


Expand All @@ -26,65 +25,120 @@
else:
return False, {"message": "GithubRepoConfig creation failed"}
except Exception as e:
print("Error: ", e)
return False, {"message": "GithubRepoConfig creation failed"}
print(f"Error: {e}")
return False, {"message": f"GithubRepoConfig creation failed: {e}"}

Check warning on line 30 in server/core/dao/repositoryConfigDAO.py

View check run for this annotation

Codecov / codecov/patch

server/core/dao/repositoryConfigDAO.py#L28-L30

Added lines #L28 - L30 were not covered by tests
def create_batch(self, data_list: List[RepositoryConfig]):
try:
records_data = [data.model_dump(exclude=["id"]) for data in data_list]

Check warning on line 33 in server/core/dao/repositoryConfigDAO.py

View check run for this annotation

Codecov / codecov/patch

server/core/dao/repositoryConfigDAO.py#L33

Added line #L33 was not covered by tests
xingwanying marked this conversation as resolved.
Show resolved Hide resolved
repo_ids = [data.repo_id for data in data_list]

# 查询现有的 repo_id
response = (
self.client.table("github_repo_config")
.select("repo_id")
.in_("repo_id", repo_ids)

Check warning on line 40 in server/core/dao/repositoryConfigDAO.py

View check run for this annotation

Codecov / codecov/patch

server/core/dao/repositoryConfigDAO.py#L40

Added line #L40 was not covered by tests
.execute()
)
existing_repo_ids = (
{record["repo_id"] for record in response.data}
if response.data
else set()
)

Check warning on line 47 in server/core/dao/repositoryConfigDAO.py

View check run for this annotation

Codecov / codecov/patch

server/core/dao/repositoryConfigDAO.py#L46-L47

Added lines #L46 - L47 were not covered by tests

def query_by_owners(self, orgs: list[str]):
response = (
self.client.table("github_repo_config")
.select("*")
.filter("owner_id", "in", f"({','.join(map(str, orgs))})")
.execute()
)
# 筛选出未存在的记录
new_records_data = [
data
for data in records_data
if data["repo_id"] not in existing_repo_ids
]

Check warning on line 54 in server/core/dao/repositoryConfigDAO.py

View check run for this annotation

Codecov / codecov/patch

server/core/dao/repositoryConfigDAO.py#L53-L54

Added lines #L53 - L54 were not covered by tests

return response.data
if not new_records_data:
return False, {

Check warning on line 57 in server/core/dao/repositoryConfigDAO.py

View check run for this annotation

Codecov / codecov/patch

server/core/dao/repositoryConfigDAO.py#L57

Added line #L57 was not covered by tests
"message": "No new GithubRepoConfig records to insert, all repo_ids already exist"
}

def update_bot_to_repos(
self,
repos: List[RepoBindBotConfigVO],
) -> bool:
for repo in repos:
res = (
# 执行插入操作
repo_config_result = (
self.client.table("github_repo_config")
.update({"robot_id": repo.robot_id})
.match({"repo_id": repo.repo_id})
.insert(new_records_data)

Check warning on line 64 in server/core/dao/repositoryConfigDAO.py

View check run for this annotation

Codecov / codecov/patch

server/core/dao/repositoryConfigDAO.py#L64

Added line #L64 was not covered by tests
.execute()
)
if not res:
raise ValueError("Failed to bind the bot.")

def get_by_repo_name(self, repo_name: str):
response = (
self.client.table("github_repo_config")
.select("*")
.eq("repo_name", repo_name)
.execute()
)
return repo_config_result

Check warning on line 68 in server/core/dao/repositoryConfigDAO.py

View check run for this annotation

Codecov / codecov/patch

server/core/dao/repositoryConfigDAO.py#L68

Added line #L68 was not covered by tests
except Exception as e:
print(f"Error: {e}")
return False, {"message": f"GithubRepoConfig batch creation failed: {e}"}

Check warning on line 71 in server/core/dao/repositoryConfigDAO.py

View check run for this annotation

Codecov / codecov/patch

server/core/dao/repositoryConfigDAO.py#L71

Added line #L71 was not covered by tests

if not response.data or not response.data[0]:
def query_by_owners(self, orgs: List[str]):
try:
response = (
self.client.table("github_repo_config")
.select("*")
.filter("owner_id", "in", f"({','.join(map(str, orgs))})")
.execute()

Check warning on line 79 in server/core/dao/repositoryConfigDAO.py

View check run for this annotation

Codecov / codecov/patch

server/core/dao/repositoryConfigDAO.py#L77-L79

Added lines #L77 - L79 were not covered by tests
)
return response.data

Check warning on line 81 in server/core/dao/repositoryConfigDAO.py

View check run for this annotation

Codecov / codecov/patch

server/core/dao/repositoryConfigDAO.py#L81

Added line #L81 was not covered by tests
except Exception as e:
print(f"Error: {e}")
return None
repo_config = response.data[0]

return RepositoryConfig(**repo_config)
def update_bot_to_repos(self, repos: List[RepoBindBotConfigVO]) -> bool:
try:
for repo in repos:
res = (
self.client.table("github_repo_config")

Check warning on line 90 in server/core/dao/repositoryConfigDAO.py

View check run for this annotation

Codecov / codecov/patch

server/core/dao/repositoryConfigDAO.py#L90

Added line #L90 was not covered by tests
.update({"robot_id": repo.robot_id})
.match({"repo_id": repo.repo_id})
.execute()
)
if not res:
raise ValueError("Failed to bind the bot.")
return True
except Exception as e:
print(f"Error: {e}")
return False

def get_by_bot_id(self, bot_id: str):
response = (
self.client.table("github_repo_config")
.select("*")
.eq("robot_id", bot_id)
.execute()
)
if not response.data or not response.data[0]:
def get_by_repo_name(self, repo_name: str):
try:
response = (
self.client.table("github_repo_config")
.select("*")
.eq("repo_name", repo_name)
.execute()
)
if not response.data or not response.data[0]:
return None
repo_config = response.data[0]
return RepositoryConfig(**repo_config)
except Exception as e:
print(f"Error: {e}")
return None
repo_configs = [RepositoryConfig(**repo) for repo in response.data]

return repo_configs
def get_by_bot_id(self, bot_id: str):
try:
response = (
self.client.table("github_repo_config")
.select("*")
.eq("robot_id", bot_id)
.execute()
)
if not response.data or not response.data[0]:
return None
return [RepositoryConfig(**repo) for repo in response.data]
except Exception as e:
print(f"Error: {e}")
return None

def delete_by_repo_ids(self, repo_ids: list):
response = (
self.client.table("github_repo_config")
.delete()
.in_("repo_id", repo_ids)
.execute()
)
return response
def delete_by_repo_ids(self, repo_ids: List[str]):
try:
response = (
self.client.table("github_repo_config")
.delete()
.in_("repo_id", repo_ids)
.execute()
)
return response
except Exception as e:
print(f"Error: {e}")
return None
71 changes: 69 additions & 2 deletions server/event_handler/intsall.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
from typing import Any
from typing import Any, List
from github import Github, Auth
from github import GithubException
from core.dao.repositoryConfigDAO import RepositoryConfigDAO
from core.models.repository import RepositoryConfig
import time


class InstallEventHandler:
class InstallationEventHandler:
event: Any
auth: Auth.AppAuth
g: Github
Expand All @@ -20,12 +22,77 @@
repository_config = RepositoryConfigDAO()
repository_config.delete_by_repo_ids(repo_ids)

def add_config(self):
repositories = self.event["repositories"]
owner_id = self.event["installation"]["account"]["id"]
repository_config_dao = RepositoryConfigDAO()
repository_configs: List[RepositoryConfig] = []
for repo in repositories:
repository_config = RepositoryConfig(

Check warning on line 31 in server/event_handler/intsall.py

View check run for this annotation

Codecov / codecov/patch

server/event_handler/intsall.py#L25-L31

Added lines #L25 - L31 were not covered by tests
owner_id=str(owner_id),
repo_name=repo["full_name"],
repo_id=str(repo["id"]),
robot_id="",
created_at=int(time.time()),
)
repository_configs.append(repository_config)
repository_config_dao.create_batch(repository_configs)

async def execute(self):
try:
action = self.event["action"]
if action == "deleted":
self.delete_config()
return {"success": True}
if action == "created":
self.add_config()
return {"success": True}
except GithubException as e:
print(f"处理 GitHub 请求时出错:{e}")
return {"success": False, "error": str(e)}


class InstallationEditEventHandler:
event: Any
auth: Auth.AppAuth
g: Github

def __init__(self, payload: Any, auth: Auth.AppAuth, installation_id: int) -> None:
self.event: Any = payload
self.auth: Auth.AppAuth = auth
self.g: Github = Github(auth=auth)

def delete_config(self):
repositories = self.event["repositories_removed"]
xingwanying marked this conversation as resolved.
Show resolved Hide resolved
repo_ids = [str(repo["id"]) for repo in repositories]
repository_config = RepositoryConfigDAO()
repository_config.delete_by_repo_ids(repo_ids)

def add_config(self):
repositories = self.event["repositories_added"]
owner_id = self.event["installation"]["account"]["id"]
repository_config_dao = RepositoryConfigDAO()
repository_configs: List[RepositoryConfig] = []
for repo in repositories:
repository_config = RepositoryConfig(
owner_id=str(owner_id),
repo_name=repo["full_name"],
repo_id=str(repo["id"]),
robot_id="",
created_at=int(time.time()),
)
repository_configs.append(repository_config)
repository_config_dao.create_batch(repository_configs)

async def execute(self):
try:
action = self.event["action"]
if action == "removed":
self.delete_config()
return {"success": True}
if action == "added":
self.add_config()
return {"success": True}
except GithubException as e:
print(f"处理 GitHub 请求时出错:{e}")
return {"success": False, "error": str(e)}
8 changes: 5 additions & 3 deletions server/github_app/handlers.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from typing import Union

from event_handler.intsall import InstallEventHandler
from event_handler.intsall import InstallationEventHandler, InstallationEditEventHandler
from petercat_utils import get_env_variable
from github import Auth

Expand All @@ -26,7 +26,8 @@
DiscussionEventHandler,
DiscussionCommentEventHandler,
PullRequestReviewCommentEventHandler,
InstallEventHandler,
InstallationEventHandler,
InstallationEditEventHandler,
None,
]:
handlers = {
Expand All @@ -37,7 +38,8 @@
"discussion_comment": DiscussionCommentEventHandler,
"pull_request_review_comment": PullRequestReviewCommentEventHandler,
"pull_request_review": PullRequestReviewCommentEventHandler,
"installation": InstallEventHandler,
"installation": InstallationEventHandler,
"installation_repositories": InstallationEditEventHandler,

Check warning on line 42 in server/github_app/handlers.py

View check run for this annotation

Codecov / codecov/patch

server/github_app/handlers.py#L42

Added line #L42 was not covered by tests
}
return (
handlers.get(event)(payload=payload, auth=auth, installation_id=installation_id)
Expand Down
46 changes: 4 additions & 42 deletions server/github_app/router.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,48 +50,10 @@
# https://github.com/login/oauth/authorize?client_id=Iv1.c2e88b429e541264
@router.get("/app/installation/callback")
def github_app_callback(code: str, installation_id: str, setup_action: str):
authorization_dao = AuthorizationDAO()
repository_config_dao = RepositoryConfigDAO()
if setup_action == "install":
if authorization_dao.exists(installation_id=installation_id):
message = (f"Installation_id {installation_id} Exists",)
return RedirectResponse(
url=f"{WEB_URL}/github/installed/{message}", status_code=302
)
else:
jwt = get_jwt()
access_token = get_app_installations_access_token(
installation_id=installation_id, jwt=jwt
)
print(f"get_app_installations_access_token: {access_token}")
authorization = Authorization(
**access_token,
code=code,
installation_id=installation_id,
created_at=int(time.time()),
)
success, message = authorization_dao.create(authorization)
installed_repositories = get_installation_repositories(
access_token=access_token["token"]
)
for repo in installed_repositories["repositories"]:
repository_config = RepositoryConfig(
owner_id=str(repo["owner"]["id"]),
repo_name=repo["full_name"],
repo_id=str(repo["id"]),
robot_id="",
created_at=int(time.time()),
)
repository_config_dao.create(repository_config)

return RedirectResponse(
url=f"{WEB_URL}/github/installed?message={message}", status_code=302
)
# ignore others setup_action,such as deleted our app
return {
"success": False,
"message": f"Invalid setup_action value {setup_action},please delete the app first then re-install the app.",
}
return RedirectResponse(
url=f"{WEB_URL}/github/installed?installation_id={installation_id}&setup_action={setup_action}&code={code}",
status_code=302,
)

Check warning on line 56 in server/github_app/router.py

View check run for this annotation

Codecov / codecov/patch

server/github_app/router.py#L53-L56

Added lines #L53 - L56 were not covered by tests


@router.post("/app/webhook")
Expand Down
Loading